[
  {
    "path": ".dockerignore",
    "content": ".git/\n.gitignore\n.idea/\n.mypy_cache/\n.pytest_cache/\n.tox/\n.venv/\n.vscode/\n*.egg-info/\n*.log\n*.py[cod]\n__pycache__/\nbuild/\ndist/\nhelp_docs/\nnode_modules/\nsite/\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: mdmintz\n"
  },
  {
    "path": ".github/Workflows.md",
    "content": "### <img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> SeleniumBase Workflows\n\n> **Table of Contents / Navigation:**\n> - [**CI build**](workflows/python-package.yml)\n"
  },
  {
    "path": ".github/workflows/pages.yml",
    "content": "# Build and deploy a Jekyll site to GitHub Pages\nname: Deploy docs to GitHub Pages\n\non:\n  # Runs on pushes targeting the default branch\n  push:\n    branches: [\"master\"]\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\n\njobs:\n  # Build job\n  build:\n    if: github.repository == 'seleniumbase/SeleniumBase'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n      - name: Build with Jekyll\n        uses: actions/jekyll-build-pages@v1\n        with:\n          source: ./\n          destination: ./_site\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v4\n\n  # Deployment job\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    if: github.repository == 'seleniumbase/SeleniumBase'\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/python-nightly-mac.yml",
    "content": "name: Nightly Tests (macOS)\n\non:\n  schedule:\n    - cron: \"40 1 * * *\"\n  workflow_dispatch:\n    branches:\n\njobs:\n  build:\n\n    env:\n      PY_COLORS: \"1\"\n    runs-on: macos-latest\n    strategy:\n      fail-fast: false\n      max-parallel: 6\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install --upgrade wheel\n        pip install -r requirements.txt\n    - name: Install SeleniumBase\n      run: |\n        pip install .\n    - name: Check the console scripts interface\n      run: |\n        seleniumbase\n        sbase\n    - name: Install chromedriver\n      run: |\n        seleniumbase install chromedriver\n    - name: Make sure pytest is working\n      run: |\n        echo \"def test_1(): pass\" > nothing.py\n        pytest nothing.py\n    - name: Make sure nosetests is working\n      run: |\n        echo \"def test_2(): pass\" > nothing2.py\n        nosetests nothing2.py\n    - name: Run pytest examples/unit_tests/verify_framework.py --browser=chrome --headless\n      run: |\n        pytest examples/unit_tests/verify_framework.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/offline_examples --browser=chrome --headless --rs\n      run: |\n        pytest examples/offline_examples --rs --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/boilerplate_test.py --browser=chrome --headless\n      run: |\n        pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/test_window_switching.py --browser=chrome --headless\n      run: |\n        pytest examples/test_window_switching.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Verify seleniumbase install from PyPI\n      run: |\n        pip install seleniumbase -U --no-deps --force-reinstall --no-cache-dir\n"
  },
  {
    "path": ".github/workflows/python-nightly-ubuntu.yml",
    "content": "name: Nightly Tests (Ubuntu)\n\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n  workflow_dispatch:\n    branches:\n\njobs:\n  build:\n\n    env:\n      PY_COLORS: \"1\"\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      max-parallel: 6\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install --upgrade wheel\n        pip install -r requirements.txt\n    - name: Install SeleniumBase\n      run: |\n        pip install .\n    - name: Check the console scripts interface\n      run: |\n        seleniumbase\n        sbase\n    - name: Install chromedriver\n      run: |\n        seleniumbase install chromedriver\n    - name: Make sure pytest is working\n      run: |\n        echo \"def test_1(): pass\" > nothing.py\n        pytest nothing.py\n    - name: Make sure nosetests is working\n      run: |\n        echo \"def test_2(): pass\" > nothing2.py\n        nosetests nothing2.py\n    - name: Run pytest examples/unit_tests/verify_framework.py --browser=chrome --headless\n      run: |\n        pytest examples/unit_tests/verify_framework.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/offline_examples --browser=chrome --headless --rs\n      run: |\n        pytest examples/offline_examples --rs --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/boilerplate_test.py --browser=chrome --headless\n      run: |\n        pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/test_window_switching.py --browser=chrome --headless\n      run: |\n        pytest examples/test_window_switching.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Verify seleniumbase install from PyPI\n      run: |\n        pip install seleniumbase -U --no-deps --force-reinstall --no-cache-dir\n"
  },
  {
    "path": ".github/workflows/python-nightly-windows.yml",
    "content": "name: Nightly Tests (Windows)\n\non:\n  schedule:\n    - cron: \"50 1 * * *\"\n  workflow_dispatch:\n    branches:\n\njobs:\n  build:\n\n    env:\n      PY_COLORS: \"1\"\n    runs-on: windows-latest\n    strategy:\n      fail-fast: false\n      max-parallel: 6\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install --upgrade wheel\n        pip install -r requirements.txt\n    - name: Install SeleniumBase\n      run: |\n        pip install .\n    - name: Check the console scripts interface\n      run: |\n        seleniumbase\n        sbase\n    - name: Install chromedriver\n      run: |\n        seleniumbase install chromedriver\n    - name: Get chrome-headless-shell\n      run: |\n        sbase get chs\n    - name: Make sure pytest is working\n      run: |\n        echo \"def test_1(): pass\" > nothing.py\n        pytest nothing.py\n    - name: Make sure nosetests is working\n      run: |\n        echo \"def test_2(): pass\" > nothing2.py\n        nosetests nothing2.py\n    - name: Run pytest examples/unit_tests/verify_framework.py --browser=chrome --headless\n      run: |\n        pytest examples/unit_tests/verify_framework.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/offline_examples --browser=chrome --headless --rs\n      run: |\n        pytest examples/offline_examples --rs --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/boilerplate_test.py --browser=chrome --headless\n      run: |\n        pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/test_window_switching.py --browser=chrome --headless\n      run: |\n        pytest examples/test_window_switching.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Verify seleniumbase install from PyPI\n      run: |\n        pip install seleniumbase -U --no-deps --force-reinstall --no-cache-dir\n"
  },
  {
    "path": ".github/workflows/python-package.yml",
    "content": "name: CI build\n\non:\n  pull_request:\n    branches:\n  push:\n    branches:\n      - master\n  workflow_dispatch:\n    branches:\n\njobs:\n  build:\n\n    env:\n      PY_COLORS: \"1\"\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      max-parallel: 6\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip wheel setuptools\n        pip install -r requirements.txt\n    - name: Install SeleniumBase\n      run: |\n        pip install .\n    - name: Lint with flake8\n      run: |\n        pip install flake8\n        # Stop the build if there are flake8 issues\n        flake8 . --count --show-source --statistics --exclude=temp\n    - name: Install Chrome\n      run: |\n        sudo apt install google-chrome-stable\n    - name: Check the console scripts interface\n      run: |\n        seleniumbase\n        sbase\n    - name: Install chromedriver\n      run: |\n        seleniumbase install chromedriver\n    - name: Make sure pytest is working\n      run: |\n        pytest --help\n        echo \"def test_1(): pass\" > nothing.py\n        pytest nothing.py\n    - name: Make sure nosetests is working\n      run: |\n        echo \"def test_2(): pass\" > nothing2.py\n        nosetests nothing2.py\n    - name: Run pytest examples/unit_tests/verify_framework.py --browser=chrome --headless\n      run: |\n        pytest examples/unit_tests/verify_framework.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/boilerplate_test.py --browser=chrome --headless\n      run: |\n        pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/test_demo_site.py --browser=chrome --xvfb\n      run: |\n        pytest examples/test_demo_site.py --browser=chrome --xvfb -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/iframe_tests.py --browser=chrome --xvfb --rs --crumbs\n      run: |\n        pytest examples/iframe_tests.py --browser=chrome --xvfb --rs --crumbs -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/test_mfa_login.py --browser=chrome --xvfb\n      run: |\n        pytest examples/test_mfa_login.py --browser=chrome --xvfb -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/iframe_tests.py --browser=chrome --xvfb --rs\n      run: |\n        pytest examples/iframe_tests.py --browser=chrome --xvfb --rs -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/test_window_switching.py --browser=chrome --headless\n      run: |\n        pytest examples/test_window_switching.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/my_first_test.py --browser=chrome --headless\n      run: |\n        pytest examples/my_first_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run pytest examples/test_inspect_html.py --browser=chrome --headless\n      run: |\n        pytest examples/test_inspect_html.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    - name: Run behave examples/behave_bdd/features/calculator.feature -D rs -D crumbs -D xvfb\n      run: |\n        behave examples/behave_bdd/features/calculator.feature -D rs -D crumbs -D xvfb -T -k\n    - name: Run behave examples/behave_bdd/features/realworld.feature -D rs -D crumbs -D xvfb\n      run: |\n        behave examples/behave_bdd/features/realworld.feature -D rs -D crumbs -D xvfb -T -k\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Python Bytecode\n*.py[cod]\n\n# Packages\n*.egg\n*.egg-info\n.eggs\neggs\ndevelop-eggs\nbin\nbuild\ndist\nlib\nlib64\nparts\nsdist\nvar\n.installed.cfg\n__pycache__\n\n# Python3 pyvenv\n.env\n.venv\nenv/\nvenv/\nENV/\nVENV/\nenv.bak/\nvenv.bak/\n.sbase\n.sbase*\nseleniumbase_env\nseleniumbase_venv\nsbase_env\nsbase_venv\npyvenv.cfg\n.Python\ninclude\npip-delete-this-directory.txt\npip-selfcheck.json\nipython.1.gz\nnosetests.1\n.noseids\n\n# Installer logs\npip-log.txt\n.swp\n\n# Unit test / coverage reports\n.coverage\n.tox\ncoverage.xml\nnosetests.xml\n\n# py.test\n.cache/*\n.pytest_cache/*\n.pytest_config\n\n# Azure Pipelines\njunit\ntest-results.xml\n\n# Developer\n.idea\n.project\n.pydevproject\n.vscode\n\n# Web Drivers\nchromedriver\ngeckodriver\nmsedgedriver\noperadriver\nuc_driver\nMicrosoftWebDriver.exe\nheadless_ie_selenium.exe\nIEDriverServer.exe\nchromedriver.exe\ngeckodriver.exe\nmsedgedriver.exe\noperadriver.exe\nuc_driver.exe\n\n# Chromium Zip Files\nchrome-mac.zip\nchrome-linux.zip\nchrome-win.zip\n\n# Chromium folders\nchrome-mac\nchrome-linux\nchrome-win\n\n# Chrome for Testing Zip Files\nchrome-mac-arm64.zip\nchrome-mac-x64.zip\nchrome-linux64.zip\nchrome-win64.zip\nchrome-win32.zip\n\n# Chrome for Testing folders\nchrome-mac-arm64\nchrome-mac-x64\nchrome-linux64\nchrome-win64\nchrome-win32\n\n# Chrome-Headless-Shell Zip Files\nchrome-headless-shell-mac-arm64.zip\nchrome-headless-shell-mac-x64.zip\nchrome-headless-shell-linux64.zip\nchrome-headless-shell-win64.zip\nchrome-headless-shell-win32.zip\n\n# Chrome-Headless-Shell folders\nchrome-headless-shell-mac-arm64\nchrome-headless-shell-mac-x64\nchrome-headless-shell-linux64\nchrome-headless-shell-win64\nchrome-headless-shell-win32\n\n# msedgedriver requirements\nlibc++.dylib\n\n# Logs\nlogs\nlatest_logs\nlog_archives\narchived_logs\ngeckodriver.log\nghostdriver.log\npytestdebug.log\n\n# Reports\nreports/*.xml\nlatest_report\nreport_archives\narchived_reports\nhtml_report.html\nlast_report.html\nreport.html\nreport.xml\n\n# Dashboard\ndashboard.html\ndashboard.json\ndash_pie.json\ndashboard.lock\n\n# Allure Reports / Results\nallure_report\nallure-report\nallure_results\nallure-results\n\n# Charts\nsaved_charts\n\n# Presentations\nsaved_presentations\n\n# Tours\ntours_exported\n\n# Images\nimages_exported\n\n# Cookies\nsaved_cookies\n\n# Recordings\nrecordings\n\n# Automated Visual Testing\nvisual_baseline\n\n# MkDocs WebSite Generator\nsite/*\nmkdocs_build/*.md\nmkdocs_build/*/*.md\nmkdocs_build/*/*/*.md\nmkdocs_build/*/*/*/*.md\n\n# macOS system files\n.DS_Store\n\n# Other\nselenium-server-standalone.jar\nproxy.zip\nproxy.lock\nverbose_hub_server.dat\nverbose_node_server.dat\nip_of_grid_hub.dat\ndownloaded_files\narchived_files\nassets\ntemp\ntemp_*/\nnode_modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> CHANGELOG</h2>\n\n## See: [SeleniumBase/releases](https://github.com/seleniumbase/SeleniumBase/releases) 🗂️ 📋\n\n### (For CDP Mode, see the [CDP Mode docs](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md))\n\n### (For Stealthy Playwright, see the [Stealthy Playwright docs](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/playwright/ReadMe.md))\n"
  },
  {
    "path": "CNAME",
    "content": "seleniumbase.dev"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of conduct\n\n(SeleniumBase uses a modified version of [Flutter's Code of conduct](https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md))\n\nThe SeleniumBase project expects SeleniumBase's contributors to act professionally and respectfully. SeleniumBase contributors are expected to maintain the safety and dignity of SeleniumBase's social environments (such as GitHub and Gitter).\n\nSpecifically:\n\n* Respect people, their identities, their culture, and their work.\n* Be kind. Be courteous. Be welcoming.\n* Listen. Consider and acknowledge people's points before responding.\n\nShould you experience anything that makes you feel unwelcome in SeleniumBase's community, please [contact us](https://gitter.im/seleniumbase/SeleniumBase).\n\nThe SeleniumBase project will not tolerate harassment in SeleniumBase's community, even outside of SeleniumBase's public communication channels.\n\n## Questions\n\nIt's always OK to ask questions. Seleniumbase is a big project, and we don't expect everyone to know everything about everything.\n\n![\"I try not to make fun of people for admitting they don't know things, because for each thing 'everyone knows' by the time they're adults, every day there are, on average, 10,000 people in the US hearing about it for the first time. If I make fun of people, I train them not to tell me when they have those moments. And I miss out on the fun.\" \"Diet coke and mentos thing? What's that?\" \"Oh, man! We're going to the grocery store.\" \"Why?\" \"You're one of today's lucky 10,000.\"](https://imgs.xkcd.com/comics/ten_thousand.png)\n\nSource: _[xkcd, May 2012](https://xkcd.com/1053/)_\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to SeleniumBase\n\nThe SeleniumBase project welcomes meaningful contributions.\n\nThere are many ways to help:\n\n## Bug Reports\n\nWhen opening a new issue or commenting on an existing issue, please make sure to provide concise, detailed instructions on how to reproduce the issue. If the issue can't be reproduced, it will be closed. Clearly describe the results you're seeing, and the results you're expecting.\n\n## Feature Requests\n\nIf you find that SeleniumBase is missing something, feel free to open an issue with details describing what feature(s) you'd like added or changed.  \n\n## Documentation\n\nSeleniumBase is a big software project, and documentation is key to\nunderstanding how it works and how to use it properly. If you feel that important documentation is missing, please let us know, or submit a pull request.\n\n## Code Contributions\n\nThe SeleniumBase project welcomes meaningful contributions. Given the complexity of the project, it may be easier to open an issue for a change you want made than to try implementing the change yourself.\n\nRecently, we've been moving toward the policy discussed in https://github.com/readme/featured/how-open-is-open-source, which is: \"Open-Source, not Open-Contribution\". As the article states, even small contributions (via pull requests) typically require hours of time to properly test and validate. We're still grateful for community involvement and when folks report bugs or suggest features. With some special exceptions, this project is mainly closed to contribution (via pull requests).\n\n## (A Note on Style Guide Rules)\n\n[flake8](https://github.com/PyCQA/flake8) is the law of the land. The only flake8 rule ignored is [W503](https://github.com/grantmcconnaughey/Flake8Rules/blob/master/_rules/W503.md). For more details on why W503 should be ignored, see [this explanation](https://peps.python.org/pep-0008/#should-a-line-break-before-or-after-a-binary-operator), or [this shorter explanation](https://github.com/PyCQA/flake8/issues/494) by Python expert [Anthony Sottile](https://github.com/asottile).\n\n--------\n\nFor questions about this document, reach out to [Michael Mintz](https://github.com/mdmintz).\n"
  },
  {
    "path": "Dockerfile",
    "content": "# SeleniumBase Docker Image\nFROM ubuntu:24.04\nSHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\nENV PYTHONUNBUFFERED=1\nENV PYTHONIOENCODING=UTF-8\nENV DEBIAN_FRONTEND=noninteractive\n\n#======================\n# Locale Configuration\n#======================\nRUN apt-get update\nRUN apt-get install -y --no-install-recommends tzdata locales\nRUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen\nENV TZ=America/New_York\nRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\nENV LANG=en_US.UTF-8\nENV LANGUAGE=en_US:en\nENV LC_ALL=en_US.UTF-8\nRUN echo \"LC_ALL=en_US.UTF-8\" >> /etc/environment\nRUN echo \"en_US.UTF-8 UTF-8\" >> /etc/locale.gen\nRUN echo \"LANG=en_US.UTF-8\" > /etc/locale.conf\nRUN locale-gen en_US.UTF-8\n\n#===========================\n# Fingerprint Configuration\n#===========================\nRUN apt-get update\nRUN apt install -y fonts-liberation fonts-noto-color-emoji libvulkan1 libnss3 libatk-bridge2.0-0 libcups2 libxcomposite1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2\nRUN apt install -y fonts-freefont-ttf fonts-dejavu-core fonts-ubuntu fonts-roboto fonts-droid-fallback\n\n#======================\n# Install Common Fonts\n#======================\nRUN apt-get update\nRUN apt-get install -y \\\n    fonts-liberation2 \\\n    fonts-font-awesome \\\n    fonts-terminus \\\n    fonts-powerline \\\n    fonts-open-sans \\\n    fonts-mononoki \\\n    fonts-lato\n\n#============================\n# Install Linux Dependencies\n#============================\nRUN apt-get update\nRUN apt-get install -y \\\n    dbus-x11 \\\n    libatk1.0-0 \\\n    libatspi2.0-0 \\\n    libdbus-1-3 \\\n    libdrm2 \\\n    libgtk-3-0 \\\n    libnspr4 \\\n    libasound2t64 \\\n    libu2f-udev \\\n    libwayland-client0 \\\n    libx11-6 \\\n    libx11-xcb1 \\\n    libxdamage1 \\\n    libxfixes3 \\\n    libxkbcommon0\n\n#==========================\n# Install useful utilities\n#==========================\nRUN apt-get update\nRUN apt-get install -y xdg-utils ca-certificates x11vnc\n\n#=================================\n# Install Bash Command Line Tools\n#=================================\nRUN apt-get update\nRUN apt-get -qy --no-install-recommends install \\\n    curl \\\n    sudo \\\n    unzip \\\n    vim \\\n    wget \\\n    xvfb\n\n#================\n# Install Chrome\n#================\nRUN apt-get update\nRUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb\nRUN apt-get install -y ./google-chrome-stable_current_amd64.deb\nRUN rm ./google-chrome-stable_current_amd64.deb\n\n#================\n# Install Python\n#================\nRUN apt-get update && apt-get install -y software-properties-common\nRUN add-apt-repository ppa:deadsnakes/ppa -y\nRUN apt-get update\nRUN apt-get install -y python3.13 python3.13-venv python3.13-dev build-essential\nRUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.13 1\nRUN apt-get clean && rm -rf /var/lib/apt/lists/*\nRUN python3.13 -m ensurepip --upgrade\nRUN python3.13 -m pip install --upgrade pip\nRUN apt-get update\nRUN apt-get install -y python3.13-tk python3.13-dev\nRUN alias python=python3\nRUN echo \"alias python=python3\" >> ~/.bashrc\nRUN rm /usr/bin/python3\nRUN ln -s python3.13 /usr/bin/python3\n\n#===============\n# Cleanup Lists\n#===============\nRUN apt-get clean\nRUN rm -rf /var/lib/apt/lists/*\n\n#=====================\n# Set up SeleniumBase\n#=====================\nCOPY sbase /SeleniumBase/sbase/\nCOPY seleniumbase /SeleniumBase/seleniumbase/\nCOPY examples /SeleniumBase/examples/\nCOPY integrations /SeleniumBase/integrations/\nCOPY requirements.txt /SeleniumBase/requirements.txt\nCOPY setup.py /SeleniumBase/setup.py\nCOPY MANIFEST.in /SeleniumBase/MANIFEST.in\nCOPY pytest.ini /SeleniumBase/pytest.ini\nCOPY setup.cfg /SeleniumBase/setup.cfg\nCOPY virtualenv_install.sh /SeleniumBase/virtualenv_install.sh\nRUN find . -name '*.pyc' -delete\nRUN pip install --upgrade pip setuptools wheel\nRUN cd /SeleniumBase && ls && pip install -r requirements.txt --upgrade\nRUN cd /SeleniumBase && pip install .\nRUN pip install pyautogui\nRUN pip install playwright\nRUN seleniumbase get cft\nRUN seleniumbase get chromium\n\n#=======================\n# Download chromedriver\n#=======================\nRUN seleniumbase get chromedriver --path\n\n#==============\n# Extra config\n#==============\nENV DISPLAY=\":99\"\nRUN Xvfb :99 -screen 1 1920x1080x16 -nolisten tcp &\n\n#==========================================\n# Create entrypoint and grab example tests\n#==========================================\nCOPY integrations/docker/docker-entrypoint.sh /\nCOPY integrations/docker/run_docker_test_in_chrome.sh /\nRUN chmod +x *.sh\nENTRYPOINT [\"/docker-entrypoint.sh\"]\nCMD [\"/bin/bash\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-2026 Michael Mintz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.md\ninclude pytest.ini\ninclude setup.cfg\ninclude .gitignore\ninclude requirements.txt\ninclude install.sh\ninclude install.bat\ninclude virtualenv_install.sh\ninclude virtualenv_install.bat\ninclude seleniumbase/core/create_db_tables.sql\ninclude seleniumbase/extensions/*.zip\ninclude seleniumbase/utilities/selenium_grid/grid-hub\ninclude seleniumbase/utilities/selenium_grid/grid-node\ninclude seleniumbase/utilities/selenium_grid/font_color\ninclude seleniumbase/utilities/selenium_grid/start-grid-hub.bat\ninclude seleniumbase/utilities/selenium_grid/register-grid-node.bat\ninclude seleniumbase/utilities/selenium_grid/start-grid-hub.sh\ninclude seleniumbase/utilities/selenium_grid/register-grid-node.sh"
  },
  {
    "path": "README.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<meta property=\"og:site_name\" content=\"SeleniumBase\">\n<meta property=\"og:title\" content=\"SeleniumBase: Python Web Automation and E2E Testing\" />\n<meta property=\"og:description\" content=\"Fast, easy, and reliable Web/UI testing with Python.\" />\n<meta property=\"og:keywords\" content=\"Python, pytest, selenium, webdriver, testing, automation, seleniumbase, framework, dashboard, recorder, reports, screenshots\">\n<meta property=\"og:image\" content=\"https://seleniumbase.github.io/cdn/img/mac_sb_logo_5b.png\" />\n<link rel=\"icon\" href=\"https://seleniumbase.github.io/img/logo7.png\" />\n\n<h1>SeleniumBase</h1>\n\n<p align=\"center\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/nice_logo_5t.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"344\" /></a></p>\n\n<p align=\"center\" class=\"hero__title\"><b>All-in-one Browser Automation Framework:<br />Web Crawling / Testing / Scraping / Stealth</b></p>\n\n<p align=\"center\"><a href=\"https://pypi.python.org/pypi/seleniumbase\" target=\"_blank\"><img src=\"https://img.shields.io/pypi/v/seleniumbase.svg?color=3399EE\" alt=\"PyPI version\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase/actions\" target=\"_blank\"><img src=\"https://github.com/seleniumbase/SeleniumBase/workflows/CI%20build/badge.svg\" alt=\"SeleniumBase GitHub Actions\" /></a> <a href=\"https://seleniumbase.io\"><img src=\"https://img.shields.io/badge/docs-seleniumbase.io-11BBAA.svg\" alt=\"SeleniumBase Docs\" /></a> <a href=\"https://pepy.tech/projects/seleniumbase?timeRange=threeMonths&category=version&includeCIDownloads=true&granularity=daily&viewType=line&versions=*\" target=\"_blank\"><img src=\"https://static.pepy.tech/badge/seleniumbase\" alt=\"SeleniumBase PyPI downloads\" /></a> <a href=\"https://discord.gg/EdhQTn3EyE\" target=\"_blank\"><img src=\"https://img.shields.io/discord/727927627830001734?color=7289DA&label=Discord&logo=discord&logoColor=white\"/></a></p>\n<p align=\"center\"></p>\n\n<p align=\"center\">\n<a href=\"#python_installation\">🚀 Start</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/features_list.md\">🏰 Features</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md\">🎛️ Options</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/ReadMe.md\">📚 Examples</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/ReadMe.md\">🪄 Scripts</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/mobile_testing.md\">📱 Mobile</a>\n<br />\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md\">📘 The API</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md\"> 🔠 SyntaxFormats</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md\">🔴 Recorder</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md\">📊 Dashboard</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/locale_codes.md\">🗾 Locale</a>\n<br />\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/commander.md\">🎖️ GUI</a> |\n<a href=\"https://seleniumbase.io/demo_page\">📰 TestPage</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md\">👤 UC Mode</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md\">🐙 CDP Mode</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/chart_maker/ReadMe.md\">📶 Charts</a>  |\n<a href=\"https://seleniumbase.io/devices/?url=seleniumbase.com\">🖥️ Farm</a>\n<br />\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/how_it_works.md\">👁️ How</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium\">🚝 Migration</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/playwright/ReadMe.md\">🎭 Stealthy Playwright</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/ReadMe.md\">🛂 MasterQA</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md\">🚎 Tours</a>\n<br />\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/github/workflows/ReadMe.md\">🤖 CI/CD</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/js_package_manager.md\">❇️ JSMgr</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/translations.md\">🌏 Translator</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/ReadMe.md\">🎞️ Presenter</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/visual_testing/ReadMe.md\">🖼️ Visual</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md\">🗂️ CPlans</a>\n<br />\n</p>\n\n📊 <a href=\"https://github.com/seleniumbase/SeleniumBase/\"><b translate=\"no\">SeleniumBase</b></a> is a complete framework for browser automation activities. It supports <a href=\"https://docs.pytest.org/en/latest/how-to/usage.html\">pytest</a>, which makes it easy to scale testing. Includes advanced tools for reporting, script-generation, JavaScript manipulation, and stealthy automation.\n\n🐙 Stealth modes: <a translate=\"no\" href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md\">UC Mode</a> and <a translate=\"no\" href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md\"><b>CDP Mode</b></a> can bypass bot-detection, handle CAPTCHAs, and call methods from the <a href=\"https://chromedevtools.github.io/devtools-protocol/\" translate=\"no\">Chrome Devtools Protocol</a>. Includes <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/playwright/ReadMe.md\"><b><span translate=\"no\">Stealthy Playwright Mode</span></b></a>, which makes Playwright stealthy via CDP Mode.\n\n📚 Example scripts and tests are located in [**SeleniumBase/examples/**](https://github.com/seleniumbase/SeleniumBase/tree/master/examples).\n\n🥷 Stealthy example scripts are located in [**SeleniumBase/examples/cdp_mode/**](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode).\n\n--------\n\n<p align=\"left\">📗 This script performs a Google Search using SeleniumBase UC Mode + CDP Mode:<br /><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py\">SeleniumBase/examples/raw_google.py</a> (Results are saved as PDF, HTML, and PNG)</p>\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://google.com/ncr\"\n    sb.activate_cdp_mode(url)\n    sb.type('[name=\"q\"]', \"SeleniumBase GitHub page\")\n    sb.click('[value=\"Google Search\"]')\n    sb.sleep(4)  # The \"AI Overview\" sometimes loads\n    print(sb.get_page_title())\n    sb.save_as_pdf_to_logs()\n    sb.save_page_source_to_logs()\n    sb.save_screenshot_to_logs()\n    print(\"Logs have been saved to: ./latest_logs/\")\n```\n\n> `python raw_google.py`\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py\"><img src=\"https://seleniumbase.github.io/cdn/img/google_sb_result.jpg\" alt=\"SeleniumBase on Google\" title=\"SeleniumBase on Google\" width=\"580\" /></a>\n\n--------\n\n<p align=\"left\">📗 Here's a script that bypasses Cloudflare's challenge page with UC Mode + CDP Mode: <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_gitlab.py\">SeleniumBase/examples/cdp_mode/raw_gitlab.py</a></p>\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"en\") as sb:\n    url = \"https://gitlab.com/users/sign_in\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.solve_captcha()\n    # (The rest is for testing and demo purposes)\n    sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\n    sb.assert_element('label[for=\"user_login\"]')\n    sb.highlight('button:contains(\"Sign in\")')\n    sb.highlight('h1:contains(\"GitLab\")')\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\n```\n\n<img src=\"https://seleniumbase.github.io/other/cf_sec.jpg\" title=\"SeleniumBase\" width=\"344\"> <img src=\"https://seleniumbase.github.io/other/gitlab_bypass.png\" title=\"SeleniumBase\" width=\"298\">\n\n<p align=\"left\">📙 There's also SeleniumBase's \"Pure CDP Mode\", which doesn't use WebDriver or Selenium at all: <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_gitlab.py\">SeleniumBase/examples/cdp_mode/raw_cdp_gitlab.py</a></p>\n\n```python\nfrom seleniumbase import sb_cdp\n\nurl = \"https://gitlab.com/users/sign_in\"\nsb = sb_cdp.Chrome(url, incognito=True)\nsb.sleep(2)\nsb.solve_captcha()\nsb.highlight('h1:contains(\"GitLab\")')\nsb.highlight('button:contains(\"Sign in\")')\nsb.driver.stop()\n```\n\n--------\n\n<p align=\"left\">📗 Here's <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_get_swag.py\">SeleniumBase/examples/test_get_swag.py</a>, which tests an e-commerce site:</p>\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)  # Call pytest\n\nclass MyTestClass(BaseCase):\n    def test_swag_labs(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n        self.assert_element(\"div.inventory_list\")\n        self.click('button[name*=\"backpack\"]')\n        self.click(\"#shopping_cart_container a\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click(\"button#checkout\")\n        self.type(\"input#first-name\", \"SeleniumBase\")\n        self.type(\"input#last-name\", \"Automation\")\n        self.type(\"input#postal-code\", \"77123\")\n        self.click(\"input#continue\")\n        self.click(\"button#finish\")\n        self.assert_text(\"Thank you for your order!\")\n```\n\n> `pytest test_get_swag.py`\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_get_swag.py\"><img src=\"https://seleniumbase.github.io/cdn/gif/fast_swag_2.gif\" alt=\"SeleniumBase Test\" title=\"SeleniumBase Test\" width=\"480\" /></a>\n\n> (The default browser is `--chrome` if not set.)\n\n--------\n\n<p align=\"left\">📗 Here's <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_coffee_cart.py\" target=\"_blank\">SeleniumBase/examples/test_coffee_cart.py</a>, which verifies an e-commerce site:</p>\n\n```zsh\npytest test_coffee_cart.py --demo\n```\n\n<p align=\"left\"><a href=\"https://seleniumbase.io/coffee/\" target=\"_blank\"><img src=\"https://seleniumbase.github.io/cdn/gif/coffee_cart.gif\" width=\"480\" alt=\"SeleniumBase Coffee Cart Test\" title=\"SeleniumBase Coffee Cart Test\" /></a></p>\n\n> <p>(<code translate=\"no\">--demo</code> mode slows down tests and highlights actions)</p>\n\n--------\n\n<a id=\"multiple_examples\"></a>\n\n<p align=\"left\">📗 Here's <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py\" target=\"_blank\">SeleniumBase/examples/test_demo_site.py</a>, which covers several actions:</p>\n\n```zsh\npytest test_demo_site.py\n```\n\n<p align=\"left\"><a href=\"https://seleniumbase.io/demo_page\" target=\"_blank\"><img src=\"https://seleniumbase.github.io/cdn/gif/demo_page_5.gif\" width=\"480\" alt=\"SeleniumBase Example\" title=\"SeleniumBase Example\" /></a></p>\n\n> Easy to type, click, select, toggle, drag & drop, and more.\n\n(For more examples, see the <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/ReadMe.md\">SeleniumBase/examples/</a> folder.)\n\n--------\n\n<p align=\"left\">📓 Here's a high-level stealthy architecture overview of SeleniumBase:</p>\n\n<img src=\"https://seleniumbase.github.io/other/sb_stealth.png\" width=\"585\" alt=\"High-Level Stealthy Architecture Overview\" title=\"High-Level Stealthy Architecture Overview\" />\n\n(For maximum stealth, use <a translate=\"no\" href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md\">CDP Mode</a>, which is used by <a translate=\"no\" href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/playwright/ReadMe.md\">Stealthy Playwright Mode</a>)\n\n--------\n\n<p align=\"left\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb3.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"232\" /></a></p>\n\n<blockquote>\n<p dir=\"auto\"><strong>Explore the README:</strong></p>\n<ul dir=\"auto\">\n<li><a href=\"#install_seleniumbase\"   ><strong>Get Started / Installation</strong></a></li>\n<li><a href=\"#basic_example_and_usage\"><strong>Basic Example / Usage</strong></a></li>\n<li><a href=\"#common_methods\"         ><strong>Common Test Methods</strong></a></li>\n<li><a href=\"#fun_facts\"              ><strong>Fun Facts / Learn More</strong></a></li>\n<li><a href=\"#demo_mode_and_debugging\"><strong>Demo Mode / Debugging</strong></a></li>\n<li><a href=\"#command_line_options\"   ><strong>Command-line Options</strong></a></li>\n<li><a href=\"#directory_configuration\"><strong>Directory Configuration</strong></a></li>\n<li><a href=\"#seleniumbase_dashboard\" ><strong>SeleniumBase Dashboard</strong></a></li>\n<li><a href=\"#creating_visual_reports\"><strong>Generating Test Reports</strong></a></li>\n</ul>\n</blockquote>\n\n--------\n\n<details>\n<summary> ▶️ How is <b>SeleniumBase</b> different from raw Selenium? (<b>click to expand</b>)</summary>\n<div>\n\n<p>💡 SeleniumBase is a Python framework for browser automation and testing. SeleniumBase uses <a href=\"https://www.w3.org/TR/webdriver2/#endpoints\" target=\"_blank\">Selenium/WebDriver</a> APIs and incorporates test-runners such as <code translate=\"no\">pytest</code>, <code translate=\"no\">pynose</code>, and <code translate=\"no\">behave</code> to provide organized structure, test discovery, test execution, test state (<i>eg. passed, failed, or skipped</i>), and command-line options for changing default settings (<i>eg. browser selection</i>). With raw Selenium, you would need to set up your own options-parser for configuring tests from the command-line.</p>\n\n<p>💡 SeleniumBase's driver manager gives you more control over automatic driver downloads. (Use <code translate=\"no\">--driver-version=VER</code> with your <code translate=\"no\">pytest</code> run command to specify the version.) By default, SeleniumBase will download a driver version that matches your major browser version if not set.</p>\n\n<p>💡 SeleniumBase automatically detects between CSS Selectors and XPath, which means you don't need to specify the type of selector in your commands (<i>but optionally you could</i>).</p>\n\n<p>💡 SeleniumBase methods often perform multiple actions in a single method call. For example, <code translate=\"no\">self.type(selector, text)</code> does the following:<br />1. Waits for the element to be visible.<br />2. Waits for the element to be interactive.<br />3. Clears the text field.<br />4. Types in the new text.<br />5. Presses Enter/Submit if the text ends in <code translate=\"no\">\"\\n\"</code>.<br />With raw Selenium, those actions require multiple method calls.</p>\n\n<p>💡 SeleniumBase uses default timeout values when not set:<br />\n✅ <code translate=\"no\">self.click(\"button\")</code><br />\nWith raw Selenium, methods would fail instantly (<i>by default</i>) if an element needed more time to load:<br />\n❌ <code translate=\"no\">self.driver.find_element(by=\"css selector\", value=\"button\").click()</code><br />\n(Reliable code is better than unreliable code.)</p>\n\n<p>💡 SeleniumBase lets you change the explicit timeout values of methods:<br />\n✅ <code translate=\"no\">self.click(\"button\", timeout=10)</code><br />\nWith raw Selenium, that requires more code:<br />\n❌ <code translate=\"no\">WebDriverWait(driver, 10).until(EC.element_to_be_clickable(\"css selector\", \"button\")).click()</code><br />\n(Simple code is better than complex code.)</p>\n\n<p>💡 SeleniumBase gives you clean error output when a test fails. With raw Selenium, error messages can get very messy.</p>\n\n<p>💡 SeleniumBase gives you the option to generate a dashboard and reports for tests. It also saves screenshots from failing tests to the <code translate=\"no\">./latest_logs/</code> folder. Raw <a href=\"https://www.selenium.dev/documentation/webdriver/\" translate=\"no\" target=\"_blank\">Selenium</a> does not have these options out-of-the-box.</p>\n\n<p>💡 SeleniumBase includes desktop GUI apps for running tests, such as <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/commander.md\" translate=\"no\">SeleniumBase Commander</a> for <code translate=\"no\">pytest</code> and <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/ReadMe.md\" translate=\"no\">SeleniumBase Behave GUI</a> for <code translate=\"no\">behave</code>.</p>\n\n<p>💡 SeleniumBase has its own <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md\">Recorder / Test Generator</a> for creating tests from manual browser actions.</p>\n\n<p>💡 SeleniumBase comes with <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md\">test case management software, (\"CasePlans\")</a>, for organizing tests and step descriptions.</p>\n\n<p>💡 SeleniumBase includes tools for <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/chart_maker/ReadMe.md\">building data apps, (\"ChartMaker\")</a>, which can generate JavaScript from Python.</p>\n\n</div>\n</details>\n\n--------\n\n<p>📚 <b>Learn about different ways of writing tests:</b></p>\n\n<p align=\"left\">📗📝 Here's <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_simple_login.py\">test_simple_login.py</a>, which uses <code translate=\"no\"><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py\">BaseCase</a></code> class inheritance, and runs with <a href=\"https://docs.pytest.org/en/latest/how-to/usage.html\">pytest</a> or <a href=\"https://github.com/mdmintz/pynose\">pynose</a>. (Use <code translate=\"no\">self.driver</code> to access Selenium's raw <code translate=\"no\">driver</code>.)</p>\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass TestSimpleLogin(BaseCase):\n    def test_simple_login(self):\n        self.open(\"seleniumbase.io/simple/login\")\n        self.type(\"#username\", \"demo_user\")\n        self.type(\"#password\", \"secret_pass\")\n        self.click('a:contains(\"Sign in\")')\n        self.assert_exact_text(\"Welcome!\", \"h1\")\n        self.assert_element(\"img#image1\")\n        self.highlight(\"#image1\")\n        self.click_link(\"Sign out\")\n        self.assert_text(\"signed out\", \"#top_message\")\n```\n\n<p align=\"left\">📘📝 Here's <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_login_sb.py\">raw_login_sb.py</a>, which uses the <b><code translate=\"no\">SB</code></b> Context Manager. Runs with pure <code translate=\"no\">python</code>. (Use <code translate=\"no\">sb.driver</code> to access Selenium's raw <code translate=\"no\">driver</code>.)</p>\n\n```python\nfrom seleniumbase import SB\n\nwith SB() as sb:\n    sb.open(\"seleniumbase.io/simple/login\")\n    sb.type(\"#username\", \"demo_user\")\n    sb.type(\"#password\", \"secret_pass\")\n    sb.click('a:contains(\"Sign in\")')\n    sb.assert_exact_text(\"Welcome!\", \"h1\")\n    sb.assert_element(\"img#image1\")\n    sb.highlight(\"#image1\")\n    sb.click_link(\"Sign out\")\n    sb.assert_text(\"signed out\", \"#top_message\")\n```\n\n<p align=\"left\">📙📝 Here's <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_login_driver.py\">raw_login_driver.py</a>, which uses the <b><code translate=\"no\">Driver</code></b> Manager. Runs with pure <code translate=\"no\">python</code>. (The <code>driver</code> is an improved version of Selenium's raw <code translate=\"no\">driver</code>, with more methods.)</p>\n\n```python\nfrom seleniumbase import Driver\n\ndriver = Driver()\ntry:\n    driver.open(\"seleniumbase.io/simple/login\")\n    driver.type(\"#username\", \"demo_user\")\n    driver.type(\"#password\", \"secret_pass\")\n    driver.click('a:contains(\"Sign in\")')\n    driver.assert_exact_text(\"Welcome!\", \"h1\")\n    driver.assert_element(\"img#image1\")\n    driver.highlight(\"#image1\")\n    driver.click_link(\"Sign out\")\n    driver.assert_text(\"signed out\", \"#top_message\")\nfinally:\n    driver.quit()\n```\n\n--------\n\n<a id=\"python_installation\"></a>\n<h2><img src=\"https://seleniumbase.github.io/cdn/img/python_logo.png\" title=\"SeleniumBase\" width=\"42\" /> Set up Python & Git:</h2>\n\n<a href=\"https://www.python.org/downloads/\"><img src=\"https://img.shields.io/pypi/pyversions/seleniumbase.svg?color=FACE42\" title=\"Supported Python Versions\" /></a>\n\n🔵 Add <b><a href=\"https://www.python.org/downloads/\">Python</a></b> and <b><a href=\"https://git-scm.com/\">Git</a></b> to your System PATH.\n\n🔵 Using a <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/virtualenv_instructions.md\">Python virtual env</a> is recommended.\n\n<a id=\"install_seleniumbase\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Install SeleniumBase:</h2>\n\n**You can install `seleniumbase` from [PyPI](https://pypi.org/project/seleniumbase/) or [GitHub](https://github.com/seleniumbase/SeleniumBase):**\n\n🔵 **How to install `seleniumbase` from PyPI:**\n\n```zsh\npip install seleniumbase\n```\n\n* (Add `--upgrade` OR `-U` to upgrade SeleniumBase.)\n* (Add `--force-reinstall` to upgrade indirect packages.)\n\n🔵 **How to install `seleniumbase` from a GitHub clone:**\n\n```zsh\ngit clone https://github.com/seleniumbase/SeleniumBase.git\ncd SeleniumBase/\npip install -e .\n```\n\n🔵 **How to upgrade an existing install from a GitHub clone:**\n\n```zsh\ngit pull\npip install -e .\n```\n\n🔵 **Type `seleniumbase` or `sbase` to verify that SeleniumBase was installed successfully:**\n\n```zsh\n ___      _          _             ___              \n/ __| ___| |___ _ _ (_)_  _ _ __  | _ ) __ _ ______ \n\\__ \\/ -_) / -_) ' \\| | \\| | '  \\ | _ \\/ _` (_-< -_)\n|___/\\___|_\\___|_||_|_|\\_,_|_|_|_\\|___/\\__,_/__|___|\n----------------------------------------------------\n\n╭──────────────────────────────────────────────────╮\n│  * USAGE: \"seleniumbase [COMMAND] [PARAMETERS]\"  │\n│  *    OR:        \"sbase [COMMAND] [PARAMETERS]\"  │\n│                                                  │\n│ COMMANDS:        PARAMETERS / DESCRIPTIONS:      │\n│    get / install    [DRIVER_NAME] [OPTIONS]      │\n│    methods          (List common Python methods) │\n│    options          (List common pytest options) │\n│    behave-options   (List common behave options) │\n│    gui / commander  [OPTIONAL PATH or TEST FILE] │\n│    behave-gui       (SBase Commander for Behave) │\n│    caseplans        [OPTIONAL PATH or TEST FILE] │\n│    mkdir            [DIRECTORY] [OPTIONS]        │\n│    mkfile           [FILE.py] [OPTIONS]          │\n│    mkrec / codegen  [FILE.py] [OPTIONS]          │\n│    recorder         (Open Recorder Desktop App.) │\n│    record           (If args: mkrec. Else: App.) │\n│    mkpres           [FILE.py] [LANG]             │\n│    mkchart          [FILE.py] [LANG]             │\n│    print            [FILE] [OPTIONS]             │\n│    translate        [SB_FILE.py] [LANG] [ACTION] │\n│    convert          [WEBDRIVER_UNITTEST_FILE.py] │\n│    extract-objects  [SB_FILE.py]                 │\n│    inject-objects   [SB_FILE.py] [OPTIONS]       │\n│    objectify        [SB_FILE.py] [OPTIONS]       │\n│    revert-objects   [SB_FILE.py] [OPTIONS]       │\n│    encrypt / obfuscate                           │\n│    decrypt / unobfuscate                         │\n│    proxy            (Start a basic proxy server) │\n│    download server  (Get Selenium Grid JAR file) │\n│    grid-hub         [start|stop] [OPTIONS]       │\n│    grid-node        [start|stop] --hub=[HOST/IP] │\n│                                                  │\n│ *  EXAMPLE => \"sbase get chromedriver stable\"    │\n│ *  For command info => \"sbase help [COMMAND]\"    │\n│ *  For info on all commands => \"sbase --help\"    │\n╰──────────────────────────────────────────────────╯\n```\n\n<h3>🔵 Downloading webdrivers:</h3>\n\n✅ SeleniumBase automatically downloads webdrivers as needed, such as `chromedriver`.\n\n<div></div>\n<details>\n<summary> ▶️ Here's sample output from a chromedriver download. (<b>click to expand</b>)</summary>\n\n```zsh\n*** chromedriver to download = 141.0.7390.78 (Latest Stable) \n\nDownloading chromedriver-mac-arm64.zip from:\nhttps://storage.googleapis.com/chrome-for-testing-public/141.0.7390.78/mac-arm64/chromedriver-mac-arm64.zip ...\nDownload Complete!\n\nExtracting ['chromedriver'] from chromedriver-mac-arm64.zip ...\nUnzip Complete!\n\nThe file [chromedriver] was saved to:\n~/github/SeleniumBase/seleniumbase/drivers/\nchromedriver\n\nMaking [chromedriver 141.0.7390.78] executable ...\n[chromedriver 141.0.7390.78] is now ready for use!\n```\n\n</details>\n\n\n<a id=\"basic_example_and_usage\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Basic Example / Usage:</h2>\n\n🔵 If you've cloned SeleniumBase, you can run tests from the [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder.\n\n<p align=\"left\">Here's <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py\">my_first_test.py</a>:</p>\n\n```zsh\ncd examples/\npytest my_first_test.py\n```\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py\"><img src=\"https://seleniumbase.github.io/cdn/gif/fast_swag_2.gif\" alt=\"SeleniumBase Test\" title=\"SeleniumBase Test\" width=\"480\" /></a>\n\n<p align=\"left\"><b>Here's the full code for <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py\">my_first_test.py</a>:</b></p>\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass MyTestClass(BaseCase):\n    def test_swag_labs(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n        self.assert_element(\"div.inventory_list\")\n        self.assert_exact_text(\"Products\", \"span.title\")\n        self.click('button[name*=\"backpack\"]')\n        self.click(\"#shopping_cart_container a\")\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click(\"button#checkout\")\n        self.type(\"#first-name\", \"SeleniumBase\")\n        self.type(\"#last-name\", \"Automation\")\n        self.type(\"#postal-code\", \"77123\")\n        self.click(\"input#continue\")\n        self.assert_text(\"Checkout: Overview\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.assert_text(\"29.99\", \"div.inventory_item_price\")\n        self.click(\"button#finish\")\n        self.assert_exact_text(\"Thank you for your order!\", \"h2\")\n        self.assert_element('img[alt=\"Pony Express\"]')\n        self.js_click(\"a#logout_sidebar_link\")\n        self.assert_element(\"div#login_button_container\")\n```\n\n* By default, **[CSS Selectors](https://www.w3schools.com/cssref/css_selectors.asp)** are used for finding page elements.\n* If you're new to CSS Selectors, games like [CSS Diner](http://flukeout.github.io/) can help you learn.\n* For more reading, [here's an advanced guide on CSS attribute selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).\n\n\n<a id=\"common_methods\"></a>\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Here are some common SeleniumBase methods:</h3>\n\n```python\nself.open(url)  # Navigate the browser window to the URL.\nself.type(selector, text)  # Update the field with the text.\nself.click(selector)  # Click the element with the selector.\nself.click_link(link_text)  # Click the link containing text.\nself.go_back()  # Navigate back to the previous URL.\nself.select_option_by_text(dropdown_selector, option)\nself.hover_and_click(hover_selector, click_selector)\nself.drag_and_drop(drag_selector, drop_selector)\nself.get_text(selector)  # Get the text from the element.\nself.get_current_url()  # Get the URL of the current page.\nself.get_page_source()  # Get the HTML of the current page.\nself.get_attribute(selector, attribute)  # Get element attribute.\nself.get_title()  # Get the title of the current page.\nself.switch_to_frame(frame)  # Switch into the iframe container.\nself.switch_to_default_content()  # Leave the iframe container.\nself.open_new_window()  # Open a new window in the same browser.\nself.switch_to_window(window)  # Switch to the browser window.\nself.switch_to_default_window()  # Switch to the original window.\nself.get_new_driver(OPTIONS)  # Open a new driver with OPTIONS.\nself.switch_to_driver(driver)  # Switch to the browser driver.\nself.switch_to_default_driver()  # Switch to the original driver.\nself.wait_for_element(selector)  # Wait until element is visible.\nself.is_element_visible(selector)  # Return element visibility.\nself.is_text_visible(text, selector)  # Return text visibility.\nself.sleep(seconds)  # Do nothing for the given amount of time.\nself.save_screenshot(name)  # Save a screenshot in .png format.\nself.assert_element(selector)  # Verify the element is visible.\nself.assert_text(text, selector)  # Verify text in the element.\nself.assert_exact_text(text, selector)  # Verify text is exact.\nself.assert_title(title)  # Verify the title of the web page.\nself.assert_downloaded_file(file)  # Verify file was downloaded.\nself.assert_no_404_errors()  # Verify there are no broken links.\nself.assert_no_js_errors()  # Verify there are no JS errors.\n```\n\n🔵 For the complete list of SeleniumBase methods, see: <b><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md\">Method Summary</a></b>\n\n\n<a id=\"fun_facts\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Fun Facts / Learn More:</h2>\n\n<p>✅ SeleniumBase automatically handles common <a href=\"https://www.selenium.dev/documentation/webdriver/\" target=\"_blank\">WebDriver</a> actions such as launching web browsers before tests, saving screenshots during failures, and closing web browsers after tests.</p>\n\n<p>✅ SeleniumBase lets you customize tests via <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md\">command-line options</a>.</p>\n\n<p>✅ SeleniumBase uses simple syntax for commands. Example:</p>\n\n```python\nself.type(\"input\", \"dogs\\n\")  # (The \"\\n\" presses ENTER)\n```\n\nMost SeleniumBase scripts can be run with <code translate=\"no\">pytest</code>, <code translate=\"no\">pynose</code>, or pure <code translate=\"no\">python</code>. Not all test runners can run all test formats. For example, tests that use the `sb` pytest fixture can only be run with `pytest`. (See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md\">Syntax Formats</a>) There's also a <a href=\"https://behave.readthedocs.io/en/stable/gherkin.html#features\" target=\"_blank\">Gherkin</a> test format that runs with <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/ReadMe.md\">behave</a>.\n\n```zsh\npytest coffee_cart_tests.py --rs\npytest test_sb_fixture.py --demo\npytest test_suite.py --rs --html=report.html --dashboard\n\npynose basic_test.py --mobile\npynose test_suite.py --headless --report --show-report\n\npython raw_sb.py\npython raw_test_scripts.py\n\nbehave realworld.feature\nbehave calculator.feature -D rs -D dashboard\n```\n\n<p>✅ <code translate=\"no\">pytest</code> includes automatic test discovery. If you don't specify a specific file or folder to run, <code translate=\"no\">pytest</code> will automatically search through all subdirectories for tests to run based on the following criteria:</p>\n\n* Python files that start with `test_` or end with `_test.py`.\n* Python methods that start with `test_`.\n\nWith a SeleniumBase [pytest.ini](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/pytest.ini) file present, you can modify default discovery settings. The Python class name can be anything because `seleniumbase.BaseCase` inherits `unittest.TestCase` to trigger autodiscovery.\n\n<p>✅ You can do a pre-flight check to see which tests would get discovered by <code translate=\"no\">pytest</code> before the actual run:</p>\n\n```zsh\npytest --co -q\n```\n\n<p>✅ You can be more specific when calling <code translate=\"no\">pytest</code> or <code translate=\"no\">pynose</code> on a file:</p>\n\n```zsh\npytest [FILE_NAME.py]::[CLASS_NAME]::[METHOD_NAME]\n\npynose [FILE_NAME.py]:[CLASS_NAME].[METHOD_NAME]\n```\n\n<p>✅ No More Flaky Tests! SeleniumBase methods automatically wait for page elements to finish loading before interacting with them (<i>up to a timeout limit</i>). This means <b>you no longer need random <span><code translate=\"no\">time.sleep()</code></span> statements</b> in your scripts.</p>\n<img src=\"https://img.shields.io/badge/Flaky%20Tests%3F-%20NO%21-11BBDD.svg\" alt=\"NO MORE FLAKY TESTS!\" />\n\n✅ SeleniumBase supports all major browsers and operating systems:\n<p><b>Browsers:</b> Chrome, Edge, Firefox, and Safari.</p>\n<p><b>Systems:</b> Linux/Ubuntu, macOS, and Windows.</p>\n\n✅ SeleniumBase works on all popular CI/CD platforms:\n<p><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/github/workflows/ReadMe.md\"><img alt=\"GitHub Actions integration\" src=\"https://img.shields.io/badge/GitHub_Actions-12B2C2.svg?logo=GitHubActions&logoColor=CFFFC2\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/azure/jenkins/ReadMe.md\"><img alt=\"Jenkins integration\" src=\"https://img.shields.io/badge/Jenkins-32B242.svg?logo=jenkins&logoColor=white\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/azure/azure_pipelines/ReadMe.md\"><img alt=\"Azure integration\" src=\"https://img.shields.io/badge/Azure-2288EE.svg?logo=AzurePipelines&logoColor=white\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/google_cloud/ReadMe.md\"><img alt=\"Google Cloud integration\" src=\"https://img.shields.io/badge/Google_Cloud-11CAE8.svg?logo=GoogleCloud&logoColor=EE0066\" /></a> <a href=\"#utilizing_advanced_features\"><img alt=\"AWS integration\" src=\"https://img.shields.io/badge/AWS-4488DD.svg?logo=AmazonAWS&logoColor=FFFF44\" /></a> <a href=\"https://en.wikipedia.org/wiki/Personal_computer\" target=\"_blank\"><img alt=\"Your Computer\" src=\"https://img.shields.io/badge/💻_Your_Computer-44E6E6.svg\" /></a></p>\n\n<p>✅ SeleniumBase includes an automated/manual hybrid solution called <b><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/ReadMe.md\">MasterQA</a></b> to speed up manual testing with automation while manual testers handle validation.</p>\n\n<p>✅ SeleniumBase supports <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples/offline_examples\">running tests while offline</a> (<i>assuming webdrivers have previously been downloaded when online</i>).</p>\n\n<p>✅ For a full list of SeleniumBase features, <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/features_list.md\">Click Here</a>.</p>\n\n\n<a id=\"demo_mode_and_debugging\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Demo Mode / Debugging:</h2>\n\n🔵 <b>Demo Mode</b> helps you see what a test is doing. If a test is moving too fast for your eyes, run it in <b>Demo Mode</b> to pause the browser briefly between actions, highlight page elements being acted on, and display assertions:\n\n```zsh\npytest my_first_test.py --demo\n```\n\n🔵 `time.sleep(seconds)` can be used to make a test wait at a specific spot:\n\n```python\nimport time; time.sleep(3)  # Do nothing for 3 seconds.\n```\n\n🔵 **Debug Mode** with Python's built-in **[pdb](https://docs.python.org/3/library/pdb.html)** library helps you debug tests:\n\n```python\nimport pdb; pdb.set_trace()\nimport pytest; pytest.set_trace()\nbreakpoint()  # Shortcut for \"import pdb; pdb.set_trace()\"\n```\n\n> (**`pdb`** commands: `n`, `c`, `s`, `u`, `d` => `next`, `continue`, `step`, `up`, `down`)\n\n🔵 To pause an active test that throws an exception or error, (*and keep the browser window open while **Debug Mode** begins in the console*), add **`--pdb`** as a `pytest` option:\n\n```zsh\npytest test_fail.py --pdb\n```\n\n🔵 To start tests in Debug Mode, add **`--trace`** as a `pytest` option:\n\n```zsh\npytest test_coffee_cart.py --trace\n```\n\n<a href=\"https://github.com/mdmintz/pdbp\"><img src=\"https://seleniumbase.github.io/cdn/gif/coffee_pdbp.gif\" alt=\"SeleniumBase test with the pdbp (Pdb+) debugger\" title=\"SeleniumBase test with the pdbp (Pdb+) debugger\" /></a>\n\n\n<a id=\"command_line_options\"></a>\n<h2>🔵 Command-line Options:</h2>\n\n<a id=\"pytest_options\"></a>\n✅ Here are some useful command-line options that come with <code translate=\"no\">pytest</code>:\n\n```zsh\n-v  # Verbose mode. Prints the full name of each test and shows more details.\n-q  # Quiet mode. Print fewer details in the console output when running tests.\n-x  # Stop running the tests after the first failure is reached.\n--html=report.html  # Creates a detailed pytest-html report after tests finish.\n--co | --collect-only  # Show what tests would get run. (Without running them)\n--co -q  # (Both options together!) - Do a dry run with full test names shown.\n-n=NUM  # Multithread the tests using that many threads. (Speed up test runs!)\n-s  # See print statements. (Should be on by default with pytest.ini present.)\n--junit-xml=report.xml  # Creates a junit-xml report after tests finish.\n--pdb  # If a test fails, enter Post Mortem Debug Mode. (Don't use with CI!)\n--trace  # Enter Debug Mode at the beginning of each test. (Don't use with CI!)\n-m=MARKER  # Run tests with the specified pytest marker.\n```\n\n<a id=\"new_pytest_options\"></a>\n✅ SeleniumBase provides additional <code translate=\"no\">pytest</code> command-line options for tests:\n\n```zsh\n--browser=BROWSER  # (The web browser to use. Default: \"chrome\".)\n--chrome  # (Shortcut for \"--browser=chrome\". On by default.)\n--edge  # (Shortcut for \"--browser=edge\".)\n--firefox  # (Shortcut for \"--browser=firefox\".)\n--safari  # (Shortcut for \"--browser=safari\".)\n--opera  # (Shortcut for \"--browser=opera\".)\n--brave  # (Shortcut for \"--browser=brave\".)\n--comet  # (Shortcut for \"--browser=comet\".)\n--atlas  # (Shortcut for \"--browser=atlas\".)\n--settings-file=FILE  # (Override default SeleniumBase settings.)\n--env=ENV  # (Set the test env. Access with \"self.env\" in tests.)\n--account=STR  # (Set account. Access with \"self.account\" in tests.)\n--data=STRING  # (Extra test data. Access with \"self.data\" in tests.)\n--var1=STRING  # (Extra test data. Access with \"self.var1\" in tests.)\n--var2=STRING  # (Extra test data. Access with \"self.var2\" in tests.)\n--var3=STRING  # (Extra test data. Access with \"self.var3\" in tests.)\n--variables=DICT  # (Extra test data. Access with \"self.variables\".)\n--user-data-dir=DIR  # (Set the Chrome user data directory to use.)\n--protocol=PROTOCOL  # (The Selenium Grid protocol: http|https.)\n--server=SERVER  # (The Selenium Grid server/IP used for tests.)\n--port=PORT  # (The Selenium Grid port used by the test server.)\n--cap-file=FILE  # (The web browser's desired capabilities to use.)\n--cap-string=STRING  # (The web browser's desired capabilities to use.)\n--proxy=SERVER:PORT  # (Connect to a proxy server:port as tests are running)\n--proxy=USERNAME:PASSWORD@SERVER:PORT  # (Use an authenticated proxy server)\n--proxy-bypass-list=STRING # (\";\"-separated hosts to bypass, Eg \"*.foo.com\")\n--proxy-pac-url=URL  # (Connect to a proxy server using a PAC_URL.pac file.)\n--proxy-pac-url=USERNAME:PASSWORD@URL  # (Authenticated proxy with PAC URL.)\n--proxy-driver  # (If a driver download is needed, will use: --proxy=PROXY.)\n--multi-proxy  # (Allow multiple authenticated proxies when multi-threaded.)\n--agent=STRING  # (Modify the web browser's User-Agent string.)\n--mobile  # (Use the mobile device emulator while running tests.)\n--metrics=STRING  # (Set mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\".)\n--chromium-arg=\"ARG=N,ARG2\"  # (Set Chromium args, \",\"-separated, no spaces.)\n--firefox-arg=\"ARG=N,ARG2\"  # (Set Firefox args, comma-separated, no spaces.)\n--firefox-pref=SET  # (Set a Firefox preference:value set, comma-separated.)\n--extension-zip=ZIP  # (Load a Chrome Extension .zip|.crx, comma-separated.)\n--extension-dir=DIR  # (Load a Chrome Extension directory, comma-separated.)\n--disable-features=\"F1,F2\"  # (Disable features, comma-separated, no spaces.)\n--binary-location=PATH  # (Set path of the Chromium browser binary to use.)\n--driver-version=VER  # (Set the chromedriver or uc_driver version to use.)\n--sjw  # (Skip JS Waits for readyState to be \"complete\" or Angular to load.)\n--wfa  # (Wait for AngularJS to be done loading after specific web actions.)\n--pls=PLS  # (Set pageLoadStrategy on Chrome: \"normal\", \"eager\", or \"none\".)\n--headless  # (The default headless mode. Linux uses this mode by default.)\n--headless1  # (Use Chrome's old headless mode. Fast, but has limitations.)\n--headless2  # (Use Chrome's new headless mode, which supports extensions.)\n--headed  # (Run tests in headed/GUI mode on Linux OS, where not default.)\n--xvfb  # (Run tests using the Xvfb virtual display server on Linux OS.)\n--xvfb-metrics=STRING  # (Set Xvfb display size on Linux: \"Width,Height\".)\n--locale=LOCALE_CODE  # (Set the Language Locale Code for the web browser.)\n--interval=SECONDS  # (The autoplay interval for presentations & tour steps)\n--start-page=URL  # (The starting URL for the web browser when tests begin.)\n--archive-logs  # (Archive existing log files instead of deleting them.)\n--archive-downloads  # (Archive old downloads instead of deleting them.)\n--time-limit=SECONDS  # (Safely fail any test that exceeds the time limit.)\n--slow  # (Slow down the automation. Faster than using Demo Mode.)\n--demo  # (Slow down and visually see test actions as they occur.)\n--demo-sleep=SECONDS  # (Set the wait time after Slow & Demo Mode actions.)\n--highlights=NUM  # (Number of highlight animations for Demo Mode actions.)\n--message-duration=SECONDS  # (The time length for Messenger alerts.)\n--check-js  # (Check for JavaScript errors after page loads.)\n--ad-block  # (Block some types of display ads from loading.)\n--host-resolver-rules=RULES  # (Set host-resolver-rules, comma-separated.)\n--block-images  # (Block images from loading during tests.)\n--do-not-track  # (Indicate to websites that you don't want to be tracked.)\n--verify-delay=SECONDS  # (The delay before MasterQA verification checks.)\n--ee | --esc-end  # (Lets the user end the current test via the ESC key.)\n--recorder  # (Enables the Recorder for turning browser actions into code.)\n--rec-behave  # (Same as Recorder Mode, but also generates behave-gherkin.)\n--rec-sleep  # (If the Recorder is enabled, also records self.sleep calls.)\n--rec-print  # (If the Recorder is enabled, prints output after tests end.)\n--disable-cookies  # (Disable Cookies on websites. Pages might break!)\n--disable-js  # (Disable JavaScript on websites. Pages might break!)\n--disable-csp  # (Disable the Content Security Policy of websites.)\n--disable-ws  # (Disable Web Security on Chromium-based browsers.)\n--enable-ws  # (Enable Web Security on Chromium-based browsers.)\n--enable-sync  # (Enable \"Chrome Sync\" on websites.)\n--uc | --undetected  # (Use undetected-chromedriver to evade bot-detection.)\n--uc-cdp-events  # (Capture CDP events when running in \"--undetected\" mode.)\n--log-cdp  # (\"goog:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"})\n--remote-debug  # (Sync to Chrome Remote Debugger chrome://inspect/#devices)\n--ftrace | --final-trace  # (Debug Mode after each test. Don't use with CI!)\n--dashboard  # (Enable the SeleniumBase Dashboard. Saved at: dashboard.html)\n--dash-title=STRING  # (Set the title shown for the generated dashboard.)\n--enable-3d-apis  # (Enables WebGL and 3D APIs.)\n--swiftshader  # (Chrome \"--use-gl=angle\" / \"--use-angle=swiftshader-webgl\")\n--incognito  # (Enable Chrome's Incognito mode.)\n--guest  # (Enable Chrome's Guest mode.)\n--dark  # (Enable Chrome's Dark mode.)\n--devtools  # (Open Chrome's DevTools when the browser opens.)\n--rs | --reuse-session  # (Reuse browser session for all tests.)\n--rcs | --reuse-class-session  # (Reuse session for tests in class.)\n--crumbs  # (Delete all cookies between tests reusing a session.)\n--disable-beforeunload  # (Disable the \"beforeunload\" event on Chrome.)\n--window-position=X,Y  # (Set the browser's starting window position.)\n--window-size=WIDTH,HEIGHT  # (Set the browser's starting window size.)\n--maximize  # (Start tests with the browser window maximized.)\n--screenshot  # (Save a screenshot at the end of each test.)\n--no-screenshot  # (No screenshots saved unless tests directly ask it.)\n--visual-baseline  # (Set the visual baseline for Visual/Layout tests.)\n--wire  # (Use selenium-wire's webdriver for replacing selenium webdriver.)\n--external-pdf  # (Set Chromium \"plugins.always_open_pdf_externally\":True.)\n--timeout-multiplier=MULTIPLIER  # (Multiplies the default timeout values.)\n--list-fail-page  # (After each failing test, list the URL of the failure.)\n```\n\n(See the full list of command-line option definitions **[here](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/plugins/pytest_plugin.py)**. For detailed examples of command-line options, see **[customizing_test_runs.md](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md)**)\n\n--------\n\n🔵 During test failures, logs and screenshots from the most recent test run will get saved to the `latest_logs/` folder. Those logs will get moved to `archived_logs/` if you add --archive_logs to command-line options, or have `ARCHIVE_EXISTING_LOGS` set to True in [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py), otherwise log files with be cleaned up at the start of the next test run. The `test_suite.py` collection contains tests that fail on purpose so that you can see how logging works.\n\n```zsh\ncd examples/\n\npytest test_suite.py --chrome\n\npytest test_suite.py --firefox\n```\n\nAn easy way to override seleniumbase/config/settings.py is by using a custom settings file.\nHere's the command-line option to add to tests: (See [examples/custom_settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/custom_settings.py))\n`--settings_file=custom_settings.py`\n(Settings include default timeout values, a two-factor auth key, DB credentials, S3 credentials, and other important settings used by tests.)\n\n🔵 To pass additional data from the command-line to tests, add `--data=\"ANY STRING\"`.\nInside your tests, you can use `self.data` to access that.\n\n<a id=\"directory_configuration\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Directory Configuration:</h2>\n\n🔵 When running tests with **`pytest`**, you'll want a copy of **[pytest.ini](https://github.com/seleniumbase/SeleniumBase/blob/master/pytest.ini)** in your root folders. When running tests with **`pynose`**, you'll want a copy of **[setup.cfg](https://github.com/seleniumbase/SeleniumBase/blob/master/setup.cfg)** in your root folders. These files specify default configuration details for tests. Test folders should also include a blank **[__init__.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/offline_examples/__init__.py)** file to allow your test files to import other files from that folder.\n\n🔵 `sbase mkdir DIR` creates a folder with config files and sample tests:\n\n```zsh\nsbase mkdir ui_tests\n```\n\n> That new folder will have these files:\n\n```zsh\nui_tests/\n├── __init__.py\n├── my_first_test.py\n├── parameterized_test.py\n├── pytest.ini\n├── requirements.txt\n├── setup.cfg\n├── test_demo_site.py\n└── boilerplates/\n    ├── __init__.py\n    ├── base_test_case.py\n    ├── boilerplate_test.py\n    ├── classic_obj_test.py\n    ├── page_objects.py\n    ├── sb_fixture_test.py\n    └── samples/\n        ├── __init__.py\n        ├── google_objects.py\n        ├── google_test.py\n        ├── sb_swag_test.py\n        └── swag_labs_test.py\n```\n\n<b>ProTip™:</b> You can also create a boilerplate folder without any sample tests in it by adding `-b` or `--basic` to the `sbase mkdir` command:\n\n```zsh\nsbase mkdir ui_tests --basic\n```\n\n> That new folder will have these files:\n\n```zsh\nui_tests/\n├── __init__.py\n├── pytest.ini\n├── requirements.txt\n└── setup.cfg\n```\n\nOf those files, the `pytest.ini` config file is the most important, followed by a blank `__init__.py` file. There's also a `setup.cfg` file (for pynose). Finally, the `requirements.txt` file can be used to help you install seleniumbase into your environments (if it's not already installed).\n\n<b>ProTip™:</b> Add `--gha` to include a GitHub Actions `.yml` file with default settings:\n\n```zsh\nui_tests/\n└── .github                    \n    └── workflows/             \n        └── python-package.yml\n```\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Log files from failed tests:</h3>\n\nLet's try an example of a test that fails:\n\n```python\n\"\"\" test_fail.py \"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass MyTestClass(BaseCase):\n\n    def test_find_army_of_robots_on_xkcd_desert_island(self):\n        self.open(\"https://xkcd.com/731/\")\n        self.assert_element(\"div#ARMY_OF_ROBOTS\", timeout=1)  # This should fail\n```\n\nYou can run it from the `examples/` folder like this:\n\n```zsh\npytest test_fail.py\n```\n\n🔵 You'll notice that a logs folder, `./latest_logs/`, was created to hold information (and screenshots) about the failing test. During test runs, past results get moved to the archived_logs folder if you have ARCHIVE_EXISTING_LOGS set to True in [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py), or if your run tests with `--archive-logs`. If you choose not to archive existing logs, they will be deleted and replaced by the logs of the latest test run.\n\n--------\n\n<a id=\"seleniumbase_dashboard\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> SeleniumBase Dashboard:</h2>\n\n🔵 The `--dashboard` option for pytest generates a SeleniumBase Dashboard located at `dashboard.html`, which updates automatically as tests run and produce results. Example:\n\n```zsh\npytest --dashboard --rs --headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dashboard_1.png\" alt=\"The SeleniumBase Dashboard\" title=\"The SeleniumBase Dashboard\" width=\"380\" />\n\n🔵 Additionally, you can host your own SeleniumBase Dashboard Server on a port of your choice. Here's an example of that using Python's `http.server`:\n\n```zsh\npython -m http.server 1948\n```\n\n🔵 Now you can navigate to `http://localhost:1948/dashboard.html` in order to view the dashboard as a web app. This requires two different terminal windows: one for running the server, and another for running the tests, which should be run from the same directory. (Use <kbd>Ctrl+C</kbd> to stop the http server.)\n\n🔵 Here's a full example of what the SeleniumBase Dashboard may look like:\n\n```zsh\npytest test_suite.py test_image_saving.py --dashboard --rs --headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dashboard_2.png\" alt=\"The SeleniumBase Dashboard\" title=\"The SeleniumBase Dashboard\" width=\"520\" />\n\n--------\n\n<a id=\"creating_visual_reports\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Generating Test Reports:</h2>\n\n<h3>🔵 <code>pytest</code> HTML Reports:</h3>\n\n✅ Using `--html=report.html` gives you a fancy report of the name specified after your test suite completes.\n\n```zsh\npytest test_suite.py --html=report.html\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/html_report.png\" alt=\"Example Pytest Report\" title=\"Example Pytest Report\" width=\"520\" />\n\n✅ When combining pytest html reports with SeleniumBase Dashboard usage, the pie chart from the Dashboard will get added to the html report. Additionally, if you set the html report URL to be the same as the Dashboard URL when also using the dashboard, (example: `--dashboard --html=dashboard.html`), then the Dashboard will become an advanced html report when all the tests complete.\n\n✅ Here's an example of an upgraded html report:\n\n```zsh\npytest test_suite.py --dashboard --html=report.html\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dash_report.jpg\" alt=\"Dashboard Pytest HTML Report\" title=\"Dashboard Pytest HTML Report\" width=\"520\" />\n\nIf viewing pytest html reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356/7058266) for the html to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/system-administration/security/configuring-content-security-policy/).\n\nYou can also use `--junit-xml=report.xml` to get an xml report instead. Jenkins can use this file to display better reporting for your tests.\n\n```zsh\npytest test_suite.py --junit-xml=report.xml\n```\n\n<h3>🔵 <code>pynose</code> Reports:</h3>\n\nThe `--report` option gives you a fancy report after your test suite completes.\n\n```zsh\npynose test_suite.py --report\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/nose_report.png\" alt=\"Example pynose Report\" title=\"Example pynose Report\" width=\"320\" />\n\n(NOTE: You can add `--show-report` to immediately display pynose reports after the test suite completes. Only use `--show-report` when running tests locally because it pauses the test run.)\n\n<h3>🔵 <code>behave</code> Dashboard & Reports:</h3>\n\n(The [behave_bdd/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/behave_bdd) folder can be found in the [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder.)\n\n```zsh\nbehave behave_bdd/features/ -D dashboard -D headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_behave_dashboard.png\" title=\"SeleniumBase\" width=\"520\">\n\nYou can also use `--junit` to get `.xml` reports for each <code translate=\"no\">behave</code> feature. Jenkins can use these files to display better reporting for your tests.\n\n```zsh\nbehave behave_bdd/features/ --junit -D rs -D headless\n```\n\n<h3>🔵 Allure Reports:</h3>\n\nSee: [https://allurereport.org/docs/pytest/](https://allurereport.org/docs/pytest/)\n\nSeleniumBase no longer includes `allure-pytest` as part of installed dependencies. If you want to use it, install it first:\n\n```zsh\npip install allure-pytest\n```\n\nNow your tests can create Allure results files, which can be processed by Allure Reports.\n\n```zsh\npytest test_suite.py --alluredir=allure_results\n```\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Using a Proxy Server:</h3>\n\nIf you wish to use a proxy server for your browser tests (Chromium or Firefox), you can add `--proxy=IP_ADDRESS:PORT` as an argument on the command line.\n\n```zsh\npytest proxy_test.py --proxy=IP_ADDRESS:PORT\n```\n\nIf the proxy server that you wish to use requires authentication, you can do the following (Chromium only):\n\n```zsh\npytest proxy_test.py --proxy=USERNAME:PASSWORD@IP_ADDRESS:PORT\n```\n\nSeleniumBase also supports SOCKS4 and SOCKS5 proxies:\n\n```zsh\npytest proxy_test.py --proxy=\"socks4://IP_ADDRESS:PORT\"\n\npytest proxy_test.py --proxy=\"socks5://IP_ADDRESS:PORT\"\n```\n\nTo make things easier, you can add your frequently-used proxies to PROXY_LIST in [proxy_list.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/proxy_list.py), and then use `--proxy=KEY_FROM_PROXY_LIST` to use the IP_ADDRESS:PORT of that key.\n\n```zsh\npytest proxy_test.py --proxy=proxy1\n```\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Changing the User-Agent:</h3>\n\n🔵 If you wish to change the User-Agent for your browser tests (Chromium and Firefox only), you can add `--agent=\"USER AGENT STRING\"` as an argument on the command-line.\n\n```zsh\npytest user_agent_test.py --agent=\"Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7412.EU\"\n```\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Handling Pop-Up Alerts:</h3>\n\n🔵 <code translate=\"no\">self.accept_alert()</code> automatically waits for and accepts alert pop-ups. <code translate=\"no\">self.dismiss_alert()</code> automatically waits for and dismisses alert pop-ups. On occasion, some methods like <code translate=\"no\">self.click(SELECTOR)</code> might dismiss a pop-up on its own because they call JavaScript to make sure that the <code translate=\"no\">readyState</code> of the page is <code translate=\"no\">complete</code> before advancing. If you're trying to accept a pop-up that got dismissed this way, use this workaround: Call <code translate=\"no\">self.find_element(SELECTOR).click()</code> instead, (which will let the pop-up remain on the screen), and then use <code translate=\"no\">self.accept_alert()</code> to accept the pop-up (<a href=\"https://github.com/seleniumbase/SeleniumBase/issues/600#issuecomment-647270426\">more on that here</a>). If pop-ups are intermittent, wrap code in a try/except block.\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Building Guided Tours for Websites:</h3>\n\n🔵 Learn about <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md\">SeleniumBase Interactive Walkthroughs</a> (in the `examples/tour_examples/` folder). It's great for prototyping a website onboarding experience.\n\n\n<a id=\"utilizing_advanced_features\"></a>\n\n--------\n\n<div></div>\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Production Environments & Integrations:</h3>\n\n<div></div>\n<details>\n<summary> ▶️ Here are some things you can do to set up a production environment for your testing. (<b>click to expand</b>)</summary>\n\n<ul>\n<li>You can set up a <a href=\"https://jenkins.io/\" target=\"_blank\">Jenkins</a> build server for running tests at regular intervals. For a real-world Jenkins example of headless browser automation in action, check out the <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/azure/jenkins/ReadMe.md\">SeleniumBase Jenkins example on Azure</a> or the <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/google_cloud/ReadMe.md\">SeleniumBase Jenkins example on Google Cloud</a>.</li>\n\n<li>You can use <a href=\"https://selenium.dev/documentation/en/grid/\" target=\"_blank\">the Selenium Grid</a> to scale your testing by distributing tests on several machines with parallel execution. To do this, check out the <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/utilities/selenium_grid\">SeleniumBase selenium_grid folder</a>, which should have everything you need, including the <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/utilities/selenium_grid/ReadMe.md\">Selenium Grid ReadMe</a> to help you get started.</li>\n\n<li>If you're using the <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/mysql_installation.md\">SeleniumBase MySQL feature</a> to save results from tests running on a server machine, you can install <a href=\"https://dev.mysql.com/downloads/tools/workbench/\">MySQL Workbench</a> to help you read & write from your DB more easily.</li>\n\n<li>If you're using AWS, you can set up an <a href=\"https://aws.amazon.com/s3/\" target=\"_blank\">Amazon S3</a> account for saving log files and screenshots from your tests. To activate this feature, modify <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py\">settings.py</a> with connection details in the S3 section, and add <code translate=\"no\">--with-s3-logging</code> on the command-line when running your tests.</li>\n</ul>\n\nHere's an example of running tests with some additional features enabled:\n\n```zsh\npytest [YOUR_TEST_FILE.py] --with-db-reporting --with-s3-logging\n```\n\n</details>\n\n\n<a id=\"detailed_method_specifications\"></a>\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Detailed Method Specifications and Examples:</h3>\n\n🔵 **Navigating to a web page: (and related commands)**\n\n```python\nself.open(\"https://xkcd.com/378/\")  # This method opens the specified page.\n\nself.go_back()  # This method navigates the browser to the previous page.\n\nself.go_forward()  # This method navigates the browser forward in history.\n\nself.refresh_page()  # This method reloads the current page.\n\nself.get_current_url()  # This method returns the current page URL.\n\nself.get_page_source()  # This method returns the current page source.\n```\n\n<b>ProTip™:</b> You can use the <code translate=\"no\">self.get_page_source()</code> method with Python's <code translate=\"no\">find()</code> command to parse through HTML to find something specific. (For more advanced parsing, see the <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_parse_soup.py\">BeautifulSoup example</a>.)\n\n```python\nsource = self.get_page_source()\nhead_open_tag = source.find('<head>')\nhead_close_tag = source.find('</head>', head_open_tag)\neverything_inside_head = source[head_open_tag+len('<head>'):head_close_tag]\n```\n\n🔵 **Clicking:**\n\nTo click an element on the page:\n\n```python\nself.click(\"div#my_id\")\n```\n\n**ProTip™:** In most web browsers, you can right-click on a page and select `Inspect Element` to see the CSS selector details that you'll need to create your own scripts.\n\n🔵 **Typing Text:**\n\n<code translate=\"no\">self.type(selector, text)</code>  # updates the text from the specified element with the specified value. An exception is raised if the element is missing or if the text field is not editable. Example:\n\n```python\nself.type(\"input#id_value\", \"2012\")\n```\n\nYou can also use <code translate=\"no\">self.add_text()</code> or the <a href=\"https://www.selenium.dev/documentation/webdriver/\" target=\"_blank\">WebDriver</a> <code translate=\"no\">.send_keys()</code> command, but those won't clear the text box first if there's already text inside.\n\n🔵 **Getting the text from an element on a page:**\n\n```python\ntext = self.get_text(\"header h2\")\n```\n\n🔵 **Getting the attribute value from an element on a page:**\n\n```python\nattribute = self.get_attribute(\"#comic img\", \"title\")\n```\n\n🔵 **Asserting existence of an element on a page within some number of seconds:**\n\n```python\nself.wait_for_element_present(\"div.my_class\", timeout=10)\n```\n\n(NOTE: You can also use: `self.assert_element_present(ELEMENT)`)\n\n🔵 **Asserting visibility of an element on a page within some number of seconds:**\n\n```python\nself.wait_for_element_visible(\"a.my_class\", timeout=5)\n```\n\n(NOTE: The short versions of that are `self.find_element(ELEMENT)` and `self.assert_element(ELEMENT)`. The `find_element()` version returns the element.)\n\nSince the line above returns the element, you can combine that with `.click()` as shown below:\n\n```python\nself.find_element(\"a.my_class\", timeout=5).click()\n\n# But you're better off using the following statement, which does the same thing:\n\nself.click(\"a.my_class\")  # DO IT THIS WAY!\n```\n\n**ProTip™:** You can use dots to signify class names (Ex: `div.class_name`) as a simplified version of `div[class=\"class_name\"]` within a CSS selector. \n\nYou can also use `*=` to search for any partial value in a CSS selector as shown below:\n\n```python\nself.click('a[name*=\"partial_name\"]')\n```\n\n🔵 **Asserting visibility of text inside an element on a page within some number of seconds:**\n\n```python\nself.assert_text(\"Make it so!\", \"div#trek div.picard div.quotes\")\nself.assert_text(\"Tea. Earl Grey. Hot.\", \"div#trek div.picard div.quotes\", timeout=3)\n```\n\n(NOTE: `self.find_text(TEXT, ELEMENT)` and `self.wait_for_text(TEXT, ELEMENT)` also do this. For backwards compatibility, older method names were kept, but the default timeout may be different.)\n\n🔵 **Asserting Anything:**\n\n```python\nself.assert_true(var1 == var2)\n\nself.assert_false(var1 == var2)\n\nself.assert_equal(var1, var2)\n```\n\n🔵 **Useful Conditional Statements: (with creative examples)**\n\n❓ `is_element_visible(selector):`  (visible on the page)\n\n```python\nif self.is_element_visible('div#warning'):\n    print(\"Red Alert: Something bad might be happening!\")\n```\n\n❓ `is_element_present(selector):`  (present in the HTML)\n\n```python\nif self.is_element_present('div#top_secret img.tracking_cookie'):\n    self.contact_cookie_monster()  # Not a real SeleniumBase method\nelse:\n    current_url = self.get_current_url()\n    self.contact_the_nsa(url=current_url, message=\"Dark Zone Found\")  # Not a real SeleniumBase method\n```\n\n```python\ndef is_there_a_cloaked_klingon_ship_on_this_page():\n    if self.is_element_present(\"div.ships div.klingon\"):\n        return not self.is_element_visible(\"div.ships div.klingon\")\n    return False\n```\n\n❓ `is_text_visible(text, selector):`  (text visible on element)\n\n```python\nif self.is_text_visible(\"You Shall Not Pass!\", \"h1\"):\n    self.open(\"https://www.youtube.com/watch?v=3xYXUeSmb-Y\")\n```\n\n<div></div>\n<details>\n<summary> ▶️ Click for a longer example of <code translate=\"no\">is_text_visible():</code></summary>\n\n```python\ndef get_mirror_universe_captain_picard_superbowl_ad(superbowl_year):\n    selector = \"div.superbowl_%s div.commercials div.transcript div.picard\" % superbowl_year\n    if self.is_text_visible(\"Yes, it was I who summoned you all here.\", selector):\n        return \"Picard Paramount+ Superbowl Ad 2020\"\n    elif self.is_text_visible(\"Commander, signal the following: Our Network is Secure!\"):\n        return \"Picard Mirror Universe iboss Superbowl Ad 2018\"\n    elif self.is_text_visible(\"For the Love of Marketing and Earl Grey Tea!\", selector):\n        return \"Picard Mirror Universe HubSpot Superbowl Ad 2015\"\n    elif self.is_text_visible(\"Delivery Drones... Engage\", selector):\n        return \"Picard Mirror Universe Amazon Superbowl Ad 2015\"\n    elif self.is_text_visible(\"Bing it on Screen!\", selector):\n        return \"Picard Mirror Universe Microsoft Superbowl Ad 2015\"\n    elif self.is_text_visible(\"OK Glass, Make it So!\", selector):\n        return \"Picard Mirror Universe Google Superbowl Ad 2015\"\n    elif self.is_text_visible(\"Number One, I've Never Seen Anything Like It.\", selector):\n        return \"Picard Mirror Universe Tesla Superbowl Ad 2015\"\n    elif self.is_text_visible(\"Let us make sure history never forgets the name ... Facebook\", selector):\n        return \"Picard Mirror Universe Facebook Superbowl Ad 2015\"\n    elif self.is_text_visible(\"\"\"With the first link, the chain is forged.\n                              The first speech censored, the first thought forbidden,\n                              the first freedom denied, chains us all irrevocably.\"\"\", selector):\n        return \"Picard Mirror Universe Wikimedia Superbowl Ad 2015\"\n    else:\n        raise Exception(\"Reports of my assimilation are greatly exaggerated.\")\n```\n\n</details>\n\n❓ `is_link_text_visible(link_text):`\n\n```python\nif self.is_link_text_visible(\"Stop! Hammer time!\"):\n    self.click_link(\"Stop! Hammer time!\")\n```\n\n<h3>🔵 Switching Tabs:</h3>\n\n<p>If your test opens up a new tab/window, you can switch to it. (SeleniumBase automatically switches to new tabs that don't open to <code translate=\"no\">about:blank</code> URLs.)</p>\n\n```python\nself.switch_to_window(1)  # This switches to the new tab (0 is the first one)\n```\n\n<h3>🔵 How to handle iframes:</h3>\n\n🔵 <b>iframes</b> follow the same principle as new windows: You must first switch to the iframe if you want to perform actions in there:\n\n```python\nself.switch_to_frame(\"iframe\")\n# ... Now perform actions inside the iframe\nself.switch_to_parent_frame()  # Exit the current iframe\n```\n\nTo exit from multiple iframes, use `self.switch_to_default_content()`. (If inside a single iframe, this has the same effect as `self.switch_to_parent_frame()`.)\n\n```python\nself.switch_to_frame('iframe[name=\"frame1\"]')\nself.switch_to_frame('iframe[name=\"frame2\"]')\n# ... Now perform actions inside the inner iframe\nself.switch_to_default_content()  # Back to the main page\n```\n\n🔵 You can also use a context manager to act inside iframes:\n\n```python\nwith self.frame_switch(\"iframe\"):\n    # ... Now perform actions while inside the code block\n# You have left the iframe\n```\n\nThis also works with nested iframes:\n\n```python\nwith self.frame_switch('iframe[name=\"frame1\"]'):\n    with self.frame_switch('iframe[name=\"frame2\"]'):\n        # ... Now perform actions while inside the code block\n    # You are now back inside the first iframe\n# You have left all the iframes\n```\n\n<h3>🔵 How to execute custom jQuery scripts:</h3>\n\n<p>jQuery is a powerful JavaScript library that allows you to perform advanced actions in a web browser.\nIf the web page you're on already has jQuery loaded, you can start executing jQuery scripts immediately.\nYou'd know this because the web page would contain something like the following in the HTML:</p>\n\n```html\n<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js\"></script>\n```\n\n🔵 It's OK if you want to use jQuery on a page that doesn't have it loaded yet. To do so, run the following command first:\n\n```python\nself.activate_jquery()\n```\n\n<div></div>\n<details>\n<summary> ▶️ Here are some examples of using jQuery in your scripts. (<b>click to expand</b>)</summary>\n\n```python\nself.execute_script(\"jQuery, window.scrollTo(0, 600)\")  # Scrolling the page\n\nself.execute_script(\"jQuery('#annoying-widget').hide()\")  # Hiding elements on a page\n\nself.execute_script(\"jQuery('#hidden-widget').show(0)\")  # Showing hidden elements on a page\n\nself.execute_script(\"jQuery('#annoying-button a').remove()\")  # Removing elements on a page\n\nself.execute_script(\"jQuery('%s').mouseover()\" % (mouse_over_item))  # Mouse-over elements on a page\n\nself.execute_script(\"jQuery('input#the_id').val('my_text')\")  # Fast text input on a page\n\nself.execute_script(\"jQuery('div#dropdown a.link').click()\")  # Click elements on a page\n\nself.execute_script(\"return jQuery('div#amazing')[0].text\")  # Returns the css \"text\" of the element given\n\nself.execute_script(\"return jQuery('textarea')[2].value\")  # Returns the css \"value\" of the 3rd textarea element on the page\n```\n\n(Most of the above commands can be done directly with built-in SeleniumBase methods.)\n\n</details>\n\n<h3>🔵 How to handle a restrictive CSP:</h3>\n\n❗ Some websites have a restrictive [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to prevent users from loading jQuery and other external libraries onto their websites. If you need to use jQuery or another JS library on those websites, add `--disable-csp` as a `pytest` command-line option to load a Chromium extension that bypasses the CSP.\n\n<h3>🔵 More JavaScript fun:</h3>\n\n<div></div>\n<details>\n<summary> ▶️ In this example, JavaScript creates a referral button on a page, which is then clicked. (<b>click to expand</b>)</summary>\n\n```python\nstart_page = \"https://xkcd.com/465/\"\ndestination_page = \"https://github.com/seleniumbase/SeleniumBase\"\nself.open(start_page)\nreferral_link = '''<a class='analytics test' href='%s'>Free-Referral Button!</a>''' % destination_page\nself.execute_script('''document.body.innerHTML = \\\"%s\\\"''' % referral_link)\nself.click(\"a.analytics\")  # Clicks the generated button\n```\n\n(Due to popular demand, this traffic generation example has been included in SeleniumBase with the <code translate=\"no\">self.generate_referral(start_page, end_page)</code> and the <code translate=\"no\">self.generate_traffic(start_page, end_page, loops)</code> methods.)\n\n</details>\n\n<h3>🔵 How to use deferred asserts:</h3>\n\n<p>Let's say you want to verify multiple different elements on a web page in a single test, but you don't want the test to fail until you verified several elements at once so that you don't have to rerun the test to find more missing elements on the same page. That's where deferred asserts come in. Here's an example:</p>\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass DeferredAssertTests(BaseCase):\n    def test_deferred_asserts(self):\n        self.open(\"https://xkcd.com/993/\")\n        self.wait_for_element(\"#comic\")\n        self.deferred_assert_element('img[alt=\"Brand Identity\"]')\n        self.deferred_assert_element('img[alt=\"Rocket Ship\"]')  # Will Fail\n        self.deferred_assert_element(\"#comicmap\")\n        self.deferred_assert_text(\"Fake Item\", \"ul.comicNav\")  # Will Fail\n        self.deferred_assert_text(\"Random\", \"ul.comicNav\")\n        self.deferred_assert_element('a[name=\"Super Fake !!!\"]')  # Will Fail\n        self.deferred_assert_exact_text(\"Brand Identity\", \"#ctitle\")\n        self.deferred_assert_exact_text(\"Fake Food\", \"#comic\")  # Will Fail\n        self.process_deferred_asserts()\n```\n\n<code translate=\"no\">deferred_assert_element()</code> and <code translate=\"no\">deferred_assert_text()</code> will save any exceptions that would be raised.\nTo flush out all the failed deferred asserts into a single exception, make sure to call <code translate=\"no\">self.process_deferred_asserts()</code> at the end of your test method. If your test hits multiple pages, you can call <code translate=\"no\">self.process_deferred_asserts()</code> before navigating to a new page so that the screenshot from your log files matches the URL where the deferred asserts were made.\n\n<h3>🔵 How to access raw <a href=\"https://www.selenium.dev/documentation/webdriver/\" target=\"_blank\">WebDriver</a>:</h3>\n\n<p>If you need access to any commands that come with standard <a href=\"https://www.selenium.dev/documentation/webdriver/\" target=\"_blank\">WebDriver</a>, you can call them directly like this:</p>\n\n```python\nself.driver.delete_all_cookies()\ncapabilities = self.driver.capabilities\nself.driver.find_elements(\"partial link text\", \"GitHub\")\n```\n\n(In general, you'll want to use the SeleniumBase versions of methods when available.)\n\n<h3>🔵 How to retry failing tests automatically:</h3>\n\n<p>You can use <code translate=\"no\">pytest --reruns=NUM</code> to retry failing tests that many times. Add <code translate=\"no\">--reruns-delay=SECONDS</code> to wait that many seconds between retries. Example:</p>\n\n```zsh\npytest --reruns=1 --reruns-delay=1\n```\n\n<p>You can use the <code translate=\"no\">@retry_on_exception()</code> decorator to retry failing methods. (First import: <code translate=\"no\">from seleniumbase import decorators</code>). To learn more about SeleniumBase decorators, <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/common\">click here</a>.</p>\n\n--------\n\n> \"Catch bugs in QA before deploying code to Production!\"\n\n<p align=\"left\"><a href=\"https://seleniumbase.io/error_page\" target=\"_blank\"><img src=\"https://seleniumbase.github.io/cdn/gif/error_page.gif\" alt=\"Catch bugs in QA before deploying code to Production!\" title=\"Catch bugs in QA before deploying code to Production!\" /></a></p>\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Wrap-Up</h3>\n\n<p>\n<div><b>If you see something, say something!</b></div>\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase/issues?q=is%3Aissue+is%3Aclosed\"><img src=\"https://img.shields.io/github/issues-closed-raw/seleniumbase/SeleniumBase.svg?color=22BB88\" title=\"Closed Issues\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase/pulls?q=is%3Apr+is%3Aclosed\"><img src=\"https://img.shields.io/github/issues-pr-closed/seleniumbase/SeleniumBase.svg?logo=github&logoColor=white&color=22BB99\" title=\"Closed Pull Requests\" /></a></div>\n</p>\n\n<p align=\"left\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_10t.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"274\" /></a></p>\n\n<a href=\"https://pypi.org/project/seleniumbase/\" target=\"_blank\"><img src=\"https://img.shields.io/pypi/pyversions/seleniumbase.svg?color=22AAEE&logo=python&logoColor=FEDC54\" title=\"Supported Python Versions\" /></a>\n\n<p><div>\n<span><a href=\"https://www.youtube.com/playlist?list=PLp9uKicxkBc5UIlGi2BuE3aWC7JyXpD3m\"><img src=\"https://seleniumbase.github.io/cdn/img/youtube.png\" title=\"SeleniumBase Playlist on YouTube\" alt=\"SeleniumBase Playlist on YouTube\" width=\"70\" /></a></span>\n<span><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/img/social/share_github.svg\" title=\"SeleniumBase on GitHub\" alt=\"SeleniumBase on GitHub\" width=\"64\" /></a></span>\n<span><a href=\"https://discord.gg/EdhQTn3EyE\"><img src=\"https://seleniumbase.github.io/other/discord_icon.png\" title=\"SeleniumBase on Discord\" alt=\"SeleniumBase on Discord\" width=\"66\" /></a></span>\n<span><a href=\"https://www.facebook.com/SeleniumBase\"><img src=\"https://seleniumbase.io/img/social/share_facebook.svg\" title=\"SeleniumBase on Facebook\" alt=\"SeleniumBase on Facebook\" width=\"62\" /></a></span>\n</div></p>\n\n<p><div><b><a href=\"https://github.com/mdmintz\">https://github.com/mdmintz</a></b></div></p>\n\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb3.png\" title=\"SeleniumBase\" width=\"310\" /></a></div>\n<div><a href=\"https://seleniumbase.io\"><img src=\"https://img.shields.io/badge/docs-seleniumbase.io-11BBAA.svg\" alt=\"SeleniumBase Docs\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE\"><img src=\"https://img.shields.io/badge/license-MIT-22BBCC.svg\" title=\"SeleniumBase\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase/stargazers\"><img src=\"https://img.shields.io/github/stars/seleniumbase/seleniumbase.svg?color=19A57B\" title=\"Stargazers\" /></a></div>\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://img.shields.io/badge/tested%20with-SeleniumBase-04C38E.svg\" alt=\"Tested with SeleniumBase\" /></a> <img src=\"https://views.whatilearened.today/views/github/seleniumbase/SeleniumBase.svg\" width=\"98px\" height=\"20px\" alt=\"Views\" /></div>\n<div align=\"left\"></div>\n<div><a href=\"https://pepy.tech/projects/seleniumbase?timeRange=threeMonths&category=version&includeCIDownloads=true&granularity=daily&viewType=line&versions=*\" target=\"_blank\"><img src=\"https://static.pepy.tech/badge/seleniumbase\" alt=\"SeleniumBase PyPI downloads\" /></a> <a href=\"https://discord.gg/EdhQTn3EyE\" target=\"_blank\"><img src=\"https://img.shields.io/discord/727927627830001734?color=7289DA&label=Discord&logo=discord&logoColor=white\"/></a></div>\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nIf you've found a security vulnerability in SeleniumBase, (or a dependency we use), please open an issue.\n\n[github.com/seleniumbase/SeleniumBase/issues](https://github.com/seleniumbase/SeleniumBase/issues)\n\nPlease describe the results you're seeing, and the results you're expecting.\n"
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-cayman\ntitle: SeleniumBase\ndescription: Reliable Browser Automation & Testing"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "# Test the SeleniumBase Python package with Azure Pipelines.\n# https://docs.microsoft.com/azure/devops/pipelines/languages/python\n\ntrigger:\n- master\n\njobs:\n\n- job: 'Test'\n  pool:\n    vmImage: 'Ubuntu-22.04'\n  strategy:\n    matrix:\n      Python3_7:\n        python.version: '3.7'\n      Python3_8:\n        python.version: '3.8'\n      Python3_9:\n        python.version: '3.9'\n      Python3_10:\n        python.version: '3.10'\n      Python3_11:\n        python.version: '3.11'\n    maxParallel: 5\n\n  steps:\n  - task: UsePythonVersion@0\n    inputs:\n      versionSpec: '$(python.version)'\n      architecture: 'x64'\n\n  - script: python -m pip install --upgrade pip && pip --version\n    displayName: 'Install/upgrade pip'\n\n  - script: python -m pip install seleniumbase\n    displayName: 'Verify install from PyPI'\n\n  #- script: python -m pip install -r requirements.txt --upgrade\n  #  displayName: 'Install dependencies'\n\n  - script: python -m pip install -e .\n    displayName: 'Install SeleniumBase'\n\n  - script: |\n      sudo apt install google-chrome-stable\n    displayName: 'Install Chrome'\n\n  #- script: |\n  #    sudo apt-get install firefox\n  #  displayName: 'Install Firefox'\n\n  - script: |\n      seleniumbase\n      sbase\n    displayName: 'Check the console scripts interface'\n\n  - script: |\n      seleniumbase install chromedriver\n    displayName: 'Install chromedriver'\n\n  - script: |\n      echo \"def test_1(): pass\" > nothing.py\n      pytest nothing.py\n    displayName: 'Make sure pytest is working'\n\n  #- script: python -m pytest examples/unit_tests/verify_framework.py\n  #  displayName: 'Run pytest verify_framework.py'\n\n  - script: python -m pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n    displayName: 'Run pytest boilerplate_test.py --browser=chrome --headless'\n\n  #- script: python -m pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n  #  displayName: 'Run pytest boilerplate_test.py --browser=firefox --headless'\n\n  #- script: python -m pytest examples/test_demo_site.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n  #  displayName: 'Run pytest test_demo_site.py --browser=chrome --headless'\n\n  #- script: python -m pytest examples/my_first_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n  #  displayName: 'Run pytest my_first_test.py --browser=chrome --headless'\n\n  #- script: python -m pytest examples/test_inspect_html.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml\n  #  displayName: 'Run pytest test_inspect_html.py --browser=chrome --headless'\n\n  - task: PublishTestResults@2\n    inputs:\n      testResultsFiles: '**/test-results.xml'\n      testRunTitle: 'Python $(python.version)'\n    condition: succeededOrFailed()\n\n#- job: 'Publish'\n#  dependsOn: 'Test'\n#  pool:\n#    vmImage: 'Ubuntu-22.04'\n\n#  steps:\n#  - task: UsePythonVersion@0\n#    inputs:\n#      versionSpec: '3.x'\n#      architecture: 'x64'\n\n#  - script: python setup.py sdist\n#    displayName: 'Build sdist'\n"
  },
  {
    "path": "examples/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Example Tests:</h2>\n\n<p align=\"left\"><a align=\"center\" href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py\"><img align=\"center\" src=\"https://seleniumbase.github.io/cdn/img/sb_demo_page.png\" alt=\"SeleniumBase Demo Page\" width=\"420\" /></a></p>\n\n* <b>SeleniumBase</b> \"tests\" are run with <b>pytest</b>.\n* Chrome is the default browser if not specified.\n* Tests are structured using [25 unique syntax formats](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md).\n* Logs from test failures are saved to `./latest_logs/`.\n* Tests can be run with [multiple command-line options](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md).\n* Examples can be found in [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples).\n* For stealthy examples, see [SeleniumBase/examples/cdp_mode/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode).\n\n(NOTE: Some example tests fail on purpose to demonstrate [logging features](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md).)\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo7.png\" title=\"SeleniumBase\" width=\"32\" /> Example tests with run commands to help you get started:</h3>\n\n--------\n\nRun an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py): (Default option: `--chrome`)\n\n```zsh\npytest my_first_test.py\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/fast_swag.gif\" title=\"SeleniumBase Demo Page\" /><br />\n\n--------\n\nHere's one way of changing the browser to Firefox:\n\n```zsh\npytest my_first_test.py --firefox\n```\n\n--------\n\nAnother [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py) for a web page that has lots of different HTML items:\n\n```zsh\npytest test_demo_site.py\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/demo_page_4.gif\" title=\"SeleniumBase Demo Page\" /><br />\n\n--------\n\nRun an example test in `--demo` mode: (highlight assertions)\n\n```zsh\npytest test_swag_labs.py --demo\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/swag_demo_3.gif\" /><br />\n\n--------\n\nRun [test_coffee_cart.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_coffee_cart.py) to test the [Coffee Cart](https://seleniumbase.io/coffee/) app:\n\n```zsh\npytest test_coffee_cart.py --demo\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/coffee_cart.gif\" title=\"SeleniumBase Coffee App Example\" alt=\"SeleniumBase Example\" title=\"SeleniumBase Coffee App Example\" />\n\nYou can debug tests easily with the included `pdbp` (Pdb+) debugger:\n\n```zsh\npytest test_coffee_cart.py --trace\n```\n\n<h3><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/cdn/gif/coffee_pdbp.gif\" alt=\"SeleniumBase test with the pdbp debugger\" title=\"SeleniumBase test with the pdbp debugger\" /></a></h3>\n\n--------\n\nRun a [Wordle-solver example](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/wordle_test.py):\n\n```zsh\npytest wordle_test.py\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/wordle.gif\" title=\"Solving Wordle with SeleniumBase\" /><br />\n\n--------\n\nRun an example test in `--headless` mode: (invisible browser)\n\n```zsh\npytest my_first_test.py --headless\n```\n\n--------\n\nRun an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_swag_labs.py) using Chrome's mobile device emulator: (default settings)\n\n```zsh\npytest test_swag_labs.py --mobile\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/swag_mobile_2.gif\" title=\"SeleniumBase Mobile Mode\" /><br />\n\n--------\n\nRun an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_xkcd.py) in `--demo` mode: (highlight assertions)\n\n```zsh\npytest test_xkcd.py --demo\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/xkcd_vid.gif\" title=\"SeleniumBase Demo Mode\" /><br />\n\n--------\n\nRun a [test suite](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_suite.py) with verbose output: (see more details)\n\n```zsh\npytest test_suite.py -v\n```\n\n--------\n\nRun a test suite using multiple parallel processes (`-n=NUM`):\n\n```zsh\npytest test_suite.py -n=8\n```\n\n--------\n\nRun a [parameterized test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/parameterized_test.py): (Generates multiple tests from one)\n\n```zsh\npytest parameterized_test.py -v\n```\n\n--------\n\nRun a test suite and generate a SeleniumBase Dashboard:\n\n```zsh\npytest test_suite.py --dashboard\n```\n\n--------\n\nRun a test suite and generate a `pytest` report:\n\n```zsh\npytest test_suite.py --html=report.html\n```\n\n--------\n\nRun a [failing test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_fail.py): (See the `latest_logs/` folder for logs and screenshots)\n\n```zsh\npytest test_fail.py\n```\n\n--------\n\nRun a failing test that activates `pdb` debug mode on failure:\n\n```zsh\npytest test_fail.py --pdb -s\n```\n\n> (**`pdb`** commands: `n`, `c`, `s`, `u`, `d` => `next`, `continue`, `step`, `up`, `down`)\n\n--------\n\nRun a test suite that demonstrates the use of `pytest` markers:\n\n```zsh\npytest -m marker_test_suite -v\n```\n\n--------\n\nRun a test suite that reuses the browser session between tests:\n\n```zsh\npytest test_suite.py --rs\n```\n\n--------\n\nRun an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/rate_limiting_test.py) demonstrating the `rate_limited` Python decorator:\n\n```zsh\npytest rate_limiting_test.py\n```\n\n--------\n\nRun an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/upload_file_test.py) that demonstrates how to upload a file to a website:\n\n```zsh\npytest upload_file_test.py\n```\n\n--------\n\n🎖️  **SeleniumBase Commander** is a GUI for `pytest`:\n\n```zsh\nsbase gui\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sbase_commander.png\" title=\"SeleniumBase Commander / GUI for pytest\" width=\"520\" /><br />\n\n--------\n\n<b>SeleniumBase tests can also be run with `pynose`:</b>\n\n```zsh\npynose my_first_test.py\n```\n\n--------\n\nRun an example test suite and generate a `pynose` test report:\n\n```zsh\npynose test_suite.py --report --show-report\n```\n\n--------\n\nRun an example test using a `pynose` configuration file:\n\n```zsh\npynose my_first_test.py --config=example_config.cfg\n```\n\n--------\n\nFor more advanced **run commands**, such as using a proxy server, see [../help_docs/customizing_test_runs.md](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md)\n\n--------\n\nIf you just need to perform some quick website verification on various devices, you can use the <a href=\"https://seleniumbase.io/devices/\">SeleniumBase Device Farm</a>. Just plug in a website URL, and it will display how the website looks on four different devices:\n\n<a href=\"https://seleniumbase.io/devices/?url=github.com\"><img src=\"https://seleniumbase.github.io/cdn/img/github_demo2.png\" width=\"540\" title=\"SeleniumBase Mobile Mode\" /></a><br />\n\n--------\n\nTo make things easier, here's a **simple GUI program** that allows you to run a few example tests by pressing a button:\n\n```zsh\npython gui_test_runner.py\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/gui_test_runner.png\" title=\"GUI Test Runner\" width=\"320\" />\n\n(The newer **[SeleniumBase Commander](https://seleniumbase.io/help_docs/commander/)** improves on that.)\n\n--------\n\n<h3><a href=\"https://discord.gg/EdhQTn3EyE\"><img src=\"https://seleniumbase.github.io/other/discord_icon.png\" title=\"Join the SeleniumBase chat on Discord\" alt=\"Join the SeleniumBase chat on Discord\" width=\"44\" /></a> <a href=\"https://discord.gg/EdhQTn3EyE\">Join the SeleniumBase chat on Discord!</a></h3>\n\nAsk questions. Find answers. Learn how to automate!\n\n--------\n\n<img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb.png\" title=\"SeleniumBase\" width=\"320\" />\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/tested%20with-SeleniumBase-04C38E.svg\" alt=\"Tested with SeleniumBase\" /></a>\n"
  },
  {
    "path": "examples/__init__.py",
    "content": ""
  },
  {
    "path": "examples/basic_test.py",
    "content": "\"\"\"Add an item to a shopping cart. Verify. Remove item. Verify.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_basics(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n        self.assert_element(\"div.inventory_list\")\n        self.assert_exact_text(\"Products\", \"span.title\")\n        self.click('button[name*=\"backpack\"]')\n        self.click(\"#shopping_cart_container a\")\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click('button:contains(\"Remove\")')  # HTML innerText\n        self.assert_text_not_visible(\"Backpack\", \"div.cart_item\")\n        self.js_click(\"a#logout_sidebar_link\")\n        self.assert_element(\"div#login_button_container\")\n"
  },
  {
    "path": "examples/behave_bdd/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## [<img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\">](https://github.com/seleniumbase/SeleniumBase/) 🐝 Behave test runner for SeleniumBase 🐝\n\n🐝 (Utilizes the [Behave BDD Python library](https://github.com/behave/behave). For more info, see the [Behave tutorial](https://behave.readthedocs.io/en/stable/tutorial/) and read about [Behave's Gherkin model](https://behave.readthedocs.io/en/stable/gherkin/).)\n\n🐝 Behave examples with SeleniumBase: [SeleniumBase/examples/behave_bdd](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd)\n\n```zsh\n> cd examples/behave_bdd/\n> behave features/realworld.feature -T -D dashboard -k\n\nDashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n********************************************************************************\nFeature: SeleniumBase scenarios for the RealWorld App # features/realworld.feature:1\n\n  Scenario: Verify RealWorld App (log in / sign out)  # features/realworld.feature:3\n    Given Open \"seleniumbase.io/realworld/login\"      # ../../sbase/steps.py:10\n    And Clear Session Storage                         # ../../sbase/steps.py:669\n    When Type \"demo_user\" into \"#username\"            # ../../sbase/steps.py:40\n    And Type \"secret_pass\" into \"#password\"           # ../../sbase/steps.py:40\n    And Do MFA \"GAXG2MTEOR3DMMDG\" into \"#totpcode\"    # ../../sbase/steps.py:322\n    Then Assert exact text \"Welcome!\" in \"h1\"         # ../../sbase/steps.py:157\n    And Highlight \"img#image1\"                        # ../../sbase/steps.py:184\n    And Click 'a:contains(\"This Page\")'               # ../../sbase/steps.py:27\n    And Save screenshot to logs                       # ../../sbase/steps.py:239\n    When Click link \"Sign out\"                        # ../../sbase/steps.py:195\n    Then Assert element 'a:contains(\"Sign in\")'       # ../../sbase/steps.py:120\n    And Assert text \"You have been signed out!\"       # ../../sbase/steps.py:145\n   ✅ Scenario Passed!\n\n- Dashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n--- LogPath: /Users/michael/github/SeleniumBase/examples/behave_bdd/latest_logs/\n==================================================================================\n1 feature passed, 0 failed, 0 skipped\n1 scenario passed, 0 failed, 0 skipped\n12 steps passed, 0 failed, 0 skipped, 0 undefined\nTook 0m4.682s\n```\n\n🐝 Another example, which uses higher-level Behave steps to simplify the ``.feature`` file:\n\n```zsh\n> cd examples/behave_bdd/\n> behave features/calculator.feature:61 -T -D dashboard -k\n\nDashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n********************************************************************************\nFeature: SeleniumBase scenarios for the Calculator App # features/calculator.feature:1\n\n  Background:   # features/calculator.feature:3\n\n  Scenario: 7.0 × (3 + 3) = 42        # features/calculator.feature:49\n    Given Open the Calculator App     # features/steps/calculator.py:4\n    When Press C                      # features/steps/calculator.py:9\n    And Press 7                       # features/steps/calculator.py:79\n    And Press .                       # features/steps/calculator.py:104\n    And Press 0                       # features/steps/calculator.py:94\n    And Press ×                       # features/steps/calculator.py:29\n    And Press (                       # features/steps/calculator.py:14\n    And Press 3                       # features/steps/calculator.py:59\n    And Press +                       # features/steps/calculator.py:39\n    And Press 3                       # features/steps/calculator.py:59\n    And Press )                       # features/steps/calculator.py:19\n    Then Verify output is \"7.0×(3+3)\" # features/steps/calculator.py:135\n    When Press =                      # features/steps/calculator.py:44\n    Then Verify output is \"42\"        # features/steps/calculator.py:135\n   ✅ Scenario Passed!\n\n- Dashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n--- LogPath: /Users/michael/github/SeleniumBase/examples/behave_bdd/latest_logs/\n==================================================================================\n1 feature passed, 0 failed, 0 skipped\n1 scenario passed, 0 failed, 8 skipped\n14 steps passed, 0 failed, 60 skipped, 0 undefined\nTook 0m1.672s\n```\n\n🐝⚪ With the Dashboard enabled, you'll get one of these:\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_behave_dashboard.png\" title=\"SeleniumBase\" width=\"600\">\n\n### 🐝 Behave-Gherkin files:\n\n🐝 The ``*.feature`` files can use any step seen from:\n\n```zsh\nbehave --steps-catalog\n```\n\n🐝 SeleniumBase includes several pre-made Behave steps, which you can use by creating a Python file with the following line in your ``features/steps/`` directory:\n\n```python\nfrom seleniumbase.behave import steps  # noqa\n```\n\n🐝 Inside your ``features/environment.py`` file, you should have the following:\n\n```python\nfrom seleniumbase import BaseCase\nfrom seleniumbase.behave import behave_sb\nbehave_sb.set_base_class(BaseCase)  # Accepts a BaseCase subclass\nfrom seleniumbase.behave.behave_sb import before_all  # noqa\nfrom seleniumbase.behave.behave_sb import before_feature  # noqa\nfrom seleniumbase.behave.behave_sb import before_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import before_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import after_feature  # noqa\nfrom seleniumbase.behave.behave_sb import after_all  # noqa\n```\n\n🐝 If you've already created a subclass of ``BaseCase`` with custom methods, you can swap ``BaseCase`` in with your own subclass, which will allow you to easily use your own custom methods in your Behave step definitions.\n\n🐝 Here's an example Python file in the ``features/steps/`` folder:\n\n```python\nfrom behave import step\n\n\n@step(\"Open the Swag Labs Login Page\")\ndef go_to_swag_labs(context):\n    sb = context.sb\n    sb.open(\"https://www.saucedemo.com\")\n    sb.clear_local_storage()\n\n\n@step(\"Login to Swag Labs with {user}\")\ndef login_to_swag_labs(context, user):\n    sb = context.sb\n    sb.type(\"#user-name\", user)\n    sb.type(\"#password\", \"secret_sauce\\n\")\n\n\n@step(\"Verify that the current user is logged in\")\ndef verify_logged_in(context):\n    sb = context.sb\n    sb.assert_element(\"#header_container\")\n    sb.assert_element(\"#react-burger-menu-btn\")\n    sb.assert_element(\"#shopping_cart_container\")\n\n\n@step('Add \"{item}\" to cart')\ndef add_item_to_cart(context, item):\n    sb = context.sb\n    sb.click('div.inventory_item:contains(\"%s\") button[name*=\"add\"]' % item)\n```\n\n🐝 A ``*.feature`` file could look like this:\n\n```gherkin\nFeature: SeleniumBase scenarios for the Swag Labs App\n\n  Background:\n    Given Open the Swag Labs Login Page\n\n  Scenario: User can order a backpack from the store\n    When Login to Swag Labs with standard_user\n    Then Verify that the current user is logged in\n    And Save price of \"Backpack\" to <item_price>\n    When Add \"Backpack\" to Cart\n    Then Verify shopping cart badge shows 1 item(s)\n    When Click on shopping cart icon\n    And Click Checkout\n    And Enter checkout info: First, Last, 12345\n    And Click Continue\n    Then Verify 1 \"Backpack\"(s) in cart\n    And Verify cost of \"Backpack\" is <item_price>\n    And Verify item total is $29.99\n    And Verify tax amount is $2.40\n    And Verify total cost is $32.39\n    When Click Finish\n    Then Verify order complete\n    When Logout from Swag Labs\n    Then Verify on Login page\n```\n\n🐝 Here's another example of a ``*.feature`` file:\n\n```gherkin\nFeature: SeleniumBase scenarios for the RealWorld App\n\n  Scenario: Verify RealWorld App (log in / sign out)\n    Given Open \"seleniumbase.io/realworld/login\"\n    And Clear Session Storage\n    When Type \"demo_user\" into \"#username\"\n    And Type \"secret_pass\" into \"#password\"\n    And Do MFA \"GAXG2MTEOR3DMMDG\" into \"#totpcode\"\n    Then Assert text \"Welcome!\" in \"h1\"\n    And Highlight element \"img#image1\"\n    And Click 'a:contains(\"This Page\")'\n    And Save screenshot to logs\n    When Click link \"Sign out\"\n    Then Assert element 'a:contains(\"Sign in\")'\n    And Assert text \"You have been signed out!\"\n```\n\n🐝 If there's a test failure, that's easy to spot:\n\n```zsh\nFeature: SeleniumBase scenarios for the Fail Page # features/fail_page.feature:1\n\n  Scenario: Fail test on purpose to see what happens  # features/fail_page.feature:3\n    When Open the Fail Page                           # features/steps/fail_page.py:4\n    Then Fail test on purpose                         # features/steps/fail_page.py:9\n      Assertion Failed: This test fails on purpose!\n      Captured stdout:\n      >>> STEP FAILED:  (#2) Fail test on purpose\n      Class / Feature:  SeleniumBase scenarios for the Fail Page\n      Test / Scenario:  Fail test on purpose to see what happens\n\n   ❌ Scenario Failed!\n```\n\n🐝🎖️ For convenience, the [SeleniumBase Behave GUI](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/behave_gui.md) lets you run ``behave`` scripts from a Desktop app.\n\n🐝🎖️ To launch it, call ``sbase behave-gui`` or ``sbase gui-behave``:\n\n```zsh\nsbase behave-gui\n* Starting the SeleniumBase Behave Commander GUI App...\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sbase_behave_gui_wide_5.png\" title=\"SeleniumBase\" width=\"600\">\n\n🐝🎖️ You can customize the tests that show up there:\n\n```zsh\nsbase behave-gui  # all tests\nsbase behave-gui -i=calculator  # tests with \"calculator\" in the name\nsbase behave-gui features/  # tests located in the \"features/\" folder\nsbase behave-gui features/calculator.feature  # tests in that feature\n```\n\n--------\n\n<div>To learn more about SeleniumBase, check out the Docs Site:</div>\n<a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a>\n\n<div>All the code is on GitHub:</div>\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase on GitHub\" /></a>\n"
  },
  {
    "path": "examples/behave_bdd/__init__.py",
    "content": ""
  },
  {
    "path": "examples/behave_bdd/behave.ini",
    "content": "[behave]\nshow_skipped=false\nshow_timings=false\n"
  },
  {
    "path": "examples/behave_bdd/features/__init__.py",
    "content": ""
  },
  {
    "path": "examples/behave_bdd/features/behave.ini",
    "content": "[behave]\nshow_skipped=false\nshow_timings=false\n"
  },
  {
    "path": "examples/behave_bdd/features/calculator.feature",
    "content": "Feature: SeleniumBase scenarios for the Calculator App\n\n  Background:\n    Given Open the Calculator App\n\n  Scenario: Pressing \"C\" outputs \"0\"\n    When Press C\n    Then Verify output is \"0\"\n\n  Scenario: 1 + 2 + 3 + 4 + 5 = 15\n    When Press C\n    And Press 1\n    And Press +\n    And Press 2\n    And Press +\n    And Press 3\n    And Press +\n    And Press 4\n    And Press +\n    And Press 5\n    Then Verify output is \"1+2+3+4+5\"\n    When Press =\n    Then Verify output is \"15\"\n\n  Scenario: 6 × 7 × 8 × 9 = 3024\n    When Press C\n    And Press 6\n    And Press ×\n    And Press 7\n    And Press ×\n    And Press 8\n    And Press ×\n    And Press 9\n    Then Verify output is \"6×7×8×9\"\n    When Press =\n    Then Verify output is \"3024\"\n\n  Scenario: 44 - 11 = 33\n    When Press C\n    And Press 4\n    And Press 4\n    And Press -\n    And Press 1\n    And Press 1\n    Then Verify output is \"44-11\"\n    When Press =\n    Then Verify output is \"33\"\n\n  Scenario: 7.0 × (3 + 3) = 42\n    When Press C\n    And Press 7\n    And Press .\n    And Press 0\n    And Press ×\n    And Press (\n    And Press 3\n    And Press +\n    And Press 3\n    And Press )\n    Then Verify output is \"7.0×(3+3)\"\n    When Press =\n    Then Verify output is \"42\"\n\n  Scenario: 4.5 × 68 = 306\n    When Press C\n    And Evaluate [4.5 × 68]\n    Then Verify output is \"306\"\n\n  Scenario Outline: <First> ÷ <Second> = <Result>\n    When Press C\n    And Press [<First>]\n    And Press ÷\n    And Press [<Second>]\n    And Press =\n    Then Verify output is \"<Result>\"\n    Examples:\n      | First | Second | Result |\n      | 1948  | 4      | 487    |\n      | 21    | 0      | Error  |\n\n  Scenario: Save calculator screenshot to logs\n    Given Press [1337]\n    Given Save calculator screenshot to logs\n"
  },
  {
    "path": "examples/behave_bdd/features/environment.py",
    "content": "from seleniumbase import BaseCase\nfrom seleniumbase.behave import behave_sb\nbehave_sb.set_base_class(BaseCase)  # Accepts a BaseCase subclass\nfrom seleniumbase.behave.behave_sb import before_all  # noqa\nfrom seleniumbase.behave.behave_sb import before_feature  # noqa\nfrom seleniumbase.behave.behave_sb import before_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import before_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import after_feature  # noqa\nfrom seleniumbase.behave.behave_sb import after_all  # noqa\n"
  },
  {
    "path": "examples/behave_bdd/features/fail_page.feature",
    "content": "Feature: SeleniumBase scenarios for the Fail Page\n\n  Scenario: Fail test on purpose to see what happens\n    When Open the Fail Page\n    Then Fail test on purpose\n"
  },
  {
    "path": "examples/behave_bdd/features/login_app.feature",
    "content": "Feature: SeleniumBase scenarios for the Simple App\n\n  Scenario: Verify the Simple App (Login / Logout)\n    Given Open \"seleniumbase.io/simple/login\"\n    And Type \"demo_user\" into \"#username\"\n    And Type \"secret_pass\" into \"#password\"\n    And Click 'a:contains(\"Sign in\")'\n    And Assert exact text \"Welcome!\" in \"h1\"\n    And Assert element \"img#image1\"\n    And Highlight \"#image1\"\n    And Click link \"Sign out\"\n    And Assert text \"signed out\" in \"#top_message\"\n"
  },
  {
    "path": "examples/behave_bdd/features/realworld.feature",
    "content": "Feature: SeleniumBase scenarios for the RealWorld App\n\n  Scenario: Verify RealWorld App (log in / sign out)\n    Given Open \"seleniumbase.io/realworld/login\"\n    And Clear Session Storage\n    When Type \"demo_user\" into \"#username\"\n    And Type \"secret_pass\" into \"#password\"\n    And Do MFA \"GAXG2MTEOR3DMMDG\" into \"#totpcode\"\n    Then Assert exact text \"Welcome!\" in \"h1\"\n    And Highlight \"img#image1\"\n    And Click 'a:contains(\"This Page\")'\n    And Save screenshot to logs\n    When Click link \"Sign out\"\n    Then Assert element 'a:contains(\"Sign in\")'\n    And Assert text \"You have been signed out!\"\n"
  },
  {
    "path": "examples/behave_bdd/features/steps/__init__.py",
    "content": ""
  },
  {
    "path": "examples/behave_bdd/features/steps/calculator.py",
    "content": "from behave import step\n\n\n@step(\"Open the Calculator App\")\ndef go_to_calculator(context):\n    context.sb.open(\"https://seleniumbase.io/apps/calculator\")\n\n\n@step(\"Press C\")\ndef press_c(context):\n    context.sb.click(\"button#clear\")\n\n\n@step(\"Press (\")\ndef press_open_paren(context):\n    context.sb.click('button[id=\"(\"]')\n\n\n@step(\"Press )\")\ndef press_close_paren(context):\n    context.sb.click('button[id=\")\"]')\n\n\n@step(\"Press ÷\")\ndef press_divide(context):\n    context.sb.click(\"button#divide\")\n\n\n@step(\"Press ×\")\ndef press_multiply(context):\n    context.sb.click(\"button#multiply\")\n\n\n@step(\"Press -\")\ndef press_subtract(context):\n    context.sb.click(\"button#subtract\")\n\n\n@step(\"Press +\")\ndef press_add(context):\n    context.sb.click(\"button#add\")\n\n\n@step(\"Press =\")\ndef press_equal(context):\n    context.sb.click(\"button#equal\")\n\n\n@step(\"Press 1\")\ndef press_1(context):\n    context.sb.click('button[id=\"1\"]')\n\n\n@step(\"Press 2\")\ndef press_2(context):\n    context.sb.click('button[id=\"2\"]')\n\n\n@step(\"Press 3\")\ndef press_3(context):\n    context.sb.click('button[id=\"3\"]')\n\n\n@step(\"Press 4\")\ndef press_4(context):\n    context.sb.click('button[id=\"4\"]')\n\n\n@step(\"Press 5\")\ndef press_5(context):\n    context.sb.click('button[id=\"5\"]')\n\n\n@step(\"Press 6\")\ndef press_6(context):\n    context.sb.click('button[id=\"6\"]')\n\n\n@step(\"Press 7\")\ndef press_7(context):\n    context.sb.click('button[id=\"7\"]')\n\n\n@step(\"Press 8\")\ndef press_8(context):\n    context.sb.click('button[id=\"8\"]')\n\n\n@step(\"Press 9\")\ndef press_9(context):\n    context.sb.click('button[id=\"9\"]')\n\n\n@step(\"Press 0\")\ndef press_0(context):\n    context.sb.click('button[id=\"0\"]')\n\n\n@step(\"Press ←\")\ndef press_delete(context):\n    context.sb.click(\"button#delete\")\n\n\n@step(\"Press .\")\ndef press_dot(context):\n    context.sb.click('button[id=\".\"]')\n\n\n@step(\"Press [{number}]\")\ndef enter_number_into_calc(context, number):\n    sb = context.sb\n    for digit in number:\n        sb.click('button[id=\"%s\"]' % digit)\n\n\n@step(\"Evaluate [{equation}]\")\ndef evaluate_equation(context, equation):\n    sb = context.sb\n    for key in equation:\n        if key == \" \":\n            continue\n        elif key == \"÷\":\n            sb.click(\"button#divide\")\n        elif key == \"×\":\n            sb.click(\"button#multiply\")\n        elif key == \"-\":\n            sb.click(\"button#subtract\")\n        elif key == \"+\":\n            sb.click(\"button#add\")\n        else:\n            sb.click('button[id=\"%s\"]' % key)\n    sb.click(\"button#equal\")\n\n\n@step('Verify output is \"{output}\"')\ndef verify_output(context, output):\n    sb = context.sb\n    sb.assert_exact_text(output, \"#output\")\n\n\n@step(\"Save calculator screenshot to logs\")\ndef save_calculator_screenshot_to_logs(context):\n    sb = context.sb\n    sb.save_screenshot_to_logs()\n"
  },
  {
    "path": "examples/behave_bdd/features/steps/fail_page.py",
    "content": "from behave import step\r\n\r\n\r\n@step(\"Open the Fail Page\")\r\ndef go_to_error_page(context):\r\n    context.sb.open(\"https://seleniumbase.io/error_page/\")\r\n\r\n\r\n@step(\"Fail test on purpose\")\r\ndef fail_on_purpose(context):\r\n    context.sb.fail(\"This test fails on purpose!\")\r\n"
  },
  {
    "path": "examples/behave_bdd/features/steps/imported.py",
    "content": "from seleniumbase.behave import steps  # noqa\r\n"
  },
  {
    "path": "examples/behave_bdd/features/steps/swag_labs.py",
    "content": "from behave import step\n\n\n@step(\"Open the Swag Labs Login Page\")\ndef go_to_swag_labs(context):\n    sb = context.sb\n    sb.open(\"https://www.saucedemo.com\")\n    sb.clear_local_storage()\n\n\n@step(\"Login to Swag Labs with {user}\")\ndef login_to_swag_labs(context, user):\n    sb = context.sb\n    sb.type(\"#user-name\", user)\n    sb.type(\"#password\", \"secret_sauce\\n\")\n\n\n@step(\"Verify that the current user is logged in\")\ndef verify_logged_in(context):\n    sb = context.sb\n    sb.assert_element(\"#header_container\")\n    sb.assert_element(\"#react-burger-menu-btn\")\n    sb.assert_element(\"#shopping_cart_container\")\n\n\n@step('Add \"{item}\" to cart')\ndef add_item_to_cart(context, item):\n    sb = context.sb\n    sb.click('div.inventory_item:contains(\"%s\") button[name*=\"add\"]' % item)\n\n\n@step('Save price of \"{item}\" to <{var}>')\ndef save_price_of_item(context, item, var):\n    sb = context.sb\n    price = sb.get_text(\n        'div.inventory_item:contains(\"%s\") .inventory_item_price' % item\n    )\n    sb.variables[var] = price\n\n\n@step('Remove \"{item}\" from cart')\ndef remove_item_to_cart(context, item):\n    sb = context.sb\n    sb.click('div.inventory_item:contains(\"%s\") button[name*=\"remove\"]' % item)\n\n\n@step(\"Verify shopping cart badge shows {number} item(s)\")\ndef verify_badge_number(context, number):\n    sb = context.sb\n    sb.assert_exact_text(number, \"span.shopping_cart_badge\")\n\n\n@step(\"Verify shopping cart badge is missing\")\ndef verify_badge_missing(context):\n    sb = context.sb\n    sb.assert_element_not_visible(\"span.shopping_cart_badge\")\n\n\n@step(\"Click on shopping cart icon\")\ndef click_shopping_cart(context):\n    sb = context.sb\n    sb.click(\"#shopping_cart_container a\")\n\n\n@step(\"Click Checkout\")\ndef click_checkout(context):\n    sb = context.sb\n    sb.click(\"#checkout\")\n\n\n@step(\"Enter checkout info: {first_name}, {last_name}, {zip_code}\")\ndef enter_checkout_info(context, first_name, last_name, zip_code):\n    sb = context.sb\n    sb.type(\"#first-name\", first_name)\n    sb.type(\"#last-name\", last_name)\n    sb.type(\"#postal-code\", zip_code)\n\n\n@step(\"Click Continue\")\ndef click_continue(context):\n    sb = context.sb\n    sb.click(\"input#continue\")\n\n\n@step('Verify {quantity} \"{item}\"(s) in cart')\ndef verify_item_in_cart(context, quantity, item):\n    sb = context.sb\n    sb.assert_exact_text(\n        quantity, 'div.cart_item:contains(\"%s\") div.cart_quantity' % item\n    )\n\n\n@step('Verify cost of \"{item}\" is <{var}>')\ndef verify_cost_of_item(context, item, var):\n    sb = context.sb\n    earlier_price = sb.variables[var]\n    sb.assert_exact_text(\n        earlier_price,\n        'div.cart_item_label:contains(\"%s\") .inventory_item_price' % item,\n    )\n\n\n@step(\"Verify item total is {item_total}\")\ndef verify_item_total(context, item_total):\n    sb = context.sb\n    sb.assert_exact_text(\n        \"Item total: %s\" % item_total, \"div.summary_subtotal_label\", timeout=1\n    )\n\n\n@step(\"Verify tax amount is {tax_amount}\")\ndef verify_tax_amount(context, tax_amount):\n    sb = context.sb\n    sb.assert_exact_text(\n        \"Tax: %s\" % tax_amount, \"div.summary_tax_label\", timeout=1\n    )\n\n\n@step(\"Verify total cost is {total_cost}\")\ndef verify_total_cost(context, total_cost):\n    sb = context.sb\n    sb.assert_exact_text(\n        \"Total: %s\" % total_cost, \"div.summary_total_label\", timeout=1\n    )\n\n\n@step(\"Click Finish\")\ndef click_finish(context):\n    sb = context.sb\n    sb.click(\"button#finish\")\n\n\n@step(\"Verify order complete\")\ndef verify_order_complete(context):\n    sb = context.sb\n    sb.assert_exact_text(\"Thank you for your order!\", \"h2\")\n    sb.assert_element('img[alt=\"Pony Express\"]')\n\n\n@step(\"Logout from Swag Labs\")\ndef logout_from_swag_labs(context):\n    sb = context.sb\n    sb.js_click(\"a#logout_sidebar_link\")\n\n\n@step(\"Verify on Login page\")\ndef verify_on_login_page(context):\n    sb = context.sb\n    sb.assert_element(\"#login-button\")\n\n\n@step(\"Sort items from Z to A\")\ndef sort_items_from_z_to_a(context):\n    sb = context.sb\n    sb.select_option_by_text(\"select.product_sort_container\", \"Name (Z to A)\")\n\n\n@step('Verify \"{item}\" on top')\ndef verify_item_on_top(context, item):\n    sb = context.sb\n    sb.assert_text(item, \"div.inventory_item_name\")\n"
  },
  {
    "path": "examples/behave_bdd/features/swag_labs.feature",
    "content": "Feature: SeleniumBase scenarios for the Swag Labs App\n\n  Background:\n    Given Open the Swag Labs Login Page\n\n  Scenario: User can log in and log out successfully\n    When Login to Swag Labs with standard_user\n    Then Verify that the current user is logged in\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can order a backpack from the store\n    When Login to Swag Labs with standard_user\n    Then Verify that the current user is logged in\n    And Save price of \"Backpack\" to <item_price>\n    When Add \"Backpack\" to Cart\n    Then Verify shopping cart badge shows 1 item(s)\n    When Click on shopping cart icon\n    And Click Checkout\n    And Enter checkout info: First, Last, 12345\n    And Click Continue\n    Then Verify 1 \"Backpack\"(s) in cart\n    And Verify cost of \"Backpack\" is <item_price>\n    And Verify item total is $29.99\n    And Verify tax amount is $2.40\n    And Verify total cost is $32.39\n    When Click Finish\n    Then Verify order complete\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can order two items from the store\n    When Login to Swag Labs with standard_user\n    And Add \"Bike Light\" to Cart\n    And Add \"Fleece Jacket\" to Cart\n    Then Verify shopping cart badge shows 2 item(s)\n    When Click on shopping cart icon\n    And Click Checkout\n    And Enter checkout info: First, Last, 54321\n    And Click Continue\n    Then Verify 1 \"Bike Light\"(s) in cart\n    Then Verify 1 \"Fleece Jacket\"(s) in cart\n    And Verify item total is $59.98\n    And Verify tax amount is $4.80\n    And Verify total cost is $64.78\n    When Click Finish\n    Then Verify order complete\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can sort items by name from Z to A\n    When Login to Swag Labs with standard_user\n    And Sort items from Z to A\n    Then Verify \"Test.allTheThings() T-Shirt\" on top\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can add & remove 6 items to/from cart\n    When Login to Swag Labs with standard_user\n    And Add \"Backpack\" to Cart\n    And Add \"Bike Light\" to Cart\n    And Add \"Bolt T-Shirt\" to Cart\n    And Add \"Fleece Jacket\" to Cart\n    And Add \"Onesie\" to Cart\n    And Add \"Test.allTheThings() T-Shirt\" to Cart\n    Then Verify shopping cart badge shows 6 item(s)\n    When Remove \"Backpack\" from Cart\n    And Remove \"Bike Light\" from Cart\n    And Remove \"Bolt T-Shirt\" from Cart\n    And Remove \"Fleece Jacket\" from Cart\n    And Remove \"Onesie\" from Cart\n    And Remove \"Test.allTheThings() T-Shirt\" from Cart\n    Then Verify shopping cart badge is missing\n    When Logout from Swag Labs\n    Then Verify on Login page\n"
  },
  {
    "path": "examples/boilerplates/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_text_f.png\" title=\"SeleniumBase\" align=\"center\" width=\"290\">\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Example Boilerplates:</h2>\n\n* Boilerplate files are located in the [SeleniumBase => examples/boilerplates/](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates) folder.\n* Boilerplates can help you structure tests using common design patterns such as the Page Object Model.\n* For all 20 SeleniumBase design patterns, see: [Syntax Formats](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md)\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Boilerplate Files:</h2>\n\n* <b>[base_test_case.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/base_test_case.py):</b> This example demonstrates a test class inheriting [BaseCase](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py) for adding new methods.\n* <b>[page_objects.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/page_objects.py):</b> This example demonstrates Page Objects for reusing commonly-used selectors in tests.\n* <b>[boilerplate_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/boilerplate_test.py):</b> This example demonstrates inheritance of the above files for making a complete test.\n* <b>[classic_obj_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/classic_obj_test.py):</b> This example demonstrates the classic Page Object Model for structuring tests.\n\n--------\n\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_gs.png\" alt=\"SeleniumBase\" width=\"300\" /></a></div>\n"
  },
  {
    "path": "examples/boilerplates/__init__.py",
    "content": ""
  },
  {
    "path": "examples/boilerplates/base_test_case.py",
    "content": "\"\"\"Use this as a boilerplate for your test framework.\nDefine customized library methods in a class like this.\nThen have your test classes inherit it.\nBaseTestCase inherits SeleniumBase methods from BaseCase.\"\"\"\nfrom seleniumbase import BaseCase\n\n\nclass BaseTestCase(BaseCase):\n    def setUp(self):\n        super().setUp()\n        # <<< Run custom setUp() code for tests AFTER the super().setUp() >>>\n\n    def tearDown(self):\n        self.save_teardown_screenshot()  # If test fails, or if \"--screenshot\"\n        if self.has_exception():\n            # <<< Run custom code if the test failed. >>>\n            pass\n        else:\n            # <<< Run custom code if the test passed. >>>\n            pass\n        # (Wrap unreliable tearDown() code in a try/except block.)\n        # <<< Run custom tearDown() code BEFORE the super().tearDown() >>>\n        super().tearDown()\n\n    def login(self):\n        # <<< Placeholder. Add your code here. >>>\n        # Reduce duplicate code in tests by having reusable methods like this.\n        # If the UI changes, the fix can be applied in one place.\n        pass\n\n    def example_method(self):\n        # <<< Placeholder. Add your code here. >>>\n        pass\n\n\n\"\"\"\n# Now you can do something like this in your test files:\n\nfrom base_test_case import BaseTestCase\n\nclass MyTests(BaseTestCase):\n\n    def test_example(self):\n        self.login()\n        self.example_method()\n        self.type(\"input\", \"Name\")\n        self.click(\"form button\")\n        ...\n\"\"\"\n"
  },
  {
    "path": "examples/boilerplates/boilerplate_test.py",
    "content": "try:  # Run with \"pytest\" (relative imports are valid)\n    from .base_test_case import BaseTestCase\n    from .page_objects import Page\nexcept (ImportError, ValueError):  # Run with \"python\"\n    from base_test_case import BaseTestCase\n    from page_objects import Page\n    BaseTestCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseTestCase):\n    def test_boilerplate(self):\n        self.login()\n        self.example_method()\n        self.assert_element(Page.html)\n"
  },
  {
    "path": "examples/boilerplates/classic_obj_test.py",
    "content": "\"\"\"Classic Page Object Model with BaseCase inheritance.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass DataPage:\r\n    def go_to_data_url(self, sb):\r\n        sb.open(\"data:text/html,<p>Hello!</p><input />\")\r\n\r\n    def add_input_text(self, sb, text):\r\n        sb.type(\"input\", text)\r\n\r\n\r\nclass ObjTests(BaseCase):\r\n    def test_data_url_page(self):\r\n        DataPage().go_to_data_url(self)\r\n        self.assert_text(\"Hello!\", \"p\")\r\n        DataPage().add_input_text(self, \"Goodbye!\")\r\n"
  },
  {
    "path": "examples/boilerplates/page_objects.py",
    "content": "\"\"\"Example of using the Page Object Pattern in tests.\nMakes code more Readable, Maintainable, and Reusable.\nImport files like this at the top of your test files.\"\"\"\n\n\nclass Page(object):\n    html = \"html\"\n    ok_button = \"#ok\"\n    cancel_button = \"#cancel\"\n    see_items_button = \"button.items\"\n\n\nclass HomePage(object):\n    see_items_button = \"button.items\"\n\n\nclass ShoppingPage(object):\n    buyable_item = 'img[alt=\"Item\"]'\n    add_to_cart = \"button.add\"\n    go_to_checkout = \"#checkout\"\n\n\nclass CheckoutPage(object):\n    remove_from_cart = \"button.remove\"\n    buy_now = \"#buy-now\"\n    shop_more = \"#shop-more\"\n\n\n\"\"\"\n# Now you can do something like this in your test files:\n\nfrom .base_test_case import BaseTestCase\nfrom .page_objects import HomePage, ShoppingPage, CheckoutPage\n\nclass MyTests(BaseTestCase):\n\n    def test_example(self):\n        self.login()\n        self.click(HomePage.see_items_button)\n        self.click(ShoppingPage.buyable_item)\n        self.click(ShoppingPage.add_to_cart)\n        self.click(CheckoutPage.buy_now)\n        self.assert_element(\"#success\")\n        self.assert_text(\"Order Received!\", \"#h2\")\n\"\"\"\n"
  },
  {
    "path": "examples/boilerplates/samples/__init__.py",
    "content": ""
  },
  {
    "path": "examples/boilerplates/samples/file_parsing/__init__.py",
    "content": ""
  },
  {
    "path": "examples/boilerplates/samples/file_parsing/parse_files.py",
    "content": "\"\"\"Example of parsing data from files.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ParseTestCase(BaseCase):\n    def get_login_credentials(self, user_type):\n        # Example of parsing data from a file (Method 1)\n        with open(\"qa_login_example.txt\") as f:\n            file_lines = [line.rstrip() for line in f]\n        for line in file_lines:\n            line_items = line.split(\",\")\n            if line_items[0] == user_type:\n                return line_items[1], line_items[2]\n\n    def get_all_login_credentials(self):\n        # Example of parsing data from a file (Method 2)\n        keys = {}\n        with open(\"staging_login_example.txt\") as f:\n            file_lines = [line.rstrip() for line in f]\n        for line in file_lines:\n            line_items = line.split(\",\")\n            if line_items[0] == \"admin\":\n                keys[\"admin\"] = {\n                    \"username\": line_items[1],\n                    \"password\": line_items[2],\n                }\n            if line_items[0] == \"employee\":\n                keys[\"employee\"] = {\n                    \"username\": line_items[1],\n                    \"password\": line_items[2],\n                }\n            if line_items[0] == \"customer\":\n                keys[\"customer\"] = {\n                    \"username\": line_items[1],\n                    \"password\": line_items[2],\n                }\n        return keys\n\n\nclass ParseTests(ParseTestCase):\n    def test_get_login_credentials(self):\n        print(\"\\nExample 1 of getting login info from parsing a config file:\")\n        print(\"\")\n        username, password = self.get_login_credentials(\"admin\")\n        print(\"Getting Admin User login data:\")\n        print(\"Username: %s\" % username)\n        print(\"Password: %s\" % password)\n\n        print(\"\\nExample 2 of getting login info from parsing a config file:\")\n        print(\"\")\n        keys = self.get_all_login_credentials()\n        print(\"Getting Customer login data:\")\n        print(\"Username: %s\" % keys[\"customer\"][\"username\"])\n        print(\"Password: %s\" % keys[\"customer\"][\"password\"])\n"
  },
  {
    "path": "examples/boilerplates/samples/file_parsing/qa_login_example.txt",
    "content": "admin,admin_username_qa,admin_password_qa\nemployee,employee_username_qa,employee_password_qa\ncustomer,customer_username_qa,customer_password_qa"
  },
  {
    "path": "examples/boilerplates/samples/file_parsing/staging_login_example.txt",
    "content": "admin,admin_username_staging,admin_password_staging\nemployee,employee_username_staging,employee_password_staging\ncustomer,customer_username_staging,customer_password_staging"
  },
  {
    "path": "examples/boilerplates/samples/google_objects.py",
    "content": "\"\"\"google.com page objects\"\"\"\n\n\nclass HomePage(object):\n    dialog_box = '[role=\"dialog\"] div'\n    search_box = '[title=\"Search\"]'\n    search_button = 'input[value=\"Google Search\"]'\n    feeling_lucky_button = \"\"\"input[value=\"I'm Feeling Lucky\"]\"\"\"\n\n\nclass ResultsPage(object):\n    search_results = \"div#center_col\"\n"
  },
  {
    "path": "examples/boilerplates/samples/google_test.py",
    "content": "\"\"\"google.com example test that uses page objects\"\"\"\nfrom seleniumbase import BaseCase\ntry:\n    from .google_objects import HomePage, ResultsPage\nexcept Exception:\n    from google_objects import HomePage, ResultsPage\n    BaseCase.main(__name__, __file__, \"--uc\")\n\n\nclass GoogleTests(BaseCase):\n    def test_google_dot_com(self):\n        if self.headless:\n            self.open_if_not_url(\"about:blank\")\n            print(\"\\n  Skipping test in headless mode.\")\n            self.skip(\"Skipping test in headless mode.\")\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.driver.get(\"https://google.com/ncr\")\n        self.assert_title_contains(\"Google\")\n        self.sleep(0.05)\n        self.save_screenshot_to_logs()  # (\"./latest_logs\" folder)\n        self.type(HomePage.search_box, \"github.com\")\n        self.assert_element(HomePage.search_button)\n        self.assert_element(HomePage.feeling_lucky_button)\n        self.click(HomePage.search_button)\n        self.assert_text(\"github.com\", ResultsPage.search_results)\n"
  },
  {
    "path": "examples/boilerplates/samples/sb_swag_test.py",
    "content": "\"\"\"Classic Page Object Model with the \"sb\" fixture.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass LoginPage:\r\n    def login_to_swag_labs(self, sb: BaseCase, username):\r\n        sb.open(\"https://www.saucedemo.com\")\r\n        sb.type(\"#user-name\", username)\r\n        sb.type(\"#password\", \"secret_sauce\")\r\n        sb.click('input[type=\"submit\"]')\r\n\r\n\r\nclass MyTests:\r\n    def test_swag_labs_login(self, sb: BaseCase):\r\n        LoginPage().login_to_swag_labs(sb, \"standard_user\")\r\n        sb.assert_element(\"div.inventory_list\")\r\n        sb.assert_element('div:contains(\"Sauce Labs Backpack\")')\r\n        sb.js_click(\"a#logout_sidebar_link\")\r\n        sb.assert_element(\"div#login_button_container\")\r\n"
  },
  {
    "path": "examples/boilerplates/samples/swag_labs_test.py",
    "content": "\"\"\"Classic Page Object Model with BaseCase inheritance.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass LoginPage:\r\n    def login_to_swag_labs(self, sb: BaseCase, username):\r\n        sb.open(\"https://www.saucedemo.com\")\r\n        sb.type(\"#user-name\", username)\r\n        sb.type(\"#password\", \"secret_sauce\")\r\n        sb.click('input[type=\"submit\"]')\r\n\r\n\r\nclass MyTests(BaseCase):\r\n    def test_swag_labs_login(self):\r\n        LoginPage().login_to_swag_labs(self, \"standard_user\")\r\n        self.assert_element(\"div.inventory_list\")\r\n        self.assert_element('div:contains(\"Sauce Labs Backpack\")')\r\n        self.js_click(\"a#logout_sidebar_link\")\r\n        self.assert_element(\"div#login_button_container\")\r\n"
  },
  {
    "path": "examples/boilerplates/samples/test_page_objects.py",
    "content": "\"\"\"An example using the Classic Page Object Model.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__, \"--uc\")\r\n\r\n\r\nclass GooglePage:\r\n    def go_to_google(self, sb):\r\n        sb.driver.get(\"https://google.com/ncr\")\r\n\r\n    def assert_google_title(self, sb):\r\n        sb.assert_title_contains(\"Google\")\r\n\r\n    def hide_sign_in_pop_up(self, sb):\r\n        if not sb.is_element_visible(\"iframe\"):\r\n            sb.sleep(1.5)  # A slow pop-up might appear\r\n        sb.hide_elements('iframe')\r\n        sb.sleep(0.05)\r\n\r\n    def do_search(self, sb, search_term):\r\n        sb.sleep(0.05)\r\n        sb.click('[title=\"Search\"]')\r\n        sb.type('[title=\"Search\"]', search_term + \"\\n\")\r\n\r\n    def click_search_result(self, sb, content):\r\n        sb.click('a:contains(\"%s\")' % content)\r\n\r\n\r\nclass SeleniumBaseIOPage:\r\n    def do_search_and_click(self, sb, search_term):\r\n        sb.sleep(0.05)\r\n        sb.type('form[name=\"search\"] input', search_term)\r\n        sb.click(\"li.md-search-result__item h1:contains(%s)\" % search_term)\r\n\r\n\r\nclass MyTests(BaseCase):\r\n    def test_page_objects(self):\r\n        if self.headless:\r\n            self.open_if_not_url(\"about:blank\")\r\n            print(\"\\n  Skipping test in headless mode.\")\r\n            self.skip(\"Skipping test in headless mode.\")\r\n        if not self.undetectable:\r\n            self.get_new_driver(undetectable=True)\r\n        search_term = \"SeleniumBase.io Docs\"\r\n        expected_text = \"SeleniumBase\"\r\n        GooglePage().go_to_google(self)\r\n        GooglePage().assert_google_title(self)\r\n        GooglePage().hide_sign_in_pop_up(self)\r\n        GooglePage().do_search(self, search_term)\r\n        self.assert_text(expected_text, \"#search\")\r\n        GooglePage().click_search_result(self, expected_text)\r\n        SeleniumBaseIOPage().do_search_and_click(self, \"Dashboard\")\r\n        self.assert_text(\"Dashboard\", \"main h1\")\r\n"
  },
  {
    "path": "examples/boilerplates/sb_fixture_test.py",
    "content": "\"\"\"Classic Page Object Model with the \"sb\" fixture.\"\"\"\r\n\r\n\r\nclass DataPage:\r\n    def go_to_data_url(self, sb):\r\n        sb.open(\"data:text/html,<p>Hello!</p><input />\")\r\n\r\n    def add_input_text(self, sb, text):\r\n        sb.type(\"input\", text)\r\n\r\n\r\nclass ObjTests:\r\n    def test_data_url_page(self, sb):\r\n        DataPage().go_to_data_url(sb)\r\n        sb.assert_text(\"Hello!\", \"p\")\r\n        DataPage().add_input_text(sb, \"Goodbye!\")\r\n"
  },
  {
    "path": "examples/capabilities/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## [<img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\">](https://github.com/seleniumbase/SeleniumBase/) Using Desired Capabilities\n\nYou can specify browser capabilities when running SeleniumBase tests on a remote Selenium Grid server (such as <a href=\"https://www.browserstack.com/automate/capabilities\" target=\"_blank\">BrowserStack</a> or <a href=\"https://saucelabs.com/products/platform-configurator\" target=\"_blank\">Sauce Labs</a>).\n\nSample run commands may look like this when run from the [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder: (The browser is now specified in the capabilities file.)\n\n```zsh\npytest test_demo_site.py --browser=remote --server=USERNAME:KEY@hub.browserstack.com --port=80 --cap_file=capabilities/sample_cap_file_BS.py\n```\n\n```zsh\npytest test_demo_site.py --browser=remote --server=USERNAME:KEY@ondemand.us-east-1.saucelabs.com --port=443 --protocol=https --cap_file=capabilities/sample_cap_file_SL.py\n```\n\n(Parameters: ``--browser=remote``, ``--server=SERVER``, ``--port=PORT``, ``--protocol=PROTOCOL``, and ``--cap_file=CAP_FILE.py``)\n\nHere's an example desired capabilities file for BrowserStack using the newer SDK format in a `.yml` / `.yaml` file:\n\n```yml\nplatforms:\n  - browserName: safari\n    osVersion: 17\n    deviceName: iPhone 15 Pro Max\nbuildIdentifier: ${BUILD_NUMBER}\nparallelsPerPlatform: 1\nprojectName: My Project\nbrowserstackLocal: true\ndebug: true\nnetworkLogs: true\n```\n\nHere's an example desired capabilities file for BrowserStack using the legacy JSONWP format in a `.py` file:\n\n```python\ndesired_cap = {\n    \"browser\": \"Chrome\",\n    \"os\": \"Windows\",\n    \"os_version\": \"11\",\n    \"browser_version\": \"latest\",\n    \"browserstack.console\": \"info\",\n    \"browserstack.debug\": \"true\",\n    \"browserstack.networkLogs\": \"true\",\n    \"browserstack.local\": \"true\",\n}\n```\n\nHere's an example desired capabilities file for Sauce Labs:\n\n```python\ncapabilities = {\n    \"browserName\": \"chrome\",\n    \"browserVersion\": \"latest\",\n    \"platformName\": \"macOS 10.14\",\n    \"sauce:options\": {},\n}\n```\n\n(Note that the browser is now being specified in the capabilities file, rather than with ``--BROWSER`` when using a **remote** Selenium Grid. If using a **local** Selenium Grid, specify the browser, eg: ``--firefox``.)\n\n<div><b>You can generate specific desired capabilities using:</b></div>\n\n<ul>\n    <li><a href=\"https://www.browserstack.com/docs/automate/capabilities\" target=\"_blank\">BrowserStack desired capabilities</a></li>\n    <li><a href=\"https://saucelabs.com/products/platform-configurator\" target=\"_blank\">Sauce Labs desired capabilities</a></li>\n</ul>\n\n<div><b>Parsing desired capabilities:</b></div>\n\nSeleniumBase has a desired capabilities parser that can capture all lines from the specified file in the following formats:\n\n```python\n'KEY': 'VALUE'\n'KEY': True\n'KEY': False\ncaps['KEY'] = \"VALUE\"\ncaps['KEY'] = True\ncaps['KEY'] = False\n```\n\n(Each pair must be on a separate line. You can interchange single and double quotes.)\n\nYou can also swap ``--browser=remote`` with an actual browser, eg ``--browser=chrome``, which will combine the default SeleniumBase desired capabilities with those that were specified in the capabilities file when using ``--cap_file=FILE.py``. Capabilities will override other parameters, so if you set the browser to one thing and the capabilities browser to another, SeleniumBase will use the capabilities browser.\n\nYou'll need default SeleniumBase capabilities for:\n* Using a proxy server (not the same as a Selenium Grid server)\n* Downloading files to a desired folder\n* Disabling some warnings on Chrome\n* Overriding a website's Content Security Policy on Chrome\n* Other possible reasons\n\nYou can also set browser desired capabilities from a command-line string. Eg:\n\n```zsh\npytest test_swag_labs.py --cap-string='{\"browserName\":\"chrome\",\"name\":\"test1\"}' --server=\"127.0.0.1\" --browser=remote\n```\n\n(Enclose cap-string in single quotes. Enclose parameter keys in double quotes.)\n\nIf you pass ``\"*\"`` into the ``\"name\"`` field of ``--cap-string``, the name will become the test identifier. Eg:\n\n```zsh\npytest my_first_test.py --cap-string='{\"browserName\":\"chrome\",\"name\":\"*\"}' --server=\"127.0.0.1\" --browser=chrome\n```\n\nExample name: ``\"my_first_test.MyTestClass.test_basics\"``\n\n<h3>Using a local Selenium Grid</h3>\n\nIf using a local Selenium Grid with SeleniumBase, start up the Grid Hub and nodes first:\n\n```zsh\nsbase grid-hub start\nsbase grid-node start\n```\n\n(The Selenium Server JAR file will be automatically downloaded for first-time Grid users. You'll also need Java installed to start up the Grid.)\n"
  },
  {
    "path": "examples/capabilities/mac_cap_file.py",
    "content": "# Desired capabilities example file for generic macOS Grid nodes\n\ncapabilities = {\n    \"platformName\": \"MAC\",\n    \"browserVersion\": \"latest\",\n}\n"
  },
  {
    "path": "examples/capabilities/sample_cap_file_BS.py",
    "content": "# Desired capabilities example .py file for BrowserStack:\n# https://www.browserstack.com/docs/automate/capabilities\ndesired_cap = {\n    \"browser\": \"Chrome\",\n    \"os\": \"Windows\",\n    \"os_version\": \"11\",\n    \"browser_version\": \"latest\",\n    \"browserstack.console\": \"info\",\n    \"browserstack.debug\": \"true\",\n    \"browserstack.networkLogs\": \"true\",\n    \"browserstack.local\": \"true\",\n}\n"
  },
  {
    "path": "examples/capabilities/sample_cap_file_BS.yml",
    "content": "# Desired capabilities example YML file for BrowserStack:\n# https://www.browserstack.com/docs/automate/capabilities\nplatforms:\n  - browserName: safari\n    osVersion: 17\n    deviceName: iPhone 15 Pro Max\nbuildIdentifier: ${BUILD_NUMBER}\nparallelsPerPlatform: 1\nprojectName: My Project\nbrowserstackLocal: true\ndebug: true\nnetworkLogs: true\n"
  },
  {
    "path": "examples/capabilities/sample_cap_file_SL.py",
    "content": "# Desired capabilities example file for Sauce Labs\n# Generate from https://saucelabs.com/products/platform-configurator\ncapabilities = {\n    \"browserName\": \"chrome\",\n    \"browserVersion\": \"latest\",\n    \"platformName\": \"macOS 10.14\",\n    \"sauce:options\": {},\n}\n"
  },
  {
    "path": "examples/capabilities/selenoid_cap_file.py",
    "content": "# Desired capabilities example file for Selenoid Grid\n#\n# The same result can be achieved on the command-line. Eg:\n#     --cap-string='{\"selenoid:options\": {\"enableVNC\": true}}'\n\ncapabilities = {\n    \"acceptSslCerts\": True,\n    \"acceptInsecureCerts\": True,\n    \"screenResolution\": \"1920x1080x24\",\n    \"selenoid:options\": {\n        \"enableVNC\": True,\n        \"enableVideo\": False,\n    },\n}\n"
  },
  {
    "path": "examples/capabilities/win10_cap_file.py",
    "content": "# Desired capabilities example file for Windows 10 Grid nodes\n\ncapabilities = {\n    \"platformName\": \"WIN10\",\n    \"browserVersion\": \"latest\",\n}\n"
  },
  {
    "path": "examples/case_plans/basic_test.MyTestClass.test_basics.md",
    "content": "``basic_test.py::MyTestClass::test_basics``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\r\n| 2 | Click on the ``Backpack`` ``ADD TO CART`` button. | The button text changed to ``REMOVE``. |\r\n| 3 | Click on the cart icon. | The ``Backpack`` is seen in the cart. |\r\n| 4 | Remove the ``Backpack`` from the cart. | The ``Backpack`` is no longer in the cart. |\r\n| 5 | Log out from the website. | Logout was successful. |\r\n"
  },
  {
    "path": "examples/case_plans/my_first_test.MyTestClass.test_swag_labs.md",
    "content": "``my_first_test.py::MyTestClass::test_swag_labs``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\r\n| 2 | Click on the ``Backpack`` ``ADD TO CART`` button. | The button text changed to ``REMOVE``. |\r\n| 3 | Click on the cart icon. | The ``Backpack`` is seen in the cart. |\r\n| 4 | Click on the ``CHECKOUT`` button. <br /> Enter user details and click ``CONTINUE``. | The ``Backpack`` is seen in the cart on the ``CHECKOUT: OVERVIEW`` page. |\r\n| 5 | Click on the ``FINISH`` button. | There is a ``Thank you`` message. |\r\n| 6 | Log out from the website. | Logout was successful. |\r\n"
  },
  {
    "path": "examples/case_plans/shadow_root_test.ShadowRootTest.test_shadow_root.md",
    "content": "``shadow_root_test.py::ShadowRootTest::test_shadow_root``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/other/shadow_dom. <br /> Click each tab and verify the text contained within the Shadow Root sections. | Tab 1 text: ``Content Panel 1`` <br /> Tab 2 text: ``Content Panel 2`` <br /> Tab 3 text: ``Content Panel 3`` |\r\n"
  },
  {
    "path": "examples/case_plans/test_assert_elements.ListAssertTests.test_assert_list_of_elements.md",
    "content": "``test_assert_elements.py::ListAssertTests::test_assert_list_of_elements``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/demo_page. | |\r\n| 2 | Use ``self.assert_elements_present(\"head\", \"style\", \"script\")`` to verify that multiple elements are present in the HTML. | The assertion is successful. |\r\n| 3 | Use ``self.assert_elements(\"h1\", \"h2\", \"h3\")`` to verify that multiple elements are visible. | The assertion is successful. |\r\n| 4 | Use ``self.assert_elements([\"#myDropdown\", \"#myButton\", \"#svgRect\"])`` to verify that multiple elements are visible. | The assertion is successful. |\r\n"
  },
  {
    "path": "examples/case_plans/test_calculator.CalculatorTests.test_6_times_7_plus_12_equals_54.md",
    "content": "``test_calculator.py::CalculatorTests::test_6_times_7_plus_12_equals_54``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/apps/calculator. <br /> Perform the following calculation: ``6 × 7 + 12`` | The output is ``54`` after pressing ``=`` |\r\n"
  },
  {
    "path": "examples/case_plans/test_demo_site.DemoSiteTests.test_demo_site.md",
    "content": "``test_demo_site.py::DemoSiteTests::test_demo_site``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/demo_page |  |\r\n| 2 | Assert the title of the current web page. <br /> Assert that a given element is visible on the page. <br /> Assert that a text substring appears in an element's text. | The assertions were successful. |\r\n| 3 | Type text into various text fields and then verify. | The assertions were successful. |\r\n| 4 | Verify that a hover dropdown link changes page text. | The assertion was successful. |\r\n| 5 | Verify that a button click changes text on the page. | The assertion was successful. |\r\n| 6 | Verify that an SVG element is located on the page. | The assertion was successful. |\r\n| 7 | Verify that a slider control updates a progress bar. | The assertion was successful. |\r\n| 8 | Verify that a \"select\" option updates a meter bar. | The assertion was successful. |\r\n| 9 | Assert an element located inside an iFrame. | The assertion was successful. |\r\n| 10 | Assert text located inside an iFrame. | The assertion was successful. |\r\n| 11 | Verify that clicking a radio button selects it. | The assertion was successful. |\r\n| 12 | Verify that clicking an empty checkbox makes it selected. | The assertion was successful. |\r\n| 13 | Verify clicking on multiple elements with one call. | The assertions were successful. |\r\n| 14 | Verify that clicking an iFrame checkbox selects it. | The assertions were successful. |\r\n| 15 | Verify that Drag and Drop works. | The assertion was successful. |\r\n| 16 | Assert link text. | The assertion was successful. |\r\n| 17 | Verify clicking on link text. | The action was successful. |\r\n| 18 | Assert exact text in an element. | The assertion was successful. |\r\n| 19 | Highlight a page element. | The action was successful. |\r\n| 20 | Verify that Demo Mode works. | The assertion was successful. |\r\n"
  },
  {
    "path": "examples/case_plans/test_login.SwagLabsLoginTests.test_swag_labs_login.md",
    "content": "``test_login.py::SwagLabsLoginTests::test_swag_labs_login``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\r\n| 2 | Log out from the website. | Logout was successful. |\r\n"
  },
  {
    "path": "examples/case_plans/test_mfa_login.TestMFALogin.test_mfa_login.md",
    "content": "``test_mfa_login.py::TestMFALogin::test_mfa_login``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/realworld/login <br /> Enter credentials and Sign In. | Sign In was successful. |\r\n| 2 | Click the ``This Page`` button. <br /> Save a screenshot to the logs. | |\r\n| 3 | Click to Sign Out | Sign Out was successful. |\r\n"
  },
  {
    "path": "examples/case_summary.md",
    "content": "<h2>Summary of existing Case Plans</h2>\r\n\r\n|   |    |   |\r\n| - | -: | - |\r\n| 🔵 | 14 | Case Plans with customized tables |\r\n| ⭕ | 0 | Case Plans using boilerplate code |\r\n| 🚧 | 0 | Case Plans that are missing tables |\r\n\r\n--------\r\n\r\n<h3>🔎 (Click rows to expand) 🔍</h3>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>basic_test.py::MyTestClass::test_basics</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\r\n| 2 | Click on the ``Backpack`` ``ADD TO CART`` button. | The button text changed to ``REMOVE``. |\r\n| 3 | Click on the cart icon. | The ``Backpack`` is seen in the cart. |\r\n| 4 | Remove the ``Backpack`` from the cart. | The ``Backpack`` is no longer in the cart. |\r\n| 5 | Log out from the website. | Logout was successful. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>my_first_test.py::MyTestClass::test_swag_labs</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\r\n| 2 | Click on the ``Backpack`` ``ADD TO CART`` button. | The button text changed to ``REMOVE``. |\r\n| 3 | Click on the cart icon. | The ``Backpack`` is seen in the cart. |\r\n| 4 | Click on the ``CHECKOUT`` button. <br /> Enter user details and click ``CONTINUE``. | The ``Backpack`` is seen in the cart on the ``CHECKOUT: OVERVIEW`` page. |\r\n| 5 | Click on the ``FINISH`` button. | There is a ``Thank you`` message. |\r\n| 6 | Log out from the website. | Logout was successful. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>shadow_root_test.py::ShadowRootTest::test_shadow_root</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/other/shadow_dom. <br /> Click each tab and verify the text contained within the Shadow Root sections. | Tab 1 text: ``Content Panel 1`` <br /> Tab 2 text: ``Content Panel 2`` <br /> Tab 3 text: ``Content Panel 3`` |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>test_assert_elements.py::ListAssertTests::test_assert_list_of_elements</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/demo_page. | |\r\n| 2 | Use ``self.assert_elements_present(\"head\", \"style\", \"script\")`` to verify that multiple elements are present in the HTML. | The assertion is successful. |\r\n| 3 | Use ``self.assert_elements(\"h1\", \"h2\", \"h3\")`` to verify that multiple elements are visible. | The assertion is successful. |\r\n| 4 | Use ``self.assert_elements([\"#myDropdown\", \"#myButton\", \"#svgRect\"])`` to verify that multiple elements are visible. | The assertion is successful. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>test_calculator.py::CalculatorTests::test_6_times_7_plus_12_equals_54</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/apps/calculator. <br /> Perform the following calculation: ``6 × 7 + 12`` | The output is ``54`` after pressing ``=`` |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>test_demo_site.py::DemoSiteTests::test_demo_site</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/demo_page |  |\r\n| 2 | Assert the title of the current web page. <br /> Assert that a given element is visible on the page. <br /> Assert that a text substring appears in an element's text. | The assertions were successful. |\r\n| 3 | Type text into various text fields and then verify. | The assertions were successful. |\r\n| 4 | Verify that a hover dropdown link changes page text. | The assertion was successful. |\r\n| 5 | Verify that a button click changes text on the page. | The assertion was successful. |\r\n| 6 | Verify that an SVG element is located on the page. | The assertion was successful. |\r\n| 7 | Verify that a slider control updates a progress bar. | The assertion was successful. |\r\n| 8 | Verify that a \"select\" option updates a meter bar. | The assertion was successful. |\r\n| 9 | Assert an element located inside an iFrame. | The assertion was successful. |\r\n| 10 | Assert text located inside an iFrame. | The assertion was successful. |\r\n| 11 | Verify that clicking a radio button selects it. | The assertion was successful. |\r\n| 12 | Verify that clicking an empty checkbox makes it selected. | The assertion was successful. |\r\n| 13 | Verify clicking on multiple elements with one call. | The assertions were successful. |\r\n| 14 | Verify that clicking an iFrame checkbox selects it. | The assertions were successful. |\r\n| 15 | Verify that Drag and Drop works. | The assertion was successful. |\r\n| 16 | Assert link text. | The assertion was successful. |\r\n| 17 | Verify clicking on link text. | The action was successful. |\r\n| 18 | Assert exact text in an element. | The assertion was successful. |\r\n| 19 | Highlight a page element. | The action was successful. |\r\n| 20 | Verify that Demo Mode works. | The assertion was successful. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>test_login.py::SwagLabsLoginTests::test_swag_labs_login</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\r\n| 2 | Log out from the website. | Logout was successful. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>test_mfa_login.py::TestMFALogin::test_mfa_login</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://seleniumbase.io/realworld/login <br /> Enter credentials and Sign In. | Sign In was successful. |\r\n| 2 | Click the ``This Page`` button. <br /> Save a screenshot to the logs. | |\r\n| 3 | Click to Sign Out | Sign Out was successful. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>visual_testing/layout_test.py::VisualLayoutTests::test_applitools_layout_change</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://applitools.com/helloworld?diff1. <br /> Call ``check_window()`` with ``baseline=True``. <br /> Click the button that changes the text of an element. <br /> Call ``check_window()`` three times for ``level=1``, ``level=2``, and ``level=3``. | No issues are detected because a text change should not affect ``check_window()`` |\r\n| 2 | Click the button that makes a hidden element visible. <br /> Call ``check_window()`` three times for ``level=1``, ``level=2``, and ``level=3``, but wrap the third call with ``self.assert_raises(Exception):``. | No exceptions are raised because the first two calls should pass and the third one was wrapped with ``self.assert_raises(Exception):``. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>visual_testing/python_home_test.py::VisualLayoutTests::test_python_home_layout_change</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://python.org/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Remove the ``Donate`` button using ``remove_element(SELECTOR)``. <br /> Call ``check_window()`` with ``level=0``. | The test detects that the ``Donate`` button was removed. The test does not fail because the check was set to ``level=0`` (print-only). <br /> A ``side_by_side_NAME.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>visual_testing/test_layout_fail.py::VisualLayout_FixtureTests::test_python_home_change</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://python.org/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Remove the ``Donate`` button using ``remove_element(SELECTOR)``. <br /> Call ``check_window()`` with ``level=3``. | The test fails because the ``Donate`` button was removed. <br /> A ``side_by_side.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>visual_testing/test_layout_fail.py::VisualLayoutFailureTests::test_applitools_change</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://applitools.com/helloworld?diff1. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Click the button that makes a hidden element visible. <br /> Call ``check_window()`` with ``level=3``. | The test fails because the element attribute has changed. <br /> A ``side_by_side.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>visual_testing/test_layout_fail.py::VisualLayoutFailureTests::test_xkcd_logo_change</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://xkcd.com/554/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Resize the logo using ``set_attribute()``. <br /> Call ``check_window()`` with ``level=3``. | The test fails because the logo has changed. <br /> A ``side_by_side.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n\r\n</details>\r\n\r\n<details>\r\n<summary> 🔵 <code><b>visual_testing/xkcd_visual_test.py::VisualLayoutTests::test_xkcd_layout_change</b></code></summary>\r\n\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://xkcd.com/554/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Resize the logo using ``set_attribute()``. <br /> Call ``check_window()`` with ``level=0``. | The test detects that the logo has changed. The test does not fail because the check was set to ``level=0`` (print-only). <br /> A ``side_by_side_NAME.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n\r\n</details>\r\n"
  },
  {
    "path": "examples/cdp_mode/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> CDP Mode 🐙</h2>\n\n🐙 <b translate=\"no\">SeleniumBase</b> <b translate=\"no\">CDP Mode</b> is a stealth mode that uses the <a href=\"https://chromedevtools.github.io/devtools-protocol/\" translate=\"no\">Chrome Devtools Protocol</a> (via <a href=\"https://github.com/mdmintz/MyCDP\" translate=\"no\"><span translate=\"no\">MyCDP</span></a>) to control the web browser. <b translate=\"no\">CDP Mode</b> can be used as a subset of <b><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md\" translate=\"no\"><span translate=\"no\">UC Mode</span></a></b>, or via <b><a href=\"#Pure_CDP_Mode\" translate=\"no\">Pure CDP Mode</a></b>, which has sync and async formats. From CDP Mode, you can make Playwright stealthy (<a translate=\"no\" href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/playwright/ReadMe.md\">Stealthy Playwright Mode</a>).\n\n----\n\n⚙️ This diagram shows the stealthy architecture with <b>CDP Mode</b>:\n\n<img src=\"https://seleniumbase.github.io/other/sb_stealth.png\" width=\"585\" alt=\"High-Level Stealthy Architecture Overview\" title=\"High-Level Stealthy Architecture Overview\" />\n\n----\n\n### 🎞️ YouTube tutorials that cover CDP Mode:\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=Mr90iQmNsKM\"><img src=\"https://github.com/user-attachments/assets/91e7ff7b-d155-4ba9-b17b-b097825fcf42\" title=\"SeleniumBase on YouTube\" width=\"320\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=Mr90iQmNsKM\">Watch \"Undetectable Automation 4\" on YouTube! ▶️</a></b>)</p>\n\n(See `examples/cdp_mode/` for up-to-date examples.)\n\n----\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=vt2zsdiNh3U\"><img src=\"https://github.com/user-attachments/assets/82ab2715-727e-4d09-9314-b8905795dc43\" title=\"SeleniumBase on YouTube\" width=\"320\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=vt2zsdiNh3U\">Watch \"Hacking websites with CDP\" on YouTube! ▶️</a></b>)</p>\n\n----\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=gEZhTfaIxHQ\"><img src=\"https://github.com/user-attachments/assets/656977e1-5d66-4d1c-9eec-0aaa41f6522f\" title=\"SeleniumBase on YouTube\" width=\"320\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=gEZhTfaIxHQ\">Watch \"Web-Scraping with GitHub Actions\" on YouTube! ▶️</a></b>)</p>\n\n----\n\nℹ️ Note the differences between <b>UC Mode</b> and <b>CDP Mode</b>:\n\n👤 <b translate=\"no\">UC Mode</b>'s stealth is based on a modified chromedriver  (<code>uc_driver</code>) that avoids bot-detection by disconnecting and reconnecting WebDriver from the browser at strategic times. Due to advancements in anti-bot technology, more stealth was needed to bypass advanced bot-detection. (That's where <b translate=\"no\">CDP Mode</b> comes in.)\n\n🐙 <b translate=\"no\">CDP Mode</b> includes multiple updates to the above, such as:\n\n* Using CDP directly, which is stealthier than WebDriver.\n* Backwards compatibility for existing UC Mode scripts.\n* More configuration options when launching browsers.\n* The ability to use WebDriver and CDP calls together.\n* Full access to call any advanced CDP library method.\n* Can be used to make the Playwright library stealthy.\n\n----\n\n### 🐙 <b translate=\"no\">CDP Mode</b> Usage (when used as a subset of UC Mode):\n\n* **`sb.activate_cdp_mode(url)`**\n\nThat disconnects WebDriver from Chrome (which prevents detection), and gives you access to `sb.cdp` methods (which don't trigger anti-bot checks).\n\n> (**New:** Calling **`sb.open(url)`** from UC Mode also activates CDP Mode now.)\n\nSimple example from [SeleniumBase/examples/cdp_mode/raw_gitlab.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_gitlab.py):\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"en\") as sb:\n    url = \"https://gitlab.com/users/sign_in\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.solve_captcha()\n    sb.sleep(2)\n```\n\n<img src=\"https://seleniumbase.github.io/other/cf_sec.jpg\" title=\"SeleniumBase\" width=\"332\"> <img src=\"https://seleniumbase.github.io/other/gitlab_bypass.png\" title=\"SeleniumBase\" width=\"288\">\n\n(If the CAPTCHA wasn't bypassed automatically when going to the URL, then `sb.solve_captcha()` gets the job done.)\n\n----\n\nHere's another example that calls `sb.solve_captcha()`:\n([SeleniumBase/examples/cdp_mode/raw_planetmc.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_planetmc.py))\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, guest=True) as sb:\n    url = \"www.planetminecraft.com/account/sign_in/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.solve_captcha()\n    sb.wait_for_element_absent(\"input[disabled]\")\n    sb.sleep(2)\n```\n\n<img src=\"https://seleniumbase.github.io/other/planet_mc.png\" title=\"SeleniumBase\" width=\"480\">\n\nIn many cases, the CAPTCHA will be solved automatically without needing to call `solve_captcha()`.\n\n----\n\nYou can also use `PyAutoGUI` to click on elements with the mouse by calling `sb.cdp.gui_click_element(selector)`.\n\nℹ️ Note that `PyAutoGUI` is an optional dependency. If calling a method that uses it when not already installed, then `SeleniumBase` installs `PyAutoGUI` at runtime.\n\n----\n\n### 🐙 Here are a few common `sb.cdp` methods:\n\n* `sb.cdp.click(selector)`  (Uses the CDP API to click)\n* `sb.cdp.click_if_visible(selector)`  (Click if visible)\n* `sb.cdp.solve_captcha()`  (Uses CDP to click a CAPTCHA)\n* `sb.cdp.gui_click_element(selector)`  (Uses `PyAutoGUI`)\n* `sb.cdp.type(selector, text)`  (Type text into a selector)\n* `sb.cdp.press_keys(selector, text)`  (Human-speed `type`)\n* `sb.cdp.select_all(selector)`  (Returns matching elements)\n* `sb.cdp.get_text(selector)`  (Returns the element's text)\n\nMethods that start with `sb.cdp.gui` use `PyAutoGUI` for interaction.\n\nTo use WebDriver methods again, call:\n\n* **`sb.reconnect()`** or **`sb.connect()`**\n\n(Note that reconnecting allows anti-bots to detect you, so only reconnect if it is safe to do so.)\n\nTo disconnect again, call:\n\n* **`sb.disconnect()`**\n\nWhile disconnected, if you call a WebDriver method, then <b translate=\"no\">SeleniumBase</b> will attempt to use the <b translate=\"no\">CDP Mode</b> version of that method (if available). For example, if you call `sb.click(selector)` instead of `sb.cdp.click(selector)`, then your WebDriver call will automatically be redirected to the <b translate=\"no\">CDP Mode</b> version. Not all WebDriver methods have a matching <b translate=\"no\">CDP Mode</b> method. In that scenario, calling a WebDriver method while disconnected could raise an error, or make WebDriver automatically reconnect first.\n\nTo find out if WebDriver is connected or disconnected, call:\n\n* **`sb.is_connected()`**\n\n<b>Note:</b> When <b translate=\"no\">CDP Mode</b> is initialized from <b translate=\"no\">UC Mode</b>, the WebDriver is disconnected from the browser. (The stealthy <b translate=\"no\">CDP-Driver</b> takes over.)\n\n----\n\n### 🐙 <b translate=\"no\">CDP Mode</b> examples ([SeleniumBase/examples/cdp_mode](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode))\n\n<p><div /></p>\n\n<div></div>\n<details>\n<summary> ▶️ 🔖 <b>Example 1: (Pokemon site using Incapsula/Imperva protection with invisible reCAPTCHA)</b></summary>\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\n    url = \"https://www.pokemon.com/us\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1.5)\n    sb.click_if_visible(\"button#onetrust-accept-btn-handler\")\n    sb.sleep(1.2)\n    sb.click(\"a span.icon_pokeball\")\n    sb.sleep(2.5)\n    sb.click('b:contains(\"Show Advanced Search\")')\n    sb.sleep(2.5)\n    sb.click('span[data-type=\"type\"][data-value=\"electric\"]')\n    sb.sleep(0.7)\n    sb.scroll_into_view(\"a#advSearch\")\n    sb.sleep(0.7)\n    sb.click(\"a#advSearch\")\n    sb.sleep(1.2)\n    sb.click('img[src*=\"img/pokedex/detail/025.png\"]')\n    sb.assert_text(\"Pikachu\", 'div[class*=\"title\"]')\n    sb.assert_element('img[alt=\"Pikachu\"]')\n    sb.scroll_into_view(\"div.pokemon-ability-info\")\n    sb.sleep(1.2)\n    sb.cdp.flash('div[class*=\"title\"]')\n    sb.cdp.flash('img[alt=\"Pikachu\"]')\n    sb.cdp.flash(\"div.pokemon-ability-info\")\n    name = sb.get_text(\"label.styled-select\")\n    info = sb.get_text(\"div.version-descriptions p.active\")\n    print(\"*** %s: ***\\n* %s\" % (name, info))\n    sb.sleep(2)\n    sb.cdp.highlight_overlay(\"div.pokemon-ability-info\")\n    sb.sleep(2)\n    sb.open(\"https://events.pokemon.com/EventLocator/\")\n    sb.sleep(2)\n    sb.click('span:contains(\"Championship\")')\n    sb.sleep(2)\n    events = sb.select_all(\"div.event-info__title\")\n    print(\"*** Pokémon Championship Events: ***\")\n    for event in events:\n        print(\"* \" + event.text)\n    sb.sleep(2)\n```\n\n</details>\n\n> [SeleniumBase/examples/cdp_mode/raw_pokemon.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode/raw_pokemon.py)\n\n\n<div></div>\n<details>\n<summary> ▶️ 🔖 <b>Example 2: (Hyatt site using Kasada protection)</b></summary>\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"en\") as sb:\n    url = \"https://www.hyatt.com/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(3.2)\n    sb.click_if_visible('button[aria-label=\"Close\"]')\n    sb.sleep(0.1)\n    sb.click_if_visible(\"#onetrust-reject-all-handler\")\n    sb.sleep(1.2)\n    location = \"Anaheim, CA, USA\"\n    sb.type('input[id=\"search-term\"]', location)\n    sb.sleep(1.2)\n    sb.click('li[data-js=\"suggestion\"]')\n    sb.sleep(0.6)\n    sb.click_if_visible('button[aria-label=\"Close\"]')\n    sb.sleep(0.6)\n    sb.click(\"button.be-button-shop\")\n    sb.sleep(1)\n    sb.click_if_visible('[label=\"Find Hotels\"]')\n    sb.sleep(5)\n    card_info = 'div[data-booking-status=\"BOOKABLE\"] [class*=\"HotelCard_info\"]'\n    hotels = sb.select_all(card_info)\n    print(\"Hyatt Hotels in %s:\" % location)\n    print(\"(\" + sb.get_text('span[class*=\"summary_destination\"]') + \")\")\n    if len(hotels) == 0:\n        print(\"No availability over the selected dates!\")\n    for hotel in hotels:\n        info = hotel.text.strip()\n        if \"Avg/Night\" in info and not info.startswith(\"Rates from\"):\n            name = info.split(\"  (\")[0].split(\" + \")[0].split(\" Award Cat\")[0]\n            name = name.split(\" Rates from :\")[0]\n            price = \"?\"\n            if \"Rates from : \" in info:\n                price = info.split(\"Rates from : \")[1].split(\" Avg/Night\")[0]\n            print(\"* %s => %s\" % (name, price))\n```\n\n</details>\n\n> [SeleniumBase/examples/cdp_mode/raw_hyatt.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode/raw_hyatt.py)\n\n\n<div></div>\n<details>\n<summary> ▶️ 🔖 <b>Example 3: (BestWestern site using DataDome protection)</b></summary>\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"en\", guest=True) as sb:\n    url = \"https://www.bestwestern.com/en_US.html\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(3)\n    sb.click_if_visible(\".onetrust-close-btn-handler\")\n    sb.sleep(1)\n    sb.click(\"input#destination-input\")\n    sb.sleep(2)\n    location = \"Palm Springs, CA, USA\"\n    sb.press_keys(\"input#destination-input\", location)\n    sb.sleep(1)\n    sb.click(\"ul#google-suggestions li\")\n    sb.sleep(1)\n    sb.click(\"button#btn-modify-stay-update\")\n    sb.sleep(4)\n    sb.click(\"label#available-label\")\n    sb.sleep(2.5)\n    print(\"Best Western Hotels in %s:\" % location)\n    summary_details = sb.get_text(\"#summary-details-column\")\n    dates = summary_details.split(\"DESTINATION\")[-1]\n    dates = dates.split(\" CHECK-OUT\")[0].strip() + \" CHECK-OUT\"\n    dates = dates.replace(\"  \", \" \")\n    print(\"(Dates: %s)\" % dates)\n    flip_cards = sb.select_all(\".flipCard\")\n    for i, flip_card in enumerate(flip_cards):\n        hotel = flip_card.query_selector(\".hotelName\")\n        price = flip_card.query_selector(\".priceSection\")\n        if hotel and price:\n            print(\"* %s: %s => %s\" % (\n                i + 1, hotel.text.strip(), price.text.strip())\n            )\n```\n\n</details>\n\n> [SeleniumBase/examples/cdp_mode/raw_bestwestern.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode/raw_bestwestern.py)\n\n\n<div></div>\n<details>\n<summary> ▶️ 🔖 <b>Example 4: (Walmart site using Akamai protection with PerimeterX)</b></summary>\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, ad_block=True) as sb:\n    url = \"https://www.walmart.com/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1.8)\n    continue_button = 'button:contains(\"Continue shopping\")'\n    if sb.is_element_visible(continue_button):\n        sb.cdp.gui_click_element(continue_button)\n        sb.sleep(0.6)\n    sb.click('input[aria-label=\"Search\"]')\n    sb.sleep(1.2)\n    search = \"Settlers of Catan Board Game\"\n    required_text = \"Catan\"\n    sb.press_keys('input[aria-label=\"Search\"]', search + \"\\n\")\n    sb.sleep(3.8)\n    if sb.is_element_visible(\"#px-captcha\"):\n        sb.cdp.gui_click_and_hold(\"#px-captcha\", 7.2)\n        sb.sleep(4.2)\n        if sb.is_element_visible(\"#px-captcha\"):\n            sb.cdp.gui_click_and_hold(\"#px-captcha\", 4.2)\n            sb.sleep(3.2)\n    sb.remove_elements('[data-testid=\"skyline-ad\"]')\n    sb.remove_elements('[data-testid=\"sba-container\"]')\n    print('*** Walmart Search for \"%s\":' % search)\n    print('    (Results must contain \"%s\".)' % required_text)\n    unique_item_text = []\n    sb.click_if_visible('[data-automation-id=\"sb-btn-close-mark\"]')\n    items = sb.find_elements('[data-item-id]')\n    for item in items:\n        if required_text in item.text:\n            description = item.querySelector(\n                '[data-automation-id=\"product-title\"]'\n            )\n            if description and description.text not in unique_item_text:\n                unique_item_text.append(description.text)\n                print(\"* \" + description.text)\n                price = item.querySelector(\n                    '[data-automation-id=\"product-price\"]'\n                )\n                if price:\n                    price_text = price.text\n                    price_text = price_text.split(\"current price Now \")[-1]\n                    price_text = price_text.split(\"current price \")[-1]\n                    price_text = price_text.split(\" \")[0]\n                    print(\"  (\" + price_text + \")\")\n```\n\n</details>\n\n> [SeleniumBase/examples/cdp_mode/raw_walmart.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode/raw_walmart.py)\n\n\n<div></div>\n<details>\n<summary> ▶️ 🔖 <b>Example 5: (Nike site using Shape Security)</b></summary>\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"en\", pls=\"none\") as sb:\n    url = \"https://www.nike.com/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2.5)\n    sb.click('[data-testid=\"user-tools-container\"] search')\n    sb.sleep(1.5)\n    search = \"Nike Air Force 1\"\n    sb.press_keys('input[type=\"search\"]', search)\n    sb.sleep(4)\n    details = 'ul[data-testid*=\"products\"] figure .details'\n    elements = sb.select_all(details)\n    if elements:\n        print('**** Found results for \"%s\": ****' % search)\n    for element in elements:\n        print(\"* \" + element.text)\n    sb.sleep(2)\n```\n\n</details>\n\n> [SeleniumBase/examples/cdp_mode/raw_nike.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode/raw_nike.py)\n\n<p><div /></p>\n\n(<b>Note:</b> Extra <code translate=\"no\">sb.sleep()</code> calls have been added to prevent bot-detection because some sites will flag you as a bot if you perform actions too quickly.)\n\n(<b>Note:</b> Some sites may IP-block you for 36 hours or more if they catch you using regular <span translate=\"no\">Selenium WebDriver</span>. Be extra careful when creating and/or modifying automation scripts that run on them.)\n\n----\n\n### 🐙 <b translate=\"no\">CDP Mode</b> API / Methods\n\n```python\nsb.cdp.get(url, **kwargs)\nsb.cdp.open(url, **kwargs)  # Same as sb.cdp.get(url, **kwargs)\nsb.cdp.reload(ignore_cache=True, script_to_evaluate_on_load=None)\nsb.cdp.refresh(*args, **kwargs)\nsb.cdp.get_event_loop()\nsb.cdp.get_rd_host()  # Returns the remote-debugging host\nsb.cdp.get_rd_port()  # Returns the remote-debugging port\nsb.cdp.get_rd_url()  # Returns the remote-debugging URL\nsb.cdp.get_endpoint_url()  # Same as sb.cdp.get_rd_url()\nsb.cdp.get_port()  # Same as sb.cdp.get_rd_port()\nsb.cdp.get_websocket_url()  # Returns the websocket URL\nsb.cdp.add_handler(event, handler)\nsb.cdp.find_element(selector, best_match=False, timeout=None)\nsb.cdp.find(selector, best_match=False, timeout=None)\nsb.cdp.locator(selector, best_match=False, timeout=None)\nsb.cdp.find_element_by_text(text, tag_name=None, timeout=None)\nsb.cdp.find_all(selector, timeout=None)\nsb.cdp.find_elements_by_text(text, tag_name=None)\nsb.cdp.select(selector, timeout=None)\nsb.cdp.select_all(selector, timeout=None)\nsb.cdp.find_elements(selector, timeout=None)\nsb.cdp.find_visible_elements(selector, timeout=None)\nsb.cdp.click(selector, timeout=None)\nsb.cdp.click_if_visible(selector, timeout=0)\nsb.cdp.click_visible_elements(selector, limit=0)\nsb.cdp.click_nth_element(selector, number)\nsb.cdp.click_nth_visible_element(selector, number)\nsb.cdp.click_with_offset(selector, x, y, center=False)\nsb.cdp.click_link(link_text)\nsb.cdp.go_back()\nsb.cdp.go_forward()\nsb.cdp.get_navigation_history()\nsb.cdp.tile_windows(windows=None, max_columns=0)\nsb.cdp.grant_permissions(permissions, origin=None)\nsb.cdp.grant_all_permissions()\nsb.cdp.reset_permissions()\nsb.cdp.get_all_cookies(*args, **kwargs)\nsb.cdp.set_all_cookies(*args, **kwargs)\nsb.cdp.save_cookies(*args, **kwargs)\nsb.cdp.load_cookies(*args, **kwargs)\nsb.cdp.clear_cookies()\nsb.cdp.sleep(seconds)\nsb.cdp.bring_active_window_to_front()\nsb.cdp.bring_to_front()\nsb.cdp.get_active_element()\nsb.cdp.get_active_element_css()\nsb.cdp.click_active_element()\nsb.cdp.mouse_click(selector, timeout=None)\nsb.cdp.nested_click(parent_selector, selector)\nsb.cdp.get_nested_element(parent_selector, selector)\nsb.cdp.select_option_by_text(dropdown_selector, option)\nsb.cdp.select_option_by_index(dropdown_selector, option)\nsb.cdp.select_option_by_value(dropdown_selector, option)\nsb.cdp.flash(selector, duration=1, color=\"44CC88\", pause=0)\nsb.cdp.highlight(selector)\nsb.cdp.focus(selector)\nsb.cdp.highlight_overlay(selector)\nsb.cdp.get_parent(element)\nsb.cdp.remove_element(selector)\nsb.cdp.remove_from_dom(selector)\nsb.cdp.remove_elements(selector)\nsb.cdp.send_keys(selector, text, timeout=None)\nsb.cdp.press_keys(selector, text, timeout=None)\nsb.cdp.type(selector, text, timeout=None)\nsb.cdp.set_value(selector, text, timeout=None)\nsb.cdp.clear_input(selector, timeout=None)\nsb.cdp.clear(selector, timeout=None)\nsb.cdp.submit(selector)\nsb.cdp.evaluate(expression)\nsb.cdp.execute_script(expression)\nsb.cdp.js_dumps(obj_name)\nsb.cdp.maximize()\nsb.cdp.minimize()\nsb.cdp.medimize()\nsb.cdp.set_window_rect(x, y, width, height)\nsb.cdp.reset_window_size()\nsb.cdp.open_new_window(url=None, switch_to=True)\nsb.cdp.switch_to_window(window)\nsb.cdp.switch_to_newest_window()\nsb.cdp.open_new_tab(url=None, switch_to=True)\nsb.cdp.switch_to_tab(tab)\nsb.cdp.switch_to_newest_tab()\nsb.cdp.close_active_tab()\nsb.cdp.get_active_tab()\nsb.cdp.get_tabs()\nsb.cdp.get_window()\nsb.cdp.get_text(selector)\nsb.cdp.get_title()\nsb.cdp.get_current_url()\nsb.cdp.get_origin()\nsb.cdp.get_html(include_shadow_dom=True)\nsb.cdp.get_page_source(include_shadow_dom=True)\nsb.cdp.get_user_agent()\nsb.cdp.get_cookie_string()\nsb.cdp.get_locale_code()\nsb.cdp.get_local_storage_item(key)\nsb.cdp.get_session_storage_item(key)\nsb.cdp.get_screen_rect()\nsb.cdp.get_window_rect()\nsb.cdp.get_window_size()\nsb.cdp.get_window_position()\nsb.cdp.get_element_rect(selector, timeout=None)\nsb.cdp.get_element_size(selector, timeout=None)\nsb.cdp.get_element_position(selector, timeout=None)\nsb.cdp.get_gui_element_rect(selector, timeout=None)\nsb.cdp.get_gui_element_center(selector, timeout=None)\nsb.cdp.get_document()\nsb.cdp.get_flattened_document()\nsb.cdp.get_element_attributes(selector)\nsb.cdp.get_element_attribute(selector, attribute)\nsb.cdp.get_attribute(selector, attribute)\nsb.cdp.get_element_html(selector)\nsb.cdp.get_mfa_code(totp_key=None)\nsb.cdp.enter_mfa_code(selector, totp_key=None, timeout=None)\nsb.cdp.activate_messenger()\nsb.cdp.set_messenger_theme(theme=\"default\", location=\"default\")\nsb.cdp.post_message(message, duration=None, pause=True, style=\"info\")\nsb.cdp.set_locale(locale)\nsb.cdp.set_local_storage_item(key, value)\nsb.cdp.set_session_storage_item(key, value)\nsb.cdp.set_attributes(selector, attribute, value)\nsb.cdp.is_attribute_present(selector, attribute, value=None)\nsb.cdp.is_online()\nsb.cdp.solve_captcha()\nsb.cdp.click_captcha()\nsb.cdp.gui_press_key(key)\nsb.cdp.gui_press_keys(keys)\nsb.cdp.gui_write(text)\nsb.cdp.gui_click_x_y(x, y, timeframe=0.25)\nsb.cdp.gui_click_element(selector, timeframe=0.25)\nsb.cdp.gui_click_with_offset(selector, x, y, timeframe=0.25, center=False)\nsb.cdp.gui_click_captcha()\nsb.cdp.gui_drag_drop_points(x1, y1, x2, y2, timeframe=0.35)\nsb.cdp.gui_drag_and_drop(drag_selector, drop_selector, timeframe=0.35)\nsb.cdp.gui_click_and_hold(selector, timeframe=0.35)\nsb.cdp.gui_hover_x_y(x, y)\nsb.cdp.gui_hover_element(selector)\nsb.cdp.gui_hover_and_click(hover_selector, click_selector)\nsb.cdp.hover_element(selector)\nsb.cdp.hover_and_click(hover_selector, click_selector)\nsb.cdp.internalize_links()\nsb.cdp.is_checked(selector)\nsb.cdp.is_selected(selector)\nsb.cdp.check_if_unchecked(selector)\nsb.cdp.select_if_unselected(selector)\nsb.cdp.uncheck_if_checked(selector)\nsb.cdp.unselect_if_selected(selector)\nsb.cdp.is_element_present(selector)\nsb.cdp.is_element_visible(selector)\nsb.cdp.is_text_visible(text, selector=\"body\")\nsb.cdp.is_exact_text_visible(text, selector=\"body\")\nsb.cdp.wait_for_text(text, selector=\"body\", timeout=None)\nsb.cdp.wait_for_text_not_visible(text, selector=\"body\", timeout=None)\nsb.cdp.wait_for_element_visible(selector, timeout=None)\nsb.cdp.wait_for_element(selector, timeout=None)\nsb.cdp.wait_for_element_not_visible(selector, timeout=None)\nsb.cdp.wait_for_element_absent(selector, timeout=None)\nsb.cdp.wait_for_any_of_elements_visible(*args, **kwargs)\nsb.cdp.wait_for_any_of_elements_present(*args, **kwargs)\nsb.cdp.assert_any_of_elements_visible(*args, **kwargs)\nsb.cdp.assert_any_of_elements_present(*args, **kwargs)\nsb.cdp.assert_element(selector, timeout=None)\nsb.cdp.assert_element_visible(selector, timeout=None)\nsb.cdp.assert_element_present(selector, timeout=None)\nsb.cdp.assert_element_absent(selector, timeout=None)\nsb.cdp.assert_element_not_visible(selector, timeout=None)\nsb.cdp.assert_element_attribute(selector, attribute, value=None)\nsb.cdp.assert_title(title)\nsb.cdp.assert_title_contains(substring)\nsb.cdp.assert_url(url)\nsb.cdp.assert_url_contains(substring)\nsb.cdp.assert_text(text, selector=\"html\", timeout=None)\nsb.cdp.assert_exact_text(text, selector=\"html\", timeout=None)\nsb.cdp.assert_text_not_visible(text, selector=\"body\", timeout=None)\nsb.cdp.assert_true()\nsb.cdp.assert_false()\nsb.cdp.assert_equal(first, second)\nsb.cdp.assert_not_equal(first, second)\nsb.cdp.assert_in(first, second)\nsb.cdp.assert_not_in(first, second)\nsb.cdp.scroll_into_view(selector)\nsb.cdp.scroll_to_y(y)\nsb.cdp.scroll_by_y(y)\nsb.cdp.scroll_to_top()\nsb.cdp.scroll_to_bottom()\nsb.cdp.scroll_up(amount=25)\nsb.cdp.scroll_down(amount=25)\nsb.cdp.save_page_source(name, folder=None)\nsb.cdp.save_as_html(name, folder=None)\nsb.cdp.save_screenshot(name, folder=None, selector=None)\nsb.cdp.print_to_pdf(name, folder=None)\nsb.cdp.save_as_pdf(name, folder=None)\n```\n\nℹ️ When available, calling `sb.METHOD()` redirects to `sb.cdp.METHOD()` when CDP Mode is active. From Pure CDP Mode, always call these methods with `sb.METHOD()` instead of `sb.cdp.METHOD()`.\n\n----\n\n<a id=\"Pure_CDP_Mode\"></a>\n\n### 🐙 <b translate=\"no\">Pure CDP Mode</b> (<code translate=\"no\">sb_cdp</code>)\n\n<b translate=\"no\">Pure CDP Mode</b> doesn't use WebDriver for anything. The browser is launched using CDP, and all browser actions are performed using CDP (or <code>PyAutoGUI</code>). Initialization:\n\n```python\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome(url=None, **kwargs)\n```\n\n<b translate=\"no\">Pure CDP Mode</b> includes all methods from regular CDP Mode, except that they're called directly from <code>sb</code> instead of <code>sb.cdp</code>. Eg: <code>sb.gui_click_captcha()</code>. To quit a CDP-launched browser, use `sb.driver.stop()`.\n\nBasic example from [SeleniumBase/examples/cdp_mode/raw_cdp_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_turnstile.py):\n\n```python\nfrom seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/apps/turnstile\"\nsb = sb_cdp.Chrome(url)\nsb.solve_captcha()\nsb.assert_element(\"img#captcha-success\")\nsb.set_messenger_theme(location=\"top_left\")\nsb.post_message(\"SeleniumBase wasn't detected\", duration=3)\nsb.driver.stop()\n```\n\nAnother example: ([SeleniumBase/examples/cdp_mode/raw_cdp_methods.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_methods.py))\n\n```python\nfrom seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/demo_page\"\nsb = sb_cdp.Chrome(url)\nsb.press_keys(\"input\", \"Text\")\nsb.highlight(\"button\")\nsb.type(\"textarea\", \"Here are some words\")\nsb.click(\"button\")\nsb.set_value(\"input#mySlider\", \"100\")\nsb.click_visible_elements(\"input.checkBoxClassB\")\nsb.select_option_by_text(\"#mySelect\", \"Set to 75%\")\nsb.gui_hover_and_click(\"#myDropdown\", \"#dropOption2\")\nsb.gui_click_element(\"#checkBox1\")\nsb.gui_drag_and_drop(\"img#logo\", \"div#drop2\")\nsb.nested_click(\"iframe#myFrame3\", \".fBox\")\nsb.sleep(2)\nsb.driver.stop()\n```\n\nℹ️ Even if you don't call `sb.driver.stop()`, the browser still quits after the script goes out-of-scope.\n\n----\n\n### 🐙 <b translate=\"no\">CDP Mode</b> Async API / Methods\n\nInitialization:\n\n```python\nfrom seleniumbase import cdp_driver\n\ndriver = await cdp_driver.start_async()\ntab = await driver.get(url, **kwargs)\n```\n\nMethods: (Sometimes `tab` is named `page` in examples)\n\n```python\nawait tab.get(url=\"about:blank\")\nawait tab.open(url=\"about:blank\")\nawait tab.find(text, best_match=False, timeout=10)  # text can be selector\nawait tab.find_all(text, timeout=10)  # text can be selector\nawait tab.select(selector, timeout=10)\nawait tab.select_all(selector, timeout=10, include_frames=False)\nawait tab.query_selector(selector)\nawait tab.query_selector_all(selector)\nawait tab.find_element_by_text(text, best_match=False)\nawait tab.find_elements_by_text(text)\nawait tab.reload(ignore_cache=True, script_to_evaluate_on_load=None)\nawait tab.evaluate(expression)\nawait tab.js_dumps(obj_name)\nawait tab.back()\nawait tab.forward()\nawait tab.get_window()\nawait tab.get_content()\nawait tab.maximize()\nawait tab.minimize()\nawait tab.fullscreen()\nawait tab.medimize()\nawait tab.set_window_size(left=0, top=0, width=1280, height=1024)\nawait tab.set_window_rect(left=0, top=0, width=1280, height=1024)\nawait tab.activate()\nawait tab.bring_to_front()\nawait tab.set_window_state(\n    left=0, top=0, width=1280, height=720, state=\"normal\")\nawait tab.get_navigation_history()\nawait tab.get_user_agent()\nawait tab.get_cookie_string()\nawait tab.get_locale_code()\nawait tab.is_online()\nawait tab.open_external_inspector()  # Open separate browser for debugging\nawait tab.close()\nawait tab.scroll_down(amount=25)\nawait tab.scroll_up(amount=25)\nawait tab.wait_for(selector=\"\", text=\"\", timeout=10)\nawait tab.set_attributes(selector, attribute, value)\nawait tab.internalize_links()\nawait tab.download_file(url, filename=None)\nawait tab.save_screenshot(\n    filename=\"auto\", format=\"png\", full_page=False)\nawait tab.print_to_pdf(filename=\"auto\")\nawait tab.set_download_path(path)\nawait tab.get_all_linked_sources()\nawait tab.get_all_urls(absolute=True)\nawait tab.get_html()\nawait tab.get_page_source()\nawait tab.is_element_present(selector)\nawait tab.is_element_visible(selector)\nawait tab.get_element_rect(selector, timeout=5)  # (window-based)\nawait tab.get_window_rect()\nawait tab.get_gui_element_rect(selector, timeout=5)  # (screen-based)\nawait tab.get_title()\nawait tab.get_current_url()\nawait tab.get_origin()\nawait tab.send_keys(selector, text, timeout=5)\nawait tab.type(selector, text, timeout=5)\nawait tab.click(selector, timeout=5)\nawait tab.click_if_visible(selector, timeout=0)\nawait tab.click_with_offset(selector, x, y, center=False, timeout=5)\nawait tab.solve_captcha()\nawait tab.click_captcha()  # Same as solve_captcha()\nawait tab.get_document()\nawait tab.get_flattened_document()\nawait tab.get_local_storage()\nawait tab.set_local_storage(items)\n```\n\n----\n\n### 🐙 <b translate=\"no\">CDP Mode</b> WebElement API / Methods\n\nAfter finding an element in CDP Mode, you can access `WebElement` methods:\n\n(Eg. After `element = sb.find_element(selector)`)\n\n```python\nelement.clear_input()\nelement.click()\nelement.click_with_offset(x, y, center=False)\nelement.flash(duration=0.5, color=\"EE4488\")\nelement.focus()\nelement.gui_click(timeframe=0.25)\nelement.highlight_overlay()\nelement.mouse_click()\nelement.mouse_drag(destination)\nelement.mouse_move()\nelement.press_keys(text)\nelement.query_selector(selector)\nelement.querySelector(selector)\nelement.query_selector_all(selector)\nelement.querySelectorAll(selector)\nelement.remove_from_dom()\nelement.save_screenshot(*args, **kwargs)\nelement.save_to_dom()\nelement.scroll_into_view()\nelement.select_option()\nelement.send_file(*file_paths)\nelement.send_keys(text)\nelement.set_text(value)\nelement.type(text)\nelement.get_position()\nelement.get_html()\nelement.get_js_attributes()\nelement.get_attribute(attribute)\nelement.get_parent()\n```\n\n----\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_text_f.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" align=\"center\" width=\"335\">\n\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_gs.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"335\" /></a></div>\n"
  },
  {
    "path": "examples/cdp_mode/__init__.py",
    "content": ""
  },
  {
    "path": "examples/cdp_mode/playwright/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Stealthy Playwright Mode 🎭</h2>\n\n🎭 <b translate=\"no\">Stealthy Playwright Mode</b> is a subset of **[SeleniumBase CDP Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md)** that launches **[Playwright](https://github.com/microsoft/playwright-python)** from an existing <b translate=\"no\">SeleniumBase</b> browser to make <span translate=\"no\">Playwright</span> stealthy (for bypassing bot-detection).  <span translate=\"no\">Playwright</span> uses <code><b>connect_over_cdp()</b></code> to attach itself onto an existing <span translate=\"no\">SeleniumBase</span> session via the <code>remote-debugging-port</code>. From here, APIs of both frameworks can be used together.\n\n--------\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=PnFD_gSmGUc\"><img src=\"https://github.com/user-attachments/assets/4c9a12e3-0ae0-446b-b38f-2178827c8377\" title=\"Stealthy Playwright Mode on YouTube\" width=\"360\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=PnFD_gSmGUc\">See Stealthy Playwright Mode on YouTube! ▶️</a></b>)</p>\n\n--------\n\n### 🎭 Getting started with <b translate=\"no\">Stealthy Playwright Mode</b>:\n\nIf `playwright` isn't already installed, then install it first:\n\n```zsh\npip install playwright\n```\n\nStealthy Playwright Mode comes in 3 formats:\n1. `sb_cdp` sync format\n2. `SB` nested sync format\n3. `cdp_driver` async format\n\n\n#### `sb_cdp` sync format (minimal boilerplate):\n\n```python\nfrom playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://example.com\")\n```\n\n#### `SB` nested sync format (minimal boilerplate):\n\n```python\nfrom playwright.sync_api import sync_playwright\nfrom seleniumbase import SB\n\nwith SB(uc=True) as sb:\n    sb.activate_cdp_mode()\n    endpoint_url = sb.cdp.get_endpoint_url()\n\n    with sync_playwright() as p:\n        browser = p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        page.goto(\"https://example.com\")\n```\n\n#### `cdp_driver` async format (minimal boilerplate):\n\n```python\nimport asyncio\nfrom seleniumbase import cdp_driver\nfrom playwright.async_api import async_playwright\n\nasync def main():\n    driver = await cdp_driver.start_async()\n    endpoint_url = driver.get_endpoint_url()\n\n    async with async_playwright() as p:\n        browser = await p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        await page.goto(\"https://example.com\")\n\nif __name__ == \"__main__\":\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main())\n```\n\n### 🎭 <b translate=\"no\">Stealthy Playwright Mode</b> details:\n\nThe `sb_cdp` and `cdp_driver` formats don't use WebDriver at all, meaning that `chromedriver` isn't needed. From these two formats, Stealthy Playwright Mode can call [CDP Mode methods](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/cdp_mode_methods.md) and Playwright methods.\n\nThe `SB()` format requires WebDriver, therefore `chromedriver` will be downloaded (as `uc_driver`) if the driver isn't already present on the local machine. The `SB()` format has access to Selenium WebDriver methods via [the SeleniumBase API](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md). Using Stealthy Playwright Mode from `SB()` grants access to all the APIs: Selenium, SeleniumBase, [UC Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md), [CDP Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md), and Playwright.\n\nIn the sync formats, `get_endpoint_url()` also applies `nest-asyncio` so that nested event loops are allowed. (Python doesn't allow nested event loops by default). Without this, you'd get the error: `\"Cannot run the event loop while another loop is running\"` when calling CDP Mode methods (such as `solve_captcha()`) from within the Playwright context manager. This `nest-asyncio` call is done behind-the-scenes so that users don't need to handle this on their own.\n\n### 🎭 <b translate=\"no\">Stealthy Playwright Mode</b> examples:\n\nHere's an example that queries Microsoft Copilot:\n\n```python\nfrom playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://copilot.microsoft.com\")\n    page.wait_for_selector(\"textarea#userInput\")\n    sb.sleep(1)\n    query = \"Playwright Python connect_over_cdp() sync example\"\n    page.fill(\"textarea#userInput\", query)\n    page.click('button[data-testid=\"submit-button\"]')\n    sb.sleep(3)\n    sb.solve_captcha()\n    page.wait_for_selector('button[data-testid*=\"-thumbs-up\"]')\n    sb.sleep(4)\n    page.click('button[data-testid*=\"scroll-to-bottom\"]')\n    sb.sleep(3)\n    chat_results = '[data-testid=\"highlighted-chats\"]'\n    result = page.locator(chat_results).inner_text()\n    print(result.replace(\"\\n\\n\", \" \\n\"))\n```\n\nHere's an example that solves the Bing CAPTCHA:\n\n```python\nfrom playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome(locale=\"en\")\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://www.bing.com/turing/captcha/challenge\")\n    sb.sleep(3)\n    sb.solve_captcha()\n    sb.sleep(3)\n```\n\nFor more examples, see [examples/cdp_mode/playwright](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode/playwright).\n\n--------\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"100\" /></a><img src=\"https://seleniumbase.github.io/other/playwright_logo.png\" alt=\"Playwright\" title=\"Playwright\" width=\"161\">\n"
  },
  {
    "path": "examples/cdp_mode/playwright/__init__.py",
    "content": ""
  },
  {
    "path": "examples/cdp_mode/playwright/raw_basic_async.py",
    "content": "import asyncio\nfrom playwright.async_api import async_playwright\nfrom seleniumbase import cdp_driver\n\n\nasync def main():\n    driver = await cdp_driver.start_async()\n    endpoint_url = driver.get_endpoint_url()\n\n    async with async_playwright() as p:\n        browser = await p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        await page.goto(\"https://seleniumbase.io/simple/login\")\n        await page.fill(\"#username\", \"demo_user\")\n        await page.fill(\"#password\", \"secret_pass\")\n        await page.click(\"#log-in\")\n        await page.wait_for_selector(\"h1\")\n        await driver.sleep(1)\n\n\nif __name__ == \"__main__\":\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main())\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_basic_nested.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import SB\n\nwith SB(uc=True) as sb:\n    sb.activate_cdp_mode()\n    endpoint_url = sb.cdp.get_endpoint_url()\n\n    with sync_playwright() as p:\n        browser = p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        page.goto(\"https://seleniumbase.io/simple/login\")\n        page.fill(\"#username\", \"demo_user\")\n        page.fill(\"#password\", \"secret_pass\")\n        page.click(\"#log-in\")\n        page.wait_for_selector(\"h1\")\n        sb.sleep(1)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_basic_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://seleniumbase.io/simple/login\")\n    page.fill(\"#username\", \"demo_user\")\n    page.fill(\"#password\", \"secret_pass\")\n    page.click(\"#log-in\")\n    page.wait_for_selector(\"h1\")\n    sb.sleep(1)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_bing_cap_async.py",
    "content": "import asyncio\nfrom playwright.async_api import async_playwright\nfrom seleniumbase import cdp_driver\n\n\nasync def main():\n    driver = await cdp_driver.start_async(locale=\"en\")\n    endpoint_url = driver.get_endpoint_url()\n\n    async with async_playwright() as p:\n        browser = await p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        await page.goto(\"https://www.bing.com/turing/captcha/challenge\")\n        await driver.sleep(3)\n        await driver.solve_captcha()\n        await driver.sleep(3)\n\n\nif __name__ == \"__main__\":\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main())\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_bing_cap_nested.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import SB\n\nwith SB(uc=True, locale=\"en\") as sb:\n    sb.activate_cdp_mode()\n    endpoint_url = sb.cdp.get_endpoint_url()\n\n    with sync_playwright() as p:\n        browser = p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        page.goto(\"https://www.bing.com/turing/captcha/challenge\")\n        sb.sleep(3)\n        sb.solve_captcha()\n        sb.sleep(3)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_bing_cap_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome(locale=\"en\")\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://www.bing.com/turing/captcha/challenge\")\n    sb.sleep(3)\n    sb.solve_captcha()\n    sb.sleep(3)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_cf_cap_sync.py",
    "content": "from playwright.sync_api import sync_playwright\r\nfrom seleniumbase import sb_cdp\r\n\r\nsb = sb_cdp.Chrome(locale=\"en\")\r\nendpoint_url = sb.get_endpoint_url()\r\n\r\nwith sync_playwright() as p:\r\n    browser = p.chromium.connect_over_cdp(endpoint_url)\r\n    context = browser.contexts[0]\r\n    page = context.pages[0]\r\n    page.goto(\"https://www.cloudflare.com/login\")\r\n    sb.sleep(3)\r\n    sb.solve_captcha()\r\n    sb.sleep(3)\r\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_copilot_async.py",
    "content": "import asyncio\nfrom playwright.async_api import async_playwright\nfrom seleniumbase import cdp_driver\n\n\nasync def main():\n    driver = await cdp_driver.start_async()\n    endpoint_url = driver.get_endpoint_url()\n\n    async with async_playwright() as p:\n        browser = await p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        await page.goto(\"https://copilot.microsoft.com\")\n        await page.wait_for_selector(\"textarea#userInput\")\n        await driver.sleep(1)\n        query = \"Playwright Python connect_over_cdp() sync example\"\n        await page.fill(\"textarea#userInput\", query)\n        await page.click('button[data-testid=\"submit-button\"]')\n        await driver.sleep(4)\n        await driver.solve_captcha()\n        await page.wait_for_selector('button[data-testid*=\"-thumbs-up\"]')\n        await driver.sleep(4)\n        await page.click('button[data-testid*=\"scroll-to-bottom\"]')\n        await driver.sleep(3)\n        chat_results = '[data-testid=\"highlighted-chats\"]'\n        result = await page.locator(chat_results).inner_text()\n        print(result.replace(\"\\n\\n\", \" \\n\"))\n\n\nif __name__ == \"__main__\":\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main())\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_copilot_nested.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import SB\n\nwith SB(uc=True) as sb:\n    sb.activate_cdp_mode()\n    endpoint_url = sb.cdp.get_endpoint_url()\n\n    with sync_playwright() as p:\n        browser = p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        page.goto(\"https://copilot.microsoft.com\")\n        page.wait_for_selector(\"textarea#userInput\")\n        sb.sleep(1)\n        query = \"Playwright Python connect_over_cdp() sync example\"\n        page.fill(\"textarea#userInput\", query)\n        page.click('button[data-testid=\"submit-button\"]')\n        sb.sleep(4)\n        sb.solve_captcha()\n        page.wait_for_selector('button[data-testid*=\"-thumbs-up\"]')\n        sb.sleep(4)\n        page.click('button[data-testid*=\"scroll-to-bottom\"]')\n        sb.sleep(3)\n        chat_results = '[data-testid=\"highlighted-chats\"]'\n        result = page.locator(chat_results).inner_text()\n        print(result.replace(\"\\n\\n\", \" \\n\"))\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_copilot_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://copilot.microsoft.com\")\n    page.wait_for_selector(\"textarea#userInput\")\n    sb.sleep(1)\n    query = \"Playwright Python connect_over_cdp() sync example\"\n    page.fill(\"textarea#userInput\", query)\n    page.click('button[data-testid=\"submit-button\"]')\n    sb.sleep(4)\n    sb.solve_captcha()\n    page.wait_for_selector('button[data-testid*=\"-thumbs-up\"]')\n    sb.sleep(4)\n    page.click('button[data-testid*=\"scroll-to-bottom\"]')\n    sb.sleep(3)\n    chat_results = '[data-testid=\"highlighted-chats\"]'\n    result = page.locator(chat_results).inner_text()\n    print(result.replace(\"\\n\\n\", \" \\n\"))\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_footlocker_sync.py",
    "content": "from playwright.sync_api import sync_playwright\r\nfrom seleniumbase import sb_cdp\r\n\r\nsb = sb_cdp.Chrome(locale=\"en\", ad_block=True)\r\nendpoint_url = sb.get_endpoint_url()\r\n\r\nwith sync_playwright() as p:\r\n    browser = p.chromium.connect_over_cdp(endpoint_url)\r\n    context = browser.contexts[0]\r\n    page = context.pages[0]\r\n    page.goto(\"https://www.footlocker.com/\")\r\n    input_field = 'input[name=\"query\"]'\r\n    page.wait_for_selector(input_field)\r\n    sb.sleep(1.5)\r\n    sb.click_if_visible('button[id*=\"Agree\"]')\r\n    sb.sleep(1.2)\r\n    page.click(input_field)\r\n    sb.sleep(0.5)\r\n    search = \"Nike Shoes\"\r\n    sb.press_keys(input_field, search)\r\n    sb.sleep(1.2)\r\n    page.click('ul[id*=\"typeahead\"] li div')\r\n    sb.sleep(3.5)\r\n    elements = sb.select_all(\"a.ProductCard-link\")\r\n    if elements:\r\n        print('**** Found results for \"%s\": ****' % search)\r\n    for element in elements:\r\n        print(\"------------------ >>>\")\r\n        print(\"* \" + element.text)\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_gas_info_async.py",
    "content": "import asyncio\nfrom playwright.async_api import async_playwright\nfrom seleniumbase import cdp_driver\n\n\nasync def main():\n    driver = await cdp_driver.start_async()\n    endpoint_url = driver.get_endpoint_url()\n    tab = await driver.get(\"about:blank\")\n\n    async with async_playwright() as p:\n        browser = await p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        url = (\n            \"https://www.gassaferegister.co.uk/gas-safety\"\n            \"/gas-safety-certificates-records/building-regulations-certificate\"\n            \"/order-replacement-building-regulations-certificate/\"\n        )\n        await page.goto(url)\n        await tab.sleep(0.6)\n        await tab.solve_captcha()\n        await page.wait_for_selector(\"#SearchTerm\")\n        await tab.sleep(1.4)\n        allow_cookies = 'button:contains(\"Allow all cookies\")'\n        await tab.click_if_visible(allow_cookies, timeout=2)\n        await tab.sleep(1)\n        await page.fill(\"#SearchTerm\", \"Hydrogen\")\n        await page.click(\"button.search-button\")\n        await tab.sleep(3)\n        results = await tab.query_selector_all(\"div.search-result\")\n        for result in results:\n            print(result.text.replace(\" \" * 12, \" \").strip() + \"\\n\")\n        await tab.scroll_down(50)\n        await tab.sleep(1)\n\n\nif __name__ == \"__main__\":\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main())\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_gas_info_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    url = (\n        \"https://www.gassaferegister.co.uk/gas-safety\"\n        \"/gas-safety-certificates-records/building-regulations-certificate\"\n        \"/order-replacement-building-regulations-certificate/\"\n    )\n    page.goto(url)\n    sb.sleep(0.6)\n    sb.solve_captcha()\n    page.wait_for_selector(\"#SearchTerm\")\n    sb.sleep(1.4)\n    allow_cookies = 'button:contains(\"Allow all cookies\")'\n    sb.click_if_visible(allow_cookies, timeout=2)\n    sb.sleep(1)\n    page.fill(\"#SearchTerm\", \"Hydrogen\")\n    page.click(\"button.search-button\")\n    sb.sleep(3)\n    items = page.locator(\"div.search-result\")\n    for i in range(items.count()):\n        item_text = items.nth(i).inner_text()\n        print(item_text.replace(\"\\n\\n\", \"\\n\") + \"\\n\")\n    sb.scroll_to_bottom()\n    sb.sleep(1)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_gitlab_async.py",
    "content": "import asyncio\nfrom playwright.async_api import async_playwright\nfrom seleniumbase import cdp_driver\n\n\nasync def main():\n    driver = await cdp_driver.start_async(locale=\"en\", agent=\"headless\")\n    endpoint_url = driver.get_endpoint_url()\n\n    async with async_playwright() as p:\n        browser = await p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        await page.goto(\"https://gitlab.com/users/sign_in\")\n        await driver.sleep(3)\n        await driver.solve_captcha()\n        await driver.sleep(1)\n        await page.locator('label[for=\"user_login\"]').click()\n        await page.wait_for_selector('[data-testid=\"sign-in-button\"]')\n        await page.locator(\"#user_login\").fill(\"Username\")\n        await driver.sleep(2)\n\n\nif __name__ == \"__main__\":\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main())\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_gitlab_nested.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import SB\n\nwith SB(uc=True, locale=\"en\") as sb:\n    sb.activate_cdp_mode()\n    endpoint_url = sb.cdp.get_endpoint_url()\n\n    with sync_playwright() as p:\n        browser = p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        page.goto(\"https://gitlab.com/users/sign_in\")\n        sb.sleep(3)\n        sb.solve_captcha()\n        sb.sleep(1)\n        page.locator('label[for=\"user_login\"]').click()\n        page.wait_for_selector('[data-testid=\"sign-in-button\"]')\n        page.locator(\"#user_login\").fill(\"Username\")\n        sb.sleep(2)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_gitlab_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome(locale=\"en\")\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://gitlab.com/users/sign_in\")\n    sb.sleep(3)\n    sb.solve_captcha()\n    sb.sleep(1)\n    page.locator('label[for=\"user_login\"]').click()\n    page.wait_for_selector('[data-testid=\"sign-in-button\"]')\n    page.locator(\"#user_login\").fill(\"Username\")\n    sb.sleep(2)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_idealista_nested.py",
    "content": "\"\"\"(Bypasses the DataDome slider CAPTCHA)\"\"\"\nfrom playwright.sync_api import sync_playwright\nfrom seleniumbase import SB\n\nwith SB(uc=True, locale=\"es\") as sb:\n    url = \"https://www.idealista.com/venta-viviendas/barcelona-provincia/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1)\n    sb.solve_captcha()\n    sb.sleep(2)\n    endpoint_url = sb.cdp.get_endpoint_url()\n\n    with sync_playwright() as p:\n        browser = p.chromium.connect_over_cdp(endpoint_url)\n        context = browser.contexts[0]\n        page = context.pages[0]\n        page.click(\"button#didomi-notice-agree-button\")\n        page.wait_for_timeout(1000)\n        print(\"*** \" + page.locator(\"h1\").inner_text())\n        items = page.locator(\"div.item-info-container\")\n        for i in range(items.count()):\n            item = items.nth(i)\n            print(item.locator(\"a.item-link\").text_content().strip())\n            print(item.locator(\"span.item-price\").text_content().strip())\n            item_text = items.nth(i)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_nike_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://www.nike.com/\")\n    page.click('[data-testid=\"user-tools-container\"] search')\n    search = \"Pegasus\"\n    page.fill('input[type=\"search\"]', search)\n    sb.sleep(4)\n    details = 'ul[data-testid*=\"products\"] figure .details'\n    items = page.locator(details)\n    if items:\n        print('**** Found results for \"%s\": ****' % search)\n    for i in range(items.count()):\n        item = items.nth(i)\n        print(item.inner_text())\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_nordstrom_sync.py",
    "content": "from playwright.sync_api import sync_playwright\r\nfrom seleniumbase import sb_cdp\r\n\r\nsb = sb_cdp.Chrome(locale=\"en\")\r\nendpoint_url = sb.get_endpoint_url()\r\n\r\nwith sync_playwright() as p:\r\n    browser = p.chromium.connect_over_cdp(endpoint_url)\r\n    context = browser.contexts[0]\r\n    page = context.pages[0]\r\n    page.goto(\"https://www.nordstrom.com/\")\r\n    sb.sleep(2)\r\n    page.click(\"input#keyword-search-input\")\r\n    sb.sleep(0.8)\r\n    search = \"cocktail dresses for women teal\"\r\n    sb.press_keys(\"input#keyword-search-input\", search + \"\\n\")\r\n    sb.sleep(2.2)\r\n    for i in range(17):\r\n        sb.scroll_down(16)\r\n        sb.sleep(0.14)\r\n    print('*** Nordstrom Search for \"%s\":' % search)\r\n    unique_item_text = []\r\n    items = sb.find_elements(\"article\")\r\n    for item in items:\r\n        description = item.querySelector(\"article h3\")\r\n        if description and description.text not in unique_item_text:\r\n            unique_item_text.append(description.text)\r\n            price_text = \"\"\r\n            price = item.querySelector('div div span[aria-hidden=\"true\"]')\r\n            if price:\r\n                price_text = price.text\r\n                print(\"* %s (%s)\" % (description.text, price_text))\r\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_planetmc_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    page.goto(\"https://www.planetminecraft.com/account/sign_in/\")\n    sb.sleep(2)\n    sb.solve_captcha()\n    sb.wait_for_element_absent(\"input[disabled]\")\n    sb.sleep(2)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_reddit_sync.py",
    "content": "from playwright.sync_api import sync_playwright\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome(use_chromium=True)\nendpoint_url = sb.get_endpoint_url()\n\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(endpoint_url)\n    context = browser.contexts[0]\n    page = context.pages[0]\n    search = \"reddit+scraper\"\n    url = f\"https://www.reddit.com/r/webscraping/search/?q={search}\"\n    page.goto(url)\n    sb.solve_captcha()  # Might not be needed\n    sb.sleep(1)\n    post_title = '[data-testid=\"post-title\"]'\n    page.wait_for_selector(post_title)\n    for i in range(8):\n        sb.scroll_down(25)\n        sb.sleep(0.2)\n    print('*** Reddit Posts for \"%s\":' % search)\n    items = page.locator(post_title)\n    for i in range(items.count()):\n        item_text = items.nth(i).inner_text()\n        print(\"* \" + item_text)\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_seatgeek_sync.py",
    "content": "from playwright.sync_api import sync_playwright\r\nfrom seleniumbase import sb_cdp\r\n\r\nsb = sb_cdp.Chrome(locale=\"en\", ad_block=True)\r\nendpoint_url = sb.get_endpoint_url()\r\n\r\nwith sync_playwright() as p:\r\n    browser = p.chromium.connect_over_cdp(endpoint_url)\r\n    context = browser.contexts[0]\r\n    page = context.pages[0]\r\n    page.goto(\"https://seatgeek.com/\")\r\n    input_field = 'input[name=\"search\"]'\r\n    page.wait_for_selector(input_field)\r\n    sb.sleep(1.6)\r\n    query = \"Jerry Seinfeld\"\r\n    sb.press_keys(input_field, query)\r\n    sb.sleep(1.6)\r\n    page.click(\"li#active-result-item\")\r\n    sb.sleep(4.2)\r\n    print('*** SeatGeek Search for \"%s\":' % query)\r\n    items = page.locator('[data-testid=\"listing-item\"]')\r\n    for i in range(items.count()):\r\n        item_text = items.nth(i).inner_text()\r\n        print(item_text.replace(\"\\n\\n\", \"\\n\"))\r\n"
  },
  {
    "path": "examples/cdp_mode/playwright/raw_walmart_sync.py",
    "content": "from playwright.sync_api import sync_playwright\r\nfrom seleniumbase import sb_cdp\r\n\r\nsb = sb_cdp.Chrome(locale=\"en\", guest=True)\r\nendpoint_url = sb.get_endpoint_url()\r\n\r\nwith sync_playwright() as p:\r\n    browser = p.chromium.connect_over_cdp(endpoint_url)\r\n    context = browser.contexts[0]\r\n    page = context.pages[0]\r\n    page.goto(\"https://www.walmart.com/\")\r\n    sb.sleep(2.6)\r\n    page.click('input[aria-label=\"Search\"]')\r\n    sb.sleep(1.4)\r\n    search = \"Settlers of Catan Board Game\"\r\n    required_text = \"Catan\"\r\n    sb.press_keys('input[aria-label=\"Search\"]', search + \"\\n\")\r\n    sb.sleep(3.8)\r\n    sb.remove_elements('[data-testid=\"skyline-ad\"]')\r\n    sb.remove_elements('[data-testid=\"sba-container\"]')\r\n    print('*** Walmart Search for \"%s\":' % search)\r\n    print('    (Results must contain \"%s\".)' % required_text)\r\n    unique_item = []\r\n    sb.click_if_visible('[data-automation-id=\"sb-btn-close-mark\"]')\r\n    items = page.locator('[data-item-id]')\r\n    for i in range(items.count()):\r\n        item = items.nth(i)\r\n        if required_text in item.inner_text():\r\n            description = item.locator('[data-automation-id=\"product-title\"]')\r\n            if (\r\n                description\r\n                and description.is_visible()\r\n                and description.inner_text() not in unique_item\r\n            ):\r\n                unique_item.append(description.inner_text())\r\n                print(\"* \" + description.inner_text())\r\n                price = item.locator('[data-automation-id=\"product-price\"]')\r\n                if price:\r\n                    price_text = price.inner_text()\r\n                    price_text = price_text.split(\"current price Now \")[-1]\r\n                    price_text = price_text.split(\"current price \")[-1]\r\n                    price_text = price_text.split(\" \")[0]\r\n                    print(\"  (\" + price_text + \")\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_ad_blocking.py",
    "content": "import mycdp\r\nfrom seleniumbase import decorators\r\nfrom seleniumbase import sb_cdp\r\n\r\n\r\nasync def block_urls(tab):\r\n    await tab.send(mycdp.network.enable())\r\n    await tab.send(mycdp.network.set_blocked_urls(\r\n        urls=[\r\n            \"*.googlesyndication.com*\",\r\n            \"*.googletagmanager.com*\",\r\n            \"*.google-analytics.com*\",\r\n            \"*.amazon-adsystem.com*\",\r\n            \"*.adsafeprotected.com*\",\r\n            \"*.doubleclick.net*\",\r\n            \"*.fastclick.net*\",\r\n            \"*.snigelweb.com*\",\r\n            \"*.2mdn.net*\",\r\n        ]\r\n    ))\r\n\r\nwith decorators.print_runtime(\"raw_ad_blocking.py\"):\r\n    sb = sb_cdp.Chrome()\r\n    loop = sb.get_event_loop()\r\n    loop.run_until_complete(block_urls(sb.get_active_tab()))\r\n    sb.open(\"https://www.w3schools.com/jquery/default.asp\")\r\n    source = sb.get_page_source()\r\n    sb.assert_false(\"doubleclick.net\" in source)\r\n    sb.assert_false(\"google-analytics.com\" in source)\r\n    sb.post_message(\"Blocking was successful!\")\r\n    sb.driver.quit()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_ahrefs.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True, locale=\"en\") as sb:\r\n    url = \"https://ahrefs.com/website-authority-checker\"\r\n    input_field = 'input[placeholder=\"Enter domain\"]'\r\n    submit_button = 'span:contains(\"Check Authority\")'\r\n    sb.activate_cdp_mode(url)  # The bot-check is later\r\n    sb.type(input_field, \"github.com/seleniumbase/SeleniumBase\")\r\n    sb.scroll_down(36)\r\n    sb.click(submit_button)\r\n    sb.sleep(2)\r\n    sb.solve_captcha()\r\n    sb.sleep(3)\r\n    sb.wait_for_text_not_visible(\"Checking\", timeout=15)\r\n    sb.click_if_visible('button[data-cky-tag=\"close-button\"]')\r\n    sb.highlight('p:contains(\"github.com/seleniumbase/SeleniumBase\")')\r\n    sb.highlight('a:contains(\"Top 100 backlinks\")')\r\n    sb.set_messenger_theme(location=\"bottom_center\")\r\n    sb.post_message(\"SeleniumBase wasn't detected!\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_albertsons.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.albertsons.com/recipes/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.5)\r\n    sb.remove_element(\"div > div > article\")\r\n    sb.scroll_into_view('input[type=\"search\"]')\r\n    close_btn = \".notification-alert-wrapper__close-button\"\r\n    sb.click_if_visible(close_btn)\r\n    sb.click(\"input#search-suggestion-input\")\r\n    sb.sleep(0.2)\r\n    search = \"Avocado Smoked Salmon\"\r\n    required_text = \"Salmon\"\r\n    sb.press_keys(\"input#search-suggestion-input\", search)\r\n    sb.sleep(0.8)\r\n    sb.click(\"#suggestion-0 a span\")\r\n    sb.sleep(0.8)\r\n    sb.click_if_visible(close_btn)\r\n    sb.sleep(3.2)\r\n    print('*** Albertsons Search for \"%s\":' % search)\r\n    print('    (Results must contain \"%s\".)' % required_text)\r\n    unique_item_text = []\r\n    item_selector = 'a[href*=\"/meal-plans-recipes/shop/\"]'\r\n    items = sb.find_elements(item_selector)\r\n    for item in items:\r\n        sb.sleep(0.06)\r\n        if required_text in item.text:\r\n            item.flash(color=\"44CC88\")\r\n            sb.sleep(0.025)\r\n            if item.text not in unique_item_text:\r\n                unique_item_text.append(item.text)\r\n                print(\"* \" + item.text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_amazon.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True, ad_block=True) as sb:\n    url = \"https://www.amazon.com\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.click_if_visible('button[alt=\"Continue shopping\"]')\n    sb.sleep(2)\n    sb.press_keys('input[role=\"searchbox\"]', \"TI-89\\n\")\n    sb.sleep(3)\n    for i in range(16):\n        sb.cdp.scroll_down(16)\n    print(sb.get_page_title())\n    sb.save_as_pdf_to_logs()\n    sb.save_page_source_to_logs()\n    sb.save_screenshot_to_logs()\n    print(\"Logs have been saved to: ./latest_logs/\")\n"
  },
  {
    "path": "examples/cdp_mode/raw_antibot.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/antibot/login\"\n    sb.activate_cdp_mode(url)\n    sb.press_keys(\"input#username\", \"demo_user\")\n    sb.press_keys(\"input#password\", \"secret_pass\")\n    sb.click(\"button#myButton\")\n    sb.sleep(1.5)\n    sb.click(\"a#log-in\")\n    sb.assert_text(\"Welcome!\", \"h1\")\n    sb.set_messenger_theme(location=\"bottom_center\")\n    sb.post_message(\"SeleniumBase wasn't detected!\")\n    sb.sleep(1.5)\n"
  },
  {
    "path": "examples/cdp_mode/raw_async.py",
    "content": "import asyncio\r\nimport time\r\nfrom contextlib import suppress\r\nfrom seleniumbase import sb_cdp\r\nfrom seleniumbase import cdp_driver\r\n\r\n\r\nasync def main():\r\n    url = \"https://seleniumbase.io/simple/login\"\r\n    driver = await cdp_driver.start_async()\r\n    page = await driver.get(url, lang=\"en\")\r\n    print(await page.get_title())\r\n    await page.type(\"#username\", \"demo_user\")\r\n    await page.type(\"#password\", \"secret_pass\")\r\n    await page.click(\"#log-in\")\r\n    print(await page.get_title())\r\n    element = await page.select(\"h1\")\r\n    assert element.text == \"Welcome!\"\r\n    top_nav = await page.select(\"div.topnav\")\r\n    links = await top_nav.query_selector_all_async(\"a\")\r\n    for nav_item in links:\r\n        print(nav_item.text)\r\n    driver.stop()\r\n\r\nif __name__ == \"__main__\":\r\n    # Call an async function with awaited methods\r\n    loop = asyncio.new_event_loop()\r\n    loop.run_until_complete(main())\r\n\r\n    # An example of wrapping all async calls with event loops\r\n    driver = cdp_driver.start_sync()\r\n    page = loop.run_until_complete(driver.get(\"about:blank\"))\r\n    loop.run_until_complete(page.set_locale(\"en\"))\r\n    loop.run_until_complete(page.get(\"https://www.pokemon.com/us\"))\r\n    time.sleep(3)\r\n    print(loop.run_until_complete(page.evaluate(\"document.title\")))\r\n    with suppress(Exception):\r\n        selector = \"button#onetrust-reject-all-handler\"\r\n        element = loop.run_until_complete(page.select(selector, timeout=1))\r\n        loop.run_until_complete(element.click_async())\r\n        time.sleep(1)\r\n    element = loop.run_until_complete(page.select(\"span.icon_pokeball\"))\r\n    loop.run_until_complete(element.click_async())\r\n    time.sleep(2)\r\n    print(loop.run_until_complete(page.evaluate(\"document.title\")))\r\n    time.sleep(1)\r\n    driver.stop()\r\n\r\n    # Call CDP methods via the simplified SB CDP API\r\n    sb = sb_cdp.Chrome(\"https://www.priceline.com/\")\r\n    sb.sleep(2.5)\r\n    sb.internalize_links()  # Don't open links in a new tab\r\n    sb.click(\"#link_header_nav_experiences\")\r\n    sb.sleep(3.5)\r\n    sb.remove_elements(\"msm-cookie-banner\")\r\n    sb.sleep(1.5)\r\n    location = \"Amsterdam\"\r\n    where_to = 'div[data-automation*=\"experiences\"] input'\r\n    button = 'button[data-automation*=\"experiences-search\"]'\r\n    sb.wait_for_text(\"Where to?\")\r\n    sb.click(where_to)\r\n    sb.press_keys(where_to, location)\r\n    sb.sleep(1)\r\n    sb.click(button)\r\n    sb.sleep(3)\r\n    print(sb.get_title())\r\n    print(\"************\")\r\n    cards = sb.select_all('span[data-automation*=\"product-list-card\"]')\r\n    for card in cards:\r\n        print(\"* %s\" % card.text)\r\n    sb.driver.stop()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_basic_async.py",
    "content": "import asyncio\r\nfrom seleniumbase import cdp_driver\r\nfrom seleniumbase import decorators\r\n\r\n\r\nasync def main():\r\n    url = \"https://seleniumbase.io/simple/login\"\r\n    driver = await cdp_driver.start_async()\r\n    page = await driver.get(url, lang=\"en\")\r\n    print(await page.get_title())\r\n    await page.type(\"#username\", \"demo_user\")\r\n    await page.type(\"#password\", \"secret_pass\")\r\n    await page.click(\"#log-in\")\r\n    print(await page.get_title())\r\n    element = await page.select(\"h1\")\r\n    assert element.text == \"Welcome!\"\r\n    top_nav = await page.select(\"div.topnav\")\r\n    links = await top_nav.query_selector_all_async(\"a\")\r\n    for nav_item in links:\r\n        print(nav_item.text)\r\n    driver.stop()\r\n\r\nif __name__ == \"__main__\":\r\n    loop = asyncio.new_event_loop()\r\n    with decorators.print_runtime(\"raw_basic_async.py\"):\r\n        loop.run_until_complete(main())\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_basic_cdp.py",
    "content": "from seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/simple/login\"\nsb = sb_cdp.Chrome(url)\nsb.type(\"#username\", \"demo_user\")\nsb.type(\"#password\", \"secret_pass\")\nsb.click('a:contains(\"Sign in\")')\nsb.assert_exact_text(\"Welcome!\", \"h1\")\nsb.assert_element(\"img#image1\")\nsb.highlight(\"#image1\")\ntop_nav = sb.find_element(\"div.topnav\")\nlinks = top_nav.query_selector_all(\"a\")\nfor nav_item in links:\n    print(nav_item.text)\nsb.click_link(\"Sign out\")\nsb.assert_text(\"signed out\", \"#top_message\")\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_basic_mobile.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True, mobile=True) as sb:\n    url = \"https://gitlab.com/users/sign_in\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.solve_captcha()\n    # (The rest is for testing and demo purposes)\n    sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\n    sb.assert_element('label[for=\"user_login\"]')\n    sb.highlight('button:contains(\"Sign in\")')\n    sb.highlight('h1:contains(\"GitLab\")')\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\n"
  },
  {
    "path": "examples/cdp_mode/raw_bestwestern.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", guest=True) as sb:\r\n    url = \"https://www.bestwestern.com/en_US.html\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(3)\r\n    sb.click_if_visible(\".onetrust-close-btn-handler\")\r\n    sb.sleep(1)\r\n    sb.click(\"input#destination-input\")\r\n    sb.sleep(2)\r\n    location = \"Palm Springs, CA, USA\"\r\n    sb.press_keys(\"input#destination-input\", location)\r\n    sb.sleep(1)\r\n    sb.click(\"ul#google-suggestions li\")\r\n    sb.sleep(1)\r\n    sb.click(\"button#btn-modify-stay-update\")\r\n    sb.sleep(4)\r\n    sb.click(\"label#available-label\")\r\n    sb.sleep(2.5)\r\n    print(\"Best Western Hotels in %s:\" % location)\r\n    summary_details = sb.get_text(\"#summary-details-column\")\r\n    dates = summary_details.split(\"DESTINATION\")[-1]\r\n    dates = dates.split(\" CHECK-OUT\")[0].strip() + \" CHECK-OUT\"\r\n    dates = dates.replace(\"  \", \" \")\r\n    print(\"(Dates: %s)\" % dates)\r\n    flip_cards = sb.select_all(\".flipCard\")\r\n    for i, flip_card in enumerate(flip_cards):\r\n        hotel = flip_card.query_selector(\".hotelName\")\r\n        price = flip_card.query_selector(\".priceSection\")\r\n        if hotel and price:\r\n            print(\"* %s: %s => %s\" % (\r\n                i + 1, hotel.text.strip(), price.text.strip())\r\n            )\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_browserscan.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True, ad_block=True) as sb:\n    url = \"https://www.browserscan.net/bot-detection\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1)\n    sb.cdp.flash(\"Test Results\", duration=4)\n    sb.sleep(1)\n    sb.assert_element('strong:contains(\"Normal\")')\n    sb.cdp.flash('strong:contains(\"Normal\")', duration=4, pause=4)\n"
  },
  {
    "path": "examples/cdp_mode/raw_canvas.py",
    "content": "\"\"\"Use SeleniumBase to interact with \"canvas\" elements.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\n\r\ndef get_canvas_pixel_colors_at_top_left(sb):\r\n    # Return the RGB colors of the canvas's top left pixel\r\n    color = sb.evaluate(\r\n        \"document.querySelector('canvas').getContext('2d')\"\r\n        \".getImageData(%s,%s,1,1).data;\" % (0, 0)\r\n    )\r\n    return [color[\"0\"], color[\"1\"], color[\"2\"]]\r\n\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    # Testing sb.cdp.click_with_offset()\r\n    url = \"https://seleniumbase.io/canvas/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.assert_title_contains(\"Canvas\")\r\n    sb.highlight(\"canvas\")\r\n    rgb = get_canvas_pixel_colors_at_top_left(sb)\r\n    sb.assert_equal(rgb, [221, 242, 231])  # Looks greenish\r\n    sb.click_with_offset(\"canvas\", 500, 350)\r\n    sb.highlight(\"canvas\", loops=5)\r\n    rgb = get_canvas_pixel_colors_at_top_left(sb)\r\n    sb.assert_equal(rgb, [39, 43, 56])  # Blue by hamburger\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    # Testing sb.cdp.gui_click_with_offset()\r\n    url = \"https://seleniumbase.io/other/canvas\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.assert_title_contains(\"Canvas\")\r\n    sb.click_with_offset(\"canvas\", 0, 0, center=True)\r\n    sb.sleep(1)\r\n    sb.uc_gui_press_key(\"ENTER\")\r\n    sb.sleep(0.5)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp.py",
    "content": "\"\"\"Example of using CDP Mode without WebDriver\"\"\"\r\nfrom seleniumbase import decorators\r\nfrom seleniumbase import sb_cdp\r\n\r\n\r\n@decorators.print_runtime(\"CDP Priceline Example\")\r\ndef main():\r\n    url = \"https://www.priceline.com/\"\r\n    sb = sb_cdp.Chrome(url, lang=\"en\")\r\n    sb.sleep(2)\r\n    sb.internalize_links()  # Don't open links in a new tab\r\n    sb.click(\"#link_header_nav_experiences\")\r\n    sb.sleep(3)\r\n    sb.remove_elements(\"msm-cookie-banner\")\r\n    sb.sleep(1)\r\n    location = \"Amsterdam\"\r\n    where_to = 'div[data-automation*=\"experiences\"] input'\r\n    button = 'button[data-automation*=\"experiences-search\"]'\r\n    sb.wait_for_text(\"Where to?\")\r\n    sb.click(where_to)\r\n    sb.press_keys(where_to, location)\r\n    sb.sleep(1)\r\n    sb.click(button)\r\n    sb.sleep(2)\r\n    sb.click_if_visible('button[aria-label=\"Close\"]')\r\n    sb.sleep(1)\r\n    print(sb.get_title())\r\n    print(\"************\")\r\n    for i in range(8):\r\n        sb.scroll_down(50)\r\n        sb.sleep(0.2)\r\n    cards = sb.select_all('span[data-automation*=\"product-list-card\"]')\r\n    for card in cards:\r\n        print(\"* %s\" % card.text)\r\n    sb.driver.stop()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    main()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_copilot.py",
    "content": "from seleniumbase import sb_cdp\r\n\r\nurl = \"https://copilot.microsoft.com/\"\r\nsb = sb_cdp.Chrome(url, locale=\"en\", guest=True)\r\ntextarea = \"textarea#userInput\"\r\nsb.wait_for_element(textarea)\r\nsb.sleep(1.3)\r\nsb.click_if_visible('[aria-label=\"Dismiss\"]')\r\nsb.sleep(0.5)\r\nsb.click('button[data-testid*=\"chat-mode-\"]')\r\nsb.sleep(1.1)\r\nsb.click_if_visible('button[title^=\"Think\"]')\r\nsb.sleep(1.1)\r\nquery = \"How to start automating with SeleniumBase?\"\r\nsb.press_keys(textarea, query)\r\nsb.sleep(1.1)\r\nseen_text = sb.get_text(textarea)\r\nif seen_text != query and seen_text in query:\r\n    # When CAPTCHA appears while typing text\r\n    sb.sleep(1.1)\r\n    sb.solve_captcha()\r\n    sb.sleep(2.2)\r\n    sb.type(textarea, \"\")\r\n    sb.press_keys(textarea, query)\r\n    sb.sleep(0.5)\r\nsb.click('button[data-testid=\"submit-button\"]')\r\nsb.sleep(2.5)\r\nsb.solve_captcha()\r\nsb.sleep(3.5)\r\nsb.solve_captcha()\r\nsb.sleep(2.5)\r\nstop_button = '[data-testid=\"stop-button\"]'\r\nthumbs_up = 'button[data-testid*=\"-thumbs-up-\"]'\r\nsb.wait_for_element_absent(stop_button, timeout=50)\r\nsb.wait_for_element(thumbs_up, timeout=20)\r\nsb.sleep(0.6)\r\nscroll = 'button[data-testid*=\"scroll-to-bottom\"]'\r\nsb.click_if_visible(scroll)\r\nsb.sleep(2.2)\r\nfolder = \"downloaded_files\"\r\nfile_name = \"copilot_results.html\"\r\nsb.save_as_html(file_name, folder)\r\nprint('\"./%s/%s\" was saved!' % (folder, file_name))\r\nsb.driver.stop()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_drivers.py",
    "content": "# An example of switching between multiple drivers\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url1 = \"https://seleniumbase.io/antibot/login\"\n    sb.activate_cdp_mode(url1)\n    url2 = \"https://seleniumbase.io/hobbit/login\"\n    driver2 = sb.get_new_driver(undetectable=True)\n    sb.activate_cdp_mode(url2)\n    sb.sleep(1)\n    sb.switch_to_default_driver()\n    sb.assert_url_contains(\"antibot\")\n    print(sb.get_current_url())\n    sb.type(\"input#username\", \"demo_user\")\n    sb.type(\"input#password\", \"secret_pass\")\n    sb.click(\"button\")\n    sb.sleep(1)\n    sb.click(\"a#log-in\")\n    sb.assert_text(\"Welcome!\", \"h1\")\n    sb.sleep(2)\n    sb.switch_to_driver(driver2)\n    sb.assert_url_contains(\"hobbit\")\n    print(sb.get_current_url())\n    sb.click(\"button\")\n    sb.assert_text(\"Welcome to Middle Earth!\")\n    sb.click(\"img\")\n    sb.sleep(3)\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_extended.py",
    "content": "\"\"\"The long way of using CDP Mode without WebDriver\"\"\"\nimport asyncio\nfrom seleniumbase import sb_cdp\nfrom seleniumbase import cdp_driver\n\nurl = \"https://seleniumbase.io/demo_page\"\nloop = asyncio.new_event_loop()\ndriver = cdp_driver.start_sync()\npage = loop.run_until_complete(driver.get(url))\nsb = sb_cdp.CDPMethods(loop, page, driver)\n\nsb.press_keys(\"input\", \"Text\")\nsb.highlight(\"button\")\nsb.type(\"textarea\", \"Here are some words\")\nsb.click(\"button\")\nsb.set_value(\"input#mySlider\", \"100\")\nsb.click_visible_elements(\"input.checkBoxClassB\")\nsb.select_option_by_text(\"#mySelect\", \"Set to 75%\")\nsb.gui_hover_and_click(\"#myDropdown\", \"#dropOption2\")\nsb.gui_click_element(\"#checkBox1\")\nsb.gui_drag_and_drop(\"img#logo\", \"div#drop2\")\nsb.nested_click(\"iframe#myFrame3\", \".fBox\")\nsb.sleep(2)\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_gitlab.py",
    "content": "from seleniumbase import sb_cdp\n\nurl = \"https://gitlab.com/users/sign_in\"\nsb = sb_cdp.Chrome(url, incognito=True)\nsb.sleep(2)\nsb.solve_captcha()\nsb.highlight('h1:contains(\"GitLab\")')\nsb.highlight('button:contains(\"Sign in\")')\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_hyatt.py",
    "content": "from seleniumbase import sb_cdp\r\n\r\nurl = \"https://www.hyatt.com/\"\r\nsb = sb_cdp.Chrome(url, locale=\"en\", guest=True)\r\nsb.sleep(3.6)\r\nsb.click_if_visible('button[aria-label=\"Close\"]')\r\nsb.sleep(0.1)\r\nsb.click_if_visible(\"#onetrust-reject-all-handler\")\r\nsb.sleep(1.2)\r\nlocation = \"Anaheim, CA, USA\"\r\nsb.type('input[id=\"search-term\"]', location)\r\nsb.sleep(1.2)\r\nsb.click('li[data-js=\"suggestion\"]')\r\nsb.sleep(1.2)\r\nsb.click(\"button.be-button-shop\")\r\nsb.sleep(6)\r\ncard_info = 'div[data-booking-status=\"BOOKABLE\"] [class*=\"HotelCard_info\"]'\r\nhotels = sb.select_all(card_info)\r\nprint(\"Hyatt Hotels in %s:\" % location)\r\nprint(\"(\" + sb.get_text('span[class*=\"summary_destination\"]') + \")\")\r\nif len(hotels) == 0:\r\n    print(\"No availability over the selected dates!\")\r\nfor hotel in hotels:\r\n    info = hotel.text.strip()\r\n    if \"Avg/Night\" in info and not info.startswith(\"Rates from\"):\r\n        name = info.split(\"  (\")[0].split(\" + \")[0].split(\" Award Cat\")[0]\r\n        name = name.split(\" Rates from :\")[0]\r\n        price = \"?\"\r\n        if \"Rates from : \" in info:\r\n            price = info.split(\"Rates from : \")[1].split(\" Avg/Night\")[0]\r\n        print(\"* %s => %s\" % (name, price))\r\nsb.driver.stop()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_login.py",
    "content": "from seleniumbase import decorators\nfrom seleniumbase import sb_cdp\n\n\ndef main():\n    url = \"https://seleniumbase.io/simple/login\"\n    sb = sb_cdp.Chrome(url)\n    sb.type(\"#username\", \"demo_user\")\n    sb.type(\"#password\", \"secret_pass\")\n    sb.click('a:contains(\"Sign in\")')\n    sb.assert_exact_text(\"Welcome!\", \"h1\")\n    sb.assert_element(\"img#image1\")\n    sb.highlight(\"#image1\")\n    top_nav = sb.find_element(\"div.topnav\")\n    links = top_nav.query_selector_all(\"a\")\n    for nav_item in links:\n        print(nav_item.text)\n    sb.click_link(\"Sign out\")\n    sb.assert_text(\"signed out\", \"#top_message\")\n    sb.driver.stop()\n\n\nif __name__ == \"__main__\":\n    with decorators.print_runtime(\"raw_cdp_login.py\"):\n        main()\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_methods.py",
    "content": "from seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/demo_page\"\nsb = sb_cdp.Chrome(url)\nsb.press_keys(\"input\", \"Text\")\nsb.highlight(\"button\")\nsb.type(\"textarea\", \"Here are some words\")\nsb.click(\"button\")\nsb.set_value(\"input#mySlider\", \"100\")\nsb.click_visible_elements(\"input.checkBoxClassB\")\nsb.select_option_by_text(\"#mySelect\", \"Set to 75%\")\nsb.gui_hover_and_click(\"#myDropdown\", \"#dropOption2\")\nsb.gui_click_element(\"#checkBox1\")\nsb.gui_drag_and_drop(\"img#logo\", \"div#drop2\")\nsb.nested_click(\"iframe#myFrame3\", \".fBox\")\nsb.sleep(2)\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_mobile.py",
    "content": "import mycdp\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome()\ntab = sb.get_active_tab()\nloop = sb.get_event_loop()\nloop.run_until_complete(\n    tab.send(\n        mycdp.emulation.set_device_metrics_override(\n            width=412, height=732, device_scale_factor=3, mobile=True\n        )\n    )\n)\nurl = \"https://gitlab.com/users/sign_in\"\nsb.open(url)\nsb.sleep(2)\nsb.solve_captcha()\n# (The rest is for testing and demo purposes)\nsb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\nsb.assert_element('label[for=\"user_login\"]')\nsb.highlight('button:contains(\"Sign in\")')\nsb.highlight('h1:contains(\"GitLab\")')\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_nike.py",
    "content": "from seleniumbase import sb_cdp\n\nurl = \"https://www.nike.com/\"\nsb = sb_cdp.Chrome(url)\nsb.sleep(1.2)\nsb.click('[data-testid=\"user-tools-container\"] search')\nsb.sleep(1)\nsearch = \"Pegasus\"\nsb.press_keys('input[type=\"search\"]', search)\nsb.sleep(4)\ndetails = 'ul[data-testid*=\"products\"] figure .details'\nelements = sb.select_all(details)\nif elements:\n    print('**** Found results for \"%s\": ****' % search)\n    for element in elements:\n        print(\"* \" + element.text)\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_nordstrom.py",
    "content": "from seleniumbase import sb_cdp\r\n\r\nurl = \"https://www.nordstrom.com/\"\r\nsb = sb_cdp.Chrome(url, locale=\"en\", guest=True)\r\nsb.sleep(2.2)\r\nsb.click(\"input#keyword-search-input\")\r\nsb.sleep(0.8)\r\nsearch = \"cocktail dresses for women teal\"\r\nsb.press_keys(\"input#keyword-search-input\", search + \"\\n\")\r\nsb.sleep(2.2)\r\nfor i in range(17):\r\n    sb.scroll_down(16)\r\n    sb.sleep(0.14)\r\nprint('*** Nordstrom Search for \"%s\":' % search)\r\nunique_item_text = []\r\nitems = sb.find_elements(\"article\")\r\nfor item in items:\r\n    description = item.querySelector(\"article h3\")\r\n    if description and description.text not in unique_item_text:\r\n        unique_item_text.append(description.text)\r\n        price_text = \"\"\r\n        price = item.querySelector('div div span[aria-hidden=\"true\"]')\r\n        if price:\r\n            price_text = price.text\r\n            print(\"* %s (%s)\" % (description.text, price_text))\r\nsb.driver.stop()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_pixelscan.py",
    "content": "from seleniumbase import sb_cdp\r\n\r\nurl = \"https://pixelscan.net/fingerprint-check\"\r\nsb = sb_cdp.Chrome(url, incognito=True)\r\nsb.remove_element(\"#headerBanner\")\r\nsb.wait_for_element(\"pxlscn-dynamic-ad\")\r\nsb.sleep(0.5)\r\nsb.remove_elements(\"pxlscn-dynamic-ad\")\r\nsb.sleep(2)\r\nsb.assert_text(\"No masking detected\", \"pxlscn-fingerprint-masking\")\r\nsb.assert_text(\"No automated behavior\", \"pxlscn-bot-detection\")\r\nsb.highlight('span:contains(\"is consistent\")')\r\nsb.sleep(1)\r\nsb.highlight(\"pxlscn-fingerprint-masking p\")\r\nsb.sleep(1)\r\nsb.highlight(\"pxlscn-bot-detection p\")\r\nsb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_recaptcha.py",
    "content": "from seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/apps/recaptcha\"\nsb = sb_cdp.Chrome(url)\nsb.solve_captcha()\nsb.assert_element(\"img#captcha-success\")\nsb.set_messenger_theme(location=\"top_left\")\nsb.post_message(\"SeleniumBase wasn't detected\", duration=3)\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_reddit.py",
    "content": "\"\"\"Reddit Search / Bypasses reCAPTCHA.\"\"\"\r\nfrom seleniumbase import sb_cdp\r\n\r\nsearch = \"reddit+scraper\"\r\nurl = f\"https://www.reddit.com/r/webscraping/search/?q={search}\"\r\nsb = sb_cdp.Chrome(url, use_chromium=True)\r\nsb.solve_captcha()  # Might not be needed\r\npost_title = '[data-testid=\"post-title\"]'\r\nsb.wait_for_element(post_title)\r\nfor i in range(8):\r\n    sb.scroll_down(25)\r\n    sb.sleep(0.2)\r\nposts = sb.select_all(post_title)\r\nprint('*** Reddit Posts for \"%s\":' % search)\r\nfor post in posts:\r\n    print(\"* \" + post.text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_shadow.py",
    "content": "\"\"\"An example of displaying Shadow DOM inside HTML\"\"\"\nfrom seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/apps/turnstile\"\nsb = sb_cdp.Chrome(url)\nelement = sb.find_element(\"div.cf-turnstile div\")\nhtml_with_shadow_dom = element.get_html()\nprint(html_with_shadow_dom)\ntext_to_find = \"Widget containing a Cloudflare security challenge\"\nsb.assert_true(text_to_find in html_with_shadow_dom)\nsb.solve_captcha()\nsb.assert_element(\"img#captcha-success\", timeout=3)\nsb.set_messenger_theme(location=\"top_left\")\nsb.post_message(\"SeleniumBase wasn't detected\", duration=3)\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_tabs.py",
    "content": "from seleniumbase import sb_cdp\r\n\r\nsb = sb_cdp.Chrome()\r\nsb.open(\"data:text/html,<h1>Page A</h1>\")\r\nsb.assert_text(\"Page A\")\r\nsb.open_new_tab()\r\nsb.open(\"data:text/html,<h1>Page B</h1>\")\r\nsb.assert_text(\"Page B\")\r\nsb.switch_to_tab(0)\r\nsb.assert_text(\"Page A\")\r\nsb.assert_text_not_visible(\"Page B\")\r\nsb.switch_to_tab(1)\r\nsb.assert_text(\"Page B\")\r\nsb.assert_text_not_visible(\"Page A\")\r\nsb.driver.stop()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_turnstile.py",
    "content": "from seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/apps/turnstile\"\nsb = sb_cdp.Chrome(url)\nsb.solve_captcha()\nsb.assert_element(\"img#captcha-success\")\nsb.set_messenger_theme(location=\"top_left\")\nsb.post_message(\"SeleniumBase wasn't detected\", duration=3)\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_walmart.py",
    "content": "from seleniumbase import sb_cdp\r\n\r\nurl = \"https://www.walmart.com/\"\r\nsb = sb_cdp.Chrome(url, locale=\"en\", guest=True)\r\nsb.sleep(3)\r\nsb.click('input[aria-label=\"Search\"]')\r\nsb.sleep(1.4)\r\nsearch = \"Settlers of Catan Board Game\"\r\nrequired_text = \"Catan\"\r\nsb.press_keys('input[aria-label=\"Search\"]', search + \"\\n\")\r\nsb.sleep(3.8)\r\nif sb.is_element_visible(\"#px-captcha\"):\r\n    sb.gui_click_and_hold(\"#px-captcha\", 7.2)\r\n    sb.sleep(4.2)\r\n    if sb.is_element_visible(\"#px-captcha\"):\r\n        sb.gui_click_and_hold(\"#px-captcha\", 4.2)\r\n        sb.sleep(3.2)\r\nsb.remove_elements('[data-testid=\"skyline-ad\"]')\r\nsb.remove_elements('[data-testid=\"sba-container\"]')\r\nprint('*** Walmart Search for \"%s\":' % search)\r\nprint('    (Results must contain \"%s\".)' % required_text)\r\nunique_item_text = []\r\nsb.click_if_visible('[data-automation-id=\"sb-btn-close-mark\"]')\r\nitems = sb.find_elements('[data-item-id]')\r\nfor item in items:\r\n    if required_text in item.text:\r\n        description = item.querySelector(\r\n            '[data-automation-id=\"product-title\"]'\r\n        )\r\n        if description and description.text not in unique_item_text:\r\n            unique_item_text.append(description.text)\r\n            print(\"* \" + description.text)\r\n            price = item.querySelector(\r\n                '[data-automation-id=\"product-price\"]'\r\n            )\r\n            if price:\r\n                price_text = price.text\r\n                price_text = price_text.split(\"current price Now \")[-1]\r\n                price_text = price_text.split(\"current price \")[-1]\r\n                price_text = price_text.split(\" \")[0]\r\n                print(\"  (\" + price_text + \")\")\r\nsb.driver.stop()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cdp_with_sb.py",
    "content": "\"\"\"Example of using CDP Mode with WebDriver\"\"\"\r\nfrom seleniumbase import SB\r\n\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.priceline.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.5)\r\n    sb.internalize_links()  # Don't open links in a new tab\r\n    sb.click(\"#link_header_nav_experiences\")\r\n    sb.sleep(3.5)\r\n    sb.remove_elements(\"msm-cookie-banner\")\r\n    sb.sleep(1.5)\r\n    location = \"Amsterdam\"\r\n    where_to = 'div[data-automation*=\"experiences\"] input'\r\n    button = 'button[data-automation*=\"experiences-search\"]'\r\n    sb.wait_for_text(\"Where to?\")\r\n    sb.cdp.gui_click_element(where_to)\r\n    sb.press_keys(where_to, location)\r\n    sb.sleep(1)\r\n    sb.cdp.gui_click_element(button)\r\n    sb.sleep(3)\r\n    print(sb.get_title())\r\n    print(\"************\")\r\n    for i in range(8):\r\n        sb.scroll_down(50)\r\n        sb.sleep(0.2)\r\n    cards = sb.select_all('span[data-automation*=\"product-list-card\"]')\r\n    for card in cards:\r\n        print(\"* %s\" % card.text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cf.py",
    "content": "\"\"\"Using CDP Mode to bypass CAPTCHAs in different ways.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, guest=True) as sb:\r\n    url = \"https://www.cloudflare.com/login\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(3)\r\n    sb.uc_gui_handle_captcha()  # PyAutoGUI Tabs + Spacebar\r\n    sb.sleep(3)\r\n\r\nwith SB(uc=True, test=True, guest=True) as sb:\r\n    url = \"https://www.cloudflare.com/login\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(4)\r\n    sb.uc_gui_click_captcha()  # PyAutoGUI mouse click\r\n    sb.sleep(3)\r\n\r\nwith SB(uc=True, test=True, guest=True) as sb:\r\n    url = \"https://www.cloudflare.com/login\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(4)\r\n    sb.solve_captcha()  # CDP Input.dispatchMouseEvent\r\n    sb.sleep(3)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cf_captcha.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, guest=True) as sb:\r\n    url = \"https://www.cloudflare.com/login\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.wait_for_element('div[data-testid*=\"challenge-widget\"]')\r\n    sb.sleep(2)\r\n    sb.solve_captcha()\r\n    sb.sleep(3)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_cf_clearance.py",
    "content": "from seleniumbase import sb_cdp\n\n\ndef get_cf_clearance_cookie(sb):\n    all_cookies = sb.get_all_cookies()\n    for cookie in all_cookies:\n        if cookie.name == \"cf_clearance\":\n            return cookie\n    return None\n\n\nurl = \"https://gitlab.com/users/sign_in\"\nsb = sb_cdp.Chrome(url)\nsb.sleep(3)  # Wait for CAPTCHA to load\nsb.solve_captcha()  # (Only if found)\nsb.sleep(2)  # Wait for CAPTCHA success\ncf_cookie = get_cf_clearance_cookie(sb)\nif cf_cookie:\n    print(\"cf_clearance cookie: %s\" % cf_cookie.value)\nelse:\n    print(\"Didn't find the cf_clearance cookie!\")\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_chatgpt.py",
    "content": "from contextlib import suppress\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, ad_block=True) as sb:\n    url = \"https://chatgpt.com/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1)\n    sb.click_if_visible('button[aria-label=\"Close dialog\"]')\n    sb.click_if_visible('button[data-testid=\"close-button\"]')\n    query = \"Compare Playwright to SeleniumBase in under 178 words\"\n    sb.press_keys(\"#prompt-textarea\", query)\n    sb.click('button[data-testid=\"send-button\"]')\n    print('*** Input for ChatGPT: ***\\n\"%s\"' % query)\n    sb.sleep(3)\n    with suppress(Exception):\n        # The \"Stop\" button disappears when ChatGPT is done typing a response\n        sb.wait_for_element_not_visible(\n            'button[data-testid=\"stop-button\"]', timeout=20\n        )\n    chat = sb.find_element('[data-message-author-role=\"assistant\"] .markdown')\n    soup = sb.get_beautiful_soup(chat.get_html()).text.strip()\n    soup = soup.replace(\"\\n\\n\\n\", \"\\n\\n\")\n    print(\"*** Response from ChatGPT: ***\\n%s\" % soup)\n    sb.sleep(3)\n"
  },
  {
    "path": "examples/cdp_mode/raw_consecutive_c.py",
    "content": "# An example of bypassing 2 consecutive CF CAPTCHAs\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://sms-man.com/login\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2.2)\n    if not sb.is_element_present('input[name=\"email\"]'):\n        sb.solve_captcha()\n        sb.sleep(1)\n        sb.wait_for_element('[name=\"email\"]', timeout=3)\n        sb.sleep(2)\n    sb.solve_captcha()\n    sb.sleep(2)\n"
  },
  {
    "path": "examples/cdp_mode/raw_cookies_async.py",
    "content": "\"\"\"A script that loads cookies to bypass login.\"\"\"\r\nimport asyncio\r\nimport time\r\nfrom seleniumbase import cdp_driver\r\n\r\n\r\n# Log in to Swag Labs and save cookies\r\nasync def get_login_cookies():\r\n    url = \"https://www.saucedemo.com\"\r\n    driver = await cdp_driver.start_async(incognito=True)\r\n    page = await driver.get(url)\r\n    await page.type(\"#user-name\", \"standard_user\")\r\n    await page.type(\"#password\", \"secret_sauce\")\r\n    await page.click('input[type=\"submit\"]')\r\n    cookies = await driver.cookies.get_all()\r\n    driver.stop()\r\n    return cookies\r\n\r\n\r\n# Load previously saved cookies to bypass login\r\nasync def login_with_cookies(cookies):\r\n    url_1 = \"https://www.saucedemo.com\"\r\n    url_2 = \"https://www.saucedemo.com/inventory.html\"\r\n    driver = await cdp_driver.start_async()\r\n    page = await driver.get(url_1)\r\n    await driver.cookies.set_all(cookies)\r\n    await driver.get(url_2)\r\n    await page.select(\"div.inventory_list\")\r\n    time.sleep(2)\r\n    driver.stop()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    loop = asyncio.new_event_loop()\r\n    cookies = loop.run_until_complete(get_login_cookies())\r\n    loop.run_until_complete(login_with_cookies(cookies))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_copilot.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://copilot.microsoft.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    textarea = \"textarea#userInput\"\r\n    sb.wait_for_element(textarea)\r\n    sb.sleep(1.3)\r\n    sb.click_if_visible('[aria-label=\"Dismiss\"]')\r\n    sb.sleep(0.5)\r\n    sb.click('button[data-testid*=\"chat-mode-\"]')\r\n    sb.sleep(1.1)\r\n    sb.click_if_visible('button[title^=\"Think\"]')\r\n    sb.sleep(1.1)\r\n    query = \"How to start automating with SeleniumBase?\"\r\n    sb.press_keys(textarea, query)\r\n    sb.sleep(1.1)\r\n    seen_text = sb.get_text(textarea)\r\n    if seen_text != query and seen_text in query:\r\n        # When CAPTCHA appears while typing text\r\n        sb.sleep(1.1)\r\n        sb.solve_captcha()\r\n        sb.sleep(2.2)\r\n        sb.type(textarea, \"\")\r\n        sb.press_keys(textarea, query)\r\n        sb.sleep(0.5)\r\n    sb.click('button[data-testid=\"submit-button\"]')\r\n    sb.sleep(2.5)\r\n    sb.solve_captcha()\r\n    sb.sleep(3.5)\r\n    sb.solve_captcha()\r\n    sb.sleep(2.5)\r\n    stop_button = '[data-testid=\"stop-button\"]'\r\n    thumbs_up = 'button[data-testid*=\"-thumbs-up-\"]'\r\n    sb.wait_for_element_absent(stop_button, timeout=50)\r\n    sb.wait_for_element(thumbs_up, timeout=20)\r\n    sb.sleep(0.6)\r\n    scroll = 'button[data-testid*=\"scroll-to-bottom\"]'\r\n    sb.click_if_visible(scroll)\r\n    sb.sleep(2.2)\r\n    folder = \"downloaded_files\"\r\n    file_name = \"copilot_results.html\"\r\n    sb.save_page_source(file_name, folder)\r\n    print('\"./%s/%s\" was saved!' % (folder, file_name))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_demo_site.py",
    "content": "\"\"\"Example of using various CDP Mode commands\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/demo_page\"\n    sb.activate_cdp_mode(url)\n\n    # Assert various things\n    sb.cdp.assert_title(\"Web Testing Page\")\n    sb.cdp.assert_element(\"tbody#tbodyId\")\n    sb.cdp.assert_text(\"Demo Page\", \"h1\")\n\n    # Type text into various text fields and then assert\n    sb.cdp.type(\"#myTextInput\", \"This is Automated\")\n    sb.cdp.type(\"textarea.area1\", \"Testing Time!\\n\")\n    sb.cdp.type('[name=\"preText2\"]', \"Typing Text!\")\n    sb.cdp.assert_text(\"This is Automated\", \"#myTextInput\")\n    sb.cdp.assert_text(\"Testing Time!\\n\", \"textarea.area1\")\n    sb.cdp.assert_text(\"Typing Text!\", '[name=\"preText2\"]')\n\n    # Hover & click a dropdown element and assert results\n    sb.cdp.assert_text(\"Automation Practice\", \"h3\")\n    sb.cdp.hover_and_click(\"#myDropdown\", \"#dropOption2\")\n    sb.cdp.assert_text(\"Link Two Selected\", \"h3\")\n\n    # Click a button and then verify the expected results\n    sb.cdp.assert_text(\"This Text is Green\", \"#pText\")\n    sb.cdp.click('button:contains(\"Click Me\")')\n    sb.cdp.assert_text(\"This Text is Purple\", \"#pText\")\n\n    # Verify that a slider control updates a progress bar\n    sb.cdp.assert_element('progress[value=\"50\"]')\n    sb.cdp.set_value(\"input#mySlider\", \"100\")\n    sb.cdp.assert_element('progress[value=\"100\"]')\n\n    # Verify that a \"select\" option updates a meter bar\n    sb.cdp.assert_element('meter[value=\"0.25\"]')\n    sb.cdp.select_option_by_text(\"#mySelect\", \"Set to 75%\")\n    sb.cdp.assert_element('meter[value=\"0.75\"]')\n\n    # Verify that clicking a radio button selects it\n    sb.cdp.assert_false(sb.cdp.is_selected(\"#radioButton2\"))\n    sb.cdp.click(\"#radioButton2\")\n    sb.cdp.assert_true(sb.cdp.is_selected(\"#radioButton2\"))\n\n    # Verify that clicking a checkbox makes it selected\n    sb.cdp.assert_element_not_visible(\"img#logo\")\n    sb.cdp.assert_false(sb.cdp.is_selected(\"#checkBox1\"))\n    sb.cdp.click(\"#checkBox1\")\n    sb.cdp.assert_true(sb.cdp.is_selected(\"#checkBox1\"))\n    sb.cdp.assert_element(\"img#logo\")\n\n    # Verify clicking on multiple elements with one call\n    sb.cdp.assert_false(sb.cdp.is_selected(\"#checkBox2\"))\n    sb.cdp.assert_false(sb.cdp.is_selected(\"#checkBox3\"))\n    sb.cdp.assert_false(sb.cdp.is_selected(\"#checkBox4\"))\n    sb.cdp.click_visible_elements(\"input.checkBoxClassB\")\n    sb.cdp.assert_true(sb.cdp.is_selected(\"#checkBox2\"))\n    sb.cdp.assert_true(sb.cdp.is_selected(\"#checkBox3\"))\n    sb.cdp.assert_true(sb.cdp.is_selected(\"#checkBox4\"))\n\n    # Verify Drag and Drop\n    sb.cdp.assert_element_not_visible(\"div#drop2 img#logo\")\n    sb.cdp.gui_drag_and_drop(\"img#logo\", \"div#drop2\")\n    sb.cdp.assert_element(\"div#drop2 img#logo\")\n\n    # Click inside an iframe and test highlighting\n    sb.cdp.flash(\"iframe#myFrame3\")\n    sb.cdp.sleep(1)\n    sb.cdp.nested_click(\"iframe#myFrame3\", \".fBox\")\n    sb.cdp.sleep(0.5)\n    sb.cdp.highlight(\"iframe#myFrame3\")\n"
  },
  {
    "path": "examples/cdp_mode/raw_drag_and_drop.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True) as sb:\r\n    url = \"https://seleniumbase.io/other/drag_and_drop\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.assert_element_not_visible(\"#div1 img#drag1\")\r\n    sb.cdp.gui_drag_and_drop(\"#drag1\", \"#div1\")\r\n    sb.assert_element(\"#div1 img#drag1\")\r\n    sb.sleep(1)\r\n\r\nwith SB(uc=True, test=True, incognito=True) as sb:\r\n    url = \"https://jqueryui.com/draggable/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.switch_to_frame(\"iframe\")\r\n    x, y = sb.get_gui_element_center(\"#draggable\")\r\n    sb.switch_to_default_content()\r\n    sb.scroll_to_top()\r\n    sb.cdp.gui_drag_drop_points(x, y, x + 180, y + 90)\r\n    sb.cdp.gui_drag_drop_points(x + 180, y + 90, x + 60, y + 120)\r\n    sb.cdp.gui_drag_drop_points(x + 60, y + 120, x + 40, y + 40)\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_driver.py",
    "content": "import atexit\nfrom seleniumbase import Driver\n\ndriver = Driver(uc=True, guest=True)\natexit.register(driver.quit)\nurl = \"www.planetminecraft.com/account\"\ndriver.activate_cdp_mode(url)\ndriver.sleep(2)\ndriver.solve_captcha()\ndriver.wait_for_element_absent(\"input[disabled]\")\ndriver.sleep(2)\n"
  },
  {
    "path": "examples/cdp_mode/raw_easyjet.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n    url = \"https://www.easyjet.com/en/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1.5)\r\n    sb.click_if_visible(\"button#ensRejectAds\", timeout=2)\r\n    sb.sleep(1)\r\n    sb.click('input[name=\"from\"]')\r\n    sb.sleep(1)\r\n    sb.type('input[name=\"from\"]', \"London Gatwick\")\r\n    sb.sleep(1)\r\n    sb.click('span[data-testid=\"airport-name\"]')\r\n    sb.sleep(1)\r\n    sb.type('input[name=\"to\"]', \"Paris\")\r\n    sb.sleep(1)\r\n    sb.click('span[data-testid=\"airport-name\"]')\r\n    sb.sleep(1)\r\n    sb.click('input[name=\"when\"]')\r\n    sb.sleep(1)\r\n    sb.click('[data-testid=\"month\"]:last-of-type [aria-disabled=\"false\"]')\r\n    sb.sleep(1)\r\n    sb.click('[data-testid=\"month\"]:last-of-type [aria-disabled=\"false\"]')\r\n    sb.sleep(1)\r\n    sb.click('button[data-testid=\"submit\"]')\r\n    sb.sleep(4)\r\n    sb.connect()\r\n    sb.sleep(1)\r\n    for window in sb.driver.window_handles:\r\n        sb.switch_to_window(window)\r\n        if \"/buy/flights\" in sb.get_current_url():\r\n            break\r\n    sb.click_if_visible(\"button#ensCloseBanner\")\r\n    days = sb.find_elements('div[class*=\"FlightGridLayout_column\"]')\r\n    for day in days:\r\n        if not day.text.strip():\r\n            continue\r\n        print(\"**** \" + \" \".join(day.text.split(\"\\n\")[0:2]) + \" ****\")\r\n        fares = day.find_elements(\"css selector\", 'button[class*=\"flightDet\"]')\r\n        if not fares:\r\n            print(\"No flights today!\")\r\n        for fare in fares:\r\n            info = fare.text\r\n            info = info.replace(\"LOWEST FARE\\n\", \"\")\r\n            info = info.replace(\"\\n\", \"  \")\r\n            print(info)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_elal.py",
    "content": "import datetime\r\nimport re\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"www.elal.com/flight-deals/en-us/flights-from-boston-to-tel-aviv\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(3)\r\n    sb.click('label:contains(\"Departure date\")')\r\n    sb.sleep(1)\r\n    today = datetime.date.today()\r\n    days_ahead = (4 - today.weekday() + 7) % 7\r\n    next_friday = today + datetime.timedelta(days=days_ahead)\r\n    formatted_date = next_friday.strftime(\"%m/%d/%Y\")\r\n    sb.cdp.gui_click_element('input[aria-describedby*=\"date-input\"]')\r\n    sb.sleep(1)\r\n    sb.cdp.gui_press_keys(\"\\b\" * 10 + formatted_date + \"\\n\")\r\n    sb.sleep(1)\r\n    days_ahead = (4 - today.weekday() + 8) % 14\r\n    following_saturday = today + datetime.timedelta(days=days_ahead)\r\n    formatted_date = following_saturday.strftime(\"%m/%d/%Y\")\r\n    sb.cdp.gui_click_element(\r\n        '[data-att=\"end-date-toggler\"] [aria-describedby*=\"date-input\"]'\r\n    )\r\n    sb.sleep(1)\r\n    sb.cdp.gui_press_keys(\"\\b\" * 10 + formatted_date + \"\\n\")\r\n    sb.sleep(1)\r\n    sb.click('button[data-att=\"done\"]')\r\n    sb.sleep(1)\r\n    sb.click('button[data-att=\"search\"]')\r\n    sb.sleep(5)\r\n    sb.click_if_visible(\"#onetrust-close-btn-container button\")\r\n    sb.sleep(1)\r\n    view_other_dates = 'button[aria-label*=\"viewOtherDates.cta\"]'\r\n    if sb.is_element_visible(view_other_dates):\r\n        sb.click(view_other_dates)\r\n        sb.sleep(5)\r\n    if sb.is_element_visible(\"flexible-search-calendar\"):\r\n        print(\"*** Flight Calendar for El Al (Boston to Tel Aviv): ***\")\r\n        print(sb.get_text(\"flexible-search-calendar\"))\r\n        prices = []\r\n        elements = sb.find_elements(\"span.matric-cell__content__price\")\r\n        if elements:\r\n            print(\"*** Prices List: ***\")\r\n            for element in elements:\r\n                prices.append(element.text)\r\n            prices.sort(key=lambda x: int(re.sub(\"[^0-9]\", \"\", x)))\r\n            for price in prices:\r\n                print(price)\r\n            print(\"*** Lowest Price: ***\")\r\n            lowest_price = prices[0]\r\n            print(lowest_price)\r\n            sb.scroll_down(12)\r\n            sb.sleep(1)\r\n            sb.find_element_by_text(lowest_price).click()\r\n            sb.sleep(2)\r\n            search_cell = 'button[aria-label*=\"Search.cell.buttonTitle\"]'\r\n            sb.scroll_into_view(search_cell)\r\n            sb.sleep(1)\r\n            sb.click(search_cell)\r\n            sb.sleep(5)\r\n    else:\r\n        print(\"*** Lowest Prices: ***\")\r\n        departure_prices = \"#uiFlightPanel0 div.ui-bound__price__value\"\r\n        return_prices = \"#uiFlightPanel1 div.ui-bound__price__value\"\r\n        elements = sb.find_elements(departure_prices)\r\n        for element in elements:\r\n            if \"lowest price\" in element.text:\r\n                print(\"Departure Flight:\")\r\n                print(element.text)\r\n                break\r\n        elements = sb.find_elements(return_prices)\r\n        for element in elements:\r\n            if \"lowest price\" in element.text:\r\n                print(\"Return Flight:\")\r\n                print(element.text)\r\n                break\r\n    dates = sb.find_elements('div[class*=\"flight-date\"]')\r\n    if len(dates) == 2:\r\n        print(\"*** Departure Date: ***\")\r\n        print(dates[0].text)\r\n        print(\"*** Return Date: ***\")\r\n        print(dates[1].text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_facebook.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True, ad_block=True) as sb:\n    url = \"https://www.facebook.com/SeleniumBase\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1)\n    sb.click_if_visible('[aria-label=\"Close\"] i')\n    sb.sleep(1)\n    for i in range(16):\n        sb.cdp.scroll_down(16)\n    print(sb.get_page_title())\n    sb.save_as_pdf_to_logs()\n    sb.save_page_source_to_logs()\n    sb.save_screenshot_to_logs()\n    print(\"Logs have been saved to: ./latest_logs/\")\n"
  },
  {
    "path": "examples/cdp_mode/raw_fingerprint.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://demo.fingerprint.com/playground\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1)\n    sb.cdp.highlight('a[href*=\"browser-bot-detection\"]')\n    bot_row_selector = 'table:contains(\"Bot\") tr:nth-of-type(3)'\n    print(sb.get_text(bot_row_selector))\n    sb.assert_text(\"Bot Not detected\", bot_row_selector)\n    sb.cdp.highlight(bot_row_selector)\n    sb.sleep(2)\n"
  },
  {
    "path": "examples/cdp_mode/raw_footlocker.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n    url = \"https://www.footlocker.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.5)\r\n    sb.click_if_visible('button[id*=\"Agree\"]')\r\n    sb.sleep(1.5)\r\n    sb.click('input[name=\"query\"]')\r\n    sb.sleep(1.5)\r\n    search = \"Nike Shoes\"\r\n    sb.press_keys('input[name=\"query\"]', search)\r\n    sb.sleep(2.5)\r\n    sb.click('ul[id*=\"typeahead\"] li div')\r\n    sb.sleep(3.5)\r\n    elements = sb.select_all(\"a.ProductCard-link\")\r\n    if elements:\r\n        print('**** Found results for \"%s\": ****' % search)\r\n    for element in elements:\r\n        print(\"------------------ >>>\")\r\n        print(\"* \" + element.text)\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_form_turnstile.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"seleniumbase.io/apps/form_turnstile\"\n    sb.activate_cdp_mode(url)\n    sb.press_keys(\"#name\", \"SeleniumBase\")\n    sb.press_keys(\"#email\", \"test@test.test\")\n    sb.press_keys(\"#phone\", \"1-555-555-5555\")\n    sb.click('[for=\"date\"]')\n    sb.click(\"td.is-today button\")\n    sb.click('div[class=\"select-wrapper\"] input')\n    sb.click('span:contains(\"9:00 PM\")')\n    sb.highlight_click('input[value=\"AR\"] + span')\n    sb.click('input[value=\"cc\"] + span')\n    sb.scroll_down(40)\n    sb.solve_captcha()\n    sb.highlight(\"img#captcha-success\", timeout=3)\n    sb.highlight_click('button:contains(\"Request & Pay\")')\n    sb.highlight(\"img#submit-success\")\n    sb.highlight('button:contains(\"Success!\")')\n"
  },
  {
    "path": "examples/cdp_mode/raw_gas_records.py",
    "content": "\"\"\"(Bypasses the Imperva/Incapsula hCaptcha)\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = (\r\n        \"https://www.gassaferegister.co.uk/gas-safety\"\r\n        \"/gas-safety-certificates-records/building-regulations-certificate\"\r\n        \"/order-replacement-building-regulations-certificate/\"\r\n    )\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(0.6)\r\n    sb.solve_captcha()\r\n    sb.wait_for_element(\"#SearchTerm\", timeout=5)\r\n    sb.sleep(1.4)\r\n    allow_cookies = 'button:contains(\"Allow all cookies\")'\r\n    sb.click_if_visible(allow_cookies, timeout=2)\r\n    sb.sleep(1)\r\n    sb.press_keys(\"#SearchTerm\", \"Hydrogen\")\r\n    sb.click(\"button.search-button\")\r\n    sb.sleep(3)\r\n    results = sb.find_elements(\"div.search-result\")\r\n    for result in results:\r\n        print(result.text.replace(\" \" * 12, \" \").strip() + \"\\n\")\r\n    sb.scroll_to_bottom()\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_geolocation.py",
    "content": "\"\"\"Geolocation example using CDP Mode without WebDriver\"\"\"\nfrom seleniumbase import decorators\nfrom seleniumbase import sb_cdp\n\n\n@decorators.print_runtime(\"Geolocation CDP Example\")\ndef main():\n    url = \"https://www.openstreetmap.org/\"\n    location = (48.87645, 2.26340)\n    sb = sb_cdp.Chrome(url, geoloc=location, use_chromium=True)\n    sb.sleep(2)\n    sb.click('a[aria-label=\"Show My Location\"]')\n    sb.assert_url_contains(\"48.876450/2.263400\")\n    sb.sleep(5)\n    sb.driver.stop()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/cdp_mode/raw_geolocation_sb.py",
    "content": "\"\"\"Geolocation example with CDP Mode\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, use_chromium=True) as sb:\n    url = \"https://www.openstreetmap.org/\"\n    location = (31.774390, 35.222450)\n    sb.activate_cdp_mode(url, geoloc=location)\n    sb.click('a[aria-label=\"Show My Location\"]')\n    sb.assert_url_contains(\"31.774390/35.222450\")\n    sb.sleep(5)\n"
  },
  {
    "path": "examples/cdp_mode/raw_gettyimages.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", pls=\"none\") as sb:\r\n    sb.activate_cdp_mode(\"https://www.gettyimages.com/\")\r\n    sb.click('label:contains(\"Editorial\")')\r\n    sb.press_keys(\"form input\", \"comic con 2024 sci fi panel\\n\")\r\n    sb.sleep(3)\r\n    items = sb.find_elements(\"figure picture img\")\r\n    for item in items:\r\n        item.flash(color=\"44CC88\")\r\n        sb.sleep(0.08)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_gitlab.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://gitlab.com/users/sign_in\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2)\r\n    sb.solve_captcha()\r\n    # (The rest is for testing and demo purposes)\r\n    sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n    sb.assert_element('label[for=\"user_login\"]')\r\n    sb.highlight('button:contains(\"Sign in\")')\r\n    sb.highlight('h1:contains(\"GitLab\")')\r\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_glassdoor.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, ad_block=True) as sb:\r\n    url = \"https://www.glassdoor.com/Reviews/index.htm\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1.5)\r\n    sb.solve_captcha()\r\n    sb.sleep(0.5)\r\n    sb.highlight('[data-test=\"global-nav-glassdoor-logo\"]')\r\n    sb.highlight('[data-test=\"site-header-companies\"]')\r\n    sb.highlight('[data-test=\"search-button\"]')\r\n    sb.highlight('[data-test=\"sign-in-button\"]')\r\n    sb.highlight('[data-test=\"company-search-autocomplete\"]')\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_handle_alerts.py",
    "content": "\"\"\"An example of handling alerts in CDP Mode.\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://the-internet.herokuapp.com/javascript_alerts\"\n    sb.activate_cdp_mode(url)\n    sb.click('button[onclick=\"jsAlert()\"]')\n    sb.sleep(1)\n    sb.uc_gui_press_key(\"\\n\")  # Accept Alert\n    sb.sleep(1)\n    sb.click('button[onclick=\"jsConfirm()\"]')\n    sb.sleep(1)\n    sb.uc_gui_press_key(\"ESC\")  # Dismiss Alert\n    sb.sleep(1)\n    sb.click('button[onclick=\"jsPrompt()\"]')\n    sb.sleep(1)\n    sb.uc_gui_write(\"Here is my prompt answer\\n\")\n    sb.sleep(1)\n"
  },
  {
    "path": "examples/cdp_mode/raw_homedepot.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, ad_block=True) as sb:\r\n    url = \"https://www.homedepot.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1.8)\r\n    search_box = \"input#typeahead-search-field-input\"\r\n    search = \"Computer Chair\"\r\n    category = \"Gaming Chairs\"\r\n    required_text = \"Chair\"\r\n    sb.click(search_box)\r\n    sb.sleep(1.2)\r\n    sb.press_keys(search_box, search)\r\n    sb.sleep(0.6)\r\n    sb.click(\"button#typeahead-search-icon-button\")\r\n    sb.sleep(3.8)\r\n    sb.click('a[aria-label=\"%s\"]' % category)\r\n    sb.sleep(3.2)\r\n    print('*** Home Depot Search for \"%s\":' % search)\r\n    print('    (Results must contain \"%s\".)' % required_text)\r\n    unique_item_text = []\r\n    items = sb.find_elements('div[data-testid=\"product-pod\"]')\r\n    for item in items:\r\n        if required_text in item.text:\r\n            description = item.querySelector(\r\n                'span[data-testid=\"attribute-product-label\"]'\r\n            )\r\n            if description and description.text not in unique_item_text:\r\n                unique_item_text.append(description.text)\r\n                print(\"* \" + description.text)\r\n                price = item.querySelector('[class*=\"sm:sui-text-4xl\"]')\r\n                if price:\r\n                    price_text = \"$%s\" % price.text\r\n                    print(\"  (\" + price_text + \")\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_hyatt.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.hyatt.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(3.2)\r\n    sb.click_if_visible('button[aria-label=\"Close\"]')\r\n    sb.sleep(0.1)\r\n    sb.click_if_visible(\"#onetrust-reject-all-handler\")\r\n    sb.sleep(1.2)\r\n    location = \"Anaheim, CA, USA\"\r\n    sb.type('input[id=\"search-term\"]', location)\r\n    sb.sleep(1.2)\r\n    sb.click('li[data-js=\"suggestion\"]')\r\n    sb.sleep(0.6)\r\n    sb.click_if_visible('button[aria-label=\"Close\"]')\r\n    sb.sleep(0.6)\r\n    sb.click(\"button.be-button-shop\")\r\n    sb.sleep(1)\r\n    sb.click_if_visible('[label=\"Find Hotels\"]')\r\n    sb.sleep(5)\r\n    card_info = 'div[data-booking-status=\"BOOKABLE\"] [class*=\"HotelCard_info\"]'\r\n    hotels = sb.select_all(card_info)\r\n    print(\"Hyatt Hotels in %s:\" % location)\r\n    print(\"(\" + sb.get_text('span[class*=\"summary_destination\"]') + \")\")\r\n    if len(hotels) == 0:\r\n        print(\"No availability over the selected dates!\")\r\n    for hotel in hotels:\r\n        info = hotel.text.strip()\r\n        if \"Avg/Night\" in info and not info.startswith(\"Rates from\"):\r\n            name = info.split(\"  (\")[0].split(\" + \")[0].split(\" Award Cat\")[0]\r\n            name = name.split(\" Rates from :\")[0]\r\n            price = \"?\"\r\n            if \"Rates from : \" in info:\r\n                price = info.split(\"Rates from : \")[1].split(\" Avg/Night\")[0]\r\n            print(\"* %s => %s\" % (name, price))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_idealista.py",
    "content": "\"\"\"(Bypasses the DataDome slider CAPTCHA)\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"es\") as sb:\n    url = \"https://www.idealista.com/venta-viviendas/barcelona-provincia/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1)\n    sb.solve_captcha()\n    sb.sleep(2)\n    sb.click(\"button#didomi-notice-agree-button\")\n    print(\"*** \" + sb.get_text(\"h1\"))\n    items = sb.find_elements(\"div.item-info-container\")\n    for item in items:\n        print(item.querySelector(\"a.item-link\").text)\n        print(item.querySelector(\"span.item-price\").text)\n"
  },
  {
    "path": "examples/cdp_mode/raw_indeed.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://www.indeed.com/companies/search\"\n    sb.activate_cdp_mode(url)\n    search_box = \"input#company-search\"\n    if not sb.is_element_present(search_box):\n        sb.sleep(2)\n        sb.solve_captcha()\n        sb.sleep(1)\n    company = \"NASA Jet Propulsion Laboratory\"\n    sb.click(search_box)\n    sb.sleep(0.1)\n    sb.press_keys(search_box, company)\n    sb.click('button[type=\"submit\"]')\n    sb.click('a:contains(\"%s\")' % company)\n    name_header = 'div[itemprop=\"name\"]'\n    sb.sleep(1)\n    if not sb.is_element_present(name_header):\n        sb.sleep(2)\n        sb.solve_captcha()\n        sb.sleep(1)\n    sb.highlight(name_header)\n    sb.sleep(1)\n    sb.cdp.highlight('h2:contains(\"About the company\")')\n    sb.sleep(1)\n    for i in range(10):\n        sb.scroll_down(12)\n        sb.sleep(0.14)\n    info = sb.find_element('[data-testid=\"AboutSection-section\"]')\n    soup = sb.get_beautiful_soup(info.get_html()).get_text(\"\\n\").strip()\n    print(\"*** %s: ***\\n%s\" % (company, soup.replace(\"\\n:\", \":\")))\n"
  },
  {
    "path": "examples/cdp_mode/raw_indeed_login.py",
    "content": "\"\"\"An example of clicking at custom CAPTCHA coordinates.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://secure.indeed.com/auth\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1)\r\n    sb.type('input[type=\"email\"]', \"test@test.com\")\r\n    sb.sleep(1)\r\n    sb.click('button[type=\"submit\"]')\r\n    sb.sleep(3.5)\r\n    selector = 'div[class*=\"pass-Captcha\"]'\r\n    sb.click_with_offset(selector, 32, 42)\r\n    sb.sleep(4.5)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_kohls.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", incognito=True) as sb:\r\n    url = \"https://www.kohls.com/\"\r\n    sb.activate_cdp_mode(url, ad_block=True)\r\n    sb.sleep(2.6)\r\n    search = \"Mickey Mouse Blanket\"\r\n    req_1 = \"Mickey\"\r\n    req_2 = \"Blanket\"\r\n    if not sb.is_element_present('input[name=\"search\"]'):\r\n        sb.refresh()\r\n        sb.sleep(2.6)\r\n    sb.press_keys('input[name=\"search\"]', search + \"\\n\")\r\n    sb.sleep(5)\r\n    item_selector = 'div[data-testid*=\"wallet-wrapper\"]'\r\n    if not sb.is_element_present(item_selector):\r\n        item_selector = \"li.products_grid\"\r\n    for item in sb.find_elements(item_selector):\r\n        if \"Sponsored\" in item.text:\r\n            item.remove_from_dom()\r\n    sb.remove_elements(\"#tce-sticky-wrapper\")\r\n    sb.remove_elements(\"li.sponsored-product\")\r\n    sb.remove_elements(\"#tce-dec-ces-3-banner\")\r\n    print('*** Kohls Search for \"%s\":' % search)\r\n    print('    (Results must contain \"%s\" and \"%s\".)' % (req_1, req_2))\r\n    title_selector = \"div.prod_nameBlock p\"\r\n    if not sb.is_element_present(title_selector):\r\n        title_selector = 'a[class*=\"sm:text\"][href*=\"/product/\"]'\r\n    for item in sb.find_elements(title_selector):\r\n        if item:\r\n            item.flash(color=\"44CC88\")\r\n            title = item.text\r\n            if title:\r\n                if (\r\n                    req_1.lower() in title.lower()\r\n                    and req_2.lower() in title.lower()\r\n                ):\r\n                    print(\"* \" + title)\r\n                    sb.sleep(0.1)\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_linkedin.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True, ad_block=True) as sb:\n    url = \"https://www.linkedin.com/company/selenium\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.click_if_visible('button[aria-label=\"Dismiss\"]')\n    sb.sleep(1)\n    for i in range(42):\n        sb.cdp.scroll_down(16)\n    print(sb.get_page_title())\n    sb.save_as_pdf_to_logs()\n    sb.save_page_source_to_logs()\n    sb.save_screenshot_to_logs()\n    print(\"Logs have been saved to: ./latest_logs/\")\n"
  },
  {
    "path": "examples/cdp_mode/raw_mfa_login.py",
    "content": "from seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/realworld/login\"\nsb = sb_cdp.Chrome(url)\nsb.type(\"#username\", \"demo_user\")\nsb.type(\"#password\", \"secret_pass\")\nsb.enter_mfa_code(\"#totpcode\", \"GAXG2MTEOR3DMMDG\")\nsb.assert_text(\"Welcome!\", \"h1\")\nsb.click('a:contains(\"This Page\")')\nsb.highlight(\"h1\")\nsb.highlight(\"img#image1\")\nsb.driver.stop()\n"
  },
  {
    "path": "examples/cdp_mode/raw_mobile_agents.py",
    "content": "from seleniumbase import SB\n\nagent = \"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36\"\nagent += \" (KHTML, like Gecko) Mobile Safari/537.36\"\n\nsites = [\"facebook\", \"twitter\", \"linkedin\", \"youtube\", \"firefox\", \"amazon\"]\nsites += [\"chatgpt\", \"gmail\", \"perplexity\", \"snapchat\", \"tiktok\", \"roblox\"]\nurls = [f\"https://www.{site}.com\" for site in sites]\n\nfor url in urls:\n    with SB(uc=True, test=True, mobile=True) as sb:\n        sb.set_window_position(20, 54)\n        sb.activate_cdp_mode()\n        sb.open(url)\n        sb.sleep(2)\n        sb.get_new_driver()\n        sb.set_window_position(550, 54)\n        sb.activate_cdp_mode(agent=agent)\n        sb.open(url)\n        sb.sleep(8)\n"
  },
  {
    "path": "examples/cdp_mode/raw_mobile_async.py",
    "content": "import asyncio\r\nimport mycdp\r\nimport time\r\nfrom seleniumbase import cdp_driver\r\nfrom seleniumbase import decorators\r\n\r\n\r\nasync def main():\r\n    url = \"https://gitlab.com/users/sign_in\"\r\n    driver = await cdp_driver.start_async()\r\n    await driver.page.send(\r\n        mycdp.emulation.set_device_metrics_override(\r\n            width=412, height=732, device_scale_factor=3, mobile=True\r\n        )\r\n    )\r\n    page = await driver.get(url, lang=\"en\")\r\n    time.sleep(3)\r\n    await page.solve_captcha()\r\n    element = await page.select('label[for=\"user_login\"]')\r\n    await element.flash_async(duration=1.5, color=\"44EE44\")\r\n    time.sleep(1)\r\n    element = await page.select('[data-testid=\"sign-in-button\"]')\r\n    await element.flash_async(duration=2, color=\"44EE44\")\r\n    time.sleep(2)\r\n    driver.stop()\r\n\r\nif __name__ == \"__main__\":\r\n    loop = asyncio.new_event_loop()\r\n    with decorators.print_runtime(\"raw_mobile_async.py\"):\r\n        loop.run_until_complete(main())\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_mobile_gitlab.py",
    "content": "import mycdp\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://gitlab.com/users/sign_in\"\n    sb.activate_cdp_mode()\n    tab = sb.cdp.get_active_tab()\n    loop = sb.cdp.get_event_loop()\n    loop.run_until_complete(\n        tab.send(\n            mycdp.emulation.set_device_metrics_override(\n                width=412, height=732, device_scale_factor=3, mobile=True\n            )\n        )\n    )\n    sb.open(url)\n    sb.sleep(2)\n    sb.solve_captcha()\n    # (The rest is for testing and demo purposes)\n    sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\n    sb.assert_element('label[for=\"user_login\"]')\n    sb.highlight('button:contains(\"Sign in\")')\n    sb.highlight('h1:contains(\"GitLab\")')\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\n"
  },
  {
    "path": "examples/cdp_mode/raw_mobile_roblox.py",
    "content": "import mycdp\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://www.roblox.com/\"\n    agent = (\n        \"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 \"\n        \"(KHTML, like Gecko) Mobile Safari/537.36\"\n    )\n    sb.activate_cdp_mode(agent=agent)\n    tab = sb.cdp.get_active_tab()\n    loop = sb.cdp.get_event_loop()\n    loop.run_until_complete(\n        tab.send(\n            mycdp.emulation.set_device_metrics_override(\n                width=412, height=732, device_scale_factor=3, mobile=True\n            )\n        )\n    )\n    sb.open(url)\n    sb.assert_element(\"#download-the-app-container\")\n    sb.assert_text(\"Roblox for Android\")\n    sb.highlight('span:contains(\"Roblox for Android\")', loops=8)\n    sb.highlight('span:contains(\"Continue in App\")', loops=8)\n"
  },
  {
    "path": "examples/cdp_mode/raw_mouser.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.mouser.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(5)\r\n    sb.press_keys('input[name=\"keyword\"]', \"FLUKE-TC01B 25HZ\")\r\n    sb.click('button[type=\"submit\"]')\r\n    sb.sleep(2)\r\n    sb.highlight(\"h1\")\r\n    sb.highlight(\"span#spnDescription\")\r\n    sb.highlight(\"td.ext-price-col\")\r\n    print(sb.get_text(\"h1\"))\r\n    print(sb.get_text(\"span#spnDescription\"))\r\n    print(sb.get_text(\"td.ext-price-col\"))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_multi_async.py",
    "content": "# Testing multiple CDP drivers using the async API\nimport asyncio\nfrom concurrent.futures import ThreadPoolExecutor\nfrom random import randint\nfrom seleniumbase import cdp_driver\nfrom seleniumbase import decorators\n\n\nasync def main(url):\n    driver = await cdp_driver.start_async()\n    page = await driver.get(url)\n    await page.set_window_rect(randint(4, 600), randint(8, 410), 860, 500)\n    await page.sleep(0.5)\n    await page.type(\"input\", \"Text\")\n    await page.click(\"button\")\n    await page.sleep(2)\n    driver.stop()\n\n\ndef set_up_loop(url):\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main(url))\n\n\nif __name__ == \"__main__\":\n    urls = [\"https://seleniumbase.io/demo_page\" for i in range(5)]\n    with decorators.print_runtime(\"raw_multi_async.py\"):\n        with ThreadPoolExecutor(max_workers=len(urls)) as executor:\n            for url in urls:\n                executor.submit(set_up_loop, url)\n"
  },
  {
    "path": "examples/cdp_mode/raw_multi_captcha.py",
    "content": "# Testing multiple CDP drivers using the sync API\nfrom concurrent.futures import ThreadPoolExecutor\nfrom random import randint\nfrom seleniumbase import decorators\nfrom seleniumbase import sb_cdp\n\n\ndef main(url):\n    sb = sb_cdp.Chrome(url, lang=\"en\", incognito=True)\n    sb.set_window_rect(randint(4, 680), randint(8, 380), 840, 520)\n    sb.sleep(2)\n    sb.gui_click_captcha()\n    sb.sleep(2)\n    sb.driver.quit()\n\n\nif __name__ == \"__main__\":\n    urls = [\"https://seleniumbase.io/apps/turnstile\" for i in range(5)]\n    with decorators.print_runtime(\"raw_multi_captcha.py\"):\n        with ThreadPoolExecutor(max_workers=len(urls)) as executor:\n            for url in urls:\n                executor.submit(main, url)\n"
  },
  {
    "path": "examples/cdp_mode/raw_multi_cdp.py",
    "content": "# Testing multiple CDP drivers using the sync API\nfrom concurrent.futures import ThreadPoolExecutor\nfrom random import randint\nfrom seleniumbase import decorators\nfrom seleniumbase import sb_cdp\n\n\ndef main(url):\n    sb = sb_cdp.Chrome(url, lang=\"en\")\n    sb.set_window_rect(randint(4, 680), randint(8, 380), 840, 520)\n    sb.press_keys(\"input\", \"Text\")\n    sb.highlight(\"button\")\n    sb.click(\"button\")\n    sb.sleep(2)\n    sb.driver.quit()\n\n\nif __name__ == \"__main__\":\n    urls = [\"https://seleniumbase.io/demo_page\" for i in range(5)]\n    with decorators.print_runtime(\"raw_multi_cdp.py\"):\n        with ThreadPoolExecutor(max_workers=len(urls)) as executor:\n            for url in urls:\n                executor.submit(main, url)\n"
  },
  {
    "path": "examples/cdp_mode/raw_mycdp_cookies.py",
    "content": "import mycdp\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    sb.activate_cdp_mode(\"https://learn.microsoft.com/en-us/\")\n    tab = sb.cdp.get_active_tab()\n    loop = sb.cdp.get_event_loop()\n    print(loop.run_until_complete(tab.send(mycdp.storage.get_cookies())))\n"
  },
  {
    "path": "examples/cdp_mode/raw_nevada_search.py",
    "content": "\"\"\"Business Entity Search / Bypasses hCaptcha.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = \"https://www.nvsilverflume.gov/home\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(3)\r\n    sb.click('a[href=\"/redirectToCenuity/be\"]')\r\n    sb.sleep(3.6)\r\n    sb.assert_element('label:contains(\"Business Search\")')\r\n    sb.click('input#BusinessSearch_Index_rdContains')\r\n    sb.sleep(0.6)\r\n    name_field = 'input[data-automation-id*=\"EntityName\"]'\r\n    search = \"Laser Tag\"\r\n    sb.press_keys(name_field, search + \"\\n\")\r\n    sb.sleep(6.5)\r\n    print('*** Business Search for \"%s\":' % search)\r\n    businesses = sb.select_all(\"td a[onclick]\")\r\n    for business in businesses:\r\n        print(business.text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_nike.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", pls=\"none\") as sb:\r\n    url = \"https://www.nike.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.5)\r\n    sb.click('[data-testid=\"user-tools-container\"] search')\r\n    sb.sleep(1.5)\r\n    search = \"Nike Air Force 1\"\r\n    sb.press_keys('input[type=\"search\"]', search)\r\n    sb.sleep(4)\r\n    details = 'ul[data-testid*=\"products\"] figure .details'\r\n    elements = sb.select_all(details)\r\n    if elements:\r\n        print('**** Found results for \"%s\": ****' % search)\r\n    for element in elements:\r\n        print(\"* \" + element.text)\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_nordstrom.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.nordstrom.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.2)\r\n    sb.click(\"input#keyword-search-input\")\r\n    sb.sleep(0.8)\r\n    search = \"cocktail dresses for women teal\"\r\n    sb.press_keys(\"input#keyword-search-input\", search + \"\\n\")\r\n    sb.sleep(2.2)\r\n    for i in range(17):\r\n        sb.scroll_down(16)\r\n        sb.sleep(0.14)\r\n    print('*** Nordstrom Search for \"%s\":' % search)\r\n    unique_item_text = []\r\n    items = sb.find_elements(\"article\")\r\n    for item in items:\r\n        description = item.querySelector(\"article h3\")\r\n        if description and description.text not in unique_item_text:\r\n            unique_item_text.append(description.text)\r\n            price_text = \"\"\r\n            price = item.querySelector('div div span[aria-hidden=\"true\"]')\r\n            if price:\r\n                price_text = price.text\r\n                print(\"* %s (%s)\" % (description.text, price_text))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_pixelscan.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True, ad_block=True) as sb:\r\n    url = \"https://pixelscan.net/fingerprint-check\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.wait_for_element(\"pxlscn-dynamic-ad\")\r\n    sb.sleep(0.5)\r\n    sb.remove_elements(\"pxlscn-dynamic-ad\")\r\n    sb.sleep(2)\r\n    sb.assert_text(\"No masking detected\", \"pxlscn-fingerprint-masking\")\r\n    sb.assert_text(\"No automated behavior\", \"pxlscn-bot-detection\")\r\n    sb.cdp.highlight(\"pxlscn-fingerprint-masking p\")\r\n    sb.sleep(1)\r\n    sb.cdp.highlight(\"pxlscn-bot-detection p\")\r\n    sb.sleep(1)\r\n    sb.cdp.highlight('span.status-success')\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_planetmc.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, guest=True) as sb:\r\n    url = \"www.planetminecraft.com/account/sign_in/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2)\r\n    sb.solve_captcha()\r\n    sb.wait_for_element_absent(\"input[disabled]\")\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_pokemon.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n    url = \"https://www.pokemon.com/us\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1.5)\r\n    sb.click_if_visible(\"button#onetrust-accept-btn-handler\")\r\n    sb.sleep(1.2)\r\n    sb.click(\"a span.icon_pokeball\")\r\n    sb.sleep(2.5)\r\n    sb.click('b:contains(\"Show Advanced Search\")')\r\n    sb.sleep(2.5)\r\n    sb.click('span[data-type=\"type\"][data-value=\"electric\"]')\r\n    sb.sleep(0.7)\r\n    sb.scroll_into_view(\"a#advSearch\")\r\n    sb.sleep(0.7)\r\n    sb.click(\"a#advSearch\")\r\n    sb.sleep(1.2)\r\n    sb.click('img[src*=\"img/pokedex/detail/025.png\"]')\r\n    sb.assert_text(\"Pikachu\", 'div[class*=\"title\"]')\r\n    sb.assert_element('img[alt=\"Pikachu\"]')\r\n    sb.scroll_into_view(\"div.pokemon-ability-info\")\r\n    sb.sleep(1.2)\r\n    sb.cdp.flash('div[class*=\"title\"]')\r\n    sb.cdp.flash('img[alt=\"Pikachu\"]')\r\n    sb.cdp.flash(\"div.pokemon-ability-info\")\r\n    name = sb.get_text(\"label.styled-select\")\r\n    info = sb.get_text(\"div.version-descriptions p.active\")\r\n    print(\"*** %s: ***\\n* %s\" % (name, info))\r\n    sb.sleep(2)\r\n    sb.cdp.highlight_overlay(\"div.pokemon-ability-info\")\r\n    sb.sleep(2)\r\n    sb.open(\"https://events.pokemon.com/EventLocator/\")\r\n    sb.sleep(2)\r\n    sb.click('span:contains(\"Championship\")')\r\n    sb.sleep(2)\r\n    events = sb.select_all(\"div.event-info__title\")\r\n    print(\"*** Pokémon Championship Events: ***\")\r\n    for event in events:\r\n        print(\"* \" + event.text)\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_priceline.py",
    "content": "\"\"\"Priceline does a lot of A/B testing. Selectors change frequently.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", guest=True, pls=\"none\") as sb:\r\n    url = \"https://www.priceline.com\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.6)\r\n    input_selector = '[name=\"endLocation\"]'\r\n    if not sb.is_element_present(input_selector):\r\n        input_selector = \"div.location-input input\"\r\n    sb.click(input_selector)\r\n    sb.sleep(1.2)\r\n    location = \"Portland, OR\"\r\n    selection = \"Oregon, United States\"  # (Dropdown option)\r\n    sb.press_keys(input_selector, location)\r\n    sb.sleep(0.6)\r\n    sb.click(selection)\r\n    sb.sleep(0.4)\r\n    sb.scroll_down(25)\r\n    sb.sleep(0.4)\r\n    calendar_close = 'button[aria-label=\"Dismiss calendar\"]'\r\n    if not sb.is_element_visible(calendar_close):\r\n        calendar_close = '[data-mode=\"range\"] span.px-1'\r\n    sb.click(calendar_close)\r\n    sb.sleep(0.6)\r\n    sb.click('form button[type=\"submit\"]')\r\n    sb.sleep(4.8)\r\n    if len(sb.cdp.get_tabs()) > 1:\r\n        sb.cdp.close_active_tab()\r\n        sb.cdp.switch_to_newest_tab()\r\n        sb.sleep(0.6)\r\n    sb.sleep(0.8)\r\n    for y in range(1, 9):\r\n        sb.scroll_to_y(y * 400)\r\n        sb.sleep(0.5)\r\n    hotel_names = sb.find_elements('h3 div[class*=\"TitleName\"]')\r\n    if not hotel_names:\r\n        hotel_names = sb.find_elements(\r\n            'a[data-autobot-element-id*=\"HOTEL_NAME\"]'\r\n        )\r\n    price_selector = '[class*=\"PriceWrap\"] .relative > .items-center'\r\n    if sb.is_element_visible(price_selector):\r\n        hotel_prices = sb.find_elements(price_selector)\r\n    elif sb.is_element_present(\r\n        '[font-size=\"12px\"] + [font-size=\"20px\"]'\r\n    ):\r\n        hotel_prices = sb.find_elements(\r\n            '[font-size=\"12px\"] + [font-size=\"20px\"]'\r\n        )\r\n    else:\r\n        hotel_prices = sb.find_elements(\r\n            'span.text-priceSuper-heading4 + div > span'\r\n        )\r\n    print(\"Priceline Hotels in %s:\" % location)\r\n    print(sb.get_text('[data-testid=\"POPOVER-DATE-PICKER\"]'))\r\n    if len(hotel_names) == 0:\r\n        print(\"No availability over the selected dates!\")\r\n    count = 0\r\n    for i, hotel in enumerate(hotel_names):\r\n        if hotel_prices[i] and hotel_prices[i].text:\r\n            count += 1\r\n            hotel_price = \"$\" + hotel_prices[i].text\r\n            if hotel_price.startswith(\"$$ \"):\r\n                hotel_price = hotel_price.replace(\"$$ \", \"$\")\r\n            print(\"* %s: %s => %s\" % (count, hotel.text, hotel_price))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_print_to_pdf.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, pls=\"none\") as sb:\r\n    url = \"https://seleniumbase.io\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.assert_title(\"SeleniumBase Docs\")\r\n    file_path = \"downloaded_files/sb.pdf\"\r\n    sb.print_to_pdf(file_path)\r\n    sb.assert_downloaded_file(\"sb.pdf\")\r\n    sb.assert_pdf_text(file_path, \"SeleniumBase\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_proxy.py",
    "content": "from seleniumbase import decorators\nfrom seleniumbase import sb_cdp\n\n# Change this to \"ip:port\" or \"user:pass@ip:port\"\nproxy = None\n\n\n@decorators.print_runtime(\"CDP Proxy Example\")\ndef main():\n    url = \"https://api.ipify.org/\"\n    sb = sb_cdp.Chrome(url, lang=\"en\", pls=\"none\", proxy=proxy)\n    ip_address = sb.get_text(\"body\")\n    if \"ERR\" in ip_address:\n        raise Exception(\"Failed to determine IP Address!\")\n    print(\"\\n\\nMy IP Address = %s\\n\" % ip_address)\n    sb.open(\"https://ipinfo.io/%s\" % ip_address)\n    sb.sleep(2)\n    sb.wait_for_text(ip_address, \"h1\", timeout=20)\n    sb.find_element('[href=\"/signup\"]')\n    sb.wait_for_text(\"Hosted domains\", timeout=20)\n    sb.highlight(\"h1\")\n    pop_up = '[role=\"dialog\"] span.cursor-pointer'\n    sb.click_if_visible(pop_up)\n    sb.highlight(\"#block-summary\")\n    sb.click_if_visible(pop_up)\n    sb.highlight(\"#block-geolocation\")\n    sb.click_if_visible(pop_up)\n    sb.sleep(2)\n    print(\"Displaying Host Info:\")\n    text = sb.get_text(\"#block-summary\").split(\"Hosted domains\")[0]\n    rows = text.split(\"\\n\")\n    data = []\n    for row in rows:\n        if row.strip() != \"\":\n            data.append(row.strip())\n    print(\"\\n\".join(data).replace('\\n\"', ' \"'))\n    print(\"\\nDisplaying GeoLocation Info:\")\n    text = sb.get_text(\"#block-geolocation\")\n    text = text.split(\"IP Geolocation data\")[0]\n    rows = text.split(\"\\n\")\n    data = []\n    for row in rows:\n        if row.strip() != \"\":\n            data.append(row.strip())\n    print(\"\\n\".join(data).replace('\\n\"', ' \"'))\n    sb.click_if_visible(pop_up)\n    sb.sleep(3)\n    sb.driver.stop()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/cdp_mode/raw_publication.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n    url = \"https://www.researchgate.net/search/publication\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.2)\r\n    shadow_head = \"div.main-content div\"\r\n    if sb.is_element_present(shadow_head):\r\n        sb.cdp.gui_click_element(shadow_head)\r\n        sb.sleep(1.5)\r\n    sb.assert_text(\"Discover the world's scientific knowledge\")\r\n    sb.click_if_visible('button[aria-label=\"Close\"]')\r\n    sb.highlight('h1[class*=\"nova\"]')\r\n    sb.highlight('input[name=\"q\"]')\r\n    sb.highlight(\"a.menu-item.selected\")\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_radwell.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", incognito=True) as sb:\r\n    url = \"https://www.radwell.com/en-US/Search/Advanced/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1)\r\n    sb.press_keys(\"form#basicsearch input\", \"821C-PM-111DA-142\")\r\n    sb.click('[value=\"Search Icon\"]')\r\n    sb.sleep(2)\r\n    if not sb.is_element_visible(\"a.manufacturer-link\"):\r\n        sb.solve_captcha()\r\n        sb.sleep(0.5)\r\n    sb.assert_text(\"MAC VALVES INC\", \"a.manufacturer-link\")\r\n    sb.highlight(\"a.manufacturer-link\")\r\n    description = sb.get_text(\"div.product-information\")\r\n    description = description.replace(\"                \", \"\")\r\n    description = description.replace(\"\\n             \\n\", \"\\n\")\r\n    print(description)\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_ralphlauren.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.ralphlauren.com.au/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1.6)\r\n    if not sb.is_element_present('[title=\"Locate Stores\"]'):\r\n        sb.evaluate(\"window.location.reload();\")\r\n        sb.sleep(1.2)\r\n    category = \"women\"\r\n    search = \"Dresses\"\r\n    sb.click('a[data-cgid=\"%s\"]' % category)\r\n    sb.sleep(2.2)\r\n    sb.click('a:contains(\"%s\")' % search)\r\n    sb.sleep(3.8)\r\n    for i in range(6):\r\n        sb.scroll_down(25)\r\n        sb.sleep(0.2)\r\n    print('*** Ralph Lauren Search for \"%s\":' % search)\r\n    unique_item_text = []\r\n    items = sb.find_elements('div.product-data')\r\n    for item in items:\r\n        description = item.querySelector(\"a.name-link\")\r\n        if description and description.text not in unique_item_text:\r\n            unique_item_text.append(description.text)\r\n            print(\"* \" + description.text)\r\n            price = item.querySelector('span[title=\"Price\"]')\r\n            if price:\r\n                print(\"  (\" + price.text.replace(\"   \", \" \") + \")\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_reddit.py",
    "content": "\"\"\"Reddit Search / Bypasses reCAPTCHA.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, use_chromium=True) as sb:\r\n    search = \"reddit+scraper\"\r\n    url = f\"https://www.reddit.com/r/webscraping/search/?q={search}\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.solve_captcha()  # Might not be needed\r\n    post_title = '[data-testid=\"post-title\"]'\r\n    sb.wait_for_element(post_title)\r\n    for i in range(8):\r\n        sb.scroll_down(25)\r\n        sb.sleep(0.2)\r\n    posts = sb.select_all(post_title)\r\n    print('*** Reddit Posts for \"%s\":' % search)\r\n    for post in posts:\r\n        print(\"* \" + post.text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_reddit_async.py",
    "content": "import asyncio\r\nfrom seleniumbase import cdp_driver\r\n\r\n\r\nasync def main():\r\n    search = \"reddit+scraper\"\r\n    url = f\"https://www.reddit.com/r/webscraping/search/?q={search}\"\r\n    driver = await cdp_driver.start_async(use_chromium=True)\r\n    page = await driver.get(url)\r\n    await page.solve_captcha()  # Might not be needed\r\n    post_title = '[data-testid=\"post-title\"]'\r\n    await page.select(post_title)\r\n    for i in range(8):\r\n        await page.scroll_down(25)\r\n        await page.sleep(0.2)\r\n    posts = await page.select_all(post_title)\r\n    print('*** Reddit Posts for \"%s\":' % search)\r\n    for post in posts:\r\n        print(\"* \" + post.text)\r\n    driver.stop()\r\n\r\nif __name__ == \"__main__\":\r\n    loop = asyncio.new_event_loop()\r\n    loop.run_until_complete(main())\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_req_async.py",
    "content": "\"\"\"Using CDP.fetch.RequestPaused to filter content in real-time.\"\"\"\r\nimport asyncio\r\nimport colorama\r\nimport mycdp\r\nimport sys\r\nfrom seleniumbase import decorators\r\nfrom seleniumbase import cdp_driver\r\n\r\nc1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\r\nc2 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\r\ncr = colorama.Style.RESET_ALL\r\nif \"linux\" in sys.platform:\r\n    c1 = c2 = cr = \"\"\r\n\r\n\r\nclass RequestPausedTest():\r\n    async def request_paused_handler(self, event, tab):\r\n        r = event.request\r\n        is_image = \".png\" in r.url or \".jpg\" in r.url or \".gif\" in r.url\r\n        if not is_image:  # Let the data through\r\n            tab.feed_cdp(\r\n                mycdp.fetch.continue_request(request_id=event.request_id)\r\n            )\r\n        else:  # Block the data (images)\r\n            TIMED_OUT = mycdp.network.ErrorReason.TIMED_OUT\r\n            s = f\"{c1}BLOCKING{cr} | {c2}{r.method}{cr} | {r.url}\"\r\n            print(f\" >>> ------------\\n{s}\")\r\n            tab.feed_cdp(\r\n                mycdp.fetch.fail_request(event.request_id, TIMED_OUT)\r\n            )\r\n\r\n    async def start_test(self):\r\n        driver = await cdp_driver.start_async()\r\n        tab = await driver.get(\"about:blank\")\r\n        tab.add_handler(mycdp.fetch.RequestPaused, self.request_paused_handler)\r\n        url = \"https://gettyimages.com/photos/firefly-2003-nathan\"\r\n        await driver.get(url)\r\n        await asyncio.sleep(5)\r\n        driver.stop()\r\n\r\n\r\n@decorators.print_runtime(\"RequestPausedTest\")\r\ndef main():\r\n    test = RequestPausedTest()\r\n    loop = asyncio.new_event_loop()\r\n    loop.run_until_complete(test.start_test())\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    main()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_req_mod.py",
    "content": "\"\"\"Using CDP.fetch.RequestPaused to modify requests in real-time.\"\"\"\r\nimport mycdp\r\nfrom seleniumbase import SB\r\n\r\n\r\nasync def request_paused_handler(event, tab):\r\n    r = event.request\r\n    rid = event.request_id\r\n    is_image = \".png\" in r.url or \".jpg\" in r.url or \".gif\" in r.url\r\n    if not is_image:  # Let the data through\r\n        tab.feed_cdp(mycdp.fetch.continue_request(request_id=rid))\r\n    else:  # Modify the data (Change the image URL)\r\n        new_url = \"https://seleniumbase.io/other/with_frakes.jpg\"\r\n        tab.feed_cdp(mycdp.fetch.continue_request(request_id=rid, url=new_url))\r\n\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", pls=\"none\") as sb:\r\n    sb.activate_cdp_mode(\"about:blank\")\r\n    sb.cdp.add_handler(mycdp.fetch.RequestPaused, request_paused_handler)\r\n    sb.cdp.open(\"https://gettyimages.com/photos/jonathan-frakes-cast-2022\")\r\n    new_size = \"--width:100;--height:100;\"\r\n    sb.cdp.set_attributes('[style*=\"--width:\"]', \"style\", new_size)\r\n    sb.sleep(6)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_req_sb.py",
    "content": "\"\"\"Using CDP.fetch.RequestPaused to filter content in real-time.\"\"\"\r\nimport colorama\r\nimport mycdp\r\nimport sys\r\nfrom seleniumbase import SB\r\n\r\nc1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\r\nc2 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\r\ncr = colorama.Style.RESET_ALL\r\nif \"linux\" in sys.platform:\r\n    c1 = c2 = cr = \"\"\r\n\r\n\r\nasync def request_paused_handler(event, tab):\r\n    r = event.request\r\n    is_image = \".png\" in r.url or \".jpg\" in r.url or \".gif\" in r.url\r\n    if not is_image:  # Let the data through\r\n        tab.feed_cdp(mycdp.fetch.continue_request(request_id=event.request_id))\r\n    else:  # Block the data (images)\r\n        TIMED_OUT = mycdp.network.ErrorReason.TIMED_OUT\r\n        s = f\"{c1}BLOCKING{cr} | {c2}{r.method}{cr} | {r.url}\"\r\n        print(f\" >>> ------------\\n{s}\")\r\n        tab.feed_cdp(mycdp.fetch.fail_request(event.request_id, TIMED_OUT))\r\n\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    sb.activate_cdp_mode(\"about:blank\")\r\n    sb.cdp.add_handler(mycdp.fetch.RequestPaused, request_paused_handler)\r\n    url = \"https://gettyimages.com/photos/firefly-2003-nathan\"\r\n    sb.open(url)\r\n    sb.sleep(5)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_res_nike.py",
    "content": "\"\"\"Using CDP.network.RequestWillBeSent and CDP.network.ResponseReceived.\"\"\"\r\nimport colorama\r\nimport mycdp\r\nimport sys\r\nfrom seleniumbase import SB\r\n\r\nc1 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\r\nc2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\r\ncr = colorama.Style.RESET_ALL\r\nif \"linux\" in sys.platform:\r\n    c1 = c2 = cr = \"\"\r\n\r\n\r\nasync def send_handler(event: mycdp.network.RequestWillBeSent):\r\n    r = event.request\r\n    s = f\"{r.method} {r.url}\"\r\n    for k, v in r.headers.items():\r\n        s += f\"\\n\\t{k} : {v}\"\r\n    print(c1 + \"*** ==> RequestWillBeSent <== ***\" + cr)\r\n    print(s)\r\n\r\n\r\nasync def receive_handler(event: mycdp.network.ResponseReceived):\r\n    print(c2 + \"*** ==> ResponseReceived <== ***\" + cr)\r\n    print(event.response)\r\n\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", pls=\"none\") as sb:\r\n    url = \"https://www.nike.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler)\r\n    sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler)\r\n    sb.sleep(2.5)\r\n    sb.click('[data-testid=\"user-tools-container\"] search')\r\n    sb.sleep(1.5)\r\n    search = \"Nike Air Force 1\"\r\n    sb.press_keys('input[type=\"search\"]', search)\r\n    sb.sleep(4)\r\n    elements = sb.select_all('ul[data-testid*=\"products\"] figure .details')\r\n    if elements:\r\n        print('**** Found results for \"%s\": ****' % search)\r\n    for element in elements:\r\n        print(\"* \" + element.text)\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_res_sb.py",
    "content": "\"\"\"Using CDP.network.RequestWillBeSent and CDP.network.ResponseReceived.\"\"\"\r\nimport colorama\r\nimport mycdp\r\nimport sys\r\nfrom seleniumbase import SB\r\n\r\nc1 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\r\nc2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\r\ncr = colorama.Style.RESET_ALL\r\nif \"linux\" in sys.platform:\r\n    c1 = c2 = cr = \"\"\r\n\r\n\r\nasync def send_handler(event: mycdp.network.RequestWillBeSent):\r\n    r = event.request\r\n    s = f\"{r.method} {r.url}\"\r\n    for k, v in r.headers.items():\r\n        s += f\"\\n\\t{k} : {v}\"\r\n    print(c1 + \"*** ==> RequestWillBeSent <== ***\" + cr)\r\n    print(s)\r\n\r\n\r\nasync def receive_handler(event: mycdp.network.ResponseReceived):\r\n    print(c2 + \"*** ==> ResponseReceived <== ***\" + cr)\r\n    print(event.response)\r\n\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    sb.activate_cdp_mode(\"about:blank\")\r\n    sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler)\r\n    sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler)\r\n    url = \"https://seleniumbase.io/apps/calculator\"\r\n    sb.open(url)\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_reuse_browser.py",
    "content": "\"\"\"Test connecting to an existing browser.\"\"\"\nfrom seleniumbase import sb_cdp\n\nsb1 = sb_cdp.Chrome(\"https://example.com\")\nport = sb1.get_rd_port()\nsb2 = sb_cdp.Chrome(host=\"127.0.0.1\", port=port)\nprint(\"The remote-debugging port: %s\" % port)\nassert sb1.get_rd_port() == sb2.get_rd_port()\nassert sb1.get_current_url() == sb2.get_current_url()\n"
  },
  {
    "path": "examples/cdp_mode/raw_science.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, incognito=True, test=True) as sb:\r\n    url = \"https://earth.esa.int/eogateway/search\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1)\r\n    sb.click_if_visible('button:contains(\"Accept cookies\")')\r\n    for i in range(20):\r\n        sb.scroll_to_bottom()\r\n        sb.click_if_visible('button:contains(\"READ MORE\")')\r\n    sb.sleep(1)\r\n    elements = sb.find_elements(\"h4 a span\")\r\n    for element in elements:\r\n        print(element.text)\r\n    print(\"*** Total entries: %s\" % len(elements))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_seatgeek.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, ad_block=True) as sb:\r\n    url = \"https://seatgeek.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    input_field = 'input[name=\"search\"]'\r\n    sb.wait_for_element(input_field)\r\n    sb.sleep(1.6)\r\n    query = \"Jerry Seinfeld\"\r\n    sb.press_keys(input_field, query)\r\n    sb.sleep(1.6)\r\n    sb.click(\"li#active-result-item\")\r\n    sb.sleep(4.2)\r\n    print('*** SeatGeek Search for \"%s\":' % query)\r\n    item_selector = '[data-testid=\"listing-item\"]'\r\n    for item in sb.find_elements(item_selector):\r\n        print(item.text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_socialblade.py",
    "content": "\"\"\"Bypass bot-detection to view SocialBlade ranks for YouTube\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, ad_block=True, pls=\"none\") as sb:\n    url = \"https://socialblade.com/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(1.5)\n    if not sb.is_element_visible('input[placeholder*=\"Search\"]'):\n        sb.solve_captcha()\n        sb.sleep(0.5)\n    channel_name = \"michaelmintz\"\n    channel_title = \"Michael Mintz\"\n    sb.press_keys('input[placeholder*=\"Search\"]', channel_name)\n    sb.sleep(2)\n    sb.click('a:contains(\"%s\")' % channel_title)\n    sb.sleep(2)\n    sb.remove_elements(\"#lngtd-top-sticky\")\n    sb.sleep(1.5)\n    name = sb.get_text(\"h3\")\n    ch_name = name.split(\" \")[-1]\n    name = name.split(\" @\")[0]\n    link = \"https://www.youtube.com/%s\" % ch_name\n    print(\"********** SocialBlade Stats for %s: **********\" % name)\n    print(\">>> (Link: %s) <<<\" % link)\n    print(sb.get_text('[class*=\"grid lg:hidden\"]'))\n    print(\"********** SocialBlade Ranks: **********\")\n    print(sb.get_text('[class*=\"gap-3 flex-1\"]'))\n    for i in range(17):\n        sb.scroll_down(6)\n        sb.sleep(0.1)\n    sb.sleep(2)\n"
  },
  {
    "path": "examples/cdp_mode/raw_softpedia.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, ad_block=True) as sb:\r\n    url = \"https://www.softpedia.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    search_box = 'input[name=\"search_term\"]'\r\n    search = \"3D Model Lab\"\r\n    sb.click(search_box)\r\n    sb.press_keys(search_box, search + \"\\n\")\r\n    sb.sleep(2)\r\n    sb.remove_elements(\"#adcontainer1\")\r\n    sb.sleep(2.5)\r\n    print('*** Softpedia Search for \"%s\":' % search)\r\n    links = []\r\n    item_container = 'div[style=\"min-height:100px;\"]'\r\n    sb.wait_for_element(item_container)\r\n    items = sb.find_elements(item_container)\r\n    for item in items:\r\n        result = item.querySelector(\"h4 a\")\r\n        links.append(result.get_attribute(\"href\"))\r\n        print(\"* \" + result.text)\r\n        print(item.querySelector(\"p\").get_attribute(\"title\"))\r\n    for link in links:\r\n        sb.open(link)\r\n        sb.remove_elements(\"div.ad\")\r\n        sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_southwest.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n    url = \"https://www.southwest.com/air/booking/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.8)\r\n    origin = \"BOS\"\r\n    destination = \"MDW\"\r\n    sb.cdp.click_if_visible(\"button#onetrust-accept-btn-handler\")\r\n    sb.sleep(0.5)\r\n    sb.cdp.gui_click_element(\"input#originationAirportCode\")\r\n    sb.sleep(0.2)\r\n    sb.cdp.select(\"input#originationAirportCode\").clear_input()\r\n    sb.sleep(0.2)\r\n    sb.uc_gui_press_keys(\" \" + \"\\n\")\r\n    sb.sleep(0.5)\r\n    sb.cdp.gui_click_element(\"input#originationAirportCode\")\r\n    sb.sleep(0.4)\r\n    sb.uc_gui_press_keys(origin + \"\\n\")\r\n    sb.sleep(0.4)\r\n    sb.cdp.gui_click_element(\"h1\")\r\n    sb.sleep(0.3)\r\n    sb.cdp.gui_click_element(\"input#destinationAirportCode\")\r\n    sb.sleep(0.2)\r\n    sb.cdp.select(\"input#destinationAirportCode\").clear_input()\r\n    sb.sleep(0.2)\r\n    sb.uc_gui_press_keys(destination + \"\\n\")\r\n    sb.sleep(0.4)\r\n    sb.cdp.gui_click_element(\"h1\")\r\n    sb.sleep(0.2)\r\n    sb.cdp.click_if_visible(\"button#onetrust-accept-btn-handler\")\r\n    sb.sleep(0.1)\r\n    sb.cdp.click('form button[data-test=\"submitField\"]')\r\n    sb.sleep(2.5)\r\n    sb.cdp.click('button[aria-labelledby*=\"nearby-airport-drawer-\"]')\r\n    sb.sleep(4)\r\n    day = sb.cdp.get_text('[aria-current=\"true\"] span[class*=\"cal\"]')\r\n    print(\"**** Flights from %s to %s ****\" % (origin, destination))\r\n    flights = sb.find_elements(\"li.air-booking-select-detail\")\r\n    for flight in flights:\r\n        info = flight.text\r\n        departs = info.split(\"Departs\")[-1].split(\"M\")[0].strip() + \"M\"\r\n        arrives = info.split(\"Arrives\")[-1].split(\"M\")[0].strip() + \"M\"\r\n        stops = flight.query_selector(\".flight-stops-badge\").text\r\n        duration = flight.query_selector('[class*=\"flight-duration\"]').text\r\n        p_elm = flight.query_selector('span.currency span[aria-hidden]')\r\n        if not p_elm:\r\n            continue\r\n        price = p_elm.text\r\n        print(f\"* {day}, {departs} -> {arrives} ({stops}: {duration}) {price}\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_stopandshop.py",
    "content": "\"\"\"Test Stop & Shop search. Non-US IPs might be blocked.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True) as sb:\r\n    url = \"https://stopandshop.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.6)\r\n    if not sb.is_element_present(\"#brand-logo_link\"):\r\n        sb.refresh()\r\n        sb.sleep(2.6)\r\n        sb.wait_for_element(\"#brand-logo_link\", timeout=3)\r\n    query = \"Fresh Turkey\"\r\n    required_text = \"Turkey\"\r\n    search_box = 'input[type=\"search\"]'\r\n    sb.wait_for_element(search_box)\r\n    sb.sleep(1.2)\r\n    sb.press_keys(search_box, query)\r\n    sb.sleep(1.2)\r\n    sb.click(\"button.search-btn\")\r\n    sb.sleep(3.2)\r\n    print('*** Stop & Shop Search for \"%s\":' % query)\r\n    print('    (Results must contain \"%s\".)' % required_text)\r\n    print('    (Results cannot contain \"Out of Stock\")')\r\n    unique_item_text = []\r\n    item_selector = \"div.product-tile_content\"\r\n    items = sb.find_elements(item_selector)\r\n    for item in items:\r\n        sb.sleep(0.06)\r\n        if \"Out of Stock\" not in item.text:\r\n            if required_text in item.text:\r\n                item.flash(color=\"44CC88\")\r\n                sb.sleep(0.025)\r\n                if item.text not in unique_item_text:\r\n                    unique_item_text.append(item.text)\r\n                    print(\"* \" + item.text)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_tab_switching.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    sb.activate_cdp_mode()\r\n    sb.open(\"data:text/html,<h1>Page A</h1>\")\r\n    sb.assert_text(\"Page A\")\r\n    sb.open_new_tab()\r\n    sb.open(\"data:text/html,<h1>Page B</h1>\")\r\n    sb.assert_text(\"Page B\")\r\n    sb.switch_to_tab(0)\r\n    sb.assert_text(\"Page A\")\r\n    sb.assert_text_not_visible(\"Page B\")\r\n    sb.switch_to_tab(1)\r\n    sb.assert_text(\"Page B\")\r\n    sb.assert_text_not_visible(\"Page A\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_theaters.py",
    "content": "\"\"\"Simple web-scraping example in CDP Mode\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n    url = \"https://architectureofcities.com/roman-theaters\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(0.5)\r\n    sb.click_if_visible(\"#cn-close-notice\")\r\n    sb.click_if_visible('[aria-label=\"Reject All\"]')\r\n    sb.click_if_visible('span:contains(\"Continue\")')\r\n    sb.sleep(1)\r\n    print(\"*** \" + sb.get_text(\"h1\") + \" ***\")\r\n    for item in sb.find_elements(\"h3\"):\r\n        if item.text and \".\" in item.text:\r\n            item.flash(color=\"44CC88\")\r\n            sb.scroll_down(34)\r\n            print(\"* \" + item.text.replace(\"  \", \" \"))\r\n            sb.sleep(0.15)\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_tiktok.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, guest=True) as sb:\r\n    url = \"https://www.tiktok.com/@startrek?lang=en\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2.5)\r\n    sb.click_if_visible('button[data-close-button=\"true\"]')\r\n    print(\"*** \" + sb.get_text('h2[data-e2e=\"user-bio\"]'))\r\n    for i in range(33):\r\n        sb.scroll_by_y(33)\r\n        sb.sleep(0.03)\r\n    items = sb.find_elements(\"picture img\")\r\n    for i, item in enumerate(items):\r\n        print(\"* %s: %s\" % (i, str(item.get_attribute(\"alt\"))))\r\n    print(\"*** %s total items found!\" % len(items))\r\n    sb.sleep(1)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_timezone.py",
    "content": "\"\"\"Timezone example using CDP Mode without WebDriver\"\"\"\r\nimport mycdp\r\nfrom seleniumbase import decorators\r\nfrom seleniumbase import sb_cdp\r\n\r\n\r\nasync def request_paused_handler(event, tab):\r\n    r = event.request\r\n    is_image = \".png\" in r.url or \".jpg\" in r.url or \".gif\" in r.url\r\n    if not is_image:  # Let the data through\r\n        tab.feed_cdp(mycdp.fetch.continue_request(request_id=event.request_id))\r\n    else:  # Block the data (images)\r\n        TIMED_OUT = mycdp.network.ErrorReason.TIMED_OUT\r\n        tab.feed_cdp(mycdp.fetch.fail_request(event.request_id, TIMED_OUT))\r\n\r\n\r\n@decorators.print_runtime(\"Timezone CDP Example\")\r\ndef main():\r\n    url = \"https://www.randymajors.org/what-time-zone-am-i-in\"\r\n    sb = sb_cdp.Chrome(\r\n        url,\r\n        ad_block=True,\r\n        lang=\"bn\",\r\n        tzone=\"Asia/Kolkata\",\r\n        geoloc=(26.855323, 80.937710)\r\n    )\r\n    sb.add_handler(mycdp.fetch.RequestPaused, request_paused_handler)\r\n    sb.remove_elements(\"#right-sidebar\")\r\n    sb.sleep(6)\r\n    sb.driver.stop()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    main()\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_timezone_sb.py",
    "content": "\"\"\"An example of changing settings during CDP Mode\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, pls=\"eager\", ad_block=True) as sb:\r\n    url = \"https://www.randymajors.org/what-time-zone-am-i-in\"\r\n    sb.activate_cdp_mode(url, tzone=\"Asia/Kolkata\", geoloc=(26.863, 80.94))\r\n    sb.remove_elements(\"#right-sidebar\")\r\n    sb.sleep(5)\r\n    sb.cdp.open(url, tzone=\"Asia/Tokyo\", geoloc=(35.050681, 136.844728))\r\n    sb.remove_elements(\"#right-sidebar\")\r\n    sb.sleep(5)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_totalwine.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.totalwine.com/\"\r\n    sb.activate_cdp_mode()\r\n    sb.open(url)\r\n    sb.sleep(1.8)\r\n    search_box = 'input[data-at=\"header-search-text\"]'\r\n    search = \"The Land by Psagot Cabernet\"\r\n    if not sb.is_element_present(search_box):\r\n        sb.evaluate(\"window.location.reload();\")\r\n        sb.sleep(1.8)\r\n    sb.click_if_visible(\"#onetrust-close-btn-container button\")\r\n    sb.sleep(0.5)\r\n    sb.click_if_visible('button[aria-label=\"Close modal\"]')\r\n    sb.sleep(1.2)\r\n    sb.click(search_box)\r\n    sb.sleep(1.2)\r\n    sb.press_keys(search_box, search)\r\n    sb.sleep(0.6)\r\n    sb.click_if_visible('button[aria-label=\"Close modal\"]')\r\n    sb.click('button[data-at=\"header-search-button\"]')\r\n    sb.sleep(1.8)\r\n    sb.click_if_visible('button[aria-label=\"Close modal\"]')\r\n    sb.click('img[data-at=\"product-search-productimage\"]')\r\n    sb.sleep(2.2)\r\n    print('*** Total Wine Search for \"%s\":' % search)\r\n    print(sb.get_text('h1[data-at=\"product-name-title\"]'))\r\n    print(sb.get_text('span[data-at=\"product-mixCaseprice-text\"]'))\r\n    print(\"Product Highlights:\")\r\n    print(sb.get_text('p[class*=\"productInformationReview\"]'))\r\n    print(\"Product Details:\")\r\n    print(sb.get_text('div[data-at=\"origin-details-table-container\"]'))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_trails.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, ad_block=True, test=True) as sb:\r\n    url = \"https://www.alltrails.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(3.5)\r\n    sb.click_if_visible(\"button.osano-cm-close\")\r\n    sb.sleep(0.5)\r\n    search_box = 'input[aria-describedby=\"search-instructions\"]'\r\n    search_term = \"Thundering Brook Falls\"\r\n    sb.wait_for_element(search_box)\r\n    sb.sleep(1.5)\r\n    sb.type(search_box, search_term + \" Trail\")\r\n    sb.sleep(1.5)\r\n    sb.click('a span:contains(\"%s\")' % search_term)\r\n    sb.sleep(3.5)\r\n    sb.click('button:contains(\"more\")')\r\n    sb.sleep(0.7)\r\n    sb.click_if_visible('button[data-testid=\"modal-close\"]')\r\n    sb.sleep(0.7)\r\n    print(\"Description: (%s)\\n\" % sb.get_text(\"h1\"))\r\n    print(sb.get_text('div[class*=\"Description_expanded\"]'))\r\n    sb.scroll_to_bottom()\r\n    sb.sleep(1.7)\r\n    sb.click_if_visible('button[data-testid=\"modal-close\"]')\r\n    sb.sleep(1.7)\r\n    summary = '[class*=\"ReviewSummary_summary\"] span'\r\n    print(\"\\nReview Summary:\\n\\n%s\" % sb.get_text(summary))\r\n    reviews = sb.select_all('p[class*=\"styles_reviewText\"]')\r\n    print(\"\\nReviews:\")\r\n    for review in reviews:\r\n        print(\"\\n\" + review.text)\r\n    folder = \"images_exported\"\r\n    file_name = \"thundering_brook_falls.png\"\r\n    sb.save_screenshot(file_name, folder, selector=\"body\")\r\n    print('\\n\"./%s/%s\" was saved!' % (folder, file_name))\r\n    folder = \"downloaded_files\"\r\n    file_name = \"thundering_brook_falls.html\"\r\n    sb.save_page_source(file_name, folder)\r\n    print('\"./%s/%s\" was saved!' % (folder, file_name))\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_turnstile.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = \"https://seleniumbase.io/apps/turnstile\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1)\r\n    sb.solve_captcha()\r\n    sb.assert_element(\"img#captcha-success\", timeout=3)\r\n    sb.set_messenger_theme(location=\"top_left\")\r\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=3)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_united.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n    url = \"https://www.united.com/en/us\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(3.5)\r\n    origin_input = 'input[placeholder=\"Origin\"]'\r\n    origin = \"JFK\"\r\n    destination_input = 'input[placeholder=\"Destination\"]'\r\n    destination = \"MCO\"\r\n    sb.wait_for_element(origin_input, timeout=20)\r\n    sb.sleep(0.5)\r\n    sb.click(origin_input)\r\n    sb.sleep(0.5)\r\n    sb.type(origin_input, origin)\r\n    sb.sleep(1.2)\r\n    sb.click('strong:contains(\"%s\")' % origin)\r\n    sb.sleep(0.6)\r\n    sb.click_if_visible('button[class*=\"__close--\"]')\r\n    sb.sleep(0.6)\r\n    sb.click(destination_input)\r\n    sb.sleep(0.5)\r\n    sb.type(destination_input, destination)\r\n    sb.sleep(1.2)\r\n    sb.click('strong:contains(\"%s\")' % destination)\r\n    sb.sleep(1.2)\r\n    sb.click('button[aria-label=\"Find flights\"]')\r\n    sb.sleep(4)\r\n    sb.click_if_visible('button[class*=\"__close--\"]')\r\n    sb.sleep(2)\r\n    flights = sb.find_elements('div[class*=\"CardContainer__block\"]')\r\n    print(\"**** Flights from %s to %s ****\" % (origin, destination))\r\n    print(\"    (\" + sb.get_text(\"h2.atm-c-heading\") + \")\")\r\n    if not flights:\r\n        print(\"* No flights found!\")\r\n    for flight in flights:\r\n        flight_info = flight.text.split(\" Destination\")[0]\r\n        part_1 = flight_info.split(\" Departing at\")[0]\r\n        part_2 = flight_info.split(\"Arriving at \")[-1]\r\n        part_2 = part_2.split(\" Duration\")[0]\r\n        part_3 = flight.text.split(\" Destination\")[-1].split(\" Aircraft\")[0]\r\n        parts = \"%s - %s %s\" % (part_1, part_2, part_3)\r\n        print(\"* \" + parts)\r\n    for category in [\"ECONOMY\"]:\r\n        prices = sb.find_elements('[aria-describedby=\"%s\"]' % category)\r\n        full_prices = []\r\n        for item in prices:\r\n            item_text = item.text\r\n            if \"Not available\" not in item.text:\r\n                full_prices.append(\"$%s\" % item.text.split(\"$\")[-1])\r\n            else:\r\n                full_prices.append(\"N/A\")\r\n        print(\"**** %s Prices:\" % category)\r\n        print(full_prices)\r\n    sb.scroll_down(50)\r\n    sb.sleep(1.5)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_walmart.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, ad_block=True) as sb:\r\n    url = \"https://www.walmart.com/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1.8)\r\n    continue_button = 'button:contains(\"Continue shopping\")'\r\n    if sb.is_element_visible(continue_button):\r\n        sb.cdp.gui_click_element(continue_button)\r\n        sb.sleep(0.6)\r\n    sb.click('input[aria-label=\"Search\"]')\r\n    sb.sleep(1.2)\r\n    search = \"Settlers of Catan Board Game\"\r\n    required_text = \"Catan\"\r\n    sb.press_keys('input[aria-label=\"Search\"]', search + \"\\n\")\r\n    sb.sleep(3.8)\r\n    if sb.is_element_visible(\"#px-captcha\"):\r\n        sb.cdp.gui_click_and_hold(\"#px-captcha\", 7.2)\r\n        sb.sleep(4.2)\r\n        if sb.is_element_visible(\"#px-captcha\"):\r\n            sb.cdp.gui_click_and_hold(\"#px-captcha\", 4.2)\r\n            sb.sleep(3.2)\r\n    sb.remove_elements('[data-testid=\"skyline-ad\"]')\r\n    sb.remove_elements('[data-testid=\"sba-container\"]')\r\n    print('*** Walmart Search for \"%s\":' % search)\r\n    print('    (Results must contain \"%s\".)' % required_text)\r\n    unique_item_text = []\r\n    sb.click_if_visible('[data-automation-id=\"sb-btn-close-mark\"]')\r\n    items = sb.find_elements('[data-item-id]')\r\n    for item in items:\r\n        if required_text in item.text:\r\n            description = item.querySelector(\r\n                '[data-automation-id=\"product-title\"]'\r\n            )\r\n            if description and description.text not in unique_item_text:\r\n                unique_item_text.append(description.text)\r\n                print(\"* \" + description.text)\r\n                price = item.querySelector(\r\n                    '[data-automation-id=\"product-price\"]'\r\n                )\r\n                if price:\r\n                    price_text = price.text\r\n                    price_text = price_text.split(\"current price Now \")[-1]\r\n                    price_text = price_text.split(\"current price \")[-1]\r\n                    price_text = price_text.split(\" \")[0]\r\n                    print(\"  (\" + price_text + \")\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_wsform.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\", incognito=True) as sb:\r\n    url = \"https://wsform.com/demo/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(2)\r\n    sb.scroll_into_view(\"div.grid\")\r\n    sb.uc_gui_click_captcha()\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_xhr_async.py",
    "content": "\"\"\"CDP.network.ResponseReceived with CDP.network.ResourceType.XHR.\"\"\"\r\nimport ast\r\nimport asyncio\r\nimport colorama\r\nimport mycdp\r\nimport sys\r\nimport time\r\nfrom seleniumbase import cdp_driver\r\n\r\nxhr_requests = []\r\nlast_xhr_request = None\r\nc1 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\r\nc2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\r\ncr = colorama.Style.RESET_ALL\r\nif \"linux\" in sys.platform:\r\n    c1 = c2 = cr = \"\"\r\n\r\n\r\ndef listenXHR(page):\r\n    async def handler(evt):\r\n        # Get AJAX requests\r\n        if evt.type_ is mycdp.network.ResourceType.XHR:\r\n            xhr_requests.append([evt.response.url, evt.request_id])\r\n            global last_xhr_request\r\n            last_xhr_request = time.time()\r\n    page.add_handler(mycdp.network.ResponseReceived, handler)\r\n\r\n\r\nasync def receiveXHR(page, requests):\r\n    responses = []\r\n    retries = 0\r\n    max_retries = 5\r\n    # Wait at least 2 seconds after last XHR request for more\r\n    while True:\r\n        if last_xhr_request is None or retries > max_retries:\r\n            break\r\n        if time.time() - last_xhr_request <= 2:\r\n            retries = retries + 1\r\n            time.sleep(2)\r\n            continue\r\n        else:\r\n            break\r\n    await page\r\n    # Loop through gathered requests and get response body\r\n    for request in requests:\r\n        try:\r\n            res = await page.send(mycdp.network.get_response_body(request[1]))\r\n            if res is None:\r\n                continue\r\n            responses.append({\r\n                \"url\": request[0],\r\n                \"body\": res[0],\r\n                \"is_base64\": res[1],\r\n            })\r\n        except Exception as e:\r\n            print(\"Error getting response:\", e)\r\n    return responses\r\n\r\n\r\nasync def crawl():\r\n    driver = await cdp_driver.start_async()\r\n    tab = await driver.get(\"about:blank\")\r\n    listenXHR(tab)\r\n\r\n    # Change url to something that makes ajax requests\r\n    tab = await driver.get(\"https://learn.microsoft.com/en-us/\")\r\n    time.sleep(1.5)\r\n    for i in range(18):\r\n        await tab.scroll_down(3)\r\n        time.sleep(0.02)\r\n\r\n    xhr_responses = await receiveXHR(tab, xhr_requests)\r\n    for response in xhr_responses:\r\n        print(c1 + \"*** ==> XHR Request URL <== ***\" + cr)\r\n        print(f'{response[\"url\"]}')\r\n        is_base64 = response[\"is_base64\"]\r\n        b64_data = \"Base64 encoded data\"\r\n        try:\r\n            headers = ast.literal_eval(response[\"body\"])[\"headers\"]\r\n            print(c2 + \"*** ==> XHR Response Headers <== ***\" + cr)\r\n            print(headers if not is_base64 else b64_data)\r\n        except Exception:\r\n            response_body = response[\"body\"]\r\n            print(c2 + \"*** ==> XHR Response Body <== ***\" + cr)\r\n            print(response_body if not is_base64 else b64_data)\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    print(\"<============= START: XHR Example =============>\")\r\n    asyncio.run(crawl())\r\n    print(\"<============== END: XHR Example ==============>\")\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_xhr_sb.py",
    "content": "\"\"\"CDP.network.ResponseReceived with CDP.network.ResourceType.XHR.\"\"\"\r\nimport ast\r\nimport colorama\r\nimport mycdp\r\nimport sys\r\nimport time\r\nfrom seleniumbase import SB\r\n\r\nxhr_requests = []\r\nlast_xhr_request = None\r\nc1 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\r\nc2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\r\ncr = colorama.Style.RESET_ALL\r\nif \"linux\" in sys.platform:\r\n    c1 = c2 = cr = \"\"\r\n\r\n\r\ndef listenXHR(page):\r\n    async def handler(evt):\r\n        # Get AJAX requests\r\n        if evt.type_ is mycdp.network.ResourceType.XHR:\r\n            xhr_requests.append([evt.response.url, evt.request_id])\r\n            global last_xhr_request\r\n            last_xhr_request = time.time()\r\n    page.add_handler(mycdp.network.ResponseReceived, handler)\r\n\r\n\r\nasync def receiveXHR(page, requests):\r\n    responses = []\r\n    retries = 0\r\n    max_retries = 5\r\n    # Wait at least 2 seconds after last XHR request for more\r\n    while True:\r\n        if last_xhr_request is None or retries > max_retries:\r\n            break\r\n        if time.time() - last_xhr_request <= 2:\r\n            retries = retries + 1\r\n            time.sleep(2)\r\n            continue\r\n        else:\r\n            break\r\n    await page\r\n    # Loop through gathered requests and get response body\r\n    for request in requests:\r\n        try:\r\n            res = await page.send(mycdp.network.get_response_body(request[1]))\r\n            if res is None:\r\n                continue\r\n            responses.append({\r\n                \"url\": request[0],\r\n                \"body\": res[0],\r\n                \"is_base64\": res[1],\r\n            })\r\n        except Exception as e:\r\n            print(\"Error getting response:\", e)\r\n    return responses\r\n\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    sb.activate_cdp_mode(\"about:blank\")\r\n    tab = sb.cdp.page\r\n    listenXHR(tab)\r\n\r\n    # Change url to something that makes ajax requests\r\n    sb.cdp.open(\"https://learn.microsoft.com/en-us/\")\r\n    time.sleep(1)\r\n    for i in range(9):\r\n        sb.cdp.scroll_down(6)\r\n\r\n    loop = sb.cdp.get_event_loop()\r\n    xhr_responses = loop.run_until_complete(receiveXHR(tab, xhr_requests))\r\n    for response in xhr_responses:\r\n        print(c1 + \"*** ==> XHR Request URL <== ***\" + cr)\r\n        print(f'{response[\"url\"]}')\r\n        is_base64 = response[\"is_base64\"]\r\n        b64_data = \"Base64 encoded data\"\r\n        try:\r\n            headers = ast.literal_eval(response[\"body\"])[\"headers\"]\r\n            print(c2 + \"*** ==> XHR Response Headers <== ***\" + cr)\r\n            print(headers if not is_base64 else b64_data)\r\n        except Exception:\r\n            response_body = response[\"body\"]\r\n            print(c2 + \"*** ==> XHR Response Body <== ***\" + cr)\r\n            print(response_body if not is_base64 else b64_data)\r\n"
  },
  {
    "path": "examples/cdp_mode/raw_xpath.py",
    "content": "\"\"\"Test that CDP Mode can autodetect and use XPath selectors.\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/demo_page\"\n    sb.activate_cdp_mode(url)\n    sb.cdp.highlight('//input[@id=\"myTextInput\"]')\n    sb.cdp.type('//*[@id=\"myTextInput\"]', \"XPath Test!\")\n    sb.sleep(0.5)\n    sb.cdp.highlight('//button[contains(text(),\"(Green)\")]')\n    sb.cdp.click('//button[starts-with(text(),\"Click Me\")]')\n    sb.cdp.assert_element('//button[contains(., \"Purple\")]')\n    sb.sleep(0.5)\n    sb.cdp.highlight(\"//table/tbody/tr/td/h3\")\n    sb.cdp.highlight(\"//table/tbody/tr[1]/td[2]/h2\")\n    sb.cdp.assert_text(\"SeleniumBase\", \"//table//h2\")\n"
  },
  {
    "path": "examples/cdp_mode/raw_zoro.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, locale=\"en\") as sb:\r\n    url = \"https://www.zoro.com/\"\r\n    sb.activate_cdp_mode()\r\n    sb.open(url)\r\n    sb.sleep(1.2)\r\n    search_box = \"input#searchInput\"\r\n    search = \"Flir Thermal Camera\"\r\n    required_text = \"Camera\"\r\n    if not sb.is_element_present(search_box):\r\n        sb.evaluate(\"window.location.reload();\")\r\n        sb.sleep(1.2)\r\n    sb.click(search_box)\r\n    sb.sleep(1.2)\r\n    sb.press_keys(search_box, search)\r\n    sb.sleep(0.6)\r\n    sb.click('button[data-za=\"searchButton\"]')\r\n    sb.sleep(3.2)\r\n    sb.wait_for_element('[data-za=\"product-cards-list\"]', timeout=5)\r\n    print('*** Zoro Search for \"%s\":' % search)\r\n    print('    (Results must contain \"%s\".)' % required_text)\r\n    unique_item_text = []\r\n    items = sb.find_elements('[data-za=\"search-product-card\"]')\r\n    for item in items:\r\n        if required_text in item.text:\r\n            description = item.querySelector(\"h2\")\r\n            if description and description.text not in unique_item_text:\r\n                unique_item_text.append(description.text)\r\n                print(\"* \" + description.text)\r\n                price = item.querySelector(\"div.price-main\")\r\n                if price:\r\n                    print(\"  (\" + price.text + \")\")\r\n"
  },
  {
    "path": "examples/chart_maker/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> 📊 ChartMaker 📶</h2>\n\n<p>SeleniumBase ChartMaker lets you use Python to generate HTML charts.</p>\n\n<a href=\"https://seleniumbase.io/other/chart_presentation.html\"><img width=\"480\" src=\"https://seleniumbase.github.io/cdn/gif/chart_pres.gif\" title=\"Chart Presentation\"></a><br>\n\n([Click to see a presentation with multiple charts](https://seleniumbase.io/other/chart_presentation.html))\n\nHere's how to run a simple pie chart presentation from [GitHub => seleniumbase/SeleniumBase/examples/chart_maker](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/chart_maker):\n\n```zsh\ncd examples/chart_maker\npytest my_chart.py\n```\n\nHere's the code for that pie chart presentation ([GitHub => seleniumbase/SeleniumBase/examples/chart_maker/my_chart.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/chart_maker/my_chart.py)):\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass MyChartMakerClass(BaseCase):\n    def test_chart_maker(self):\n        self.create_presentation()\n        self.create_pie_chart(title=\"Automated Tests\")\n        self.add_data_point(\"Passed\", 7, color=\"#95d96f\")\n        self.add_data_point(\"Untested\", 2, color=\"#eaeaea\")\n        self.add_data_point(\"Failed\", 1, color=\"#f1888f\")\n        self.add_slide(\"<p>Pie Chart</p>\" + self.extract_chart())\n        self.begin_presentation(filename=\"my_chart.html\")\n```\n\nHere's how to run an example presentation with multiple charts:\n\n```zsh\ncd examples/chart_maker\npytest chart_presentation.py\n```\n\nHere are screenshots from the examples:\n\n<a href=\"https://seleniumbase.github.io/other/chart_presentation.html\"><img width=\"500\" src=\"https://seleniumbase.github.io/other/sample_pie_chart.png\" title=\"Screenshot\"></a><br>\n\n<a href=\"https://seleniumbase.github.io/other/sample_column_chart.png\"><img width=\"500\" src=\"https://seleniumbase.github.io/other/sample_column_chart.png\" title=\"Screenshot\"></a><br>\n\n<a href=\"https://seleniumbase.github.io/other/sample_bar_chart.png\"><img width=\"500\" src=\"https://seleniumbase.github.io/other/sample_bar_chart.png\" title=\"Screenshot\"></a><br>\n\n<a href=\"https://seleniumbase.github.io/other/sample_line_chart.png\"><img width=\"500\" src=\"https://seleniumbase.github.io/other/sample_line_chart.png\" title=\"Screenshot\"></a><br>\n\n<a href=\"https://seleniumbase.github.io/other/sample_area_chart.png\"><img width=\"500\" src=\"https://seleniumbase.github.io/other/sample_area_chart.png\" title=\"Screenshot\"></a><br>\n\n<a href=\"https://seleniumbase.github.io/other/multi_series_chart.png\"><img width=\"500\" src=\"https://seleniumbase.github.io/other/multi_series_chart.png\" title=\"Screenshot\"></a><br>\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Here's a line chart example:</h3>\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass MyChartMakerClass(BaseCase):\n    def test_chart_maker(self):\n        self.create_presentation()\n        self.create_line_chart(\n            title=\"Time Outside\", subtitle=\"Last Week\", unit=\"Minutes\")\n        self.add_data_point(\"Sun\", 5)\n        self.add_data_point(\"Mon\", 10)\n        self.add_data_point(\"Tue\", 20)\n        self.add_data_point(\"Wed\", 40)\n        self.add_data_point(\"Thu\", 80)\n        self.add_data_point(\"Fri\", 65)\n        self.add_data_point(\"Sat\", 50)\n        self.add_slide(\"<p>Line Chart</p>\" + self.extract_chart())\n        self.begin_presentation(filename=\"line_chart.html\", interval=8)\n```\n\nThis example is from [test_line_chart.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/chart_maker/test_line_chart.py), which you can run from the ``examples/chart_maker`` folder with the following command:\n\n```zsh\npytest test_line_chart.py\n```\n\nBecause that presentation above has an ``interval`` set to ``8``, it will automatically advance to the next slide after 8 seconds. (Or exit if there are no more slides.)\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> For a more advanced example (multiple charts in a presentation):</h3>\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass MyChartMakerClass(BaseCase):\n    def test_chart_maker_presentation(self):\n        self.create_presentation(theme=\"sky\")\n\n        self.create_pie_chart(title=\"Automated Tests\")\n        self.add_data_point(\"Passed\", 7, color=\"#95d96f\")\n        self.add_data_point(\"Untested\", 2, color=\"#eaeaea\")\n        self.add_data_point(\"Failed\", 1, color=\"#f1888f\")\n        self.add_slide(\"<p>Pie Chart</p>\" + self.extract_chart())\n\n        self.create_bar_chart(title=\"Language\")\n        self.add_data_point(\"Python\", 33, color=\"Orange\")\n        self.add_data_point(\"JavaScript\", 27, color=\"Teal\")\n        self.add_data_point(\"HTML + CSS\", 21, color=\"Purple\")\n        self.add_slide(\"<p>Bar Chart</p>\" + self.extract_chart())\n\n        self.create_column_chart(title=\"Colors\")\n        self.add_data_point(\"Red\", 10, color=\"Red\")\n        self.add_data_point(\"Green\", 25, color=\"Green\")\n        self.add_data_point(\"Blue\", 15, color=\"Blue\")\n        self.add_slide(\"<p>Column Chart</p>\" + self.extract_chart())\n\n        self.create_line_chart(title=\"Last Week's Data\")\n        self.add_data_point(\"Sun\", 5)\n        self.add_data_point(\"Mon\", 10)\n        self.add_data_point(\"Tue\", 20)\n        self.add_data_point(\"Wed\", 40)\n        self.add_data_point(\"Thu\", 80)\n        self.add_data_point(\"Fri\", 65)\n        self.add_data_point(\"Sat\", 50)\n        self.add_slide(\"<p>Line Chart</p>\" + self.extract_chart())\n\n        self.begin_presentation(filename=\"chart_presentation.html\")\n```\n\nHere's how to run that example:\n\n```zsh\ncd examples/chart_maker\npytest chart_presentation.py\n```\n\n(Press the Right Arrow to advance to the next slide in that chart presentation)\n\n([Click to see a live example of that presentation](https://seleniumbase.io/other/chart_presentation.html))\n\nMulti-Series charts can also be created. Try the available examples to learn more.\n\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> ChartMaker API</h2>\n\n```python\nself.create_pie_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, libs=True):\n\"\"\" Creates a JavaScript pie chart using \"HighCharts\".\n    @Params\n    chart_name - If creating multiple charts,\n                 use this to select which one.\n    title - The title displayed for the chart.\n    subtitle - The subtitle displayed for the chart.\n    data_name - The series name. Useful for multi-series charts.\n                If no data_name, will default to using \"Series 1\".\n    unit - The description label given to the chart's y-axis values.\n    libs - The option to include Chart libraries (JS and CSS files).\n           Should be set to True (default) for the first time creating\n           a chart on a web page. If creating multiple charts on the\n           same web page, you won't need to re-import the libraries\n           when creating additional charts.\n    labels - If True, displays labels on the chart for data points.\n    legend - If True, displays the data point legend on the chart.\n\"\"\"\n```\n\n```python\nself.create_bar_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, libs=True):\n\"\"\" Creates a JavaScript bar chart using \"HighCharts\".\n    @Params\n    chart_name - If creating multiple charts,\n                 use this to select which one.\n    title - The title displayed for the chart.\n    subtitle - The subtitle displayed for the chart.\n    data_name - The series name. Useful for multi-series charts.\n                If no data_name, will default to using \"Series 1\".\n    unit - The description label given to the chart's y-axis values.\n    libs - The option to include Chart libraries (JS and CSS files).\n           Should be set to True (default) for the first time creating\n           a chart on a web page. If creating multiple charts on the\n           same web page, you won't need to re-import the libraries\n           when creating additional charts.\n    labels - If True, displays labels on the chart for data points.\n    legend - If True, displays the data point legend on the chart.\n\"\"\"\n```\n\n```python\nself.create_column_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, libs=True):\n\"\"\" Creates a JavaScript column chart using \"HighCharts\".\n    @Params\n    chart_name - If creating multiple charts,\n                 use this to select which one.\n    title - The title displayed for the chart.\n    subtitle - The subtitle displayed for the chart.\n    data_name - The series name. Useful for multi-series charts.\n                If no data_name, will default to using \"Series 1\".\n    unit - The description label given to the chart's y-axis values.\n    libs - The option to include Chart libraries (JS and CSS files).\n           Should be set to True (default) for the first time creating\n           a chart on a web page. If creating multiple charts on the\n           same web page, you won't need to re-import the libraries\n           when creating additional charts.\n    labels - If True, displays labels on the chart for data points.\n    legend - If True, displays the data point legend on the chart.\n\"\"\"\n```\n\n```python\nself.create_line_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, zero=False, libs=True):\n\"\"\" Creates a JavaScript line chart using \"HighCharts\".\n    @Params\n    chart_name - If creating multiple charts,\n                 use this to select which one.\n    title - The title displayed for the chart.\n    subtitle - The subtitle displayed for the chart.\n    data_name - The series name. Useful for multi-series charts.\n                If no data_name, will default to using \"Series 1\".\n    unit - The description label given to the chart's y-axis values.\n    zero - If True, the y-axis always starts at 0. (Default: False).\n    libs - The option to include Chart libraries (JS and CSS files).\n           Should be set to True (default) for the first time creating\n           a chart on a web page. If creating multiple charts on the\n           same web page, you won't need to re-import the libraries\n           when creating additional charts.\n    labels - If True, displays labels on the chart for data points.\n    legend - If True, displays the data point legend on the chart.\n\"\"\"\n```\n\n```python\nself.create_area_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, zero=False, libs=True):\n\"\"\" Creates a JavaScript area chart using \"HighCharts\".\n    @Params\n    chart_name - If creating multiple charts,\n                 use this to select which one.\n    title - The title displayed for the chart.\n    subtitle - The subtitle displayed for the chart.\n    data_name - The series name. Useful for multi-series charts.\n                If no data_name, will default to using \"Series 1\".\n    unit - The description label given to the chart's y-axis values.\n    zero - If True, the y-axis always starts at 0. (Default: False).\n    libs - The option to include Chart libraries (JS and CSS files).\n           Should be set to True (default) for the first time creating\n           a chart on a web page. If creating multiple charts on the\n           same web page, you won't need to re-import the libraries\n           when creating additional charts.\n    labels - If True, displays labels on the chart for data points.\n    legend - If True, displays the data point legend on the chart.\n\"\"\"\n```\n\nIf creating multiple charts at the same time, you can pass the ``chart_name`` parameter to distinguish between different charts.\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Adding a data point to a chart:</h3>\n\n```python\nself.add_data_point(label, value, color=None, chart_name=None):\n\"\"\" Add a data point to a SeleniumBase-generated chart.\n    @Params\n    label - The label name for the data point.\n    value - The numeric value of the data point.\n    color - The HTML color of the data point.\n            Can be an RGB color. Eg: \"#55ACDC\".\n            Can also be a named color. Eg: \"Teal\".\n    chart_name - If creating multiple charts,\n                 use this to select which one.\n\"\"\"\n```\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Adding a new data series to an existing chart:</h3>\n\n```python\nself.add_series_to_chart(self, data_name=None, chart_name=None):\n\"\"\" Add a new data series to an existing chart.\n    This allows charts to have multiple data sets.\n    @Params\n    data_name - Set the series name. Useful for multi-series charts.\n    chart_name - If creating multiple charts,\n                 use this to select which one.\n\"\"\"\n```\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Saving a chart to a file:</h3>\n\n```python\nself.save_chart(chart_name=None, filename=None):\n\"\"\" Saves a SeleniumBase-generated chart to a file for later use.\n    @Params\n    chart_name - If creating multiple charts at the same time,\n                 use this to select the one you wish to use.\n    filename - The name of the HTML file that you wish to\n               save the chart to. (filename must end in \".html\")\n\"\"\"\n```\n\nThe full HTML of the chart is saved to the ``saved_charts/`` folder.\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Extracting the HTML of a chart:</h3>\n\n```python\nself.extract_chart(chart_name=None):\n\"\"\" Extracts the HTML from a SeleniumBase-generated chart.\n    @Params\n    chart_name - If creating multiple charts at the same time,\n                 use this to select the one you wish to use.\n\"\"\"\n```\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Displaying a chart in the browser window:</h3>\n\n```python\nself.display_chart(chart_name=None, filename=None):\n\"\"\" Displays a SeleniumBase-generated chart in the browser window.\n    @Params\n    chart_name - If creating multiple charts at the same time,\n                 use this to select the one you wish to use.\n    filename - The name of the HTML file that you wish to\n               save the chart to. (filename must end in \".html\")\n    interval - The delay time for auto-advancing charts. (in seconds)\n               If set to 0 (default), auto-advancing is disabled.\n\"\"\"\n```\n\nAll methods have the optional ``chart_name`` argument, which is only needed when storing multiple charts at the same time.\n\n--------\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=TMQx3FLWvUY\"><img src=\"http://img.youtube.com/vi/TMQx3FLWvUY/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"285\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=TMQx3FLWvUY\">Video: Pie Charts on Pi Day</a></b>)</p>\n"
  },
  {
    "path": "examples/chart_maker/chart_presentation.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ChartMakerPresentation(BaseCase):\n    def test_chart_maker_presentation(self):\n        self.create_presentation(theme=\"sky\", transition=\"zoom\")\n\n        self.create_pie_chart(title=\"Automated Tests\")\n        self.add_data_point(\"Passed\", 7, color=\"#95d96f\")\n        self.add_data_point(\"Untested\", 2, color=\"#eaeaea\")\n        self.add_data_point(\"Failed\", 1, color=\"#f1888f\")\n        self.add_slide(\"<p>Pie Chart</p>\" + self.extract_chart())\n\n        self.create_bar_chart(title=\"Language\", legend=False)\n        self.add_data_point(\"Python\", 33, color=\"Orange\")\n        self.add_data_point(\"JavaScript\", 27, color=\"Teal\")\n        self.add_data_point(\"HTML + CSS\", 21, color=\"Purple\")\n        self.add_slide(\"<p>Bar Chart</p>\" + self.extract_chart())\n\n        self.create_column_chart(title=\"Colors\", legend=False)\n        self.add_data_point(\"Red\", 10, color=\"Red\")\n        self.add_data_point(\"Green\", 25, color=\"Green\")\n        self.add_data_point(\"Blue\", 15, color=\"Blue\")\n        self.add_slide(\"<p>Column Chart</p>\" + self.extract_chart())\n\n        self.create_line_chart(title=\"Last Week's Data\")\n        self.add_data_point(\"Sun\", 5)\n        self.add_data_point(\"Mon\", 10)\n        self.add_data_point(\"Tue\", 20)\n        self.add_data_point(\"Wed\", 40)\n        self.add_data_point(\"Thu\", 80)\n        self.add_data_point(\"Fri\", 65)\n        self.add_data_point(\"Sat\", 50)\n        self.add_slide(\"<p>Line Chart</p>\" + self.extract_chart())\n\n        self.begin_presentation(filename=\"chart_presentation.html\")\n"
  },
  {
    "path": "examples/chart_maker/my_chart.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyChartMakerClass(BaseCase):\n    def test_chart_maker(self):\n        self.create_presentation()\n        self.create_pie_chart(title=\"Automated Tests\")\n        self.add_data_point(\"Passed\", 7, color=\"#95d96f\")\n        self.add_data_point(\"Untested\", 2, color=\"#eaeaea\")\n        self.add_data_point(\"Failed\", 1, color=\"#f1888f\")\n        self.add_slide(\"<p>Pie Chart</p>\" + self.extract_chart())\n        self.begin_presentation(filename=\"my_chart.html\")\n"
  },
  {
    "path": "examples/chart_maker/pie_charts.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass PieCharts(BaseCase):\n    def test_pie_charts(self):\n        self.create_presentation(theme=\"serif\", transition=\"convex\")\n\n        self.create_pie_chart(labels=False)\n        self.add_data_point(\"Meringue Cream\", 3, color=\"#f1eeea\")\n        self.add_data_point(\"Lemon Filling\", 3, color=\"#e9d655\")\n        self.add_data_point(\"Graham Cracker Crust\", 1, color=\"#9d5b34\")\n        self.add_slide(\"<p>Lemon Meringue Pie</p>\" + self.extract_chart())\n\n        self.create_pie_chart(labels=False)\n        self.add_data_point(\"Blueberries\", 1, color=\"#5c81b7\")\n        self.add_data_point(\"Blueberry Filling\", 2, color=\"#12405e\")\n        self.add_data_point(\"Golden Brown Crust\", 1, color=\"#cd7b54\")\n        self.add_slide(\"<p>Blueberry Pie</p>\" + self.extract_chart())\n\n        self.create_pie_chart(labels=False)\n        self.add_data_point(\"Strawberries\", 1, color=\"#ff282c\")\n        self.add_data_point(\"Kiwis\", 1, color=\"#a9c208\")\n        self.add_data_point(\"Apricots\", 1, color=\"#f47a14\")\n        self.add_data_point(\"Raspberries\", 1, color=\"#b10019\")\n        self.add_data_point(\"Black Berries\", 1, color=\"#44001e\")\n        self.add_data_point(\"Blueberries\", 1, color=\"#5c81b7\")\n        self.add_data_point(\"Custard\", 3, color=\"#eee896\")\n        self.add_data_point(\"Golden Crust\", 4, color=\"#dca422\")\n        self.add_slide(\"<p>Fruit Tart Pie</p>\" + self.extract_chart())\n\n        self.create_pie_chart(labels=False)\n        self.add_data_point(\"Apple Crust\", 4, color=\"#b66327\")\n        self.add_data_point(\"Apple Filling\", 5, color=\"#c5903e\")\n        self.add_data_point(\"Cinnamon\", 1, color=\"#76210d\")\n        self.add_data_point(\"Whipped Cream\", 2, color=\"#f2f2f2\")\n        self.add_slide(\"<p>Apple Pie</p>\" + self.extract_chart())\n\n        self.create_pie_chart(labels=False)\n        self.add_data_point(\"Sponge Cake\", 4, color=\"#e0d5a0\")\n        self.add_data_point(\"Custard\", 3, color=\"#eee896\")\n        self.add_data_point(\"Chocolate\", 1, color=\"#5c3625\")\n        self.add_slide(\"<p>Boston Cream Pie</p>\" + self.extract_chart())\n\n        self.begin_presentation(filename=\"pie_charts.html\")\n"
  },
  {
    "path": "examples/chart_maker/test_area_chart.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyChartMakerClass(BaseCase):\n    def test_area_chart(self):\n        self.create_presentation(theme=\"moon\")\n        self.create_area_chart(\n            title=\"Time Outside\", subtitle=\"Last Week\", unit=\"Minutes\"\n        )\n        self.add_data_point(\"Sun\", 5)\n        self.add_data_point(\"Mon\", 10)\n        self.add_data_point(\"Tue\", 20)\n        self.add_data_point(\"Wed\", 40)\n        self.add_data_point(\"Thu\", 80)\n        self.add_data_point(\"Fri\", 65)\n        self.add_data_point(\"Sat\", 50)\n        self.add_slide(\"<p><b>Area Chart</b></p>\" + self.extract_chart())\n        self.begin_presentation(filename=\"line_chart.html\", interval=4)\n"
  },
  {
    "path": "examples/chart_maker/test_display_chart.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyChartMakerClass(BaseCase):\n    def test_display_chart(self):\n        self.create_pie_chart(title=\"Pie Chart\")\n        self.add_data_point(\"Passed\", 7, color=\"#95d96f\")\n        self.add_data_point(\"Untested\", 2, color=\"#eaeaea\")\n        self.add_data_point(\"Failed\", 1, color=\"#f1888f\")\n        self.display_chart(filename=\"pie_chart.html\", interval=2.5)\n\n        self.create_bar_chart(title=\"Bar Chart\", legend=False)\n        self.add_data_point(\"Python\", 33, color=\"Orange\")\n        self.add_data_point(\"JavaScript\", 27, color=\"Teal\")\n        self.add_data_point(\"HTML + CSS\", 21, color=\"Purple\")\n        self.display_chart(filename=\"bar_chart.html\", interval=2.5)\n\n        self.create_column_chart(title=\"Column Chart\", legend=False)\n        self.add_data_point(\"Red\", 10, color=\"Red\")\n        self.add_data_point(\"Green\", 25, color=\"Green\")\n        self.add_data_point(\"Blue\", 15, color=\"Blue\")\n        self.display_chart(filename=\"column_chart.html\", interval=2.5)\n\n        self.create_line_chart(title=\"Line Chart\")\n        self.add_data_point(\"Sun\", 5)\n        self.add_data_point(\"Mon\", 10)\n        self.add_data_point(\"Tue\", 20)\n        self.add_data_point(\"Wed\", 40)\n        self.add_data_point(\"Thu\", 80)\n        self.add_data_point(\"Fri\", 65)\n        self.add_data_point(\"Sat\", 50)\n        self.display_chart(filename=\"line_chart.html\", interval=2.5)\n"
  },
  {
    "path": "examples/chart_maker/test_line_chart.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyChartMakerClass(BaseCase):\n    def test_line_chart(self):\n        self.create_presentation()\n        self.create_line_chart(\n            title=\"Time Outside\", subtitle=\"Last Week\", unit=\"Minutes\"\n        )\n        self.add_data_point(\"Sun\", 5)\n        self.add_data_point(\"Mon\", 10)\n        self.add_data_point(\"Tue\", 20)\n        self.add_data_point(\"Wed\", 40)\n        self.add_data_point(\"Thu\", 80)\n        self.add_data_point(\"Fri\", 65)\n        self.add_data_point(\"Sat\", 50)\n        self.add_slide(\"<p>Line Chart</p>\" + self.extract_chart())\n        self.begin_presentation(filename=\"line_chart.html\", interval=4)\n"
  },
  {
    "path": "examples/chart_maker/test_multi_series.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyChartMakerClass(BaseCase):\n    def test_multi_series(self):\n        self.create_presentation(theme=\"league\")\n\n        self.create_line_chart(\n            title=\"Fruit Sold Last Week\", data_name=\"Apples\", unit=\"Count\"\n        )\n        self.add_data_point(\"Sun\", 33)\n        self.add_data_point(\"Mon\", 16)\n        self.add_data_point(\"Tue\", 19)\n        self.add_data_point(\"Wed\", 28)\n        self.add_data_point(\"Thu\", 20)\n        self.add_data_point(\"Fri\", 30)\n        self.add_data_point(\"Sat\", 36)\n\n        self.add_series_to_chart(data_name=\"Oranges\")\n        self.add_data_point(\"Sun\", 22)\n        self.add_data_point(\"Mon\", 27)\n        self.add_data_point(\"Tue\", 23)\n        self.add_data_point(\"Wed\", 21)\n        self.add_data_point(\"Thu\", 26)\n        self.add_data_point(\"Fri\", 17)\n        self.add_data_point(\"Sat\", 25)\n\n        self.add_series_to_chart(data_name=\"Strawberries\")\n        self.add_data_point(\"Sun\", 41)\n        self.add_data_point(\"Mon\", 32)\n        self.add_data_point(\"Tue\", 38)\n        self.add_data_point(\"Wed\", 33)\n        self.add_data_point(\"Thu\", 31)\n        self.add_data_point(\"Fri\", 42)\n        self.add_data_point(\"Sat\", 40)\n\n        self.add_series_to_chart(data_name=\"Cherries\")\n        self.add_data_point(\"Sun\", 28)\n        self.add_data_point(\"Mon\", 37)\n        self.add_data_point(\"Tue\", 29)\n        self.add_data_point(\"Wed\", 24)\n        self.add_data_point(\"Thu\", 34)\n        self.add_data_point(\"Fri\", 26)\n        self.add_data_point(\"Sat\", 31)\n\n        self.add_slide(\"<p>Multi-Series Line Chart</p>\" + self.extract_chart())\n        self.begin_presentation(filename=\"multi_series_chart.html\", interval=4)\n"
  },
  {
    "path": "examples/chart_maker/test_save_chart.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyChartMakerClass(BaseCase):\n    def test_save_chart(self):\n        self.create_pie_chart(title=\"Pie Chart\")\n        self.add_data_point(\"Passed\", 7, color=\"#95d96f\")\n        self.add_data_point(\"Untested\", 2, color=\"#eaeaea\")\n        self.add_data_point(\"Failed\", 1, color=\"#f1888f\")\n        self.save_chart(filename=\"pie_chart.html\")\n\n        self.create_bar_chart(title=\"Bar Chart\")\n        self.add_data_point(\"Python\", 33, color=\"Orange\")\n        self.add_data_point(\"JavaScript\", 27, color=\"Teal\")\n        self.add_data_point(\"HTML + CSS\", 21, color=\"Purple\")\n        self.save_chart(filename=\"bar_chart.html\")\n\n        self.create_column_chart(title=\"Column Chart\")\n        self.add_data_point(\"Red\", 10, color=\"Red\")\n        self.add_data_point(\"Green\", 25, color=\"Green\")\n        self.add_data_point(\"Blue\", 15, color=\"Blue\")\n        self.save_chart(filename=\"column_chart.html\")\n\n        self.create_line_chart(title=\"Line Chart\")\n        self.add_data_point(\"Sun\", 5)\n        self.add_data_point(\"Mon\", 10)\n        self.add_data_point(\"Tue\", 20)\n        self.add_data_point(\"Wed\", 40)\n        self.add_data_point(\"Thu\", 80)\n        self.add_data_point(\"Fri\", 65)\n        self.add_data_point(\"Sat\", 50)\n        self.save_chart(filename=\"line_chart.html\")\n"
  },
  {
    "path": "examples/coffee_cart_tests.py",
    "content": "\"\"\"Use SeleniumBase to test the Coffee Cart App.\"\"\"\r\nfrom parameterized import parameterized\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass CoffeeCartTests(BaseCase):\r\n    def test_1_verify_nav_link_to_coffee_cart(self):\r\n        self.open(\"https://seleniumbase.io/help_docs/customizing_test_runs/\")\r\n        self.js_click('nav a:contains(\"Coffee Cart\")')\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.assert_element('h4:contains(\"Espresso\")')\r\n\r\n    def test_buy_one_cappuccino(self):\r\n        self.open(\"https://seleniumbase.io/coffee/\")\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.click('div[data-test=\"Cappuccino\"]')\r\n        self.assert_exact_text(\"cart (1)\", 'a[aria-label=\"Cart page\"]')\r\n        self.click('a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $19.00\", 'button[data-test=\"checkout\"]')\r\n        self.click('button[data-test=\"checkout\"]')\r\n        self.type(\"input#name\", \"Selenium Coffee\")\r\n        self.type(\"input#email\", \"test@test.test\")\r\n        self.click(\"button#submit-payment\")\r\n        self.assert_text(\"Thanks for your purchase.\", \"div#app div.success\")\r\n        self.assert_exact_text(\"cart (0)\", 'a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $0.00\", 'button[data-test=\"checkout\"]')\r\n\r\n    @parameterized.expand([[False], [True]])\r\n    def test_coffee_promo_with_preview(self, accept_promo):\r\n        self.open(\"https://seleniumbase.io/coffee/\")\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.click('div[data-test=\"Espresso\"]')\r\n        self.click('div[data-test=\"Americano\"]')\r\n        self.click('div[data-test=\"Cafe_Latte\"]')\r\n        self.assert_exact_text(\"cart (3)\", 'a[aria-label=\"Cart page\"]')\r\n        promo = False\r\n        total_string = \"Total: $33.00\"\r\n        if self.is_element_visible(\"div.promo\"):\r\n            self.assert_text(\"Get an extra cup of Mocha for $4.\", \"div.promo\")\r\n            if accept_promo:\r\n                self.click(\"div.promo button.yes\")\r\n                self.assert_exact_text(\"cart (4)\", 'a[aria-label=\"Cart page\"]')\r\n                promo = True\r\n                total_string = \"Total: $37.00\"\r\n            else:\r\n                self.click(\"div.promo button.no\")\r\n        checkout_button = 'button[data-test=\"checkout\"]'\r\n        if promo and not self.browser == \"safari\":\r\n            self.hover(checkout_button)\r\n            if not self.is_element_visible(\"ul.cart-preview\"):\r\n                self.highlight(checkout_button)\r\n                self.post_message(\"STOP moving the mouse!<br />Hover blocked!\")\r\n                self.hover(checkout_button)\r\n            self.assert_text(\"(Discounted) Mocha\", \"ul.cart-preview\")\r\n        self.assert_exact_text(total_string, checkout_button)\r\n        self.click(checkout_button)\r\n        self.type(\"input#name\", \"Selenium Coffee\")\r\n        self.type(\"input#email\", \"test@test.test\")\r\n        self.click(\"button#submit-payment\")\r\n        self.assert_text(\"Thanks for your purchase.\", \"div#app div.success\")\r\n\r\n    def test_context_click_add_coffee(self):\r\n        self.open(\"https://seleniumbase.io/coffee/\")\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.context_click('div[data-test=\"Espresso_Macchiato\"]')\r\n        self.click('form button:contains(\"Yes\")')\r\n        self.assert_exact_text(\"cart (1)\", 'a[aria-label=\"Cart page\"]')\r\n        self.click('a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $12.00\", 'button[data-test=\"checkout\"]')\r\n        self.click('button[data-test=\"checkout\"]')\r\n        self.type(\"input#name\", \"Selenium Coffee\")\r\n        self.type(\"input#email\", \"test@test.test\")\r\n        self.click(\"button#submit-payment\")\r\n        self.assert_text(\"Thanks for your purchase.\", \"div#app div.success\")\r\n\r\n    def test_remove_added_coffee(self):\r\n        self.open(\"https://seleniumbase.io/coffee/\")\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.assert_exact_text(\"cart (0)\", 'a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $0.00\", \"button.pay\")\r\n        self.wait_for_element('div[class=\"cup-body\"]')\r\n        self.click_visible_elements('div[class=\"cup-body\"]', limit=6)\r\n        self.assert_exact_text(\"cart (6)\", 'a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $74.00\", 'button[data-test=\"checkout\"]')\r\n        self.click('a[aria-label=\"Cart page\"]')\r\n        self.click_visible_elements(\"button.delete\")\r\n        self.assert_text(\"No coffee, go add some.\", \"div#app\")\r\n        self.click('a[aria-label=\"Menu page\"]')\r\n        self.assert_exact_text(\"cart (0)\", 'a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $0.00\", 'button[data-test=\"checkout\"]')\r\n"
  },
  {
    "path": "examples/custom_settings.py",
    "content": "\"\"\"\nTo override default settings stored in seleniumbase/config/settings.py,\nchange the values here and add \"--settings=custom_settings.py\" when running.\n\"\"\"\n\n# Default timeout values for waiting for page elements to appear.\nMINI_TIMEOUT = 2\nSMALL_TIMEOUT = 7\nLARGE_TIMEOUT = 10\nEXTREME_TIMEOUT = 30\n\n# Default page load timeout.\n# In global Selenium settings, this value is set to 300 seconds by default.\n# When the timeout is exceeded: \"Timed out receiving message from renderer\"\nPAGE_LOAD_TIMEOUT = 120\n\n# Default page load strategy.\n# [\"normal\", \"eager\", \"none\"]\n# Selenium default = \"normal\"\nPAGE_LOAD_STRATEGY = \"normal\"\n\n# If True, existing logs from past test runs will be saved and take up space.\n# If False, only the logs from the most recent test run will be saved locally.\n# You can also archive existing logs on the command line with: \"--archive_logs\"\nARCHIVE_EXISTING_LOGS = False\n\n# If True, existing downloads from past runs will be saved and take up space.\n# If False, only the downloads from the most recent run will be saved locally.\nARCHIVE_EXISTING_DOWNLOADS = False\n\n# If True, the last page screenshot will include the <body> and background.\n# If False, the last page screenshot will only include the <body> section.\n# Depending on the screen size, including a background could make the <body>\n# appear very small in the screenshot, which may require manually zooming in\n# on the <body> to see page details if you decide to include the background.\nSCREENSHOT_WITH_BACKGROUND = False\n\n# If True, switch to new tabs automatically if a click opens a new one.\n# (Only happens if the initial tab is still on same URL as before.)\nSWITCH_TO_NEW_TABS_ON_CLICK = True\n\n\"\"\"\nThese methods add JS waits, such as self.wait_for_ready_state_complete(),\nwhich waits for document.readyState to be \"complete\" after Selenium actions.\n\"\"\"\n# Called after self.open(URL), NOT driver.get(URL)\nWAIT_FOR_RSC_ON_PAGE_LOADS = True\n# Called after self.click(selector), NOT element.click()\nWAIT_FOR_RSC_ON_CLICKS = False\n# Wait for AngularJS calls to complete after various browser actions.\nWAIT_FOR_ANGULARJS = True\n# Skip ALL calls to wait_for_ready_state_complete() and wait_for_angularjs().\nSKIP_JS_WAITS = False\n\n# Changing the default behavior of Demo Mode. Activate with: --demo_mode\nDEFAULT_DEMO_MODE_TIMEOUT = 0.5\nHIGHLIGHTS = 4\nDEFAULT_MESSAGE_DURATION = 2.55\n\n# Disabling the Content Security Policy of the browser by default.\nDISABLE_CSP_ON_FIREFOX = True\nDISABLE_CSP_ON_CHROME = False\n\n# If True and --proxy=IP_ADDRESS:PORT is invalid, then error immediately.\nRAISE_INVALID_PROXY_STRING_EXCEPTION = True\n\n# Default browser coordinates when opening new windows for tests.\nWINDOW_START_X = 20\nWINDOW_START_Y = 54\n\n# Default browser resolutions when opening new windows for tests.\n# (Headless resolutions take priority, and include all browsers.)\n# (Firefox starts maximized by default when running in GUI Mode.)\nCHROME_START_WIDTH = 1250\nCHROME_START_HEIGHT = 840\nHEADLESS_START_WIDTH = 1440\nHEADLESS_START_HEIGHT = 1880\n\n# If True, hides messages related to downloading drivers.\n# If False, you'll see details about downloading drivers.\n# (This only affects driver downloads coming from tests.)\n# (Eg. Using \"sbase get chromedriver\" won't hide output.)\nHIDE_DRIVER_DOWNLOADS = False\n\n# Changing the default behavior of MasterQA Mode.\nMASTERQA_DEFAULT_VALIDATION_MESSAGE = \"Does the page look good?\"\nMASTERQA_WAIT_TIME_BEFORE_VERIFY = 0.5\nMASTERQA_START_IN_FULL_SCREEN_MODE = False\nMASTERQA_MAX_IDLE_TIME_BEFORE_QUIT = 600\n\n# Google Authenticator\n# (For 2-factor authentication using a time-based one-time password algorithm)\n# (See https://github.com/pyotp/pyotp and https://pypi.org/project/pyotp/ )\n# (Also works with Authy and other compatible apps.)\n# Usage: \"self.get_google_auth_password()\"  (output based on timestamp)\n# Usage with override: \"self.get_google_auth_password(totp_key=TOTP_KEY)\"\nTOTP_KEY = \"base32secretABCD\"\n\n# MySQL DB Credentials\n# (For saving data from tests to a MySQL DB)\n# Usage: \"--with-db_reporting\"\nDB_HOST = \"127.0.0.1\"\nDB_PORT = 3306\nDB_USERNAME = \"root\"\nDB_PASSWORD = \"test\"\nDB_SCHEMA = \"test_db\"\n\n# Amazon S3 Bucket Credentials\n# (For saving screenshots and other log files from tests)\n# (Bucket names are unique across all existing bucket names in Amazon S3)\n# Usage: \"--with-s3_logging\"\nS3_LOG_BUCKET = \"[S3 BUCKET NAME]\"\nS3_BUCKET_URL = \"https://s3.amazonaws.com/[S3 BUCKET NAME]/\"\nS3_SELENIUM_ACCESS_KEY = \"[S3 ACCESS KEY]\"\nS3_SELENIUM_SECRET_KEY = \"[S3 SECRET KEY]\"\n\n# Encryption Settings\n# (Used for string/password obfuscation)\n# (You should reset the Encryption Key for every clone of SeleniumBase)\nENCRYPTION_KEY = \"Pg^.l!8UdJ+Y7dMIe&fl*%!p9@ej]/#tL~3E4%6?\"\n# These tokens are added to the beginning and end of obfuscated passwords.\n# Helps identify which strings/passwords have been obfuscated.\nOBFUSCATION_START_TOKEN = \"$^*ENCRYPT=\"\nOBFUSCATION_END_TOKEN = \"?&#$\"\n"
  },
  {
    "path": "examples/desktop_apps/ReadMe.md",
    "content": "<h3 align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_b.png\" alt=\"SeleniumBase\" width=\"320\" /></h3>\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Desktop Apps</h2>\n\n* **Recorder** (Run using `python recorder.py` or `sbase recorder`)\n"
  },
  {
    "path": "examples/desktop_apps/recorder.py",
    "content": "\"\"\" Run this file using ``python recorder.py`` \"\"\"\r\n\r\nimport os\r\n\r\n\r\ndef open_recorder_desktop_app():\r\n    command = \"sbase recorder\"\r\n    os.system(command)\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    open_recorder_desktop_app()\r\n"
  },
  {
    "path": "examples/dialog_boxes/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"> Dialog Boxes 🛂</h2>\n\nSeleniumBase Dialog Boxes let your users provide input in the middle of automation scripts.\n\n* This feature utilizes the [jquery-confirm](https://craftpip.github.io/jquery-confirm/) library.\n* A Python API is used to call the JavaScript API.\n\n<img src=\"https://seleniumbase.github.io/cdn/img/emoji_sports_dialog.png\" alt=\"SeleniumBase\" width=\"400\" />\n\n<h4>↕️ (<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/dialog_boxes/dialog_box_tour.py\">Example: dialog_box_tour.py</a>) ↕️</h4>\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/sports_dialog.gif\" alt=\"SeleniumBase\" width=\"400\" />\n\n<h4>Here's how to run that example:</h4>\n\n```zsh\ncd examples/dialog_boxes\npytest test_dialog_boxes.py\n```\n\n<h4>Here's a code snippet from that:</h4>\n\n```python\nself.open(\"https://xkcd.com/1920/\")\nskip_button = [\"SKIP\", \"red\"]  # Can be a [text, color] list or tuple.\nbuttons = [\"Fencing\", \"Football\", \"Metaball\", \"Go/Chess\", skip_button]\nmessage = \"Choose a sport:\"\nchoice = self.get_jqc_button_input(message, buttons)\nif choice == \"Fencing\":\n    self.open(\"https://xkcd.com/1424/\")\n```\n\n* You can create forms that include buttons and input fields.\n\n<h4>Here's a simple form with only buttons as input:</h4>\n\n```python\nchoice = self.get_jqc_button_input(\"Ready?\", [\"YES\", \"NO\"])\nprint(choice)  # This prints \"YES\" or \"NO\"\n\n# You may want to customize the color of buttons:\nbuttons = [(\"YES\", \"green\"), (\"NO\", \"red\")]\nchoice = self.get_jqc_button_input(\"Ready?\", buttons)\n```\n\n<h4>Here's a simple form with an input field:</h4>\n\n```python\ntext = self.get_jqc_text_input(\"Enter text:\", [\"Search\"])\nprint(text)  # This prints the text entered\n```\n\n<h4>This form has an input field and buttons:</h4>\n\n```python\nmessage = \"Type your name and choose a language:\"\nbuttons = [\"Python\", \"JavaScript\"]\ntext, choice = self.get_jqc_form_inputs(message, buttons)\nprint(\"Your name is: %s\" % text)\nprint(\"You picked %s!\" % choice)\n```\n\n<h4>You can customize options if you want:</h4>\n\n```python\n# Themes: bootstrap, modern, material, supervan, light, dark, seamless\noptions = [(\"theme\", \"modern\"), (\"width\", \"50%\")]\nself.get_jqc_text_input(\"You Won!\", [\"OK\"], options)\n```\n\n<h4>Default options can be set with <code>set_jqc_theme()</code>:</h4>\n\n```python\nself.set_jqc_theme(\"light\", color=\"green\", width=\"38%\")\n\n# To reset jqc theme settings to factory defaults:\nself.reset_jqc_theme()\n```\n\n<h3>All methods for Dialog Boxes:</h3>\n\n```python\nself.get_jqc_button_input(message, buttons, options=None)\n\nself.get_jqc_text_input(message, button=None, options=None)\n\nself.get_jqc_form_inputs(message, buttons, options=None)\n\nself.set_jqc_theme(theme, color=None, width=None)\n\nself.reset_jqc_theme()\n\nself.activate_jquery_confirm()  # Automatic for jqc methods\n```\n\n<h3>Detailed method summaries for Dialog Boxes:</h3>\n\n```python\nself.get_jqc_button_input(message, buttons, options=None)\n\"\"\"\nPop up a jquery-confirm box and return the text of the button clicked.\nIf running in headless mode, the last button text is returned.\n@Params\nmessage: The message to display in the jquery-confirm dialog.\nbuttons: A list of tuples for text and color.\n    Example: [(\"Yes!\", \"green\"), (\"No!\", \"red\")]\n    Available colors: blue, green, red, orange, purple, default, dark.\n    A simple text string also works: \"My Button\". (Uses default color.)\noptions: A list of tuples for options to set.\n    Example: [(\"theme\", \"bootstrap\"), (\"width\", \"450px\")]\n    Available theme options: bootstrap, modern, material, supervan,\n                             light, dark, and seamless.\n    Available colors: (For the BORDER color, NOT the button color.)\n        \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\n    Example option for changing the border color: (\"color\", \"default\")\n    Width can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\n\"\"\"\n\nself.get_jqc_text_input(message, button=None, options=None)\n\"\"\"\nPop up a jquery-confirm box and return the text submitted by the input.\nIf running in headless mode, the text returned is \"\" by default.\n@Params\nmessage: The message to display in the jquery-confirm dialog.\nbutton: A 2-item list or tuple for text and color. Or just the text.\n    Example: [\"Submit\", \"blue\"] -> (default button if not specified)\n    Available colors: blue, green, red, orange, purple, default, dark.\n    A simple text string also works: \"My Button\". (Uses default color.)\noptions: A list of tuples for options to set.\n    Example: [(\"theme\", \"bootstrap\"), (\"width\", \"450px\")]\n    Available theme options: bootstrap, modern, material, supervan,\n                             light, dark, and seamless.\n    Available colors: (For the BORDER color, NOT the button color.)\n        \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\n    Example option for changing the border color: (\"color\", \"default\")\n    Width can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\n\"\"\"\n\nself.get_jqc_form_inputs(message, buttons, options=None)\n\"\"\"\nPop up a jquery-confirm box and return the input/button texts as tuple.\nIf running in headless mode, returns the (\"\", buttons[-1][0]) tuple.\n@Params\nmessage: The message to display in the jquery-confirm dialog.\nbuttons: A list of tuples for text and color.\n    Example: [(\"Yes!\", \"green\"), (\"No!\", \"red\")]\n    Available colors: blue, green, red, orange, purple, default, dark.\n    A simple text string also works: \"My Button\". (Uses default color.)\noptions: A list of tuples for options to set.\n    Example: [(\"theme\", \"bootstrap\"), (\"width\", \"450px\")]\n    Available theme options: bootstrap, modern, material, supervan,\n                             light, dark, and seamless.\n    Available colors: (For the BORDER color, NOT the button color.)\n        \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\n    Example option for changing the border color: (\"color\", \"default\")\n    Width can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\n\"\"\"\n\nself.set_jqc_theme(theme, color=None, width=None)\n\"\"\" Sets the default jquery-confirm theme and width (optional).\nAvailable themes: \"bootstrap\", \"modern\", \"material\", \"supervan\",\n                  \"light\", \"dark\", and \"seamless\".\nAvailable colors: (This sets the BORDER color, NOT the button color.)\n    \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\nWidth can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\n\"\"\"\n\nself.reset_jqc_theme()\n\"\"\" Resets the jqc theme settings to factory defaults. \"\"\"\n\nself.activate_jquery_confirm()  # Automatic for jqc methods\n\"\"\" See https://craftpip.github.io/jquery-confirm/ for usage. \"\"\"\n```\n\n--------\n\n<h4>✅ 🛂 Automated/Manual Hybrid Mode (MasterQA)</h4>\n<p><b><a href=\"https://seleniumbase.github.io/seleniumbase/masterqa/ReadMe/\">MasterQA</a></b> uses <b>SeleniumBase Dialog Boxes</b> to speed up manual testing by having automation perform all the browser actions while the manual tester handles validation. See <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples/master_qa\">the MasterQA GitHub page</a> for examples.</p>\n"
  },
  {
    "path": "examples/dialog_boxes/dialog_box_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass DialogBoxTests(BaseCase):\n    def test_dialog_boxes(self):\n        self.open(\"https://xkcd.com/1920/\")\n        self.assert_element('img[alt=\"Emoji Sports\"]')\n        self.highlight(\"#comic img\")\n\n        skip_button = [\"SKIP\", \"red\"]  # Can be a [text, color] list or tuple.\n        buttons = [\"Fencing\", \"Football\", \"Metaball\", \"Go/Chess\", skip_button]\n        message = \"Choose a sport:\"\n        choice = None\n        while choice != \"STOP\":\n            choice = self.get_jqc_button_input(message, buttons)\n            if choice == \"Fencing\":\n                self.open(\"https://xkcd.com/1424/\")\n                buttons.remove(\"Fencing\")\n            elif choice == \"Football\":\n                self.open(\"https://xkcd.com/1107/\")\n                buttons.remove(\"Football\")\n            elif choice == \"Metaball\":\n                self.open(\"https://xkcd.com/1507/\")\n                buttons.remove(\"Metaball\")\n            elif choice == \"Go/Chess\":\n                self.open(\"https://xkcd.com/1287/\")\n                buttons.remove(\"Go/Chess\")\n            else:\n                break\n            self.highlight(\"#comic img\")\n            if len(buttons) == 2:\n                message = \"One Sport Remaining:\"\n            if len(buttons) == 1:\n                message = \"Part One Complete. You saw all 4 sports!\"\n                btn_text_1 = \"NEXT Tutorial Please!\"\n                btn_text_2 = \"WAIT, Go/Chess is a sport?\"\n                buttons = [(btn_text_1, \"green\"), (btn_text_2, \"purple\")]\n                choice_2 = self.get_jqc_button_input(message, buttons)\n                if choice_2 == btn_text_2:\n                    self.open_if_not_url(\"https://xkcd.com/1287/\")\n                    message = \"Brain sports count as sports!<br /><br />\"\n                    message += \"Are you ready for more?\"\n                    self.get_jqc_button_input(message, [\"Let's Go!\"])\n                break\n\n        self.open(\"https://xkcd.com/1117/\")\n        sb_banner_logo = \"//seleniumbase.io/cdn/img/sb_logo_10.png\"\n        self.set_attributes(\"#news img\", \"src\", sb_banner_logo)\n        options = [(\"theme\", \"material\"), (\"width\", \"52%\")]\n        message = 'With one button, you can press \"Enter/Return\", \"Y\", or \"1\".'\n        self.get_jqc_button_input(message, [\"OK\"], options)\n\n        self.open(\"https://xkcd.com/556/\")\n        self.set_attributes(\"#news img\", \"src\", sb_banner_logo)\n        options = [(\"theme\", \"bootstrap\"), (\"width\", \"52%\")]\n        message = 'If the lowercase button text is \"yes\" or \"no\", '\n        message += '<br><br>you can use the \"Y\" or \"N\" keys as shortcuts. '\n        message += \"<br><br><br>Other shortcuts include: <br><br>\"\n        message += '\"1\": 1st button, \"2\": 2nd button, etc. Got it?'\n        buttons = [(\"YES\", \"green\"), (\"NO\", \"red\")]\n        choice = self.get_jqc_button_input(message, buttons, options)\n\n        message = 'You said \"%s\"! <br><br>' % choice\n        if choice == \"YES\":\n            message += \"Wonderful! Let's continue with the next example...\"\n        else:\n            message += \"You can learn more from SeleniumBase Docs...\"\n        choice = self.get_jqc_button_input(message, [\"OK\"], options)\n\n        self.open(\"https://seleniumbase.io\")\n        self.set_jqc_theme(\"light\", color=\"green\", width=\"38%\")\n        message = \"<b>This is the SeleniumBase Docs website!</b><br /><br />\"\n        message += \"What would you like to search for?<br />\"\n        text = self.get_jqc_text_input(message, [\"Search\"])\n        self.type('input[aria-label=\"Search\"]', text + \"\\n\")\n        self.wait_for_ready_state_complete()\n        self.set_jqc_theme(\"bootstrap\", color=\"red\", width=\"32%\")\n        if self.is_text_visible(\"No matching documents\", \".md-search-result\"):\n            self.get_jqc_button_input(\"Your search had no results!\", [\"OK\"])\n        elif self.is_text_visible(\"Type to start searching\", \"div.md-search\"):\n            self.get_jqc_button_input(\"You did not do a search!\", [\"OK\"])\n        else:\n            self.click_if_visible(\"a.md-search-result__link\")\n            self.set_jqc_theme(\"bootstrap\", color=\"green\", width=\"32%\")\n            self.get_jqc_button_input(\"You found search results!\", [\"OK\"])\n\n        self.open(\"https://seleniumbase.io/help_docs/ReadMe/\")\n        self.highlight(\"h1\")\n        self.slow_scroll_to('article p a[href*=\"/examples/ReadMe/\"]')\n        zoom_in = 'article p a[href*=\"/examples/ReadMe/\"]{zoom: 1.8;}'\n        self.add_css_style(zoom_in)\n        self.highlight_click('article p a[href*=\"/examples/ReadMe/\"]')\n        self.highlight(\"h1\")\n\n        self.set_jqc_theme(\"bootstrap\", color=\"green\", width=\"52%\")\n        message = 'See the \"SeleniumBase/examples\" section for more info!'\n        self.get_jqc_button_input(message, [\"OK\"])\n\n        self.set_jqc_theme(\"bootstrap\", color=\"purple\", width=\"56%\")\n        message = \"Now let's combine form inputs with multiple button options!\"\n        message += \"<br /><br />\"\n        message += \"Pick something to search. Then pick the site to search on.\"\n        buttons = [\"SeleniumBase.io\", \"Wikipedia.org\"]\n        text, choice = self.get_jqc_form_inputs(message, buttons)\n        if choice == \"SeleniumBase.io\":\n            self.open(\"https://seleniumbase.io/\")\n            self.highlight_type('input[aria-label=\"Search\"]', text + \"\\n\")\n        else:\n            self.open(\"https://en.wikipedia.org/wiki/Special:Search\")\n            self.highlight_type('input[id*=\"search\"]', text)\n            self.sleep(1)\n            self.click(\"#searchform button\")\n        self.wait_for_ready_state_complete()\n        self.sleep(1)\n        self.highlight(\"body\")\n        self.reset_jqc_theme()\n        self.get_jqc_button_input(\"<b>Here are your results.</b>\", [\"OK\"])\n        message = \"<h3>You've reached the end of this tutorial!</h3><br />\"\n        message += \"Now you know about SeleniumBase Dialog Boxes!<br />\"\n        message += \"<br />Check out SeleniumBase on GitHub for more!\"\n        self.set_jqc_theme(\"modern\", color=\"purple\", width=\"56%\")\n        self.get_jqc_button_input(message, [\"Goodbye!\"])\n"
  },
  {
    "path": "examples/edge_test.py",
    "content": "\"\"\"This test is only for Microsoft Edge (Chromium)!\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--edge\")\n\n\nclass EdgeTests(BaseCase):\n    def test_edge(self):\n        if self.browser != \"edge\":\n            self.open_if_not_url(\"about:blank\")\n            print(\"\\n  This test is only for Microsoft Edge (Chromium)!\")\n            print('  (Run this test using \"--edge\" or \"--browser=edge\")')\n            self.skip('Use \"--edge\" or \"--browser=edge\"')\n        elif self.headless:\n            self.open_if_not_url(\"about:blank\")\n            print(\"\\n  This test is NOT designed for Headless Mode!\")\n            self.skip('Do NOT use \"--headless\" with this test!')\n        self.open(\"edge://settings/help\")\n        self.assert_element(\"app-shell\")\n        self.assert_text(\"Microsoft Edge\", \"app-shell\")\n        self.sleep(2)\n"
  },
  {
    "path": "examples/example_config.cfg",
    "content": "[nosetests]\nnocapture=1\nlogging-level=INFO\nbrowser=chrome\n"
  },
  {
    "path": "examples/example_logs/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## [<img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\">](https://github.com/seleniumbase/SeleniumBase/) Logs, The Dashboard, and Reports:\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=XpuJCjJhJwQ\"><img src=\"http://img.youtube.com/vi/XpuJCjJhJwQ/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"285\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=XpuJCjJhJwQ\">The Dashboard Tutorial on YouTube</a></b>)</p>\n\n🔵 During test failures, logs and screenshots from the most recent test run will get saved to the ``latest_logs/`` folder. If ``--archive-logs`` is specified (or if ARCHIVE_EXISTING_LOGS is set to True in [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py)), test logs will also get archived to the ``archived_logs/`` folder. Otherwise, the log files will be cleaned out when the next test run begins (by default).\n\n```zsh\npytest test_fail.py\n```\n\n(Log files in [SeleniumBase/examples/example_logs](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/example_logs) were generated when [test_fail.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_fail.py) ran and failed.)\n\n<b>Examples of expected log files generated during failures:</b>\n<ul>\n<li><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/basic_test_info.txt\">basic_test_info.txt</a></li>\n<li><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/page_source.html\">page_source.html</a></li>\n<li><a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/screenshot.png\">screenshot.png</a></li>\n</ul>\n\n<b>In addition to log files, you can also generate dashboards and test reports.</b>\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> The SeleniumBase Dashboard:</h3>\n\n🔵 The ``--dashboard`` option for pytest generates a SeleniumBase Dashboard located at ``dashboard.html``, which updates automatically as tests run and produce results. Example:\n\n```zsh\npytest --dashboard --rs --headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dashboard_1.png\" alt=\"The SeleniumBase Dashboard\" title=\"The SeleniumBase Dashboard\" width=\"360\" />\n\n🔵 Additionally, you can host your own SeleniumBase Dashboard Server on a port of your choice. Here's an example of that using Python 3's ``http.server``:\n\n```zsh\npython -m http.server 1948\n```\n\n🔵 Now you can navigate to ``http://localhost:1948/dashboard.html`` in order to view the dashboard as a web app. This requires two different terminal windows: one for running the server, and another for running the tests, which should be run from the same directory. (Use <kbd>Ctrl+C</kbd> to stop the http server.)\n\n🔵 Here's a full example of what the SeleniumBase Dashboard may look like:\n\n```zsh\npytest test_suite.py test_image_saving.py --dashboard --rs --headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dashboard_2.png\" alt=\"The SeleniumBase Dashboard\" title=\"The SeleniumBase Dashboard\" width=\"480\" />\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Pytest Reports:</h3>\n\n🔵 Using ``--html=report.html`` gives you a fancy report of the name specified after your test suite completes.\n\n```zsh\npytest test_suite.py --html=report.html\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/html_report.png\" alt=\"Example Pytest Report\" title=\"Example Pytest Report\" width=\"520\" />\n\n🔵 When combining pytest html reports with SeleniumBase Dashboard usage, the pie chart from the Dashboard will get added to the html report. Additionally, if you set the html report URL to be the same as the Dashboard URL when also using the dashboard, (example: ``--dashboard --html=dashboard.html``), then the Dashboard will become an advanced html report when all the tests complete.\n\n🔵 Here's an example of an upgraded html report:\n\n```zsh\npytest test_suite.py --dashboard --html=report.html\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dash_report.png\" alt=\"Dashboard Pytest HTML Report\" title=\"Dashboard Pytest HTML Report\" width=\"520\" />\n\n--------\n\nIf viewing ``pytest-html`` reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356/7058266) for the HTML to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/security/configuring-content-security-policy/). That setting can be changed from ``Manage Jenkins`` > ``Script Console`` by running:\n\n```js\nSystem.setProperty(\"hudson.model.DirectoryBrowserSupport.CSP\", \"\")\n```\n\n--------\n\nYou can also use ``--junit-xml=report.xml`` to get an xml report instead. Jenkins can use this file to display better reporting for your tests.\n\n```zsh\npytest test_suite.py --junit-xml=report.xml\n```\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> pynose Test Reports:</h3>\n\nThe ``pynose`` ``--report`` option gives you a fancy report after your tests complete.\n\n```zsh\npynose test_suite.py --report\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/nose_report.png\" alt=\"Example pynose Test Report\" title=\"Example pynose Test Report\" width=\"320\" />\n\n(NOTE: You can add ``--show-report`` to immediately display pynose reports after the test suite completes. Only use ``--show-report`` when running tests locally because it pauses the test run.)\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> 🐝⚪ Behave Dashboard & Reports:</h3>\n\n(The [behave_bdd/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/behave_bdd) folder can be found in the [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder.)\n\n```zsh\nbehave behave_bdd/features/ -D dashboard -D headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_behave_dashboard.png\" title=\"SeleniumBase\" width=\"600\">\n\nYou can also use ``--junit`` to get ``.xml`` reports for each Behave feature. Jenkins can use these files to display better reporting for your tests.\n\n```zsh\nbehave behave_bdd/features/ --junit -D rs -D headless\n```\n\n--------\n\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/img/sb_logo_10.png\" alt=\"SeleniumBase\" width=\"240\" /></a></div>\n"
  },
  {
    "path": "examples/example_logs/basic_test_info.txt",
    "content": "test_fail.py::FailingTests::test_find_army_of_robots_on_xkcd_desert_island\r\n--------------------------------------------------------------------\r\nLast Page: https://xkcd.com/731/\r\n Duration: 1.63s\r\n  Browser: Chrome 108.0.5359.124\r\n   Driver: chromedriver 108.0.5359.71\r\nTimestamp: 1672785363  (Unix Timestamp)\r\n     Date: Tuesday, January 3, 2023\r\n     Time: 5:36:03 PM  (EDT, UTC-05:00)\r\n--------------------------------------------------------------------\r\nTraceback:   File \"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/case.py\", line 57, in testPartExecutor\n    yield\n  File \"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/case.py\", line 623, in run\n    self._callTestMethod(testMethod)\n  File \"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/case.py\", line 579, in _callTestMethod\n    if method() is not None:\n       ^^^^^^^^\n  File \"/Users/michael/github/SeleniumBase/examples/test_fail.py\", line 16, in test_find_army_of_robots_on_xkcd_desert_island\n    self.assert_element(\"div#ARMY_OF_ROBOTS\", timeout=1)\n  File \"/Users/michael/github/SeleniumBase/seleniumbase/fixtures/base_case.py\", line 8279, in assert_element\n    self.wait_for_element_visible(selector, by=by, timeout=timeout)\n  File \"/Users/michael/github/SeleniumBase/seleniumbase/fixtures/base_case.py\", line 7718, in wait_for_element_visible\n    return page_actions.wait_for_element_visible(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/Users/michael/github/SeleniumBase/seleniumbase/fixtures/page_actions.py\", line 428, in wait_for_element_visible\n    timeout_exception(NoSuchElementException, message)\n  File \"/Users/michael/github/SeleniumBase/seleniumbase/fixtures/page_actions.py\", line 191, in timeout_exception\n    raise exc(msg)\n\r\nException: Message: \n Element {div#ARMY_OF_ROBOTS} was not present after 1 second!\n"
  },
  {
    "path": "examples/example_logs/page_source.html",
    "content": "<base href=\"https://xkcd.com\">\n<html><head>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"/s/b0dcca.css\" title=\"Default\">\n<title>xkcd: Desert Island</title>\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n<link rel=\"shortcut icon\" href=\"/s/919f27.ico\" type=\"image/x-icon\">\n<link rel=\"icon\" href=\"/s/919f27.ico\" type=\"image/x-icon\">\n<link rel=\"alternate\" type=\"application/atom+xml\" title=\"Atom 1.0\" href=\"/atom.xml\">\n<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS 2.0\" href=\"/rss.xml\">\n<script type=\"text/javascript\" src=\"/s/b66ed7.js\" async=\"\"></script>\n<script type=\"text/javascript\" src=\"/s/1b9456.js\" async=\"\"></script>\n\n</head>\n<body>\n<div id=\"topContainer\">\n<div id=\"topLeft\">\n<ul>\n<li><a href=\"/archive\">Archive</a></li>\n<li><a href=\"http://what-if.xkcd.com\">What If?</a></li>\n<li><a href=\"http://blag.xkcd.com\">Blag</a></li>\n<li><a href=\"http://store.xkcd.com/\">Store</a></li>\n<li><a rel=\"author\" href=\"/about\">About</a></li>\n</ul>\n</div>\n<div id=\"topRight\">\n<div id=\"masthead\">\n<span><a href=\"/\"><img src=\"/s/0b7742.png\" alt=\"xkcd.com logo\" width=\"185\" height=\"83\"></a></span>\n<span id=\"slogan\">A webcomic of romance,<br> sarcasm, math, and language.</span>\n</div>\n<div id=\"news\">\nxkcd updates every Monday, Wednesday, and Friday.\n\n</div>\n</div>\n<div id=\"bgLeft\" class=\"bg box\"></div>\n<div id=\"bgRight\" class=\"bg box\"></div>\n</div>\n<div id=\"middleContainer\" class=\"box\">\n\n<div id=\"ctitle\">Desert Island</div>\n<ul class=\"comicNav\">\n<li><a href=\"/1/\">|&lt;</a></li>\n<li><a rel=\"prev\" href=\"/730/\" accesskey=\"p\">&lt; Prev</a></li>\n<li><a href=\"//c.xkcd.com/random/comic/\">Random</a></li>\n<li><a rel=\"next\" href=\"/732/\" accesskey=\"n\">Next &gt;</a></li>\n<li><a href=\"/\">&gt;|</a></li>\n</ul>\n<div id=\"comic\">\n<img src=\"//imgs.xkcd.com/comics/desert_island.png\" title=\"Telescopes and bathyscaphes and sonar probes of Scottish lakes, Tacoma Narrows bridge collapse explained with abstract phase-space maps, some x-ray slides, a music score, Minard's Napoleonic war: the most exciting new frontier is charting what's already here.\" alt=\"Desert Island\">\n</div>\n<ul class=\"comicNav\">\n<li><a href=\"/1/\">|&lt;</a></li>\n<li><a rel=\"prev\" href=\"/730/\" accesskey=\"p\">&lt; Prev</a></li>\n<li><a href=\"//c.xkcd.com/random/comic/\">Random</a></li>\n<li><a rel=\"next\" href=\"/732/\" accesskey=\"n\">Next &gt;</a></li>\n<li><a href=\"/\">&gt;|</a></li>\n</ul>\n<br>\nPermanent link to this comic: https://xkcd.com/731/<br>\nImage URL (for hotlinking/embedding): https://imgs.xkcd.com/comics/desert_island.png\n<div id=\"transcript\" style=\"display: none\">[[A man sits writing in a diary on a desert island, only the sandy tip of which with a palm tree on it stands above the water. Beneath the surface is a kelp forest, some sharks, a stingray, a shipwreck, a submarine, several large jellyfish, a giant squid fighting a sperm whale, a crashed plane, some coral formations, a thermal vent emitting a plume of smoke surrounded by several annelids, and a snail.]]\nMan: Day 44: Still stranded, with nothing but flat empty water as far as the eye can see.\n\n{{Title text: Telescopes and bathyscapes and sonar probes of Scottish lakes, Tacoma Narrows bridge collapse explained with abstract phase-space maps, some x-ray slides, a music score, Minard's Napoleonic war: the most exciting new frontier is charting what's already here.}}</div>\n</div>\n<div id=\"bottom\" class=\"box\">\n<img src=\"//imgs.xkcd.com/s/a899e84.jpg\" alt=\"Selected Comics\" usemap=\"#comicmap\" width=\"520\" height=\"100\">\n<map id=\"comicmap\" name=\"comicmap\">\n<area shape=\"rect\" coords=\"0,0,100,100\" href=\"/150/\" alt=\"Grownups\">\n<area shape=\"rect\" coords=\"104,0,204,100\" href=\"/730/\" alt=\"Circuit Diagram\">\n<area shape=\"rect\" coords=\"208,0,308,100\" href=\"/162/\" alt=\"Angular Momentum\">\n<area shape=\"rect\" coords=\"312,0,412,100\" href=\"/688/\" alt=\"Self-Description\">\n<area shape=\"rect\" coords=\"416,0,520,100\" href=\"/556/\" alt=\"Alternative Energy Revolution\">\n</map>\n<div>\n<!--\nSearch comic titles and transcripts:\n<script type=\"text/javascript\" src=\"//www.google.com/jsapi\"></script>\n<script type=\"text/javascript\">google.load('search', '1');google.setOnLoadCallback(function() {google.search.CustomSearchControl.attachAutoCompletion('012652707207066138651:zudjtuwe28q',document.getElementById('q'),'cse-search-box');});</script>\n<form action=\"//www.google.com/cse\" id=\"cse-search-box\">\n<div>\n<input type=\"hidden\" name=\"cx\" value=\"012652707207066138651:zudjtuwe28q\"/>\n<input type=\"hidden\" name=\"ie\" value=\"UTF-8\"/>\n<input type=\"text\" name=\"q\" id=\"q\" size=\"31\"/>\n<input type=\"submit\" name=\"sa\" value=\"Search\"/>\n</div>\n</form>\n<script type=\"text/javascript\" src=\"//www.google.com/cse/brand?form=cse-search-box&amp;lang=en\"></script>\n-->\n<a href=\"/rss.xml\">RSS Feed</a> - <a href=\"/atom.xml\">Atom Feed</a>\n</div>\n<br>\n<div id=\"comicLinks\">\nComics I enjoy:<br>\n        <a href=\"http://threewordphrase.com/\">Three Word Phrase</a>,\n        <a href=\"http://www.smbc-comics.com/\">SMBC</a>,\n        <a href=\"http://www.qwantz.com\">Dinosaur Comics</a>,\n        <a href=\"http://oglaf.com/\">Oglaf</a> (nsfw),\n        <a href=\"http://www.asofterworld.com\">A Softer World</a>,\n        <a href=\"http://buttersafe.com/\">Buttersafe</a>,\n        <a href=\"http://pbfcomics.com/\">Perry Bible Fellowship</a>,\n        <a href=\"http://questionablecontent.net/\">Questionable Content</a>,\n        <a href=\"http://www.buttercupfestival.com/\">Buttercup Festival</a>,\n        <a href=\"http://www.mspaintadventures.com/?s=6&amp;p=001901\">Homestuck</a>,\n\t<a href=\"http://www.jspowerhour.com/\">Junior Scientist Power Hour</a>,\n</div>\n<br>\n<div id=\"comicLinks\">\nOther things:<br>\n        <a href=\"http://womenalsoknowstuff.com/\">Women Also Know Stuff</a>,\n        <a href=\"https://techsolidarity.org/\">Tech Solidarity</a>,\n        <a href=\"https://medium.com/civic-tech-thoughts-from-joshdata/so-you-want-to-reform-democracy-7f3b1ef10597\">Tips on technology and government</a>\n</div>\n<br>\n<center>\n<div id=\"footnote\" style=\"width:70%\">xkcd.com is best viewed with Netscape Navigator 4.0 or below on a Pentium 3±1 emulated in Javascript on an Apple IIGS<br>at a screen resolution of 1024x1. Please enable your ad blockers, disable high-heat drying, and remove your device<br>from Airplane Mode and set it to Boat Mode. For security reasons, please leave caps lock on while browsing.</div>\n</center>\n<div id=\"licenseText\">\n<p>\nThis work is licensed under a\n<a href=\"http://creativecommons.org/licenses/by-nc/2.5/\">Creative Commons Attribution-NonCommercial 2.5 License</a>.\n</p><p>\nThis means you're free to copy and share these comics (but not to sell them). <a rel=\"license\" href=\"/license.html\">More details</a>.</p>\n</div>\n</div>\n\n\n\n\n</body><!-- Layout by Ian Clasbey, davean, and chromakode --></html>"
  },
  {
    "path": "examples/github_test.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass GitHubTests(BaseCase):\n    def test_github(self):\n        if self.headless or self.page_load_strategy == \"none\":\n            self.open_if_not_url(\"about:blank\")\n            print(\"\\n  Unsupported mode for this test.\")\n            self.skip(\"Unsupported mode for this test.\")\n        self.open(\"https://github.com/seleniumbase/SeleniumBase\")\n        self.click_if_visible('[data-action=\"click:signup-prompt#dismiss\"]')\n        self.highlight(\"div.Layout-main\")\n        self.highlight(\"div.Layout-sidebar\")\n        self.assert_element(\"div.repository-content\")\n        self.assert_text(\"SeleniumBase\", \"strong a\")\n        self.js_click('a[title=\"seleniumbase\"]')\n        self.slow_click('td[class*=\"large\"] a[title=\"fixtures\"]')\n        self.highlight('td[class*=\"large\"] a[title=\"base_case.py\"]', loops=8)\n"
  },
  {
    "path": "examples/gui_test_runner.py",
    "content": "\"\"\"GUI TEST RUNNER\nRun by Typing: \"python gui_test_runner.py\"\n(Use Python 3)\"\"\"\nimport subprocess\nfrom tkinter import Tk, Frame, Button, Label\n\n\nclass App:\n    def __init__(self, master):\n        frame = Frame(master)\n        frame.pack()\n        self.label = Label(root, width=40).pack()\n        self.title = Label(frame, text=\"\", fg=\"black\").pack()\n        self.title1 = Label(\n            frame,\n            text=(\"Run a Test in Chrome (default):\"),\n            fg=\"blue\",\n        ).pack()\n        self.run1 = Button(\n            frame,\n            command=self.run_1,\n            text=(\"pytest my_first_test.py\"),\n            fg=\"green\",\n        ).pack()\n        self.title2 = Label(\n            frame,\n            text=(\"Run a Test in Firefox:\"),\n            fg=\"blue\",\n        ).pack()\n        self.run2 = Button(\n            frame,\n            command=self.run_2,\n            text=(\"pytest my_first_test.py --firefox\"),\n            fg=\"green\",\n        ).pack()\n        self.title3 = Label(\n            frame,\n            text=\"Run a Test with Demo Mode:\",\n            fg=\"blue\",\n        ).pack()\n        self.run3 = Button(\n            frame,\n            command=self.run_3,\n            text=(\"pytest my_first_test.py --demo_mode\"),\n            fg=\"green\",\n        ).pack()\n        self.title4 = Label(\n            frame,\n            text=\"Run a Parameterized Test and reuse session:\",\n            fg=\"blue\",\n        ).pack()\n        self.run4 = Button(\n            frame,\n            command=self.run_4,\n            text=(\"pytest parameterized_test.py --rs\"),\n            fg=\"green\",\n        ).pack()\n        self.title5 = Label(\n            frame,\n            text=\"Run a Failing Test with a Test Report:\",\n            fg=\"blue\",\n        ).pack()\n        self.run5 = Button(\n            frame,\n            command=self.run_5,\n            text=(\"pytest test_fail.py --html=report.html\"),\n            fg=\"red\",\n        ).pack()\n        self.title6 = Label(\n            frame,\n            text=\"Run a Failing Test Suite with the Dashboard:\",\n            fg=\"blue\",\n        ).pack()\n        self.run6 = Button(\n            frame,\n            command=self.run_6,\n            text=(\"pytest test_suite.py --rs --dashboard\"),\n            fg=\"red\",\n        ).pack()\n        self.title7 = Label(\n            frame,\n            text=\"Run a Failing Test with Deferred Asserts:\",\n            fg=\"blue\",\n        ).pack()\n        self.run7 = Button(\n            frame,\n            command=self.run_7,\n            text=(\"pytest test_deferred_asserts.py\"),\n            fg=\"red\",\n        ).pack()\n        self.end_title = Label(frame, text=\"\", fg=\"black\").pack()\n        self.quit = Button(frame, text=\"QUIT\", command=frame.quit).pack()\n\n    def run_1(self):\n        subprocess.Popen(\"pytest my_first_test.py\", shell=True)\n\n    def run_2(self):\n        subprocess.Popen(\"pytest my_first_test.py --firefox\", shell=True)\n\n    def run_3(self):\n        subprocess.Popen(\"pytest my_first_test.py --demo_mode\", shell=True)\n\n    def run_4(self):\n        subprocess.Popen(\"pytest parameterized_test.py --rs\", shell=True)\n\n    def run_5(self):\n        subprocess.Popen(\"pytest test_fail.py --html=report.html\", shell=True)\n\n    def run_6(self):\n        subprocess.Popen(\"pytest test_suite.py --rs --dashboard\", shell=True)\n\n    def run_7(self):\n        subprocess.Popen(\"pytest test_deferred_asserts.py\", shell=True)\n\n\nif __name__ == \"__main__\":\n    root = Tk()\n    root.title(\"Select Test Job To Run\")\n    root.minsize(320, 420)\n    app = App(root)\n    root.mainloop()\n"
  },
  {
    "path": "examples/hack_the_planet.py",
    "content": "\"\"\" Video Link: https://youtu.be/1s-Tj65AKZA \"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass HackTests(BaseCase):\n    def test_all_your_base_are_belong_to_us(self):\n        self.set_window_size(1250, 740)\n        ayb = \"ALL YOUR BASE\"\n        abtu = \"ARE BELONG TO US\"\n        aybabtu = \"%s %s\" % (ayb, abtu)\n        sb_banner_logo = \"//seleniumbase.github.io/cdn/img/sb_logo_10.png\"\n        sb_dashboard_logo = \"//seleniumbase.github.io/img/dash_pie_3.png\"\n        wiki = \"https://en.wikipedia.org/wiki/All_your_base_are_belong_to_us\"\n\n        self.open(wiki)\n        self.click_if_visible('button[aria-label=\"Close\"]')\n        self.set_text_content(\"h1#firstHeading\", aybabtu)\n        self.set_text_content(\"#ca-history a\", aybabtu)\n        self.set_text_content(\"#n-mainpage-description a\", \"ALL\")\n        self.set_text_content(\"#n-contents a\", \"YOUR\")\n        self.set_text_content(\"#n-currentevents a\", \"BASE\")\n        self.set_text_content(\"#n-randompage a\", \"ARE\")\n        self.set_text_content(\"#n-aboutsite a\", \"BELONG\")\n        self.set_text_content(\"#n-contactpage a\", \"TO US\")\n        self.highlight(\"h1#firstHeading\", loops=5, scroll=False)\n        zoom_in = \"#ca-history a{zoom: 1.8;-moz-transform: scale(1.8);}\"\n        self.add_css_style(zoom_in)\n        self.highlight(\"#ca-history a\", loops=5, scroll=False)\n        zoom_in = \"img[src*=Ayb]{zoom: 1.6;-moz-transform: scale(1.6);}\"\n        self.add_css_style(zoom_in)\n        self.highlight(\"img[src*=Ayb]\", loops=10, scroll=False)\n\n        if not self.headless:\n            self.open(\"https://www.apple.com/store\")\n            self.set_text_content(\"div.rs-shop-subheader\", aybabtu)\n            self.set_text_content('#shelf-1 a[href*=\"mac\"]', \"ALL\")\n            self.set_text_content('#shelf-1 a[href*=\"iphone\"]', \"YOUR\")\n            self.set_text_content('#shelf-1 a[href*=\"ipad\"]', \"BASE\")\n            self.remove_element('#shelf-1 [role=\"listitem\"]:nth-child(5)')\n            self.set_text_content('#shelf-1 a[href*=\"watch\"]', \"ARE\")\n            self.set_text_content('#shelf-1 a[href*=\"airpods\"]', \"BELONG\")\n            self.set_text_content('#shelf-1 a[href*=\"airtag\"]', \"TO\")\n            self.set_text_content('#shelf-1 a[href*=\"tv\"]', \"US\")\n            self.set_text_content('#shelf-1 a[href*=\"homepod\"]', \".\")\n            self.set_text_content(\"#shelf-2_section h2\", ayb + \". \")\n            self.set_text_content(\"#shelf-2_section span\", abtu + \". \")\n            self.highlight(\"div.rs-shop-subheader\", loops=6, scroll=False)\n            self.highlight(\"#shelf-1\", loops=2, scroll=False)\n            self.highlight('#shelf-1 a[href*=\"mac\"]', loops=1, scroll=False)\n            self.highlight('#shelf-1 a[href*=\"iphone\"]', loops=1, scroll=False)\n            self.highlight('#shelf-1 a[href*=\"ipad\"]', loops=3, scroll=False)\n            self.highlight('#shelf-1 a[href*=\"watch\"]', loops=1, scroll=False)\n            self.highlight('#shelf-1 a[href*=\"airpod\"]', loops=1, scroll=False)\n            self.highlight('#shelf-1 a[href*=\"airtag\"]', loops=1, scroll=False)\n            self.highlight('#shelf-1 a[href*=\"tv\"]', loops=3, scroll=False)\n            self.highlight(\"#shelf-2_section h2\", loops=5, scroll=False)\n            self.highlight(\"#shelf-2_section span\", loops=6, scroll=False)\n\n        self.open(\"https://google.com/ncr\")\n        self.hide_elements(\"iframe\")\n        if self.is_element_visible('a[href*=\"about.google\"]'):\n            self.set_text_content('a[href*=\"about.google\"]', ayb)\n            if self.is_element_visible('a[href*=\"store.google\"]'):\n                self.set_text_content('a[href*=\"store.google\"]', abtu)\n        self.set_text_content('a[href*=\"mail.google.com\"]', ayb)\n        self.set_text_content('a[href*=\"google.com/img\"]', abtu)\n        self.set_attributes('[value=\"Google Search\"]', \"value\", ayb)\n        self.set_attributes('[value=\"I\\'m Feeling Lucky\"]', \"value\", abtu)\n        self.hide_elements(\"iframe\")\n        zoom_in = \"a{zoom: 1.2;-moz-transform: scale(1.2);}\"\n        self.add_css_style(zoom_in)\n        zoom_in = (\n            '[value=\"ALL YOUR BASE\"]{zoom: 1.3;-moz-transform: scale(1.3);}'\n            '[value=\"ARE BELONG TO US\"]{zoom: 1.3;-moz-transform: scale(1.3);}'\n        )\n        self.add_css_style(zoom_in)\n        self.hide_elements(\"iframe\")\n        if self.is_element_visible('a[href*=\"about.google\"]'):\n            self.highlight('a[href*=\"about.google\"]', loops=3)\n            if self.is_element_visible('a[href*=\"store.google\"]'):\n                self.highlight('a[href*=\"store.google\"]', loops=3)\n        self.highlight('a[href*=\"mail.google.com\"]', loops=3)\n        self.highlight('a[href*=\"google.com/img\"]', loops=3)\n        self.highlight('form[role=\"search\"]', loops=8)\n\n        self.open(\"https://github.com/features/actions\")\n        self.set_text_content(\"#hero-section-brand-heading\", aybabtu)\n        self.highlight(\"#hero-section-brand-heading\", loops=14, scroll=False)\n\n        self.open(\"https://dev.to/top/infinity\")\n        self.click_if_visible('button[aria-label=\"Close campaign banner\"]')\n        self.click_if_visible('svg[aria-label=\"Close campaign banner\"]')\n        self.click_if_visible('button[id*=\"sponsorship-close-trigger\"]')\n        if self.is_element_visible('main div:contains(\"Pinned\")'):\n            self.hide_elements('main div:contains(\"Pinned\")')\n        if self.is_element_visible('[data-type-of=\"in_house\"]'):\n            self.hide_elements('[data-type-of=\"in_house\"]')\n        self.set_text_content('nav a[data-text=\"Relevant\"]', \"ALL\")\n        self.set_text_content('nav a[data-text=\"Latest\"]', \"YOUR\")\n        self.set_text_content('nav a[data-text=\"Top\"]', \"BASE\")\n        self.set_text_content('nav a[data-text=\"Week\"]', \"ARE\")\n        self.set_text_content('nav a[data-text=\"Month\"]', \"BELONG\")\n        self.set_text_content('nav a[data-text=\"Year\"]', \"TO\")\n        self.set_text_content('nav a[data-text=\"Infinity\"]', \"US\")\n        self.click_if_visible('button[id*=\"sponsorship-close-trigger\"]')\n        self.set_text_content('aside a[class*=\"tful\"]', aybabtu)\n        self.set_text_content('aside a[aria-label=\"Create new account\"]', ayb)\n        self.set_text_content('aside a[aria-label=\"Log in\"]', abtu)\n        self.set_text_content('aside a[class*=\"tful\"]:nth-child(2)', aybabtu)\n        self.set_text_content('aside a[class*=\"tful\"]:nth-child(3)', aybabtu)\n        self.set_text_content('aside a[class*=\"tful\"]:nth-child(4)', aybabtu)\n        self.set_text_content('aside a[class*=\"tful\"]:nth-child(5)', aybabtu)\n        self.set_attribute(\"a.crayons-avatar img\", \"src\", sb_dashboard_logo)\n        self.set_text_content(\".profile-preview-card button\", \"SeleniumBase\")\n        if self.is_element_visible('h2 a[href*=\"simonh\"]'):\n            self.set_text_content('h2 a[href*=\"simonh\"]', aybabtu)\n        if self.is_element_visible('main h2 a[id*=\"article\"]'):\n            self.set_text_content('main h2 a[id*=\"article\"]', aybabtu)\n        self.highlight('[aria-label=\"Primary sidebar\"] div div', scroll=False)\n        self.highlight('nav a[data-text=\"Relevant\"]', loops=1, scroll=False)\n        self.highlight('nav a[data-text=\"Latest\"]', loops=1, scroll=False)\n        self.highlight('nav a[data-text=\"Top\"]', loops=2, scroll=False)\n        self.highlight('nav a[data-text=\"Week\"]', loops=1, scroll=False)\n        self.highlight('nav a[data-text=\"Month\"]', loops=1, scroll=False)\n        self.highlight('nav a[data-text=\"Year\"]', loops=1, scroll=False)\n        self.highlight('nav a[data-text=\"Infinity\"]', loops=3, scroll=False)\n        if self.is_element_visible('main h2 a[id*=\"article\"]'):\n            self.highlight('main h2 a[id*=\"article\"]', loops=7, scroll=False)\n        self.highlight(\"section.crayons-card\", loops=7, scroll=False)\n\n        self.open(\"https://store.steampowered.com/\")\n        self.set_text_content('a[href*=\"steamcommunity.com/\"]', \" \")\n        self.set_text_content('div.content a[href*=\"/about/\"]', \" \")\n        self.set_text_content('div.content a[href*=\"help.steam\"]', aybabtu)\n        zoom_in = '[href*=\"help.steam\"]{zoom: 1.5;-moz-transform: scale(1.5);}'\n        self.add_css_style(zoom_in)\n        self.highlight('div.content a[href*=\"help.steam\"]', loops=12)\n\n        self.open(\"https://xkcd.com/286/\")\n        self.set_text_content('a[href=\"/archive\"]', \"ALL\")\n        self.set_text_content('a[href*=\"what-if\"]', \"YOUR BASE\")\n        self.set_text_content('a[href*=\"/about\"]', abtu)\n        self.remove_element('li:contains(\"Feed\")')\n        self.remove_element('li:contains(\"TW\")')\n        self.remove_element('li:contains(\"Books\")')\n        self.remove_element('li:contains(\"What\")')\n        self.remove_element('li:contains(\"WI\")')\n        self.set_attributes(\"#countdown img\", \"src\", sb_banner_logo)\n        self.set_text_content(\"#ctitle\", aybabtu)\n        self.set_text_content('a[rel=\"prev\"]', \"All\")\n        self.set_text_content('a[href*=\"random\"]', \"Your\")\n        self.set_text_content('a[rel=\"next\"]', \"Base\")\n        self.highlight(\"#topLeft ul\", loops=5, scroll=False)\n        self.highlight('a[href=\"/archive\"]', loops=1, scroll=False)\n        self.highlight('a[href*=\"what-if\"]', loops=3, scroll=False)\n        self.highlight('a[href*=\"/about\"]', loops=5, scroll=False)\n        self.highlight('a[rel=\"prev\"]', loops=1, scroll=False)\n        self.highlight('a[href*=\"random\"]', loops=1, scroll=False)\n        self.highlight('a[rel=\"next\"]', loops=3, scroll=False)\n        self.highlight(\"#ctitle\", loops=7, scroll=False)\n\n        self.open(\"https://www.nintendo.com/whatsnew/\")\n        self.set_text_content(\"main section h1\", aybabtu)\n        self.highlight(\"main section h1\", loops=10, scroll=False)\n\n        if not self.headless:\n            self.open(\"https://support.gog.com/hc/en-us?product=gog\")\n            self.set_text_content(\"div.intro-title\", aybabtu)\n            self.set_text_content(\"h4\", aybabtu)\n            self.highlight(\"div.intro-title\", loops=8, scroll=False)\n            self.highlight(\"h4\", loops=8, scroll=False)\n\n        self.open(\"https://slack.com/help/articles/204714258-Giphy-for-Slack\")\n        self.set_text_content(\"h1\", aybabtu)\n        self.set_text_content('a[prettyslug=\"getting-started\"]', \"ALL\")\n        self.set_text_content('a[prettyslug=\"using-slack\"]', \"YOUR\")\n        self.set_text_content('a[prettyslug=\"your-profile\"]', \"BASE\")\n        self.set_text_content('a[prettyslug=\"connect-tools\"]', \"ARE\")\n        self.set_text_content('a[prettyslug=\"administration\"]', \"BELONG\")\n        self.set_text_content('a[prettyslug*=\"tutorials\"]', \"TO US\")\n        self.set_text_content(\"h1.article_title\", aybabtu)\n        self.highlight(\"h1\", loops=4, scroll=False)\n        self.highlight(\"div#global_menu\", loops=2, scroll=False)\n        self.highlight('a[prettyslug*=\"g-started\"]', loops=1, scroll=False)\n        self.highlight('a[prettyslug=\"using-slack\"]', loops=1, scroll=False)\n        self.highlight('a[prettyslug=\"your-profile\"]', loops=2, scroll=False)\n        self.highlight('a[prettyslug=\"connect-tools\"]', loops=1, scroll=False)\n        self.highlight('a[prettyslug=\"administration\"]', loops=1, scroll=False)\n        self.highlight('a[prettyslug*=\"tutorials\"]', loops=2, scroll=False)\n        self.highlight(\"h1.article_title\", loops=5, scroll=False)\n\n        self.open(\"https://kubernetes.io/\")\n        self.set_text_content('nav a[href=\"/docs/\"]', \"ALL\")\n        self.set_text_content('nav a[href=\"/blog/\"]', \"YOUR\")\n        self.set_text_content('nav a[href=\"/training/\"]', \"BASE\")\n        self.set_text_content('nav a[href=\"/careers/\"]', \"ARE\")\n        self.set_text_content('nav a[href=\"/partners/\"]', \"BELONG\")\n        self.set_text_content('nav a[href=\"/community/\"]', \"TO\")\n        self.set_text_content(\"nav #navbarDropdown\", \"US\")\n        self.set_text_content(\"nav #navbarDropdownMenuLink\", \".\")\n        if self.is_element_visible(\"h1\"):\n            self.set_text_content(\"h1\", aybabtu)\n        self.highlight(\"nav ul.navbar-nav\", loops=3, scroll=False)\n        self.highlight('nav a[href=\"/docs/\"]', loops=1, scroll=False)\n        self.highlight('nav a[href=\"/blog/\"]', loops=1, scroll=False)\n        self.highlight('nav a[href=\"/training/\"]', loops=2, scroll=False)\n        self.highlight('nav a[href=\"/careers/\"]', loops=1, scroll=False)\n        self.highlight('nav a[href=\"/partners/\"]', loops=1, scroll=False)\n        self.highlight('nav a[href=\"/community/\"]', loops=1, scroll=False)\n        self.highlight(\"nav #navbarDropdown\", loops=2, scroll=False)\n        if self.is_element_visible(\"h1\"):\n            self.highlight(\"h1\", loops=6, scroll=False)\n\n        self.open(\"https://www.selenium.dev/\")\n        if self.is_element_visible('button[data-dismiss=\"alert\"] span'):\n            self.js_click('button[data-dismiss=\"alert\"] span', scroll=False)\n        self.set_attributes(\"a.dropdown-toggle\", \"class\", \"nav-link\")\n        self.set_text_content('li a:contains(\"About\")', \"ALL\")\n        self.set_text_content('li a:contains(\"Downloads\")', \"YOUR\")\n        self.set_text_content('li a:contains(\"Documentation\")', \"BASE\")\n        self.set_text_content('li a:contains(\"Projects\")', \"ARE\")\n        self.set_text_content('li a:contains(\"Support\")', \"BELONG\")\n        self.set_text_content('li a:contains(\"Blog\")', \"TO\")\n        self.set_text_content('li a:contains(\"English\")', \"US\")\n        self.set_text_content(\"div.mx-auto p\", aybabtu)\n        self.set_text_content(\"h2\", aybabtu)\n        if self.is_element_visible('button[data-dismiss=\"alert\"] span'):\n            self.js_click('button[data-dismiss=\"alert\"] span', scroll=False)\n        zoom_in = \"div.mx-auto p{zoom: 1.1;-moz-transform: scale(1.1);}\"\n        self.add_css_style(zoom_in)\n        self.highlight(\"div#main_navbar\", loops=1, scroll=False)\n        self.highlight('li a:contains(\"ALL\")', loops=1, scroll=False)\n        self.highlight('li a:contains(\"YOUR\")', loops=1, scroll=False)\n        self.highlight('li a:contains(\"BASE\")', loops=2, scroll=False)\n        self.highlight('li a:contains(\"ARE\")', loops=1, scroll=False)\n        self.highlight('li a:contains(\"BELONG\")', loops=1, scroll=False)\n        self.highlight('li a:contains(\"TO\")', loops=1, scroll=False)\n        self.highlight('li a:contains(\"US\")', loops=2, scroll=False)\n        self.highlight(\"div.mx-auto p\", loops=6, scroll=False)\n        self.highlight(\"h2\", loops=8, scroll=False)\n\n        self.open(\"https://www.python.org/\")\n        self.set_text_content('a[class=\"donate-button\"]', ayb)\n        self.set_text_content(\"#about a\", \"ALL\")\n        self.set_text_content(\"#downloads a\", \"YOUR\")\n        self.set_text_content(\"#documentation a\", \"BASE\")\n        self.set_text_content(\"#community a\", \"ARE\")\n        self.set_text_content(\"#success-stories a\", \"BELONG\")\n        self.set_text_content(\"#news a\", \"TO\")\n        self.set_text_content(\"#events a\", \"US\")\n        self.highlight('a[class=\"donate-button\"]', loops=4, scroll=False)\n        self.highlight(\"nav#mainnav\", loops=5, scroll=False)\n        self.highlight(\"#about a\", loops=1, scroll=False)\n        self.highlight(\"#downloads a\", loops=1, scroll=False)\n        self.highlight(\"#documentation a\", loops=2, scroll=False)\n        self.highlight(\"#community a\", loops=1, scroll=False)\n        self.highlight(\"#success-stories a\", loops=1, scroll=False)\n        self.highlight(\"#news a\", loops=1, scroll=False)\n        self.highlight(\"#events a\", loops=2, scroll=False)\n\n        self.open(\"https://docs.pytest.org/\")\n        self.set_text_content(\"h1\", \"pytest: \" + aybabtu)\n        self.highlight(\"h1\", loops=10, scroll=False)\n\n        self.open(\"https://wordpress.com/\")\n        zoom_out = \"h1{zoom: 0.8;-moz-transform: scale(0.8);}\"\n        self.add_css_style(zoom_out)\n        zoom_in = \"a.wp-element-button{zoom: 1.4;-moz-transform: scale(1.4);}\"\n        self.add_css_style(zoom_in)\n        self.set_text_content(\"h1\", aybabtu)\n        self.set_text_content(\"a.wp-element-button\", \"Use SeleniumBase!\")\n        self.highlight(\"h1\", loops=8, scroll=False)\n        self.highlight(\"a.wp-element-button\", loops=8, scroll=False)\n\n        self.open(\"https://seleniumbase.com/\")\n        self.set_text_content(\"h1\", aybabtu)\n        self.highlight(\"h1\", loops=10, scroll=False)\n\n        self.open(\"https://pypi.org/\")\n        self.set_text_content('a[href=\"/sponsors/\"]', aybabtu)\n        self.set_text_content(\"h1\", aybabtu)\n        self.set_value(\"input#search\", aybabtu, scroll=False)\n        self.highlight('a[href=\"/sponsors/\"]', loops=6, scroll=False)\n        self.highlight(\"h1\", loops=6, scroll=False)\n        self.highlight(\"input#search\", loops=8, scroll=False)\n\n        self.open(\"https://status.iboss.com/ibcloud/app/cloudStatus.html\")\n        self.wait_for_element_clickable('div[translate*=\"cloudStatus\"]')\n        self.set_text_content('div[translate*=\"cloudStatus\"]', ayb)\n        self.set_text_content('div[translate*=\"maintenance\"]', \"ARE\")\n        self.set_text_content('div[translate*=\"advisory\"]', \"BELONG\")\n        self.set_text_content('div[translate*=\"incident\"]', \"TO US\")\n        self.set_text_content(\"h1\", \"Cloud Status - \" + aybabtu)\n        self.highlight(\"nav div.ibcloud-header-contents\", loops=3)\n        self.highlight('div[translate*=\"cloudStatus\"]', loops=4)\n        self.highlight('div[translate*=\"maintenance\"]', loops=1)\n        self.highlight('div[translate*=\"advisory\"]', loops=1)\n        self.highlight('div[translate*=\"incident\"]', loops=3)\n        self.highlight(\"h1\", loops=9, scroll=False)\n\n        self.open(\"https://git-scm.com/\")\n        self.set_text_content(\"span#tagline\", aybabtu)\n        self.highlight(\"span#tagline\", loops=10, scroll=False)\n\n        self.open(\"https://pragprog.com/\")\n        self.set_text_content(\"header p\", aybabtu)\n        zoom_in = \"header p{zoom: 1.35;-moz-transform: scale(1.35);}\"\n        self.add_css_style(zoom_in)\n        self.highlight(\"header p\", loops=10, scroll=False)\n\n        self.open(\"https://seleniumbase.io/\")\n        self.set_text_content(\"h1\", aybabtu)\n        self.highlight(\"h1\", loops=10, scroll=False)\n"
  },
  {
    "path": "examples/handle_alert_test.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass HandleAlertTests(BaseCase):\n    def test_alerts(self):\n        self.open(\"about:blank\")\n        self.execute_script('window.alert(\"ALERT!!!\");')\n        self.sleep(1)  # Not needed (Lets you see the alert pop up)\n        self.assert_true(self.is_alert_present())\n        self.accept_alert()\n        self.sleep(1)  # Not needed (Lets you see the alert go away)\n        self.execute_script('window.prompt(\"My Prompt\",\"defaultText\");')\n        self.sleep(1)  # Not needed (Lets you see the alert pop up)\n        alert = self.switch_to_alert()\n        self.assert_equal(alert.text, \"My Prompt\")  # Not input field\n        self.dismiss_alert()\n        self.sleep(1)  # Not needed (Lets you see the alert go away)\n        self.assert_false(self.is_alert_present())\n        if self.browser == \"safari\" and self._reuse_session:\n            # Alerts can freeze Safari if reusing the browser session\n            self.driver.quit()\n"
  },
  {
    "path": "examples/iframe_tests.py",
    "content": "\"\"\"Use SeleniumBase methods to interact with iframes.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass FrameTests(BaseCase):\r\n    def test_iframe_basics(self):\r\n        self.open(\"https://seleniumbase.io/w3schools/iframes.html\")\r\n        self.assert_title(\"iframe Testing\")\r\n        self.click(\"button#runbtn\")\r\n        self.switch_to_frame(\"iframeResult\")  # Enter the iframe\r\n        self.assert_text(\"HTML Iframes\", \"h2\")\r\n        self.switch_to_frame('[title*=\"Iframe\"]')  # Enter iframe inside iframe\r\n        self.assert_text(\"This page is displayed in an iframe\", \"h1\")\r\n        self.switch_to_parent_frame()  # Exit only the inner iframe\r\n        self.assert_text(\"Use CSS width & height to specify\", \"p\")\r\n        self.switch_to_frame('[title*=\"Iframe\"]')  # Enter iframe inside iframe\r\n        self.assert_text(\"seleniumbase.io/w3schools/iframes\", \"a\")\r\n        self.switch_to_default_content()  # Exit all iframes\r\n        self.click(\"button#runbtn\")\r\n        self.switch_to_frame(\"iframeResult\")  # Go back inside 1st iframe\r\n        self.highlight('iframe[title=\"Iframe Example\"]')\r\n\r\n    def test_iframes_with_context_manager(self):\r\n        self.open(\"https://seleniumbase.io/w3schools/iframes.html\")\r\n        self.assert_title(\"iframe Testing\")\r\n        self.click(\"button#runbtn\")\r\n        with self.frame_switch(\"iframeResult\"):\r\n            self.assert_text(\"HTML Iframes\", \"h2\")\r\n            with self.frame_switch('[title*=\"Iframe\"]'):\r\n                self.assert_text(\"This page is displayed in an iframe\", \"h1\")\r\n            self.assert_text(\"Use CSS width & height to specify\", \"p\")\r\n            with self.frame_switch('[title*=\"Iframe\"]'):\r\n                self.assert_text(\"seleniumbase.io/w3schools/iframes\", \"a\")\r\n        self.click(\"button#runbtn\")\r\n        with self.frame_switch(\"iframeResult\"):\r\n            self.highlight('iframe[title=\"Iframe Example\"]')\r\n\r\n    def test_set_content_to_frame(self):\r\n        self.open(\"https://seleniumbase.io/w3schools/iframes.html\")\r\n        self.assert_title(\"iframe Testing\")\r\n        self.click(\"button#runbtn\")\r\n        self.set_content_to_frame(\"iframeResult\")\r\n        self.highlight('iframe[title=\"Iframe Example\"]')\r\n        self.set_content_to_frame(\"iframe\")\r\n        self.assert_element_not_visible(\"iframe\")\r\n        self.highlight(\"body\")\r\n        self.set_content_to_parent()\r\n        self.highlight('iframe[title=\"Iframe Example\"]')\r\n        self.set_content_to_default()\r\n        self.click(\"button#runbtn\")\r\n        self.highlight(\"#iframeResult\")\r\n"
  },
  {
    "path": "examples/locale_code_test.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass LocaleCodeTests(BaseCase):\r\n    def test_locale_code(self):\r\n        self.open(\"about:blank\")\r\n        locale_code = self.get_locale_code()  # navigator.language\r\n        print(\"\\nYour Browser's Locale Code: %s\" % locale_code)\r\n        if self.browser == \"chrome\" and not self.headless:\r\n            self.open(\"chrome://settings/languages\")\r\n            language_info = self.get_text(\r\n                \"settings-ui::shadow \"\r\n                \"settings-main::shadow \"\r\n                \"settings-languages-page-index::shadow \"\r\n                \"settings-languages-page::shadow \"\r\n                \"settings-section \"\r\n                \"#languagesSection div.start div\"\r\n            )\r\n            print(\"Language info (chrome://settings/languages):\")\r\n            print(language_info)\r\n            self.sleep(1)\r\n"
  },
  {
    "path": "examples/master_qa/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n![](https://seleniumbase.github.io/cdn/img/masterqa_logo.png \"MasterQA\")\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> MasterQA combines automation with manual verification steps.</h3>\n\n![](https://seleniumbase.github.io/cdn/gif/masterqa6.gif \"MasterQA\")\n\nHere's code from [basic_masterqa_test_0.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/basic_masterqa_test_0.py):\n\n```python\nfrom seleniumbase import MasterQA\n\nclass MasterQATests(MasterQA):\n    def test_masterqa(self):\n        self.open(\"https://xkcd.com/1700/\")\n        self.verify(\"Do you see a webcomic?\")\n        self.open(\"https://seleniumbase.io/demo_page\")\n        self.highlight('table')\n        self.verify(\"Do you see elements in a table?\")\n        self.open(\"https://seleniumbase.io/devices/\")\n        self.highlight(\"div.mockup-wrapper\")\n        self.verify(\"Do you see 4 computer devices?\")\n```\n\nAfter each automation checkpoint, a pop-up window will ask the user questions for each verification command.\n\nWhen the test run completes, as seen from [this longer example](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/masterqa_test_1.py), you'll reach the results page that appears after answering all the verification questions. (Failed verifications generate links to screenshots and log files.)\n\n![](https://seleniumbase.github.io/cdn/img/mqa_hybrid.png \"MasterQA\")\n\nYou may have noticed the ``Incomplete Test Runs`` row on the results page. If the value for that is not zero, it means that one of the automated steps failed. This could happen if you tell your script to perform an action on an element that doesn't exist. Now that we're mixing automation with manual QA, it's good to tell apart the failures from each. The results_table CSV file contains a spreadsheet with the details of each failure (if any) for both manual and automated steps.\n\n**How to run the example tests from scratch:**\n\n```zsh\ngit clone https://github.com/seleniumbase/SeleniumBase.git\ncd SeleniumBase\npip install .\ncd examples/master_qa\npytest basic_masterqa_test_0.py\npytest masterqa_test_1.py\n```\n\nAt the end of your test run, you'll receive a report with results, screenshots, and log files. Close the Results Page window when you're done.\n\n**Check out [masterqa_test_1.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/masterqa_test_1.py) to learn how to write your own MasterQA tests:**\n\nYou'll notice that tests are written the same way as regular [SeleniumBase](https://seleniumbase.com) tests, with the key difference being a different import: ``from seleniumbase import MasterQA`` rather than ``from seleniumbase import BaseCase``. Now your Python test class will import ``MasterQA`` instead of ``BaseCase``.\n\nTo add a manual verification step, use ``self.verify()`` in the code after each part of your test that needs a manual verification step. If you want to include a custom question, add text inside that call (in quotes). Example:\n\n```python\nself.verify()\n\nself.verify(\"Can you find the moon?\")\n```\n\n--------\n\nMasterQA is powered by [SeleniumBase](https://seleniumbase.com), the most advanced open-source automation framework on the [Planet](https://en.wikipedia.org/wiki/Earth).\n"
  },
  {
    "path": "examples/master_qa/__init__.py",
    "content": ""
  },
  {
    "path": "examples/master_qa/basic_masterqa_test_0.py",
    "content": "from seleniumbase import MasterQA\nMasterQA.main(__name__, __file__)\n\n\nclass MasterQATests(MasterQA):\n    def test_masterqa(self):\n        self.open(\"https://seleniumbase.io/devices/\")\n        self.highlight(\"div.mockup-wrapper\")\n        self.verify(\"Do you see 4 computer devices?\")\n        self.open(\"https://seleniumbase.io/demo_page\")\n        self.highlight(\"table\")\n        self.verify(\"Do you see elements in a table?\")\n        self.open(\"https://xkcd.com/1700/\")\n        self.highlight(\"#comic\")\n        self.verify(\"Do you see a webcomic?\")\n"
  },
  {
    "path": "examples/master_qa/masterqa_test_1.py",
    "content": "from seleniumbase import MasterQA\nMasterQA.main(__name__, __file__)\n\n\nclass MasterQATests(MasterQA):\n    def test_xkcd(self):\n        self.open(\"https://xkcd.com/1512/\")\n        for i in range(4):\n            self.click('a[rel=\"next\"]')\n        for i in range(3):\n            self.click('a[rel=\"prev\"]')\n        self.verify()\n        self.open(\"https://xkcd.com/1520/\")\n        for i in range(2):\n            self.click('a[rel=\"next\"]')\n        self.verify(\"Can you find the moon?\")\n        self.click('a[rel=\"next\"]')\n        self.verify(\"Do the drones look safe?\")\n\n        self.open(\"https://seleniumbase.io/devices/\")\n        self.type(\"input#urlInput\", \"seleniumbase.io/error_page\\n\")\n        self.verify(\"Do you see Octocat in a Jedi knight robe?\")\n\n        self.open(\"https://xkcd.com/213/\")\n        for i in range(5):\n            self.click('a[rel=\"prev\"]')\n        self.verify(\"Does the page say 'Abnormal Expressions'?\")\n"
  },
  {
    "path": "examples/master_qa/pytest.ini",
    "content": "[pytest]\n\n# Display console output. Disable cacheprovider:\naddopts = --capture=tee-sys -p no:cacheprovider\n\n# Skip these directories during test collection:\nnorecursedirs = .* build dist recordings temp assets\n\n# Ignore DeprecationWarning, PytestUnknownMarkWarning\nfilterwarnings =\n    ignore::pytest.PytestWarning\n    ignore:.*U.*mode is deprecated:DeprecationWarning\n\n# Configure the junit_family option explicitly:\njunit_family = legacy\n\n# Set pytest discovery rules:\n# (Most of the rules here are similar to the default rules.)\n# (Inheriting unittest.TestCase could override these rules.)\npython_files = test_*.py *_test.py *_tests.py *_suite.py *_test_*.py\npython_classes = Test* *Test* *Test *Tests *Suite\npython_functions = test_*\n\n# Common pytest markers used in examples:\n# (pytest may require marker registration to prevent warnings.)\n# (Future versions may turn those marker warnings into errors.)\nmarkers =\n    marker1: custom marker\n    marker2: custom marker\n    marker3: custom marker\n    marker_test_suite: custom marker\n    expected_failure: custom marker\n    local: custom marker\n    remote: custom marker\n    offline: custom marker\n    develop: custom marker\n    qa: custom marker\n    ci: custom marker\n    e2e: custom marker\n    ready: custom marker\n    smoke: custom marker\n    deploy: custom marker\n    active: custom marker\n    master: custom marker\n    release: custom marker\n    staging: custom marker\n    production: custom marker\n"
  },
  {
    "path": "examples/migration/__init__.py",
    "content": ""
  },
  {
    "path": "examples/migration/protractor/ReadMe.md",
    "content": "## Support for migrating from Protractor to SeleniumBase\n\n🔵 The Protractor/Angular tests from [github.com/angular/protractor/tree/master/example](https://github.com/angular/protractor/tree/master/example) have been migrated to SeleniumBase and placed in this directory.\n\n🔵 Protractor tests that end in `.spec.js` will now end in `_test.py` for the conversion to SeleniumBase/Python format with pytest auto-discovery.\n\n✅ Here's a test run with `pytest` using `--reuse-session` mode and Chromium `--guest` mode:\n\n```zsh\n$ pytest --rs -v --guest\n=========================== test session starts ============================\nplatform darwin -- Python 3.11.9, pytest-8.3.3, pluggy-1.5.0 -- /Users/michael/.virtualenvs/sbase11/bin/python\nmetadata: {'Python': '3.11.9', 'Platform': 'macOS-13.2.1-arm64-arm-64bit', 'Packages': {'pytest': '8.3.3', 'pluggy': '1.5.0'}, 'Plugins': {'cov': '6.0.0', 'html': '2.0.1', 'metadata': '3.1.1', 'seleniumbase': '4.33.2', 'ordering': '0.6', 'rerunfailures': '15.0', 'xdist': '3.6.1'}}\nrootdir: /Users/michael/github/SeleniumBase/examples\nconfigfile: pytest.ini\nplugins: html-2.0.1, metadata-3.1.1, seleniumbase-4.33.2, ordering-0.6, rerunfailures-15.0, xdist-3.6.1\ncollected 4 items\n\nexample_test.py::AngularJSHomePageTests::test_greet_user PASSED\nexample_test.py::AngularJSHomePageTests::test_todo_list PASSED\ninput_test.py::AngularMaterialInputTests::test_invalid_input PASSED\nmat_paginator_test.py::AngularMaterialPaginatorTests::test_pagination PASSED\n\n============================ 4 passed in 4.24s =============================\n```\n"
  },
  {
    "path": "examples/migration/protractor/__init__.py",
    "content": ""
  },
  {
    "path": "examples/migration/protractor/example_spec.js",
    "content": "describe('angularjs homepage', function() {\n  it('should greet the named user', function() {\n    browser.get('http://www.angularjs.org');\n\n    element(by.model('yourName')).sendKeys('Julie');\n\n    var greeting = element(by.binding('yourName'));\n\n    expect(greeting.getText()).toEqual('Hello Julie!');\n  });\n\n  describe('todo list', function() {\n    var todoList;\n\n    beforeEach(function() {\n      browser.get('http://www.angularjs.org');\n\n      todoList = element.all(by.repeater('todo in todoList.todos'));\n    });\n\n    it('should list todos', function() {\n      expect(todoList.count()).toEqual(2);\n      expect(todoList.get(1).getText()).toEqual('build an AngularJS app');\n    });\n\n    it('should add a todo', function() {\n      var addTodo = element(by.model('todoList.todoText'));\n      var addButton = element(by.css('[value=\"add\"]'));\n\n      addTodo.sendKeys('write a protractor test');\n      addButton.click();\n\n      expect(todoList.count()).toEqual(3);\n      expect(todoList.get(2).getText()).toEqual('write a protractor test');\n    });\n  });\n});\n"
  },
  {
    "path": "examples/migration/protractor/example_test.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass AngularJSHomePageTests(BaseCase):\r\n    def test_greet_user(self):\r\n        self.open(\"http://www.angularjs.org\")\r\n        self.type('[ng-model=\"yourName\"]', \"Julie\")\r\n        self.assert_exact_text(\"Hello Julie!\", \"h1.ng-binding\")\r\n\r\n    def test_todo_list(self):\r\n        self.open(\"http://www.angularjs.org\")\r\n        todo_selector = '[ng-repeat=\"todo in todoList.todos\"]'\r\n        # Verify that the todos are listed\r\n        self.wait_for_element(todo_selector)\r\n        todos = self.find_visible_elements(todo_selector)\r\n        self.assert_equal(len(todos), 2)\r\n        self.assert_equal(todos[1].text.strip(), \"build an AngularJS app\")\r\n        # Verify adding a new todo\r\n        self.type('[ng-model=\"todoList.todoText\"]', \"write a protractor test\")\r\n        self.click('[value=\"add\"]')\r\n        todos = self.find_visible_elements(todo_selector)\r\n        self.assert_equal(len(todos), 3)\r\n        self.assert_equal(todos[2].text.strip(), \"write a protractor test\")\r\n"
  },
  {
    "path": "examples/migration/protractor/input_spec.js",
    "content": "describe('angular-material input component page', function() {\n  const EC = protractor.ExpectedConditions;\n\n  it('Should change input component value', async() => {\n    await browser.get('https://material.angular.io/components/input/examples');\n      \n    await browser.wait(EC.elementToBeClickable($('.mat-button-wrapper > .mat-icon')), 5000);\n      \n    const emailInputField = $$('#mat-input-1').get(1);\n      \n    await emailInputField.sendKeys('invalid');\n      \n    expect($('mat-error').isPresent()).toBe(true);\n  });\n});\n"
  },
  {
    "path": "examples/migration/protractor/input_test.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass AngularMaterialInputTests(BaseCase):\r\n    def test_invalid_input(self):\r\n        # Test that there's an error for an invalid input\r\n        self.open(\"https://material.angular.io/components/input/examples\")\r\n        self.type('input[type=\"email\"]', \"invalid\")\r\n        self.assert_element(\"mat-error\")\r\n"
  },
  {
    "path": "examples/migration/protractor/mat_paginator_spec.js",
    "content": "describe('angular-material paginator component page', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async() => {\n    await browser.get('https://material.angular.io/components/paginator/examples');\n\n    await browser.wait(EC.elementToBeClickable($('.mat-button-wrapper > .mat-icon')), 5000);\n  });\n\n  it('Should navigate to next page', async() => {\n    await $('button[aria-label=\\'Next page\\']').click();\n    \n    await expect($('.mat-paginator-range-label').getAttribute('innerText')).toEqual('Page 2 of 10');\n  });\n\n  it('Should navigate to previous page', async() => {\n    await $('button[aria-label=\\'Previous page\\']').click();\n    \n    await expect($('.mat-paginator-range-label').getAttribute('innerText')).toEqual('Page 1 of 10');\n  });\n\n  it('Should change list length to 5 items per page', async() => {\n    await $('mat-select > div').click();\n    \n    const fiveItemsOption = $$('mat-option > .mat-option-text').first();\n\n    await fiveItemsOption.click();\n    \n    await expect($('.mat-paginator-range-label').getAttribute('innerText')).toEqual('Page 1 of 20');\n  });\n});\n"
  },
  {
    "path": "examples/migration/protractor/mat_paginator_test.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass AngularMaterialPaginatorTests(BaseCase):\r\n    def test_pagination(self):\r\n        self.open(\"https://material.angular.io/components/paginator/examples\")\r\n        self.click_if_visible(\"button.mat-mdc-button\")\r\n        self.scroll_to(\"div.mat-mdc-paginator-page-size\")\r\n        # Set pagination to 5 items per page\r\n        self.click(\"mat-select > div\")\r\n        self.click(\"mat-option:nth-of-type(1)\")\r\n        # Verify navigation to the next page\r\n        self.click('button[aria-label=\"Next page\"]')\r\n        self.assert_exact_text(\r\n            \"6 – 10 of 50\", \".mat-mdc-paginator-range-label\"\r\n        )\r\n        # Verify navigation to the previous page\r\n        self.click('button[aria-label=\"Previous page\"]')\r\n        self.assert_exact_text(\r\n            \"1 – 5 of 50\", \".mat-mdc-paginator-range-label\"\r\n        )\r\n        # Set pagination to 10 items per page\r\n        self.click(\"mat-select > div\")\r\n        self.click(\"mat-option:nth-of-type(2)\")\r\n        # Verify page with correct number of pages\r\n        self.assert_exact_text(\r\n            \"1 – 10 of 50\", \".mat-mdc-paginator-range-label\"\r\n        )\r\n"
  },
  {
    "path": "examples/migration/raw_selenium/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## ✅ Support for migrating from raw Selenium to SeleniumBase\n\n\n### 🔵 Here are some examples that can help you understand how to migrate from raw Selenium to SeleniumBase\n\nThe five main examples in the [examples/migration/raw_selenium](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium) folder are:\n\n* [flaky_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/flaky_messy_raw.py)\n* [long_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/long_messy_raw.py)\n* [messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/messy_raw.py)\n* [refined_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/refined_raw.py)\n* [simple_sbase.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/simple_sbase.py)\n\nEach of these examples is structured as a test that can be run with `pytest`. They all inherit `unittest.TestCase` either directly, or via `seleniumbase.BaseCase`, which extends it. This provides automatically-called `setUp()` and `tearDown()` methods before and after each test.\n\n> These examples show the evolution of tests from raw Selenium to SeleniumBase. By understanding common progressions of Selenium engineers, you can avoid making the same mistakes as they did, and learn to write good tests efficiently without the long learning curve.\n\n* [flaky_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/flaky_messy_raw.py)\n\nThis is common example of how newcomers to Selenium write tests (assuming they've already learned how to break out reusuable code into `setUp()` and `tearDown()` methods). It uses `find_element()` calls, which can lead to flaky tests because those calls fail if a page element is slow to load.\n\n* [long_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/long_messy_raw.py)\n\nAt the next stage of learning, newcomers to Selenium realize that their tests are flaky, so they start replacing existing `find_element()` calls with `WebDriverWait` and internal Selenium `expected_conditions` methods, such as `visibility_of_element_located` and `element_to_be_clickable`. This can result in long/messy tests that are unmaintainable if not written carefully.\n\n* [messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/messy_raw.py)\n\nBy this stage, newcomers to Selenium have evolved into legitimate test automation engineers. They have become better at writing reusable code, so they've broken down the long `WebDriverWait` and `expected_conditions` calls into shorter method calls, which are easier to read, but could still be improved on for better maintainability. Here, individual page actions are still written out as multiple lines of code, (or multiple method calls per line), which isn't efficient.\n\n* [refined_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/refined_raw.py)\n\nBy now, the test automation engineer has become an expert in breaking out code into reusable methods, and the test itself has been simplified down to a single page action per line. The code is easy to read and easy to maintain. The error output is also simplified. The journey of writing a complete test automation framework for Selenium has begun.\n\n* [simple_sbase.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/simple_sbase.py)\n\nWith a complete test automation framework built, most of the hard work is already done for you. By importing `BaseCase` into your test classes, your tests gain access to all SeleniumBase methods, which can simplify your code. SeleniumBase also provides a lot of additional functionality that isn't included with raw Selenium.\n\n\n### 🔵 How is SeleniumBase different from raw Selenium?\n\n<div>\n\n<p>💡 SeleniumBase is a Python framework for browser automation and testing. SeleniumBase uses <a href=\"https://www.w3.org/TR/webdriver2/#endpoints\" target=\"_blank\">Selenium/WebDriver</a> APIs and incorporates test-runners such as <code translate=\"no\">pytest</code>, <code translate=\"no\">pynose</code>, and <code translate=\"no\">behave</code> to provide organized structure, test discovery, test execution, test state (<i>eg. passed, failed, or skipped</i>), and command-line options for changing default settings (<i>eg. browser selection</i>). With raw Selenium, you would need to set up your own options-parser for configuring tests from the command-line.</p>\n\n<p>💡 SeleniumBase's driver manager gives you more control over automatic driver downloads. (Use <code translate=\"no\">--driver-version=VER</code> with your <code translate=\"no\">pytest</code> run command to specify the version.) By default, SeleniumBase will download a driver version that matches your major browser version if not set.</p>\n\n<p>💡 SeleniumBase automatically detects between CSS Selectors and XPath, which means you don't need to specify the type of selector in your commands (<i>but optionally you could</i>).</p>\n\n<p>💡 SeleniumBase methods often perform multiple actions in a single method call. For example, <code translate=\"no\">self.type(selector, text)</code> does the following:<br />1. Waits for the element to be visible.<br />2. Waits for the element to be interactive.<br />3. Clears the text field.<br />4. Types in the new text.<br />5. Presses Enter/Submit if the text ends in <code translate=\"no\">\"\\n\"</code>.<br />With raw Selenium, those actions require multiple method calls.</p>\n\n<p>💡 SeleniumBase uses default timeout values when not set:<br />\n✅ <code translate=\"no\">self.click(\"button\")</code><br />\nWith raw Selenium, methods would fail instantly (<i>by default</i>) if an element needed more time to load:<br />\n❌ <code translate=\"no\">self.driver.find_element(by=\"css selector\", value=\"button\").click()</code><br />\n(Reliable code is better than unreliable code.)</p>\n\n<p>💡 SeleniumBase lets you change the explicit timeout values of methods:<br />\n✅ <code translate=\"no\">self.click(\"button\", timeout=10)</code><br />\nWith raw Selenium, that requires more code:<br />\n❌ <code translate=\"no\">WebDriverWait(driver, 10).until(EC.element_to_be_clickable(\"css selector\", \"button\")).click()</code><br />\n(Simple code is better than complex code.)</p>\n\n<p>💡 SeleniumBase gives you clean error output when a test fails. With raw Selenium, error messages can get very messy.</p>\n\n<p>💡 SeleniumBase gives you the option to generate a dashboard and reports for tests. It also saves screenshots from failing tests to the <code translate=\"no\">./latest_logs/</code> folder. Raw <a href=\"https://www.selenium.dev/documentation/webdriver/\" translate=\"no\" target=\"_blank\">Selenium</a> does not have these options out-of-the-box.</p>\n\n<p>💡 SeleniumBase includes desktop GUI apps for running tests, such as <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/commander.md\" translate=\"no\">SeleniumBase Commander</a> for <code translate=\"no\">pytest</code> and <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/ReadMe.md\" translate=\"no\">SeleniumBase Behave GUI</a> for <code translate=\"no\">behave</code>.</p>\n\n<p>💡 SeleniumBase has its own <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md\">Recorder / Test Generator</a> for creating tests from manual browser actions.</p>\n\n<p>💡 SeleniumBase comes with <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md\">test case management software, (\"Case Plans\")</a>, for organizing tests and step descriptions.</p>\n\n<p>💡 SeleniumBase includes tools for <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/chart_maker/ReadMe.md\">building data apps, (\"Chart Maker\")</a>, which can generate JavaScript from Python.</p>\n\n</div>\n\n--------\n\n[<img src=\"https://seleniumbase.github.io/cdn/img/fancy_logo_14.png\" title=\"SeleniumBase\" width=\"290\">](https://github.com/seleniumbase/SeleniumBase)\n"
  },
  {
    "path": "examples/migration/raw_selenium/__init__.py",
    "content": ""
  },
  {
    "path": "examples/migration/raw_selenium/flaky_messy_raw.py",
    "content": "\"\"\"Flaky Raw Selenium Example - (ONLY Selenium / NO SeleniumBase)\"\"\"\r\nimport sys\r\nfrom selenium import webdriver\r\nfrom selenium.webdriver.chrome.service import Service\r\nfrom selenium.webdriver.common.by import By\r\nfrom unittest import TestCase\r\n\r\n\r\nclass FlakyMessyRawSelenium(TestCase):\r\n    def setUp(self):\r\n        self.driver = None\r\n        options = webdriver.ChromeOptions()\r\n        options.add_argument(\"--disable-notifications\")\r\n        if \"linux\" in sys.platform:\r\n            options.add_argument(\"--headless=new\")\r\n        options.add_experimental_option(\r\n            \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"],\r\n        )\r\n        prefs = {\r\n            \"credentials_enable_service\": False,\r\n            \"profile.password_manager_enabled\": False,\r\n        }\r\n        options.add_experimental_option(\"prefs\", prefs)\r\n        service = Service(service_args=[\"--disable-build-check\"])\r\n        self.driver = webdriver.Chrome(options=options, service=service)\r\n\r\n    def tearDown(self):\r\n        if self.driver:\r\n            try:\r\n                if self.driver.service.process:\r\n                    self.driver.quit()\r\n            except Exception:\r\n                pass\r\n\r\n    def is_element_visible(self, selector, by=\"css selector\"):\r\n        try:\r\n            element = self.driver.find_element(by, selector)\r\n            if element.is_displayed():\r\n                return True\r\n        except Exception:\r\n            pass\r\n        return False\r\n\r\n    def test_add_item_to_cart(self):\r\n        self.driver.get(\"https://www.saucedemo.com\")\r\n        by_css = By.CSS_SELECTOR  # \"css selector\"\r\n        element = self.driver.find_element(by_css, \"#user-name\")\r\n        element.clear()\r\n        element.send_keys(\"standard_user\")\r\n        element = self.driver.find_element(by_css, \"#password\")\r\n        element.clear()\r\n        element.send_keys(\"secret_sauce\")\r\n        element.submit()\r\n        self.driver.find_element(by_css, \"div.inventory_list\")\r\n        element = self.driver.find_element(by_css, \"span.title\")\r\n        self.assertEqual(element.text, \"Products\")\r\n        self.driver.find_element(by_css, 'button[name*=\"backpack\"]').click()\r\n        self.driver.find_element(by_css, \"#shopping_cart_container a\").click()\r\n        element = self.driver.find_element(by_css, \"span.title\")\r\n        self.assertEqual(element.text, \"Your Cart\")\r\n        element = self.driver.find_element(by_css, \"div.cart_item\")\r\n        self.assertIn(\"Backpack\", element.text)\r\n        self.driver.find_element(by_css, \"#remove-sauce-labs-backpack\").click()\r\n        self.assertFalse(self.is_element_visible(\"div.cart_item\"))\r\n        self.driver.find_element(by_css, \"#react-burger-menu-btn\").click()\r\n        self.driver.find_element(by_css, \"a#logout_sidebar_link\").click()\r\n        self.driver.find_element(by_css, \"input#login-button\")\r\n\r\n\r\n# When run with \"python\" instead of \"pytest\" or \"python -m unittest\"\r\nif __name__ == \"__main__\":\r\n    from unittest import main\r\n    main()\r\n"
  },
  {
    "path": "examples/migration/raw_selenium/long_messy_raw.py",
    "content": "\"\"\"Long & Messy Raw Selenium Example - (ONLY Selenium / NO SeleniumBase)\"\"\"\r\nimport sys\r\nfrom selenium import webdriver\r\nfrom selenium.webdriver.chrome.service import Service\r\nfrom selenium.webdriver.common.by import By\r\nfrom selenium.webdriver.support import expected_conditions as EC\r\nfrom selenium.webdriver.support.ui import WebDriverWait\r\nfrom unittest import TestCase\r\n\r\n\r\nclass LongMessyRawSelenium(TestCase):\r\n    def setUp(self):\r\n        self.driver = None\r\n        options = webdriver.ChromeOptions()\r\n        options.add_argument(\"--disable-notifications\")\r\n        if \"linux\" in sys.platform:\r\n            options.add_argument(\"--headless=new\")\r\n        options.add_experimental_option(\r\n            \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"],\r\n        )\r\n        prefs = {\r\n            \"credentials_enable_service\": False,\r\n            \"profile.password_manager_enabled\": False,\r\n        }\r\n        options.add_experimental_option(\"prefs\", prefs)\r\n        service = Service(service_args=[\"--disable-build-check\"])\r\n        self.driver = webdriver.Chrome(options=options, service=service)\r\n\r\n    def tearDown(self):\r\n        if self.driver:\r\n            try:\r\n                if self.driver.service.process:\r\n                    self.driver.quit()\r\n            except Exception:\r\n                pass\r\n\r\n    def test_add_item_to_cart(self):\r\n        self.driver.get(\"https://www.saucedemo.com\")\r\n        by_css = By.CSS_SELECTOR  # \"css selector\"\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.element_to_be_clickable((by_css, \"#user-name\"))\r\n        )\r\n        element.clear()\r\n        element.send_keys(\"standard_user\")\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.element_to_be_clickable((by_css, \"#password\"))\r\n        )\r\n        element.clear()\r\n        element.send_keys(\"secret_sauce\")\r\n        element.submit()\r\n        WebDriverWait(self.driver, 10).until(\r\n            EC.visibility_of_element_located((by_css, \"div.inventory_list\"))\r\n        )\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.visibility_of_element_located((by_css, \"span.title\"))\r\n        )\r\n        self.assertEqual(element.text, \"Products\")\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.element_to_be_clickable((by_css, 'button[name*=\"backpack\"]'))\r\n        )\r\n        element.click()\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.element_to_be_clickable((by_css, \"#shopping_cart_container a\"))\r\n        )\r\n        element.click()\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.visibility_of_element_located((by_css, \"span.title\"))\r\n        )\r\n        self.assertEqual(element.text, \"Your Cart\")\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.visibility_of_element_located((by_css, \"div.cart_item\"))\r\n        )\r\n        self.assertIn(\"Backpack\", element.text)\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.element_to_be_clickable((by_css, \"#remove-sauce-labs-backpack\"))\r\n        )\r\n        element.click()\r\n        WebDriverWait(self.driver, 10).until(\r\n            EC.invisibility_of_element((by_css, \"div.cart_item\"))\r\n        )\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.element_to_be_clickable((by_css, \"#react-burger-menu-btn\"))\r\n        )\r\n        element.click()\r\n        element = WebDriverWait(self.driver, 10).until(\r\n            EC.element_to_be_clickable((by_css, \"a#logout_sidebar_link\"))\r\n        )\r\n        element.click()\r\n        WebDriverWait(self.driver, 10).until(\r\n            EC.visibility_of_element_located((by_css, \"input#login-button\"))\r\n        )\r\n\r\n\r\n# When run with \"python\" instead of \"pytest\" or \"python -m unittest\"\r\nif __name__ == \"__main__\":\r\n    from unittest import main\r\n    main()\r\n"
  },
  {
    "path": "examples/migration/raw_selenium/messy_raw.py",
    "content": "\"\"\"Messy Raw Selenium Example - (ONLY Selenium / NO SeleniumBase)\"\"\"\r\nimport sys\r\nfrom selenium import webdriver\r\nfrom selenium.webdriver.chrome.service import Service\r\nfrom selenium.webdriver.support import expected_conditions as EC\r\nfrom selenium.webdriver.support.ui import WebDriverWait\r\nfrom unittest import TestCase\r\n\r\n\r\nclass MessyRawSelenium(TestCase):\r\n    def setUp(self):\r\n        self.driver = None\r\n        options = webdriver.ChromeOptions()\r\n        options.add_argument(\"--disable-notifications\")\r\n        if \"linux\" in sys.platform:\r\n            options.add_argument(\"--headless=new\")\r\n        options.add_experimental_option(\r\n            \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"],\r\n        )\r\n        prefs = {\r\n            \"credentials_enable_service\": False,\r\n            \"profile.password_manager_enabled\": False,\r\n        }\r\n        options.add_experimental_option(\"prefs\", prefs)\r\n        service = Service(service_args=[\"--disable-build-check\"])\r\n        self.driver = webdriver.Chrome(options=options, service=service)\r\n\r\n    def tearDown(self):\r\n        if self.driver:\r\n            try:\r\n                if self.driver.service.process:\r\n                    self.driver.quit()\r\n            except Exception:\r\n                pass\r\n\r\n    def wait_for_element_visible(\r\n        self, selector, by=\"css selector\", timeout=10\r\n    ):\r\n        return WebDriverWait(self.driver, timeout).until(\r\n            EC.visibility_of_element_located((by, selector))\r\n        )\r\n\r\n    def wait_for_element_clickable(\r\n        self, selector, by=\"css selector\", timeout=10\r\n    ):\r\n        return WebDriverWait(self.driver, timeout).until(\r\n            EC.element_to_be_clickable((by, selector))\r\n        )\r\n\r\n    def wait_for_element_not_visible(\r\n        self, selector, by=\"css selector\", timeout=10\r\n    ):\r\n        return WebDriverWait(self.driver, timeout).until(\r\n            EC.invisibility_of_element((by, selector))\r\n        )\r\n\r\n    def test_add_item_to_cart(self):\r\n        self.driver.get(\"https://www.saucedemo.com\")\r\n        element = self.wait_for_element_clickable(\"#user-name\")\r\n        element.clear()\r\n        element.send_keys(\"standard_user\")\r\n        element = self.wait_for_element_clickable(\"#password\")\r\n        element.clear()\r\n        element.send_keys(\"secret_sauce\")\r\n        element.submit()\r\n        self.wait_for_element_visible(\"div.inventory_list\")\r\n        element = self.wait_for_element_visible(\"span.title\")\r\n        self.assertEqual(element.text, \"Products\")\r\n        self.wait_for_element_clickable('button[name*=\"backpack\"]').click()\r\n        self.wait_for_element_clickable(\"#shopping_cart_container a\").click()\r\n        element = self.wait_for_element_visible(\"span.title\")\r\n        self.assertEqual(element.text, \"Your Cart\")\r\n        element = self.wait_for_element_visible(\"div.cart_item\")\r\n        self.assertIn(\"Backpack\", element.text)\r\n        self.wait_for_element_clickable(\"#remove-sauce-labs-backpack\").click()\r\n        self.wait_for_element_not_visible(\"div.cart_item\")\r\n        self.wait_for_element_clickable(\"#react-burger-menu-btn\").click()\r\n        self.wait_for_element_clickable(\"a#logout_sidebar_link\").click()\r\n        self.wait_for_element_visible(\"input#login-button\")\r\n\r\n\r\n# When run with \"python\" instead of \"pytest\" or \"python -m unittest\"\r\nif __name__ == \"__main__\":\r\n    from unittest import main\r\n    main()\r\n"
  },
  {
    "path": "examples/migration/raw_selenium/pytest.ini",
    "content": "[pytest]\n\n# Display console output. Disable cacheprovider:\naddopts = --capture=tee-sys -p no:cacheprovider\n\n# Skip these directories during test collection:\nnorecursedirs = .* build dist recordings temp assets\n\n# Ignore DeprecationWarning, PytestUnknownMarkWarning\nfilterwarnings =\n    ignore::pytest.PytestWarning\n    ignore:.*U.*mode is deprecated:DeprecationWarning\n\n# Configure the junit_family option explicitly:\njunit_family = legacy\n\n# Set pytest discovery rules:\n# (Most of the rules here are similar to the default rules.)\n# (Inheriting unittest.TestCase could override these rules.)\npython_files = *.py\npython_classes = Test* *Test* *Test *Tests *Suite\npython_functions = test_*\n\n# Common pytest markers used in examples:\n# (pytest may require marker registration to prevent warnings.)\n# (Future versions may turn those marker warnings into errors.)\nmarkers =\n    marker1: custom marker\n    marker2: custom marker\n    marker3: custom marker\n    marker_test_suite: custom marker\n    expected_failure: custom marker\n    local: custom marker\n    remote: custom marker\n    offline: custom marker\n    develop: custom marker\n    qa: custom marker\n    ci: custom marker\n    e2e: custom marker\n    ready: custom marker\n    smoke: custom marker\n    deploy: custom marker\n    active: custom marker\n    master: custom marker\n    release: custom marker\n    staging: custom marker\n    production: custom marker\n"
  },
  {
    "path": "examples/migration/raw_selenium/refined_raw.py",
    "content": "\"\"\"Refined Raw Selenium Example - (ONLY Selenium / NO SeleniumBase)\"\"\"\r\nimport sys\r\nfrom selenium import webdriver\r\nfrom selenium.webdriver.chrome.service import Service\r\nfrom selenium.webdriver.support import expected_conditions as EC\r\nfrom selenium.webdriver.support.ui import WebDriverWait\r\nfrom unittest import TestCase\r\n\r\n\r\nclass RefinedRawSelenium(TestCase):\r\n    def setUp(self):\r\n        self.driver = None\r\n        options = webdriver.ChromeOptions()\r\n        options.add_argument(\"--disable-notifications\")\r\n        if \"linux\" in sys.platform:\r\n            options.add_argument(\"--headless=new\")\r\n        options.add_experimental_option(\r\n            \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"],\r\n        )\r\n        prefs = {\r\n            \"credentials_enable_service\": False,\r\n            \"profile.password_manager_enabled\": False,\r\n        }\r\n        options.add_experimental_option(\"prefs\", prefs)\r\n        service = Service(service_args=[\"--disable-build-check\"])\r\n        self.driver = webdriver.Chrome(options=options, service=service)\r\n\r\n    def tearDown(self):\r\n        if self.driver:\r\n            try:\r\n                if self.driver.service.process:\r\n                    self.driver.quit()\r\n            except Exception:\r\n                pass\r\n\r\n    def wait_for_element_visible(\r\n        self, selector, by=\"css selector\", timeout=10\r\n    ):\r\n        try:\r\n            return WebDriverWait(self.driver, timeout).until(\r\n                EC.visibility_of_element_located((by, selector))\r\n            )\r\n        except Exception:\r\n            raise Exception(\r\n                \"Element {%s} was not visible after %s seconds!\"\r\n                % (selector, timeout)\r\n            )\r\n\r\n    def wait_for_element_clickable(\r\n        self, selector, by=\"css selector\", timeout=10\r\n    ):\r\n        try:\r\n            return WebDriverWait(self.driver, timeout).until(\r\n                EC.element_to_be_clickable((by, selector))\r\n            )\r\n        except Exception:\r\n            raise Exception(\r\n                \"Element {%s} was not visible/clickable after %s seconds!\"\r\n                % (selector, timeout)\r\n            )\r\n\r\n    def wait_for_element_not_visible(\r\n        self, selector, by=\"css selector\", timeout=10\r\n    ):\r\n        try:\r\n            return WebDriverWait(self.driver, timeout).until(\r\n                EC.invisibility_of_element((by, selector))\r\n            )\r\n        except Exception:\r\n            raise Exception(\r\n                \"Element {%s} was still visible after %s seconds!\"\r\n                % (selector, timeout)\r\n            )\r\n\r\n    def open(self, url):\r\n        self.driver.get(url)\r\n\r\n    def click(self, selector, by=\"css selector\", timeout=7):\r\n        el = self.wait_for_element_clickable(selector, by=by, timeout=timeout)\r\n        el.click()\r\n\r\n    def type(self, selector, text, by=\"css selector\", timeout=10):\r\n        el = self.wait_for_element_clickable(selector, by=by, timeout=timeout)\r\n        el.clear()\r\n        if not text.endswith(\"\\n\"):\r\n            el.send_keys(text)\r\n        else:\r\n            el.send_keys(text[:-1])\r\n            el.submit()\r\n\r\n    def assert_element(self, selector, by=\"css selector\", timeout=7):\r\n        self.wait_for_element_visible(selector, by=by, timeout=timeout)\r\n\r\n    def assert_text(self, text, selector=\"html\", by=\"css selector\", timeout=7):\r\n        el = self.wait_for_element_visible(selector, by=by, timeout=timeout)\r\n        self.assertIn(text, el.text)\r\n\r\n    def assert_exact_text(self, text, selector, by=\"css selector\", timeout=7):\r\n        el = self.wait_for_element_visible(selector, by=by, timeout=timeout)\r\n        self.assertEqual(text, el.text)\r\n\r\n    def assert_element_not_visible(\r\n        self, selector, by=\"css selector\", timeout=7\r\n    ):\r\n        self.wait_for_element_not_visible(selector, by=by, timeout=timeout)\r\n\r\n    def test_add_item_to_cart(self):\r\n        self.open(\"https://www.saucedemo.com\")\r\n        self.type(\"#user-name\", \"standard_user\")\r\n        self.type(\"#password\", \"secret_sauce\\n\")\r\n        self.assert_element(\"div.inventory_list\")\r\n        self.assert_text(\"Products\", \"span.title\")\r\n        self.click('button[name*=\"backpack\"]')\r\n        self.click(\"#shopping_cart_container a\")\r\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\r\n        self.assert_text(\"Backpack\", \"div.cart_item\")\r\n        self.click(\"#remove-sauce-labs-backpack\")\r\n        self.assert_element_not_visible(\"div.cart_item\")\r\n        self.click(\"#react-burger-menu-btn\")\r\n        self.click(\"a#logout_sidebar_link\")\r\n        self.assert_element(\"input#login-button\")\r\n\r\n\r\n# When run with \"python\" instead of \"pytest\" or \"python -m unittest\"\r\nif __name__ == \"__main__\":\r\n    from unittest import main\r\n    main()\r\n"
  },
  {
    "path": "examples/migration/raw_selenium/simple_sbase.py",
    "content": "\"\"\"Clean SeleniumBase Example - (Uses simple, reliable methods)\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass CleanSeleniumBase(BaseCase):\r\n    def test_add_item_to_cart(self):\r\n        self.open(\"https://www.saucedemo.com\")\r\n        self.type(\"#user-name\", \"standard_user\")\r\n        self.type(\"#password\", \"secret_sauce\\n\")\r\n        self.assert_element(\"div.inventory_list\")\r\n        self.assert_text(\"Products\", \"span.title\")\r\n        self.click('button[name*=\"backpack\"]')\r\n        self.click(\"#shopping_cart_container a\")\r\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\r\n        self.assert_text(\"Backpack\", \"div.cart_item\")\r\n        self.click(\"#remove-sauce-labs-backpack\")\r\n        self.assert_element_not_visible(\"div.cart_item\")\r\n        self.click(\"#react-burger-menu-btn\")\r\n        self.click(\"a#logout_sidebar_link\")\r\n        self.assert_element(\"input#login-button\")\r\n"
  },
  {
    "path": "examples/multiple_cdp_drivers.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__, \"--uc\")\r\n\r\n\r\nclass MultipleDriversTest(BaseCase):\r\n    def test_multiple_drivers(self):\r\n        url1 = \"https://seleniumbase.io/demo_page\"\r\n        self.activate_cdp_mode(url1)\r\n        driver1 = self.driver\r\n        url2 = \"https://seleniumbase.io/coffee/\"\r\n        driver2 = self.get_new_driver(undetectable=True)\r\n        self.activate_cdp_mode(url2)\r\n        print(\"\\n\" + driver1.get_current_url())\r\n        print(driver2.get_current_url())\r\n"
  },
  {
    "path": "examples/my_first_test.py",
    "content": "\"\"\"A complete end-to-end test for an e-commerce website.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_swag_labs(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n        self.assert_element(\"div.inventory_list\")\n        self.assert_exact_text(\"Products\", \"span.title\")\n        self.click('button[name*=\"backpack\"]')\n        self.click(\"#shopping_cart_container a\")\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click(\"button#checkout\")\n        self.type(\"#first-name\", \"SeleniumBase\")\n        self.type(\"#last-name\", \"Automation\")\n        self.type(\"#postal-code\", \"77123\")\n        self.click(\"input#continue\")\n        self.assert_text(\"Checkout: Overview\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.assert_text(\"29.99\", \"div.inventory_item_price\")\n        self.click(\"button#finish\")\n        self.assert_exact_text(\"Thank you for your order!\", \"h2\")\n        self.assert_element('img[alt=\"Pony Express\"]')\n        self.js_click(\"a#logout_sidebar_link\")\n        self.assert_element(\"div#login_button_container\")\n\n\n#######################################################################\n#\n#    ****  NOTES / USEFUL INFO  ****\n#\n# 1. By default, page elements are identified by \"css selector\".\n#    CSS Guide: \"https://www.w3schools.com/cssref/css_selectors.asp\".\n#    Other selectors include: \"link text\", \"partial link text\", \"name\",\n#    \"class name\", and \"id\", but most of those can be expressed as CSS.\n#\n#    Here's an example of changing the \"by\":\n#    [\n#        self.click('Next', by=\"partial link text\")\n#    ]\n#\n#    XPath is used by default if the arg starts with \"/\", \"./\", or \"(\":\n#    [\n#        self.click('/html/body/div[3]/div[4]/p[2]/a')\n#    ]\n#\n#    If you're completely new to CSS selectors, right-click on a\n#    web page and select \"Inspect\" to see the CSS in the html.\n#\n# 2. Most methods have the optional \"timeout\" argument.\n#    Here's an example of changing the \"timeout\":\n#    [\n#        self.assert_element('img[alt=\"Python\"]', timeout=15)\n#    ]\n#    The \"timeout\" argument tells the method how many seconds to wait\n#    for an element to appear before failing the test. This is\n#    useful if a web page needs additional time to load an element.\n#    If you don't specify a \"timeout\", a default timeout is used.\n#    Default timeouts are configured in seleniumbase/config/settings.py\n#\n# 3. SeleniumBase methods often perform multiple actions.\n#    Example: self.type(SELECTOR, TEXT) does the following:\n#    * Waits for the element to be visible\n#    * Waits for the element to be interactive\n#    * Clears the text field\n#    * Types in the new text\n#    * Presses Enter/Return if the text ends in \"\\n\": element.submit()\n#\n# 4. There are duplicate method names that exist for the same method:\n#    (This makes it easier to switch over from other test frameworks.)\n#    Example:\n#    self.open() = self.visit() = self.open_url() = self.goto()\n#    self.type() = self.update_text() = self.input() = self.fill()\n#    self.send_keys() = self.add_text()\n#    self.get_element() = self.wait_for_element_present()\n#    self.find_element() = self.wait_for_element_visible()\n#                        = self.wait_for_element()\n#    self.assert_element() = self.assert_element_visible()\n#    self.assert_text() = self.assert_text_visible()\n#    self.find_text() = self.wait_for_text_visible()\n#                     = self.wait_for_text()\n#    self.click_link(\"LinkText\") = self.click(\"link=LinkText\")\n#                            = self.click_link_text(\"LinkText\")\n#                            = self.click('a:contains(\"LinkText\")')\n#    * self.get(url) is SPECIAL: *\n#    If {url} is a valid URL, self.get() works just like self.open()\n#    Otherwise {url} becomes a selector for calling self.get_element()\n#\n# 5. There's usually more than one way to do the same thing.\n#    Example 1:\n#    [\n#        self.assert_text(\"xkcd: volume 0\", \"h3\")\n#    ]\n#    Is the same as:\n#    [\n#        text = self.get_text(\"h3\")\n#        self.assert_true(\"xkcd: volume 0\" in text)\n#    ]\n#    Is also the same as:\n#    [\n#        element = self.find_element(\"h3\")\n#        text = element.text\n#        self.assert_true(\"xkcd: volume 0\" in text)\n#    ]\n#\n#    Example 2:\n#    [\n#        self.assert_exact_text(\"xkcd.com\", \"h2\")\n#    ]\n#    Is the same as:\n#    [\n#        text = self.get_text(\"h2\").strip()\n#        self.assert_true(\"xkcd.com\".strip() == text)\n#    ]\n#    Is also the same as:\n#    [\n#        element = self.find_element(\"h2\")\n#        text = element.text.strip()\n#        self.assert_true(\"xkcd.com\".strip() == text)\n#    ]\n#\n#    Example 3:\n#    [\n#        title = self.get_attribute(\"#comic img\", \"title\")\n#    ]\n#    Is the same as:\n#    [\n#        element = self.find_element(\"#comic img\")\n#        title = element.get_attribute(\"title\")\n#    ]\n#\n# 6. self.assert_exact_text(TEXT) ignores leading and trailing\n#    whitespace in the TEXT assertion.\n#    So, self.assert_exact_text(\"Some Text\") accepts [\" Some Text \"].\n#\n# 7. self.js_click(SELECTOR) can be used to click on hidden elements.\n#\n# 8. self.open(URL) will automatically complete URLs missing a prefix.\n#    Example: google.com will become https://google.com before opened.\n#\n# 9. For the full method list, see one of the following:\n#    * SeleniumBase/seleniumbase/fixtures/base_case.py\n#    * SeleniumBase/help_docs/method_summary.md\n#\n# 10. BaseCase.main(__name__, __file__) enables \"python\" to run pytest,\n#     which is useful if someone forgets that tests run with \"pytest\".\n"
  },
  {
    "path": "examples/nth_child_test.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass NthChildSelectorTests(BaseCase):\n    def test_locate_rows_with_colors(self):\n        self.open(\"https://xkcd.com/color/rgb/\")\n        tbody = \"center > table tbody\"\n        if self.headed:\n            self.demo_mode = True\n            self.demo_sleep = 0.5\n            self.message_duration = 2.0\n        else:\n            self.demo_mode = False\n            self.message_duration = 0.1\n        self.highlight(tbody)\n        self.post_message(\"Part 1: Assert text in given row.\")\n        self.assert_text(\"teal\", tbody + \" tr:nth-child(2)\")\n        self.assert_text(\"aqua\", tbody + \" tr:nth-child(4)\")\n        self.assert_text(\"mint\", tbody + \" tr:nth-child(14)\")\n        self.assert_text(\"jade\", tbody + \" tr:nth-child(36)\")\n        soup = self.get_beautiful_soup(self.get_page_source())\n        self.post_message(\"Part 2: Find row with given text.\")\n        self.locate_first_row_with_color(\"rust\", tbody, soup)\n        self.locate_first_row_with_color(\"azure\", tbody, soup)\n        self.locate_first_row_with_color(\"topaz\", tbody, soup)\n\n    def locate_first_row_with_color(self, color, tbody, soup):\n        rows = soup.body.table.find_all(\"tr\")\n        num_rows = len(rows)\n        for row in range(num_rows):\n            row_selector = tbody + \" tr:nth-child(%s)\" % (row + 1)\n            if color in rows[row].text:\n                message = '\"%s\" found on row %s' % (color, row + 1)\n                self.post_message_and_highlight(message, row_selector)\n                return  # Found row and done\n        self.post_error_message('\"%s\" could not be found on any row!' % color)\n"
  },
  {
    "path": "examples/offline_examples/__init__.py",
    "content": ""
  },
  {
    "path": "examples/offline_examples/demo_page.html",
    "content": "<html>\n    <head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=0.41, shrink-to-fit=no\">\n        <title>Web Testing Page</title>\n        <style>\n            html {\n                background-color: #9988ad;\n            }\n            html, body {\n                font-size: 100%;\n                box-sizing: border-box;\n            }\n            body {\n                background-image: none;\n                background-origin: padding-box;\n                background-color: #c6d6f0;\n                padding: 30;\n                margin: 10;\n                font-family: \"Proxima Nova\",\"proxima-nova\",\n                \"Helvetica Neue\",Helvetica,Arial,sans-serif !important;\n                text-rendering: optimizelegibility;\n                -moz-osx-font-smoothing: grayscale;\n                box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.24),\n                1px 2px 12px 0px rgba(0, 0, 0, 0.18) !important;\n            }\n            table {\n                width: 100%;\n                border-collapse: collapse;\n                border-spacing: 0;\n                box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.27),\n                1px 2px 12px 0px rgba(0, 0, 0, 0.21) !important;\n                transition: all 0.15s ease-out 0s;\n                transition-property: all;\n                transition-duration: 0.1s;\n                transition-timing-function: ease-out;\n                transition-delay: 0s;\n            }\n            table:hover {\n                box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.35),\n                1px 2px 12px 0px rgba(0, 0, 0, 0.28) !important;\n            }\n            thead th, thead td {\n                padding: 0.5rem 0.625rem 0.625rem;\n                font-weight: bold;\n                text-align: left;\n            }\n            thead {\n                text-align: center;\n                border: 1px solid #e1e1e1;\n                width: 150%;\n                color: #0C8CDF;\n                background-color: #c0f0ff;\n            }\n            tbody tr:nth-child(even) {\n                background-color: #f1f1f1;\n            }\n            tbody tr:nth-child(odd) {\n                background-color: #ffffff;\n            }\n            tbody tr:nth-child(even):hover {\n                background-color: #f8f8d2;\n            }\n            tbody tr:nth-child(odd):hover {\n                background-color: #ffffe0;\n            }\n            tbody th, tbody td {\n                padding: 0.5rem 0.625rem 0.625rem;\n            }\n            tbody {\n                border: 1px solid #e1e1e1;\n                background-color: #fefefe;\n            }\n            td {\n                padding: 5px 5px 5px 0;\n                vertical-align: top;\n            }\n            h1, h2, h3 {\n                text-align: center;\n                height: 32px;\n                margin: 2px;\n            }\n            h1 {\n                font-size: 24px;\n                color:#0066AA;\n            }\n            h2 {\n                color:#0B7CA7;\n            }\n            h3 {\n                color:#087B95;\n                font-size: 19px;\n            }\n            h1 table {\n                font-size: 27px;\n                text-align: left;\n                padding: 0.5rem 0.625rem 0.625rem;\n                font-weight: bold;\n                padding-right: 10px;\n                padding-left: 20px;\n                padding: 15px 15px 15px 0;\n            }\n            h2 table {\n                color: #0C8CDF;\n                font-size: 16px;\n                text-align: left;\n                font-weight: bold;\n                padding: 5px 5px 5px 0;\n                padding-right: 10px;\n                padding-left: 20px;\n            }\n            textarea {\n                font-family: \"Proxima Nova\",\"proxima-nova\",\n                \"Helvetica Neue\",Helvetica,Arial,sans-serif !important;\n            }\n            button {\n                width: 94%;\n                font-size: 20px;\n            }\n            div.dropbtn {\n                width: 94%;\n                font-size: 17px;\n                text-align: center;\n                height: 23px;\n                padding: 3px 4px;\n                border: 1px solid #e1e1e1;\n            }\n            input {\n                font-size: 14px;\n            }\n            .dropbtn {\n                background-color: #4CAF50;\n                color: white;\n                width: 100%;\n                height: 30px;\n                padding: 2px 6px;\n                font-size: 17px;\n                border: 1px solid #e1e1e1;\n            }\n            .dropdown {\n                position: relative;\n                display: inline-block;\n                width: 100%;\n            }\n            .dropdown-content {\n                display: none;\n                position: absolute;\n                background-color: #f1f1f1;\n                width: 100%;\n                box-shadow: 0px 6px 12px 0px rgba(0,0,0,0.25);\n                z-index: 1;\n            }\n            .dropdown-content a {\n                color: black;\n                padding: 9px 16px;\n                text-decoration: none;\n                display: block;\n            }\n            .dropdown-content a:hover {\n                background-color: #cbdacb;\n            }\n            .dropdown:hover .dropdown-content {\n                display: block;\n            }\n            .dropdown:hover .dropbtn {\n                background-color: #3e8e41;\n            }\n            ul.horizontal {\n                list-style-type: none;\n                padding: 0;\n            }\n            ul.horizontal li {\n                display: inline;\n                background-color: lightblue;\n                padding: 0 5px;\n            }\n            tbody {\n                font-size: 11pt;\n            }\n            td.left {\n                text-align: right;\n            }\n            [contenteditable] {\n                border-style: solid;\n                border-width: 1px;\n            }\n            iframe.frameClass1 {\n                height: 26px;\n                width: 32px;\n                padding: 2px 2px 2px 2px;\n                color: #0C8CDF;\n                overflow:hidden;\n                background-color: #fefefe;\n                border: 2px solid #c1c1c1;\n            }\n            iframe.frameClass2 {\n                height: 26px;\n                width: 103px;\n                padding: 2px 2px 2px 2px;\n                border: none;\n                margin: none;\n                color: #0C8CDF;\n                overflow: hidden;\n                position: absolute;\n                background-color: #fefefe;\n                border: 2px solid #c1c1c1;\n            }\n            iframe.frameClass3 {\n                height: 28px;\n                width: 34px;\n                padding: 1px 1px 1px 1px;\n                border: none;\n                margin: none;\n                color: #0C8CDF;\n                overflow: hidden;\n                position: absolute;\n                background-color: #fefefe;\n                border: 2px solid #c1c1c1;\n            }\n            .slidecontainer {\n                width: 100%;\n            }\n            .slider {\n                -webkit-appearance: none;\n                width: 100%;\n                height: 20px;\n                background: #d3d3d3;\n                outline: none;\n                opacity: 0.7;\n                -webkit-transition: .2s;\n                transition: opacity .2s;\n            }\n            .slider:hover {\n                opacity: 1;\n            }\n            .slider::-webkit-slider-thumb {\n                -webkit-appearance: none;\n                appearance: none;\n                width: 16px;\n                height: 28px;\n                background: #4CAF50;\n                cursor: pointer;\n            }\n            .slider::-moz-range-thumb {\n                width: 16px;\n                height: 28px;\n                background: #4CAF50;\n                cursor: pointer;\n            }\n            #drop1,#drop2 {\n                width: 131px;\n                height: 21px;\n                padding: 1px;\n                border: 1.5px solid #aaaaaa;\n                background-color: #d6e6f0;\n            }\n            img:active {\n                box-shadow: 0px 2px 5px 1px rgba(105, 165, 155, 0.3),\n                1px 2px 12px 0px rgba(105, 165, 155, 0.2) !important;\n            }\n        </style>\n    </head>\n    <body>\n        <!-- Tested with SeleniumBase - https://seleniumbase.io -->\n        <form id=\"myForm\">\n            <table id=\"myTable\" style=\"width: 804px; padding: 10px;\">\n                <tbody id=\"tbodyId\">\n                    <tr>\n                        <td>\n                            <h1>Demo Page</h1>\n                        </td>\n                        <td>\n                            <h2>SeleniumBase</h2>\n                        </td>\n                        <td>\n                            <div class=\"dropdown\">\n                                <div id=\"myDropdown\" class=\"dropbtn\" style=\"cursor: default;\"\n                                        onclick=\"clickDropdownFunction()\"\n                                        onmousemove=\"hoverDropdownFunction()\">Hover Dropdown</div>\n                                <div class=\"dropdown-content\" style=\"cursor: pointer;\">\n                                    <a id=\"dropOption1\" onclick=\"clickLink1()\">Link One</a>\n                                    <a id=\"dropOption2\" onclick=\"clickLink2()\">Link Two</a>\n                                    <a id=\"dropOption3\" onclick=\"clickLink3()\">Link Three</a>\n                                </div>\n                            </div>\n                        </td>\n                        <td>\n                            <h3>Automation Practice</h3>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td>Text Input Field:</td>\n                        <td><input type=\"text\" id=\"myTextInput\"/></td>\n                        <td>Textarea:</td>\n                        <td style=\"padding: 6px 10px 6px 10px;\">\n                            <textarea id=\"myTextarea\" name=\"textareaName\"\n                                      class=\"textareaClass area1\" rows=\"2\" cols=\"28\"\n                                      style=\"resize: none; width: 94%; font-size: 14px;\"></textarea>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td>Pre-Filled Text Field:</td>\n                        <td>\n                            <input type=\"text\" value=\"Text...\"\n                                   id=\"myTextInput2\" name=\"preText2\"/>\n                        </td>\n                        <td>Button:</td>\n                        <td>\n                            <button onclick=\"buttonFunction1()\" id=\"myButton\" type=\"button\"\n                                    style=\"color: green;\">Click Me (Green)</button>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td>Placeholder Text Field:</td>\n                        <td><input id=\"placeholderText\" type=\"text\"\n                                   placeholder=\"Placeholder Text Field\" /></td>\n                        <td>Read-Only Text Field:</td>\n                        <td>\n                            <input type=\"text\" id=\"readOnlyText\"\n                                   value=\"The Color is Green\"\n                                   style=\"font-size: 19px; color: green; width: 94%;\"\n                                   readonly />\n                        </td>\n                    </tr>\n                    <tr>\n                        <td>HTML SVG with rect:</td>\n                        <td>\n                            <svg id=\"mySVG\" name=\"svgName\" width=\"154\" height=\"20\">\n                                <rect id=\"svgRect\" width=\"154\" height=\"20\"\n                                      stroke=\"teal\" stroke-width=\"4\" fill=\"#4CA0A0\">\n                                    <animate attributeType=\"CSS\" attributeName=\"opacity\"\n                                             from=\"0.1\" to=\"1\" dur=\"1s\" repeatCount=\"1\"\n                                             restart=\"whenNotActive\" />\n                                    <animate attributeType=\"CSS\" attributeName=\"width\"\n                                             from=\"1\" to=\"154\" dur=\"1s\" repeatCount=\"1\"\n                                             restart=\"whenNotActive\" />\n                                </rect>\n                            </svg>\n                        </td>\n                        <td><p>Paragraph with Text:</p></td>\n                        <td><p id=\"pText\" style=\"color: green; font-size: 20px;\">This Text is Green</p></td>\n                    </tr>\n                    <tr>\n                        <td>Input Slider Control:</td>\n                        <td>\n                            <input type=\"range\" min=\"0\" max=\"100\" step=\"10\"\n                                   id=\"mySlider\" name=\"sliderName\" value=\"50\"\n                                   style=\"width: 88%;\" class=\"slider\"\n                                   onclick=\"sliderFunction1()\"\n                                   onchange=\"sliderFunction1()\"\n                                   onmousemove=\"sliderFunction1()\"/>\n                        </td>\n                        <td><label id=\"progressLabel\" for=\"progressBar\">Progress Bar: (50%)</label></td>\n                        <td><progress id=\"progressBar\" value=\"50\" style=\"width: 94%;\" max=\"100\" /></td>\n                    </tr>\n                    <tr>\n                        <td>Select Dropdown:</td>\n                        <td>\n                            <select id=\"mySelect\" name=\"selectName\"\n                                    class=\"selectClass\" onchange=\"selectFunction1()\"\n                                    style=\"width: 95%; font-size: 14px;\">\n                                <option value=\"25%\">Set to 25%</option>\n                                <option value=\"50%\">Set to 50%</option>\n                                <option value=\"75%\">Set to 75%</option>\n                                <option value=\"100%\">Set to 100%</option>\n                            </select>\n                        </td>\n                        <td><label id=\"meterLabel\" for=\"meterBar\">HTML Meter: (25%)</label></td>\n                        <td><meter id=\"meterBar\" value=\"0.25\" style=\"width: 94%;\"></meter></td>\n                    </tr>\n                    <tr>\n                        <td><div style=\"color: #845342;\">Image in iFrame:</div></td>\n                        <td style=\"padding: 4px 4px 3px 4px;\">\n                            <iframe id=\"myFrame1\" name=\"frameName1\" class=\"frameClass1\" scrolling=\"no\"\n                            src='data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7'></iframe>\n                            &nbsp;&nbsp;\n                            <iframe id=\"myFrame2\" name=\"frameName2\" class=\"frameClass2\" scrolling=\"no\"\n                            src=\"data:text/html,<body%20style=%22background-color:%23F2F6F8;%22>\n                            <h4>iFrame%20Text<%2Fh4></body>\"></iframe>\n                        </td>\n                        <td>RadioButton 1:<input type=\"radio\" checked id=\"radioButton1\" \n                                                 name=\"radioGroup1\" class=\"radioClass1\"\n                                                 style=\"width: 30px;\"/>\n                        </td>\n                        <td>RadioButton 2:<input type=\"radio\" id=\"radioButton2\"\n                                                 name=\"radioGroup1\" class=\"radioClass1\"\n                                                 style=\"width: 30px;\"/>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td style=\"width: 150px;\">\n                            CheckBox:\n                            <input type=\"checkbox\" id=\"checkBox1\"\n                                   name=\"checkBoxName1\" class=\"checkBoxClassA\"\n                                   onchange=\"revealRow(event)\"/>\n                        </td>\n                        <td style=\"width: 180px;\">\n                            CheckBoxes:\n                            <input type=\"checkbox\" id=\"checkBox2\"\n                                   name=\"checkBoxName2\" class=\"checkBoxClassB\"/>\n                            <input type=\"checkbox\" id=\"checkBox3\"\n                                   name=\"checkBoxName3\" class=\"checkBoxClassB\"/>\n                            <input type=\"checkbox\" id=\"checkBox4\"\n                                   name=\"checkBoxName4\" class=\"checkBoxClassB\"/>\n                        </td>\n                        <td style=\"width: 150px\">\n                            Pre-Check Box:\n                            <input type=\"checkbox\" id=\"checkBox5\"\n                                   name=\"checkBoxName5\" class=\"checkBoxClassC\" checked />\n                        </td>\n                        <td style=\"color: #845342; padding: 3px 3px 7px 9px; height: 38px;\">\n                            CheckBox in iFrame:&nbsp;&nbsp;\n                            <iframe style=\"padding: 0px 0px 0px 0px;\"\n                            id=\"myFrame3\" name=\"frameName3\" class=\"frameClass3\" scrolling=\"no\"\n                            src=\"data:text/html,\n                                <body%20style=%22background-color:%23F2F6F8;%22>\n                                <input%20type=%22checkbox%22%20id=%22checkBox6%22\n                                %20style=%22padding:%201px%201px%201px%201px;%22\n                                %20name=%22checkBoxName6%22\n                                %20class=%22checkBoxClassD%20fBox%22%20/>\n                                </body>\"></iframe>\n                        </td>\n                    </tr>\n                    <tr style=\"display: none\" class=\"hidden_row\">\n                        <td>\n                            Drag and Drop A:\n                        </td>\n                        <td>\n                            <div id=\"drop1\" class=\"dropzone\" ondrop=\"drop(event)\" ondragover=\"dragOver(event)\" ondragenter=\"dragEnter(event)\" ondragleave=\"dragLeave(event)\"><img id=\"logo\" src=\"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAANMAAAAhCAIAAADMPwmcAAAAAXNSR0IArs4c6QAAEyJJREFUeAHtXHmQHNV97vuYe2f23tVKWmm10uoEiQUBBkmAwBjfHA6BxEmBgbhMVf7Kf/7DlUqlKikXTlJOXDiECtiQqBwOC4JxhEEgocsgJCF0rK7VnrOzcx89feZ707Oz3TO7SLtaSbbYV12zr9/xe69ff+/7Ha8l+odP/4i6zGl3flOG6bzMg8yL/yNbAeaPbL7z071WVoCzHyTQ0+Nb1jW3D1WMjcc//NAyjLkVOy/t2liBMvKE+npf97K5fSTGc57eQ1tzK3Re2rWyAmXk2Y9z8FzxxIg6o0db1iysWyjOqMt84/kVwAq4kAfYvfFJbqbrMo+8ma7YfPtq5F3hFTG8TPomOd8j6SHWHprNmvKpYmB3gY/ps56MxVFahKNoih83aO3yanutgTNZisuamPmsJzzrjpagF5bELHaaoS1GOlfHZv9ANZKL82a9BDPuSFOpWzyxrwcMf7Vzne8W4nf6Anvyke1ZLj0b7yS3Whp+PGwxVNMvU8GdM6bwi38WI8Cc+VEjIO45prY/E7v4jnPVsrAoPviDnRYz7e6iddp3qC28fZU4FJyrQedKztVBXnaNFP2TkAWmMykuY3IJnQbGaEoLs3qQtQQ6dZtXj3Ct/xafBWlZDA1RSADf5U0Y5coMdKHHoFWWUXhnK0vUTNGwOCtz/UCxLdX2z7fzMa+zwVXPu5AHd2GmE5pFF1Nixh4KAnaMakW2Z3wHFD6hU6V9C7Wb3OxN3OG1ODq3UszcIAd252c6pSvWnslbkTezUO7CwOxtgzmZre9wa/3/rHOK0gOFQvdo4s7jhk9VmzLp3rORN1c6G1z1vAt58BWugLuQXy5odcSw8x4qhv4vSzusFC5pRH6dsWgqsdWHBpkNf9DIAx9HXk9f9VeICTBFjh/3OGeCW/lMhE3Lo4/uAzEXlo1Zb5u0frm1gHMKF8iXkSdy6YA4fIG2M63mx6HxajsRf6KkpKR+1Qk7uyWtW4G9hfQtHvgfapNrY6ABuDDfLeZXCHodxxRN8Zwmn1LFQW2qcWpHJto8t0YqdAoUSzNFy/Op4vmsyOYd2Kco1JpehsnD11HVRi6zXlbbeEwYu8L3iSL1FSfnzFD55aLF0lzCEAc0Mj2BTA8ZLm6QWTmSKdGFpaRK7isyClkWmInKQgHwxUB4rvQNcgF9LQrL4jtQ4OPExtUauew6SekQMAE+qvsOKqi9yIdFd+/RZihiC2qXMSm3OQguzK8cyndHTZEQNjfuBXFKZyK16NSDhdzaoXxX1JYAte75rMl7qI1Ry34hutvJ4sz8smhuzZDhVyiDkfrrPEdbxOHAlBMuv9qQp78jNMfbdyyu9E2+pYnZOf6qzdXAsiuFqB56J6dHWKy1ozlVXMCP/FlIbeXwssvlGylGswJ7Cg3/lSSW4vQJNl/8vkBiswe63sY92sKzBmiaXkh6jhUrXWMPBpUOXhjS4eXE7/EZvkmeSN7mqXsnB56zx0LV4PcjkCYfV9t/QjwMoHboqTAynqPFtn/BxptMhSWiXdX607j3UwUVwPTYA0FaNeFLpW/2FFuIP14qlxJbfA3/nQJY0cCUJx6WomCHNL6cCl60BWJKGsUSlNMGQ5mTcvIrRkf/dL8WzlGTD0clt5wQz4eaXtog9teReZQSoAnW1CK5yqKhOL3xjOdIS8u/bwTXlttRVLE1FX1kv9IRh3FpF2Z6zzHFw/79Cxt/uYF2jG7XVnqaFG0VjRmEkVmG5egy6ms7iqxAw32YKpHdjBqGSvfKbMoMfpDnUgaortKWaLE3MpVbO1Ns5wd/ENEDZKm4tCmeVeGLqO28KdAAhOFhmp+P0y6gOgTQVOyBYGKTF8uHvSCc18BJagcPCtQi7NCTYbgyFfABo7iA77FvByCCUUx+3NSDDDjY4unEXT7pjAruIdJpijhJoLqJzQ/npuzWVPYGqS8liLWbTbxsu7EFq/d+wgrgYNq0TI4GceohJvpo0BQZSKNVi9EtbDY8KarGvhP0HVbYzNRrOzEY+QsGSmw+iV/k5TP1FTJTFseHH99teKBwaH7Iz0f9FGMqnXHDV0TV4F+9v/Bv77ZjMVpjduQv9uihAlqKCNAkZVPW1AUJw6OB2MYe+qjpP3vtEYvtycGn39UDZANzKUk8EzGCSrEjDi8ndetpSzCanu+lzYknL/WpII/c/ezYK8+ffLNUfuGfD7/6LMeWl7yq43e77n165UPTifAcUeTTamGpABUT/7IvcbdPGNCksxr0CF4qgnC2MnJ2x6KP3R8ksLOowL4C+IDNmXgr+RVS9OEg0JO9Xsoekv37Cs5elXx+hYggDoCCqFvTL1K+gwXIAVjHv+aHE02o5cHggn8YYwqT6CevXKfq3snWvZWFOkab2DcCyU1elGfXyWXkVQa4tAw2Q/C9XP2raYAPm2H4sbDSyRsyeU+g8/pXUlzKxGyjj4Qy10tYisx1Uminy/FSFsXH7j9YNQulM1boHAdovIdbQzvKR6MI/sXvOUpgZ9CNL20I7F5ss5ERUEYe3ZdbPQycgeFs5KVuPo1biI28vir8mx5baWoN2aEnPii2p9I3ng2/2cPHfKakx771CYGdSQd3L2741TqmQOyT3Mrh6MMHtHA+fcM5aGf/gQXOGbqQ56y4fHksdMuziZG/rCPgYwnBFDt4XCnKg/0tjuiwmbxHFO+RIm7tacAAKiwjfje2e9MLCZvbIAdqq/FlCqQFOambPf79BFJVCb4nlBdeGMqbn09Cst0AeGrYljL8bGa9BL2f65H8v3cBl8QUXysrVmyG8FtZDAHW0RpJ3Kcyt6rhZnErnVTrX0nD00dfKITA3rzSScJvYr/W9GLS1gaYLeAO5KGcxMndCZoOl7usfMclpeDOpQQKdmJNtTltwzGwd6ENO9SwaSn0XheQV25WokxlYRy3IEvfxwsqC8uP+ep2LB+/7wiUpOkhtiwQll8+igwsxcaX15fJ1aK8R1qaXugdfOp9cF5y0wn/7yeFoHH1M6DoCiSo19Z/Hc+tknIrJVhvWj0LUsG4eKOwsXClb/JgocFtsOtRnusRAVCkut9kq1QqcREyJtST2sRDIdaeJcBLAIWgrzCiV2BHZBHrhwr9LgvkEfQv4J3Iow3Ld0hx2o5Qu2ze0gUaliJ0IjuxK2xRl/ILx8WGnS0EQ9sZcLPTCJnGeCFt4Uaw+eqIGIgNr1wLF4Yf29Ww7brAHkJvtMY2P3cTvApwFfL2QPgFbymdLsMUwzEKgQf0NRRrePtKaSBEq6TEv6/Dc7QZyOOSMn6zawftaHbw/SUVnW5Llk80CDEfdoXamIHRObkBrhbyMC2wiP9AARf0iBEkXh4osNjK2aYboID9DYOv+T8SpkgXF5a2rIXtJRW6qpfYfkhY4iADNlttquphDkOQNnAy7iHBGmcyS0oNJcTAdyRQWpV/46ic46wTdk7RTPWjOCtdeRLP+5Urnodq2G3pm86kvnTalPWxBz+WzofhQFAWLZ3DAY8FGy63ZrDYnNHqs/Be9bqC2uZiTZhlYMFC1xi84FzPSGHpGD8SkPvq5dP1wnBQGPXbIDMB7qayb5rvGYbB55oZgAvdhF/RUBuy8HYrta7lrpReyQz0CC5hmPiS4Dy1kU3c4cv0eqBAEUmuezsL2ivjg6bi91ZDpzJVorhJ1KI62U4JSkF+sNWqqyfuLXHS9UMZvu4qrdhE9SX/BR9fsoxpBZB4XtwVz0NTlIgDIabIx798FJSW3HSy6cUboDShbce+fVBZEkOh8+Stiq4gwXO8qfm5jWMPfqQ2Z4CwYkcCV3JzH7hQHArUv7pWPtkAw9GQy55dYsvJ6aZo0RYI2FnrQt4Ty7+Jy1n9OXn4tpXaGXRkqMSdPqABYYva6ACx8wZ0nLeCvWDYgfmURbzv4OSMPcdhGteYcqV5oC+X/DyPD4pY6nfF2CrzRwbGlvN2zvOI3s25zAsKhHqV+xrsZoUlY0AJAntDT+xSWwhLQUd7TzQKIwEh6oOHC78VVS6ZsNWONst/vzXfPZpZfz6/YsTwF4l5J2uFJeOD39/Z/sxmPjrJBfKpegDdJWHihlYZopodqYw82qIZg5apqUjD0dqVdbzlKToacNVpF42UOoOZ8EEAXgM/RkhuSl6BkvUdUWyXAoYobB2mQAZD45afx2sDCvCRcVGWBd/QNcPSDR8z0BEghsHe9k9uU6bUoGRiwgByPE+tlEsugdN6yTJmJ6C8JtB3xCZbN2DDDoZa0y828OOTh7kI3VUNQILMiEPBw/ikDRcIEpwHZwK+KlQz4dHb+xpfvp7N8/aGbnxpPVi2SojFm+RrGhI2ctFc+aYhxnb3uSqq+s/iVh5mp/gi2aSgW4E8ROOUTkHum5ppKu+JLpJAl3heR5CWUOBiwXuo7JzaUwKUhx6vU5YIiD+3PzNe63LCW2TTBg5OtAYWjZ1OAyRA7PD3iGtc/1oakcVZPObUXdx7Dp41Zj51y8taylgw1OwRpNMRxJONUHn16t7rcsIObYptLhMN9Dbw17/TA0rovaWR7asAHTCodDaMK7BvYf/f/FYPKloLIkGceL4OYR1IwAFxFfLgnQw9vgu+Cx/zLPjHO50KvawCcLLCGRSnGBd1oWXVVdvRoFiiJN1vAPcG5f+IBC9g0kW/E4JLgbwzAV7ZtRLi+ygkJ0snSXASPqnt0sbv8lV8AtKLptK3ePOrJRwniEN6LezQhIvrckmT4lu65GafM2oP3wVfasEvRogYni8ReGmJy5QNAy3EOA8/cGQHh/3SZM+4t+FV41uPJTf1oSd0WmDvIucxhiG79nyxNZ26nbScTCZNbDhfMbO+H27yZDl8F3jNpbM4rmRcgg5tNzm55bghO+wZ2sJpB9wOCOGjAXKO4kiuF2+8+rHxxiFH7RRZ9itr2Ac2VFVUdZyyTaULoqY4vdDquWI7N/xkGN+BSqdV278DLeV7xMw6yYZXcFcegWV0lE8UpT61sFyAYzv4ZDi0M4dyYBcATd8oA6xs2sTHB5UhnBmo2rodufwqEbHZ2Nf98HO9RwmOtTCTus1n63QgG3Nw9ppdXhjWYGsCyjhvHX0kREjUtMDHCEHX7MHZjTB1LyjK4e+5TDSLtRDI0JoyBCIW7d+7UD7WiM44n7VFjH/tMOImQjRgcQbCeFCgerhM+YWuKIJ2CNP493eM3/cp3IuR7+6Bn4tCQBjENv7Vw8TgQ6D7w0X49Rxv9BxrhBAYfzjzCL2/hM1IlqhnVw+lbj2FwzR4JOG3l1cigvYEXMiziy73LyLybT+NDz4VBgmV/E345xYJXNE0SMgeHXBBPC/yevkMDQq3cVty5LEwYh+FbkHpgg8M64EGV6E9zjNankt8DmlJ59TQb3Pxe/1oD+84cZcXboolkLMpJGj8hm0IrtojX9IvAI1T3ejDIYSvcdKPb1Qhjqh4E69HxUevlyR9+s44dSBHq1MlHPAHdy3Gx6FMKRSH6C5ibPh0BUcLo3++D1wFjFq8gSMN38E2KE0cWsS+eSjT29/xd1uDHyzJrRpGIVCV7xlhCpg/cS/QBUMFP+j0fNqCDITUv7YGZxvAKE7V8iuHK2JRC3Xc9GJvlRZG+VVAHkaFY9v+k3j8bl92vQQSKrsIqMBjmJQIoLybh1KGti2VkR9xUG//cWz8K4H0jYQRcdxJGhsEoOH/hXkxSfIAIgemLBFhubtFhd/KwLGNfSsA/Q78lSELptyZD72bdcafEcThfSzxaRyjk7FM8rUIbXJcDN+xkonRGimhOEZwfLtP3CYdXyf4sKmAOST4UvWvZ/H1q1ZPolmYHinF0qcMPo68xU7oaLscLlTpQxUaDeyScnnOgLdEMTSfKJezCg8eAm6czSp5RN0Q9fAebkGmclYBHLQ+ezNOIFK3nIERZrE6VDBIq25Ht+ez5vjWzxCCIUe9Jc0Ib7Tl5xsBxOx1A0AblKYtHCezoR3dde92VcQCWO0/3pLYeiz1pVPwS4jYUpJPRyK/Xo1vWyqzqmToH5b+j4E7eutvXRs2th2YpbZ1d7S17bnh/ItvDem6+Tn/xwBMb62ZBxpMD5xTvEgDR7cs3vok5CpTLWfwOqGp8Q0fEpwVfD1Q3QIvE7WljwOqq2gKcTW99PkJmuBfewAl1QkTQR0ouHYOpSpSPlFVHshRYkvDJBHBxj/RQGMgzyZUm2LtfLlZiXSdJRcor3kuxMmm0+Ow7SrzrH5GigK3GfiShayhwGWkSkv4DTABhai/giq0UVtT5MOWUmIUAR+z1H4iZddiG6gT7MtoHJeQnXLsNvbv1eG8ygxg3on9Kq5KyQUz4DlhtBYvrn5OcLgqLPLRgJPhXLX2zZSYm6ZquoEwSQCuSngtwmpL7C7TltdA/PPhVTUB5y34jKNcATa7FnyGy9kSeWEoiKuqcMpb6FlxZNpwvbOLC3nsN67D5ay+yPysO16k/Plm194KuJBH8bONds6647W3ovNPdHErUEbe8bPZdLZaO1ychGlbZfK6aU5hhE3bYb7ii7QCZeQNRBVcX6QHn3/Wq7wCrrDyVZ7L/PBfpBX4f5/F9Rvz341RAAAAAElFTkSuQmCC\" draggable=\"true\" ondragstart=\"dragStart(event)\" width=\"130\" height=\"20\"></div>\n                        </td>\n                        <td>\n                            Drag and Drop B:\n                        </td>\n                        <td>\n                            <div id=\"drop2\" class=\"dropzone\" ondrop=\"drop(event)\" ondragover=\"dragOver(event)\" ondragenter=\"dragEnter(event)\" ondragleave=\"dragLeave(event)\"></div>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td>\n                            URL Link:\n                        </td>\n\n                        <td>\n                            <a id=\"myLink1\" name=\"linkName1\" class=\"linkClass\"\n                               href=\"https://seleniumbase.com\">seleniumbase.com</a>\n                        </td>\n                        <td>\n                            Link with Text:\n                        </td>\n\n                        <td>\n                            <a id=\"myLink2\" name=\"linkName2\" class=\"linkClass\"\n                               href=\"https://github.com/seleniumbase/SeleniumBase\">\n                               SeleniumBase on GitHub</a>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td>\n                            SeleniumBase Docs:\n                        </td>\n\n                        <td>\n                            <a id=\"myLink3\" name=\"linkName3\" class=\"linkClass\"\n                               href=\"https://seleniumbase.io\">seleniumbase.io</a>\n                        </td>\n                        <td>\n                            The Demo Page:\n                        </td>\n\n                        <td>\n                            <a id=\"myLink4\" name=\"linkName4\" class=\"linkClass\"\n                               href=\"https://seleniumbase.io/demo_page/\">\n                               SeleniumBase Demo Page</a>\n                        </td>\n                    </tr>\n                </tbody>\n            </table>\n        </form>\n        <script>\n            function buttonFunction1() {\n              var x = document.getElementById(\"myButton\");\n              var y = document.getElementById(\"pText\");\n              var z = document.getElementById(\"readOnlyText\");\n              if (x.style.color != \"purple\") {\n                x.style.color = \"purple\";\n                x.textContent = \"Click Me (Purple)\";\n                y.textContent = \"This Text is Purple\";\n                y.style.color = \"purple\";\n                z.value = \"The Color is Purple\";\n                z.style.color = \"purple\";\n              }\n              else {\n                x.style.color = \"green\";\n                x.textContent = \"Click Me (Green)\";\n                y.textContent = \"This Text is Green\";\n                y.style.color = \"green\";\n                z.value = \"The Color is Green\";\n                z.style.color = \"green\";\n              }\n            }\n        </script>\n        <script>\n            function sliderFunction1() {\n              var s = document.getElementById(\"mySlider\");\n              var p = document.getElementById(\"progressBar\");\n              var pl = document.getElementById(\"progressLabel\");\n              p.value = s.value;\n              pl.textContent = \"Progress Bar: (\" + p.value + \"%)\";\n            }\n        </script>\n        <script>\n            function selectFunction1() {\n                var d = document.getElementById(\"mySelect\").value;\n                var m = document.getElementById(\"meterBar\");\n                var ml = document.getElementById(\"meterLabel\");\n                if (d == \"25%\") {\n                    m.value = \"0.25\";\n                    ml.textContent = \"HTML Meter: (25%)\";\n                }\n                if (d == \"50%\") {\n                    m.value = \"0.5\";\n                    ml.textContent = \"HTML Meter: (50%)\";\n                }\n                if (d == \"75%\") {\n                    m.value = \"0.75\";\n                    ml.textContent = \"HTML Meter: (75%)\";\n                }\n                if (d == \"100%\") {\n                    m.value = \"1.0\";\n                    ml.textContent = \"HTML Meter: (100%)\";\n                }\n            }\n        </script>\n        <script>\n            function hoverDropdownFunction() {\n                overlay = document.querySelector(\".dropdown-content\");\n                overlay.style.pointerEvents = \"auto\";\n            }\n        </script>\n        <script>\n            function clickDropdownFunction() {\n                the_h3 = document.querySelector(\"h3\");\n                the_h3.textContent = \"Automation Practice\";\n                overlay = document.querySelector(\".dropdown-content\");\n                overlay.style.pointerEvents = \"none\";\n            }\n        </script>\n        <script>\n            function clickLink1() {\n                the_h3 = document.querySelector(\"h3\");\n                the_h3.textContent = \"Link One Selected\";\n                overlay = document.querySelector(\".dropdown-content\");\n                overlay.style.pointerEvents = \"none\";\n            }\n        </script>\n        <script>\n            function clickLink2() {\n                the_h3 = document.querySelector(\"h3\");\n                the_h3.textContent = \"Link Two Selected\";\n                overlay = document.querySelector(\".dropdown-content\");\n                overlay.style.pointerEvents = \"none\";\n            }\n        </script>\n        <script>\n            function clickLink3() {\n                the_h3 = document.querySelector(\"h3\");\n                the_h3.textContent = \"Link Three Selected\";\n                overlay = document.querySelector(\".dropdown-content\");\n                overlay.style.pointerEvents = \"none\";\n            }\n        </script>\n        <script>\n            document.getElementById(\"svgRect\").addEventListener(\"click\", evt => {\n                document.querySelectorAll(\"animate\").forEach(element => {\n                    element.beginElement();\n                });\n            });\n        </script>\n        <script>\n            function revealRow(event) {\n                // Show the Drag & Drop row if the first checkbox is checked.\n                if (event.target.checked)\n                {\n                    document.querySelector('tr.hidden_row').style.display='';\n                }\n                else\n                {\n                    document.querySelector('tr.hidden_row').style.display='none';\n                }\n            }\n        </script>\n        <script>\n            function dragOver(event) {\n                // Allow dropping.\n                event.preventDefault();\n            }\n            function dragEnter(event) {\n                event.preventDefault();\n                if ( event.target.className === \"dropzone\" ) {\n                    event.target.style.background = \"#c6b6d6\";\n                }\n            }\n            function dragLeave(event) {\n                event.preventDefault();\n                if ( event.target.className === \"dropzone\" ) {\n                    event.target.style.background = \"#d6e6f0\";\n                }\n            }\n            function dragStart(event) {\n                event.dataTransfer.setData(\"id_of_dragged_element\", event.target.id);\n            }\n            function drop(event) {\n                event.preventDefault();\n                var data = event.dataTransfer.getData(\"id_of_dragged_element\");\n                try {\n                    event.target.appendChild(document.getElementById(data));\n                    event.target.style.background = \"#d6e6f0\";\n                }\n                catch (HierarchyRequestError) {\n                    // Drap & Drop to same location. Do nothing.\n                }\n            }\n        </script>\n    </body>\n</html>"
  },
  {
    "path": "examples/offline_examples/load_html_test.py",
    "content": "import pytest\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\n@pytest.mark.offline  # Can be run with: \"pytest -m offline\"\r\nclass OfflineTests(BaseCase):\r\n    def test_load_html_string(self):\r\n        html = \"<h2>Hello</h2><p><input />&nbsp;&nbsp;<button>OK!</button></p>\"\r\n        self.load_html_string(html)  # Open \"data:text/html,\" then replace html\r\n        self.assert_text(\"Hello\", \"h2\")\r\n        self.assert_text(\"OK!\", \"button\")\r\n        self.type(\"input\", \"Goodbye\")\r\n        self.click(\"button\")\r\n        new_html = '<h3>Checkbox</h3><p><input type=\"checkbox\" />Check Me!</p>'\r\n        self.set_content(new_html)  # Same as load_html_string(), but keeps URL\r\n        self.assert_text(\"Checkbox\", \"h3\")\r\n        self.assert_text(\"Check Me!\", \"p\")\r\n        self.assert_false(self.is_selected(\"input\"))\r\n        self.click(\"input\")\r\n        self.assert_true(self.is_selected(\"input\"))\r\n"
  },
  {
    "path": "examples/offline_examples/test_demo_page.py",
    "content": "import os\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n@pytest.mark.offline  # Can be run with: \"pytest -m offline\"\nclass OfflineTests(BaseCase):\n    def test_demo_page(self):\n        # Load a local html file into the web browser\n        dir_path = os.path.dirname(os.path.abspath(__file__))\n        file_path = os.path.join(dir_path, \"demo_page.html\")\n        self.load_html_file(file_path)\n\n        # Assert the title of the current web page\n        self.assert_title(\"Web Testing Page\")\n\n        # Assert that an element is visible on the page\n        self.assert_element(\"tbody#tbodyId\")\n\n        # Assert that a text substring appears in an element\n        self.assert_text(\"Demo Page\", \"h1\")\n\n        # Type text into various text fields and then assert\n        self.type(\"#myTextInput\", \"This is Automated\")\n        self.type(\"textarea.area1\", \"Testing Time!\\n\")\n        self.type('[name=\"preText2\"]', \"Typing Text!\")\n        self.assert_text(\"This is Automated\", \"#myTextInput\")\n        self.assert_text(\"Testing Time!\\n\", \"textarea.area1\")\n        self.assert_text(\"Typing Text!\", '[name=\"preText2\"]')\n\n        # Hover & click a dropdown element and assert results\n        self.assert_text(\"Automation Practice\", \"h3\")\n        try:\n            self.hover_and_click(\"#myDropdown\", \"#dropOption2\", timeout=1)\n        except Exception:\n            # Someone probably moved the mouse while the test ran\n            self.hover_and_js_click(\"#myDropdown\", \"#dropOption2\")\n        self.assert_text(\"Link Two Selected\", \"h3\")\n\n        # Click a button and then verify the expected results\n        self.assert_text(\"This Text is Green\", \"#pText\")\n        self.click('button:contains(\"Click Me\")')\n        self.assert_text(\"This Text is Purple\", \"#pText\")\n\n        # Assert that the given SVG is visible on the page\n        self.assert_element('svg[name=\"svgName\"]')\n\n        # Verify that a slider control updates a progress bar\n        self.assert_element('progress[value=\"50\"]')\n        self.set_value(\"input#mySlider\", \"100\")\n        self.assert_element('progress[value=\"100\"]')\n\n        # Verify that a \"select\" option updates a meter bar\n        self.assert_element('meter[value=\"0.25\"]')\n        self.select_option_by_text(\"#mySelect\", \"Set to 75%\")\n        self.assert_element('meter[value=\"0.75\"]')\n\n        # Assert an element located inside an iframe\n        self.assert_false(self.is_element_visible(\"img\"))\n        self.switch_to_frame(\"#myFrame1\")\n        self.assert_true(self.is_element_visible(\"img\"))\n        self.switch_to_default_content()\n\n        # Assert text located inside an iframe\n        self.assert_false(self.is_text_visible(\"iFrame Text\"))\n        self.switch_to_frame(\"#myFrame2\")\n        self.assert_true(self.is_text_visible(\"iFrame Text\"))\n        self.switch_to_default_content()\n\n        # Verify that clicking a radio button selects it\n        self.assert_false(self.is_selected(\"#radioButton2\"))\n        self.click(\"#radioButton2\")\n        self.assert_true(self.is_selected(\"#radioButton2\"))\n\n        # Verify that clicking a checkbox makes it selected\n        self.assert_element_not_visible(\"img#logo\")\n        self.assert_false(self.is_selected(\"#checkBox1\"))\n        self.click(\"#checkBox1\")\n        self.assert_true(self.is_selected(\"#checkBox1\"))\n        self.assert_element(\"img#logo\")\n\n        # Verify clicking on multiple elements with one call\n        self.assert_false(self.is_selected(\"#checkBox2\"))\n        self.assert_false(self.is_selected(\"#checkBox3\"))\n        self.assert_false(self.is_selected(\"#checkBox4\"))\n        self.click_visible_elements(\"input.checkBoxClassB\")\n        self.assert_true(self.is_selected(\"#checkBox2\"))\n        self.assert_true(self.is_selected(\"#checkBox3\"))\n        self.assert_true(self.is_selected(\"#checkBox4\"))\n\n        # Verify that clicking an iframe checkbox selects it\n        self.assert_false(self.is_element_visible(\".fBox\"))\n        self.switch_to_frame(\"#myFrame3\")\n        self.assert_true(self.is_element_visible(\".fBox\"))\n        self.assert_false(self.is_selected(\".fBox\"))\n        self.click(\".fBox\")\n        self.assert_true(self.is_selected(\".fBox\"))\n        self.switch_to_default_content()\n\n        # Verify Drag and Drop\n        self.assert_element_not_visible(\"div#drop2 img#logo\")\n        self.drag_and_drop(\"img#logo\", \"div#drop2\")\n        self.assert_element(\"div#drop2 img#logo\")\n\n        # Assert link text - Use click_link() to click\n        self.assert_link_text(\"seleniumbase.com\")\n        self.assert_link_text(\"SeleniumBase on GitHub\")\n        self.assert_link_text(\"seleniumbase.io\")\n        self.assert_link_text(\"SeleniumBase Demo Page\")\n\n        # Assert exact text\n        self.assert_exact_text(\"Demo Page\", \"h1\")\n\n        # Highlight a page element (Also asserts visibility)\n        self.highlight(\"h2\")\n"
  },
  {
    "path": "examples/offline_examples/test_extended_driver.py",
    "content": "import os\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n@pytest.mark.offline  # Can be run with: \"pytest -m offline\"\nclass OfflineTests(BaseCase):\n    def test_extended_driver(self):\n        # Load a local html file into the web browser\n        dir_path = os.path.dirname(os.path.abspath(__file__))\n        file_path = os.path.join(dir_path, \"demo_page.html\")\n        self.load_html_file(file_path)\n\n        # Get the raw driver\n        driver = self.driver\n\n        # Assert that an element is visible on the page\n        driver.assert_element(\"tbody#tbodyId\")\n\n        # Assert that a text substring appears in an element\n        driver.assert_text(\"Demo Page\", \"h1\")\n\n        # Type text into various text fields and then assert\n        driver.type(\"#myTextInput\", \"This is Automated\")\n        driver.type(\"textarea.area1\", \"Testing Time!\\n\")\n        driver.type('[name=\"preText2\"]', \"Typing Text!\")\n        driver.assert_text(\"This is Automated\", \"#myTextInput\")\n        driver.assert_text(\"Testing Time!\\n\", \"textarea.area1\")\n        driver.assert_text(\"Typing Text!\", '[name=\"preText2\"]')\n\n        # Hover & click a dropdown element and assert results\n        driver.assert_text(\"Automation Practice\", \"h3\")\n        driver.js_click(\"#dropOption2\")\n        driver.assert_text(\"Link Two Selected\", \"h3\")\n\n        # Click a button and then verify the expected results\n        driver.assert_text(\"This Text is Green\", \"#pText\")\n        driver.click('button:contains(\"Click Me\")')\n        driver.assert_text(\"This Text is Purple\", \"#pText\")\n\n        # Assert that the given SVG is visible on the page\n        driver.assert_element('svg[name=\"svgName\"]')\n\n        # Assert an element located inside an iframe\n        self.assert_false(driver.is_element_visible(\"img\"))\n        driver.switch_to.frame(\"myFrame1\")\n        self.assert_true(driver.is_element_visible(\"img\"))\n        driver.switch_to.default_content()\n\n        # Assert text located inside an iframe\n        self.assert_false(driver.is_text_visible(\"iFrame Text\"))\n        driver.switch_to.frame(\"myFrame2\")\n        self.assert_true(driver.is_text_visible(\"iFrame Text\"))\n        driver.switch_to.default_content()\n\n        # Assert exact text\n        driver.assert_exact_text(\"Demo Page\", \"h1\")\n\n        # Highlight a page element (Also asserts visibility)\n        driver.highlight(\"h2\")\n"
  },
  {
    "path": "examples/offline_examples/test_handle_alerts.py",
    "content": "import pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n@pytest.mark.offline  # Can be run with: \"pytest -m offline\"\nclass OfflineTests(BaseCase):\n    def test_alerts(self):\n        self.open(\"data:,\")\n        self.execute_script('window.alert(\"ALERT!!!\");')\n        self.sleep(1)  # Not needed (Lets you see the alert pop up)\n        self.accept_alert()\n        self.sleep(1)  # Not needed (Lets you see the alert go away)\n        self.execute_script('window.prompt(\"My Prompt\",\"defaultText\");')\n        self.sleep(1)  # Not needed (Lets you see the alert pop up)\n        alert = self.switch_to_alert()\n        self.assert_equal(alert.text, \"My Prompt\")  # Not input field\n        self.dismiss_alert()\n        self.sleep(1)  # Not needed (Lets you see the alert go away)\n        if self.browser == \"safari\" and self._reuse_session:\n            # Alerts can freeze Safari if reusing the browser session\n            self.driver.quit()\n"
  },
  {
    "path": "examples/offline_examples/test_request_fixture.py",
    "content": "import pytest\r\n\r\n\r\n# Use the pytest \"request\" fixture to get the \"sb\" fixture (no class)\r\n@pytest.mark.offline\r\ndef test_request_fixture(request):\r\n    sb = request.getfixturevalue(\"sb\")\r\n    sb.open(\"data:text/html,<p>Hello<br><input></p>\")\r\n    sb.assert_element(\"html > body\")\r\n    sb.assert_text(\"Hello\", \"body p\")\r\n    sb.type(\"input\", \"Goodbye\")\r\n    sb.click(\"body p\")\r\n    sb.tearDown()\r\n\r\n\r\n# Use the pytest \"request\" fixture to get the \"sb\" fixture (in class)\r\n@pytest.mark.offline\r\nclass RequestTests:\r\n    def test_request_fixture_in_class(self, request):\r\n        sb = request.getfixturevalue(\"sb\")\r\n        sb.open(\"data:text/html,<p>Hello<br><input></p>\")\r\n        sb.assert_element(\"html > body\")\r\n        sb.assert_text(\"Hello\", \"body p\")\r\n        sb.type(\"input\", \"Goodbye\")\r\n        sb.click(\"body p\")\r\n        sb.tearDown()\r\n"
  },
  {
    "path": "examples/offline_examples/test_user_agent.py",
    "content": "import pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n@pytest.mark.offline  # Can be run with: \"pytest -m offline\"\nclass OfflineTests(BaseCase):\n    def test_get_user_agent(self):\n        self.open(\"data:,\")\n        user_agent = self.get_user_agent()\n        print('\\nUser Agent = \"%s\"' % user_agent)\n\n        # Now change the user-agent using \"execute_cdp_cmd()\"\n        if not self.is_chromium():\n            msg = \"\\n* execute_cdp_cmd() is only for Chromium browsers\"\n            print(msg)\n            self.skip(msg)\n        print(\"\\n--------------------------\")\n        if not self.headless:\n            self.open(\"chrome://version/\")\n            self.highlight(\"#useragent\", loops=6)\n            self.sleep(0.8)\n        try:\n            self.execute_cdp_cmd(\n                \"Network.setUserAgentOverride\",\n                {\n                    \"userAgent\": \"Mozilla/5.0 \"\n                    \"(Nintendo Switch; WifiWebAuthApplet) \"\n                    \"AppleWebKit/606.4 (KHTML, like Gecko) \"\n                    \"NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393\"\n                },\n            )\n            new_user_agent = self.get_user_agent()\n            print('\\nOverrided User Agent = \"%s\"' % new_user_agent)\n        finally:\n            # Reset the user-agent back to the original\n            self.execute_cdp_cmd(\n                \"Network.setUserAgentOverride\",\n                {\"userAgent\": user_agent},\n            )\n        print(\"\\n--------------------------\")\n        user_agent = self.get_user_agent()\n        print('\\nUser Agent = \"%s\"' % user_agent)\n"
  },
  {
    "path": "examples/old_wordle_script.py",
    "content": "\"\"\"Solve the Wordle game using SeleniumBase.\r\nThis test runs on archived versions of Wordle, containing Shadow-DOM.\"\"\"\r\nimport ast\r\nimport random\r\nimport requests\r\nfrom seleniumbase import version_tuple\r\nfrom seleniumbase import BaseCase\r\n\r\nif __name__ == \"__main__\":\r\n    from pytest import main\r\n    main([__file__, \"--sjw\", \"--pls=none\"])\r\n\r\n\r\nclass WordleTests(BaseCase):\r\n\r\n    word_list = []\r\n\r\n    def initialize_word_list(self):\r\n        txt_file = \"https://seleniumbase.github.io/cdn/txt/wordle_words.txt\"\r\n        word_string = requests.get(txt_file, timeout=3).text\r\n        self.word_list = ast.literal_eval(word_string)\r\n\r\n    def modify_word_list(self, word, letter_status):\r\n        new_word_list = []\r\n        correct_letters = []\r\n        present_letters = []\r\n        for i in range(len(word)):\r\n            if letter_status[i] == \"correct\":\r\n                correct_letters.append(word[i])\r\n                for w in self.word_list:\r\n                    if w[i] == word[i]:\r\n                        new_word_list.append(w)\r\n                self.word_list = new_word_list\r\n                new_word_list = []\r\n        for i in range(len(word)):\r\n            if letter_status[i] == \"present\":\r\n                present_letters.append(word[i])\r\n                for w in self.word_list:\r\n                    if word[i] in w and word[i] != w[i]:\r\n                        new_word_list.append(w)\r\n                self.word_list = new_word_list\r\n                new_word_list = []\r\n        for i in range(len(word)):\r\n            if letter_status[i] == \"absent\":\r\n                if (\r\n                    word[i] not in correct_letters\r\n                    and word[i] not in present_letters\r\n                ):\r\n                    for w in self.word_list:\r\n                        if word[i] not in w:\r\n                            new_word_list.append(w)\r\n                else:\r\n                    for w in self.word_list:\r\n                        if word[i] != w[i]:\r\n                            new_word_list.append(w)\r\n                self.word_list = new_word_list\r\n                new_word_list = []\r\n\r\n    def skip_if_incorrect_env(self):\r\n        if self.headless:\r\n            message = \"This test doesn't run in headless mode!\"\r\n            print(message)\r\n            self.skip(message)\r\n        if not self.is_chromium():\r\n            message = \"This test requires a Chromium-based browser!\"\r\n            print(message)\r\n            self.skip(message)\r\n        if version_tuple < (4, 0, 0):\r\n            message = \"This test requires SeleniumBase 4.0.0 or newer!\"\r\n            print(message)\r\n            self.skip(message)\r\n\r\n    def test_wordle(self):\r\n        self.skip_if_incorrect_env()\r\n        random.seed()\r\n        year = \"2022\"\r\n        month = random.randint(3, 5)\r\n        day = random.randint(1, 30)\r\n        date = str(year) + \"0\" + str(month) + str(day)\r\n        archive = \"https://web.archive.org/web/\"\r\n        url = \"https://www.nytimes.com/games/wordle/index.html\"\r\n        past_wordle = archive + date + \"/\" + url\r\n        print(\"\\n\" + past_wordle)\r\n        self.open(past_wordle)\r\n        self.wait_for_element(\"#wm-ipp-base\")\r\n        self.remove_elements(\"#wm-ipp-base\")\r\n        self.click(\"game-app::shadow game-modal::shadow game-icon\")\r\n        self.initialize_word_list()\r\n        keyboard_base = \"game-app::shadow game-keyboard::shadow \"\r\n        word = random.choice(self.word_list)\r\n        num_attempts = 0\r\n        found_word = False\r\n        for attempt in range(6):\r\n            num_attempts += 1\r\n            word = random.choice(self.word_list)\r\n            letters = []\r\n            for letter in word:\r\n                letters.append(letter)\r\n                button = 'button[data-key=\"%s\"]' % letter\r\n                self.click(keyboard_base + button)\r\n            button = \"button.one-and-a-half\"\r\n            self.click(keyboard_base + button)\r\n            row = 'game-app::shadow game-row[letters=\"%s\"]::shadow ' % word\r\n            tile = row + \"game-tile:nth-of-type(%s)\"\r\n            self.wait_for_element(tile % \"5\" + '::shadow [data-state$=\"t\"]')\r\n            self.wait_for_element(\r\n                tile % \"5\" + '::shadow [data-animation=\"idle\"]'\r\n            )\r\n            letter_status = []\r\n            for i in range(1, 6):\r\n                letter_eval = self.get_attribute(tile % str(i), \"evaluation\")\r\n                letter_status.append(letter_eval)\r\n            if letter_status.count(\"correct\") == 5:\r\n                found_word = True\r\n                break\r\n            self.word_list.remove(word)\r\n            self.modify_word_list(word, letter_status)\r\n\r\n        self.save_screenshot_to_logs()\r\n        if found_word:\r\n            print('Word: \"%s\"\\nAttempts: %s' % (word.upper(), num_attempts))\r\n        else:\r\n            print('Final guess: \"%s\" (Not the correct word!)' % word.upper())\r\n            self.fail(\"Unable to solve for the correct word in 6 attempts!\")\r\n        self.sleep(3)\r\n"
  },
  {
    "path": "examples/parameterized_test.py",
    "content": "from parameterized import parameterized\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass SearchTests(BaseCase):\n    @parameterized.expand(\n        [\n            [\"SeleniumBase Commander\", \"Commander\", \"GUI / Commander\"],\n            [\"SeleniumBase Recorder\", \"Recorder\", \"Recorder Mode\"],\n            [\"SeleniumBase Syntax\", \"Syntax\", \"Syntax Formats\"],\n        ]\n    )\n    def test_parameterized_search(self, search_term, keyword, title_text):\n        self.open(\"https://seleniumbase.io/help_docs/how_it_works/\")\n        self.assert_title_contains(\"SeleniumBase Docs\")\n        self.type('input[aria-label=\"Search\"]', search_term)\n        self.click('mark:contains(\"%s\")' % keyword)\n        self.assert_title_contains(title_text)\n        self.save_screenshot_to_logs()\n"
  },
  {
    "path": "examples/performance_test.py",
    "content": "\"\"\"Performance test example.\n\nUses decorators.print_runtime(), which prints the runtime duration\nof a method or \"with\"-block after the method (or block) completes.\nAlso raises an exception when exceeding the time \"limit\" if set.\n\nArguments ->\n    description  # Optional - Shows description in print output.\n    limit  # Optional - Fail if the duration is above the limit.\n\nMethod / Function example usage ->\n    from seleniumbase import decorators\n\n    @decorators.print_runtime(\"My Method\")\n    def my_method():\n        # code ...\n        # code ...\n\n\"with\"-block example usage ->\n    from seleniumbase import decorators\n\n    with decorators.print_runtime(\"My Code Block\"):\n        # code ...\n        # code ... \"\"\"\nfrom seleniumbase import BaseCase\nfrom seleniumbase import decorators\nBaseCase.main(__name__, __file__)\n\n\nclass PerformanceClass(BaseCase):\n    @decorators.print_runtime(\"Open Swag Labs and Log In\")\n    def login_to_swag_labs(self):\n        print()\n        with decorators.print_runtime(\"Open Swag Labs\"):\n            self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n\n    def test_performance_of_swag_labs(self):\n        self.login_to_swag_labs()\n        self.assert_element(\"div.inventory_list\")\n        self.assert_exact_text(\"Products\", \"span.title\")\n        with decorators.print_runtime(\"Add backpack and see cart\"):\n            self.click('button[name*=\"backpack\"]')\n            self.click(\"#shopping_cart_container a\")\n            self.assert_text(\"Backpack\", \"div.cart_item\")\n        with decorators.print_runtime(\"Remove backpack from cart\"):\n            self.click('button:contains(\"Remove\")')  # HTML innerText\n            self.assert_text_not_visible(\"Backpack\", \"div.cart_item\")\n        with decorators.print_runtime(\"Log out from Swag Labs\", 3):\n            self.js_click(\"a#logout_sidebar_link\")\n            self.assert_element(\"div#login_button_container\")\n"
  },
  {
    "path": "examples/presenter/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> 📑 Presenter 🎞️</h2>\n\n<p>SeleniumBase Presenter (slide-maker) lets you use Python to generate HTML presentations.</p>\n\n<b>Here's a sample presentation:</b>\n\n<a href=\"https://seleniumbase.github.io/other/presenter.html\"><img width=\"500\" src=\"https://seleniumbase.github.io/other/presenter.gif\" title=\"Screenshot\"></a><br>\n\n([Click on the image/GIF for the actual presentation](https://seleniumbase.io/other/presenter.html))\n\n([Here's the code for that presentation](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/my_presentation.py))\n\nSlides can include HTML, code, images, and iframes.\n\nHere's how to run the example presentation:\n\n```zsh\ncd examples/presenter\npytest my_presentation.py\n```\n\n<b>Here's a presentation with a chart:</b>\n\n<a href=\"https://seleniumbase.github.io/other/core_presentation.html\"><img width=\"428\" src=\"https://seleniumbase.github.io/other/sb_core_areas.png\" title=\"Screenshot\"></a><br>\n\n([Click on the image/GIF for the actual presentation](https://seleniumbase.io/other/core_presentation.html))\n\n([Here's the code for that presentation](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/core_presentation.py))\n\nHere's how to run that example:\n\n```zsh\ncd examples/presenter\npytest core_presentation.py\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Creating a new presentation:</h3>\n\n```python\nself.create_presentation(name=None, theme=\"serif\", transition=\"default\")\n\"\"\" Creates a Reveal-JS presentation that you can add slides to.\n    @Params\n    name - If creating multiple presentations at the same time,\n           use this to specify the name of the current presentation.\n    theme - Set a theme with a unique style for the presentation.\n            Valid themes: \"serif\" (default), \"sky\", \"white\", \"black\",\n                          \"simple\", \"league\", \"moon\", \"night\",\n                          \"beige\", \"blood\", and \"solarized\".\n    transition - Set a transition between slides.\n                 Valid transitions: \"none\" (default), \"slide\", \"fade\",\n                                    \"zoom\", \"convex\", and \"concave\".\n\"\"\"\n```\n\nIf creating multiple presentations at the same time, you can pass the ``name`` parameter to distinguish between different presentations.\nNotes are disabled by default. You can enable notes by specifying:\n``show_notes=True``\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Adding a slide to a presentation:</h3>\n\n```python\nself.add_slide(content=None, image=None, code=None, iframe=None,\n               content2=None, notes=None, transition=None, name=None)\n\"\"\" Allows the user to add slides to a presentation.\n    @Params\n    content - The HTML content to display on the presentation slide.\n    image - Attach an image (from a URL link) to the slide.\n    code - Attach code of any programming language to the slide.\n           Language-detection will be used to add syntax formatting.\n    iframe - Attach an iFrame (from a URL link) to the slide.\n    content2 - HTML content to display after adding an image or code.\n    notes - Additional notes to include with the slide.\n            ONLY SEEN if show_notes is set for the presentation.\n    transition - Set a transition between slides. (overrides previous)\n                 Valid transitions: \"none\" (default), \"slide\", \"fade\",\n                                    \"zoom\", \"convex\", and \"concave\".\n    name - If creating multiple presentations at the same time,\n           use this to select the presentation to add slides to.\n\"\"\"\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Running a presentation:</h3>\n\n```python\nself.begin_presentation(\n    filename=\"my_presentation.html\", show_notes=False, interval=0)\n\"\"\" Begin a Reveal-JS Presentation in the web browser.\n    @Params\n    name - If creating multiple presentations at the same time,\n           use this to select the one you wish to add slides to.\n    filename - The name of the HTML file that you wish to\n               save the presentation to. (filename must end in \".html\")\n    show_notes - When set to True, the Notes feature becomes enabled,\n                 which allows presenters to see notes next to slides.\n    interval - The delay time between autoplaying slides. (in seconds)\n               If set to 0 (default), autoplay is disabled.\n\"\"\"\n```\n\nBefore the presentation is run, the full HTML is saved to the ``saved_presentations/`` folder.\n\n\nAll methods have the optional ``name`` argument, which is only needed if you're creating multiple presentations at once.\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Here's an example of using SeleniumBase Presenter:</h3>\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass MyPresenterClass(BaseCase):\n    def test_presenter(self):\n        self.create_presentation(theme=\"serif\")\n        self.add_slide(\n            '<h1>Welcome</h1><br />\\n'\n            '<h3>Press the <b>Right Arrow</b></h3>')\n        self.add_slide(\n            '<h3>SeleniumBase Presenter</h3><br />\\n'\n            '<img width=\"240\" src=\"https://seleniumbase.io/img/logo3a.png\" />'\n            '<span style=\"margin:144px;\" />'\n            '<img src=\"https://seleniumbase.io/other/python_3d_logo.png\" />'\n            '<br /><br />\\n<h4>Create presentations with <b>Python</b></h4>')\n        self.add_slide(\n            '<h3>Make slides using <b>HTML</b>:</h3><br />\\n'\n            '<table style=\"padding:10px;border:4px solid black;font-size:50;\">'\n            '\\n<tr style=\"background-color:CDFFFF;\">\\n'\n            '<th>Row ABC</th><th>Row XYZ</th></tr>\\n'\n            '<tr style=\"background-color:DCFDDC;\">'\n            '<td>Value ONE</td><td>Value TWO</td></tr>\\n'\n            '<tr style=\"background-color:DFDFFB;\">\\n'\n            '<td>Value THREE</td><td>Value FOUR</td></tr>\\n'\n            '</table><br />\\n<h4>(HTML <b>table</b> example)</h4>')\n        self.add_slide(\n            '<h3>Keyboard Shortcuts:</h3>\\n'\n            '<table style=\"padding:10px;border:4px solid black;font-size:30;'\n            'background-color:FFFFDD;\">\\n'\n            '<tr><th>Key</th><th>Action</th></tr>\\n'\n            '<tr><td><b>=></b></td><td>Next Slide (N also works)</td></tr>\\n'\n            '<tr><td><b><=</b></td><td>Previous Slide (P also works)</td></tr>'\n            '\\n<tr><td>F</td><td>Full Screen Mode</td></tr>\\n'\n            '<tr><td>O</td><td>Overview Mode Toggle</td></tr>\\n'\n            '<tr><td>esc</td><td>Exit Full Screen / Overview Mode</td></tr>\\n'\n            '<tr><td><b>.</b></td><td>Pause/Resume Toggle</td></tr>\\n'\n            '<tr><td>space</td><td>Next Slide (alternative)</td></tr></table>')\n        self.add_slide(\n            '<h3>Add <b>images</b> to slides:</h3>',\n            image=\"https://seleniumbase.github.io/other/seagulls.jpg\")\n        self.add_slide(\n            '<h3>Add <b>code</b> to slides:</h3>',\n            code=(\n                'from seleniumbase import BaseCase\\n\\n'\n                'class MyTestClass(BaseCase):\\n\\n'\n                '    def test_basics(self):\\n'\n                '        self.open(\"https://store.xkcd.com/search\")\\n'\n                '        self.type(\\'input[name=\"q\"]\\', \"xkcd book\\\\n\")\\n'\n                '        self.assert_text(\"xkcd: volume 0\", \"h3\")\\n'\n                '        self.open(\"https://xkcd.com/353/\")\\n'\n                '        self.assert_title(\"xkcd: Python\")\\n'\n                '        self.assert_element(\\'img[alt=\"Python\"]\\')\\n'\n                '        self.click(\\'a[rel=\"license\"]\\')\\n'\n                '        self.assert_text(\"free to copy and reuse\")\\n'\n                '        self.go_back()\\n'\n                '        self.click_link(\"About\")\\n'\n                '        self.assert_exact_text(\"xkcd.com\", \"h2\")'))\n        self.add_slide(\n            \"<h3>Highlight <b>code</b> in slides:</h3>\",\n            code=(\n                'from seleniumbase import BaseCase\\n\\n'\n                '<mark>class MyTestClass(BaseCase):</mark>\\n\\n'\n                '    def test_basics(self):\\n'\n                '        self.open(\"https://store.xkcd.com/search\")\\n'\n                '        self.type(\\'input[name=\"q\"]\\', \"xkcd book\\\\n\")\\n'\n                '        self.assert_text(\"xkcd: volume 0\", \"h3\")'))\n        self.add_slide(\n            '<h3>Add <b>iFrames</b> to slides:</h3>',\n            iframe=\"https://seleniumbase.io/demo_page\")\n        self.add_slide(\n            '<h3>Getting started is <b>easy</b>:</h3>',\n            code=(\n                'from seleniumbase import BaseCase\\n\\n'\n                'class MyPresenterClass(BaseCase):\\n\\n'\n                '    def test_presenter(self):\\n'\n                '        self.create_presentation(theme=\"serif\")\\n'\n                '        self.add_slide(\"Welcome to Presenter!\")\\n'\n                '        self.add_slide(\\n'\n                '            \"Add code to slides:\",\\n'\n                '            code=(\\n'\n                '                \"from seleniumbase import BaseCase\\\\n\\\\n\"\\n'\n                '                \"class MyPresenterClass(BaseCase):\\\\n\\\\n\"\\n'\n                '                \"    def test_presenter(self):\\\\n\"\\n'\n                '                \"        self.create_presentation()\\\\n\"))\\n'\n                '        self.begin_presentation(\\n'\n                '            filename=\"demo.html\", show_notes=True)'))\n        self.add_slide(\n            '<h3>Include <b>notes</b> with slides:</h3><br />',\n            code=('self.add_slide(\"[Your HTML goes here]\",\\n'\n                  '               code=\"[Your software code goes here]\",\\n'\n                  '               content2=\"[Additional HTML goes here]\",\\n'\n                  '               notes=\"[Attached speaker notes go here]\"\\n'\n                  '                     \"[Note A! -- Note B! -- Note C! ]\")'),\n            notes='<h2><ul><li>Note A!<li>Note B!<li>Note C!<li>Note D!</h2>',\n            content2=\"<h4>(Notes can include HTML tags)</h4>\")\n        self.add_slide(\n            '<h3>Multiple <b>themes</b> available:</h3>',\n            code=(\n                'self.create_presentation(theme=\"serif\")\\n\\n'\n                'self.create_presentation(theme=\"sky\")\\n\\n'\n                'self.create_presentation(theme=\"simple\")\\n\\n'\n                'self.create_presentation(theme=\"white\")\\n\\n'\n                'self.create_presentation(theme=\"moon\")\\n\\n'\n                'self.create_presentation(theme=\"black\")\\n\\n'\n                'self.create_presentation(theme=\"night\")\\n\\n'\n                'self.create_presentation(theme=\"beige\")\\n\\n'\n                'self.create_presentation(theme=\"league\")'))\n        self.add_slide(\n            '<h2><b>The End</b></h2>',\n            image=\"https://seleniumbase.github.io/img/sb_logo_10.png\")\n        self.begin_presentation(\n            filename=\"presenter.html\", show_notes=True, interval=0)\n```\n\nThat example is from [my_presentation.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/my_presentation.py), which you can run from the ``examples/presenter`` folder with the following command:\n\n```zsh\npytest my_presentation.py\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Saving a presentation:</h3>\n\nIf you want to save the presentation you created as an HTML file, use:\n\n```python\nself.save_presentation(filename=\"my_presentation.html\", show_notes=True)\n```\n\nPresentations automatically get saved when calling:\n\n```python\nself.begin_presentation(show_notes=True)\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> Special abilities:</h3>\n\nIf you want to highlight multiple lines at different times in the same slide with the `<mark>` / `</mark>` tags, you can use the new `<mk-0>`-`</mk-0>`, `<mk-1>`-`</mk-1>` tags, which will generate multiple HTML slides from one Python slide.\n\nExample:\n\n```python\nself.add_slide(\n    code=(\n        <p><mk-0>Highlight this on the 1st generated slide</mk-0></p>\n        <p><mk-1>Highlight this on the 2nd generated slide</mk-1></p>\n        <p><mk-2>Highlight this on the 3rd generated slide</mk-2></p>\n        <p><mk-3>Highlight this on the 4th generated slide</mk-3></p>\n    )\n)\n```\n\nThose should automatically get converted to `<mark>` ... `</mark>` on their turn:\n\nEg. First generated slide:\n\n```html\n<p><mark>Highlight this on the first generated slide</mark></p>\n<p>Highlight this on the second generated slide</p>\n<p>Highlight this on the third generated slide</p>\n<p>Highlight this on the fourth generated slide></p>\n```\n\nEg. Second generated slide:\n\n```html\n<p>Highlight this on the first generated slide</p>\n<p><mark>Highlight this on the second generated slide</mark></p>\n<p>Highlight this on the third generated slide</p>\n<p>Highlight this on the fourth generated slide></p>\n```\n\nEtc...\n\n--------\n\n<h3 align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_b.png\" alt=\"SeleniumBase\" width=\"240\" /></h3>\n"
  },
  {
    "path": "examples/presenter/core_presentation.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass PresentationWithChart(BaseCase):\n    def test_seleniumbase_chart(self):\n        self.create_presentation(theme=\"league\", transition=\"slide\")\n        self.create_pie_chart(title=\"The 4 core areas of SeleniumBase:\")\n        self.add_data_point(\"Basic API (test methods)\", 1)\n        self.add_data_point(\"Command-line options (pytest options)\", 1)\n        self.add_data_point(\"The Console Scripts interface\", 1)\n        self.add_data_point(\"Advanced API (Tours, Charts, & Presentations)\", 1)\n        self.add_slide(\"<p>SeleniumBase core areas</p>\" + self.extract_chart())\n        self.add_slide(\n            \"<p>Basic API (test methods). Example test:</p>\",\n            code=(\n                \"from seleniumbase import BaseCase\\n\\n\"\n                \"class TestMFALogin(BaseCase):\\n\\n\"\n                \"    def test_mfa_login(self):\\n\"\n                '        self.open(\"seleniumbase.io/realworld/login\")\\n'\n                '        self.type(\"#username\", \"demo_user\")\\n'\n                '        self.type(\"#password\", \"secret_pass\")\\n'\n                '        self.enter_mfa_code('\n                '\"#totpcode\", \"GAXG2MTEOR3DMMDG\")\\n'\n                '        self.assert_text(\"Welcome!\", \"h1\")\\n'\n                '        self.highlight(\"img#image1\")\\n'\n                \"        self.click('a:contains(\\\"This Page\\\")')\\n\"\n                \"        self.save_screenshot_to_logs()\\n\"\n                '        self.click_link(\"Sign out\")\\n'\n                '        self.assert_element(\\'a:contains(\"Sign in\")\\')\\n'\n            ),\n        )\n        self.add_slide(\n            \"<p>Command-line options. Examples:</p>\",\n            code=(\n                \"$ pytest my_first_test.py\\n\"\n                \"$ pytest test_swag_labs.py --mobile\\n\"\n                \"$ pytest edge_test.py --browser=edge\\n\"\n                \"$ pytest basic_test.py --headless\\n\"\n                \"$ pytest my_first_test.py --demo --guest\\n\"\n                \"$ pytest basic_test.py --slow\\n\"\n                \"$ pytest -v -m marker2 --headless --save-screenshot\\n\"\n                \"$ pytest parameterized_test.py --reuse-session\\n\"\n                \"$ pytest test_suite.py --html=report.html --rs\\n\"\n                \"$ pytest test_suite.py --dashboard --html=report.html\\n\"\n                \"$ pytest github_test.py --demo --disable-csp\\n\"\n                \"$ pytest test_suite.py -n=2 --rs --crumbs\\n\"\n                \"$ pytest test_demo_site.py --incognito\\n\"\n                \"$ pytest verify_undetected.py --uc\\n\"\n                \"$ pytest basic_test.py --sjw --pls=none\\n\"\n            ),\n        )\n        self.add_slide(\n            \"<p>The Console Scripts interface. Examples:</p>\",\n            code=(\n                \"$ sbase get chromedriver\\n\"\n                \"$ sbase mkdir new_test_folder\\n\"\n                \"$ sbase mkfile new_test.py\\n\"\n                \"$ sbase mkpres new_presentation.py\\n\"\n                \"$ sbase mkchart new_chart.py\\n\"\n                \"$ sbase print basic_test.py -n\\n\"\n                \"$ sbase translate basic_test.py -p --ru -n\\n\"\n                \"$ sbase grid-hub start\\n\"\n                '$ sbase grid-node start --hub=\"127.0.0.1\"\\n'\n                \"$ sbase grid-node stop\\n\"\n                \"$ sbase grid-hub stop\\n\"\n                \"$ sbase recorder\\n\"\n                \"$ sbase commander\\n\"\n                \"$ sbase methods\\n\"\n                \"$ sbase options\\n\"\n            ),\n        )\n        self.add_slide(\n            '<p>Advanced API. \"Presenter\" example:</p>',\n            code=(\n                \"from seleniumbase import BaseCase\\n\\n\"\n                \"class MyPresenterClass(BaseCase):\\n\"\n                \"    def test_presenter(self):\\n\"\n                '        self.create_presentation(theme=\"serif\")\\n'\n                '        self.add_slide(\"Welcome to Presenter!\")\\n'\n                \"        self.add_slide(\\n\"\n                '            \"Add code to slides:\",\\n'\n                \"            code=(\\n\"\n                '                \"from seleniumbase import BaseCase\\\\n\\\\n\"\\n'\n                '                \"class MyPresenterClass(BaseCase):\\\\n\\\\n\"\\n'\n                '                \"    def test_presenter(self):\\\\n\"\\n'\n                '                \"        self.create_presentation()\\\\n\"))\\n'\n                \"        self.begin_presentation(\\n\"\n                '            filename=\"demo.html\", show_notes=True)'\n            ),\n        )\n        self.add_slide(\n            \"<p><b>The End</b></p>\",\n            image=\"https://seleniumbase.io/cdn/img/sb_logo_g.png\",\n        )\n        self.begin_presentation(filename=\"core_presentation.html\")\n"
  },
  {
    "path": "examples/presenter/edge_presentation.py",
    "content": "from seleniumbase import BaseCase\r\n\r\nif __name__ == \"__main__\":\r\n    from pytest import main\r\n    main([__file__, \"--edge\", \"-s\", \"--disable-csp\"])\r\n\r\n\r\nclass EdgePresentationClass(BaseCase):\r\n    def test_presentation(self):\r\n        if not self.browser == \"edge\" or not self.disable_csp:\r\n            self.driver.quit()\r\n            self.get_new_driver(browser=\"edge\", disable_csp=True)\r\n        self.demo_mode = False\r\n        self.maximize_window()\r\n        self._output_file_saves = False\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h3>A deep dive into:</h3>\"\r\n            \"<h2>Browser automation</h2>\"\r\n            \"<h2>on Edge, with Python!</h2>\\n\"\r\n            \"<br /><hr /><br />\\n\"\r\n            \"<h3>Presented by <b>Michael Mintz</b></h3>\\n\"\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n        self.open(\"data:,\")\r\n        self.open(\"https://www.bostoncodecamp.com/CC34/Schedule/SessionGrid\")\r\n        self.highlight(\"h2\", loops=8)\r\n        if self.is_element_visible('[data-sessionid=\"467776\"]'):\r\n            self.highlight('div[data-sessionid=\"467776\"]', loops=10)\r\n            self.create_tour(theme=\"driverjs\")\r\n            self.add_tour_step(\r\n                \"<h2>Here we are</h2>\", '[data-sessionid=\"467776\"]'\r\n            )\r\n            self.play_tour()\r\n            self.click('a[onclick*=\"467776\"]')\r\n            self.create_tour(theme=\"hopscotch\")\r\n            self.add_tour_step(\r\n                \"<h2>What to expect</h2>\",\r\n                \"div.sz-modal-session\",\r\n                alignment=\"left\",\r\n            )\r\n            self.play_tour()\r\n            self.sleep(0.25)\r\n        self.open(\"data:,\")\r\n        self.create_presentation(theme=\"beige\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<p><b>About the presenter:</b></p>\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>I created <b>SeleniumBase</b> (for Python).</li>\\n\"\r\n            \"<li>I lead the Automation Team at <b>iboss</b>.</li>\\n\"\r\n            \"</ul>\\n\",\r\n            image=\"https://seleniumbase.io/other/iboss_booth.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>By the end of this presentation, you'll learn:</b></p>\"\r\n            \"<hr /><br />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>How to automate on Edge using Microsoft's WebDriver.</li>\"\r\n            \"<br />\\n\"\r\n            \"<li>How Python frameworks can simplify Edge automation.</li>\"\r\n            \"<br />\\n\"\r\n            \"</ul>\\n\",\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n        self.open(\"data:,\")\r\n        self.open(\r\n            \"https://learn.microsoft.com/en-us/microsoft-edge/\"\r\n            \"test-and-automation/test-and-automation\"\r\n        )\r\n        self.wait_for_element(\"h1\")\r\n        self.sleep(1)\r\n        self.create_tour(theme=\"default\")\r\n        self.add_tour_step(\"<h1>Let's begin the overview!</h1>\")\r\n        self.play_tour()\r\n        if self.is_element_visible('button[data-bi-name=\"close\"]'):\r\n            self.click('button[data-bi-name=\"close\"]')\r\n            self.wait_for_element_not_visible('button[data-bi-name=\"close\"]')\r\n        self.highlight(\"h1#test-and-automation-in-microsoft-edge\")\r\n        self.create_tour(theme=\"driverjs\")\r\n        self.add_tour_step(\r\n            \"\", \"h1#test-and-automation-in-microsoft-edge\", alignment=\"right\"\r\n        )\r\n        self.add_tour_step(\"\", \"nav#center-doc-outline ~ p\", alignment=\"right\")\r\n        self.add_tour_step(\r\n            \"\", 'table[aria-label*=\"Test and automation\"]', alignment=\"right\"\r\n        )\r\n        self.add_tour_step(\"A framework\", \"#playwright\", alignment=\"left\")\r\n        self.add_tour_step(\"Another framework\", \"#puppeteer\", alignment=\"left\")\r\n        self.add_tour_step(\"Today's framework\", \"#webdriver\", alignment=\"left\")\r\n        self.add_tour_step(\r\n            \"\", 'a[href=\"../webdriver-chromium/\"]', alignment=\"right\"\r\n        )\r\n        self.play_tour()\r\n        self.highlight('a:contains(\"Use WebDriver to automate\")')\r\n        self.open(\r\n            \"https://learn.microsoft.com/en-us/\"\r\n            \"microsoft-edge/webdriver-chromium/?tabs=python\"\r\n        )\r\n        self.wait_for_element(\"h1\")\r\n        self.create_tour(theme=\"driverjs\")\r\n        self.add_tour_step(\r\n            \"\", \"#use-webdriver-to-automate-microsoft-edge\", alignment=\"right\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", 'div[data-heading-level=\"h2\"] ~ p', alignment=\"right\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", 'div[data-heading-level=\"h2\"] ~ ul', alignment=\"right\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", 'table[aria-label=\"Table 1\"]', alignment=\"right\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", \"#download-microsoft-edge-webdriver\", alignment=\"right\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", 'img[src*=\"microsoft-edge-version\"]', alignment=\"right\"\r\n        )\r\n        self.play_tour()\r\n        self.highlight('img[src*=\"microsoft-edge-version\"]')\r\n        self.highlight('img[src*=\"microsoft-edge-driver-install\"]', loops=8)\r\n        self.highlight('p:contains(\"that matches your version\")', loops=8)\r\n\r\n        self.create_tour(theme=\"driverjs\")\r\n        self.add_tour_step(\r\n            \"\", '[href*=\"microsoft-edge/tools/webdriver\"]', alignment=\"right\"\r\n        )\r\n        self.play_tour()\r\n        self.highlight('[href*=\"microsoft-edge/tools/webdriver\"]')\r\n\r\n        self.get_new_driver(browser=\"edge\", disable_csp=True)\r\n        self.maximize_window()\r\n        self.open(\r\n            \"https://developer.microsoft.com/en-us/\"\r\n            \"microsoft-edge/tools/webdriver/\"\r\n        )\r\n        self.wait_for_element(\"div.common-heading\")\r\n        self.scroll_to(\"div.common-heading\")\r\n        zoom_in = 'div.h1{zoom: 1.02;-moz-transform: scale(1.02);}'\r\n        self.add_css_style(zoom_in)\r\n        self.highlight(\"div.common-heading\", loops=8)\r\n        self.create_tour(theme=\"hopscotch\")\r\n        self.add_tour_step(\"\", \"div.common-heading\", alignment=\"right\")\r\n        self.play_tour()\r\n\r\n        self.create_tour(theme=\"hopscotch\")\r\n        self.add_tour_step(\r\n            \"\", \"div.block-heading--sixtyforty\", alignment=\"left\"\r\n        )\r\n        self.play_tour()\r\n        self.quit_extra_driver()\r\n\r\n        self.switch_to_default_driver()\r\n        self.create_tour(theme=\"hopscotch\")\r\n        self.add_tour_step(\r\n            \"\", 'img[src*=\"microsoft-edge-driver-install\"]', alignment=\"left\"\r\n        )\r\n        self.play_tour()\r\n        self.highlight('p:contains(\"After the download completes\")', loops=10)\r\n        self.sleep(0.5)\r\n\r\n        self.create_tour(theme=\"hopscotch\")\r\n        self.add_tour_step(\r\n            \"\", \"#choose-a-webdriver-testing-framework\", alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\"\", \"#using-selenium-4\", alignment=\"left\")\r\n        self.add_tour_step(\r\n            \"\", \"#automate-microsoft-edge-with-webdriver\", alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\"\", \"#automate-microsoft-edge\", alignment=\"left\")\r\n        self.add_tour_step(\"\", \"#tabgroup_1\", alignment=\"left\")\r\n        self.add_tour_step(\r\n            \"\", '[id*=\"configure-the-edge-webdriver-serv\"]', alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\"\", \"#tabgroup_2\", alignment=\"left\")\r\n        self.add_tour_step(\r\n            \"\", \"#configure-microsoft-edge-options\", alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", \"#choose-specific-browser-binaries\", alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\"\", \"#tabgroup_3\", alignment=\"left\")\r\n        self.add_tour_step(\r\n            \"\", \"#pass-extra-command-line-arguments\", alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\"\", \"#tabgroup_4\", alignment=\"left\")\r\n        self.add_tour_step(\r\n            \"\", \"#other-webdriver-installation-options\", alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", 'code[data-author-content*=\"docker run\"]', alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", \"#opt-out-of-diagnostic-data-collection\", alignment=\"left\"\r\n        )\r\n        self.add_tour_step(\r\n            \"\", \"#developer-tools-availability-policy\", alignment=\"left\"\r\n        )\r\n        self.play_tour()\r\n        self.sleep(0.25)\r\n        self.open(\"data:,\")\r\n\r\n        self.create_presentation(theme=\"sky\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<p>How do you get Selenium?</p>\\n\"\r\n            \"<hr />\\n\"\r\n            \"<p>(for Python)</p><br />\\n\"\r\n            \"<h3><code><mark>pip install selenium</mark></code></h3>\",\r\n            image=\"https://seleniumbase.io/other/selenium_pypi.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What are some building blocks?</p>\\n\"\r\n            \"<hr /><br />\\n\",\r\n            code=(\r\n                \"<mk-0>from selenium import webdriver</mk-0>\\n\\n\"\r\n                \"<mk-1>driver = webdriver.Edge()</mk-1>\\n\\n\"\r\n                '<mk-2>driver.get(\"http://selenium.dev\")</mk-2>\\n\\n'\r\n                \"<mk-3>element = driver.find_element\"\r\n                '(\"css selector\", \"#docsearch span\")\\n\\n'\r\n                \"element.click()</mk-3>\\n\\n\"\r\n                \"<mk-4>elem_2 = driver.find_element\"\r\n                '(\"css selector\", \"#docsearch-input\")\\n\\n'\r\n                'elem_2.send_keys(\"Python\")</mk-4>\\n\\n'\r\n                \"<mk-5>driver.quit()</mk-5>\\n\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p>Is Selenium really a framework, or just a library?</p>\\n\"\r\n            \"<hr /><br />\\n\"\r\n            \"<p>Given that Selenium uses WebDriver APIs for interacting with\"\r\n            \" websites, but lacks essential features for structuring tests,\"\r\n            \" (and more...), Selenium is really: JUST A LIBRARY!</p>\\n\",\r\n            image=\"https://seleniumbase.io/other/selenium_slogan.png\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>JUST A LIBRARY, continued...</p>\\n\"\r\n            \"<hr /><br />\\n\"\r\n            \"<p>Technically, Selenium consists of multiple language bindings\"\r\n            \" for interacting with WebDriver APIs. These bindings include:\"\r\n            \" C#, Java, JS, Python, and Ruby.</p>\\n\",\r\n            image=\"https://seleniumbase.io/other/library_books.jpg\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>Test frameworks wrap Selenium to improve things!</p><hr />\\n\"\r\n            \"<br />\"\r\n            '<a href=\"https://selenium.dev/documentation/overview/components/'\r\n            '#where-frameworks-fit-in\">'\r\n            '(Where does a framework fit in?)</a>\\n',\r\n            image=\"https://seleniumbase.io/other/with_a_framework.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without extra libraries or frameworks?</h4><hr />\"\r\n            \"<p>\\n\"\r\n            \"<br />\",\r\n            image=\"https://seleniumbase.io/other/sel_and_py_2.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without extra libraries or frameworks?</p><hr />\"\r\n            \"<p>\\n\"\r\n            \"<mark>The default timeout is 0: If an element isn't immediately \"\r\n            \"ready to be interacted with, you'll get errors when trying \"\r\n            \"to interact with those elements.</mark>\\n\"\r\n            \"</p>\\n\",\r\n            image=\"https://seleniumbase.io/other/messy_stacktrace.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without extra libraries or frameworks?</p><hr />\"\r\n            \"<p><br />\\n\"\r\n            \"The command statements can get a bit too long:</p>\\n\"\r\n            \"<p><code><mk-0>\"\r\n            \"driver.find_element(By.CSS_SELECTOR, CSS_SELECTOR).click()\"\r\n            \"</code></mk-0></p><br />\"\r\n            \"<p>This is better:</p>\"\r\n            \"<p><code><mk-1>self.click(CSS_SELECTOR)</mk-1></code><p><br />\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without extra libraries or frameworks?</p><hr /><br />\\n\"\r\n            \"<mark>No HTML reports, dashboards, screenshots...</mark><br />\"\r\n            \"<p>A test framework can provide those!</p><br />\",\r\n        )\r\n        self.add_slide(\r\n            \"<h6>Raw Selenium disadvantages, continued...</h6><hr />\"\r\n            \"<h6>No HTML reports, dashboards, screenshots...</h6>\\n\"\r\n            \"<mark>A test framework can provide those!</mark>\",\r\n            image=\"https://seleniumbase.io/cdn/img/dash_report.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>Raw Selenium disadvantages, continued...</p><hr />\\n<br />\\n\"\r\n            \"<p><mk-0>It takes multiple lines of code to do simple tasks:\"\r\n            \"</mk-0></p>\\n<pre>\\n\"\r\n            'element = driver.find_element(\"css selector\", \"#password\")\\n'\r\n            \"element.clear()\\n\"\r\n            'element.send_keys(\"secret_sauce\")\\n'\r\n            'element.submit()\\n'\r\n            \"</pre>\\n<br />\\n\"\r\n            \"<p><mk-1>But with a framework, do all that in ONE line:</mk-1>\"\r\n            '</p>\\n<pre>self.type(\"#password\", \"secret_sauce\\\\n\")</pre>'\r\n        )\r\n        self.add_slide(\r\n            \"<p>What else can test frameworks provide?</p><hr />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Driver management.</li>\\n\"\r\n            \"<li>Advanced methods. Eg. \"\r\n            \"<pre>self.assert_no_broken_links()</pre></li>\\n\"\r\n            \"<li>Test assertions. Eg. \"\r\n            \"<pre>self.assert_text(TEXT, SELECTOR)</pre></li>\\n\"\r\n            \"<li>Command-line options. Eg. \"\r\n            \"<pre>pytest --browser=edge --html=report.html</pre></li>\\n\"\r\n            \"<li>Advanced tools (Eg. test recorders)</li>\\n\"\r\n            \"<li>Easy to read error messages. Eg. \"\r\n            '<pre>Element \"h2\" was not visible after 10s!</pre></li>'\r\n            \"</ul>\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What about test runners?</p><hr />\\n\"\r\n            \"<p>Python includes powerful test runners, such as <b>pytest</b>.\"\r\n            \"</p>\\n\",\r\n            image=\"https://seleniumbase.io/other/invoke_pytest.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What can <b><code>pytest</code></b> do?</p><hr />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Auto-collect tests to run.</li>\\n\"\r\n            \"<li>Use markers for organizing tests.</li>\\n\"\r\n            \"<li>Generate test reports.</li>\\n\"\r\n            \"<li>Provide test assertions.</li>\\n\"\r\n            \"<li>Multithread your tests.</li>\\n\"\r\n            \"<li>Use a large number of existing plugins.</li>\\n\"\r\n            \"</ul>\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>What about complete frameworks?</p><hr />\\n\"\r\n            \"<p><b><code>SeleniumBase</code></b> combines the best of both \"\r\n            \"<b><code>Selenium</code></b> and <b><code>pytest</code></b> \"\r\n            \"into a super framework.</p>\\n\",\r\n            image=\"https://seleniumbase.io/cdn/img/sb_logo_10c.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>SeleniumBase features. <b>(You already saw this!)</b>\"\r\n            \"</p><hr />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Driver management.</li>\\n\"\r\n            \"<li>Advanced methods. Eg. \"\r\n            \"<pre>self.assert_no_broken_links()</pre></li>\\n\"\r\n            \"<li>Test assertions. Eg. \"\r\n            \"<pre>self.assert_text(TEXT, SELECTOR)</pre></li>\\n\"\r\n            \"<li>Command-line options. Eg. \"\r\n            \"<pre>pytest --browser=edge --html=report.html</pre></li>\\n\"\r\n            \"<li>Advanced tools (Eg. test recorders)</li>\\n\"\r\n            \"<li>Easy to read error messages. Eg. \"\r\n            '<pre>Element \"h2\" was not visible after 10s!</pre></li>'\r\n            \"</ul>\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>How do you get SeleniumBase?</p>\\n\"\r\n            \"<hr /><br />\\n\"\r\n            \"<h3><code><mark>pip install seleniumbase</mark></code></h3>\",\r\n            image=\"https://seleniumbase.io/other/seleniumbase_github.png\",\r\n        )\r\n        code = (\r\n            \"from seleniumbase import BaseCase\\n\"\r\n            \"BaseCase.main(__name__, __file__)\\n\\n\"\r\n            \"class MyTestClass(BaseCase):\\n\"\r\n            \"    def test_basics(self):\\n\"\r\n            '        self.open(\"https://www.saucedemo.com\")\\n'\r\n            '        self.type(\"#user-name\", \"standard_user\")\\n'\r\n            '        self.type(\"#password\", \"secret_sauce\\\\n\")\\n'\r\n            '        self.assert_element(\"div.inventory_list\")\\n'\r\n            '        self.assert_exact_text(\"Products\", \"span.title\")\\n'\r\n            \"        self.click('button[name*=\\\"backpack\\\"]')\\n\"\r\n            '        self.click(\"#shopping_cart_container a\")\\n'\r\n            '        self.assert_exact_text(\"Your Cart\", \"span.title\")\\n'\r\n            '        self.assert_text(\"Backpack\", \"div.cart_item\")\\n'\r\n            '        self.click(\"button#checkout\")\\n'\r\n            '        self.type(\"#first-name\", \"SeleniumBase\")\\n'\r\n            '        self.type(\"#last-name\", \"Automation\")\\n'\r\n            '        self.type(\"#postal-code\", \"77123\")\\n'\r\n            '        self.click(\"input#continue\")\\n'\r\n            '        self.assert_text(\"Checkout: Overview\")'\r\n        )\r\n        self.add_slide(\r\n            \"SeleniumBase example test<hr />\",\r\n            code=code,\r\n        )\r\n        self.add_slide(\r\n            \"SeleniumBase example test <mark>(Time to run this!)</mark><hr />\",\r\n            code=code,\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n\r\n        self.get_new_driver(browser=\"edge\")\r\n        self.maximize_window()\r\n        self.open(\"data:,\")\r\n        self.open(\"https://www.saucedemo.com\")\r\n        self.type(\"#user-name\", \"standard_user\")\r\n        self.type(\"#password\", \"secret_sauce\\n\")\r\n        self.assert_element(\"div.inventory_list\")\r\n        self.assert_exact_text(\"Products\", \"span.title\")\r\n        self.click('button[name*=\"backpack\"]')\r\n        self.click(\"#shopping_cart_container a\")\r\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\r\n        self.assert_text(\"Backpack\", \"div.cart_item\")\r\n        self.click(\"button#checkout\")\r\n        self.type(\"#first-name\", \"SeleniumBase\")\r\n        self.type(\"#last-name\", \"Automation\")\r\n        self.type(\"#postal-code\", \"77123\")\r\n        self.click(\"input#continue\")\r\n        self.assert_text(\"Checkout: Overview\")\r\n        self.quit_extra_driver()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            '<mark>(Now to run that same test in \"--demo\" mode!)</mark><hr />',\r\n            code=code,\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n        self.get_new_driver(browser=\"edge\")\r\n        self.maximize_window()\r\n        self.open(\"data:,\")\r\n        self.demo_mode = True\r\n        self.open(\"https://www.saucedemo.com\")\r\n        self.type(\"#user-name\", \"standard_user\")\r\n        self.type(\"#password\", \"secret_sauce\\n\")\r\n        self.assert_element(\"div.inventory_list\")\r\n        self.assert_exact_text(\"Products\", \"span.title\")\r\n        self.click('button[name*=\"backpack\"]')\r\n        self.click(\"#shopping_cart_container a\")\r\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\r\n        self.assert_text(\"Backpack\", \"div.cart_item\")\r\n        self.click(\"button#checkout\")\r\n        self.type(\"#first-name\", \"SeleniumBase\")\r\n        self.type(\"#last-name\", \"Automation\")\r\n        self.type(\"#postal-code\", \"77123\")\r\n        self.click(\"input#continue\")\r\n        self.assert_text(\"Checkout: Overview\")\r\n        self.demo_mode = False\r\n        self.quit_extra_driver()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h3>What about Edge tests using a mobile emulator?</h3><hr />\"\r\n            \"<br /><h3><code><mark>pytest --edge --mobile</mark></code></h3>\"\r\n            \"<br /><h3>Another demo...</h3>\",\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n        self.get_new_driver(browser=\"edge\", is_mobile=True)\r\n        self.maximize_window()\r\n        self.open(\"https://www.roblox.com/\")\r\n        self.assert_element(\"#download-the-app-container\")\r\n        self.assert_text(\"Roblox for Android\")\r\n        self.assert_text(\"Continue in App\", \"a.content-action-emphasis\")\r\n        self.highlight('span:contains(\"Roblox for Android\")', loops=8)\r\n        self.highlight(\"a.content-action-emphasis\", loops=8)\r\n        self.quit_extra_driver()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h3>What about 2-Factor Auth?</h3><hr /><br />\\n\"\r\n            \"<br /><h3>Another demo...</h3>\",\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n        self.get_new_driver(browser=\"edge\")\r\n        self.maximize_window()\r\n        self.demo_mode = True\r\n        self.open(\"https://seleniumbase.io/realworld/login\")\r\n        self.type(\"#username\", \"demo_user\")\r\n        self.type(\"#password\", \"secret_pass\")\r\n        self.enter_mfa_code(\"#totpcode\", \"GAXG2MTEOR3DMMDG\")  # 6-digit\r\n        self.assert_text(\"Welcome!\", \"h1\")\r\n        self.highlight(\"img#image1\")  # A fancier assert_element() call\r\n        self.click('a:contains(\"This Page\")')  # Use :contains() on any tag\r\n        self.save_screenshot_to_logs()  # (\"./latest_logs\" folder for test)\r\n        self.click_link(\"Sign out\")  # Link must be \"a\" tag. Not \"button\".\r\n        self.assert_element('a:contains(\"Sign in\")')\r\n        self.assert_exact_text(\"You have been signed out!\", \"#top_message\")\r\n        self.demo_mode = False\r\n        self.quit_extra_driver()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h2>Need some coffee?<h2><hr /><br />\\n\"\r\n            \"<h2>Another demo...</h2>\",\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n        self.get_new_driver(browser=\"edge\")\r\n        self.maximize_window()\r\n        self.demo_mode = True\r\n        self.open(\"https://seleniumbase.io/coffee/\")\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.click('div[data-sb=\"Cappuccino\"]')\r\n        self.click('div[data-sb=\"Flat-White\"]')\r\n        self.click('div[data-sb=\"Cafe-Latte\"]')\r\n        self.click('a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $53.00\", \"button.pay\")\r\n        self.click(\"button.pay\")\r\n        self.type(\"input#name\", \"Selenium Coffee\")\r\n        self.type(\"input#email\", \"test@test.test\")\r\n        self.click(\"button#submit-payment\")\r\n        self.assert_text(\"Thanks for your purchase.\", \"#app .success\")\r\n        self.demo_mode = False\r\n        self.quit_extra_driver()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h3>Let's have some fun!</h3><hr /><br />\\n\"\r\n            \"<br /><h3>Another demo...</h3>\",\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n        self.sleep(0.25)\r\n        self.get_new_driver(browser=\"edge\")\r\n        self.maximize_window()\r\n        self.open(\"https://seleniumbase.io/error_page/\")\r\n        self.highlight('img[alt=\"500 Error\"]')\r\n        self.highlight(\"img#parallax_octocat\")\r\n        self.highlight(\"#parallax_error_text\")\r\n        self.highlight('img[alt*=\"404\"]')\r\n        self.highlight(\"img#octobi_wan_catnobi\")\r\n        self.highlight(\"img#speeder\")\r\n        self.quit_extra_driver()\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h2>Let's learn more...</h2><hr /><br />\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Common SeleniumBase methods:</h3><hr />\",\r\n            code=(\r\n                \"self.open(url)  # Navigate the browser window to the URL.\\n\"\r\n                \"self.type(selector, text)  # Update field with the text.\\n\"\r\n                \"self.click(selector)  # Click element with the selector.\\n\"\r\n                \"self.click_link(link_text)  # Click link containing text.\\n\"\r\n                \"self.check_if_unchecked(selector)  # Check checkbox.\\n\"\r\n                \"self.uncheck_if_checked(selector)  # Uncheck checkbox.\\n\"\r\n                \"self.select_option_by_text(dropdown_selector, option)\\n\"\r\n                \"self.hover_and_click(hover_selector, click_selector)\\n\"\r\n                \"self.drag_and_drop(drag_selector, drop_selector)\\n\"\r\n                \"self.choose_file(selector, file_path)  # Upload a file.\\n\"\r\n                \"self.switch_to_frame(frame)  # Switch into the iframe.\\n\"\r\n                \"self.switch_to_default_content()  # Exit all iframes.\\n\"\r\n                \"self.switch_to_parent_frame()  # Exit current iframe.\\n\"\r\n                \"self.open_new_window()  # Open new window in same browser.\\n\"\r\n                \"self.switch_to_window(window)  # Switch to browser window.\\n\"\r\n                \"self.switch_to_default_window()  # Go to original window.\\n\"\r\n                \"self.assert_element(selector)  # Verify element visible.\\n\"\r\n                \"self.assert_text(text, selector)  # Substring assertion.\\n\"\r\n                \"self.assert_exact_text(text, selector)  # String assert.\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Common command-line options:</h3><hr />\"\r\n            \"<pre>\\n\"\r\n            '<span class=\"kwd\">--browser=BROWSER</span>'\r\n            '<span class=\"str\">'\r\n            '  (Choose web browser. Default: \"chrome\".)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--edge / --firefox / --safari</span>'\r\n            '<span class=\"str\">'\r\n            '  (Browser Shortcut.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--headless</span>'\r\n            '<span class=\"str\">'\r\n            '  (Run tests headlessly.  Default on Linux OS.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--demo</span>'\r\n            '<span class=\"str\">'\r\n            '  (Slow down and see test actions as they occur.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--slow</span>'\r\n            '<span class=\"str\">'\r\n            '  (Slow down the automation. Faster than Demo Mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--rs / --reuse-session</span>'\r\n            '<span class=\"str\">'\r\n            '  (Reuse browser session for tests.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--rcs / --reuse-class-session</span>'\r\n            '<span class=\"str\">'\r\n            '  (RS, but for class tests.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--crumbs</span>'\r\n            '<span class=\"str\">'\r\n            '  (Clear cookies between tests reusing a session.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--maximize</span>'\r\n            '<span class=\"str\">'\r\n            '  (Start tests with the web browser maximized.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--dashboard</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable the SB Dashboard at dashboard.html)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--uc</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable undetected-chromedriver mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--incognito</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable Incognito mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--guest</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable Guest mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-m=MARKER</span>'\r\n            '<span class=\"str\">'\r\n            '  (Run tests with the specified pytest marker.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-n=NUM</span>'\r\n            '<span class=\"str\">'\r\n            '  (Multithread the tests using that many threads.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-v</span>'\r\n            '<span class=\"str\">'\r\n            '  (Verbose mode. Print the full names of each test run.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--html=report.html</span>'\r\n            '<span class=\"str\">'\r\n            '  (Create a detailed pytest-html report.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--co / --collect-only</span>'\r\n            '<span class=\"str\">'\r\n            '  (Only show discovered tests. No run.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--co -q</span>'\r\n            '<span class=\"str\">'\r\n            '  (Only show full names of discovered tests. No run.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-x</span>'\r\n            '<span class=\"str\">'\r\n            '  (Stop running tests after the first failure is reached.)'\r\n            '</span>\\n'\r\n            \"</pre>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Common console scripts:</h3><hr />\",\r\n            code=(\r\n                \"sbase get [DRIVER] [OPTIONS]  # Eg. chromedriver\\n\"\r\n                \"sbase methods  # List common Python methods\\n\"\r\n                \"sbase options  # List common pytest options\\n\"\r\n                \"sbase gui  # Open the SB GUI for pytest\\n\"\r\n                \"sbase caseplans  # Open the SB Case Plans App\\n\"\r\n                \"sbase mkdir [DIRECTORY]  # Create a test directory\\n\"\r\n                \"sbase mkfile [FILE.py]  # Create a test file\\n\"\r\n                \"sbase codegen [FILE.py] [OPTIONS]  # Record a test\\n\"\r\n                \"sbase recorder  # Open the SB Recorder App\\n\"\r\n                \"sbase mkpres  # Create a Presentation boilerplate\\n\"\r\n                \"sbase mkchart  # Create a Chart boilerplate\\n\"\r\n                \"sbase print [FILE]  # Print file to console\\n\"\r\n                \"sbase translate [FILE.py] [OPTIONS]  # Translate\\n\"\r\n                \"sbase extract-objects [SB_FILE.py]  # Get objects\\n\"\r\n                \"sbase inject-objects [SB_FILE.py]  # Swap selectors\\n\"\r\n                \"sbase objectify [SB_FILE.py]  # Get & swap objects\\n\"\r\n                \"sbase revert-objects [SB_FILE.py]  # Undo objectify\\n\"\r\n                \"sbase download server  # Get Selenium Grid JAR file\\n\"\r\n                \"sbase grid-hub [start|stop] [OPTIONS]  # Start Grid\\n\"\r\n                \"sbase grid-node [start|stop] --hub=[IP]  # Add Node\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h3><b>Live Demo Time!</b></h3><hr />\"\r\n            \"<h3>(Let's head over to GitHub...)</h3>\",\r\n            image=\"https://seleniumbase.io/other/sbase_qr_code_s.png\",\r\n        )\r\n        self.begin_presentation(filename=\"edge_presentation.html\")\r\n"
  },
  {
    "path": "examples/presenter/fundamentals.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass MyTestClass(BaseCase):\r\n    def test_presentation(self):\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h2>Python Selenium:</h2>\\n\"\r\n            \"<h3>Fundamentals to Frameworks</h3>\"\r\n            \"<h3>(with SeleniumBase)</h3>\\n\"\r\n            \"<br /><hr /><br />\\n\"\r\n            \"<h3>Presented by <b>Michael Mintz</b></h3>\\n\"\r\n            \"<h3>SeleniumConf 2023 - Chicago</h3>\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3><b>Welcome to Chicago!</b></h3><hr />\\n\",\r\n            image=\"https://seleniumbase.io/other/chicago_skyline.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3><b>A few shout-outs before starting:</b></h3><hr /><br />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li><b>The Selenium Org</b> (made everything possible)</li><br />\"\r\n            \"<li><b>Conference organizers</b> (made today possible)</li><br />\"\r\n            \"<li><b>My wife</b> (a major supporter of my work)</li><br />\"\r\n            \"<li><b>iboss</b> (my employer)</li>\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>About me:</b></p>\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>I created the <b>SeleniumBase</b> framework.</li>\\n\"\r\n            \"<li>I lead the Automation Team at <b>iboss</b>.</li>\\n\"\r\n            \"</ul>\",\r\n            image=\"https://seleniumbase.io/other/iboss_booth.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>\"\r\n            \"This is the ONLY Python session at SeleniumConf!\"\r\n            \"</b></p><hr /><br />\\n\"\r\n            \"<h3>\\n\"\r\n            \"If one Python session is not enough, come see me afterwards.\\n\"\r\n            \"</h3>\\n\",\r\n            image=\"https://seleniumbase.io/other/python_3d_logo.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4><b>By the end of this presentation, you'll learn:</b></h4>\"\r\n            \"<hr /><br />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Python Selenium fundamentals.</li><br />\\n\"\r\n            \"<li>How test frameworks can improve on the fundamentals.</li>\"\r\n            \"<br />\\n\"\r\n            \"<li>How SeleniumBase makes Python Web Automation easier.</li>\"\r\n            \"<br />\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>The Format:</h3>\"\r\n            \"<hr />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Slides.</li>\\n\"\r\n            \"<li>ReadMe files.</li>\\n\"\r\n            \"<li>LOTS of live demos!!!</li>\\n\"\r\n            \"</ul>\",\r\n            image=\"https://seleniumbase.io/other/presentation_parts.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>What does Selenium provide?</h3>\\n\"\r\n            \"<hr /><br />\\n\"\r\n            \"<h4><b>Selenium provides an automation library!</b></h4>\\n\"\r\n            \"<br /><h5>(It does NOT provide a test framework.)</h5>\\n\",\r\n            image=\"https://seleniumbase.io/other/selenium_slogan.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>How do you get Selenium?</h3>\\n\"\r\n            \"<hr />\\n\"\r\n            \"<p>(for Python)</p><br />\\n\"\r\n            \"<h3><code><mark>pip install selenium</mark></code></h3>\",\r\n            image=\"https://seleniumbase.io/other/selenium_pypi.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>What are some building blocks?</h3>\\n\"\r\n            \"<hr /><br />\\n\",\r\n            code=(\r\n                \"<mk-0>from selenium import webdriver</mk-0>\\n\\n\"\r\n                \"<mk-1>driver = webdriver.Edge()</mk-1>\\n\\n\"\r\n                '<mk-2>driver.get(\"http://selenium.dev\")</mk-2>\\n\\n'\r\n                \"<mk-3>element = driver.find_element\"\r\n                '(\"css selector\", \"#docsearch span\")\\n\\n'\r\n                \"element.click()</mk-3>\\n\\n\"\r\n                \"<mk-4>elem_2 = driver.find_element\"\r\n                '(\"css selector\", \"#docsearch-input\")\\n\\n'\r\n                'elem_2.send_keys(\"Python\")</mk-4>\\n\\n'\r\n                \"<mk-5>driver.quit()</mk-5>\\n\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Launching a browser can get more complicated:</h4>\\n\"\r\n            \"<hr /><br />\\n\",\r\n            code=(\r\n                \"from selenium import webdriver\\n\\n\"\r\n                \"options = webdriver.ChromeOptions()\\n\"\r\n                'options.add_argument(\"--disable-notifications\")\\n'\r\n                \"options.add_experimental_option(\\n    \"\r\n                '\"excludeSwitches\", [\"enable-automation\", \"enable-logging\"]\\n'\r\n                ')\\n'\r\n                \"prefs = {\\n\"\r\n                '    \"credentials_enable_service\": False,\\n'\r\n                '    \"profile.password_manager_enabled\": False,\\n'\r\n                \"}\\n\"\r\n                'options.add_experimental_option(\"prefs\", prefs)\\n'\r\n                \"driver = webdriver.Chrome(options=options)\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Test frameworks wrap Selenium to improve things!</h4><hr />\\n\"\r\n            \"<br />\"\r\n            '<a href=\"https://selenium.dev/documentation/overview/components/'\r\n            '#where-frameworks-fit-in\">'\r\n            '(Where does a framework fit in?)</a>\\n',\r\n            image=\"https://seleniumbase.io/other/with_a_framework.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without additional libraries or frameworks?</h4><hr />\"\r\n            \"<h4>\\n\"\r\n            \"<br />\",\r\n            image=\"https://seleniumbase.io/other/sel_and_py_2.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without additional libraries or frameworks?</h4><hr />\"\r\n            \"<h4>\\n\"\r\n            \"<mark>The default timeout is 0: If an element isn't immediately \"\r\n            \"ready to be interacted with, you'll get errors when trying \"\r\n            \"to interact with those elements.</mark>\\n\"\r\n            \"</h4>\\n\",\r\n            image=\"https://seleniumbase.io/other/messy_stacktrace.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without extra libraries or frameworks?</h4><hr />\"\r\n            \"<h4><br />\\n\"\r\n            \"The command statements can get a bit too long:</h4>\\n\"\r\n            \"<p><code><mk-0>\"\r\n            \"driver.find_element(By.CSS_SELECTOR, CSS_SELECTOR).click()\"\r\n            \"</code></mk-0></p><br />\"\r\n            \"<h4>This is better:</h4>\"\r\n            \"<p><code><mk-1>self.click(CSS_SELECTOR)</mk-1></code><p><br />\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4>What are some disadvantages of using <b>raw</b> Selenium \"\r\n            \"without additional libraries or frameworks?</h4><hr />\"\r\n            \"<h4><mark><br />\\n\"\r\n            \"No HTML reports, dashboards, results, screenshots...\"\r\n            \"</mark><br /><br />A test framework can provide those!<br />\",\r\n        )\r\n        self.add_slide(\r\n            \"<h5>Raw Selenium disadvantages, continued...<hr />\\n\"\r\n            \"No HTML reports, dashboards, results, screenshots...<div />\"\r\n            \"<mark>A test framework can provide those!</mark></h5>\",\r\n            image=\"https://seleniumbase.io/cdn/img/dash_report.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Raw Selenium disadvantages, continued...</h4><hr />\\n\"\r\n            \"<br />\\n\"\r\n            \"<p><mark>It takes multiple lines of code to do simple tasks:\"\r\n            \"</mark></p>\\n<pre>\\n\"\r\n            'element = driver.find_element(\"css selector\", \"#password\")\\n'\r\n            \"element.clear()\\n\"\r\n            'element.send_keys(\"secret_sauce\")\\n'\r\n            'element.submit()\\n'\r\n            \"</pre>\\n<br />\\n\"\r\n            \"<p>But with a framework, do all that in ONE line:</p>\\n\"\r\n            '<pre>self.type(\"#password\", \"secret_sauce\\\\n\")</pre>'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Raw Selenium disadvantages, continued...</h4><hr />\\n\"\r\n            \"<br />\\n\"\r\n            \"<p>It takes multiple lines of code to do simple tasks:</p>\\n\"\r\n            \"<pre>\\n\"\r\n            'element = driver.find_element(\"css selector\", \"#password\")\\n'\r\n            \"element.clear()\\n\"\r\n            'element.send_keys(\"secret_sauce\")\\n'\r\n            'element.submit()\\n'\r\n            \"</pre>\\n<br />\\n\"\r\n            \"<p><mark>But with a framework, do all that in ONE line:\"\r\n            \"</mark></p>\\n\"\r\n            '<pre>self.type(\"#password\", \"secret_sauce\\\\n\")</pre>'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>What else can test frameworks provide?</h4><hr />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Driver management.</li>\\n\"\r\n            \"<li>Advanced methods. Eg. \"\r\n            \"<pre>self.assert_no_broken_links()</pre></li>\\n\"\r\n            \"<li>Test assertions. Eg. \"\r\n            \"<pre>self.assert_text(TEXT, SELECTOR)</pre></li>\\n\"\r\n            \"<li>Command-line options. Eg. \"\r\n            \"<pre>pytest --browser=edge --html=report.html</pre></li>\\n\"\r\n            \"<li>Advanced tools (Eg. test recorders)</li>\\n\"\r\n            \"<li>Easy to read error messages. Eg. \"\r\n            '<pre>Element \"h2\" was not visible after 10s!</pre></li>'\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>What about test runners?</h3><hr />\\n\"\r\n            \"<h3>Python includes powerful test runners, such as <b>pytest</b>.\"\r\n            \"</h3>\\n\",\r\n            image=\"https://seleniumbase.io/other/invoke_pytest.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>What can <b><code>pytest</code></b> do?</h3><hr />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Auto-collect tests to run.</li>\\n\"\r\n            \"<li>Use markers for organizing tests.</li>\\n\"\r\n            \"<li>Generate test reports.</li>\\n\"\r\n            \"<li>Provide test assertions.</li>\\n\"\r\n            \"<li>Multithread your tests.</li>\\n\"\r\n            \"<li>Use a large number of existing plugins.</li>\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4>What about complete frameworks?</h4><hr />\\n\"\r\n            \"<h4><b><code>SeleniumBase</code></b> combines the best of both \"\r\n            \"<b><code>Selenium</code></b> and <b><code>pytest</code></b> \"\r\n            \"into a super framework.</h4>\\n\",\r\n            image=\"https://seleniumbase.io/cdn/img/sb_logo_10c.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4>SeleniumBase features. <b>(You already saw this slide!)</b>\"\r\n            \"</h4><hr />\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>Driver management.</li>\\n\"\r\n            \"<li>Advanced methods. Eg. \"\r\n            \"<pre>self.assert_no_broken_links()</pre></li>\\n\"\r\n            \"<li>Test assertions. Eg. \"\r\n            \"<pre>self.assert_text(TEXT, SELECTOR)</pre></li>\\n\"\r\n            \"<li>Command-line options. Eg. \"\r\n            \"<pre>pytest --browser=edge --html=report.html</pre></li>\\n\"\r\n            \"<li>Advanced tools (Eg. test recorders)</li>\\n\"\r\n            \"<li>Easy to read error messages. Eg. \"\r\n            '<pre>Element \"h2\" was not visible after 10s!</pre></li>'\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>How do you get SeleniumBase?</h3>\\n\"\r\n            \"<hr /><br />\\n\"\r\n            \"<h3><code><mark>pip install seleniumbase</mark></code></h3>\",\r\n            image=\"https://seleniumbase.io/other/seleniumbase_github.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>SeleniumBase example test:</h3><hr />\",\r\n            code=(\r\n                \"from seleniumbase import BaseCase\\n\"\r\n                \"BaseCase.main(__name__, __file__)\\n\\n\"\r\n                \"class MyTestClass(BaseCase):\\n\"\r\n                \"    def test_basics(self):\\n\"\r\n                '        self.open(\"https://www.saucedemo.com\")\\n'\r\n                '        self.type(\"#user-name\", \"standard_user\")\\n'\r\n                '        self.type(\"#password\", \"secret_sauce\\\\n\")\\n'\r\n                '        self.assert_element(\"div.inventory_list\")\\n'\r\n                '        self.assert_exact_text(\"Products\", \"span.title\")\\n'\r\n                \"        self.click('button[name*=\\\"backpack\\\"]')\\n\"\r\n                '        self.click(\"#shopping_cart_container a\")\\n'\r\n                '        self.assert_exact_text(\"Your Cart\", \"span.title\")\\n'\r\n                '        self.assert_text(\"Backpack\", \"div.cart_item\")\\n'\r\n                '        self.click(\"button#checkout\")\\n'\r\n                '        self.type(\"#first-name\", \"SeleniumBase\")\\n'\r\n                '        self.type(\"#last-name\", \"Automation\")\\n'\r\n                '        self.type(\"#postal-code\", \"77123\")\\n'\r\n                '        self.click(\"input#continue\")\\n'\r\n                '        self.assert_text(\"Checkout: Overview\")'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Common SeleniumBase methods:</h3><hr />\",\r\n            code=(\r\n                \"self.open(url)  # Navigate the browser window to the URL.\\n\"\r\n                \"self.type(selector, text)  # Update field with the text.\\n\"\r\n                \"self.click(selector)  # Click element with the selector.\\n\"\r\n                \"self.click_link(link_text)  # Click link containing text.\\n\"\r\n                \"self.check_if_unchecked(selector)  # Check checkbox.\\n\"\r\n                \"self.uncheck_if_checked(selector)  # Uncheck checkbox.\\n\"\r\n                \"self.select_option_by_text(dropdown_selector, option)\\n\"\r\n                \"self.hover_and_click(hover_selector, click_selector)\\n\"\r\n                \"self.drag_and_drop(drag_selector, drop_selector)\\n\"\r\n                \"self.choose_file(selector, file_path)  # Upload a file.\\n\"\r\n                \"self.switch_to_frame(frame)  # Switch into the iframe.\\n\"\r\n                \"self.switch_to_default_content()  # Exit all iframes.\\n\"\r\n                \"self.switch_to_parent_frame()  # Exit current iframe.\\n\"\r\n                \"self.open_new_window()  # Open new window in same browser.\\n\"\r\n                \"self.switch_to_window(window)  # Switch to browser window.\\n\"\r\n                \"self.switch_to_default_window()  # Go to original window.\\n\"\r\n                \"self.assert_element(selector)  # Verify element visible.\\n\"\r\n                \"self.assert_text(text, selector)  # Substring assertion.\\n\"\r\n                \"self.assert_exact_text(text, selector)  # String assert.\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Common command-line options:</h3><hr />\"\r\n            \"<pre>\\n\"\r\n            '<span class=\"kwd\">--browser=BROWSER</span>'\r\n            '<span class=\"str\">'\r\n            '  (Choose web browser. Default: \"chrome\".)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--edge / --firefox / --safari</span>'\r\n            '<span class=\"str\">'\r\n            '  (Browser Shortcut.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--headless</span>'\r\n            '<span class=\"str\">'\r\n            '  (Run tests headlessly.  Default on Linux OS.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--demo</span>'\r\n            '<span class=\"str\">'\r\n            '  (Slow down and see test actions as they occur.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--slow</span>'\r\n            '<span class=\"str\">'\r\n            '  (Slow down the automation. Faster than Demo Mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--reuse-session / --rs</span>'\r\n            '<span class=\"str\">'\r\n            '  (Reuse browser session for tests.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--reuse-class-session / --rcs</span>'\r\n            '<span class=\"str\">'\r\n            '  (RS, but for class tests.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--crumbs</span>'\r\n            '<span class=\"str\">'\r\n            '  (Clear cookies between tests reusing a session.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--maximize</span>'\r\n            '<span class=\"str\">'\r\n            '  (Start tests with the web browser maximized.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--dashboard</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable the SB Dashboard at dashboard.html)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--uc</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable undetected-chromedriver mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--incognito</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable Incognito mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--guest</span>'\r\n            '<span class=\"str\">'\r\n            '  (Enable Guest mode.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-m=MARKER</span>'\r\n            '<span class=\"str\">'\r\n            '  (Run tests with the specified pytest marker.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-n=NUM</span>'\r\n            '<span class=\"str\">'\r\n            '  (Multithread the tests using that many threads.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-v</span>'\r\n            '<span class=\"str\">'\r\n            '  (Verbose mode. Print the full names of each test run.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--html=report.html</span>'\r\n            '<span class=\"str\">'\r\n            '  (Create a detailed pytest-html report.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--collect-only / --co</span>'\r\n            '<span class=\"str\">'\r\n            '  (Only show discovered tests. No run.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">--co -q</span>'\r\n            '<span class=\"str\">'\r\n            '  (Only show full names of discovered tests. No run.)'\r\n            '</span>\\n'\r\n            '<span class=\"kwd\">-x</span>'\r\n            '<span class=\"str\">'\r\n            '  (Stop running tests after the first failure is reached.)'\r\n            '</span>\\n'\r\n            \"</pre>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Common console scripts:</h3><hr />\",\r\n            code=(\r\n                \"sbase get [DRIVER] [OPTIONS]  # Eg. chromedriver\\n\"\r\n                \"sbase methods  # List common Python methods\\n\"\r\n                \"sbase options  # List common pytest options\\n\"\r\n                \"sbase gui  # Open the SB GUI for pytest\\n\"\r\n                \"sbase caseplans  # Open the SB Case Plans App\\n\"\r\n                \"sbase mkdir [DIRECTORY]  # Create a test directory\\n\"\r\n                \"sbase mkfile [FILE.py]  # Create a test file\\n\"\r\n                \"sbase codegen [FILE.py] [OPTIONS]  # Record a test\\n\"\r\n                \"sbase recorder  # Open the SB Recorder App\\n\"\r\n                \"sbase mkpres  # Create a Presentation boilerplate\\n\"\r\n                \"sbase mkchart  # Create a Chart boilerplate\\n\"\r\n                \"sbase print [FILE]  # Print file to console\\n\"\r\n                \"sbase translate [FILE.py] [OPTIONS]  # Translate\\n\"\r\n                \"sbase extract-objects [SB_FILE.py]  # Get objects\\n\"\r\n                \"sbase inject-objects [SB_FILE.py]  # Swap selectors\\n\"\r\n                \"sbase objectify [SB_FILE.py]  # Get & swap objects\\n\"\r\n                \"sbase revert-objects [SB_FILE.py]  # Undo objectify\\n\"\r\n                \"sbase download server  # Get Selenium Grid JAR file\\n\"\r\n                \"sbase grid-hub [start|stop] [OPTIONS]  # Start Grid\\n\"\r\n                \"sbase grid-node [start|stop] --hub=[IP]  # Add Node\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h2><b>Live Demo Time!</b></h2><hr /><br />\"\r\n            \"<h3>(Starting with raw Selenium, and evolving that into \"\r\n            \"SeleniumBase.)</h3>\",\r\n            image=\"https://seleniumbase.io/other/python_3d_logo.png\",\r\n        )\r\n        self.begin_presentation(filename=\"fundamentals.html\")\r\n"
  },
  {
    "path": "examples/presenter/hacking_with_cdp.py",
    "content": "# https://www.youtube.com/watch?v=vt2zsdiNh3U\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass UCPresentationClass(BaseCase):\r\n    def test_hacking_with_cdp(self):\r\n        self.open(\"data:,\")\r\n        self.set_window_position(4, 40)\r\n        self._output_file_saves = False\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\"<h2>Press SPACE to begin!</h2>\\n\")\r\n        self.add_slide(\r\n            \"<p><h3><mk-0>Coming up on the Hacker Show...</mk-0></h3></p>\\n\"\r\n            \"<hr /><ul>\\n\"\r\n            '<img src=\"https://seleniumbase.io/other/hackers_at_comp.jpg\"'\r\n            ' width=\"100%\">'\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up on the Hacker Show...</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li><mk-0>Intercepting requests/responses/XHR with CDP.\"\r\n            \"</mk-0></li><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up on the Hacker Show...</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Intercepting requests/responses/XHR with CDP.\"\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-0>Modifying requests: CDP.Fetch.continueRequest.\"\r\n            \"</mk-0></li><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up on the Hacker Show...</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Intercepting requests/responses/XHR with CDP.\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Modifying requests: CDP.Fetch.continueRequest.\"\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-0>Controlling browsers via remote-debugging-port\"\r\n            \"</mk-0></li><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up on the Hacker Show...</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Intercepting requests/responses/XHR with CDP.\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Modifying requests: CDP.Fetch.continueRequest.\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Controlling browsers via remote-debugging-port\"\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-0>Bypassing CAPTCHAs & anti-bot defenses.\"\r\n            \"</mk-0></li><br />\\n\"\r\n            \"<br /><br />\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up on the Hacker Show...</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Intercepting requests/responses/XHR with CDP.\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Modifying requests: CDP.Fetch.continueRequest.\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Controlling browsers via remote-debugging-port\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Bypassing CAPTCHAs & anti-bot defenses.\"\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-0>And live demos of all the above... with Python!\"\r\n            \"</mk-0></li><br />\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<h2>Get ready for some<br />serious hacking!</h2>\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/hacking_with_cdp.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/cdp.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/ms_edp.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/vid4_on_yt.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/cdp_in_sb.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/hacker_news.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/sb_star_history_3.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/top_trending_month.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/cdp_con_req.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/mycdp_con_req.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/sb_con_req.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/xhr_info.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<h3>The <code>remote-debugging-port</code></h3>'\r\n            '<img src=\"https://seleniumbase.io/other/rd_port.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Let's get to the fun part...</h3>\"\r\n            '<img src=\"https://seleniumbase.io/other/hackers_at_comp.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n"
  },
  {
    "path": "examples/presenter/multi_uc.py",
    "content": "\"\"\"Part of the UC presentation\"\"\"\r\nimport pytest\r\nfrom random import randint\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__, \"--uc\", \"-n3\")\r\n\r\n\r\n@pytest.mark.parametrize(\"\", [[]] * 3)\r\ndef test_multi_threaded(sb):\r\n    url = \"https://gitlab.com/users/sign_in\"\r\n    sb.driver.uc_open_with_reconnect(url, 3)\r\n    sb.set_window_rect(randint(0, 755), randint(38, 403), 700, 500)\r\n    try:\r\n        sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n        sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n        sb._print(\"\\n Success! Website did not detect Selenium! \")\r\n    except Exception:\r\n        sb.driver.uc_open_with_reconnect(url, 3)\r\n        try:\r\n            sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n            sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n            sb._print(\"\\n Success! Website did not detect Selenium! \")\r\n        except Exception:\r\n            sb.fail('Selenium was detected! Try using: \"pytest --uc\"')\r\n"
  },
  {
    "path": "examples/presenter/my_presentation.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyPresenterClass(BaseCase):\n    def test_presenter(self):\n        self.create_presentation(theme=\"serif\", transition=\"none\")\n        self.add_slide(\n            \"<h1>Welcome</h1><br />\\n\" \"<h3>Press the <b>Right Arrow</b></h3>\"\n        )\n        self.add_slide(\n            \"<h3>SeleniumBase Presenter</h3><br />\\n\"\n            '<img width=\"240\" '\n            'src=\"https://seleniumbase.github.io/img/logo3a.png\" />'\n            '<span style=\"margin:144px;\" />'\n            '<img '\n            'src=\"https://seleniumbase.github.io/other/python_3d_logo.png\" />'\n            \"<br /><br />\\n<h4>Create presentations with <b>Python</b></h4>\"\n        )\n        self.add_slide(\n            \"<h3>Make slides using <b>HTML</b>:</h3><br />\\n\"\n            '<table style=\"padding:10px;border:4px solid black;font-size:50;\">'\n            '\\n<tr style=\"background-color:CDFFFF;\">\\n'\n            \"<th>Row ABC</th><th>Row XYZ</th></tr>\\n\"\n            '<tr style=\"background-color:DCFDDC;\">'\n            \"<td>Value ONE</td><td>Value TWO</td></tr>\\n\"\n            '<tr style=\"background-color:DFDFFB;\">\\n'\n            \"<td>Value THREE</td><td>Value FOUR</td></tr>\\n\"\n            \"</table><br />\\n<h4>(HTML <b>table</b> example)</h4>\"\n        )\n        self.add_slide(\n            \"<h3>Keyboard Shortcuts:</h3>\\n\"\n            '<table style=\"padding:10px;border:4px solid black;font-size:30;'\n            'background-color:FFFFDD;\">\\n'\n            \"<tr><th>Key</th><th>Action</th></tr>\\n\"\n            \"<tr><td><b>=></b></td><td>Next Slide (N also works)</td></tr>\\n\"\n            \"<tr><td><b><=</b></td><td>Previous Slide (P also works)</td></tr>\"\n            \"\\n<tr><td>F</td><td>Full Screen Mode</td></tr>\\n\"\n            \"<tr><td>O</td><td>Overview Mode Toggle</td></tr>\\n\"\n            \"<tr><td>esc</td><td>Exit Full Screen / Overview Mode</td></tr>\\n\"\n            \"<tr><td><b>.</b></td><td>Pause/Resume Toggle</td></tr>\\n\"\n            \"<tr><td>space</td><td>Next Slide (alternative)</td></tr></table>\"\n        )\n        self.add_slide(\n            \"<h3>Add <b>images</b> to slides:</h3>\",\n            image=\"https://seleniumbase.github.io/other/seagulls.jpg\",\n        )\n        self.add_slide(\n            \"<h3>Add <b>code</b> to slides:</h3>\",\n            code=(\n                \"from seleniumbase import BaseCase\\n\"\n                \"BaseCase.main(__name__, __file__)\\n\\n\"\n                \"class MyTestClass(BaseCase):\\n\"\n                \"    def test_basics(self):\\n\"\n                '        self.open(\"https://xkcd.com/353/\")\\n'\n                '        self.assert_title(\"xkcd: Python\")\\n'\n                \"        self.assert_element('img[alt=\\\"Python\\\"]')\\n\"\n                \"        self.click('a[rel=\\\"license\\\"]')\\n\"\n                '        self.assert_text(\"free to copy and reuse\")\\n'\n                \"        self.go_back()\\n\"\n                '        self.click_link(\"About\")\\n'\n                '        self.assert_exact_text(\"xkcd.com\", \"h2\")'\n            ),\n        )\n        self.add_slide(\n            \"<h3>Highlight <b>code</b> in slides:</h3>\",\n            code=(\n                \"from seleniumbase import BaseCase\\n\"\n                \"BaseCase.main(__name__, __file__)\\n\\n\"\n                \"<mark>class MyTestClass(BaseCase):</mark>\\n\"\n                \"    def test_basics(self):\\n\"\n                '        self.open(\"https://xkcd.com/353/\")\\n'\n                '        self.assert_title(\"xkcd: Python\")\\n'\n                \"        self.assert_element('img[alt=\\\"Python\\\"]')\\n\"\n                \"        self.click('a[rel=\\\"license\\\"]')\\n\"\n                '        self.assert_text(\"free to copy and reuse\")\\n'\n                \"        self.go_back()\\n\"\n                '        self.click_link(\"About\")\\n'\n                '        self.assert_exact_text(\"xkcd.com\", \"h2\")'\n            ),\n        )\n        self.add_slide(\n            \"<h3>Add <b>iFrames</b> to slides:</h3>\",\n            iframe=\"https://seleniumbase.io/demo_page\",\n        )\n        self.add_slide(\n            \"<h3>Getting started is <b>easy</b>:</h3>\",\n            code=(\n                \"from seleniumbase import BaseCase\\n\"\n                \"BaseCase.main(__name__, __file__)\\n\\n\"\n                \"class MyPresenterClass(BaseCase):\\n\"\n                \"    def test_presenter(self):\\n\"\n                '        self.create_presentation(theme=\"serif\")\\n'\n                '        self.add_slide(\"Welcome to Presenter!\")\\n'\n                \"        self.add_slide(\\n\"\n                '            \"Add code to slides:\",\\n'\n                \"            code=(\\n\"\n                '                \"from seleniumbase import BaseCase\\\\n\\\\n\"\\n'\n                '                \"class MyPresenterClass(BaseCase):\\\\n\\\\n\"\\n'\n                '                \"    def test_presenter(self):\\\\n\"\\n'\n                '                \"        self.create_presentation()\\\\n\"))\\n'\n                \"        self.begin_presentation(\\n\"\n                '            filename=\"demo.html\", show_notes=True)'\n            ),\n        )\n        self.add_slide(\n            \"<h3>Include <b>notes</b> with slides:</h3><br />\",\n            code=(\n                'self.add_slide(\"[Your HTML goes here]\",\\n'\n                '               code=\"[Your software code goes here]\",\\n'\n                '               content2=\"[Additional HTML goes here]\",\\n'\n                '               notes=\"[Attached speaker notes go here]\"\\n'\n                '                     \"[Note A! -- Note B! -- Note C! ]\")'\n            ),\n            notes=\"<h2><ul><li>Note A!<li>Note B!<li>Note C!<li>Note D!</h2>\",\n            content2=\"<h4>(Notes can include HTML tags)</h4>\",\n        )\n        self.add_slide(\n            \"<h3>Multiple <b>themes</b> available:</h3>\",\n            code=(\n                'self.create_presentation(theme=\"serif\")\\n\\n'\n                'self.create_presentation(theme=\"sky\")\\n\\n'\n                'self.create_presentation(theme=\"simple\")\\n\\n'\n                'self.create_presentation(theme=\"white\")\\n\\n'\n                'self.create_presentation(theme=\"moon\")\\n\\n'\n                'self.create_presentation(theme=\"black\")\\n\\n'\n                'self.create_presentation(theme=\"night\")\\n\\n'\n                'self.create_presentation(theme=\"beige\")\\n\\n'\n                'self.create_presentation(theme=\"league\")'\n            ),\n        )\n        self.add_slide(\n            \"<h2><b>The End</b></h2>\",\n            image=\"https://seleniumbase.github.io/img/sb_logo_10.png\",\n        )\n        self.begin_presentation(\n            filename=\"presenter.html\", show_notes=True, interval=0\n        )\n"
  },
  {
    "path": "examples/presenter/py_virtual_envs.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass PythonVirtualEnvs(BaseCase):\n    def test_py_virtual_envs(self):\n        self.create_presentation(theme=\"serif\", transition=\"slide\")\n        self.add_slide(\n            \"<h2>Python Virtual Environments:</h2><br />\\n\"\n            \"<h2>What, Why, and How</h2><hr /><br />\\n\"\n            \"<h3>Presented by <b>Michael Mintz</b></h3>\\n\"\n            \"<p>Granite State Code Camp - Sat, Nov 14, 2020</p>\"\n        )\n        self.add_slide(\n            \"<p><b>About me:</b></p>\\n\"\n            \"<ul>\"\n            \"<li>I created the <b>SeleniumBase</b> framework.</li>\"\n            \"<li>I'm currently the DevOps Lead at <b>iboss</b>.</li>\"\n            \"</ul>\\n\",\n            image=\"https://seleniumbase.io/other/iboss_booth.png\",\n        )\n        self.add_slide(\n            \"<p><b>Topics & tools covered by this presentation:</b></p>\"\n            \"<hr /><br />\\n\"\n            \"<ul>\"\n            \"<li>Overview of Virtual Environments</li>\"\n            \"<li>Python package management</li>\"\n            '<li>Python 3 \"venv\"</li>'\n            \"<li>virtualenv / virtualenvwrapper</li>\"\n            '<li>pip / \"pip install\"</li>'\n            \"<li>requirements.txt files</li>\"\n            \"<li>setup.py files</li>\"\n            \"</ul>\"\n        )\n        self.add_slide(\n            \"<p><b>Topics & tools that are NOT covered here:</b></p><hr />\\n\"\n            \"<br /><div><ul>\"\n            '<li>\"conda\"</li>'\n            '<li>\"pipenv\"</li>'\n            '<li>\"poetry\"</li>'\n            '<li>\"pipx\"</li>'\n            \"</ul></div><br />\"\n            \"<p>(Other Python package management tools)</p>\"\n        )\n        self.add_slide(\n            \"<p><b>What is a Python virtual environment?</b></p><hr /><br />\\n\"\n            \"<p>A Python virtual environment is a partitioned directory\"\n            \" where a Python interpreter, libraries/packages, and scripts\"\n            \" can be installed and isolated from those installed in other\"\n            \" virtual environments or the global environment.</p>\"\n        )\n        self.add_slide(\n            \"<p><b>Why should we use Python virtual environments?</b>\"\n            \"</p><hr /><br />\\n\"\n            \"<p>We should use Python virtual environments because different\"\n            \" Python projects can have conflicting Python dependencies that\"\n            \" cannot coexist in the same env.</p>\"\n        )\n        self.add_slide(\n            \"<p><b>Why? - continued</b></p><hr /><br />\\n\"\n            \"<p>Example: Project A and Project B both depend on\"\n            \" different versions of the same Python library!</p>\"\n            \"<p>Therefore, installing the second project requirements\"\n            \" would overwrite the first one, causing it to break.</p>\",\n            code=(\n                \"# Project A requirement:\\n\"\n                \"urllib3==1.25.3\\n\\n\"\n                \"# Project B requirement:\\n\"\n                \"urllib3==1.26.2\"\n            ),\n        )\n        self.add_slide(\n            \"<p><b>Why? - continued</b></p><hr /><br />\\n\"\n            \"<p>It is also possible that Project A and Project B\"\n            \" require different versions of Python installed!</p>\",\n            code=(\n                \"# Project A requirement:\\n\"\n                \"Python-3.8\\n\\n\"\n                \"# Project B requirement:\\n\"\n                \"Python-2.7\"\n            ),\n        )\n        self.add_slide(\n            \"<p><b>How do we create and use Python virtual envs?</b>\"\n            \"</p><hr /><br />\\n\"\n            \"<div><b>There are tools/scripts we can use:</b></div><br />\"\n            \"<ul>\"\n            '<li>The Python 3 \"venv\" command</li>'\n            \"<li>virtualenv / virtualenvwrapper</li>\"\n            \"</ul>\"\n        )\n        self.add_slide(\n            '<p><b>Python 3 \"venv\"</b></p><hr /><br />\\n'\n            '\"venv\" creates virtual environments in the location where run'\n            \" (generally with Python projects).\",\n            code=(\n                \"# Mac / Linux\\n\"\n                \"python3 -m venv ENV_NAME\\n\"\n                \"source ENV_NAME/bin/activate\\n\\n\"\n                \"# Windows\\n\"\n                \"py -m venv ENV_NAME\\n\"\n                \"call ENV_NAME\\\\Scripts\\\\activate\\n\\n\"\n                '# (Type \"deactivate\" to leave a virtual environment.)'\n            ),\n        )\n        self.add_slide(\n            '<p><b>\"mkvirtualenv\" (from virtualenvwrapper)</b></p><hr />\\n'\n            '<br />\"mkvirtualenv\" creates virtual environments in one place'\n            \" (generally in your home directory).\",\n            code=(\n                \"# Mac / Linux\\n\"\n                \"python3 -m pip install virtualenvwrapper\\n\"\n                \"export WORKON_HOME=$HOME/.virtualenvs\\n\"\n                \"source `which virtualenvwrapper.sh`\\n\"\n                \"mkvirtualenv ENV_NAME\\n\\n\"\n                \"# Windows\\n\"\n                \"py -m pip install virtualenvwrapper-win\\n\"\n                \"mkvirtualenv ENV_NAME\\n\\n\"\n                '# (Type \"deactivate\" to leave a virtual environment.)'\n            ),\n        )\n        self.add_slide(\n            \"<p><b>List of commands from virtualenvwrapper</b></p>\"\n            \"<hr /><br />\",\n            code=(\n                \"# Create a virtual environment:\\n\"\n                \"mkvirtualenv ENV_NAME\\n\\n\"\n                \"# Exit your virtual environment:\\n\"\n                \"deactivate\\n\\n\"\n                \"# Re-enter a virtual environment:\\n\"\n                \"workon ENV_NAME\\n\\n\"\n                \"# List all virtual environments:\\n\"\n                \"workon\\n\\n\"\n                \"# Delete a virtual environment:\\n\"\n                \"rmvirtualenv ENV_NAME\"\n            ),\n        )\n        self.add_slide(\n            \"<p><b>Determining if you are in a virtual env</b></p>\"\n            \"<hr /><br />\"\n            \"<p>When activated, the name of your virtual env\"\n            \" will appear in parentheses on the left side of your\"\n            \" command prompt.</p>\",\n            code=(\n                \"# Example of how it may look on a Windows machine:\\n\"\n                \"C:\\\\Users\\\\Michael\\\\github> mkvirtualenv my_env\\n\"\n                \"(my_env)  C:\\\\Users\\\\Michael\\\\github>\"\n            ),\n        )\n        self.add_slide(\n            '<p><b>Installing packages with \"pip install\"</b></p><hr /><br />'\n            \"<p>Once you have created a Python virtual environment and are\"\n            \" inside, it is now safe to install packages from PyPI,\"\n            \" setup.py files, and/or requirements.txt files.</p>\\n\",\n            code=(\n                \"# Install a package from PyPI:\\n\"\n                \"pip install seleniumbase\\n\\n\"\n                \"# Install packages from a folder with setup.py:\\n\"\n                \"pip install .  # Normal installation\\n\"\n                \"pip install -e .  # Editable install\\n\\n\"\n                \"# Install packages from a requirements.txt file:\\n\"\n                \"pip install -r requirements.txt\\n\"\n            ),\n        )\n        self.add_slide(\n            '<p><b>Other useful \"pip\" commands</b></p><hr /><br />',\n            code=(\n                \"# See which Python packages are installed:\\n\"\n                \"pip list\\n\\n\"\n                \"# See which installed Python packages are outdated:\\n\"\n                \"pip list --outdated\\n\\n\"\n                \"# Create a requirements file from installed packages:\\n\"\n                \"pip freeze > my_requirements.txt\"\n            ),\n        )\n        self.add_slide(\n            \"<br /><br /><h2><b>Live Demo Time!</b></h2><hr /><br />\",\n            image=\"https://seleniumbase.io/other/python_3d_logo.png\",\n        )\n        self.add_slide(\n            \"<h2><b>The End. Questions?</b></h2><hr /><br />\\n\"\n            \"<h3>Where to find me:</h3>\"\n            \"<ul>\"\n            '<li><a href=\"https://github.com/mdmintz\">'\n            \"github.com/mdmintz</a></li>\"\n            '<li><a href=\"https://github.com/seleniumbase/SeleniumBase\">'\n            \"github.com/seleniumbase/SeleniumBase</a></li>\"\n            '<li><a href=\"https://seleniumbase.io/\">'\n            \"seleniumbase.io</a></li>\"\n            \"</ul>\"\n        )\n        self.begin_presentation(\n            filename=\"py_virtual_envs.html\", show_notes=False, interval=0\n        )\n"
  },
  {
    "path": "examples/presenter/uc_presentation.py",
    "content": "# https://www.youtube.com/watch?v=5dMFI3e85ig\r\nimport os\r\nimport subprocess\r\nfrom contextlib import suppress\r\nfrom seleniumbase import BaseCase\r\nfrom seleniumbase import SB\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass UCPresentationClass(BaseCase):\r\n    def test_presentation(self):\r\n        self.open(\"data:,\")\r\n        self._output_file_saves = False\r\n        self.create_presentation(theme=\"beige\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<p>A deep dive into <b>undetectable automation</b>, with:</p>\"\r\n            \"<h5><code>SeleniumBase UC Mode\"\r\n            \" and undetected-chromedriver</code></h5>\",\r\n            image=\"https://seleniumbase.io/cdn/img/uc_mode_phases_3.png\",\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/cdn/img/uc_mode_phases_3.png\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>🔹 <b>The Objective</b> 🔹</p><hr /><br />\"\r\n            \"<p><mk-0>By the end of this presentation, you'll learn how to\"\r\n            \" create bots that appear as humans to websites.</mk-0></p><br />\"\r\n            \"<p><mk-1>(These bots won't get detected or blocked.)</mk-1></p>\"\r\n            \"<br /><p><mk-2>Here's a live demo of that...</mk-2></p>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        self.get_new_driver(undetectable=True)\r\n        url = \"https://gitlab.com/users/sign_in\"\r\n        try:\r\n            self.uc_open_with_reconnect(url, reconnect_time=3)\r\n            try:\r\n                self.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n                self.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n            except Exception:\r\n                self.uc_open_with_reconnect(url, reconnect_time=4)\r\n                self.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n                self.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n        finally:\r\n            self.quit_extra_driver()\r\n\r\n        if os.path.exists(\"multi_uc.py\"):\r\n            self.create_presentation(theme=\"beige\", transition=\"fade\")\r\n            self.add_slide(\r\n                \"<p>🔹 <b>There's a lot more to come!</b> 🔹</p><hr /><br />\"\r\n                \"<p><mk-0>If one bot isn't enough, how about several?</mk-0>\"\r\n                \"</p><br /><br />\"\r\n                \"<p><mk-1>Here's a demo of multithreaded bots in parallel...\"\r\n                \"</mk-1></p>\"\r\n            )\r\n            self.begin_presentation(filename=\"uc_presentation.html\")\r\n            subprocess.Popen(\"pytest multi_uc.py --uc -n3\", shell=True).wait()\r\n            self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n            self.add_slide(\r\n                \"<p>Not just an army of bots, but an army of bots<br />\"\r\n                \"that look just like humans using web browsers.</p><br />\"\r\n                \"<p>(That's how they weren't detected!)</p>\"\r\n            )\r\n            self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"If this is what you came here for, stick around<br />to\"\r\n            \" learn how to do the things you just saw.<br />\"\r\n            \"<p>You may find it easier to build a Selenium<br />\"\r\n            \"bot than to navigate an obstacle course.</p>\",\r\n            image=\"https://seleniumbase.io/other/yeah_you_passed.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>But first, you may be wondering who I am...</p>\"\r\n            \"<p>Or maybe you're one of over one million people<br />\"\r\n            \"that I've already helped on Stack Overview:</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/me_st_overflow.jpg\" '\r\n            'width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>About the presenter (Michael Mintz):</b></p>\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>I created <b>SeleniumBase</b> (for Python).</li>\\n\"\r\n            \"<li>I lead the Automation Team at <b>iboss</b>.</li>\\n\"\r\n            \"</ul>\\n\",\r\n            image=\"https://seleniumbase.io/other/iboss_booth.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>I've been doing video podcasts since 2012!</p>\"\r\n            \"<p>(That's when I first co-hosted the<br />Marketing Update\"\r\n            \" on HubSpot TV)</p>\",\r\n            image=\"https://seleniumbase.io/other/hub_tv.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>I spoke at Selenium Conference 2023:</p>\"\r\n            \"<p>(As the dedicated Python Selenium speaker)</p>\",\r\n            image=\"https://seleniumbase.io/other/me_se_conf.jpg\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>Here's me with the creators<br />\"\r\n            \"of <b>Selenium</b> / <b>WebDriver</b>:</p>\",\r\n            image=\"https://seleniumbase.io/other/selenium_builders.jpg\",\r\n        )\r\n        self.add_slide(\r\n            \"<b>SeleniumBase</b> Fun Fact:\"\r\n            \"<div />The 1st SB GitHub issue was from a Tesla engineer:\",\r\n            image=\"https://seleniumbase.io/other/first_issue.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>Now, let me explain how we got here...</p>\"\r\n            \"<p>And by here, I mean a time when lots of companies\"\r\n            \" have been building services to detect and block bots:</p>\",\r\n            image=\"https://seleniumbase.io/other/verify_human.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>In the early days, there were few bots,<br />\"\r\n            \"and those bots didn't look human at all.</p><br />\"\r\n            \"<p>Those early bots were mostly innocent, and<br />\"\r\n            \"most websites didn't care if they were around.</p>\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>At some point, the number of bots grew by a lot...</p><br />\"\r\n            \"<p>Many bots were programmed with bad intentions...</p><br />\"\r\n            \"<p>And sometimes you couldn't tell apart the good bots\"\r\n            \" from the bad ones until it was already too late...</p>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>Then came Google <b>reCAPTCHA v1</b>...</p>\"\r\n            \"<p>Although intended as a defense against bots,<br />\"\r\n            \" humans on a web browser also got hit by it.</p>\",\r\n            image=\"https://seleniumbase.io/other/recaptcha_v1.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>Then Google made improvements:</p>\"\r\n            \"<p>(<b>reCAPTCHA v2</b> / <b>reCAPTCHA v3</b>)</p>\",\r\n            image=\"https://seleniumbase.io/other/recaptcha_v2a.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>And that annoyed a lot of people...</p>\",\r\n            image=\"https://seleniumbase.io/other/recaptcha_v2b.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>Then came the next iteration of anti-bot security:</p>\"\r\n            \"<p>Cloudflare <b>Turnstile</b> CAPTCHA-replacement.\"\r\n            \"<p>That changed the game in many ways.</p>\",\r\n            image=\"https://seleniumbase.io/other/check_if_secure.png\",\r\n        )\r\n        self.add_slide(\r\n            \"For awhile, Cloudflare's Turnstile did a decent<br />\"\r\n            \"job filtering out bot traffic from human traffic.\"\r\n            \"<p>Then <code>undetected-chromedriver</code> arrived:</p>\",\r\n            image=\"https://seleniumbase.io/other/undetected_ch.png\",\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/undetected_ch.png\" '\r\n            'width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><code>undetected-chromedriver</code> was found to be<br />\"\r\n            \"incredibly effective at getting past the Turnstile:</p>\",\r\n            image=\"https://seleniumbase.io/other/connection_secure.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p>The maintainer of <code>undetected-chromedriver</code>:</p>\"\r\n            \"<hr /><p>He appears to be very busy with various projects.</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/the_uc_starter.png\" '\r\n            'width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>The biggest challenge for undetected-chromedriver\"\r\n            \" has been adapting to breaking changes caused by:</p><div />\"\r\n            \"<ul>\\n\"\r\n            \"<li>New versions of Chrome.</li>\\n\"\r\n            \"<li>New versions of Selenium.</li>\\n\"\r\n            \"<li>New versions of Cloudflare.</li>\\n\"\r\n            \"</ul><br /><hr />\\n\"\r\n            \"<p>Those can brake things until updates are released:</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/uc_open_issues.png\" '\r\n            'width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>Thankfully, <code>undetected-chromedriver</code> has<br />\"\r\n            \"supporters helping to figure out and fix things.<p>\"\r\n            '<img src=\"https://seleniumbase.io/other/uc_helping_out.png\" '\r\n            'width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Sometimes all that help isn't enough...</h4>\"\r\n            \"<p>That's where <code>seleniumbase</code> UC Mode comes in:</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/sb_github.png\" '\r\n            'width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>SeleniumBase <b>UC Mode</b> is a modified fork of<br />\"\r\n            \"undetected-chromedriver with multiple changes.</h4>\"\r\n            \"<h4>UC Mode includes bug fixes and additional features, such as\"\r\n            \" multithreading support via <code>pytest-xdist</code>.</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/super_server.jpg\" '\r\n            'width=\"60%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4><mk-0><b>UC Mode</b> is one of many SeleniumBase modes:\"\r\n            \"</mk-0></h4>\\n<ul>\\n\"\r\n            \"<li><mk-1>UC Mode</mk-1> (<code>--uc / uc=True</code>)</li>\\n\"\r\n            \"<li><mk-2>Slow Mode</mk-2> (<code>--slow</code>)</li>\\n\"\r\n            \"<li><mk-3>Demo Mode</mk-3> (<code>--demo</code>)</li>\\n\"\r\n            '<li><mk-4>Proxy Mode</mk-4>'\r\n            ' (<code>--proxy=\"h:p\"/\"u:p@h:p\"</code>)</li>\\n'\r\n            \"<li><mk-5>Debug Mode</mk-5> \"\r\n            \"(<code>--pdb/--trace/--ftrace</code>)</li>\\n\"\r\n            \"<li><mk-6>Mobile Mode</mk-6> \"\r\n            \"(<code>--mobile / mobile=True)</code></li>\\n\"\r\n            \"<li><mk-7>Recorder Mode</mk-7> \"\r\n            \"(<code>--rec / --rec-behave</code>)</li>\\n\"\r\n            \"<li><mk-8>Multithreaded Mode</mk-8> \"\r\n            \"(<code>pytest -n4 / -n8</code>)</li>\\n\"\r\n            \"</ul>\\n\"\r\n            \"<p><mk-9>And more! (You can even combine modes!)</mk-9></p>\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0>ℹ️: UC Mode is not enabled by default.<br />\"\r\n            \" It must be activated by switching it on:</mk-0></p>\"\r\n            \"<ul>\\n\"\r\n            \"<li><code><mk-1>--uc</mk-1></code> &nbsp\"\r\n            \" (pytest command-line option)</li>\\n\"\r\n            \"<li><code><mk-2>uc=True</mk-2></code> &nbsp\"\r\n            \" (SB/driver manager formats)</li>\\n\"\r\n            \"</ul><br /><br /><hr /><br />\\n<p>\"\r\n            \"<mk-3>Then websites can no longer detect chromedriver.</mk-3></p>\"\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0>Here's an example script that uses UC Mode:</mk-0></p>\\n\"\r\n            \"<h5><mk-1>(Note that SeleniumBase has <code>driver</code> methods\"\r\n            \"<br />that aren't included with standard Selenium.)</mk-1></h5>\\n\"\r\n            \"<hr /><br />\\n\",\r\n            code=(\r\n                \"<mk-2>from seleniumbase import Driver</mk-2>\\n\\n\"\r\n                \"<mk-3>driver = Driver(uc=True)</mk-3>\\n\"\r\n                \"<mk-4>try:</mk-4>\\n\"\r\n                '    <mk-5>driver.get(\"https://gitlab.com/users/sign_in\")'\r\n                '</mk-5>\\n'\r\n                \"    <mk-1><mk-6>driver.sleep(4)</mk-6></mk-1>\\n\"\r\n                \"    <mk-7># DO MORE STUFF</mk-7>\\n\"\r\n                \"<mk-4>finally:</mk-4>\\n\"\r\n                \"    <mk-4>driver.quit()</mk-4>\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0>For reference, here's a script in a different<br />\"\r\n            \"SeleniumBase format, with more methods:</mk-0></p>\"\r\n            \"<h5><mk-12>(Get ready for another live demo...)</mk-12></h5>\",\r\n            code=(\r\n                \"<mk-1>from seleniumbase import SB</mk-1>\\n\\n\"\r\n                \"<mk-2>with SB(uc=True) as sb:</mk-2>\\n\"\r\n                '    <mk-3>sb.get(\"seleniumbase.io/simple/login\")</mk-3>\\n'\r\n                '    <mk-4>sb.type(\"#username\", \"demo_user\")</mk-4>\\n'\r\n                '    <mk-5>sb.type(\"#password\", \"secret_pass\")</mk-5>\\n'\r\n                '    <mk-6>sb.click(\\'a:contains(\"Sign in\")\\')</mk-6>\\n'\r\n                '    <mk-7>sb.assert_exact_text(\"Welcome!\", \"h1\")</mk-7>\\n'\r\n                '    <mk-8>sb.assert_element(\"img#image1\")</mk-8>\\n'\r\n                '    <mk-9>sb.highlight(\"#image1\")</mk-9>\\n'\r\n                '    <mk-10>sb.click_link(\"Sign out\")</mk-10>\\n'\r\n                '    <mk-11>sb.assert_text(\"signed out\", \"#top_message\")'\r\n                \"</mk-11>\\n\"\r\n            ),\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True) as sb:\r\n                sb.get(\"https://seleniumbase.io/simple/login\")\r\n                sb.type(\"#username\", \"demo_user\")\r\n                sb.type(\"#password\", \"secret_pass\")\r\n                sb.click('a:contains(\"Sign in\")')\r\n                sb.assert_exact_text(\"Welcome!\", \"h1\")\r\n                sb.assert_element(\"img#image1\")\r\n                sb.highlight(\"#image1\")\r\n                sb.click_link(\"Sign out\")\r\n                sb.assert_text(\"signed out\", \"#top_message\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<h3><mk-0>Was that too fast for you?</mk-0></h3>\"\r\n            \"<h4><mk-1>Let's run that again in <b>Demo Mode</b>:</mk-1></h4>\",\r\n            code=(\r\n                \"from seleniumbase import SB\\n\\n\"\r\n                \"with SB(uc=True<mk-1>, demo=True</mk-1>) as sb:\\n\"\r\n                '    sb.get('\r\n                '\"https://seleniumbase.io/simple/login\")\\n'\r\n                '    sb.type(\"#username\", \"demo_user\")\\n'\r\n                '    sb.type(\"#password\", \"secret_pass\")\\n'\r\n                '    sb.click(\\'a:contains(\"Sign in\")\\')\\n'\r\n                '    sb.assert_exact_text(\"Welcome!\", \"h1\")\\n'\r\n                '    sb.assert_element(\"img#image1\")\\n'\r\n                '    sb.highlight(\"#image1\")\\n'\r\n                '    sb.click_link(\"Sign out\")\\n'\r\n                '    sb.assert_text(\"signed out\", \"#top_message\")\\n'\r\n            ),\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True, demo=True) as sb:\r\n                sb.get(\"https://seleniumbase.io/simple/login\")\r\n                sb.type(\"#username\", \"demo_user\")\r\n                sb.type(\"#password\", \"secret_pass\")\r\n                sb.click('a:contains(\"Sign in\")')\r\n                sb.assert_exact_text(\"Welcome!\", \"h1\")\r\n                sb.assert_element(\"img#image1\")\r\n                sb.highlight(\"#image1\")\r\n                sb.click_link(\"Sign out\")\r\n                sb.assert_text(\"signed out\", \"#top_message\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<p><mk-0>Note the differences between undetected-chromedriver and\"\r\n            \" SeleniumBase UC Mode.</mk-0><br /><hr />SeleniumBase UC Mode:\"\r\n            \"</p><ul>\\n\"\r\n            \"<li><mk-1>Has driver version-detection & management.</mk-1>\"\r\n            \"</li>\\n\"\r\n            \"<li><mk-2>Allows mismatched browser/driver versions.</mk-2>\"\r\n            \"</li>\\n\"\r\n            \"<li><mk-3>Changes the user agent to prevent detection.</mk-3>\"\r\n            \"</li>\\n\"\r\n            \"<li><mk-4>Hides chromedriver from Chrome as needed.</mk-4>\"\r\n            \"</li>\\n\"\r\n            \"<li><mk-5>Allows for multithreaded tests in parallel.</mk-5>\"\r\n            \"</li>\\n\"\r\n            \"<li><mk-6>Adjusts configuration based on the environment.</mk-6>\"\r\n            \"</li>\\n\"\r\n            \"<li><mk-7>Has options for proxy and proxy-with-auth.</mk-7>\"\r\n            \"</li>\\n</ul>\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>Here's another UC Mode script:</mk-0></h3>\"\r\n            \"<h4><mk-1>(Get ready for another live demo...)</mk-1></h4>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"from seleniumbase import SB\\n\\n\"\r\n                \"with SB(uc=True) as sb:\\n\"\r\n                '    url = \"https://gitlab.com/users/sign_in\"\\n'\r\n                \"    sb.uc_open_with_reconnect(url, 4)\\n\\n\"\r\n                \"    ...\\n\"\r\n            ),\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True) as sb:\r\n                url = \"https://gitlab.com/users/sign_in\"\r\n                sb.uc_open_with_reconnect(url, 4)\r\n                sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n                sb.assert_element('[for=\"user_login\"]')\r\n                sb.highlight('button:contains(\"Sign in\")')\r\n                sb.highlight('h1:contains(\"GitLab\")')\r\n                sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            \"<p>Now let's learn how UC Mode works in general.</p>\"\r\n            \"<p>First, there are several things that need to happen for\"\r\n            \" browsers to remain undetected from anti-bot services.</p>\\n\",\r\n            image=\"https://seleniumbase.io/other/yeah_you_passed.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<mk-0>Requirements for avoiding detection</mk-0> (UC Mode)<hr />\"\r\n            \"<ul>\\n\"\r\n            \"<li><mk-1>Modify chromedriver to rename driver variables\"\r\n            \" that appear in the Chrome DevTools console.</mk-1></li>\\n\"\r\n            \"<li><mk-2>Launch Chrome before attaching chromedriver.<br />\"\r\n            \"(Don't launch Chrome with chromedriver)</mk-2></li>\\n\"\r\n            \"<li><mk-3>Don't use Selenium-specific Chrome options.</mk-3>\"\r\n            \"</li>\\n<li><mk-4>If using headless Chrome, change\"\r\n            \" HeadlessChrome to Chrome in the User Agent.</mk-4></li>\\n\"\r\n            \"<li><mk-5>If using a custom user_data_dir, don't let that\"\r\n            \" folder be used with non-UC-Mode Chrome.</mk-5></li>\\n\"\r\n            \"<li><mk-6>Disconnect chromedriver briefly from Chrome before\"\r\n            \" loading websites with detection services.</mk-6></li>\\n\"\r\n            \"</ul>\"\r\n        )\r\n        self.add_slide(\r\n            \"Requirements, continued... / <b>Good news:</b><hr />\\n\"\r\n            \"<h3><mk-0>Most of those things are already done automatically\"\r\n            \" when using UC Mode with default settings.</mk-0></h3>\\n\"\r\n            \"<h4><mk-1>The part that's your responsibility, (if setting a\"\r\n            \" custom <code>user_data_dir</code>), is making sure that\"\r\n            \" the u_d_d is only used by UC Mode Chrome instances. If you\"\r\n            ' \"cross the streams\", UC Mode can be detected.</mk-1></h4><div />'\r\n            \"<h4><mk-2>(UC Mode takes care of the other requirements.)</mk-2>\"\r\n            \"</h4>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h4>With those things done, your bot can appear human.</h4>\\n\"\r\n            \"<h5>But if anyone looks too closely at what your bot does,<br />\"\r\n            'it may raise suspicion, even if already marked \"not a bot\".</h5>',\r\n            image=\"https://seleniumbase.io/other/other_anti_bots.jpg\",\r\n        )\r\n        self.add_slide(\r\n            \"<h4><mk-0>There are additional methods that you can use\"\r\n            \" to have a better experience when using UC Mode:</mk-0></h4>\\n\"\r\n            \"<h6><br /><mk-1>Note that <code><b>driver.get(url)</b></code> has\"\r\n            \" been modified from the original to<br />reconnect automatically\"\r\n            \" if a web page is using bot-detection software.</mk-1></h6>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"driver.default_get(url)\\n\\n\"\r\n                \"driver.uc_open(url)\\n\\n\"\r\n                \"driver.uc_open_with_tab(url)\\n\\n\"\r\n                \"driver.uc_open_with_reconnect(url, reconnect_time)\\n\\n\"\r\n                'driver.uc_click(selector, by=\"css selector\", timeout=7)\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h4>There are additional methods that you can use\"\r\n            \" to have a better experience when using UC Mode:</h4>\"\r\n            \"<h6><br /><mark>Since <code><b>driver.get(url)</b></code> has\"\r\n            \" been modified, <code><b>driver.default_get(url)</b></code>\"\r\n            \" exists to do a regular <code><b>get(url)</b></code>,\"\r\n            \" which may be useful if revisiting a website.</mark></h6>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"<mark>driver.default_get(url)</mark>\\n\\n\"\r\n                \"driver.uc_open(url)\\n\\n\"\r\n                \"driver.uc_open_with_tab(url)\\n\\n\"\r\n                \"driver.uc_open_with_reconnect(url, reconnect_time)\\n\\n\"\r\n                'driver.uc_click(selector, by=\"css selector\", timeout=7)\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h4>There are additional methods that you can use\"\r\n            \" to have a better experience when using UC Mode:</h4>\"\r\n            \"<h6><br /><mark><code><b>driver.uc_open(url)</b></code> will\"\r\n            \" open a URL in the same tab with a disconnect.<br />\"\r\n            \"(This might not be enough to bypass detection.)</mark></h6>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"driver.default_get(url)\\n\\n\"\r\n                \"<mark>driver.uc_open(url)</mark>\\n\\n\"\r\n                \"driver.uc_open_with_tab(url)\\n\\n\"\r\n                \"driver.uc_open_with_reconnect(url, reconnect_time)\\n\\n\"\r\n                'driver.uc_click(selector, by=\"css selector\", timeout=7)\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h4>There are additional methods that you can use\"\r\n            \" to have a better experience when using UC Mode:</h4>\\n\"\r\n            \"<h6><br /><mark><code><b>driver.uc_open_with_tab(url)</b></code>\"\r\n            \" opens a URL in a new tab with a disconnect. Similar to the new\"\r\n            \" <code><b>driver.get(url)</b></code>, but without the pre-check.\"\r\n            \"</mark></h6><hr /><br />\",\r\n            code=(\r\n                \"driver.default_get(url)\\n\\n\"\r\n                \"driver.uc_open(url)\\n\\n\"\r\n                \"<mark>driver.uc_open_with_tab(url)</mark>\\n\\n\"\r\n                \"driver.uc_open_with_reconnect(url, reconnect_time)\\n\\n\"\r\n                'driver.uc_click(selector, by=\"css selector\", timeout=7)\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h4>There are additional methods that you can use\"\r\n            \" to have a better experience when using UC Mode:</h4>\\n\"\r\n            \"<h6><br /><code><b>driver.uc_open_with_tab(url)</b></code> opens\"\r\n            \" a URL in a new tab with a disconnect. Similar to the new\"\r\n            \" <code><b>driver.get(url)</b></code>, but without the pre-check.\"\r\n            \"</h6><hr /><br />\\n\"\r\n            \"<h6><mark>As a reminder, the <code><b>driver.get(url)</b></code>\"\r\n            \" pre-check checks to see if a URL has bot-detection software\"\r\n            \" on it before opening the URL in a new tab with a disconnect.\"\r\n            \"</mark><br /></h6><div /><div />\\n\"\r\n            \"<h6><mark>This pre-check is done using\"\r\n            \" <code><b>requests.get(URL)</b></code><br />before opening\"\r\n            \" a URL in the <code>UC Mode</code> web browser.</mark></h6>\"\r\n            \"<h5><mark>If the response code is a\"\r\n            ' <code><b>\"403\"</b></code> (Forbidden),<br />'\r\n            \"then the URL is opened with a <code>disconnect</code>.\"\r\n            \"</mark></h5>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h4><b>Customizing the default disconnect/reconnect time</b></h4>\"\r\n            \"<hr />\\n\"\r\n            \"<p>Here's a method for a custom reconnect time<br />\"\r\n            \"when opening a page that tries to detect bots:</p>\"\r\n            \"<pre><mk-0>driver.uc_open_with_reconnect(url, reconnect_time)\"\r\n            \"</mk-0></pre>\"\r\n            \"<h6>(The default reconnect_time is slightly more than 2 seconds.)\"\r\n            \"</h6><hr /><br />\",\r\n            code=(\r\n                \"# Example:\\n\"\r\n                \"<mk-1>driver.uc_open_with_reconnect(\\n\"\r\n                '    \"https://steamdb.info/login/\", reconnect_time=6\\n)'\r\n                \"</mk-1>\"\r\n                \"\\n\\n\"\r\n                \"# Short form example:\\n\"\r\n                \"<mk-2>driver.uc_open_with_reconnect(\"\r\n                '\"https://steamdb.info/login/\", 6)</mk-2>\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h4><b>Clicking with a disconnect/reconnect</b></h4><hr />\\n\"\r\n            \"<p>If your bot needs to click a button on a website that has\"\r\n            \" anti-bot services, you might be able to do it with this special\"\r\n            \" method, which forces a short disconnect:</p>\\n\"\r\n            \"<pre><mk-0>driver.uc_click(selector)</mk-0></pre>\"\r\n            '<h6>(Defaults: <code>by=\"css selector\", timeout=7</code>)</h6>'\r\n            \"<hr /><br />\\n\",\r\n            code=(\r\n                \"# Examples:\\n\"\r\n                '<mk-1>driver.uc_click(\"button\")\\n\\n'\r\n                'driver.uc_click(\"button#id\", timeout=10)</mk-1>\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h2>Links to UC Mode code</h2><hr /><br /><ul>\\n\"\r\n            '<li><a href=\"https://github.com/seleniumbase/SeleniumBase'\r\n            '/blob/master/examples/verify_undetected.py\" target=\"_blank\">'\r\n            'SeleniumBase/examples/verify_undetected.py</a></li><br />\\n'\r\n            '<li><a href=\"https://github.com/seleniumbase/SeleniumBase'\r\n            '/blob/master/examples/uc_cdp_events.py\" target=\"_blank\">'\r\n            'SeleniumBase/examples/uc_cdp_events.py</a></li><br />\\n'\r\n            '<li><a href=\"https://github.com/seleniumbase/SeleniumBase'\r\n            '/blob/master/examples/raw_uc_mode.py\" target=\"_blank\">'\r\n            'SeleniumBase/examples/raw_uc_mode.py</a></li>'\r\n            \"<br />\\n</ul>\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<h2>Things to keep in mind</h2><hr /><ul>\\n\"\r\n            \"<li>You may need to adjust default settings<br />\"\r\n            \"for your bot to remain undetected.</li><br />\\n\"\r\n            \"<li>Once your bot enters a website,<br />\"\r\n            \"it should continue to act accordingly.</li><br />\\n\"\r\n            \"<li>Improvise if your bot makes any mistakes.</li><br />\\n\"\r\n            \"<li>Your bot should look human to avoid detection.</li></ul>\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<h2>Ethical concerns</h2><hr /><ul>\\n\"\r\n            \"<li>Don't use bots for evil purposes.</li><br />\\n\"\r\n            \"<li>Do use bots with honorable intentions.</li><br />\\n\"\r\n            \"<li>Do use bots for automating tedious manual tasks.</li><br />\\n\"\r\n            \"<li>Do take the time to train & configure your bots.</li></ul>\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<h2>🔹 Final remarks 🔹</h2><div /><hr /><h4><br /><ul>\\n\"\r\n            \"<li>Not all bots are created equal.</li><br />\\n\"\r\n            \"<li>SeleniumBase UC Mode lets bots appear human.</li><br />\\n\"\r\n            \"<li>Visit SeleniumBase on GitHub for more info:\\n\"\r\n            \"https://github.com/seleniumbase/SeleniumBase</li></ul></h4>\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<h3>❓ Questions? ❓</h3>\"\r\n            \"<h5>https://github.com/seleniumbase/SeleniumBase/discussions</h5>\"\r\n            \"<br /><h3>📌 Found a bug? 🐞</h3><h5>\"\r\n            \"https://github.com/seleniumbase/SeleniumBase/issues</h5><hr />\"\r\n            \"<br /><h4>🔰 Perfection takes practice. Keep iterating! 🔰</h4>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h1>The End</h1>\",\r\n            image=\"https://seleniumbase.io/other/sb_github.png\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n"
  },
  {
    "path": "examples/presenter/uc_presentation_3.py",
    "content": "# https://www.youtube.com/watch?v=-EpZlhGWo9k\r\nimport sys\r\nfrom contextlib import suppress\r\nfrom seleniumbase import BaseCase\r\nfrom seleniumbase import SB\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass UCPresentationClass(BaseCase):\r\n    def test_presentation_3(self):\r\n        self.open(\"data:,\")\r\n        self.set_window_position(4, 40)\r\n        self._output_file_saves = False\r\n        self.open(\"https://seleniumbase.io/other/uc3_title.jpg\")\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/uc3_title.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        self.open(\"https://seleniumbase.io/other/uc3_title.jpg\")\r\n        self.sleep(2.5)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/uc3_title.jpg\"'\r\n            ' width=\"100%\">'\r\n            '<br /><h4><b>(with SeleniumBase UC Mode)</b></h4>'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>This is the follow-up to my previous video:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/uc2_title.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/uc_vid1_ss9.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Which was the follow-up to an earlier video:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/uc_automation.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/uc_vid1_ss8.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h5>There, you learned the basics of bypassing CAPTCHAs:</h5>\"\r\n            '<img src=\"https://seleniumbase.io/other/check_if_secure.png\"'\r\n            ' width=\"85%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h2><mk-0>Here's a LIVE DEMO of bypassing a CAPTCHA:</mk-0></h2>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True) as sb:\r\n                url = \"https://gitlab.com/users/sign_in\"\r\n                sb.uc_open_with_reconnect(url, 4)\r\n                sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n                sb.assert_element('[for=\"user_login\"]')\r\n                sb.highlight('button:contains(\"Sign in\")')\r\n                sb.highlight('h1:contains(\"GitLab\")')\r\n                sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p><mk-0><b>The code for the previous live demo:</b></mk-0></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"<mk-1>from seleniumbase import SB</mk-1>\\n\\n\"\r\n                \"<mk-2>with SB(uc=True) as sb:</mk-2>\\n\"\r\n                '<mk-3>    url = \"https://gitlab.com/users/sign_in\"</mk-3>\\n'\r\n                \"<mk-4>    sb.uc_open_with_reconnect(url, 4)</mk-4>\\n\\n\"\r\n                \"<mk-5>    ...</mk-5>\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>The code for the previous live demo:</b></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"from seleniumbase import SB\\n\\n\"\r\n                \"with SB(uc=True) as sb:\\n\"\r\n                '    url = \"https://gitlab.com/users/sign_in\"\\n'\r\n                \"    sb.uc_open_with_reconnect(url, 4)\\n\\n\"\r\n                '<mk-1>    sb.assert_text(\"Username\", \\'[for=\"user_login\"]\\','\r\n                ' timeout=3)</mk-1>\\n'\r\n                '<mk-2>    sb.assert_element(\\'[for=\"user_login\"]\\')</mk-2>\\n'\r\n                '<mk-3>    sb.highlight(\\'button:contains(\"Sign in\")\\')'\r\n                '</mk-3>\\n'\r\n                '<mk-4>    sb.highlight(\\'h1:contains(\"GitLab\")\\')'\r\n                '</mk-4>\\n'\r\n                '<mk-5>    sb.post_message(\"SeleniumBase wasn\\'t detected\",'\r\n                ' duration=4)</mk-5>\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>Even if using UC Mode, you may still need to\"\r\n            \" click the CAPTCHA checkbox in order to bypass it.</mk-0></h3>\"\r\n            \"<br />\"\r\n            \"<h4><mk-1>(That's not a problem because there are special<br />\"\r\n            \"UC Mode methods for handling that situation.)</mk-1>\"\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0>Special <b>UC Mode</b> methods for clicking CAPTCHAs:\"\r\n            \"</mk-0></p><hr /><div></div>\"\r\n            \"<ul><br />\\n\"\r\n            \"<li><mk-1><code><b>sb.uc_gui_handle_captcha()</b></code></mk-1>\"\r\n            \"</li>\\n\"\r\n            \"PyAutoGUI uses the TAB key with SPACEBAR.<br /><br />\\n\\n\"\r\n            \"<li><mk-2><code><b>sb.uc_gui_click_captcha()</b></code></mk-2>\"\r\n            \"</li>\\n\\n\"\r\n            \"PyAutoGUI clicks CAPTCHA with the mouse.<br />\\n\"\r\n            \"(Note that you'll need to use this one on Linux!)\\n\"\r\n            \"</ul>\\n\\n\\n\\n\"\r\n            \"<p></p><br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p><b><mk-0>When is clicking the CAPTCHA checkbox required?\"\r\n            \"</mk-0></b></p><hr /><h5>&nbsp</h5>\"\r\n            \"<h4><li><mk-1>They've seen your IP Address too many times.\"\r\n            \"</mk-1></li><br />\"\r\n            \"<li><mk-2>They don't accept your User-Agent string.</mk-2>\"\r\n            \"<br />(UC Mode gives you a good one by default)</li>\"\r\n            \"<br />\"\r\n            \"<li><mk-3>You're using Linux. (Likely a server)</mk-3></li>\"\r\n            \"<br />\"\r\n            \"<li><mk-4>You're using a VPN. (If detected)</mk-4></li>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>For testing purposes...</mk-0></h3><br />\"\r\n            \"<mk-1>I'll use a bad User-Agent for some Live Demos...</mk-1>\"\r\n            \"<br /><br />\"\r\n            \"<mk-2>This will force me to click the CAPTCHA to bypass it.\"\r\n            \"</mk-2>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>On the topic of live demos,</mk-0></h3>\"\r\n            \"<h3><mk-0>I'll run some of them now:</mk-0></h3>\"\r\n            \"<br /><br />\"\r\n            \"<h4><mk-1>Get ready for a live demo of:</mk-1></h4>\"\r\n            \"<h4><mk-1>Bypassing Cloudflare with TAB + SPACEBAR...</mk-1></h4>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        agent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0.0.0\"\r\n        if \"linux\" in sys.platform or \"win32\" in sys.platform:\r\n            agent = None  # Use the default UserAgent\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True, test=True, agent=agent) as sb:\r\n                url = \"https://gitlab.com/users/sign_in\"\r\n                sb.uc_open_with_reconnect(url, 4)\r\n                sb.uc_gui_handle_captcha()  # Only if needed\r\n                sb.assert_element('label[for=\"user_login\"]')\r\n                sb.set_messenger_theme(location=\"bottom_center\")\r\n                sb.post_message(\"SeleniumBase wasn't detected!\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p><mk-0><b>The code for the previous live demo:</b></mk-0></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"<mk-1>from seleniumbase import SB</mk-1>\\n\\n\"\r\n                \"<mk-2>with SB(uc=True) as sb:</mk-2>\\n\"\r\n                '<mk-3>    url = \"https://gitlab.com/users/sign_in\"</mk-3>\\n'\r\n                \"<mk-4>    sb.uc_open_with_reconnect(url, 4)</mk-4>\\n\"\r\n                \"<mk-5>    sb.uc_gui_handle_captcha()</mk-5>\\n\\n\"\r\n                \"<mk-6>    ...</mk-6>\\n\\n\\n\\n\\n\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>The code for the previous live demo:</b></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"from seleniumbase import SB\\n\\n\"\r\n                \"with SB(uc=True) as sb:\\n\"\r\n                '    url = \"https://gitlab.com/users/sign_in\"\\n'\r\n                \"    sb.uc_open_with_reconnect(url, 4)\\n\"\r\n                \"    sb.uc_gui_handle_captcha()\\n\\n\"\r\n                '<mk-1>    sb.assert_text(\"Username\", \\'[for=\"user_login\"]\\','\r\n                ' timeout=3)</mk-1>\\n'\r\n                '<mk-2>    sb.assert_element(\\'[for=\"user_login\"]\\')</mk-2>\\n'\r\n                '<mk-3>    sb.set_messenger_theme(location=\"bottom_center\")'\r\n                '</mk-3>\\n'\r\n                '<mk-4>    sb.post_message(\"SeleniumBase wasn\\'t detected!\")'\r\n                '</mk-4>'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>Live demos, continued...</mk-0></h3>\"\r\n            \"<br /><br />\"\r\n            \"<h4><mk-1>Get ready for a live demo of:</mk-1></h4>\"\r\n            \"<h4><mk-1>Bypassing Cloudflare with a mouse click...</mk-1></h4>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True, test=True, agent=agent) as sb:\r\n                url = \"https://gitlab.com/users/sign_in\"\r\n                sb.uc_open_with_reconnect(url, 4)\r\n                sb.uc_gui_click_captcha()  # Only if needed\r\n                sb.assert_element('label[for=\"user_login\"]')\r\n                sb.set_messenger_theme(location=\"bottom_center\")\r\n                sb.post_message(\"SeleniumBase wasn't detected!\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p><mk-0><b>The code for the previous live demo:</b></mk-0></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"<mk-1>from seleniumbase import SB</mk-1>\\n\\n\"\r\n                \"<mk-2>with SB(uc=True) as sb:</mk-2>\\n\"\r\n                '<mk-3>    url = \"https://gitlab.com/users/sign_in\"</mk-3>\\n'\r\n                \"<mk-4>    sb.uc_open_with_reconnect(url, 4)</mk-4>\\n\"\r\n                \"<mk-5>    sb.uc_gui_click_captcha()</mk-5>\\n\\n\"\r\n                \"<mk-6>    ...</mk-6>\\n\\n\\n\\n\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>The code for the previous live demo:</b></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"from seleniumbase import SB\\n\\n\"\r\n                \"with SB(uc=True) as sb:\\n\"\r\n                '    url = \"https://gitlab.com/users/sign_in\"\\n'\r\n                \"    sb.uc_open_with_reconnect(url, 4)\\n\"\r\n                \"    sb.uc_gui_click_captcha()\\n\\n\"\r\n                '<mk-1>    sb.assert_text(\"Username\", \\'[for=\"user_login\"]\\','\r\n                ' timeout=3)</mk-1>\\n'\r\n                '<mk-2>    sb.assert_element(\\'[for=\"user_login\"]\\')</mk-2>\\n'\r\n                '<mk-3>    sb.set_messenger_theme(location=\"bottom_center\")'\r\n                '</mk-3>\\n'\r\n                '<mk-4>    sb.post_message(\"SeleniumBase wasn\\'t detected!\")'\r\n                '</mk-4>\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0><b>Quick recap of what you just learned:</b>\"\r\n            \"</mk-0></p><hr /><div></div>\"\r\n            \"<ul>\\n\"\r\n            \"<li><mk-1>Activate UC Mode with <code><b>SB(uc=True)</b></code>\"\r\n            \"</mk-1></li><br />\\n\"\r\n            \"<li><mk-2>Navigate with stealth by calling \"\r\n            \"<code><b>sb.uc_open_with_reconnect(url)</b></code>\"\r\n            \"</mk-2></li><br />\\n\"\r\n            \"<li><mk-3>Use <code><b>sb.uc_gui_handle_captcha()</b></code>\"\r\n            \" or <code><b>sb.uc_gui_click_captcha()</b></code>\"\r\n            \" to bypass CAPTCHAs as needed.</mk-3></li>\\n\"\r\n            \"</ul>\\n\"\r\n            \"<p><br /><mk-4>(It's that easy!)</mk-4></p><br />\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0><b>Things can get more complicated</b></mk-0></p>\"\r\n            \"<hr /><div></div><br />\"\r\n            \"<ul>\\n\"\r\n            \"<li><mk-1>Previous tutorials mentioned this method:<br />\"\r\n            \"<code><b>sb.uc_click(selector)</b></code>\"\r\n            \"</mk-1></li><br />\\n\"\r\n            \"<mk-2>Although this method can no longer click a<br />\"\r\n            \"CAPTCHA directly, it should be used when<br />\"\r\n            \"clicking on something else that causes a<br />\"\r\n            \"CAPTCHA to appear after that.</mk-2>\\n\"\r\n            \"<br /><br />\"\r\n            \"<li><mk-3>Here's a live demo of that...</mk-3></li><br />\\n\"\r\n            \"</ul>\\n\"\r\n            \"<p><br /></p><br />\\n\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True, incognito=True, locale=\"en\") as sb:\r\n                url = \"https://ahrefs.com/website-authority-checker\"\r\n                input_field = 'input[placeholder=\"Enter domain\"]'\r\n                submit_button = 'span:contains(\"Check Authority\")'\r\n                sb.uc_open_with_reconnect(url)  # The bot-check is later\r\n                sb.type(input_field, \"github.com/seleniumbase/SeleniumBase\")\r\n                sb.reconnect(0.1)\r\n                sb.uc_click(submit_button, reconnect_time=4)\r\n                sb.uc_gui_click_captcha()\r\n                sb.wait_for_text_not_visible(\"Checking\", timeout=10)\r\n                sb.highlight('p:contains(\".com/seleniumbase/SeleniumBase\")')\r\n                sb.highlight('a:contains(\"Top 100 backlinks\")')\r\n                sb.set_messenger_theme(location=\"bottom_center\")\r\n                sb.post_message(\"SeleniumBase wasn't detected!\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p><mk-0>The code for the previous live demo can be<br />\"\r\n            \"found in the SeleniumBase GitHub repo:<br /><br />\"\r\n            \"<code>github.com/seleniumbase/SeleniumBase</code></mk-0>\"\r\n            \"<br /><br /><br /><br />\"\r\n            '<mk-1>(See the \"examples\" folder for all examples)</mk-1></p>\\n'\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>Live demos, continued...</mk-0></h3>\"\r\n            \"<br /><br />\"\r\n            \"<h4><mk-1>Get ready for another live demo of\"\r\n            \" using the <code>uc_click(selector)</code> method\"\r\n            \" to bypass a Cloudflare CAPTCHA on\"\r\n            \" <code>steamdb.info</code> ...</mk-1></h4>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True, test=True, disable_csp=True) as sb:\r\n                url = \"https://steamdb.info/\"\r\n                sb.uc_open_with_reconnect(url, 3)\r\n                sb.uc_click(\"a.header-login span\", 3)\r\n                sb.uc_gui_click_captcha()\r\n                sb.assert_text(\"Sign in\", \"button#js-sign-in\", timeout=3)\r\n                sb.uc_click(\"button#js-sign-in\", 2)\r\n                sb.highlight(\"div.page_content form\")\r\n                sb.highlight('button:contains(\"Sign in\")', scroll=False)\r\n                sb.set_messenger_theme(location=\"top_center\")\r\n                sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p><mk-0><b>The code for the previous live demo:</b></mk-0></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"<mk-1>from seleniumbase import SB</mk-1>\\n\\n\"\r\n                \"<mk-2>with SB(uc=True, disable_csp=True) as sb:</mk-2>\\n\"\r\n                '<mk-3>    url = \"https://steamdb.info/\"</mk-3>\\n'\r\n                \"<mk-4>    sb.uc_open_with_reconnect(url, 3)</mk-4>\\n\"\r\n                '<mk-5>    sb.uc_click(\"a.header-login span\", 3)</mk-5>\\n\\n'\r\n                \"<mk-6>    ...</mk-6>\\n\\n\\n\\n\\n\\n\\n\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>The code for the previous live demo:</b></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"from seleniumbase import SB\\n\\n\"\r\n                \"with SB(uc=True) as sb:\\n\"\r\n                '    url = \"https://steamdb.info/\"\\n'\r\n                \"    sb.uc_open_with_reconnect(url, 3)\\n\"\r\n                '    sb.uc_click(\"a.header-login span\", 3)\\n\\n'\r\n                \"<mk-0>    sb.uc_gui_click_captcha()</mk-0>\\n\"\r\n                '<mk-1>    sb.assert_text(\"Sign in\", \"button#js-sign-in\",'\r\n                ' timeout=3)</mk-1>\\n'\r\n                '<mk-2>    sb.uc_click(\"button#js-sign-in\", 2)</mk-2>\\n'\r\n                '<mk-3>    sb.highlight(\"div.page_content form\")</mk-3>\\n'\r\n                '<mk-4>    sb.highlight(\\'button:contains(\"Sign in\")\\','\r\n                ' scroll=False)</mk-4>\\n'\r\n                '<mk-5>    sb.set_messenger_theme(location=\"top_center\")'\r\n                '</mk-5>\\n'\r\n                '<mk-6>    sb.post_message(\"SeleniumBase wasn\\'t detected!\")'\r\n                '</mk-6>\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>Important information</b></mk-0> 👤</p>\"\r\n            \"<hr /><div></div>\"\r\n            \"<ul>\\n\"\r\n            \"<li><mk-1>UC Mode now requires <code>PyAutoGUI</code> for all\"\r\n            \" features to work.</mk-1></li><p></p>\\n\"\r\n            \"<li><mk-2>PyAutoGUI may require enabling admin-level permissions\"\r\n            \" for controlling the mouse and the keyboard.</mk-2></li><p></p>\\n\"\r\n            \"<p></p>\"\r\n            \"<li><mk-3><code>PyAutoGUI</code> doesn't support Headless Mode.\"\r\n            \"</mk-3></li>\\n<p></p>\"\r\n            \"<li><mk-4>UC Mode now includes a special virtual display\"\r\n            \" on Linux so that you no longer need to use Headless Mode\"\r\n            \" in GUI-less environments.</mk-4></li>\\n\"\r\n            \"</ul>\\n\"\r\n            \"<p></p>\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>General information</b></mk-0> 👤</p>\"\r\n            \"<hr /><br />\"\r\n            \"<p><mk-1>Don't assume that all CAPTCHA services\"\r\n            \" are secure, even if they say they are...</mk-1></p>\"\r\n            \"<br /><br /><br /><br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <b>General information</b> 👤</p>\"\r\n            \"<hr /><br />\"\r\n            \"<p>Don't assume that all CAPTCHA services\"\r\n            \" are secure, even if they say they are...</p><br />\"\r\n            \"<p><mk-0>(Looking at you, Cloudflare!)</mk-0></p><br />\"\r\n            \"<div /><p></p>\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>General information</b></mk-0> 👤</p>\"\r\n            \"<hr /><br />\"\r\n            \"<p><mk-1>\"\r\n            \"On the other hand,<br />\"\r\n            \"some CAPTCHA services are quite good...\"\r\n            \"</mk-1></p>\"\r\n            \"<br /><br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True, test=True) as sb:\r\n                url = \"https://seleniumbase.io/apps/recaptcha\"\r\n                sb.uc_open_with_reconnect(url)\r\n                sb.uc_gui_click_captcha()  # Try with PyAutoGUI Click\r\n                sb.assert_element(\"img#captcha-success\", timeout=3)\r\n                sb.set_messenger_theme(location=\"top_left\")\r\n                sb.post_message(\"SeleniumBase wasn't detected\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p>👤 <b>General information</b> 👤</p>\"\r\n            \"<hr /><br />\"\r\n            \"<p>On the other hand,<br />\"\r\n            \"some CAPTCHA services are quite good...\"\r\n            \"</p><br />\"\r\n            \"<p><mk-0>(Well done, Google reCAPTCHA!)</mk-0></p><br />\"\r\n            \"<div /><p></p>\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>General information</b></mk-0> 👤</p>\"\r\n            \"<hr /><br />\"\r\n            \"<p><mk-1>\"\r\n            \"However, the real reason UC Mode is popular,\"\r\n            \" which you saw earlier,\"\r\n            \" is because of the Cloudflare-bypass capabilities.\"\r\n            \" That's where the reputation comes from...\"\r\n            \"</mk-1></p>\"\r\n            \"<br /><br /><br /><br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>Catching up</b></mk-0> 👤</p>\"\r\n            \"<hr /><br />\"\r\n            \"<p><mk-1>\"\r\n            \"If this is your first tutorial on UC Mode or SeleniumBase,\"\r\n            \" then here are some important<br />things to know to\"\r\n            \" understand things better...\"\r\n            \"</mk-1></p>\"\r\n            \"<br /><br /><br /><br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>What is SeleniumBase?</b></mk-0> 👤</p>\"\r\n            \"<hr /><br />\"\r\n            \"<p><mk-1>\"\r\n            \"SeleniumBase is a complete framework for web automation\"\r\n            \" and testing with Python and Selenium.\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Although there are many different features,<br />\"\r\n            \"the most popular one today is UC Mode,<br />\"\r\n            \"which enables Selenium browsers to appear<br />\"\r\n            \"as human-controlled browsers to websites.\"\r\n            \"</mk-2></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>Structuring Scripts / Tests</b></mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"There are different ways of stucturing SeleniumBase scripts.\"\r\n            ' (Internally called: \"The 25 Syntax Formats\")'\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            'Most examples use Syntax Format 1: \"BaseCase direct class'\r\n            ' inheritance\", which uses the \"pytest\" test runner.'\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            'The next one in popularity is Syntax Format 21: \"SeleniumBase SB\"'\r\n            ' (Python context manager)\",<br />which is ideal and recommended'\r\n            \" for UC Mode.\"\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/sb_sf_01.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/sb_sf_21.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<div>📊 <b>The SeleniumBase GitHub Page</b> 📊</div>\"\r\n            '<img src=\"https://seleniumbase.io/other/sb_github.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>About me: (Michael Mintz)</b></p>\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li><mk-0>I created the <b>SeleniumBase</b> framework.\"\r\n            \"</mk-0></li>\\n\"\r\n            \"<li><mk-1>I lead the Automation Team at <b>iboss</b>.\"\r\n            \"</mk-1></li>\\n\"\r\n            \"</ul>\",\r\n            image=\"https://seleniumbase.io/other/iboss_booth.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>About me: (Michael Mintz)</b></p>\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li><mk-0>I've reached over 2 million developers<br />\"\r\n            \" on Stack Overflow.</mk-0></li>\\n\"\r\n            \"</ul>\"\r\n            '<img src=\"https://seleniumbase.io/other/me_st_o_reached.jpg\"'\r\n            ' width=\"88%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>The Great CAPTCHA Duel:</b></mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"Throughout the past few years, Cloudflare has pushed a lot\"\r\n            \" of changes to their Turnstile CAPTCHA.\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"In order to keep UC Mode working, I had to push a lot of\"\r\n            \" updates to counter those changes.\"\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            \"It has been an epic duel...\"\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>The Great CAPTCHA Duel:</b></mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"Sometimes Cloudflare pushed multiple<br />\"\r\n            \"changes at the same time...\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"That's when I had to make multiple<br />\"\r\n            \"UC Mode updates to counter those changes...\"\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            \"Sometimes I received a little assistance from GitHub...\"\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Timeline of major Cloudflare updates</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"<b>March 20, 2024</b>: Cloudflare pushed a major update\"\r\n            \" where they could detect UC Mode Selenium clicks.\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Outcome: UC Mode's <code>uc_click(selector)</code> method\"\r\n            \" was updated to click on CAPTCHAs<br />via JavaScript using\"\r\n            \" <code>window.setTimeout()</code>.\"\r\n            \"</mk-2><br /><br /></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Timeline of major Cloudflare updates</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"<b>May 10, 2024</b>: Cloudflare pushed a major update\"\r\n            \" where CSS Selectors of CAPTCHAs were updated.\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Outcome: I had to update all the UC Mode<br />\"\r\n            'examples to change \"span.mark\" to just \"span\".'\r\n            \"</mk-2><br /><br /></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Timeline of major Cloudflare updates</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"<b>June 7, 2024</b>: Cloudflare pushed an update where CAPTCHAs\"\r\n            \" could detect JavaScript clicks.<br />(This was a major setback!)\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Outcome: I had to add new UC Mode methods for clicking on\"\r\n            \" CAPTCHAs with <code>PyAutoGUI</code>.\"\r\n            \"</mk-2><br /><br /></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Timeline of major Cloudflare updates</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"<b>July 8, 2024</b>: Cloudflare made an update where CAPTCHAs\"\r\n            \" were hidden behind Shadow-DOM.<br />\"\r\n            \"(They went for a killing blow!)\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Outcome: I updated existing UC Mode methods so they could\"\r\n            \" determine the CAPTCHA coordinates<br />for\"\r\n            \" <code>PyAutoGUI</code>.\"\r\n            \" (Same-day delivery, thanks to an advanced warning\"\r\n            \" on Discord a few days earlier.)\"\r\n            \"</mk-2><br /><br /></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Timeline of major Cloudflare updates</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"<b>July 25, 2024</b>: Cloudflare made updates to the<br />\"\r\n            \"CSS Selectors that come before Shadow-DOM.\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Outcome: I updated existing UC Mode methods.\"\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            'Note: You can use \"uc_gui_handle_captcha()\" or<br />'\r\n            '\"uc_gui_click_captcha()\" for any CAPTCHA now.<br />'\r\n            '(On Linux, only \"uc_gui_click_captcha\" works.)'\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Timeline of major Cloudflare updates</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"Only minor changes from Cloudflare<br />\"\r\n            \"have been shipped since then so far...\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Remember: Give me space to work<br />\"\r\n            \"on UC Mode updates as needed...\"\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            \"...because you never know when they'll strike next...\"\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Theories on how Cloudflare detects JS</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"A month before Cloudflare added JS-detection,<br />\"\r\n            \"a GitHub repo named Brotector was released.\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Brotector is capable of detecting both Selenium & JS.\"\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            \"Based on experiements, Brotector's detection mechanisms\"\r\n            \" appear to get the same results as Cloudflare's detection\"\r\n            \" mechanisms.<br />(It appears that Cloudflare learned from them.)\"\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<div><b>Brotector info</b></div>\"\r\n            '<img src=\"https://seleniumbase.io/other/brotector_gh1.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<div><b>Brotector info</b></div>\"\r\n            '<img src=\"https://seleniumbase.io/other/brotector_gh2.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Using Brotector to make UC Mode better</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"In order to make UC Mode better, I decided to build my own\"\r\n            \" open-source CAPTCHA using Brotector:<br />\"\r\n            '\"The Brotector CAPTCHA\"'\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Unlike Cloudflare's detection system, which only scans for\"\r\n            \" bots on page loads and CAPTCHA-clicks, the Brotector CAPTCHA\"\r\n            \" continuously scans for bots.\"\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            \"This makes it a more powerful anti-bot system.\"\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/brotector_c1.png\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/brotector_c2.png\"'\r\n            ' width=\"84%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h2><mk-0>Here's a LIVE DEMO of Brotector CAPTCHA:</mk-0></h2>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(test=True) as sb:\r\n                url = \"https://seleniumbase.io/hobbit/login\"\r\n                sb.open(url)\r\n                sb.click_if_visible(\"button\")\r\n                sb.assert_text(\"Gandalf blocked you!\", \"h1\")\r\n                sb.click(\"img\")\r\n                sb.highlight(\"h1\")\r\n                sb.sleep(3)  # Gandalf: \"You Shall Not Pass!\"\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3><mk-0>Here's a LIVE DEMO of UC Mode bypassing\"\r\n            \" Brotector CAPTCHA:</mk-0></h3>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            with SB(uc=True, test=True) as sb:\r\n                url = \"https://seleniumbase.io/hobbit/login\"\r\n                sb.uc_open_with_disconnect(url, 2.2)\r\n                sb.uc_gui_press_keys(\"\\t \")\r\n                sb.reconnect(1.5)\r\n                sb.assert_text(\"Welcome to Middle Earth!\", \"h1\")\r\n                sb.set_messenger_theme(location=\"bottom_center\")\r\n                sb.post_message(\"SeleniumBase wasn't detected!\")\r\n                sb.click(\"img\")\r\n                sb.sleep(5.888)  # Cool animation happening now!\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>What happens when Cloudflare adds real-time<br />\"\r\n            \"bot-detection, like Brotector already has?</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"Currently, UC Mode uses Selenium to locate the CAPTCHA checkbox\"\r\n            \" before the <code>PyAutoGUI</code> click.<br />(This is fine\"\r\n            \" for now because CF only scans<br />\"\r\n            \"during page loads and CAPTCHA clicks.)\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"There's already a plan in place for the day<br />\"\r\n            \"Cloudflare adds real-time bot-scanning...\"\r\n            \"</mk-2><br /></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>The plan to handle real-time bot-scanning</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p>\"\r\n            '<pre><code>sb.uc_gui_click_captcha(frame=\"iframe\", retry=False,'\r\n            ' <mk-1>blind=True</mk-1>)</code></pre><br /><mk-1>'\r\n            'Set the third arg, `blind`, to `True` to force a retry'\r\n            ' (if the first click failed) by clicking at the last known'\r\n            ' coordinates of the CAPTCHA checkbox without confirming first'\r\n            ' with Selenium that a CAPTCHA is still on the page.'\r\n            ' (The page will need to reload first.)'\r\n            \"</mk-1><br /><br /></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Field trip to the UC Mode help docs</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"Let's take a look at the UC Mode docs<br />\"\r\n            \"from the SeleniumBase GitHub repo...\"\r\n            \"</mk-1></p>\"\r\n            '<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/'\r\n            'master/help_docs/uc_mode.md\" target=\"_blank\">'\r\n            '<img src=\"https://seleniumbase.io/other/sb_github.jpg\"'\r\n            ' width=\"50%\"></a>'\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>Study, study, study!</b></mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"There's lots of important information in the UC Mode docs,\"\r\n            \" so study well to avoid falling into traps...\"\r\n            \"</mk-1><br /><br /><mk-2>\"\r\n            \"Sometimes you might still be able to<br />\"\r\n            \"get out of a trap you fell into...\"\r\n            \"</mk-2><br /><br /><mk-3>\"\r\n            \"Once you bypass a CAPTCHA, be ready for anything!\"\r\n            \"</mk-3></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0><b>There's more to come</b></mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>\"\r\n            \"As usual, export more UC Mode updates,<br />\"\r\n            \"but new projects are classified until released.\"\r\n            \"</mk-1></p>\"\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>❓ <mk-0>Questions?</mk-0> ❓</h3><h5><mk-0>\"\r\n            \"https://github.com/seleniumbase/SeleniumBase/discussions\"\r\n            \"</mk-0></h5><br />\"\r\n            \"<br /><h3>📌 <mk-1>Found a bug?</mk-1> 🐞</h3><h5><mk-1>\"\r\n            \"https://github.com/seleniumbase/SeleniumBase/issues\"\r\n            \"</mk-0></h5>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>📊 <mk-0>Final remarks</mk-0> 📣</h3><hr /><br />\"\r\n            \"<h3>\"\r\n            \"🛠️ <mk-1>SeleniumBase gives you</mk-1> 🛠️<br />\"\r\n            \"<mk-1>the tools you need to succeed!\"\r\n            \"</mk-1></h3>\"\r\n            \"<h3><mk-2><br />\"\r\n            \"And tools to build lots of bots...\"\r\n            \"</mk-2></h3><br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<div>🏁 <b>The End</b> 🏁</div>\"\r\n            '<img src=\"https://seleniumbase.io/other/sb_github.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n"
  },
  {
    "path": "examples/presenter/uc_presentation_4.py",
    "content": "# https://www.youtube.com/watch?v=Mr90iQmNsKM\r\nfrom contextlib import suppress\r\nfrom seleniumbase import BaseCase\r\nfrom seleniumbase import SB\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass UCPresentationClass(BaseCase):\r\n    def test_presentation_4(self):\r\n        self.open(\"data:,\")\r\n        self.set_window_position(4, 40)\r\n        self._output_file_saves = False\r\n        self.create_presentation(theme=\"serif\", transition=\"fade\")\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/uc4_title.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>This continues my Undetectable Automation series:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/ua_1_details.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            self.open(\"https://www.bostoncodecamp.com/CC37/info\")\r\n            self.create_tour(theme=\"hopscotch\")\r\n            self.add_tour_step(\r\n                \"<h2>Good Afternoon and Welcome!</h2>\", 'h1.wow'\r\n            )\r\n            self.add_tour_step(\r\n                \"<h4>PSA: Visit our sponsors later.</h4>\",\r\n                '[href*=\"/Sponsors\"]',\r\n            )\r\n            self.add_tour_step(\r\n                \"<h4>Let's check out the schedule...</h4>\",\r\n                '[href*=\"/Schedule/SessionGrid\"]'\r\n            )\r\n            self.play_tour()\r\n\r\n        with suppress(Exception):\r\n            self.open(\r\n                \"https://www.bostoncodecamp.com/CC37/Schedule/SessionGrid\"\r\n            )\r\n            self.highlight(\"h2\", loops=8)\r\n            if self.is_element_visible('[data-sessionid=\"765448\"]'):\r\n                self.highlight('div[data-sessionid=\"765448\"]', loops=10)\r\n                self.create_tour(theme=\"driverjs\")\r\n                self.add_tour_step(\r\n                    \"<h2>Here we are</h2>\", '[data-sessionid=\"765448\"]'\r\n                )\r\n                self.play_tour()\r\n                self.click('a[onclick*=\"765448\"]')\r\n                self.create_tour(theme=\"hopscotch\")\r\n                self.add_tour_step(\r\n                    \"<h2>What to expect</h2>\",\r\n                    \"div.sz-modal-session\",\r\n                    alignment=\"left\",\r\n                )\r\n                self.play_tour()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h2>Last time...</h2>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Last time...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/uc3_title.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>This time...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/anti_bots.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"Note: There are different kinds of reCAPTCHA,<br />\"\r\n            \"and not all of them are created equal.<br />\"\r\n            '<img src=\"https://seleniumbase.io/other/recaptcha_v2a.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>This is what happens when you fail reCAPTCHA:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/recaptcha_v2b.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>This is what happens when you fail hCAPTCHA:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/hcaptcha_bunny.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>If you like puppies, hCAPTCHA has you covered:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/hcaptcha_puppy.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>This is what happens when some anti-bots detect you:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/you_are_blocked.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>And this is what happens when Gandalf blocks you:</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/gandalf.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>No joke... There's a Hobbit CAPTCHA</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/hobbit_captcha.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Important Notice:</h3>\"\r\n            \"(Know the laws and legal implications!)\"\r\n            '<img src=\"https://seleniumbase.io/other/legal_scraping.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>🔹 <b>By the end of this presentation...</b> 🔹</p><hr /><br />\"\r\n            \"✅ You'll learn which anti-bot systems work,<br />\"\r\n            \"and which ones don't. (Hint: Most don't work.)<br /><br />\"\r\n            \"✅ There will be multiple live demos.\"\r\n            \"<br /><br />\"\r\n            \"✅ You'll learn how to bypass weak defenses.\"\r\n            \"<br /><br />\"\r\n            \"✅ You'll learn powerful web-scraping techniques.\"\r\n            \"</mk-1></p>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h4>But first, a little about me...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/profile_t1.png\"'\r\n            ' width=\"48%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>About me: (Michael Mintz)</b></p>\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li><mk-0>I created the <b>SeleniumBase</b> framework.\"\r\n            \"</mk-0></li>\\n\"\r\n            \"<li><mk-1>I lead the Automation Team at <b>iboss</b>.\"\r\n            \"</mk-1></li>\\n\"\r\n            \"</ul>\",\r\n            image=\"https://seleniumbase.io/other/iboss_booth.png\",\r\n        )\r\n        self.add_slide(\r\n            \"<h2>In my spare time,</h2>\"\r\n            \"<h2>I may be found...</h2>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Spending time with entrepreneurs...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/with_hs_founders.jpg\"'\r\n            ' width=\"85%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Spending time with celebrities...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/with_frakes.jpg\"'\r\n            ' width=\"52%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Spending time with politicians...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/with_tulsi.jpg\"'\r\n            ' width=\"50%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Spending time with philanthropists...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/with_jeff.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Speaking at conferences...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/mintz_present.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Attending conferences as a guest...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/at_ms_build.jpg\"'\r\n            ' width=\"75%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Jet-skiing in Key West...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/on_jetski.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>And working on SeleniumBase...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/sb_star_history.jpg\"'\r\n            ' width=\"85%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Enough about me...</h4><br />\"\r\n            \"<h3>Let's begin the presentation!</h3><br /><br />\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/blue_chrome.jpg\"'\r\n            ' width=\"50%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/cdp_logo.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/blue_chrome.jpg\"'\r\n            ' width=\"30%\">'\r\n            '<img src=\"https://seleniumbase.io/other/cdp_definition.jpg\"'\r\n            ' width=\"100%\">'\r\n            \"<br />\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/se_pw_cdp.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"Playwright using CDP\"\r\n            '<img src=\"https://seleniumbase.io/other/pw_cdp.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"Selenium using CDP\"\r\n            '<img src=\"https://seleniumbase.io/other/se_cdp.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>Microsoft still supports Selenium,<br />\"\r\n            \"even though they have Playwright.</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/ms_edge_webdriver.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/twenty_se.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"As a birthday gift, BrightData invested a lot of money into \"\r\n            \"Selenium (making them an official sponsor).\"\r\n            '<img src=\"https://seleniumbase.io/other/bd_invests_in_se.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"That's great news for the Selenium community!\"\r\n            '<img src=\"https://seleniumbase.io/other/se_community.jpg\"'\r\n            ' width=\"68%\">'\r\n        )\r\n        self.add_slide(\r\n            \"Now, let's get back to CDP...\"\r\n            '<img src=\"https://seleniumbase.io/other/cdp_logo.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"There are lots of GitHub repos using CDP.<br />\"\r\n            \"(This repo tracks some of them)\"\r\n            '<img src=\"https://seleniumbase.io/other/awesome_cdp.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"The first major Python implementation of CDP:\"\r\n            '<img src=\"https://seleniumbase.io/other/python_cdp.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/mark_haase.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"PyCDP was the key ingredient to stealthy automation.\"\r\n            '<img src=\"https://seleniumbase.io/other/cdp_in_nodriver.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/nodriver.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/undetected_ch.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"In addition to using CDP for controlling Chrome in a\"\r\n            \" stealthy way, you can also achieve stealth by using\"\r\n            \" tools that can control the mouse and keyboard.<br /><br />\"\r\n            \"<code>PyAutoGUI</code> is one such tool:\"\r\n            '<img src=\"https://seleniumbase.io/other/pyautogui_tree.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/al_sweigart.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"PyAutoGUI requires a headed browser to work.<br /><br />\"\r\n            \" Since most Linux machines have headless displays that\"\r\n            \" don't support headed browsers, an external tool called\"\r\n            \" Xvfb must be used in order to simulate a headed browser\"\r\n            \" in a headless Linux environment...\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/xvfb_info.jpg\"'\r\n            ' width=\"56%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0>To have a completely stealthy framework\"\r\n            \" for clicking CAPTCHAs & bypassing anti-bot systems,\"\r\n            \" you need:</mk-0><br /><hr />\"\r\n            \"</p><ul>\\n\"\r\n            '<li><mk-1>A framework that uses a \"regular\" browser<br />'\r\n            '(to hide evidence of automation activity)'\r\n            '</mk-1>'\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-2>CDP capabilities for performing stealthy actions\"\r\n            \"</mk-2></li><br />\\n\"\r\n            \"<li><mk-3>PyAutoGUI for performing tricky actions<br />\"\r\n            \"(eg. clicking Shadow-root CAPTCHAs)</mk-3>\"\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-4>Xvfb integration for headless Linux systems</mk-4>\"\r\n            \"</li>\\n</ul><br />\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"SeleniumBase CDP Mode simplifies all that for you:<br /><br />\"\r\n            '<img src=\"https://seleniumbase.io/other/cdp_in_sb.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/sb_on_github.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/sb_on_discord.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/detect_antibots.jpg\"'\r\n            ' width=\"92%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/sc_en_invite.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"List of sites with their invisible anti-bot services:\"\r\n            '<img src=\"https://seleniumbase.io/other/sites_to_antibots.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>Let's get started with live demos of bypassing\"\r\n            \" physical CAPTCHAs:</mk-0></h3>\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up first...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"planetminecraft.com/account/sign_in/\"\r\n            \"</code></mk-0></h4><br /><br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True) as sb:\r\n            url = \"www.planetminecraft.com/account/sign_in/\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(2)\r\n            sb.solve_captcha()\r\n            sb.wait_for_element_absent(\"input[disabled]\")\r\n            sb.sleep(2)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"cloudflare.com/login\"\r\n            \"</code></mk-0></h4><br /><br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\") as sb:\r\n            url = \"https://www.cloudflare.com/login\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(3.5)\r\n            sb.solve_captcha()\r\n            sb.sleep(2.5)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"gitlab.com/users/sign_in\"\r\n            \"</code></mk-0></h4><br /><br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\") as sb:\r\n            url = \"https://gitlab.com/users/sign_in\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(2)\r\n            sb.solve_captcha()\r\n            # (The rest is for testing and demo purposes)\r\n            sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n            sb.assert_element('label[for=\"user_login\"]')\r\n            sb.highlight('button:contains(\"Sign in\")')\r\n            sb.highlight('h1:contains(\"GitLab\")')\r\n            sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<p><mk-0><b>The code for the previous live demo:</b></mk-0></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"<mk-1>from seleniumbase import SB</mk-1>\\n\\n\"\r\n                \"<mk-2>with SB(uc=True) as sb:</mk-2>\\n\"\r\n                '<mk-3>    url = \"https://gitlab.com/users/sign_in\"</mk-3>\\n'\r\n                \"<mk-4>    sb.activate_cdp_mode(url)</mk-4>\\n\"\r\n                \"<mk-5>    sb.sleep(2)</mk-5>\\n\"\r\n                \"<mk-6>    sb.solve_captcha()</mk-6>\\n\\n\"\r\n                \"<mk-7>    ...</mk-7>\\n\\n\\n\\n\\n\"\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>The code for the previous live demo:</b></p>\"\r\n            \"<hr /><br />\",\r\n            code=(\r\n                \"from seleniumbase import SB\\n\\n\"\r\n                \"with SB(uc=True) as sb:\\n\"\r\n                '    url = \"https://gitlab.com/users/sign_in\"\\n'\r\n                \"    sb.activate_cdp_mode(url)\\n\"\r\n                \"    sb.sleep(2)\\n\"\r\n                \"    sb.solve_captcha()\\n\\n\"\r\n                '<mk-1>    sb.assert_text(\"Username\", \\'[for=\"user_login\"]\\','\r\n                ' timeout=3)</mk-1>\\n'\r\n                '<mk-2>    sb.assert_element(\\'[for=\"user_login\"]\\')</mk-2>\\n'\r\n                '<mk-3>    sb.set_messenger_theme(location=\"bottom_center\")'\r\n                '</mk-3>\\n'\r\n                '<mk-4>    sb.post_message(\"SeleniumBase wasn\\'t detected!\")'\r\n                '</mk-4>\\n'\r\n            ),\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"bing.com/turing/captcha/challenge\"\r\n            \"</code></mk-0></h4><br /><br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True) as sb:\r\n            url = \"https://www.bing.com/turing/captcha/challenge\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(1)\r\n            sb.solve_captcha()\r\n            sb.sleep(2)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\"<h2>Having fun yet?!?</h2>\")\r\n        self.add_slide(\r\n            \"<h4>If you're not yet concerned about online security,<br />\"\r\n            \" then you probably need to see more live demos...</h4>\")\r\n        self.add_slide(\r\n            \"<h3><mk-0>Time for live demos of bypassing<br />\"\r\n            \"some invisible anti-bot services:</mk-0></h3>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"pokemon.com/us\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by Imperva / Incapsula)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n            url = \"https://www.pokemon.com/us\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(1.5)\r\n            sb.click_if_visible(\"button#onetrust-accept-btn-handler\")\r\n            sb.sleep(1.2)\r\n            sb.click(\"a span.icon_pokeball\")\r\n            sb.sleep(2.5)\r\n            sb.click('b:contains(\"Show Advanced Search\")')\r\n            sb.sleep(2.5)\r\n            sb.click('span[data-type=\"type\"][data-value=\"electric\"]')\r\n            sb.sleep(0.7)\r\n            sb.scroll_into_view(\"a#advSearch\")\r\n            sb.sleep(0.7)\r\n            sb.click(\"a#advSearch\")\r\n            sb.sleep(0.5)\r\n            sb.cdp.click(\"a#advSearch\")\r\n            sb.sleep(1.2)\r\n            sb.cdp.click('img[src*=\"img/pokedex/detail/025.png\"]')\r\n            sb.cdp.assert_text(\"Pikachu\", 'div[class*=\"title\"]')\r\n            sb.cdp.assert_element('img[alt=\"Pikachu\"]')\r\n            sb.cdp.scroll_into_view(\"div.pokemon-ability-info\")\r\n            sb.sleep(1.2)\r\n            sb.cdp.flash('div[class*=\"title\"]')\r\n            sb.cdp.flash('img[alt=\"Pikachu\"]')\r\n            sb.cdp.flash(\"div.pokemon-ability-info\")\r\n            name = sb.cdp.get_text(\"label.styled-select\")\r\n            info = sb.cdp.get_text(\"div.version-descriptions p.active\")\r\n            print(\"*** %s: ***\\n* %s\" % (name, info))\r\n            sb.sleep(2)\r\n            sb.cdp.highlight_overlay(\"div.pokemon-ability-info\")\r\n            sb.sleep(2)\r\n            sb.cdp.open(\"https://events.pokemon.com/EventLocator/\")\r\n            sb.sleep(2)\r\n            sb.cdp.click('span:contains(\"Championship\")')\r\n            sb.sleep(2)\r\n            events = sb.cdp.select_all(\"div.event-info__title\")\r\n            print(\"*** Pokémon Championship Events: ***\")\r\n            for event in events:\r\n                print(\"* \" + event.text)\r\n            sb.sleep(2)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"walmart.com\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by Akamai + PerimeterX)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, ad_block=True) as sb:\r\n            url = \"https://www.walmart.com/\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(1.8)\r\n            continue_button = 'button:contains(\"Continue shopping\")'\r\n            if sb.is_element_visible(continue_button):\r\n                sb.cdp.gui_click_element(continue_button)\r\n                sb.sleep(0.6)\r\n            sb.click('input[aria-label=\"Search\"]')\r\n            sb.sleep(1.2)\r\n            search = \"Settlers of Catan Board Game\"\r\n            required_text = \"Catan\"\r\n            sb.press_keys('input[aria-label=\"Search\"]', search + \"\\n\")\r\n            sb.sleep(3.8)\r\n            if sb.is_element_visible(\"#px-captcha\"):\r\n                sb.cdp.gui_click_and_hold(\"#px-captcha\", 7.2)\r\n                sb.sleep(3.2)\r\n                if sb.is_element_visible(\"#px-captcha\"):\r\n                    sb.cdp.gui_click_and_hold(\"#px-captcha\", 4.2)\r\n                    sb.sleep(3.2)\r\n            sb.remove_elements('[data-testid=\"skyline-ad\"]')\r\n            sb.remove_elements('[data-testid=\"sba-container\"]')\r\n            print('*** Walmart Search for \"%s\":' % search)\r\n            print('    (Results must contain \"%s\".)' % required_text)\r\n            unique_item_text = []\r\n            sb.click_if_visible('[data-automation-id=\"sb-btn-close-mark\"]')\r\n            items = sb.find_elements('[data-item-id]')\r\n            for item in items:\r\n                if required_text in item.text:\r\n                    description = item.querySelector(\r\n                        '[data-automation-id=\"product-title\"]'\r\n                    )\r\n                    if (\r\n                        description\r\n                        and description.text not in unique_item_text\r\n                    ):\r\n                        unique_item_text.append(description.text)\r\n                        print(\"* \" + description.text)\r\n                        price = item.querySelector(\r\n                            '[data-automation-id=\"product-price\"]'\r\n                        )\r\n                        if price:\r\n                            price_text = price.text\r\n                            price_text = (\r\n                                price_text.split(\"current price Now \")[-1]\r\n                            )\r\n                            price_text = price_text.split(\"current price \")[-1]\r\n                            price_text = price_text.split(\" \")[0]\r\n                            print(\"  (\" + price_text + \")\")\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"albertsons.com/recipes/\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by Imperva / Incapsula)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\") as sb:\r\n            url = \"https://www.albertsons.com/recipes/\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(2.5)\r\n            sb.remove_element(\"div > div > article\")\r\n            sb.scroll_into_view('input[type=\"search\"]')\r\n            close_btn = \".notification-alert-wrapper__close-button\"\r\n            sb.click_if_visible(close_btn)\r\n            sb.click(\"input#search-suggestion-input\")\r\n            sb.sleep(0.2)\r\n            search = \"Avocado Smoked Salmon\"\r\n            required_text = \"Salmon\"\r\n            sb.press_keys(\"input#search-suggestion-input\", search)\r\n            sb.sleep(0.8)\r\n            sb.click(\"#suggestion-0 a span\")\r\n            sb.sleep(0.8)\r\n            sb.click_if_visible(close_btn)\r\n            sb.sleep(3.2)\r\n            print('*** Albertsons Search for \"%s\":' % search)\r\n            print('    (Results must contain \"%s\".)' % required_text)\r\n            unique_item_text = []\r\n            item_selector = 'a[href*=\"/meal-plans-recipes/shop/\"]'\r\n            items = sb.find_elements(item_selector)\r\n            for item in items:\r\n                sb.sleep(0.06)\r\n                if required_text in item.text:\r\n                    item.flash(color=\"44CC88\")\r\n                    sb.sleep(0.025)\r\n                    if item.text not in unique_item_text:\r\n                        unique_item_text.append(item.text)\r\n                        print(\"* \" + item.text)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"easyjet.com/en/\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by Akamai)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n            url = \"https://www.easyjet.com/en/\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(2)\r\n            sb.click_if_visible(\"button#ensCloseBanner\")\r\n            sb.sleep(1.2)\r\n            sb.click('input[name=\"from\"]')\r\n            sb.sleep(1.2)\r\n            sb.type('input[name=\"from\"]', \"London Gatwick\")\r\n            sb.sleep(0.6)\r\n            sb.click_if_visible(\"button#ensCloseBanner\")\r\n            sb.sleep(0.6)\r\n            sb.click('span[data-testid=\"airport-name\"]')\r\n            sb.sleep(1.2)\r\n            sb.type('input[name=\"to\"]', \"Paris\")\r\n            sb.sleep(1.2)\r\n            sb.click('span[data-testid=\"airport-name\"]')\r\n            sb.sleep(1.2)\r\n            sb.click('input[name=\"when\"]')\r\n            sb.sleep(1.2)\r\n            sb.cdp.click(\r\n                '[data-testid=\"month\"]:last-of-type'\r\n                ' [aria-disabled=\"false\"]'\r\n            )\r\n            sb.sleep(1.2)\r\n            sb.click(\r\n                '[data-testid=\"month\"]:last-of-type'\r\n                ' [aria-disabled=\"false\"]'\r\n            )\r\n            sb.sleep(1.2)\r\n            sb.click('button[data-testid=\"submit\"]')\r\n            sb.sleep(3.5)\r\n            sb.connect()\r\n            sb.sleep(4.2)\r\n            for window in sb.driver.window_handles:\r\n                sb.switch_to_window(window)\r\n                if \"/buy/flights\" in sb.get_current_url():\r\n                    break\r\n            sb.click_if_visible(\"button#ensCloseBanner\")\r\n            days = sb.find_elements('div[class*=\"FlightGridLayout_column\"]')\r\n            for day in days:\r\n                if not day.text.strip():\r\n                    continue\r\n                print(\r\n                    \"\\n\\n**** \" + \" \".join(day.text.split(\"\\n\")[0:2]) + \" ****\"\r\n                )\r\n                fares = day.find_elements(\r\n                    \"css selector\", 'button[class*=\"flightDet\"]'\r\n                )\r\n                if not fares:\r\n                    print(\"No flights today!\")\r\n                for fare in fares:\r\n                    info = fare.text\r\n                    info = info.replace(\"LOWEST FARE\\n\", \"\")\r\n                    info = info.replace(\"\\n\", \"  \")\r\n                    print(info)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"hyatt.com\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by Kasada)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\", ad_block=True) as sb:\r\n            url = \"https://www.hyatt.com/\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(3.2)\r\n            sb.click_if_visible('button[aria-label=\"Close\"]')\r\n            sb.sleep(0.1)\r\n            sb.click_if_visible(\"#onetrust-reject-all-handler\")\r\n            sb.sleep(1.2)\r\n            location = \"Anaheim, CA, USA\"\r\n            sb.type('input[id=\"search-term\"]', location)\r\n            sb.sleep(1.2)\r\n            sb.click('li[data-js=\"suggestion\"]')\r\n            sb.sleep(1.2)\r\n            sb.click(\"button.be-button-shop\")\r\n            sb.sleep(6)\r\n            card_info = (\r\n                'div[data-booking-status=\"BOOKABLE\"] [class*=\"HotelCard_info\"]'\r\n            )\r\n            hotels = sb.select_all(card_info)\r\n            destination_selector = 'span[class*=\"summary_destination\"]'\r\n            print(\"Hyatt Hotels in %s:\" % location)\r\n            print(\"(\" + sb.get_text(destination_selector) + \")\")\r\n            if len(hotels) == 0:\r\n                print(\"No availability over the selected dates!\")\r\n            for hotel in hotels:\r\n                info = hotel.text.strip()\r\n                if \"Avg/Night\" in info and not info.startswith(\"Rates from\"):\r\n                    name = info.split(\"  (\")[0]\r\n                    name = name.split(\" + \")[0].split(\" Award Cat\")[0]\r\n                    name = name.split(\" Rates from :\")[0]\r\n                    price = \"?\"\r\n                    if \"Rates from : \" in info:\r\n                        price = info.split(\"Rates from : \")[1]\r\n                        price = price.split(\" Avg/Night\")[0]\r\n                    print(\"* %s => %s\" % (name, price))\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"bestwestern.com/en_US.html\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by DataDome)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\", guest=True) as sb:\r\n            url = \"https://www.bestwestern.com/en_US.html\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(3)\r\n            sb.click_if_visible(\".onetrust-close-btn-handler\")\r\n            sb.sleep(1)\r\n            sb.click(\"input#destination-input\")\r\n            sb.sleep(2)\r\n            location = \"Palm Springs, CA, USA\"\r\n            sb.press_keys(\"input#destination-input\", location)\r\n            sb.sleep(1)\r\n            sb.click(\"ul#google-suggestions li\")\r\n            sb.sleep(1)\r\n            sb.click(\"button#btn-modify-stay-update\")\r\n            sb.sleep(4)\r\n            sb.click(\"label#available-label\")\r\n            sb.sleep(2.5)\r\n            print(\"Best Western Hotels in %s:\" % location)\r\n            summary_details = sb.get_text(\"#summary-details-column\")\r\n            dates = summary_details.split(\"DESTINATION\")[-1]\r\n            dates = dates.split(\" CHECK-OUT\")[0].strip() + \" CHECK-OUT\"\r\n            dates = dates.replace(\"  \", \" \")\r\n            print(\"(Dates: %s)\" % dates)\r\n            flip_cards = sb.select_all(\".flipCard\")\r\n            for i, flip_card in enumerate(flip_cards):\r\n                hotel = flip_card.query_selector(\".hotelName\")\r\n                price = flip_card.query_selector(\".priceSection\")\r\n                if hotel and price:\r\n                    print(\"* %s: %s => %s\" % (\r\n                        i + 1, hotel.text.strip(), price.text.strip())\r\n                    )\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"priceline.com\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by DataDome)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\", guest=True, pls=\"none\") as sb:\r\n            url = \"https://www.priceline.com\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(2.6)\r\n            input_selector = '[name=\"endLocation\"]'\r\n            if not sb.is_element_present(input_selector):\r\n                input_selector = \"div.location-input input\"\r\n            sb.click(input_selector)\r\n            sb.sleep(1.2)\r\n            location = \"Portland, OR\"\r\n            selection = \"Oregon, United States\"  # (Dropdown option)\r\n            sb.press_keys(input_selector, location)\r\n            sb.sleep(0.6)\r\n            sb.click(selection)\r\n            sb.sleep(0.4)\r\n            sb.scroll_down(25)\r\n            sb.sleep(0.4)\r\n            calendar_close = 'button[aria-label=\"Dismiss calendar\"]'\r\n            if not sb.is_element_visible(calendar_close):\r\n                calendar_close = '[data-mode=\"range\"] span.px-1'\r\n            sb.click(calendar_close)\r\n            sb.sleep(0.6)\r\n            sb.click('form button[type=\"submit\"]')\r\n            sb.sleep(4.8)\r\n            if len(sb.cdp.get_tabs()) > 1:\r\n                sb.cdp.close_active_tab()\r\n                sb.cdp.switch_to_newest_tab()\r\n                sb.sleep(0.6)\r\n            sb.sleep(0.8)\r\n            for y in range(1, 9):\r\n                sb.scroll_to_y(y * 400)\r\n                sb.sleep(0.5)\r\n            hotel_names = sb.find_elements('h3 div[class*=\"TitleName\"]')\r\n            if not hotel_names:\r\n                hotel_names = sb.find_elements(\r\n                    'a[data-autobot-element-id*=\"HOTEL_NAME\"]'\r\n                )\r\n            price_selector = '[class*=\"PriceWrap\"] .relative > .items-center'\r\n            if sb.is_element_visible(price_selector):\r\n                hotel_prices = sb.find_elements(price_selector)\r\n            elif sb.is_element_present(\r\n                '[font-size=\"12px\"] + [font-size=\"20px\"]'\r\n            ):\r\n                hotel_prices = sb.find_elements(\r\n                    '[font-size=\"12px\"] + [font-size=\"20px\"]'\r\n                )\r\n            else:\r\n                hotel_prices = sb.find_elements(\r\n                    'span.text-priceSuper-heading4 + div > span'\r\n                )\r\n            print(\"Priceline Hotels in %s:\" % location)\r\n            print(sb.get_text('[data-testid=\"POPOVER-DATE-PICKER\"]'))\r\n            if len(hotel_names) == 0:\r\n                print(\"No availability over the selected dates!\")\r\n            count = 0\r\n            for i, hotel in enumerate(hotel_names):\r\n                if hotel_prices[i] and hotel_prices[i].text:\r\n                    count += 1\r\n                    hotel_price = \"$\" + hotel_prices[i].text\r\n                    if hotel_price.startswith(\"$$ \"):\r\n                        hotel_price = hotel_price.replace(\"$$ \", \"$\")\r\n                    print(\"* %s: %s => %s\" % (count, hotel.text, hotel_price))\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/shatner_priceline.jpg\"'\r\n            ' width=\"60%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/mintz_karate.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/with_shatner.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/mintz_enterprise.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"nike.com\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by Shape Security)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\", pls=\"none\") as sb:\r\n            url = \"https://www.nike.com/\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(2.5)\r\n            sb.click('[data-testid=\"user-tools-container\"] search')\r\n            sb.sleep(1.5)\r\n            search = \"Nike Air Force 1\"\r\n            sb.press_keys('input[type=\"search\"]', search)\r\n            sb.sleep(4)\r\n            details = 'ul[data-testid*=\"products\"] figure .details'\r\n            elements = sb.select_all(details)\r\n            if elements:\r\n                print('**** Found results for \"%s\": ****' % search)\r\n            for element in elements:\r\n                print(\"* \" + element.text)\r\n            sb.sleep(2)\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>Up next...</h3><hr /><br /><br /><h4><mk-0><code>\"\r\n            \"nordstrom.com\"\r\n            \"</code></mk-0></h4><br />\"\r\n            \"(Protected by Shape Security)\"\r\n            \"<br /><br /><br />\"\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with SB(uc=True, test=True, locale=\"en\") as sb:\r\n            url = \"https://www.nordstrom.com/\"\r\n            sb.activate_cdp_mode(url)\r\n            sb.sleep(2.2)\r\n            sb.click(\"input#keyword-search-input\")\r\n            sb.sleep(0.8)\r\n            search = \"cocktail dresses for women teal\"\r\n            sb.press_keys(\"input#keyword-search-input\", search + \"\\n\")\r\n            sb.sleep(2.2)\r\n            for i in range(17):\r\n                sb.scroll_down(16)\r\n                sb.sleep(0.14)\r\n            print('*** Nordstrom Search for \"%s\":' % search)\r\n            unique_item_text = []\r\n            items = sb.find_elements(\"article\")\r\n            for item in items:\r\n                description = item.querySelector(\"article h3\")\r\n                if description and description.text not in unique_item_text:\r\n                    unique_item_text.append(description.text)\r\n                    price_text = \"\"\r\n                    price = item.querySelector(\r\n                        'div div span[aria-hidden=\"true\"]'\r\n                    )\r\n                    if price:\r\n                        price_text = price.text\r\n                        print(\"* %s (%s)\" % (description.text, price_text))\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h3>CDP is powerful, as you can see.</h3>\"\r\n            \"<h4>(Especially when used for stealth!)</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/blue_chrome.jpg\"'\r\n            ' width=\"40%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Out of the following 9 anti-bot defense systems...</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/anti_bots.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>These are weak: (Can't detect stealthy CDP)</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/bypassable_anti_bots.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>And these are strong: (CDP is detected)</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/capable_anti_bots.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3><mk-0>What is Microsoft's stance<br />on stealthy CDP?</mk-0>\"\r\n            \"</h3><br /><br /><h3><mk-1>Officially...</mk-1></h3>\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/pw_no_stealth.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>What is Microsoft's stance<br />on stealthy CDP?</h3>\"\r\n            \"<br /><br /><h3><mk-0>Unofficially...</mk-0></h3>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>There are external repos<br />using Playwright for stealth.\"\r\n            \"</h3><br /><br /><h4>\"\r\n            \"And Microsoft employees are<br />endorsing them via GitHub Stars.\"\r\n            \"</h4>\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/scrapling_pw.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/ms_stars_scrapling.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"And steathy CDP works well in GitHub Actions.\"\r\n            '<img src=\"https://seleniumbase.io/other/gh_actions_scrape.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"Why does stealthy CDP work in GitHub Actions,<br />\"\r\n            \"but not in other kinds of services like AWS?\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Answer:<br /><br />\"\r\n            'GitHub Actions runs in a<br />'\r\n            '\"residential IP address\" space!'\r\n            \"<br /><br /></h3>\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/rp_def.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>People can use residential proxies<br />\"\r\n            \"to get a residential IP address.</h3>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Legal info:</h3>\"\r\n            '<img src=\"https://seleniumbase.io/other/legal_rp_scraping.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\"<h2>To summarize that...</h2>\")\r\n        self.add_slide(\r\n            \"<h4>Scraping public data is probably legal.<br />\"\r\n            \"(Think Light Side)</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/sw_light_side.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>Scraping private data is probably NOT legal.<br />\"\r\n            \"(Think Dark Side)</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/sw_dark_side.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>If you break local and/or international laws,<br />\"\r\n            \"then bounty hunters may come after you.</h4>\"\r\n            '<img src=\"https://seleniumbase.io/other/sw_bounty_hunters.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Let's get back to SeleniumBase</h3>\"\r\n            '<img src=\"https://seleniumbase.io/other/sb_laptop.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h4>SeleniumBase includes a special Chrome extension:</h4>\"\r\n            '<h2>The \"Recorder\"</h2>'\r\n            \"<h4>(You can generate complete scripts with it.)</h4>\"\r\n            \"<h3><code>sbase recorder --uc</code></h3>\"\r\n            '<img src=\"https://seleniumbase.io/cdn/img/'\r\n            'sb_recorder_notification.png\" width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/cdn/img/recorder_desktop.png\"'\r\n            ' width=\"56%\">'\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        # import sys\r\n        # from seleniumbase.console_scripts import sb_recorder\r\n        # sys.argv.append(\"--uc\")\r\n        # sb_recorder.main()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\r\n            \"<h4>How does one make an automation Recorder?</h4>\"\r\n            '<img src=\"https://seleniumbase.io/cdn/img/'\r\n            'sb_recorder_notification.png\" width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/analytics_auto_ext.jpg\"'\r\n            ' width=\"75%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/event_listeners_1.jpg\"'\r\n            ' width=\"92%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/event_listeners_2.jpg\"'\r\n            ' width=\"92%\">'\r\n        )\r\n        self.add_slide(\r\n            \"And that's the secret to building a test recorder!\"\r\n            '<img src=\"https://seleniumbase.io/cdn/img/'\r\n            'sb_recorder_notification.png\" width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"Also note that there are more stealth CDP repos<br />\"\r\n            \"other than the ones that you have already seen.\"\r\n            '<img src=\"https://seleniumbase.io/other/other_stealth_repos.jpg\"'\r\n            ' width=\"92%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>👤 <mk-0>Field trip to the CDP Mode help docs</mk-0> 👤</p>\"\r\n            \"<hr /><h6><br /></h6>\"\r\n            \"<p><mk-1>Let's take a look at the CDP Mode docs<br />\"\r\n            \"from the SeleniumBase GitHub repo...</mk-1></p>\"\r\n            '<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/'\r\n            'master/examples/cdp_mode/ReadMe.md\" target=\"_blank\">'\r\n            '<img src=\"https://seleniumbase.io/other/cdp_in_sb.jpg\"'\r\n            ' width=\"60%\"></a>'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>❓ <mk-0>Questions?</mk-0> ❓</h3><h5><mk-0>\"\r\n            \"https://github.com/seleniumbase/SeleniumBase/discussions\"\r\n            \"</mk-0></h5><br />\"\r\n            \"<br /><h3>📌 <mk-1>Found a bug?</mk-1> 🐞</h3><h5><mk-1>\"\r\n            \"https://github.com/seleniumbase/SeleniumBase/issues\"\r\n            \"</mk-0></h5>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>📊 <mk-0>Final remarks</mk-0> 📣</h3><hr /><br />\"\r\n            \"<h3>🛠️ <mk-1>SeleniumBase gives you</mk-1> 🛠️<br />\"\r\n            \"<mk-1>the tools you need to succeed!\"\r\n            \"</mk-1></h3><h3><mk-2><br />\"\r\n            \"And tools to build lots of bots...\"\r\n            \"</mk-2></h3><br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<div>🏁 <b>The End</b> 🏁</div>\"\r\n            '<img src=\"https://seleniumbase.io/other/sb_github.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n"
  },
  {
    "path": "examples/presenter/web_scraping_on_gha.py",
    "content": "from contextlib import suppress\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass UCPresentationClass(BaseCase):\r\n    def test_hacking_with_cdp(self):\r\n        self.open(\"data:,\")\r\n        self.set_window_position(4, 40)\r\n        self._output_file_saves = False\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\"<h2>Press SPACE to continue!</h2>\\n\")\r\n        self.add_slide(\r\n            \"<h3><b>Before we begin</b></h3><hr />\"\r\n            \"<p><b>(Here's the GitHub page)</b></p>\",\r\n            image=\"https://seleniumbase.io/other/sbase_qr_code.png\",\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n\r\n        with suppress(Exception):\r\n            self.open(\"https://www.bostoncodecamp.com/CC38/info\")\r\n            self.create_tour(theme=\"hopscotch\")\r\n            self.add_tour_step(\r\n                \"<h2>Good Afternoon and Welcome!</h2>\", 'h1.wow'\r\n            )\r\n            self.add_tour_step(\r\n                \"<h4>PSA: Visit our sponsors later.</h4>\",\r\n                '[href*=\"/Sponsors\"]',\r\n            )\r\n            self.add_tour_step(\r\n                \"<h4>Let's check out the schedule...</h4>\",\r\n                '[href*=\"/Schedule/SessionGrid\"]'\r\n            )\r\n            self.play_tour()\r\n\r\n        with suppress(Exception):\r\n            self.open(\r\n                \"https://www.bostoncodecamp.com/CC38/Schedule/SessionGrid\"\r\n            )\r\n            self.highlight(\"h2\", loops=8)\r\n            if self.is_element_visible('[data-sessionid=\"869465\"]'):\r\n                self.highlight(\r\n                    'div[data-sessionid=\"869465\"]', loops=10, scroll=False\r\n                )\r\n                self.create_tour(theme=\"driverjs\")\r\n                self.add_tour_step(\r\n                    \"<h2>Here we are</h2>\", '[data-sessionid=\"869465\"]'\r\n                )\r\n                self.play_tour()\r\n                self.click('a[onclick*=\"869465\"]')\r\n                self.create_tour(theme=\"hopscotch\")\r\n                self.add_tour_step(\r\n                    \"<h2>What to expect</h2>\",\r\n                    \"div.sz-modal-session\",\r\n                    alignment=\"left\",\r\n                )\r\n                self.play_tour()\r\n\r\n        self.create_presentation(theme=\"serif\", transition=\"none\")\r\n        self.add_slide(\"<h2>Press SPACE to begin!</h2>\\n\")\r\n        self.add_slide(\r\n            \"<p><b><mk-0>Coming up... on the Hacker Show:</mk-0></b></p>\\n\"\r\n            \"<hr /><ul>\\n\"\r\n            '<img src=\"https://seleniumbase.io/other/robot_ai.jpg\"'\r\n            ' width=\"92%\"></ul>\\n'\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up... on the Hacker Show:</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li><mk-0>Unlimited free web-scraping w/ GitHub Actions&nbsp\\n\"\r\n            \"</mk-0></li>\\n\"\r\n            \"</ul>\"\r\n            '<img src=\"https://seleniumbase.io/other/gha_scraping.jpg\"'\r\n            ' width=\"82%\">',\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up... on the Hacker Show:</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Unlimited free web-scraping w/ GitHub Actions&nbsp\"\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-0>Using GitHub Secrets to hide within open-source\"\r\n            \"</mk-0></li></ul>\\n\"\r\n            '<img src=\"https://seleniumbase.io/other/gh_secret.png\"'\r\n            ' width=\"80%\">\\n'\r\n            \"<br /><br />\\n\"\r\n            \"<br /><br />\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up... on the Hacker Show:</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Unlimited free web-scraping w/ GitHub Actions&nbsp\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Using GitHub Secrets to hide within open-source\"\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-0>Launching your own, free, local proxy server\"\r\n            \"</mk-0></li></ul>\\n\"\r\n            '<img src=\"https://seleniumbase.io/other/sbase_proxy.png\"'\r\n            ' width=\"80%\">'\r\n            \"<br /><br />\\n\"\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up... on the Hacker Show:</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Unlimited free web-scraping w/ GitHub Actions&nbsp\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Using GitHub Secrets to hide within open-source\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Launching your own, free, local proxy server\"\r\n            \"</li><br />\\n\"\r\n            '<li><mk-0>Using \"iptables\" to make a proxy server public'\r\n            \"</mk-0></li></ul>\\n\"\r\n            '<img src=\"https://seleniumbase.io/other/tiny_iptables.png\"'\r\n            ' width=\"80%\">'\r\n            \"<br /><br />\\n\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Coming up... on the Hacker Show:</b></p>\\n\"\r\n            \"<hr /><br /><ul>\\n\"\r\n            \"<li>Unlimited free web-scraping w/ GitHub Actions&nbsp\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Using GitHub Secrets to hide within open-source\"\r\n            \"</li><br />\\n\"\r\n            \"<li>Launching your own, free, local proxy server\"\r\n            \"</li><br />\\n\"\r\n            '<li>Using \"iptables\" to make a proxy server public'\r\n            \"</li><br />\\n\"\r\n            \"<li><mk-0>And multiple live demos after the previews\"\r\n            \"</mk-0></li><br />\\n\"\r\n            \"</ul>\",\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Get ready for some serious hacking!</b></p>\"\r\n            '<img src=\"https://seleniumbase.io/other/hackers_at_comp.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/web_scraping.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0>And YES, that means bypassing bot-detection!</mk-0>\"\r\n            '<img src=\"https://seleniumbase.io/other/bypassable_anti_bots.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>But first, a little bit about me...</b></p>\"\r\n            '<img src=\"https://seleniumbase.io/other/mintz_present.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>About me: (Michael Mintz)</b></p>\\n\"\r\n            \"<ul>\\n\"\r\n            \"<li>I created the <b>SeleniumBase</b> framework.\"\r\n            \"</li>\\n\"\r\n            \"<li>And I lead the Automation Team at <b>iboss</b>.\"\r\n            \"</li>\\n\"\r\n            \"</ul>\"\r\n            '<img src=\"https://seleniumbase.io/other/iboss_me_2.jpg\"'\r\n            ' width=\"60%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Fun Fact</b></p><hr />\\n\"\r\n            \"<p>I once showed SeleniumBase to Sam Altman at MIT.<br />\"\r\n            \"(Sam Altman cofounded OpenAI with Elon Musk.)\"\r\n            '<img src=\"https://seleniumbase.io/other/with_altman.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>Recently, SeleniumBase was trending on GitHub:\"\r\n            \"</b></p>\\n\"\r\n            '<img src=\"https://seleniumbase.io/other/trending_2025.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>The recent popularity can be attributed to <b>CDP Mode</b>,\"\r\n            \"<br />which provides advanced stealth during automation.</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/cdp_in_sb.jpg\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>That stealth is enough to bypass bot-detection<br />\"\r\n            \"while web-scraping from <b>GitHub Actions</b>:</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/gha_scraping.jpg\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><b>GitHub Actions</b> is free for public repositories:</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/gha_info.png\"'\r\n            ' width=\"90%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/gha_is_free.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>To hide sensitive information while using\"\r\n            \"<br />GitHub Actions for open-source projects,\"\r\n            \"<br />there's a feature called: <b>GitHub Secrets</b>.</p>\"\r\n            \"<br />\"\r\n            \"<p>That removes the limitation\"\r\n            r\"<br />of being 100% open-source,\"\r\n            r\"<br />while still being 100% free.</p>\"\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/using_secrets_in_gha.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img '\r\n            'src=\"https://seleniumbase.io/other/limits_for_gh_secrets.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/creating_gh_secrets.jpg\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/gh_secrets_in_wf.png\"'\r\n            ' width=\"88%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img '\r\n            'src=\"https://seleniumbase.io/other/using_gh_secrets_in_py.png\"'\r\n            ' width=\"88%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>And that's the secret<br />to <b>GitHub Secrets!</b></h3>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Up next:</h3>\"\r\n            \"<br />\"\r\n            \"<h2>Instant proxy server</h2>\"\r\n            \"<br />\"\r\n            \"<p>(Faster to launch than making Instant Coffee!)\"\r\n        )\r\n        self.add_slide(\r\n            '<h2><code>\"sbase proxy\"</code></h2>'\r\n            \"<br />\"\r\n            \"<h3>(That's it!)</h3>\"\r\n            \"<br />\"\r\n            '<img src=\"https://seleniumbase.io/other/sbase_proxy.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<p>More configuration options for \"<b>sbase proxy</b>\":</p>'\r\n            '<img src=\"https://seleniumbase.io/other/instant_proxy.png\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p>The proxy server code comes from <b>proxy.py</b>:</p>\"\r\n            '<img src=\"https://seleniumbase.io/other/proxy_dot_py.png\"'\r\n            ' width=\"80%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<p><mk-0>\"\r\n            \"Here's how to configure a proxy with <b>SeleniumBase</b>:</mk-0>\"\r\n            \"</p><hr /><br /><ul>\\n\"\r\n            '<li><mk-1>Proxy Mode via <code><b>pytest</b></code>:</mk-1>'\r\n            '<br /><code>pytest --proxy=\"host:port\"</code>'\r\n            '<br /><code>pytest --proxy=\"user:pass@host:port\"</code>'\r\n            '</li><br />\\n'\r\n            '<li><mk-2>Proxy Mode via <code><b>SB()</b></code> manager:</mk-2>'\r\n            '<br /><code>SB(proxy=\"host:port\")</code>'\r\n            '<br /><code>SB(proxy=\"user:pass@host:port\")</code>'\r\n            '</li>\\n'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>That's the secret to<br />instant proxy servers!</h3>\"\r\n            \"<br /><p>(And how to use them with SeleniumBase)</p>\"\r\n        )\r\n        self.add_slide(\r\n            \"<h3>How about opening up a\"\r\n            \"<br />proxy server to the world?</h3><br />\"\r\n        )\r\n        self.add_slide(\r\n            \"<h2>For that, there's <b>iptables</b></h2><br />\"\r\n            '<img src=\"https://seleniumbase.io/other/iptables_info.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            '<img src=\"https://seleniumbase.io/other/iptables_guide.png\"'\r\n            ' width=\"100%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>And that's the secret to<br />making a server public!</h3>\"\r\n            '<img src=\"https://seleniumbase.io/other/super_server.jpg\"'\r\n            ' width=\"66%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3>Let's move on to<br />some live demos</h3>\"\r\n            '<img src=\"https://seleniumbase.io/other/hackers_at_comp.jpg\"'\r\n            ' width=\"70%\">'\r\n        )\r\n        self.add_slide(\r\n            \"<h3><b>Live Demo Time!</b></h3><hr />\"\r\n            \"<h3>(Let's head over to GitHub...)</h3>\",\r\n            image=\"https://seleniumbase.io/other/sbase_qr_code.png\",\r\n        )\r\n        self.begin_presentation(filename=\"uc_presentation.html\")\r\n"
  },
  {
    "path": "examples/proxy_test.py",
    "content": "from seleniumbase.config import settings\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ProxyTests(BaseCase):\n    def test_proxy(self):\n        if self.headless or self.recorder_mode or self.browser == \"safari\":\n            self.open_if_not_url(\"about:blank\")\n            print(\"\\n  Unsupported mode for this test.\")\n            self.skip(\"Unsupported mode for this test.\")\n        settings.SKIP_JS_WAITS = True\n        self.open(\"https://api.ipify.org/\")\n        ip_address = self.get_text(\"body\")\n        if \"ERR\" in ip_address:\n            raise Exception(\"Failed to determine IP Address!\")\n        print(\"\\n\\nMy IP Address = %s\\n\" % ip_address)\n        self.open(\"https://ipinfo.io/%s\" % ip_address)\n        self.sleep(2)\n        self.wait_for_text(ip_address, \"h1\", timeout=20)\n        self.wait_for_element_present('[href=\"/signup\"]')\n        self.wait_for_text(\"Hosted domains\", timeout=20)\n        self.highlight(\"h1\")\n        pop_up = '[role=\"dialog\"] span.cursor-pointer'\n        self.click_if_visible(pop_up)\n        self.highlight(\"#block-summary\")\n        self.click_if_visible(pop_up)\n        self.highlight(\"#block-geolocation\")\n        self.click_if_visible(pop_up)\n        self.sleep(2)\n        print(\"Displaying Host Info:\")\n        text = self.get_text(\"#block-summary\").split(\"Hosted domains\")[0]\n        rows = text.split(\"\\n\")\n        data = []\n        for row in rows:\n            if row.strip() != \"\":\n                data.append(row.strip())\n        print(\"\\n\".join(data).replace('\\n\"', ' \"'))\n        print(\"\\nDisplaying GeoLocation Info:\")\n        text = self.get_text(\"#block-geolocation\")\n        text = text.split(\"IP Geolocation data\")[0]\n        rows = text.split(\"\\n\")\n        data = []\n        for row in rows:\n            if row.strip() != \"\":\n                data.append(row.strip())\n        print(\"\\n\".join(data).replace('\\n\"', ' \"'))\n        self.click_if_visible(pop_up)\n        self.sleep(3)\n"
  },
  {
    "path": "examples/pytest.ini",
    "content": "[pytest]\n\n# Display console output. Disable cacheprovider:\naddopts = --capture=tee-sys -p no:cacheprovider\n\n# Skip these directories during test collection:\nnorecursedirs = .* build dist recordings temp assets\n\n# Ignore DeprecationWarning, PytestUnknownMarkWarning\nfilterwarnings =\n    ignore::pytest.PytestWarning\n    ignore:.*U.*mode is deprecated:DeprecationWarning\n\n# Configure the junit_family option explicitly:\njunit_family = legacy\n\n# Set pytest discovery rules:\n# (Most of the rules here are similar to the default rules.)\n# (Inheriting unittest.TestCase could override these rules.)\npython_files = test_*.py *_test.py *_tests.py *_suite.py\npython_classes = Test* *Test* *Test *Tests *Suite\npython_functions = test_*\n\n# Common pytest markers used in examples:\n# (pytest may require marker registration to prevent warnings.)\n# (Future versions may turn those marker warnings into errors.)\nmarkers =\n    marker1: custom marker\n    marker2: custom marker\n    marker3: custom marker\n    marker_test_suite: custom marker\n    expected_failure: custom marker\n    local: custom marker\n    remote: custom marker\n    offline: custom marker\n    develop: custom marker\n    qa: custom marker\n    ci: custom marker\n    e2e: custom marker\n    ready: custom marker\n    smoke: custom marker\n    deploy: custom marker\n    active: custom marker\n    master: custom marker\n    release: custom marker\n    staging: custom marker\n    production: custom marker\n"
  },
  {
    "path": "examples/rate_limiting_test.py",
    "content": "\"\"\"This test demonstrates the use of the \"rate_limited\" decorator.\nYou can use this decorator on any method to rate-limit it.\"\"\"\nfrom seleniumbase import BaseCase\nfrom seleniumbase import decorators\n\n\nclass RateLimitingTests(BaseCase):\n    @decorators.rate_limited(4.2)  # The arg is max calls per second\n    def print_item(self, item):\n        print(item)\n\n    def test_rate_limited_printing(self):\n        if self._multithreaded or self.recorder_mode:\n            self.open_if_not_url(\"about:blank\")\n            print(\"\\n  Unsupported mode for this test.\")\n            self.skip(\"Unsupported mode for this test.\")\n        message = \"Running rate-limited print() on the command line\"\n        self.open(\"data:text/html,<p>%s</p>\" % message)\n        print(\"\\n%s:\" % message)\n        for item in range(1, 11):\n            self.print_item(item)\n"
  },
  {
    "path": "examples/raw_ahrefs.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True, locale=\"en\") as sb:\r\n    url = \"https://ahrefs.com/website-authority-checker\"\r\n    input_field = 'input[placeholder=\"Enter domain\"]'\r\n    submit_button = 'span:contains(\"Check Authority\")'\r\n    sb.activate_cdp_mode(url)\r\n    sb.type(input_field, \"github.com/seleniumbase/SeleniumBase\")\r\n    sb.click(submit_button)\r\n    sb.sleep(2)\r\n    sb.solve_captcha()\r\n    sb.wait_for_text_not_visible(\"Checking\", timeout=15)\r\n    sb.click_if_visible('button[data-cky-tag=\"close-button\"]')\r\n    sb.highlight('p:contains(\"github.com/seleniumbase/SeleniumBase\")')\r\n    sb.highlight('a:contains(\"Top 100 backlinks\")')\r\n    sb.set_messenger_theme(location=\"bottom_center\")\r\n    sb.post_message(\"SeleniumBase wasn't detected!\")\r\n"
  },
  {
    "path": "examples/raw_antibot_login.py",
    "content": "\"\"\"UC Mode has PyAutoGUI methods for CAPTCHA-bypass.\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/antibot/login\"\n    sb.uc_open_with_disconnect(url, 2.15)\n    sb.uc_gui_write(\"\\t\" + \"demo_user\")\n    sb.uc_gui_write(\"\\t\" + \"secret_pass\")\n    sb.uc_gui_press_keys(\"\\t\" + \" \")  # For Single-char keys\n    sb.sleep(1.5)\n    sb.uc_gui_press_keys([\"\\t\", \"ENTER\"])  # Multi-char keys\n    sb.reconnect(1.8)\n    sb.assert_text(\"Welcome!\", \"h1\")\n    sb.set_messenger_theme(location=\"bottom_center\")\n    sb.post_message(\"SeleniumBase wasn't detected!\")\n"
  },
  {
    "path": "examples/raw_bing_captcha.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = \"https://www.bing.com/turing/captcha/challenge\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1)\r\n    sb.solve_captcha()\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/raw_block.py",
    "content": "\"\"\"If Brotector catches you, Gandalf blocks you!\"\"\"\nfrom seleniumbase import SB\n\nwith SB(test=True) as sb:\n    url = \"https://seleniumbase.io/hobbit/login\"\n    sb.open(url)\n    sb.click_if_visible(\"button\")\n    sb.assert_text(\"Gandalf blocked you!\", \"h1\")\n    sb.click(\"img\")\n    sb.highlight(\"h1\")\n    sb.sleep(3)  # Gandalf: \"You Shall Not Pass!\"\n"
  },
  {
    "path": "examples/raw_brotector_captcha.py",
    "content": "\"\"\"UC Mode has PyAutoGUI methods for CAPTCHA-bypass.\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/apps/brotector\"\n    sb.uc_open_with_disconnect(url, 2.2)\n    sb.uc_gui_press_key(\"\\t\")\n    sb.uc_gui_press_key(\" \")\n    sb.reconnect(2.2)\n"
  },
  {
    "path": "examples/raw_call.py",
    "content": "\"\"\"Can run with \"python\" instead of using \"pytest\" directly.\nUsage: \"python raw_call.py\".\nTwo examples: pytest.main() and subprocess.call().\"\"\"\nimport pytest\nimport subprocess\n\nif __name__ == \"__main__\":\n    pytest.main([\"test_coffee_cart.py\", \"--chrome\", \"-v\"])\n    subprocess.call([\"pytest\", \"test_mfa_login.py\", \"--chrome\", \"-v\"])\n"
  },
  {
    "path": "examples/raw_cdp_drivers.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url1 = \"https://seleniumbase.io/demo_page\"\r\n    sb.activate_cdp_mode(url1)\r\n    driver1 = sb.driver\r\n    url2 = \"https://seleniumbase.io/coffee/\"\r\n    driver2 = sb.get_new_driver(undetectable=True)\r\n    sb.activate_cdp_mode(url2)\r\n    print(driver1.get_current_url())\r\n    print(driver2.get_current_url())\r\n    sb.switch_to_default_driver()\r\n    sb.assert_url_contains(\"demo_page\")\r\n    print(sb.get_current_url())\r\n    sb.switch_to_driver(driver2)\r\n    sb.assert_url_contains(\"coffee\")\r\n    print(sb.get_current_url())\r\n"
  },
  {
    "path": "examples/raw_cdp_logging.py",
    "content": "from rich.pretty import pprint\nfrom seleniumbase import Driver\n\ndriver = Driver(uc=True, log_cdp=True)\ntry:\n    url = \"seleniumbase.io/apps/turnstile\"\n    driver.uc_open_with_reconnect(url, 2)\n    driver.uc_gui_handle_captcha()\n    driver.sleep(2)\n    pprint(driver.get_log(\"performance\"))\nfinally:\n    driver.quit()\n"
  },
  {
    "path": "examples/raw_cf.py",
    "content": "\"\"\"SB Manager using CDP Mode for bypassing CAPTCHAs.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, guest=True) as sb:\r\n    url = \"https://www.cloudflare.com/login\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.wait_for_element('div[data-testid*=\"challenge-widget\"]')\r\n    sb.sleep(1.5)\r\n    sb.solve_captcha()\r\n    sb.sleep(3)\r\n"
  },
  {
    "path": "examples/raw_cookies.py",
    "content": "\"\"\"A SeleniumBase test that loads cookies to bypass login.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\n# Log in to Swag Labs and save cookies\r\nwith SB(test=True) as sb:\r\n    sb.open(\"https://www.saucedemo.com\")\r\n    sb.wait_for_element(\"div.login_logo\")\r\n    sb.type(\"#user-name\", \"standard_user\")\r\n    sb.type(\"#password\", \"secret_sauce\")\r\n    sb.click('input[type=\"submit\"]')\r\n    sb.highlight(\"div.inventory_list\", loops=6)\r\n    sb.save_cookies(name=\"cookies.txt\")\r\n\r\n# Load previously saved cookies to bypass login\r\nwith SB(test=True) as sb:\r\n    sb.open(\"https://www.saucedemo.com\")\r\n    sb.load_cookies(name=\"cookies.txt\")\r\n    sb.open(\"https://www.saucedemo.com/inventory.html\")\r\n    sb.highlight(\"div.inventory_list\", loops=12)\r\n"
  },
  {
    "path": "examples/raw_detection.py",
    "content": "\"\"\"The Brotector CAPTCHA in action.\"\"\"\nfrom seleniumbase import SB\n\nwith SB(test=True) as sb:\n    sb.open(\"https://seleniumbase.io/antibot/login\")\n    sb.highlight(\"h4\", loops=6)\n    sb.type(\"#username\", \"demo_user\")\n    sb.type(\"#password\", \"secret_pass\")\n    sb.click_if_visible(\"button span\")\n    sb.highlight(\"label#pText\")\n    sb.highlight(\"table#detections\")\n    sb.sleep(4.4)  # Add time to read the table\n"
  },
  {
    "path": "examples/raw_driver_context.py",
    "content": "\"\"\"DriverContext() example. (Runs with \"python\").\"\"\"\r\nfrom seleniumbase import DriverContext\r\n\r\nwith DriverContext() as driver:\r\n    driver.open(\"seleniumbase.io/\")\r\n    driver.highlight('img[alt=\"SeleniumBase\"]', loops=6)\r\n\r\nwith DriverContext(browser=\"chrome\", incognito=True) as driver:\r\n    driver.open(\"seleniumbase.io/apps/calculator\")\r\n    driver.click('[id=\"4\"]')\r\n    driver.click('[id=\"2\"]')\r\n    driver.assert_text(\"42\", \"#output\")\r\n    driver.highlight(\"#output\", loops=6)\r\n\r\nwith DriverContext() as driver:\r\n    driver.open(\"seleniumbase.io/demo_page\")\r\n    driver.highlight(\"h2\")\r\n    driver.type(\"#myTextInput\", \"Automation\")\r\n    driver.click(\"#checkBox1\")\r\n    driver.highlight(\"img\", loops=6)\r\n"
  },
  {
    "path": "examples/raw_driver_manager.py",
    "content": "\"\"\"Driver() manager example. (Runs with \"python\").\"\"\"\r\nfrom seleniumbase import Driver\r\n\r\ndriver = Driver()\r\ntry:\r\n    driver.open(\"seleniumbase.io/demo_page\")\r\n    driver.highlight(\"h2\")\r\n    driver.type(\"#myTextInput\", \"Automation\")\r\n    driver.click(\"#checkBox1\")\r\n    driver.highlight(\"img\", loops=6)\r\nfinally:\r\n    driver.quit()\r\n\r\ndriver = Driver(browser=\"chrome\", headless=False)\r\ntry:\r\n    driver.open(\"seleniumbase.io/apps/calculator\")\r\n    driver.click('[id=\"4\"]')\r\n    driver.click('[id=\"2\"]')\r\n    driver.assert_text(\"42\", \"#output\")\r\n    driver.highlight(\"#output\", loops=6)\r\nfinally:\r\n    driver.quit()\r\n"
  },
  {
    "path": "examples/raw_file_call.py",
    "content": "\"\"\"Call a file with \"python\" instead of \"pytest\" directly.\nAdded pytest args from the command-line won't be included.\nTo run, use: \"python raw_file_call.py\".\"\"\"\nfrom seleniumbase import BaseCase\nif __name__ == \"__main__\":\n    from pytest import main\n    main([__file__, \"-s\"])\n\n\nclass TinyMceTest(BaseCase):\n    def test_tinymce(self):\n        self.open(\"https://seleniumbase.io/tinymce/\")\n        self.wait_for_element(\"div.mce-container-body\")\n        self.click('span:contains(\"File\")')\n        self.click('span:contains(\"New document\")')\n        self.click('span:contains(\"Paragraph\")')\n        self.click('span:contains(\"Heading 1\")')\n        with self.frame_switch(\"iframe\"):\n            self.add_text(\"#tinymce\", \"SeleniumBase!\")\n            self.highlight(\"#tinymce\")\n            self.post_message(\"SeleniumBase is fast!\", duration=1.5)\n        self.post_message(\"And SeleniumBase is fun!\", duration=1.5)\n"
  },
  {
    "path": "examples/raw_form_turnstile.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = \"seleniumbase.io/apps/form_turnstile\"\r\n    sb.uc_open_with_reconnect(url, 1.1)\r\n    sb.press_keys(\"#name\", \"SeleniumBase\")\r\n    sb.press_keys(\"#email\", \"test@test.test\")\r\n    sb.press_keys(\"#phone\", \"1-555-555-5555\")\r\n    sb.click('[for=\"date\"]')\r\n    sb.click(\"td.is-today button\")\r\n    sb.click('div[class=\"select-wrapper\"] input')\r\n    sb.click('span:contains(\"9:00 PM\")')\r\n    sb.highlight_click('input[value=\"AR\"] + span')\r\n    sb.click('input[value=\"cc\"] + span')\r\n    sb.scroll_to('div[class*=\"cf-turnstile\"]')\r\n    sb.uc_gui_handle_captcha()\r\n    sb.highlight(\"img#captcha-success\", timeout=3)\r\n    sb.highlight_click('button:contains(\"Request & Pay\")')\r\n    sb.highlight(\"img#submit-success\")\r\n    sb.highlight('button:contains(\"Success!\")')\r\n"
  },
  {
    "path": "examples/raw_games.py",
    "content": "\"\"\"SB Manager using CDP Mode for evading bot-detection.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, disable_csp=True) as sb:\r\n    url = \"https://steamdb.info/\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1)\r\n    sb.click(\"a.header-login span\")\r\n    sb.sleep(2)\r\n    sb.solve_captcha()\r\n    sb.assert_text(\"Sign in\", \"button#js-sign-in\", timeout=4)\r\n    sb.sleep(0.5)\r\n    sb.click(\"button#js-sign-in\")\r\n    sb.sleep(0.5)\r\n    sb.highlight(\"div.page_content form\")\r\n    sb.highlight('button:contains(\"Sign in\")', scroll=False)\r\n    sb.set_messenger_theme(location=\"top_center\")\r\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n"
  },
  {
    "path": "examples/raw_gitlab.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True, locale=\"en\") as sb:\n    url = \"https://gitlab.com/users/sign_in\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.solve_captcha()\n    # (The rest is for testing and demo purposes)\n    sb.assert_element('label[for=\"user_login\"]')\n    sb.assert_element('input[data-testid*=\"username\"]')\n    sb.assert_element('input[data-testid*=\"password\"]')\n    sb.set_messenger_theme(location=\"bottom_center\")\n    sb.post_message(\"SeleniumBase wasn't detected!\")\n"
  },
  {
    "path": "examples/raw_google.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = \"https://google.com/ncr\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.type('[name=\"q\"]', \"SeleniumBase GitHub page\")\r\n    sb.click('[value=\"Google Search\"]')\r\n    sb.sleep(4)  # The \"AI Overview\" sometimes loads\r\n    print(sb.get_page_title())\r\n    sb.save_as_pdf_to_logs()\r\n    sb.save_page_source_to_logs()\r\n    sb.save_screenshot_to_logs()\r\n    print(\"Logs have been saved to: ./latest_logs/\")\r\n"
  },
  {
    "path": "examples/raw_gui_click.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"seleniumbase.io/apps/form_turnstile\"\n    sb.activate_cdp_mode(url)\n    sb.press_keys(\"#name\", \"SeleniumBase\")\n    sb.press_keys(\"#email\", \"test@test.test\")\n    sb.press_keys(\"#phone\", \"1-555-555-5555\")\n    sb.click('[for=\"date\"]')\n    sb.click(\"td.is-today button\")\n    sb.click('div[class=\"select-wrapper\"] input')\n    sb.click('span:contains(\"9:00 PM\")')\n    sb.highlight_click('input[value=\"AR\"] + span')\n    sb.click('input[value=\"cc\"] + span')\n    sb.scroll_to('div[class*=\"cf-turnstile\"]')\n    sb.scroll_down(40)\n    sb.uc_gui_click_captcha()\n    sb.highlight(\"img#captcha-success\", timeout=3)\n    sb.highlight_click('button:contains(\"Request & Pay\")')\n    sb.highlight(\"img#submit-success\")\n    sb.highlight('button:contains(\"Success!\")')\n"
  },
  {
    "path": "examples/raw_hobbit.py",
    "content": "\"\"\"UC Mode has PyAutoGUI methods for CAPTCHA-bypass.\"\"\"\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/hobbit/login\"\n    sb.uc_open_with_disconnect(url, 2.2)\n    sb.uc_gui_press_keys(\"\\t \")\n    sb.reconnect(1.5)\n    sb.assert_text(\"Welcome to Middle Earth!\", \"h1\")\n    sb.set_messenger_theme(location=\"bottom_center\")\n    sb.post_message(\"SeleniumBase wasn't detected!\")\n    sb.click(\"img\")\n    sb.sleep(5.888)  # Cool animation happening now!\n"
  },
  {
    "path": "examples/raw_invisible_captcha.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True) as sb:\r\n    url = \"https://seleniumbase.io/apps/invisible_recaptcha\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.sleep(1)\r\n    sb.assert_element(\"img#captcha-success\", timeout=3)\r\n    sb.set_messenger_theme(location=\"top_left\")\r\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=3)\r\n"
  },
  {
    "path": "examples/raw_login_context.py",
    "content": "from seleniumbase import DriverContext\r\n\r\nwith DriverContext() as driver:\r\n    driver.open(\"seleniumbase.io/simple/login\")\r\n    driver.type(\"#username\", \"demo_user\")\r\n    driver.type(\"#password\", \"secret_pass\")\r\n    driver.click('a:contains(\"Sign in\")')\r\n    driver.assert_exact_text(\"Welcome!\", \"h1\")\r\n    driver.assert_element(\"img#image1\")\r\n    driver.highlight(\"#image1\")\r\n    driver.click_link(\"Sign out\")\r\n    driver.assert_text(\"signed out\", \"#top_message\")\r\n"
  },
  {
    "path": "examples/raw_login_driver.py",
    "content": "from seleniumbase import Driver\r\n\r\ndriver = Driver()\r\ntry:\r\n    driver.open(\"seleniumbase.io/simple/login\")\r\n    driver.type(\"#username\", \"demo_user\")\r\n    driver.type(\"#password\", \"secret_pass\")\r\n    driver.click('a:contains(\"Sign in\")')\r\n    driver.assert_exact_text(\"Welcome!\", \"h1\")\r\n    driver.assert_element(\"img#image1\")\r\n    driver.highlight(\"#image1\")\r\n    driver.click_link(\"Sign out\")\r\n    driver.assert_text(\"signed out\", \"#top_message\")\r\nfinally:\r\n    driver.quit()\r\n"
  },
  {
    "path": "examples/raw_login_sb.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB() as sb:\r\n    sb.open(\"seleniumbase.io/simple/login\")\r\n    sb.type(\"#username\", \"demo_user\")\r\n    sb.type(\"#password\", \"secret_pass\")\r\n    sb.click('a:contains(\"Sign in\")')\r\n    sb.assert_exact_text(\"Welcome!\", \"h1\")\r\n    sb.assert_element(\"img#image1\")\r\n    sb.highlight(\"#image1\")\r\n    sb.click_link(\"Sign out\")\r\n    sb.assert_text(\"signed out\", \"#top_message\")\r\n"
  },
  {
    "path": "examples/raw_main_call.py",
    "content": "\"\"\"Can use \"python\" instead of using \"pytest\".\nAdded pytest args will be included in the run.\nExample usage: \"python raw_file_call.py\".\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass TinyMceTest(BaseCase):\n    def test_tinymce(self):\n        self.open(\"https://seleniumbase.io/tinymce/\")\n        self.wait_for_element(\"div.mce-container-body\")\n        self.click('span:contains(\"File\")')\n        self.click('span:contains(\"New document\")')\n        self.click('span:contains(\"Paragraph\")')\n        self.click('span:contains(\"Heading 1\")')\n        with self.frame_switch(\"iframe\"):\n            self.add_text(\"#tinymce\", \"SeleniumBase!\")\n            self.highlight(\"#tinymce\")\n            self.post_message(\"SeleniumBase is fast!\", duration=1.5)\n        self.post_message(\"And SeleniumBase is fun!\", duration=1.5)\n"
  },
  {
    "path": "examples/raw_mobile.py",
    "content": "from seleniumbase import Driver\n\ndriver = Driver(mobile=True)\ntry:\n    driver.open(\"https://www.roblox.com/\")\n    driver.assert_element(\"#download-the-app-container\")\n    driver.assert_text(\"Roblox for Android\")\n    driver.highlight('span:contains(\"Roblox for Android\")', loops=8)\n    driver.highlight('span:contains(\"Continue in App\")', loops=8)\nfinally:\n    driver.quit()\n"
  },
  {
    "path": "examples/raw_multi_drivers.py",
    "content": "import sys\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor\nfrom random import randint, seed\nfrom seleniumbase import Driver\nsys.argv.append(\"-n\")  # Tell SeleniumBase to do thread-locking as needed\n\n\ndef launch_driver(url):\n    seed(len(threading.enumerate()))  # Random seed for browser placement\n    driver = Driver()\n    try:\n        driver.set_window_rect(randint(4, 720), randint(8, 410), 700, 500)\n        driver.get(url=url)\n        if driver.is_element_visible(\"h1\"):\n            driver.highlight(\"h1\", loops=9)\n        else:\n            driver.sleep(2.2)\n    finally:\n        driver.quit()\n\n\nif __name__ == \"__main__\":\n    urls = ['https://seleniumbase.io/demo_page' for i in range(4)]\n    with ThreadPoolExecutor(max_workers=len(urls)) as executor:\n        for url in urls:\n            executor.submit(launch_driver, url)\n"
  },
  {
    "path": "examples/raw_multi_sb.py",
    "content": "import sys\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor\nfrom random import randint, seed\nfrom seleniumbase import SB\nsys.argv.append(\"-n\")  # Tell SeleniumBase to do thread-locking as needed\n\n\ndef launch_driver(url):\n    seed(len(threading.enumerate()))  # Random seed for browser placement\n    with SB() as sb:\n        sb.set_window_rect(randint(4, 720), randint(8, 410), 700, 500)\n        sb.open(url=url)\n        if sb.is_element_visible(\"h1\"):\n            sb.highlight(\"h1\", loops=9)\n        else:\n            sb.sleep(2.2)\n\n\nif __name__ == \"__main__\":\n    urls = ['https://seleniumbase.io/demo_page' for i in range(4)]\n    with ThreadPoolExecutor(max_workers=len(urls)) as executor:\n        for url in urls:\n            executor.submit(launch_driver, url)\n"
  },
  {
    "path": "examples/raw_no_context_mgr.py",
    "content": "\"\"\"SB() without the context manager `with` block.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nsb_context = SB()\r\nsb = sb_context.__enter__()\r\nsb.open(\"data:text/html,<h1>Test Page</h1>\")\r\nsb.highlight(\"h1\", loops=8)\r\nsb_context.__exit__(None, None, None)\r\n\r\n\"\"\"Same example using `with`:\r\nfrom seleniumbase import SB\r\n\r\nwith SB() as sb:\r\n    sb.open(\"data:text/html,<h1>Test Page</h1>\")\r\n    sb.highlight(\"h1\", loops=8)\r\n\"\"\"\r\n"
  },
  {
    "path": "examples/raw_order_tickets.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, ad_block=True) as sb:\r\n    url = \"https://www.ticketmaster.com\"\r\n    sb.activate_cdp_mode(url)\r\n    input_field = 'input[name=\"q\"]'\r\n    sb.wait_for_element(input_field)\r\n    sb.sleep(1.6)\r\n    query = \"Jerry Seinfeld\"\r\n    sb.press_keys(input_field, query)\r\n    sb.sleep(1.6)\r\n    sb.click('a:contains(\"%s\")' % query)\r\n    sb.sleep(4.2)\r\n    print('*** TicketMaster Search for \"%s\":' % query)\r\n    item_selector = '[data-testid=\"eventList\"] li'\r\n    for item in sb.find_elements(item_selector):\r\n        print(\"* \" + item.text)\r\n"
  },
  {
    "path": "examples/raw_parameter_script.py",
    "content": "\"\"\" The main purpose of this file is to demonstrate running SeleniumBase\n    scripts without the use of Pytest by calling the script directly\n    with Python or from a Python interactive interpreter.\n    Based on whether relative imports work or don't work, this script\n    can autodetect how this file was run. With pure Python, it\n    initializes all the variables that would've been automatically\n    initialized by the Pytest plugin. The setUp() and tearDown() methods\n    are also now called from the script itself.\n\n    (Note: The SB() and Driver() formats make this example obsolete.)\n\n    One big advantage to running tests with Pytest is that most of this\n    is done for you automatically, with the option to update any of the\n    parameters through command-line parsing. Pytest also provides you\n    with other plugins, such as ones for generating test reports,\n    handling multithreading, and parametrized tests. Depending on your\n    specific needs, you may need to call SeleniumBase commands without\n    using Pytest, and this example shows you one way of doing that.\n\"\"\"\n\npure_python = False\ntry:\n    # Running with Pytest / (Finds test methods to run using autodiscovery)\n    # Example run command:  \"pytest raw_parameter_script.py\"\n    from .my_first_test import MyTestClass  # (relative imports work: \".~\")\n\nexcept (ImportError, ValueError):\n    # Running with pure Python OR from a Python interactive interpreter\n    # Example run command:  \"python raw_parameter_script.py\"\n    from my_first_test import MyTestClass  # (relative imports do not work)\n\n    pure_python = True\n\nif pure_python:\n    sb = MyTestClass(\"test_swag_labs\")\n    sb.browser = \"chrome\"\n    sb.is_behave = False\n    sb.headless = False\n    sb.headless1 = False\n    sb.headless2 = False\n    sb.headed = False\n    sb.xvfb = False\n    sb.xvfb_metrics = None\n    sb.start_page = None\n    sb.locale_code = None\n    sb.protocol = \"http\"\n    sb.servername = \"localhost\"\n    sb.port = 4444\n    sb.data = None\n    sb.var1 = None\n    sb.var2 = None\n    sb.var3 = None\n    sb.variables = {}\n    sb.account = None\n    sb.environment = \"test\"\n    sb.env = \"test\"  # should match sb.environment\n    sb.user_agent = None\n    sb.incognito = False\n    sb.guest_mode = False\n    sb.dark_mode = False\n    sb.devtools = False\n    sb.mobile_emulator = False\n    sb.device_metrics = None\n    sb.extension_zip = None\n    sb.extension_dir = None\n    sb.database_env = \"test\"\n    sb.archive_logs = False\n    sb.disable_csp = False\n    sb.disable_ws = False\n    sb.enable_ws = False\n    sb.enable_sync = False\n    sb.use_auto_ext = False\n    sb.undetectable = False\n    sb.uc_cdp_events = False\n    sb.uc_subprocess = False\n    sb.no_sandbox = False\n    sb.disable_js = False\n    sb.disable_gpu = False\n    sb.log_cdp_events = False\n    sb._multithreaded = False\n    sb._reuse_session = False\n    sb._crumbs = False\n    sb._final_debug = False\n    sb.esc_end = False\n    sb.use_wire = False\n    sb.enable_3d_apis = False\n    sb.window_position = None\n    sb.window_size = None\n    sb.maximize_option = False\n    sb.visual_baseline = False\n    sb.disable_cookies = False\n    sb.disable_features = None\n    sb._disable_beforeunload = False\n    sb.save_screenshot_after_test = False\n    sb.no_screenshot_after_test = False\n    sb.host_resolver_rules = None\n    sb.page_load_strategy = None\n    sb.timeout_multiplier = None\n    sb.pytest_html_report = None\n    sb.with_db_reporting = False\n    sb.with_s3_logging = False\n    sb.js_checking_on = False\n    sb.recorder_mode = False\n    sb.recorder_ext = False\n    sb.record_sleep = False\n    sb.rec_behave = False\n    sb.rec_print = False\n    sb.report_on = False\n    sb.is_pytest = False\n    sb.slow_mode = False\n    sb.demo_mode = False\n    sb.time_limit = None\n    sb.demo_sleep = None\n    sb.dashboard = False\n    sb._dash_initialized = False\n    sb.message_duration = None\n    sb.binary_location = None\n    sb.driver_version = None\n    sb.block_images = False\n    sb.do_not_track = False\n    sb.external_pdf = False\n    sb.remote_debug = False\n    sb.settings_file = None\n    sb.user_data_dir = None\n    sb.chromium_arg = None\n    sb.firefox_arg = None\n    sb.firefox_pref = None\n    sb.proxy_string = None\n    sb.proxy_bypass_list = None\n    sb.proxy_pac_url = None\n    sb._swiftshader = False\n    sb.multi_proxy = False\n    sb.ad_block_on = False\n    sb.highlights = None\n    sb.interval = None\n    sb.cap_file = None\n    sb.cap_string = None\n\n    sb.setUp()\n    try:\n        sb.test_swag_labs()\n    finally:\n        sb.tearDown()\n        del sb\n"
  },
  {
    "path": "examples/raw_performance_logs.py",
    "content": "from rich.pretty import pprint\nfrom seleniumbase import SB\n\nwith SB(log_cdp=True) as sb:\n    sb.open(\"seleniumbase.io/demo_page\")\n    sb.sleep(1)\n    pprint(sb.driver.get_log(\"performance\"))\n"
  },
  {
    "path": "examples/raw_pixelscan.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True) as sb:\r\n    url = \"https://pixelscan.net/fingerprint-check\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.remove_element(\"#headerBanner\")\r\n    sb.wait_for_element(\"pxlscn-dynamic-ad\")\r\n    sb.sleep(0.5)\r\n    sb.remove_elements(\"pxlscn-dynamic-ad\")\r\n    sb.sleep(2)\r\n    sb.assert_text(\"No masking detected\", \"pxlscn-fingerprint-masking\")\r\n    sb.assert_text(\"No automated behavior\", \"pxlscn-bot-detection\")\r\n    sb.cdp.highlight('span:contains(\"is consistent\")')\r\n    sb.sleep(1)\r\n    sb.cdp.highlight(\"pxlscn-fingerprint-masking p\")\r\n    sb.sleep(1)\r\n    sb.cdp.highlight(\"pxlscn-bot-detection p\")\r\n    sb.sleep(2)\r\n"
  },
  {
    "path": "examples/raw_pyautogui.py",
    "content": "from seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/apps/turnstile\"\n    sb.activate_cdp_mode(url)\n    sb.uc_gui_handle_captcha()  # Cycle with TAB, then SPACEBAR\n    sb.assert_element(\"img#captcha-success\", timeout=3)\n    sb.set_messenger_theme(location=\"top_left\")\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=3)\n"
  },
  {
    "path": "examples/raw_recaptcha.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True, incognito=True) as sb:\r\n    url = \"https://seleniumbase.io/apps/recaptcha\"\r\n    sb.activate_cdp_mode(url)\r\n    sb.uc_gui_click_captcha('iframe[src*=\"/recaptcha/\"]')\r\n    sb.assert_element(\"img#captcha-success\", timeout=3)\r\n    sb.set_messenger_theme(location=\"top_left\")\r\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=3)\r\n"
  },
  {
    "path": "examples/raw_robot.py",
    "content": "from seleniumbase import SB\n\nwith SB(enable_3d_apis=True, test=True) as sb:\n    sb.open(\"threejs.org/examples/#webgl_animation_skinning_morph\")\n    sb.switch_to_frame(\"iframe#viewer\")\n    sb.set_text_content(\"#info p\", \"Hi, I'm Michael Mintz\")\n    sb.add_css_style(\"#info p{zoom: 2.54}\")\n    sb.sleep(0.8)\n    sb.click('button:contains(\"Wave\")')\n    sb.highlight(\"#info p\")\n    sb.select_option_by_text(\"select\", \"Idle\")\n    sb.click('button:contains(\"ThumbsUp\")')\n    sb.set_text_content(\"#info p\", \"I created SeleniumBase\")\n    sb.highlight(\"#info p\")\n    sb.sleep(0.8)\n    sb.click('button:contains(\"Jump\")')\n    sb.sleep(1.5)\n"
  },
  {
    "path": "examples/raw_sb.py",
    "content": "\"\"\"SB() context manager example. (Runs with \"python\").\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB() as sb:  # By default, browser=\"chrome\" if not set.\r\n    sb.open(\"https://seleniumbase.io/realworld/login\")\r\n    sb.type(\"#username\", \"demo_user\")\r\n    sb.type(\"#password\", \"secret_pass\")\r\n    sb.enter_mfa_code(\"#totpcode\", \"GAXG2MTEOR3DMMDG\")  # 6-digit\r\n    sb.assert_text(\"Welcome!\", \"h1\")\r\n    sb.highlight(\"img#image1\")  # A fancier assert_element() call\r\n    sb.click('a:contains(\"This Page\")')  # Use :contains() on any tag\r\n    sb.click_link(\"Sign out\")  # Link must be \"a\" tag. Not \"button\".\r\n    sb.assert_element('a:contains(\"Sign in\")')\r\n    sb.assert_exact_text(\"You have been signed out!\", \"#top_message\")\r\n"
  },
  {
    "path": "examples/raw_test_scripts.py",
    "content": "\"\"\"Context Manager Test. Runs with \"python\". (pytest not needed)\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    sb.open(\"https://google.com/ncr\")\r\n    sb.type('[name=\"q\"]', \"SeleniumBase on GitHub\")\r\n    sb.click('[value=\"Google Search\"]')\r\n    sb.highlight('a[href*=\"github.com/seleniumbase\"]')\r\n    sb.sleep(0.5)\r\n\r\nwith SB(test=True, rtf=True, demo=True) as sb:\r\n    sb.open(\"seleniumbase.github.io/demo_page\")\r\n    sb.type(\"#myTextInput\", \"This is Automated\")\r\n    sb.assert_text(\"This is Automated\", \"#myTextInput\")\r\n    sb.assert_text(\"This Text is Green\", \"#pText\")\r\n    sb.click('button:contains(\"Click Me\")')\r\n    sb.assert_text(\"This Text is Purple\", \"#pText\")\r\n    sb.click(\"#checkBox1\")\r\n    sb.assert_element_not_visible(\"div#drop2 img#logo\")\r\n    sb.drag_and_drop(\"img#logo\", \"div#drop2\")\r\n    sb.assert_element(\"div#drop2 img#logo\")\r\n"
  },
  {
    "path": "examples/raw_turnstile.py",
    "content": "from seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = \"https://seleniumbase.io/apps/turnstile\"\r\n    sb.uc_open_with_reconnect(url)\r\n    sb.uc_gui_click_captcha()\r\n    sb.assert_element(\"img#captcha-success\", timeout=3)\r\n    sb.set_messenger_theme(location=\"top_left\")\r\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=3)\r\n"
  },
  {
    "path": "examples/raw_uc_events.py",
    "content": "from rich.pretty import pprint\r\nfrom seleniumbase import SB\r\n\r\n\r\ndef add_cdp_listener(sb):\r\n    # (To print everything, use \"*\". Otherwise select specific headers.)\r\n    # self.driver.add_cdp_listener(\"*\", lambda data: print(pformat(data)))\r\n    sb.driver.add_cdp_listener(\r\n        \"Network.requestWillBeSentExtraInfo\",\r\n        lambda data: pprint(data)\r\n    )\r\n\r\n\r\ndef click_turnstile_and_verify(sb):\r\n    sb.uc_gui_handle_captcha()\r\n    sb.assert_element(\"img#captcha-success\", timeout=3)\r\n    sb.highlight(\"img#captcha-success\", loops=8)\r\n\r\n\r\nwith SB(uc_cdp_events=True, test=True) as sb:\r\n    url = \"seleniumbase.io/apps/turnstile\"\r\n    sb.uc_open_with_reconnect(url, 2)\r\n    add_cdp_listener(sb)\r\n    click_turnstile_and_verify(sb)\r\n    sb.sleep(1)\r\n    sb.refresh()\r\n    sb.sleep(1.2)\r\n"
  },
  {
    "path": "examples/raw_uc_mode.py",
    "content": "\"\"\"SB Manager using UC Mode for evading bot-detection.\"\"\"\r\nfrom seleniumbase import SB\r\n\r\nwith SB(uc=True, test=True) as sb:\r\n    url = \"https://gitlab.com/users/sign_in\"\r\n    sb.uc_open_with_reconnect(url, 4)\r\n    sb.uc_gui_click_captcha()\r\n    sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n    sb.assert_element('label[for=\"user_login\"]')\r\n    sb.highlight('button:contains(\"Sign in\")')\r\n    sb.highlight('h1:contains(\"GitLab\")')\r\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n"
  },
  {
    "path": "examples/sb_fixture_tests.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\n# \"sb\" pytest fixture test in a method with no class\r\ndef test_sb_fixture_with_no_class(sb: BaseCase):\r\n    sb.open(\"seleniumbase.io/simple/login\")\r\n    sb.type(\"#username\", \"demo_user\")\r\n    sb.type(\"#password\", \"secret_pass\")\r\n    sb.click('a:contains(\"Sign in\")')\r\n    sb.assert_exact_text(\"Welcome!\", \"h1\")\r\n    sb.assert_element(\"img#image1\")\r\n    sb.highlight(\"#image1\")\r\n    sb.click_link(\"Sign out\")\r\n    sb.assert_text(\"signed out\", \"#top_message\")\r\n\r\n\r\n# \"sb\" pytest fixture test in a method inside a class\r\nclass Test_SB_Fixture:\r\n    def test_sb_fixture_inside_class(self, sb: BaseCase):\r\n        sb.open(\"seleniumbase.io/simple/login\")\r\n        sb.type(\"#username\", \"demo_user\")\r\n        sb.type(\"#password\", \"secret_pass\")\r\n        sb.click('a:contains(\"Sign in\")')\r\n        sb.assert_exact_text(\"Welcome!\", \"h1\")\r\n        sb.assert_element(\"img#image1\")\r\n        sb.highlight(\"#image1\")\r\n        sb.click_link(\"Sign out\")\r\n        sb.assert_text(\"signed out\", \"#top_message\")\r\n"
  },
  {
    "path": "examples/setup.cfg",
    "content": "[flake8]\n# W503 (line break before binary operator) can be ignored.\nexclude=recordings,temp\nignore=W503\n\n[nosetests]\n# nocapture=1 (Display print statements from output)\n#             (Undo this by using: \"--nologcapture\")\n# logging-level=INFO (Shorter logs than using DEBUG)\nnocapture=1\nlogging-level=INFO\n\n[behave]\nshow_skipped=false\nshow_timings=false\n"
  },
  {
    "path": "examples/shadow_root_test.py",
    "content": "\"\"\"Piercing through shadow-root elements with the \"::shadow\" selector.\r\nTo confirm that \"::shadow\" works, print text and assert exact text.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass ShadowRootTest(BaseCase):\r\n    def test_shadow_root(self):\r\n        if self.recorder_mode or not self.is_chromium():\r\n            self.open_if_not_url(\"about:blank\")\r\n            print(\"\\n  Unsupported mode for this test.\")\r\n            self.skip(\"Unsupported mode for this test.\")\r\n        self.open(\"https://seleniumbase.io/other/shadow_dom\")\r\n        print(\"\")\r\n        self.click(\"button.tab_1\")\r\n        print(self.get_text(\"fancy-tabs::shadow #panels\"))\r\n        self.assert_exact_text(\"Content Panel 1\", \"fancy-tabs::shadow #panels\")\r\n        self.click(\"button.tab_2\")\r\n        print(self.get_text(\"fancy-tabs::shadow #panels\"))\r\n        self.assert_exact_text(\"Content Panel 2\", \"fancy-tabs::shadow #panels\")\r\n        self.click(\"button.tab_3\")\r\n        print(self.get_text(\"fancy-tabs::shadow #panels\"))\r\n        self.assert_exact_text(\"Content Panel 3\", \"fancy-tabs::shadow #panels\")\r\n"
  },
  {
    "path": "examples/swag_labs_user_tests.py",
    "content": "from parameterized import parameterized\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass SwagLabsTests(BaseCase):\n    def login_to_swag_labs(self, username=\"standard_user\"):\n        \"\"\"Login to Swag Labs and verify success.\"\"\"\n        url = \"https://www.saucedemo.com\"\n        self.open(url)\n        self.wait_for_element(\"div.login_logo\")\n        if username not in self.get_text(\"#login_credentials\"):\n            self.fail(\"Invalid user for login: %s\" % username)\n        self.type(\"#user-name\", username)\n        self.type(\"#password\", \"secret_sauce\")\n        self.click('input[type=\"submit\"]')\n        self.assert_element(\"div.inventory_list\")\n        self.assert_element('div:contains(\"Sauce Labs Backpack\")')\n\n    @parameterized.expand(\n        [\n            [\"standard_user\"],\n            [\"problem_user\"],\n        ]\n    )\n    def test_swag_labs_user_flows(self, username):\n        \"\"\"This parameterized test checks basic user actions on the website.\n        It also shows you how SeleniumBase can help you catch bugs.\"\"\"\n        self.login_to_swag_labs(username=username)\n        if username == \"problem_user\":\n            print(\"\\n(This test should fail)\")\n\n        # Verify that the \"Test.allTheThings() T-Shirt\" appears on the page\n        item_name = \"Test.allTheThings() T-Shirt\"\n        self.assert_text(item_name)\n\n        # Verify that a reverse-alphabetical sort works as expected\n        self.select_option_by_value(\"select.product_sort_container\", \"za\")\n        if item_name not in self.get_text(\"div.inventory_item\"):\n            self.fail('Sort Failed! Expecting \"%s\" on top!' % item_name)\n\n        # Add the \"Test.allTheThings() T-Shirt\" to the cart\n        self.assert_exact_text(\"Add to cart\", \"button.btn_inventory\")\n        item_price = self.get_text(\"div.inventory_item_price\")\n        self.click(\"button.btn_inventory\")\n        self.assert_exact_text(\"Remove\", \"button.btn_inventory\")\n        self.assert_exact_text(\"1\", \"span.shopping_cart_badge\")\n\n        # Verify your cart\n        self.click(\"#shopping_cart_container a\")\n        self.assert_element('span:contains(\"Your Cart\")')\n        self.assert_text(item_name, \"div.inventory_item_name\")\n        self.assert_exact_text(\"1\", \"div.cart_quantity\")\n        self.assert_exact_text(\"Remove\", \"button.cart_button\")\n        self.assert_element(\"button#continue-shopping\")\n\n        # Checkout - Add info\n        self.click(\"button#checkout\")\n        self.assert_element('span:contains(\"Checkout: Your Information\")')\n        self.assert_element(\"button#cancel\")\n        self.type(\"#first-name\", \"SeleniumBase\")\n        self.type(\"#last-name\", \"Rocks\")\n        self.type(\"#postal-code\", \"01720\")\n\n        # Checkout - Overview\n        self.click(\"input#continue\")\n        self.assert_element('span:contains(\"Checkout: Overview\")')\n        self.assert_element(\"button#cancel\")\n        self.assert_text(item_name, \"div.inventory_item_name\")\n        self.assert_text(item_price, \"div.inventory_item_price\")\n        self.assert_exact_text(\"1\", \"div.cart_quantity\")\n\n        # Finish Checkout and verify that the cart is now empty\n        self.click(\"button#finish\")\n        self.assert_exact_text(\"Thank you for your order!\", \"h2\")\n        self.assert_element(\"img.pony_express\")\n        self.click(\"#shopping_cart_container a\")\n        self.assert_element_absent(\"div.inventory_item_name\")\n        self.click(\"button#continue-shopping\")\n        self.assert_element_absent(\"span.shopping_cart_badge\")\n\n    def tearDown(self):\n        self.save_teardown_screenshot()  # Only if a test fails\n        # Reset App State and Logout if the controls are present\n        try:\n            self.wait_for_ready_state_complete()\n            if self.is_element_visible(\"#react-burger-menu-btn\"):\n                self.click(\"#react-burger-menu-btn\")\n                self.wait_for_element(\"a#reset_sidebar_link\")\n            self.js_click_if_present(\"a#reset_sidebar_link\")\n            self.js_click_if_present(\"a#logout_sidebar_link\")\n        except Exception:\n            pass\n        super().tearDown()\n"
  },
  {
    "path": "examples/test_3d_apis.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--enable-3d-apis\")\n\n\nclass ThreeJSTests(BaseCase):\n    def test_animation(self):\n        if self.headless:\n            self.open_if_not_url(\"about:blank\")\n            self.skip(\"Skip this test in headless mode!\")\n        if self.is_chromium() and not self.enable_3d_apis:\n            self.get_new_driver(enable_3d_apis=True)  # --enable-3d-apis\n        url = \"https://threejs.org/examples/#webgl_animation_skinning_morph\"\n        self.open(url)\n        self.switch_to_frame(\"iframe#viewer\")\n        self.sleep(0.8)\n        self.click('button:contains(\"Wave\")')\n        self.sleep(3)\n"
  },
  {
    "path": "examples/test_apple_site.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass AppleTests(BaseCase):\n    def test_apple_developer_site_webdriver_instructions(self):\n        if self.headed:\n            self.demo_mode = True\n            self.demo_sleep = 0.5\n            self.message_duration = 2.0\n            if self.is_chromium() and not self.disable_csp:\n                self.get_new_driver(browser=self.browser, disable_csp=True)\n        if self.headless:\n            if self._multithreaded or self.undetectable or self.recorder_mode:\n                self.open_if_not_url(\"about:blank\")\n                print(\"\\n  Unsupported mode for this test.\")\n                self.skip(\"Unsupported mode for this test.\")\n            elif self.is_chromium():\n                self.get_new_driver(browser=self.browser, headless2=True)\n        self.open(\"https://developer.apple.com/search/\")\n        title = \"Testing with WebDriver in Safari\"\n        self.type('[placeholder*=\"developer.apple.com\"]', title + \"\\n\")\n        self.click(\"link=%s\" % title)\n        self.assert_element(\"nav.documentation-nav\")\n        self.assert_text(title, \"h1\")\n        self.assert_text(\"Enable WebDriver and run a test.\", \"div.abstract\")\n        if self.demo_mode:\n            self.highlight(\"div.content h2\")\n        else:\n            self.assert_element(\"div.content h2\")\n        h3 = \"div.content h3:nth-of-type(%s)\"\n        self.assert_text(\"Make Sure You Have Safari’s WebDriver\", h3 % \"1\")\n        self.assert_text(\"Get the Correct Selenium Library\", h3 % \"2\")\n        self.assert_text(\"Configure Safari to Enable WebDriver\", h3 % \"3\")\n        self.assert_text(\"Write a WebDriver Testing Suite\", h3 % \"4\")\n        self.assert_text(\"Run Your Test\", h3 % \"5\")\n"
  },
  {
    "path": "examples/test_assert_elements.py",
    "content": "\"\"\"Assert that multiple elements are present or visible:\r\nHTML Presence: assert_elements_present()\r\nHTML Visibility: assert_elements() <> assert_elements_visible()\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass ListAssertTests(BaseCase):\r\n    def test_assert_list_of_elements(self):\r\n        self.open(\"https://seleniumbase.io/demo_page\")\r\n        self.assert_elements_present(\"head\", \"style\", \"script\")\r\n        self.assert_elements(\"h1\", \"h2\", \"h3\")\r\n        my_list = [\"#myDropdown\", \"#myButton\", \"#mySlider\"]\r\n        self.assert_elements(my_list)\r\n"
  },
  {
    "path": "examples/test_calculator.py",
    "content": "\"\"\"Test the SeleniumBase Calculator App\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass CalculatorTests(BaseCase):\r\n    def test_6_times_7_plus_12_equals_54(self):\r\n        self.open(\"seleniumbase.io/apps/calculator\")\r\n        self.click('button[id=\"6\"]')\r\n        self.click(\"button#multiply\")\r\n        self.click('button[id=\"7\"]')\r\n        self.click(\"button#add\")\r\n        self.click('button[id=\"1\"]')\r\n        self.click('button[id=\"2\"]')\r\n        self.click(\"button#equal\")\r\n        self.assert_exact_text(\"54\", \"input#output\")\r\n"
  },
  {
    "path": "examples/test_canvas.py",
    "content": "\"\"\"Use SeleniumBase methods to interact with \"canvas\" elements.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass CanvasTests(BaseCase):\r\n    def get_canvas_pixel_colors_at_top_left(self):\r\n        # Return the RGB colors of the canvas's top left pixel\r\n        x = 0\r\n        y = 0\r\n        if self.browser == \"safari\":\r\n            x = 1\r\n            y = 1\r\n        color = self.execute_script(\r\n            \"return document.querySelector('canvas').getContext('2d')\"\r\n            \".getImageData(%s,%s,1,1).data;\" % (x, y)\r\n        )\r\n        if self.is_chromium():\r\n            return [color[0], color[1], color[2]]\r\n        else:\r\n            return [color[\"0\"], color[\"1\"], color[\"2\"]]\r\n\r\n    def test_canvas_click_from_center(self):\r\n        self.open(\"https://seleniumbase.io/other/canvas\")\r\n        self.assert_title_contains(\"Canvas\")\r\n        self.click_with_offset(\"canvas\", 0, 0, mark=True, center=True)\r\n        self.sleep(0.55)  # Not needed (Lets you see the alert pop up)\r\n        alert = self.switch_to_alert()\r\n        self.assert_equal(alert.text, \"You clicked on the square!\")\r\n        self.accept_alert()\r\n        self.sleep(0.55)  # Not needed (Lets you see the alert go away)\r\n        if self.browser == \"safari\" and self._reuse_session:\r\n            # Alerts can freeze Safari if reusing the browser session\r\n            self.driver.quit()\r\n\r\n    def test_click_with_offset(self):\r\n        self.open(\"https://seleniumbase.io/canvas/\")\r\n        if self.undetectable:\r\n            self.open_if_not_url(\"about:blank\")\r\n            print(\"\\n  Skip this test in undetectable mode.\")\r\n            self.skip(\"Skip this test in undetectable mode.\")\r\n        self.assert_title_contains(\"Canvas\")\r\n        self.highlight(\"canvas\")\r\n        rgb = self.get_canvas_pixel_colors_at_top_left()\r\n        self.assert_equal(rgb, [221, 242, 231])  # Looks greenish\r\n        self.click_with_offset(\"canvas\", 500, 350)\r\n        self.highlight(\"canvas\", loops=5)\r\n        rgb = self.get_canvas_pixel_colors_at_top_left()\r\n        self.assert_equal(rgb, [39, 42, 56])  # Blue by hamburger\r\n"
  },
  {
    "path": "examples/test_cdp_ad_blocking.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass CDPNetworkBlockingTests(BaseCase):\r\n    def test_cdp_network_blocking(self):\r\n        self.open(\"about:blank\")\r\n        if self._reuse_session or not self.is_chromium():\r\n            message = \"Skipping test if reusing session or not Chromium!\"\r\n            print(message)\r\n            self.skip(message)\r\n        self.execute_cdp_cmd(\"Network.enable\", {})\r\n        self.execute_cdp_cmd(\r\n            \"Network.setBlockedURLs\", {\"urls\": [\r\n                \"*.googlesyndication.com*\",\r\n                \"*.googletagmanager.com*\",\r\n                \"*.google-analytics.com*\",\r\n                \"*.amazon-adsystem.com*\",\r\n                \"*.adsafeprotected.com*\",\r\n                \"*.doubleclick.net*\",\r\n                \"*.fastclick.net*\",\r\n                \"*.snigelweb.com*\",\r\n                \"*.2mdn.net*\",\r\n            ]})\r\n        self.open(\"https://www.w3schools.com/jquery/default.asp\")\r\n        source = self.get_page_source()\r\n        self.assert_false(\"doubleclick.net\" in source)\r\n        self.assert_false(\"google-analytics.com\" in source)\r\n        self.post_message(\"Blocking was successful!\")\r\n"
  },
  {
    "path": "examples/test_checkboxes.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass CheckboxTests(BaseCase):\n    def test_checkboxes_and_radio_buttons(self):\n        self.open(\"https://seleniumbase.io/w3schools/checkboxes\")\n        self.click(\"button#runbtn\")\n        self.switch_to_frame(\"iframeResult\")\n        checkbox = \"input#vehicle2\"\n        self.assert_false(self.is_selected(checkbox))\n        self.click(checkbox)\n        self.assert_true(self.is_selected(checkbox))\n        self.open(\"https://seleniumbase.io/w3schools/radio_buttons\")\n        self.click(\"button#runbtn\")\n        self.switch_to_frame(\"iframeResult\")\n        option_button = \"input#css\"\n        self.assert_false(self.is_selected(option_button))\n        self.click(option_button)\n        self.assert_true(self.is_selected(option_button))\n"
  },
  {
    "path": "examples/test_chinese_pdf.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ChinesePdfTests(BaseCase):\n    def test_chinese_pdf(self):\n        self.open(\"data:,\")\n        pdf = \"https://seleniumbase.io/cdn/pdf/unittest_zh.pdf\"\n\n        # Get and print PDF text\n        pdf_text = self.get_pdf_text(pdf, page=2)\n        print(\"\\n\" + pdf_text)\n\n        # Assert PDF contains the expected text on Page 2\n        self.assert_pdf_text(pdf, \"个测试类\", page=2)\n\n        # Assert PDF contains the expected text on any of the pages\n        self.assert_pdf_text(pdf, \"运行单元测试\")\n        self.assert_pdf_text(pdf, \"等待测试结束后显示所有结果\")\n        self.assert_pdf_text(pdf, \"测试的执行跟方法的顺序没有关系\")\n"
  },
  {
    "path": "examples/test_chromedriver.py",
    "content": "\"\"\"This test is only for Chrome!\n(Verify that your chromedriver is compatible with your version of Chrome.)\"\"\"\nimport colorama\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ChromedriverTests(BaseCase):\n    def test_chromedriver_matches_chrome(self):\n        self.open(\"about:blank\")\n        if self.browser != \"chrome\":\n            print(\"\\n  This test is only for Chrome!\")\n            self.skip(\"This test is only for Chrome!\")\n        chrome_version = self.get_chrome_version()\n        major_chrome_version = chrome_version.split(\".\")[0]\n        chromedriver_version = self.get_chromedriver_version()\n        major_chromedriver_version = chromedriver_version.split(\".\")[0]\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n        c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n        c4 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTGREEN_EX\n        cr = colorama.Style.RESET_ALL\n        pr_chromedriver_version = c3 + chromedriver_version + cr\n        pr_chrome_version = c2 + chrome_version + cr\n        message = (\n            \"\\n\"\n            \"* Your version of chromedriver is: %s\\n\"\n            \"*\\n* And your version of Chrome is: %s\"\n            % (pr_chromedriver_version, pr_chrome_version)\n        )\n        print(message)\n        if major_chromedriver_version < major_chrome_version:\n            install_sb = (\n                \"seleniumbase get chromedriver %s\" % major_chrome_version\n            )\n            pr_install_sb = c1 + install_sb + cr\n            up_msg = \"You may want to upgrade your version of chromedriver:\"\n            up_msg = c4 + up_msg + cr\n            message = \"*\\n* %s\\n*\\n* >>> %s\" % (up_msg, pr_install_sb)\n            print(message)\n        elif major_chromedriver_version > major_chrome_version:\n            up_msg = \"You may want to upgrade your version of Chrome:\"\n            up_msg = c5 + up_msg + cr\n            up_url = c1 + \"chrome://settings/help\" + cr\n            message = \"*\\n* %s\\n*\\n* See: %s\" % (up_msg, up_url)\n            print(message)\n        else:\n            up_msg = (\n                \"Success! Your chromedriver is compatible with your Chrome!\"\n            )\n            up_msg = c1 + up_msg + cr\n            message = \"*\\n* %s\\n\" % up_msg\n            print(message)\n"
  },
  {
    "path": "examples/test_coffee_cart.py",
    "content": "\"\"\"Use SeleniumBase to test the Coffee Cart App.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass CoffeeCartTest(BaseCase):\r\n    def test_coffee_cart(self):\r\n        self.open(\"https://seleniumbase.io/coffee/\")\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.assert_element('button:contains(\"Total: $0.00\")')\r\n        self.click('div[data-sb=\"Cappuccino\"]')\r\n        self.assert_exact_text(\"cart (1)\", 'a[aria-label=\"Cart page\"]')\r\n        self.click('div[data-sb=\"Flat-White\"]')\r\n        self.assert_exact_text(\"cart (2)\", 'a[aria-label=\"Cart page\"]')\r\n        self.click('div[data-sb=\"Cafe-Latte\"]')\r\n        self.assert_exact_text(\"cart (3)\", 'a[aria-label=\"Cart page\"]')\r\n        self.click('a[aria-label=\"Cart page\"]')\r\n        self.assert_exact_text(\"Total: $53.00\", \"button.pay\")\r\n        self.click(\"button.pay\")\r\n        self.type(\"input#name\", \"Selenium Coffee\")\r\n        self.type(\"input#email\", \"test@test.test\")\r\n        self.click(\"button#submit-payment\")\r\n        self.assert_text(\"Thanks for your purchase.\", \"#app .success\")\r\n"
  },
  {
    "path": "examples/test_console_logging.py",
    "content": "\"\"\"Use SeleniumBase methods to interact with console logs.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass TestConsoleLogging(BaseCase):\r\n    def test_console_logging(self):\r\n        self.open(\"https://seleniumbase.io/demo_page\")\r\n        self.wait_for_element_visible(\"h2\")\r\n        self.start_recording_console_logs()\r\n        self.console_log_string(\"Hello World!\")\r\n        self.console_log_script('document.querySelector(\"h2\").textContent')\r\n        console_logs = [log[0] for log in self.get_recorded_console_logs()]\r\n        self.assert_in(\"Hello World!\", console_logs)\r\n        self.assert_in(\"SeleniumBase\", console_logs)\r\n"
  },
  {
    "path": "examples/test_contains_selector.py",
    "content": "\"\"\"TAG:contains(\"TEXT\") is a special, non-standard CSS Selector\r\nthat gets converted to XPath: '//TAG[contains(., \"TEXT\")]'\r\nbefore it's used by Selenium calls. Also part of jQuery.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass ContainsSelectorTests(BaseCase):\r\n    def test_contains_selector(self):\r\n        self.open(\"https://xkcd.com/2207/\")\r\n        self.assert_element('div.box div:contains(\"Math Work\")')\r\n        self.click('a:contains(\"Next\")')\r\n        self.assert_element('div div:contains(\"Drone Fishing\")')\r\n"
  },
  {
    "path": "examples/test_cycle_elements.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass CycleTests(BaseCase):\r\n    def test_cycle_elements_with_tab_and_press_enter(self):\r\n        \"\"\"\r\n        Test pressing the tab key to cycle through elements.\r\n        Then click on the active element and verify actions.\r\n        This can all be performed by using a single command.\r\n        The \"\\t\" is the tab key. The \"\\n\" is the RETURN key.\r\n        \"\"\"\r\n        self.open(\"seleniumbase.io/demo_page\")\r\n        self.assert_text(\"This Text is Green\", \"#pText\")\r\n        self.send_keys(\"html\", \"\\t\\t\\t\\t\\n\")\r\n        self.assert_text(\"This Text is Purple\", \"#pText\")\r\n"
  },
  {
    "path": "examples/test_decryption.py",
    "content": "\"\"\"This test demonstrates the use of password encryption/decryption.\n(Technically considered to be obfuscation/unobfuscation.)\"\"\"\nfrom seleniumbase import BaseCase\nfrom seleniumbase import encryption\nBaseCase.main(__name__, __file__)\n\n\nclass DecryptionTests(BaseCase):\n    def test_decrypt_password(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n\n        encrypted_password = \"$^*ENCRYPT=S3BDTAdCWzMmKEY8Gjg=?&#$\"\n        print(\"\\nEncrypted Password = %s\" % encrypted_password)\n        password = encryption.decrypt(encrypted_password)\n        print(\"Decrypted Password = %s\" % password)\n        self.type(\"#password\", password)\n\n        self.click('input[type=\"submit\"]')\n        self.assert_element(\"#inventory_container\")\n        self.assert_element('div:contains(\"Sauce Labs Backpack\")')\n"
  },
  {
    "path": "examples/test_deferred_asserts.py",
    "content": "\"\"\"This test shows the use of SeleniumBase deferred asserts.\nDeferred asserts won't raise exceptions from failures until\nprocess_deferred_asserts() is called, or the test completes.\"\"\"\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass DeferredAssertTests(BaseCase):\n    @pytest.mark.expected_failure\n    def test_deferred_asserts(self):\n        self.open(\"https://xkcd.com/993/\")\n        self.wait_for_element(\"#comic\")\n        print(\"\\n(This test should fail)\")\n        self.deferred_assert_element('img[alt=\"Brand Identity\"]')\n        self.deferred_assert_element('img[alt=\"Rocket Ship\"]')  # Will Fail\n        self.deferred_assert_element(\"#comicmap\")\n        self.deferred_assert_text(\"Fake Item\", \"ul.comicNav\")  # Will Fail\n        self.deferred_assert_text(\"Random\", \"ul.comicNav\")\n        self.deferred_assert_element('a[name=\"Super Fake !!!\"]')  # Will Fail\n        self.deferred_assert_exact_text(\"Brand Identity\", \"#ctitle\")\n        self.deferred_assert_exact_text(\"Fake Food\", \"#comic\")  # Will Fail\n        self.process_deferred_asserts()\n"
  },
  {
    "path": "examples/test_demo_site.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass DemoSiteTests(BaseCase):\n    def test_demo_site(self):\n        # Open a web page in the active browser window\n        self.open(\"https://seleniumbase.io/demo_page\")\n\n        # Assert the title of the current web page\n        self.assert_title(\"Web Testing Page\")\n\n        # Assert that an element is visible on the page\n        self.assert_element(\"tbody#tbodyId\")\n\n        # Assert that a text substring appears in an element\n        self.assert_text(\"Demo Page\", \"h1\")\n\n        # Type text into various text fields and then assert\n        self.type(\"#myTextInput\", \"This is Automated\")\n        self.type(\"textarea.area1\", \"Testing Time!\\n\")\n        self.type('[name=\"preText2\"]', \"Typing Text!\")\n        self.assert_text(\"This is Automated\", \"#myTextInput\")\n        self.assert_text(\"Testing Time!\\n\", \"textarea.area1\")\n        self.assert_text(\"Typing Text!\", '[name=\"preText2\"]')\n\n        # Hover & click a dropdown element and assert results\n        self.assert_text(\"Automation Practice\", \"h3\")\n        try:\n            self.hover_and_click(\"#myDropdown\", \"#dropOption2\", timeout=1)\n        except Exception:\n            # Someone probably moved the mouse while the test ran\n            self.hover_and_js_click(\"#myDropdown\", \"#dropOption2\")\n        self.assert_text(\"Link Two Selected\", \"h3\")\n\n        # Click a button and then verify the expected results\n        self.assert_text(\"This Text is Green\", \"#pText\")\n        self.click('button:contains(\"Click Me\")')\n        self.assert_text(\"This Text is Purple\", \"#pText\")\n\n        # Assert that the given SVG is visible on the page\n        self.assert_element('svg[name=\"svgName\"]')\n\n        # Verify that a slider control updates a progress bar\n        self.assert_element('progress[value=\"50\"]')\n        self.set_value(\"input#mySlider\", \"100\")\n        self.assert_element('progress[value=\"100\"]')\n\n        # Verify that a \"select\" option updates a meter bar\n        self.assert_element('meter[value=\"0.25\"]')\n        self.select_option_by_text(\"#mySelect\", \"Set to 75%\")\n        self.assert_element('meter[value=\"0.75\"]')\n\n        # Assert an element located inside an iframe\n        self.assert_false(self.is_element_visible(\"img\"))\n        self.switch_to_frame(\"#myFrame1\")\n        self.assert_true(self.is_element_visible(\"img\"))\n        self.switch_to_default_content()\n\n        # Assert text located inside an iframe\n        self.assert_false(self.is_text_visible(\"iFrame Text\"))\n        self.switch_to_frame(\"#myFrame2\")\n        self.assert_true(self.is_text_visible(\"iFrame Text\"))\n        self.switch_to_default_content()\n\n        # Verify that clicking a radio button selects it\n        self.assert_false(self.is_selected(\"#radioButton2\"))\n        self.click(\"#radioButton2\")\n        self.assert_true(self.is_selected(\"#radioButton2\"))\n\n        # Verify that clicking a checkbox makes it selected\n        self.assert_element_not_visible(\"img#logo\")\n        self.assert_false(self.is_selected(\"#checkBox1\"))\n        self.click(\"#checkBox1\")\n        self.assert_true(self.is_selected(\"#checkBox1\"))\n        self.assert_element(\"img#logo\")\n\n        # Verify clicking on multiple elements with one call\n        self.assert_false(self.is_selected(\"#checkBox2\"))\n        self.assert_false(self.is_selected(\"#checkBox3\"))\n        self.assert_false(self.is_selected(\"#checkBox4\"))\n        self.click_visible_elements(\"input.checkBoxClassB\")\n        self.assert_true(self.is_selected(\"#checkBox2\"))\n        self.assert_true(self.is_selected(\"#checkBox3\"))\n        self.assert_true(self.is_selected(\"#checkBox4\"))\n\n        # Verify that clicking an iframe checkbox selects it\n        self.assert_false(self.is_element_visible(\".fBox\"))\n        self.switch_to_frame(\"#myFrame3\")\n        self.assert_true(self.is_element_visible(\".fBox\"))\n        self.assert_false(self.is_selected(\".fBox\"))\n        self.click(\".fBox\")\n        self.assert_true(self.is_selected(\".fBox\"))\n        self.switch_to_default_content()\n\n        # Verify Drag and Drop\n        self.assert_element_not_visible(\"div#drop2 img#logo\")\n        self.drag_and_drop(\"img#logo\", \"div#drop2\")\n        self.assert_element(\"div#drop2 img#logo\")\n\n        # Assert link text\n        self.assert_link_text(\"seleniumbase.com\")\n        self.assert_link_text(\"SeleniumBase on GitHub\")\n        self.assert_link_text(\"seleniumbase.io\")\n\n        # Click link text\n        self.click_link(\"SeleniumBase Demo Page\")\n\n        # Assert exact text\n        self.assert_exact_text(\"Demo Page\", \"h1\")\n\n        # Highlight a page element (Also asserts visibility)\n        self.highlight(\"h2\")\n\n        # Actions with Demo Mode enabled\n        if self.headed:\n            self.activate_demo_mode()\n        self.type(\"input\", \"Have a Nice Day!\")\n        self.assert_text(\"SeleniumBase\", \"h2\")\n"
  },
  {
    "path": "examples/test_detect_404s.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass BrokenLinkTests(BaseCase):\r\n    def test_link_checking(self):\r\n        self.open(\"https://seleniumbase.io/other/broken_page.html\")\r\n        print(\"\\n(This test should fail)\")\r\n        self.assert_no_404_errors()\r\n"
  },
  {
    "path": "examples/test_docs_site.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass DocsSiteTests(BaseCase):\r\n    def test_docs(self):\r\n        self.open(\"https://seleniumbase.io/help_docs/customizing_test_runs/\")\r\n        self.assert_text(\"Command Line Options\", \"h1\")\r\n        self.js_click('a[href$=\"/examples/example_logs/ReadMe/\"]')\r\n        self.assert_text(\"Dashboard / Reports\", \"h1\")\r\n        self.js_click('a[href$=\"/seleniumbase/console_scripts/ReadMe/\"]')\r\n        self.assert_text(\"Console Scripts\", \"h1\")\r\n        self.js_click('a[href$=\"/help_docs/syntax_formats/\"]')\r\n        self.assert_text(\"Syntax Formats\", \"h1\")\r\n        self.js_click('a[href$=\"/recorder_mode/\"]')\r\n        self.assert_text(\"Recorder Mode\", \"h1\")\r\n        self.js_click('a[href$=\"/method_summary/\"]')\r\n        self.assert_text(\"API Reference\", \"h1\")\r\n        self.js_click('a[href$=\"/uc_mode/\"]')\r\n        self.assert_text(\"UC Mode\", \"h1\")\r\n        self.js_click('img[alt=\"logo\"]')\r\n        self.assert_text(\"SeleniumBase\", \"h1\")\r\n"
  },
  {
    "path": "examples/test_double_click.py",
    "content": "\"\"\"Test double_click() after switching into iframes.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass DoubleClickTests(BaseCase):\n    def test_switch_to_frame_and_double_click(self):\n        self.open(\"https://seleniumbase.io/w3schools/double_click\")\n        self.assert_title(\"Double Click Testing\")\n        self.click(\"button#runbtn\")\n        self.switch_to_frame(\"iframe#iframeResult\")\n        self.double_click('[ondblclick=\"myFunction()\"]')\n        self.assert_text(\"Hello World\", \"#demo\")\n\n    def test_switch_to_frame_of_element_and_double_click(self):\n        self.open(\"https://seleniumbase.io/w3schools/double_click\")\n        self.assert_title(\"Double Click Testing\")\n        self.click(\"button#runbtn\")\n        self.switch_to_frame_of_element('[ondblclick=\"myFunction()\"]')\n        self.double_click('[ondblclick=\"myFunction()\"]')\n        self.assert_text(\"Hello World\", \"#demo\")\n"
  },
  {
    "path": "examples/test_download_files.py",
    "content": "\"\"\"Use SeleniumBase to download files and verify.\"\"\"\r\nimport math\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass DownloadTests(BaseCase):\r\n    def test_download_chromedriver_notes(self):\r\n        if self._multithreaded:\r\n            self.open_if_not_url(\"about:blank\")\r\n            self.skip(\"Skipping test in multi-threaded mode.\")\r\n        self.open(\"https://chromedriver.chromium.org/downloads\")\r\n        notes_file = \"notes.txt\"\r\n        notes_link = (\r\n            \"https://chromedriver.storage.googleapis.com\"\r\n            \"/101.0.4951.41/%s\" % notes_file\r\n        )\r\n        self.download_file(notes_link)\r\n        self.assert_downloaded_file(notes_file)\r\n        notes_path = self.get_path_of_downloaded_file(notes_file)\r\n        with open(notes_path, \"r\") as f:\r\n            notes_data = f.read()\r\n        self.assert_true(len(notes_data) > 100)  # Verify file not empty\r\n        text = \"Switching to nested frame fails with chrome/chromedriver 100\"\r\n        self.assert_true(text in notes_data)  # Verify file has expected data\r\n\r\n    def test_download_files_from_pypi(self):\r\n        self.open(\"https://pypi.org/project/sbvirtualdisplay/#files\")\r\n        self.assert_element(\"span#pip-command\")\r\n        self.assert_text(\"Download files\", \"div#files h2.page-title\")\r\n        self.assert_text(\"Download files\", \"a#files-tab\")\r\n        pkg_header = self.get_text(\"h1.package-header__name\").strip()\r\n        pkg_name = pkg_header.replace(\" \", \"-\")\r\n        whl_file = pkg_name + \"-py3-none-any.whl\"\r\n        tar_gz_file = pkg_name + \".tar.gz\"\r\n\r\n        # Click the links to download the files into: \"./downloaded_files/\"\r\n        # (If using Safari, IE, or Chromium Guest Mode: download directly.)\r\n        # (The default Downloads Folder can't be changed when using those.)\r\n        # (Use self.get_browser_downloads_folder() to get the folder used.)\r\n        whl_selector = 'div#files a[href$=\"%s\"]' % whl_file\r\n        tar_selector = 'div#files a[href$=\"%s\"]' % tar_gz_file\r\n        if (\r\n            self.browser == \"safari\"\r\n            or self.browser == \"ie\"\r\n            or self.browser == \"edge\"\r\n            or (self.is_chromium() and self.guest_mode)\r\n            or (self.is_chromium() and (self.headless or self.headless2))\r\n        ):\r\n            whl_href = self.get_attribute(whl_selector, \"href\")\r\n            tar_href = self.get_attribute(tar_selector, \"href\")\r\n            self.download_file(whl_href)\r\n            self.download_file(tar_href)\r\n        else:\r\n            self.js_click(whl_selector)  # Download the \"whl\" file\r\n            self.sleep(0.1)\r\n            self.js_click(tar_selector)  # Download the \"tar\" file\r\n\r\n        # Verify that the downloaded files appear in the [Downloads Folder]\r\n        # (This only guarantees that the exact file name is in the folder.)\r\n        # (This does not guarantee that the downloaded files are complete.)\r\n        # (Later, we'll check that the files were downloaded successfully.)\r\n        self.assert_downloaded_file(whl_file)\r\n        self.assert_downloaded_file(tar_gz_file)\r\n\r\n        self.sleep(1)  # Add more time to make sure downloads have completed\r\n\r\n        # Get the actual size of the downloaded files (in bytes)\r\n        whl_path = self.get_path_of_downloaded_file(whl_file)\r\n        with open(whl_path, \"rb\") as f:\r\n            whl_file_bytes = len(f.read())\r\n        print(\"\\n%s | Download = %s bytes.\" % (whl_file, whl_file_bytes))\r\n        tar_gz_path = self.get_path_of_downloaded_file(tar_gz_file)\r\n        with open(tar_gz_path, \"rb\") as f:\r\n            tar_gz_file_bytes = len(f.read())\r\n        print(\"%s | Download = %s bytes.\" % (tar_gz_file, tar_gz_file_bytes))\r\n\r\n        # Check to make sure the downloaded files are not empty or too small\r\n        self.assert_true(whl_file_bytes > 5000)\r\n        self.assert_true(tar_gz_file_bytes > 5000)\r\n\r\n        # Get file sizes in kB to compare actual values with displayed values\r\n        whl_file_kb = whl_file_bytes / 1000.0\r\n        whl_line_fi = self.get_text('a[href$=\".whl\"]').strip()\r\n        whl_line = self.get_text('div.file:contains(\"%s\")' % whl_line_fi)\r\n        whl_display_kb = float(whl_line.split(\"(\")[1].split(\" \")[0])\r\n        tar_gz_file_kb = tar_gz_file_bytes / 1000.0\r\n        tar_gz_line_fi = self.get_text('a[href$=\".tar.gz\"]').strip()\r\n        tar_gz_line = self.get_text('div.file:contains(\"%s\")' % tar_gz_line_fi)\r\n        tar_gz_display_kb = float(tar_gz_line.split(\"(\")[1].split(\" \")[0])\r\n\r\n        # Verify downloaded files are the correct size (account for rounding)\r\n        self.assert_true(\r\n            abs(math.floor(whl_file_kb) - math.floor(whl_display_kb)) < 2\r\n        )\r\n        self.assert_true(\r\n            abs(math.floor(tar_gz_file_kb) - math.floor(tar_gz_display_kb)) < 2\r\n        )\r\n\r\n        # Delete the downloaded files from the [Downloads Folder]\r\n        self.delete_downloaded_file_if_present(whl_file)\r\n        self.delete_downloaded_file_if_present(tar_gz_file)\r\n\r\n        # Verify that the downloaded files have been successfully deleted\r\n        self.assert_false(self.is_downloaded_file_present(whl_file))\r\n        self.assert_false(self.is_downloaded_file_present(tar_gz_file))\r\n"
  },
  {
    "path": "examples/test_download_images.py",
    "content": "\"\"\"Use SeleniumBase to download images and verify.\"\"\"\r\nimport os\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass DownloadImages(BaseCase):\r\n    def test_download_images_directly(self):\r\n        if self._multithreaded:\r\n            self.open_if_not_url(\"about:blank\")\r\n            self.skip(\"Skipping test in multi-threaded mode.\")\r\n        self.open(\"seleniumbase.io/examples/chart_maker/ReadMe\")\r\n        img_elements_with_src = self.find_elements(\"img[src]\")\r\n        unique_src_values = []\r\n        for img in img_elements_with_src:\r\n            src = img.get_attribute(\"src\")\r\n            if src not in unique_src_values:\r\n                unique_src_values.append(src)\r\n        print()\r\n        for src in unique_src_values:\r\n            if src.split(\".\")[-1] not in [\"png\", \"jpg\", \"jpeg\"]:\r\n                continue\r\n            self.download_file(src)  # Goes to downloaded_files/\r\n            filename = src.split(\"/\")[-1]\r\n            self.assert_downloaded_file(filename)\r\n            folder = \"downloaded_files\"\r\n            file_path = os.path.join(folder, filename)\r\n            print(file_path)\r\n\r\n    def test_download_images_via_screenshot(self):\r\n        if self.recorder_mode:\r\n            self.open(\"about:blank\")\r\n            self.skip(\"Skipping test in Recorder Mode.\")\r\n        self.open(\"seleniumbase.io/error_page/\")\r\n        img_elements_with_src = self.find_elements(\"img[src]\")\r\n        unique_src_values = []\r\n        for img in img_elements_with_src:\r\n            src = img.get_attribute(\"src\")\r\n            if src not in unique_src_values:\r\n                unique_src_values.append(src)\r\n        print()\r\n        count = 0\r\n        for src in unique_src_values:\r\n            self.open(src)\r\n            if not self.headless and not self.headless2:\r\n                self.sleep(0.3)\r\n            image = self.find_element(\"img\")\r\n            if src.startswith(\"data:\") or \";base64\" in src:\r\n                # Special Cases: SVGs, etc. Convert to PNG.\r\n                count += 1\r\n                filename = \"svg_image_%s.png\" % count\r\n            else:\r\n                filename = src.split(\"/\")[-1]\r\n            folder = \"downloaded_files\"\r\n            file_path = os.path.join(folder, filename)\r\n            image.screenshot(file_path)\r\n            self.assert_downloaded_file(filename)\r\n            print(file_path)\r\n"
  },
  {
    "path": "examples/test_drag_and_drop.py",
    "content": "\"\"\"Test drag_and_drop() on different pages.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass DragAndDropTests(BaseCase):\n    def test_drag_and_drop(self):\n        self.open(\"https://seleniumbase.io/other/drag_and_drop\")\n        self.assert_element_not_visible(\"#div1 img#drag1\")\n        self.drag_and_drop(\"#drag1\", \"#div1\")\n        self.assert_element(\"#div1 img#drag1\")\n        self.sleep(0.8)\n\n    def test_w3schools_drag_and_drop(self):\n        self.open(\"https://seleniumbase.io/w3schools/drag_drop\")\n        self.assert_url_contains(\"drag_drop\")\n        self.click(\"button#runbtn\")\n        self.switch_to_frame(\"iframeResult\")\n        self.assert_element_not_visible(\"#div1 img#drag1\")\n        self.drag_and_drop(\"#drag1\", \"#div1\")\n        self.assert_element(\"#div1 img#drag1\")\n        self.sleep(0.8)\n"
  },
  {
    "path": "examples/test_error_page.py",
    "content": "\"\"\"Test an error page with the \"highlight() command, which uses a\nJavaScript animation to point out page objects that are found.\nIf an element isn't visible, the test fails with an exception.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ErrorPageTests(BaseCase):\n    def test_error_page(self):\n        self.open(\"https://seleniumbase.io/error_page/\")\n        self.highlight('img[alt=\"500 Error\"]')\n        self.highlight(\"img#parallax_octocat\")\n        self.highlight(\"#parallax_error_text\")\n        self.highlight('img[alt*=\"404\"]')\n        self.highlight(\"img#octobi_wan_catnobi\")\n        self.highlight(\"img#speeder\")\n        self.save_screenshot_after_test = True\n"
  },
  {
    "path": "examples/test_event_firing.py",
    "content": "\"\"\"Test EventFiringWebDriver with AbstractEventListener\"\"\"\nfrom selenium.webdriver.support.events import EventFiringWebDriver\nfrom selenium.webdriver.support.events import AbstractEventListener\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyListener(AbstractEventListener):\n    def before_navigate_to(self, url, driver):\n        print(\"Before navigating to: %s\" % url)\n\n    def after_navigate_to(self, url, driver):\n        print(\"After navigating to: %s\" % url)\n\n    def before_find(self, by, value, driver):\n        print('Before find \"%s\" (by = %s)' % (value, by))\n\n    def after_find(self, by, value, driver):\n        print('After find \"%s\" (by = %s)' % (value, by))\n\n    def before_click(self, element, driver):\n        print('Before clicking on element with text: \"%s\"' % element.text)\n\n    def after_click(self, element, driver):\n        print(\"Click complete!\")\n\n\nclass EventFiringTests(BaseCase):\n    def test_event_firing_webdriver(self):\n        self.driver = EventFiringWebDriver(self.driver, MyListener())\n        print(\"\\n* EventFiringWebDriver example *\")\n        self.open(\"https://xkcd.com/1862/\")\n        self.click(\"link=About\")\n        self.open(\"https://xkcd.com/1820/\")\n        self.assert_text(\"Security Advice\", \"#ctitle\")\n        self.click('a:contains(\"Next >\")')\n        self.assert_text(\"Incinerator\", \"#ctitle\")\n        self.click('a[rel=\"next\"]')\n        self.assert_text(\"Existential Bug Reports\", \"#ctitle\")\n"
  },
  {
    "path": "examples/test_fail.py",
    "content": "\"\"\" This test fails on purpose to demonstrate\n    the logging capabilities of SeleniumBase.\n    >>> pytest test_fail.py --html=report.html\n    This creates ``report.html`` with details.\n    (Also find log files in ``latest_logs/``)\"\"\"\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass FailingTests(BaseCase):\n    @pytest.mark.expected_failure\n    def test_find_army_of_robots_on_xkcd_desert_island(self):\n        self.open(\"https://xkcd.com/731/\")\n        print(\"\\n(This test should fail)\")\n        self.assert_element(\"div#ARMY_OF_ROBOTS\", timeout=1)\n"
  },
  {
    "path": "examples/test_geolocation.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass TestGeolocation(BaseCase):\n    def tearDown(self):\n        self.save_teardown_screenshot()  # If test fails, or if \"--screenshot\"\n        if self.is_chromium() and not self._multithreaded:\n            # Reset Permissions and GeolocationOverride\n            try:\n                self.open(\"about:blank\")\n                self.execute_cdp_cmd(\"Emulation.setGeolocationOverride\", {})\n                self.execute_cdp_cmd(\"Browser.resetPermissions\", {})\n            except Exception:\n                pass\n        super().tearDown()\n\n    def test_geolocation(self):\n        self.open(\"about:blank\")\n        if self._multithreaded:\n            self.skip(\"Skipping test in multi-threaded mode.\")\n        if not self.is_chromium():\n            print(\"\\n* execute_cdp_cmd() is only for Chromium browsers\")\n            self.skip(\"execute_cdp_cmd() is only for Chromium browsers\")\n        self.execute_cdp_cmd(\n            \"Browser.grantPermissions\",\n            {\n                \"origin\": \"https://www.randymajors.org/\",\n                \"permissions\": [\"geolocation\"],\n            },\n        )\n        self.execute_cdp_cmd(\n            \"Emulation.setGeolocationOverride\",\n            {\n                \"latitude\": 48.87645,\n                \"longitude\": 2.26340,\n                \"accuracy\": 100,\n            },\n        )\n        self.open(\"https://www.randymajors.org/what-time-zone-am-i-in\")\n        self.ad_block()\n        self.assert_text(\"Paris, France\", \"#statecountrylabel\")\n        self.assert_text(\"Central European Standard Time\", \"#currentlabel\")\n        self.save_screenshot_to_logs()\n        if self.headed:\n            self.sleep(4)\n"
  },
  {
    "path": "examples/test_get_coffee.py",
    "content": "\"\"\"Use SeleniumBase to get coffee\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass GetCoffeeTest(BaseCase):\r\n    def test_get_coffee(self):\r\n        self.open(\"https://seleniumbase.io/coffee/\")\r\n        self.assert_title(\"Coffee Cart\")\r\n        self.assert_exact_text(\"cart (0)\", 'a[aria-label=\"Cart page\"]')\r\n        self.assert_element('div[data-sb=\"Mocha\"]')\r\n        self.click('div[data-sb=\"Mocha\"]')\r\n        self.assert_link_text(\"cart (1)\")\r\n        self.click_link_text(\"cart (1)\")\r\n        self.assert_exact_text(\"Total: $8.00\", \"button.pay\")\r\n        self.click(\"button.pay\")\r\n        self.type(\"input#name\", \"Selenium Coffee\")\r\n        self.type(\"input#email\", \"test@test.test\")\r\n        self.click(\"button#submit-payment\")\r\n        self.assert_text(\"Thanks\", \"#app div.success\")\r\n"
  },
  {
    "path": "examples/test_get_locale_code.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass LocaleTests(BaseCase):\n    def test_get_locale_code(self):\n        self.open(\"about:blank\")\n        locale_code = self.get_locale_code()\n        message = '\\nLocale Code = \"%s\"' % locale_code\n        print(message)\n        self.set_messenger_theme(theme=\"flat\", location=\"top_center\")\n        self.post_message(message, duration=4)\n"
  },
  {
    "path": "examples/test_get_pdf_text.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass PdfTests(BaseCase):\n    def test_get_pdf_text(self):\n        self.open(\"data:,\")\n        pdf = (\n            \"https://nostarch.com/download/\"\n            \"Automate_the_Boring_Stuff_sample_ch17.pdf\"\n        )\n        pdf_text = self.get_pdf_text(pdf, page=1)\n        print(\"\\n\" + pdf_text)\n"
  },
  {
    "path": "examples/test_get_swag.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_swag_labs(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n        self.assert_element(\"div.inventory_list\")\n        self.click('button[name*=\"backpack\"]')\n        self.click(\"#shopping_cart_container a\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click(\"button#checkout\")\n        self.type(\"input#first-name\", \"SeleniumBase\")\n        self.type(\"input#last-name\", \"Automation\")\n        self.type(\"input#postal-code\", \"77123\")\n        self.click(\"input#continue\")\n        self.click(\"button#finish\")\n        self.assert_text(\"Thank you for your order!\")\n"
  },
  {
    "path": "examples/test_get_user_agent.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass UserAgentTests(BaseCase):\n    def test_get_user_agent(self):\n        self.open(\"data:,\")\n        user_agent = self.get_user_agent()\n        print('\\nUser Agent = \"%s\"' % user_agent)\n        self.set_messenger_theme(theme=\"flat\", location=\"top_center\")\n        self.post_message(user_agent, duration=4)\n"
  },
  {
    "path": "examples/test_hack_search.py",
    "content": "\"\"\" Testing the \"self.set_attribute()\" and \"self.set_attributes()\" methods\n    to modify a Google search into becoming a Bing search.\n    set_attribute() -> Modifies the attribute of the first matching element.\n    set_attributes() -> Modifies the attribute of all matching elements. \"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass HackingTests(BaseCase):\n    def test_hack_search(self):\n        if self.headless or self.browser != \"chrome\":\n            self.open_if_not_url(\"about:blank\")\n            self.skip('Skip test if headless or not chrome.')\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.open(\"https://google.com/ncr\")\n        self.hide_elements(\"iframe\")\n        self.assert_element('[title=\"Search\"]')\n        self.sleep(0.5)\n        self.set_attribute('[action=\"/search\"]', \"action\", \"//bing.com/search\")\n        self.set_attributes('[value=\"Google Search\"]', \"value\", \"Bing Search\")\n        self.type('[title=\"Search\"]', \"SeleniumBase GitHub Page URL\")\n        self.sleep(0.5)\n        self.js_click('[value=\"Bing Search\"]')\n        self.highlight(\"h1.b_logo\", loops=8)\n        source = self.get_page_source()\n        self.assert_true(\"github.com/seleniumbase/SeleniumBase\" in source)\n        self.click('a:contains(\"seleniumbase/SeleniumBase\")')\n        self.js_click('a[title=\"examples\"]')\n        self.highlight('#repo-content-turbo-frame')\n        self.js_click('a[title=\"test_hack_search.py\"]')\n        self.assert_text(\"test_hack_search.py\", \"#file-name-id-wide\")\n        self.highlight(\"#file-name-id-wide\")\n"
  },
  {
    "path": "examples/test_highlight_elements.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass HighlightTest(BaseCase):\n    def test_highlight_inputs(self):\n        self.open(\"https://seleniumbase.io/demo_page\")\n        if self.headed:\n            self.highlight_elements(\"input\", loops=2)  # Default: 4\n        else:\n            self.highlight_elements(\"input\", loops=1, limit=3)\n"
  },
  {
    "path": "examples/test_image_saving.py",
    "content": "\"\"\" Image-saving with \"save_element_as_image_file()\".\n\n    Also shown are ways of ordering tests. (Currently commented out)\n    For ordering tests, add the marker \"@pytest.mark.run(order=NUM)\"\n        before a test definition or class definition.\n    This changes the global test order when running \"pytest\".\n    Eg: If you want a test to always run first before any test\n        from all discovered files, add \"@pytest.mark.run(order=0)\".\n    For local class/module test-ordering, name your tests\n        using alphabetical order to set the order desired.\n    Eg: \"def test_AAAAA\" will run before \"def test_ZZZZZ\".\n    You can also add in numbers to force a specific order.\n    Eg: \"def test_1_ZZZ\" will run before \"def test_2_AAA\".\n\"\"\"\nimport os\n# import pytest  # For ordering tests globally with @pytest.mark.run()\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ImageTests(BaseCase):\n    # @pytest.mark.run(order=1)\n    def test_1_save_element_as_image_file(self):\n        \"\"\"Pull an image from a website and save it as a PNG file.\"\"\"\n        self.open(\"https://xkcd.com/1117/\")\n        selector = \"#comic\"\n        file_name = \"comic.png\"\n        folder = \"images_exported\"\n        self.save_element_as_image_file(selector, file_name, folder)\n        file_path = os.path.join(folder, file_name)\n        self.assert_true(os.path.exists(file_path))\n        print('\\n\"%s\" was saved!' % file_path)\n\n    # @pytest.mark.run(order=2)\n    def test_2_add_text_overlay_to_image(self):\n        \"\"\"Add a text overlay to an image.\"\"\"\n        self.open(\"https://xkcd.com/1117/\")\n        selector = \"#comic\"\n        file_name = \"image_overlay.png\"\n        folder = \"images_exported\"\n        overlay_text = 'This is an XKCD comic!\\nTitle: \"My Sky\"'\n        self.save_element_as_image_file(\n            selector, file_name, folder, overlay_text\n        )\n        file_path = os.path.join(folder, file_name)\n        self.assert_true(os.path.exists(file_path))\n        print('\\n\"%s\" was saved!' % file_path)\n\n    # @pytest.mark.run(order=3)\n    def test_3_add_text_overlay_to_page_section(self):\n        \"\"\"Add a text overlay to a section of a page.\"\"\"\n        self.open(\"https://xkcd.com/2200/\")\n        selector = \"#middleContainer\"\n        file_name = \"section_overlay.png\"\n        folder = \"images_exported\"\n        overlay_text = (\n            \"Welcome to %s\\n\"\n            \"This is a comment added to the image.\\n\"\n            \"Unreachable states come from logic errors.\"\n            % self.get_current_url()\n        )\n        self.save_element_as_image_file(\n            selector, file_name, folder, overlay_text\n        )\n        file_path = os.path.join(folder, file_name)\n        self.assert_true(os.path.exists(file_path))\n        print('\\n\"%s\" was saved!' % file_path)\n\n    # @pytest.mark.run(order=4)\n    def test_4_add_text_overlay_to_full_page(self):\n        \"\"\"Add a text overlay to a full page.\"\"\"\n        self.open(\"https://xkcd.com/1922/\")\n        self.remove_element(\"#bottom\")\n        selector = \"body\"\n        file_name = \"page_overlay.png\"\n        folder = \"images_exported\"\n        overlay_text = \"A text overlay on %s\" % self.get_current_url()\n        self.save_element_as_image_file(\n            selector, file_name, folder, overlay_text\n        )\n        file_path = os.path.join(folder, file_name)\n        self.assert_true(os.path.exists(file_path))\n        print('\\n\"%s\" was saved!' % file_path)\n"
  },
  {
    "path": "examples/test_inspect_html.py",
    "content": "\"\"\"Uses the SeleniumBase implementation of HTML-Inspector to inspect the HTML.\nSee https://github.com/philipwalton/html-inspector for more details.\n(Only works on Chrome and Chromium-based browsers.)\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass HtmlInspectorTests(BaseCase):\n    def test_html_inspector(self):\n        self.open(\"https://xkcd.com/1144/\")\n        self.inspect_html()\n"
  },
  {
    "path": "examples/test_login.py",
    "content": "\"\"\"A SeleniumBase test for verifying Login functionality on Swag Labs.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass SwagLabsLoginTests(BaseCase):\n    def login_to_swag_labs(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.wait_for_element(\"div.login_logo\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\")\n        self.click('input[type=\"submit\"]')\n\n    def test_swag_labs_login(self):\n        self.login_to_swag_labs()\n        self.assert_element(\"div.inventory_list\")\n        self.assert_element('.inventory_item:contains(\"Backpack\")')\n        self.js_click(\"a#logout_sidebar_link\")\n        self.assert_element(\"div#login_button_container\")\n"
  },
  {
    "path": "examples/test_markers.py",
    "content": "\"\"\" These tests demonstrate pytest marker use for finding and running tests.\n\n    Usage examples from this file:\n        pytest -v -m marker_test_suite                 # Runs A, B, C, D\n        pytest -v -m marker1                           # Runs A\n        pytest -v -m marker2                           # Runs B, C\n        pytest -v -m marker3                           # Runs C\n        pytest test_markers.py -v -m \"not marker2\"     # Runs A, D\n\n    (The \"-v\" will display the names of tests as they run.)\n    (Add \"--collect-only\" to display names of tests without running them.)\"\"\"\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n@pytest.mark.marker_test_suite\nclass MarkerTestSuite(BaseCase):\n    @pytest.mark.marker1\n    def test_A(self):\n        self.open(\"https://xkcd.com/1319/\")\n        self.assert_text(\"Automation\", \"div#ctitle\")\n\n    @pytest.mark.marker2\n    def test_B(self):\n        self.open(\"https://www.xkcd.com/1700/\")\n        self.assert_text(\"New Bug\", \"div#ctitle\")\n\n    @pytest.mark.marker2\n    @pytest.mark.marker3  # Tests can have multiple markers\n    def test_C(self):\n        self.open(\"https://xkcd.com/844/\")\n        self.assert_text(\"Good Code\", \"div#ctitle\")\n\n    def test_D(self):\n        self.open(\"https://xkcd.com/2021/\")\n        self.assert_text(\"Software Development\", \"div#ctitle\")\n"
  },
  {
    "path": "examples/test_mfa_login.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass TestMFALogin(BaseCase):\r\n    def test_mfa_login(self):\r\n        self.open(\"https://seleniumbase.io/realworld/login\")\r\n        self.type(\"#username\", \"demo_user\")\r\n        self.type(\"#password\", \"secret_pass\")\r\n        self.enter_mfa_code(\"#totpcode\", \"GAXG2MTEOR3DMMDG\")  # 6-digit\r\n        self.assert_text(\"Welcome!\", \"h1\")\r\n        self.highlight(\"img#image1\")  # A fancier assert_element() call\r\n        self.click('a:contains(\"This Page\")')  # Use :contains() on any tag\r\n        self.save_screenshot_to_logs()  # (\"./latest_logs\" folder for test)\r\n        self.click_link(\"Sign out\")  # Link must be \"a\" tag. Not \"button\".\r\n        self.assert_element('a:contains(\"Sign in\")')\r\n        self.assert_exact_text(\"You have been signed out!\", \"#top_message\")\r\n"
  },
  {
    "path": "examples/test_multiple_drivers.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass MultipleDriversTest(BaseCase):\r\n    def test_multiple_drivers(self):\r\n        if self.browser == \"safari\":\r\n            self.open_if_not_url(\"about:blank\")\r\n            print(\"\\n  Safari doesn't support multiple drivers.\")\r\n            self.skip(\"Safari doesn't support multiple drivers.\")\r\n        self.open(\"data:text/html,<h1>Driver 1</h1>\")\r\n        driver2 = self.get_new_driver()\r\n        self.open(\"data:text/html,<h1>Driver 2</h1>\")\r\n        self.switch_to_default_driver()  # Driver 1\r\n        self.highlight(\"h1\")\r\n        self.assert_text(\"Driver 1\", \"h1\")\r\n        self.switch_to_driver(driver2)  # Driver 2\r\n        self.highlight(\"h1\")\r\n        self.assert_text(\"Driver 2\", \"h1\")\r\n"
  },
  {
    "path": "examples/test_null.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass NullTests(BaseCase):\n    def test_null(self):\n        pass\n"
  },
  {
    "path": "examples/test_override_driver.py",
    "content": "from selenium import webdriver\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass OverrideDriverTest(BaseCase):\n    def get_new_driver(self, *args, **kwargs):\n        \"\"\"This method overrides get_new_driver() from BaseCase.\"\"\"\n        options = webdriver.ChromeOptions()\n        options.add_argument(\"--disable-notifications\")\n        if self.headless:\n            options.add_argument(\"--headless=new\")\n            options.add_argument(\"--disable-gpu\")\n        options.add_experimental_option(\n            \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"],\n        )\n        prefs = {\n            \"credentials_enable_service\": False,\n            \"profile.password_manager_enabled\": False,\n        }\n        options.add_experimental_option(\"prefs\", prefs)\n        return webdriver.Chrome(options=options)\n\n    def test_driver_override(self):\n        self.open(\"https://seleniumbase.io/demo_page\")\n        self.type(\"#myTextInput\", \"This is Automated\")\n        self.set_value(\"input#mySlider\", \"100\")\n        self.select_option_by_text(\"#mySelect\", \"Set to 100%\")\n        self.click(\"#checkBox1\")\n        self.drag_and_drop(\"img#logo\", \"div#drop2\")\n        self.click('button:contains(\"Click Me\")')\n"
  },
  {
    "path": "examples/test_override_sb_fixture.py",
    "content": "\"\"\"Overriding the \"sb\" fixture to override the driver.\"\"\"\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n@pytest.fixture()\ndef sb(request):\n    from selenium import webdriver\n    from seleniumbase import BaseCase\n    from seleniumbase import config as sb_config\n    from seleniumbase.core import session_helper\n\n    class BaseClass(BaseCase):\n        def get_new_driver(self, *args, **kwargs):\n            \"\"\"This method overrides get_new_driver() from BaseCase.\"\"\"\n            options = webdriver.ChromeOptions()\n            options.add_argument(\"--disable-notifications\")\n            if self.headless:\n                options.add_argument(\"--headless=new\")\n                options.add_argument(\"--disable-gpu\")\n            options.add_experimental_option(\n                \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"],\n            )\n            prefs = {\n                \"credentials_enable_service\": False,\n                \"profile.password_manager_enabled\": False,\n            }\n            options.add_experimental_option(\"prefs\", prefs)\n            return webdriver.Chrome(options=options)\n\n        def setUp(self):\n            super().setUp()\n\n        def base_method(self):\n            pass\n\n        def tearDown(self):\n            self.save_teardown_screenshot()\n            super().tearDown()\n\n    if request.cls:\n        if sb_config.reuse_class_session:\n            the_class = str(request.cls).split(\".\")[-1].split(\"'\")[0]\n            if the_class != sb_config._sb_class:\n                session_helper.end_reused_class_session_as_needed()\n                sb_config._sb_class = the_class\n        request.cls.sb = BaseClass(\"base_method\")\n        request.cls.sb.setUp()\n        request.cls.sb._needs_tearDown = True\n        request.cls.sb._using_sb_fixture = True\n        request.cls.sb._using_sb_fixture_class = True\n        sb_config._sb_node[request.node.nodeid] = request.cls.sb\n        yield request.cls.sb\n        if request.cls.sb._needs_tearDown:\n            request.cls.sb.tearDown()\n            request.cls.sb._needs_tearDown = False\n    else:\n        sb = BaseClass(\"base_method\")\n        sb.setUp()\n        sb._needs_tearDown = True\n        sb._using_sb_fixture = True\n        sb._using_sb_fixture_no_class = True\n        sb_config._sb_node[request.node.nodeid] = sb\n        yield sb\n        if sb._needs_tearDown:\n            sb.tearDown()\n            sb._needs_tearDown = False\n\n\ndef test_override_fixture_no_class(sb: BaseCase):\n    sb.open(\"https://seleniumbase.io/demo_page\")\n    sb.type(\"#myTextInput\", \"This is Automated\")\n    sb.set_value(\"input#mySlider\", \"100\")\n    sb.select_option_by_text(\"#mySelect\", \"Set to 100%\")\n    sb.click(\"#checkBox1\")\n    sb.drag_and_drop(\"img#logo\", \"div#drop2\")\n    sb.click('button:contains(\"Click Me\")')\n\n\nclass TestOverride:\n    def test_override_fixture_inside_class(self, sb: BaseCase):\n        sb.open(\"https://seleniumbase.io/demo_page\")\n        sb.type(\"#myTextInput\", \"This is Automated\")\n        sb.set_value(\"input#mySlider\", \"100\")\n        sb.select_option_by_text(\"#mySelect\", \"Set to 100%\")\n        sb.click(\"#checkBox1\")\n        sb.drag_and_drop(\"img#logo\", \"div#drop2\")\n        sb.click('button:contains(\"Click Me\")')\n"
  },
  {
    "path": "examples/test_parse_soup.py",
    "content": "import re\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass SoupParsingTests(BaseCase):\r\n    def click_menu_item(self, text):\r\n        # Use BeautifulSoup to parse the selector ID from element text.\r\n        # Then click on the element with the ID.\r\n        # (This is useful when the selector ID is auto-generated.)\r\n        pattern = re.compile(text)\r\n        soup = self.get_beautiful_soup()\r\n        the_id = soup.find(string=pattern).parent.parent.attrs[\"id\"]\r\n        self.click(\"#%s\" % the_id)\r\n\r\n    def test_beautiful_soup_parsing(self):\r\n        if self.headless:\r\n            self.open_if_not_url(\"about:blank\")\r\n            self.skip(\"Skip this test in headless mode!\")\r\n        self.open(\"https://seleniumbase.io/tinymce/\")\r\n        self.wait_for_element(\"div.mce-container-body\")\r\n        self.click_menu_item(\"File\")\r\n        self.click_menu_item(\"New document\")\r\n        self.click_menu_item(\"Paragraph\")\r\n        self.click_menu_item(\"Heading 2\")\r\n        self.switch_to_frame(\"iframe\")\r\n        self.add_text(\"#tinymce\", \"Automate anything with SeleniumBase!\\n\")\r\n        self.switch_to_default_content()\r\n        self.click(\"button i.mce-i-image\")\r\n        self.type('input[aria-label=\"Width\"].mce-textbox', \"300\")\r\n        image_url = \"https://seleniumbase.github.io/img/sb_logo_10.png\"\r\n        self.type(\"input.mce-textbox\", image_url + \"\\n\")\r\n        with self.frame_switch(\"iframe\"):\r\n            self.click(\"h2\")\r\n            self.post_message(\"Automate anything with SeleniumBase!\")\r\n        self.click_menu_item(\"File\")\r\n        self.click_menu_item(\"Preview\")\r\n        with self.frame_switch('iframe[sandbox=\"allow-scripts\"]'):\r\n            self.post_message(\"Learn SeleniumBase Today!\")\r\n"
  },
  {
    "path": "examples/test_pdf_asserts.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass PdfAssertTests(BaseCase):\n    def test_assert_pdf_text(self):\n        self.open(\"data:,\")\n        # Assert PDF contains the expected text on Page 1\n        self.assert_pdf_text(\n            \"https://nostarch.com/download/Automate_the_Boring_Stuff_dTOC.pdf\",\n            \"Programming Is a Creative Activity\",\n            page=1,\n        )\n\n        # Assert PDF contains the expected text on any of the pages\n        self.assert_pdf_text(\n            \"https://nostarch.com/download/Automate_the_Boring_Stuff_dTOC.pdf\",\n            \"Extracting Text from PDFs\",\n        )\n"
  },
  {
    "path": "examples/test_pytest_parametrize.py",
    "content": "import pytest\n\n\n@pytest.mark.parametrize(\n    \"value\", [\"List of Features\", \"Command Line Options\"]\n)\ndef test_sb_fixture_with_no_class(sb, value):\n    sb.open(\"seleniumbase.io/help_docs/install/\")\n    sb.type('input[aria-label=\"Search\"]', value)\n    sb.click(\"nav h1 mark\")\n    sb.assert_title_contains(value)\n    sb.assert_text(value, \"div.md-content\")\n\n\nclass Test_SB_Fixture:\n    @pytest.mark.parametrize(\n        \"value\", [\"Console Scripts\", \"API Reference\"]\n    )\n    def test_sb_fixture_inside_class(self, sb, value):\n        sb.open(\"seleniumbase.io/help_docs/install/\")\n        sb.type('input[aria-label=\"Search\"]', value)\n        sb.click(\"nav h1 mark\")\n        sb.assert_title_contains(value)\n        sb.assert_text(value, \"div.md-content\")\n"
  },
  {
    "path": "examples/test_repeat_tests.py",
    "content": "\"\"\" Tests to demonstrate how to repeat the same test multiple times.\n    The 1st example uses the \"parameterized\" library.\n    The 2nd example uses \"pytest.mark.parametrize()\". (NO class)\n    The 3rd example uses \"pytest.mark.parametrize()\". (in class) \"\"\"\nimport pytest\nfrom parameterized import parameterized\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"-n6\")\n\nurl = \"data:text/html,<h2>Hello</h2><p><input />&nbsp;<button>OK!</button></p>\"\n\n\nclass RepeatTests(BaseCase):\n    @parameterized.expand([[]] * 2)\n    def test_repeat_this_test_with_parameterized(self):\n        self.open(url)\n        self.type(\"input\", \"SeleniumBase is fun\")\n        self.click('button:contains(\"OK!\")')\n        self.assert_text(\"Hello\", \"h2\")\n\n\n@pytest.mark.parametrize(\"\", [[]] * 2)\ndef test_repeat_this_test_with_pytest_parametrize(sb):\n    sb.open(url)\n    sb.type(\"input\", \"SeleniumBase is fun\")\n    sb.click('button:contains(\"OK!\")')\n    sb.assert_text(\"Hello\", \"h2\")\n\n\nclass RepeatTestsWithPytest:\n    @pytest.mark.parametrize(\"\", [[]] * 2)\n    def test_repeat_test_with_pytest_parametrize(self, sb):\n        sb.open(url)\n        sb.type(\"input\", \"SeleniumBase is fun\")\n        sb.click('button:contains(\"OK!\")')\n        sb.assert_text(\"Hello\", \"h2\")\n"
  },
  {
    "path": "examples/test_request_sb_fixture.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\n# Use the pytest \"request\" fixture to get the \"sb\" fixture (no class)\r\ndef test_request_sb_fixture(request):\r\n    sb: BaseCase = request.getfixturevalue(\"sb\")\r\n    sb.open(\"https://seleniumbase.io/demo_page\")\r\n    sb.assert_text(\"SeleniumBase\", \"#myForm h2\")\r\n    sb.assert_element(\"input#myTextInput\")\r\n    sb.type(\"#myTextarea\", \"This is me\")\r\n    sb.click(\"#myButton\")\r\n    sb.tearDown()\r\n\r\n\r\n# Use the pytest \"request\" fixture to get the \"sb\" fixture (in class)\r\nclass Test_Request_Fixture:\r\n    def test_request_sb_fixture_in_class(self, request):\r\n        sb: BaseCase = request.getfixturevalue(\"sb\")\r\n        sb.open(\"https://seleniumbase.io/demo_page\")\r\n        sb.assert_element(\"input#myTextInput\")\r\n        sb.type(\"#myTextarea\", \"Automated\")\r\n        sb.assert_text(\"This Text is Green\", \"#pText\")\r\n        sb.click(\"#myButton\")\r\n        sb.assert_text(\"This Text is Purple\", \"#pText\")\r\n        sb.tearDown()\r\n"
  },
  {
    "path": "examples/test_roblox_mobile.py",
    "content": "\"\"\"Mobile device test for Chromium-based browsers\nExample: \"pytest test_roblox_mobile.py --mobile\"\n\"\"\"\nfrom seleniumbase import BaseCase\n\nif __name__ == \"__main__\":\n    from pytest import main\n    main([__file__, \"--mobile\", \"-s\"])\n\n\nclass RobloxTests(BaseCase):\n    def test_roblox_mobile_site(self):\n        if not self.mobile_emulator:\n            self.open_if_not_url(\"about:blank\")\n            print(\"\\n  This test is only for mobile-device web browsers!\")\n            print('  (Use \"--mobile\" to run this test in Mobile Mode!)')\n            self.skip('Use \"--mobile\" to run this test in Mobile Mode!')\n        self.open(\"https://www.roblox.com/\")\n        self.assert_element(\"#download-the-app-container\")\n        self.assert_text(\"Roblox for Android\")\n        self.assert_text(\"Continue in App\", \"a.content-action-emphasis\")\n        self.highlight('span:contains(\"Roblox for Android\")', loops=8)\n        self.highlight(\"a.content-action-emphasis\", loops=8)\n"
  },
  {
    "path": "examples/test_save_screenshots.py",
    "content": "import os\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass ScreenshotTests(BaseCase):\r\n    def test_save_screenshot(self):\r\n        self.open(\"https://seleniumbase.io/demo_page\")\r\n        # \"./downloaded_files\" is a special SeleniumBase folder for downloads\r\n        self.save_screenshot(\"demo_page.png\", folder=\"./downloaded_files\")\r\n        self.assert_downloaded_file(\"demo_page.png\")\r\n        print('\\n\"%s/%s\" was saved!' % (\"downloaded_files\", \"demo_page.png\"))\r\n\r\n    def test_save_screenshot_to_logs(self):\r\n        self.open(\"https://seleniumbase.io/demo_page\")\r\n        self.save_screenshot_to_logs()\r\n        # \"self.log_path\" is the absolute path to the \"./latest_logs\" folder.\r\n        # Each test that generates log files will create a subfolder in there\r\n        test_logpath = os.path.join(self.log_path, self.test_id)\r\n        expected_screenshot = os.path.join(test_logpath, \"_1_screenshot.png\")\r\n        self.assert_true(os.path.exists(expected_screenshot))\r\n        print('\\n\"%s\" was saved!' % (expected_screenshot))\r\n\r\n        self.open(\"https://seleniumbase.io/tinymce/\")\r\n        self.save_screenshot_to_logs()\r\n        expected_screenshot = os.path.join(test_logpath, \"_2_screenshot.png\")\r\n        self.assert_true(os.path.exists(expected_screenshot))\r\n        print('\"%s\" was saved!' % (expected_screenshot))\r\n\r\n        self.open(\"https://seleniumbase.io/error_page/\")\r\n        self.save_screenshot_to_logs(\"error_page\")\r\n        expected_screenshot = os.path.join(test_logpath, \"_3_error_page.png\")\r\n        self.assert_true(os.path.exists(expected_screenshot))\r\n        print('\"%s\" was saved!' % (expected_screenshot))\r\n\r\n        self.open(\"https://seleniumbase.io/devices/\")\r\n        self.save_screenshot_to_logs(\"devices\")\r\n        expected_screenshot = os.path.join(test_logpath, \"_4_devices.png\")\r\n        self.assert_true(os.path.exists(expected_screenshot))\r\n        print('\"%s\" was saved!' % (expected_screenshot))\r\n"
  },
  {
    "path": "examples/test_sb_fixture.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n# \"sb\" pytest fixture test in a method with no class\ndef test_sb_fixture_with_no_class(sb: BaseCase):\n    sb.open(\"seleniumbase.io/help_docs/install/\")\n    sb.type('input[aria-label=\"Search\"]', \"GUI Commander\")\n    sb.click('mark:contains(\"Commander\")')\n    sb.assert_title_contains(\"GUI / Commander\")\n\n\n# \"sb\" pytest fixture test in a method inside a class\nclass Test_SB_Fixture:\n    def test_sb_fixture_inside_class(self, sb: BaseCase):\n        sb.open(\"seleniumbase.io/help_docs/install/\")\n        sb.type('input[aria-label=\"Search\"]', \"GUI Commander\")\n        sb.click('mark:contains(\"Commander\")')\n        sb.assert_title_contains(\"GUI / Commander\")\n"
  },
  {
    "path": "examples/test_scrape_bing.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ScrapeBingTests(BaseCase):\n    def test_scrape_bing(self):\n        if self._multithreaded:\n            self.open_if_not_url(\"about:blank\")\n            self.skip(\"Skipping test in multi-threaded mode.\")\n        self.open(\"www.bing.com/search?q=SeleniumBase+GitHub&qs=n&form=QBRE\")\n        self.wait_for_element(\"main h2 a\")\n        self.sleep(0.5)\n        soup = self.get_beautiful_soup()\n        titles = [item.text for item in soup.select(\"main h2 a\")]\n        print(\"\\nSearch Result Headers:\")\n        for title in titles:\n            if (\n                \"seleniumbase/\" in title.lower()\n                or \"SeleniumBase Docs\" in title\n            ):\n                print(\"    \" + title)\n        links = [item[\"href\"] for item in soup.select(\"main h2 a\")]\n        print(\"Search Result Links:\")\n        for link in links:\n            if (\n                \"github.com/seleniumbase\" in link.lower()\n                or \"https://seleniumbase.io/\" in link.lower()\n            ):\n                print(\"    \" + link)\n        self.click_if_visible('a[href=\"https://github.com/seleniumbase\"]')\n        print(\"Last Page = \" + self.get_current_url())\n"
  },
  {
    "path": "examples/test_select_options.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass SelectTestClass(BaseCase):\r\n    def test_base(self):\r\n        self.open(\"https://seleniumbase.io/demo_page\")\r\n\r\n        expected_option_texts = [\r\n            \"Set to 25%\", \"Set to 50%\", \"Set to 75%\", \"Set to 100%\"\r\n        ]\r\n        option_texts = self.get_select_options(\"select#mySelect\")\r\n        self.assert_equal(option_texts, expected_option_texts)\r\n\r\n        expected_option_indexes = [\"0\", \"1\", \"2\", \"3\"]\r\n        option_indexes = self.get_select_options(\r\n            \"select#mySelect\", attribute=\"index\"\r\n        )\r\n        self.assert_equal(option_indexes, expected_option_indexes)\r\n\r\n        expected_option_values = [\"25%\", \"50%\", \"75%\", \"100%\"]\r\n        option_values = self.get_select_options(\r\n            \"select#mySelect\", attribute=\"value\"\r\n        )\r\n        self.assert_equal(option_values, expected_option_values)\r\n\r\n        for index, option_text in enumerate(option_texts):\r\n            self.select_option_by_text(\"#mySelect\", option_text)\r\n            selected_value = self.get_attribute(\"#mySelect\", \"value\")\r\n            self.assert_equal(selected_value, option_values[index])\r\n\r\n        for index, option_value in enumerate(option_values):\r\n            self.select_option_by_value(\"#mySelect\", option_value)\r\n            selected_value = self.get_attribute(\"#mySelect\", \"value\")\r\n            self.assert_equal(selected_value, option_values[index])\r\n\r\n        for index, option_index in enumerate(option_indexes):\r\n            self.select_option_by_index(\"#mySelect\", option_index)\r\n            # assert_attribute() combines get_attribute() and assert_equal()\r\n            # It also highlights the element when Demo Mode is enabled.\r\n            self.assert_attribute(\"#mySelect\", \"value\", option_values[index])\r\n"
  },
  {
    "path": "examples/test_shadow_dom.py",
    "content": "\"\"\"Shadow DOM test.\r\nFirst download files from PyPI.\r\nThen search for them on a multi-layered Shadow DOM page.\r\nThis uses the \"::shadow\" selector for piercing shadow-root elements.\r\nHere's the URL that contains Shadow DOM: chrome://downloads/ \"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass ShadowDomTests(BaseCase):\r\n    def download_tar_file_from_pypi(self, package):\r\n        self.open(\"https://pypi.org/project/%s/#files\" % package)\r\n        pkg_header = self.get_text(\"h1.package-header__name\").strip()\r\n        pkg_name = pkg_header.replace(\" \", \"-\")\r\n        tar_file = pkg_name + \".tar.gz\"\r\n        tar_selector = 'div#files a[href$=\"%s\"]' % tar_file\r\n        self.delete_downloaded_file_if_present(tar_file, browser=True)\r\n        self.click(tar_selector)\r\n        return tar_file\r\n\r\n    def test_shadow_dom(self):\r\n        if not self.browser == \"chrome\" or self.headless or self.recorder_mode:\r\n            self.open_if_not_url(\"about:blank\")\r\n            print(\"\\n  Unsupported mode for this test.\")\r\n            self.skip(\"Unsupported mode for this test.\")\r\n\r\n        # Download Python package files from PyPI\r\n        file_name_1 = self.download_tar_file_from_pypi(\"sbase\")\r\n        file_name_2 = self.download_tar_file_from_pypi(\"seleniumbase\")\r\n        self.assert_downloaded_file(file_name_1, browser=True)\r\n        self.assert_downloaded_file(file_name_2, browser=True)\r\n\r\n        # Navigate to the Chrome downloads page.\r\n        self.open(\"chrome://downloads/\")\r\n\r\n        # Shadow DOM selectors\r\n        search_icon = (\r\n            \"downloads-manager::shadow downloads-toolbar::shadow\"\r\n            \" cr-toolbar::shadow cr-toolbar-search-field::shadow\"\r\n            \" cr-icon-button\"\r\n        )\r\n        search_input = (\r\n            \"downloads-manager::shadow downloads-toolbar::shadow\"\r\n            \" cr-toolbar::shadow cr-toolbar-search-field::shadow\"\r\n            \" #searchInput\"\r\n        )\r\n        clear_search_icon = (\r\n            \"downloads-manager::shadow downloads-toolbar::shadow\"\r\n            \" cr-toolbar::shadow cr-toolbar-search-field::shadow\"\r\n            \" #clearSearch\"\r\n        )\r\n        file_link = (\r\n            \"downloads-manager::shadow #downloadsList\"\r\n            \" downloads-item::shadow #file-link\"\r\n        )\r\n        remove_button = (\r\n            \"downloads-manager::shadow #downloadsList\"\r\n            \" downloads-item::shadow #quick-remove\"\r\n        )\r\n        no_downloads_area = \"downloads-manager::shadow #no-downloads\"\r\n\r\n        self.assert_element(search_icon)\r\n        self.type(search_input, \"sbase\")\r\n        self.assert_text(file_name_1, file_link)\r\n        print(\"\\n  Download 1: %s\" % self.get_text(file_link))\r\n        self.type(search_input, \"seleniumbase\")\r\n        self.assert_text(file_name_2, file_link)\r\n        print(\"  Download 2: %s\" % self.get_text(file_link))\r\n        self.click(clear_search_icon)\r\n        self.type(search_input, \"fake-file.zzz\")\r\n        self.assert_text(\"No search results found\", no_downloads_area)\r\n        self.click(clear_search_icon)\r\n        self.assert_element(remove_button)\r\n\r\n        # Delete the downloaded files from the [Downloads Folder]\r\n        self.delete_downloaded_file_if_present(file_name_1, browser=True)\r\n        self.delete_downloaded_file_if_present(file_name_2, browser=True)\r\n"
  },
  {
    "path": "examples/test_show_file_choosers.py",
    "content": "\"\"\"self.show_file_choosers() is used to show hidden file-upload fields.\r\nVerify that one can choose a file after the hidden input is visible.\"\"\"\r\nimport os\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass FileUpload(BaseCase):\r\n    def test_show_file_choosers(self):\r\n        self.open(\"https://seleniumbase.io/apps/img_upload\")\r\n        self.wait_for_element('img[alt=\"ImgBB\"]')\r\n        choose_file_selector = 'input[type=\"file\"]'\r\n        uploaded_image = \"#anywhere-upload-queue li.queue-item\"\r\n        self.assert_element_not_visible(choose_file_selector)\r\n        self.show_file_choosers()\r\n        self.highlight(choose_file_selector)\r\n        self.assert_element(choose_file_selector)\r\n        self.assert_attribute(choose_file_selector, \"value\", \"\")\r\n        self.assert_element_not_visible(uploaded_image)\r\n        dir_name = os.path.dirname(os.path.abspath(__file__))\r\n        my_file = \"screenshot.png\"\r\n        file_path = os.path.join(dir_name, \"example_logs/%s\" % my_file)\r\n        self.choose_file(choose_file_selector, file_path)\r\n        if self.browser != \"safari\":\r\n            seen_path = \"%s\\\\%s\" % (\"C:\\\\fakepath\", my_file)\r\n            self.assert_attribute(choose_file_selector, \"value\", seen_path)\r\n        self.demo_mode = True\r\n        self.assert_element(uploaded_image)\r\n"
  },
  {
    "path": "examples/test_simple_login.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass TestSimpleLogin(BaseCase):\r\n    def test_simple_login(self):\r\n        self.open(\"seleniumbase.io/simple/login\")\r\n        self.type(\"#username\", \"demo_user\")\r\n        self.type(\"#password\", \"secret_pass\")\r\n        self.click('a:contains(\"Sign in\")')\r\n        self.assert_exact_text(\"Welcome!\", \"h1\")\r\n        self.assert_element(\"img#image1\")\r\n        self.highlight(\"#image1\")\r\n        self.click_link(\"Sign out\")\r\n        self.assert_text(\"signed out\", \"#top_message\")\r\n"
  },
  {
    "path": "examples/test_suite.py",
    "content": "\"\"\"This test suite contains 2 passing tests and 2 failing tests.\"\"\"\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestSuite(BaseCase):\n    def test_1(self):\n        self.open(\"https://xkcd.com/1722/\")\n        self.assert_text(\"Debugging\", \"div#ctitle\", timeout=4)\n        for p in range(3):\n            self.click('a[rel=\"next\"]')\n        self.assert_text(\"Linear Regression\", \"div#ctitle\", timeout=4)\n\n    @pytest.mark.expected_failure\n    def test_2(self):\n        print(\"\\n(This test should fail)\")\n        self.open(\"https://xkcd.com/1373/\")\n        self.assert_text(\"FakeText\", \"div#ctitle\", timeout=0.4)\n\n    def test_3(self):\n        self.open(\"https://xkcd.com/2224/\")\n        self.assert_text(\"Software Updates\", \"div#ctitle\", timeout=4)\n        self.open(\"https://xkcd.com/608/\")\n        self.assert_exact_text(\"Form\", \"div#ctitle\", timeout=4)\n\n    @pytest.mark.expected_failure\n    def test_4(self):\n        print(\"\\n(This test should fail)\")\n        self.open(\"https://xkcd.com/2224/\")\n        self.assert_element(\"FakeElement.DoesNotExist\", timeout=0.4)\n"
  },
  {
    "path": "examples/test_swag_labs.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass SwagLabsTests(BaseCase):\n    def login_to_swag_labs(self, username=\"standard_user\"):\n        \"\"\"Login to Swag Labs and verify success.\"\"\"\n        self.open(\"https://www.saucedemo.com\")\n        if username not in self.get_text(\"#login_credentials\"):\n            self.fail(\"Invalid user for login: %s\" % username)\n        self.type(\"#user-name\", username)\n        self.type(\"#password\", \"secret_sauce\")\n        self.click('input[type=\"submit\"]')\n        self.assert_element(\"div.inventory_list\")\n        self.assert_element('.inventory_item:contains(\"Sauce Labs Backpack\")')\n\n    def test_swag_labs_basic_flow(self):\n        \"\"\"This test checks functional flow of the Swag Labs store.\"\"\"\n        self.login_to_swag_labs(username=\"standard_user\")\n\n        # Verify that the \"Test.allTheThings() T-Shirt\" appears on the page\n        item_name = \"Test.allTheThings() T-Shirt\"\n        self.assert_text(item_name)\n\n        # Verify that a reverse-alphabetical sort works as expected\n        self.select_option_by_value(\"select.product_sort_container\", \"za\")\n        if item_name not in self.get_text(\"div.inventory_item\"):\n            self.fail('Sort Failed! Expecting \"%s\" on top!' % item_name)\n\n        # Add the \"Test.allTheThings() T-Shirt\" to the cart\n        self.assert_exact_text(\"Add to cart\", \"button.btn_inventory\")\n        item_price = self.get_text(\"div.inventory_item_price\")\n        self.click(\"button.btn_inventory\")\n        self.assert_exact_text(\"Remove\", \"button.btn_inventory\")\n        self.assert_exact_text(\"1\", \"span.shopping_cart_badge\")\n\n        # Verify your cart\n        self.click(\"#shopping_cart_container a\")\n        self.assert_element('span:contains(\"Your Cart\")')\n        self.assert_text(item_name, \"div.inventory_item_name\")\n        self.assert_exact_text(\"1\", \"div.cart_quantity\")\n        self.assert_exact_text(\"Remove\", \"button.cart_button\")\n        self.assert_element(\"button#continue-shopping\")\n\n        # Checkout - Add info\n        self.click(\"button#checkout\")\n        self.assert_element('span:contains(\"Checkout: Your Information\")')\n        self.assert_element(\"button#cancel\")\n        self.type(\"#first-name\", \"SeleniumBase\")\n        self.type(\"#last-name\", \"Rocks\")\n        self.type(\"#postal-code\", \"01720\")\n\n        # Checkout - Overview\n        self.click(\"input#continue\")\n        self.assert_element('span:contains(\"Checkout: Overview\")')\n        self.assert_element(\"button#cancel\")\n        self.assert_text(item_name, \"div.inventory_item_name\")\n        self.assert_text(item_price, \"div.inventory_item_price\")\n        self.assert_exact_text(\"1\", \"div.cart_quantity\")\n\n        # Finish Checkout and verify that the cart is now empty\n        self.click(\"button#finish\")\n        self.assert_exact_text(\"Thank you for your order!\", \"h2\")\n        self.click(\"#shopping_cart_container a\")\n        self.assert_element_absent(\"div.inventory_item_name\")\n        self.click(\"button#continue-shopping\")\n        self.assert_element_absent(\"span.shopping_cart_badge\")\n\n    def tearDown(self):\n        self.save_teardown_screenshot()  # Only if a test fails\n        # Reset App State and Logout if the controls are present\n        try:\n            self.wait_for_ready_state_complete()\n            if self.is_element_visible(\"#react-burger-menu-btn\"):\n                self.click(\"#react-burger-menu-btn\")\n                self.wait_for_element(\"a#reset_sidebar_link\")\n            self.js_click_if_present(\"a#reset_sidebar_link\")\n            self.js_click_if_present(\"a#logout_sidebar_link\")\n        except Exception:\n            pass\n        super().tearDown()\n"
  },
  {
    "path": "examples/test_tinymce.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass TinyMceTests(BaseCase):\r\n    def test_tinymce(self):\r\n        if self.headless:\r\n            self.open_if_not_url(\"about:blank\")\r\n            self.skip(\"Skip this test in headless mode!\")\r\n        self.open(\"https://seleniumbase.io/tinymce/\")\r\n        self.wait_for_element(\"div.mce-container-body\")\r\n        self.click('span:contains(\"File\")')\r\n        self.click('span:contains(\"New document\")')\r\n        self.click('span:contains(\"Paragraph\")')\r\n        self.click('span:contains(\"Heading 2\")')\r\n        self.switch_to_frame(\"iframe\")\r\n        self.add_text(\"#tinymce\", \"Automate anything with SeleniumBase!\\n\")\r\n        self.switch_to_parent_frame()\r\n        self.click(\"button i.mce-i-image\")\r\n        self.type('input[aria-label=\"Width\"].mce-textbox', \"300\")\r\n        image_url = \"https://seleniumbase.github.io/img/sb_logo_10.png\"\r\n        self.type(\"input.mce-textbox\", image_url + \"\\n\")\r\n        with self.frame_switch(\"iframe\"):\r\n            self.click(\"h2\")\r\n            self.post_message(\"Automate anything with SeleniumBase!\")\r\n        self.click('span:contains(\"File\")')\r\n        self.click('span:contains(\"Preview\")')\r\n        self.switch_to_frame('iframe[sandbox=\"allow-scripts\"]')\r\n        self.post_message(\"Learn SeleniumBase Today!\")\r\n"
  },
  {
    "path": "examples/test_todomvc.py",
    "content": "from parameterized import parameterized\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass TodoMVC(BaseCase):\r\n    def setUp(self):\r\n        super().setUp()\r\n        self.open_new_window()\r\n\r\n    def tearDown(self):\r\n        self.save_teardown_screenshot()\r\n        self.driver.close()\r\n        super().tearDown()\r\n\r\n    @parameterized.expand([[\"jquery\"], [\"react\"], [\"vue\"]])\r\n    def test_todomvc(self, framework):\r\n        self.open(\"https://todomvc.com/\")\r\n        self.clear_local_storage()\r\n        self.click('a[href*=\"examples/%s/dist\"]' % framework)\r\n        self.assert_element(\"section.todoapp\")\r\n        self.assert_text(\"todos\", \"header h1\")\r\n        self.wait_for_ready_state_complete()\r\n        title = self.get_title()\r\n        self.assert_in(framework, title.lower())\r\n        new_todo_input = \"input.new-todo\"\r\n        todo_count_span = \"span.todo-count\"\r\n        self.wait_for_ready_state_complete()\r\n        self.type(new_todo_input, \"Learn Python\\n\")\r\n        self.type(new_todo_input, \"Learn JavaScript\\n\")\r\n        self.type(new_todo_input, \"Learn SeleniumBase\\n\")\r\n        self.assert_text(\"3 items left\", todo_count_span)\r\n        self.check_if_unchecked(\"ul.todo-list li input\")\r\n        self.check_if_unchecked(\"ul.todo-list li:nth-of-type(2) input\")\r\n        self.check_if_unchecked(\"ul.todo-list li:nth-of-type(3) input\")\r\n        self.assert_text(\"0 items left\", todo_count_span)\r\n        self.click(\"button.clear-completed\")\r\n        self.assert_element_not_visible(todo_count_span)\r\n"
  },
  {
    "path": "examples/test_url_asserts.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass URLTestClass(BaseCase):\r\n    def test_url_asserts(self):\r\n        self.open(\"https://seleniumbase.io/help_docs/how_it_works/\")\r\n        self.assert_url(\"https://seleniumbase.io/help_docs/how_it_works/\")\r\n        self.assert_title_contains(\"How it Works\")\r\n        self.js_click('nav a:contains(\"Coffee Cart\")')\r\n        self.assert_url_contains(\"/coffee\")\r\n        self.assert_title(\"Coffee Cart\")\r\n"
  },
  {
    "path": "examples/test_usefixtures.py",
    "content": "import pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\n@pytest.mark.usefixtures(\"sb\")\nclass Test_UseFixtures:\n    def test_usefixtures_on_class(self):\n        if not hasattr(self, \"sb\"):\n            print(\"This test is for pytest only!\")\n            return\n        sb: BaseCase = self.sb\n        sb.open(\"https://seleniumbase.io/realworld/login\")\n        sb.type(\"#username\", \"demo_user\")\n        sb.type(\"#password\", \"secret_pass\")\n        sb.enter_mfa_code(\"#totpcode\", \"GAXG2MTEOR3DMMDG\")  # 6-digit\n        sb.assert_text(\"Welcome!\", \"h1\")\n        sb.highlight(\"img#image1\")  # A fancier assert_element() call\n        sb.click('a:contains(\"This Page\")')  # Use :contains() on any tag\n        sb.save_screenshot_to_logs()  # In \"./latest_logs/\" folder.\n        sb.click_link(\"Sign out\")  # Link must be \"a\" tag. Not \"button\".\n        sb.assert_element('a:contains(\"Sign in\")')\n        sb.assert_exact_text(\"You have been signed out!\", \"#top_message\")\n"
  },
  {
    "path": "examples/test_verify_chromedriver.py",
    "content": "import sys\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass ChromedriverTests(BaseCase):\n    def test_fail_if_versions_dont_match(self):\n        self.open(\"about:blank\")\n        if self.browser != \"chrome\":\n            print(\"\\n  This test is only for Chrome!\")\n            self.skip(\"This test is only for Chrome!\")\n        chrome_version = self.get_chrome_version()\n        major_chrome_version = chrome_version.split(\".\")[0]\n        chromedriver_version = self.get_chromedriver_version()\n        major_chromedriver_version = chromedriver_version.split(\".\")[0]\n        install_sb = \"sbase get chromedriver %s\" % major_chrome_version\n        arg_join = \" \".join(sys.argv)\n        message = (\n            'Your version of chromedriver: \"%s\"\\n  '\n            'does not match your version of Chrome: \"%s\"\\n'\n            'Run this command to fix that: \"%s\"'\n            % (chromedriver_version, chrome_version, install_sb)\n        )\n        if \"--driver-version=\" in arg_join or \"--driver-version=\" in arg_join:\n            if int(major_chromedriver_version) != int(major_chrome_version):\n                print(\"\\nWarning -> \" + message)\n        elif int(major_chromedriver_version) != int(major_chrome_version):\n            raise Exception(message)\n        else:\n            print(\n                \"\\n* Chrome version: {%s}\\n* Driver version: {%s}\"\n                % (chromedriver_version, chrome_version)\n            )\n"
  },
  {
    "path": "examples/test_window_switching.py",
    "content": "\"\"\"\r\nSometimes tests open new tabs/windows, and you'll need\r\nto switch to them first in order to interact with them.\r\nThe starting window is window(0). Then increments by 1.\r\n\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass TabSwitchingTests(BaseCase):\r\n    def test_switch_to_tabs(self):\r\n        self.open(\"about:blank\")\r\n        self.get_new_driver()\r\n        self.open(\"data:text/html,<h1>Page A</h1>\")\r\n        self.assert_text(\"Page A\")\r\n        self.open_new_window()\r\n        self.open(\"data:text/html,<h1>Page B</h1>\")\r\n        self.assert_text(\"Page B\")\r\n        self.switch_to_window(0)\r\n        self.assert_text(\"Page A\")\r\n        self.assert_text_not_visible(\"Page B\")\r\n        self.switch_to_window(-1)\r\n        self.assert_text(\"Page B\")\r\n        self.assert_text_not_visible(\"Page A\")\r\n"
  },
  {
    "path": "examples/test_xfail.py",
    "content": "\"\"\"Testing the @pytest.mark.xfail marker.\nhttps://docs.pytest.org/en/latest/skipping.html\n(The test is expected to fail, but don't fail the entire build for it.)\"\"\"\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass XFailTests(BaseCase):\n    @pytest.mark.xfail\n    def test_xfail(self):\n        self.open(\"https://xkcd.com/376/\")\n        self.sleep(1)  # Time to read the comic\n        self.fail(\"There is a known bug here!\")\n"
  },
  {
    "path": "examples/test_xkcd.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_xkcd(self):\n        self.open(\"https://xkcd.com/353/\")\n        self.assert_title(\"xkcd: Python\")\n        self.assert_element('img[alt=\"Python\"]')\n        self.click('a[rel=\"license\"]')\n        self.assert_text(\"free to copy and reuse\")\n        self.go_back()\n        self.click_link(\"About\")\n        self.assert_exact_text(\"xkcd.com\", \"h2\")\n        self.click_link(\"comic #249\")\n        self.assert_element('img[alt*=\"Chess\"]')\n"
  },
  {
    "path": "examples/time_limit_test.py",
    "content": "import pytest\nfrom seleniumbase import BaseCase\nfrom seleniumbase import decorators\nBaseCase.main(__name__, __file__)\n\n\nclass TimeLimitTests(BaseCase):\n    @pytest.mark.expected_failure\n    def test_runtime_limit_decorator(self):\n        \"\"\"This test fails on purpose to show the runtime_limit() decorator\n        for code blocks that run longer than the time limit specified.\"\"\"\n        print(\"\\n(This test should fail)\")\n        self.open(\"https://xkcd.com/2511\")\n        with decorators.runtime_limit(0.7):\n            self.sleep(0.95)\n\n    @pytest.mark.expected_failure\n    def test_set_time_limit_method(self):\n        \"\"\"This test fails on purpose to show the set_time_limit() method\n        for tests that run longer than the time limit specified (seconds).\n        The time-limit clock starts after the browser has fully launched,\n        which is after pytest starts it's own internal clock for tests.\n        Usage: (inside tests) =>  self.set_time_limit(SECONDS)\n        Usage: (command-line) =>  --time-limit=SECONDS\"\"\"\n        self.set_time_limit(2.2)  # Fail test if time exceeds 2.2 seconds\n        print(\"\\n(This test should fail)\")\n        self.open(\"https://xkcd.com/1658\")\n        self.sleep(3)\n"
  },
  {
    "path": "examples/tour_examples/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h3 align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/img/g_maps_tour.png\" alt=\"SeleniumBase Tour\" width=\"340\" /></h3>\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> 🌏 Interactive Product Tours 🚎</h2>\n\n<p><b>Increase SaaS Product Adoption by 10x or more.</b></p>\n\n* SeleniumBase Tours utilize 5 JavaScript libraries for creating interactive walkthroughs on **any website**:\n\n> **[IntroJS](https://introjs.com/)**, **[Bootstrap Tour](http://bootstraptour.com/)**, **[DriverJS](https://kamranahmed.info/driver.js/)**, **[Shepherd](https://shepherdjs.dev/)**, and **[Hopscotch](https://linkedinattic.github.io/hopscotch/)**.\n\n<b>A tour demo: (with autoplay)</b>\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/introjs_tour.gif\" title=\"SeleniumBase Tour of Google\"><br>\n\n[SeleniumBase maps_introjs_tour.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/maps_introjs_tour.py)\n\n```zsh\ncd examples/tour_examples\npytest maps_introjs_tour.py --interval=1\n```\n\n<b>Here's a longer version:</b>\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/google_tour_4.gif\" title=\"SeleniumBase Tour of Google\"><br>\n\n[SeleniumBase google_tour.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/google_tour.py)\n\n```zsh\ncd examples/tour_examples\npytest google_tour.py\n```\n\n> (From [GitHub => SeleniumBase/examples/tour_examples](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples))\n\n\n### Creating a new tour:\n\n#### To create a tour utilizing the Shepherd Library, use one of the following:\n\n``self.create_shepherd_tour()``\n\nOR\n\n``self.create_tour(theme=\"shepherd\")``\n\nYou can pass a custom theme to change the look & feel of Shepherd tours. Valid themes for Shepherd Tours are ``dark``, ``light`` / ``arrows``, ``default``, ``square``, and ``square-dark``.\n\n#### To create a tour utilizing the Bootstrap Tour Library, use one of the following:\n\n``self.create_bootstrap_tour()``\n\nOR\n\n``self.create_tour(theme=\"bootstrap\")``\n\n#### To create a tour utilizing the IntroJS Library, use one of the following:\n\n``self.create_introjs_tour()``\n\nOR\n\n``self.create_tour(theme=\"introjs\")``\n\n#### To create a tour utilizing the DriverJS Library, use one of the following:\n\n``self.create_driverjs_tour()``\n\nOR\n\n``self.create_tour(theme=\"driverjs\")``\n\n#### To create a tour utilizing the Hopscotch Library, use one of the following:\n\n``self.create_hopscotch_tour()``\n\nOR\n\n``self.create_tour(theme=\"hopscotch\")``\n\n### Adding a step to a tour:\n\n#### To add a tour step, use the following:\n\n``self.add_tour_step(message, css_selector, title, alignment, theme)``\n\n> With the ``self.add_tour_step()`` method, you must first pass a message to display. You can then specify a web element to attach to (by using [CSS selectors](https://www.w3schools.com/cssref/css_selectors.asp)). If no element is specified, the tour step will tether to the top of the screen by default. You can also add an optional title above the message to display with the tour step, as well as change the theme for that step (Shepherd tours only), and even specify the alignment (which is the side of the element that you want the tour message to tether to).\n\n\n### Playing a tour:\n\nYou can play a tour by calling:\n\n``self.play_tour(interval)``\n\n> If you specify an ``interval`` (optional), the tour will automatically walk through each step after that many seconds have passed.\n\n\nAll methods have the optional ``name`` argument, which is only needed if you're creating multiple tours at once. Then, when you're adding a step or playing a tour, SeleniumBase knows which tour you're referring too. You can avoid using the ``name`` arg for multiple tours if you play a tour before creating a new one.\n\n### Here's how the code looks:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--uc\")\n\nclass MyTourClass(BaseCase):\n\n    def test_google_tour(self):\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.open('https://google.com/ncr')\n        self.wait_for_element('input[title=\"Search\"]')\n        self.hide_elements(\"iframe\")\n\n        self.create_tour(theme=\"dark\")\n        self.add_tour_step(\"Welcome to Google!\", title=\"SeleniumBase Tours\")\n        self.add_tour_step(\"Type in your query here.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('input[title=\"Search\"]', \"Google\")\n        self.wait_for_element('[role=\"listbox\"]')  # Wait for autocomplete\n\n        self.create_tour(theme=\"light\")\n        self.add_tour_step(\"Then click to search.\", '[value=\"Google Search\"]')\n        self.add_tour_step(\"Or press [ENTER] after entry.\", '[title=\"Search\"]')\n        self.play_tour()\n```\n\n#### That code is from [google_tour.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/google_tour.py), which you can run from the ``tour_examples/`` folder with the following command:\n\n```zsh\npytest google_tour.py\n```\n\n### Exporting a Tour:\n\nIf you want to save the tour you created as a JavaScript file, use:\n\n``self.export_tour()``\n\nOR\n\n``self.export_tour(name=None, filename=\"my_tour.js\")``\n\n> (``name`` is optional unless you gave custom names to your tours when you created them. ``filename`` is the name of the file to save the JavaScript to.) Once you've exported your tour, you can use it outside of SeleniumBase. You can even copy the tour's JavaScript code to the Console of your web browser to play the tour from there (you need to be on the correct web page for it to work).\n\n--------\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/driverjs_tour_2.gif\" title=\"SeleniumBase Tour of Google\"><br>\n\n<h3 align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_b.png\" alt=\"SeleniumBase\" width=\"320\" /></h3>\n"
  },
  {
    "path": "examples/tour_examples/bootstrap_google_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--uc\")\n\n\nclass MyTourClass(BaseCase):\n    def test_google_tour(self):\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.open(\"https://google.com/ncr\")\n        self.wait_for_element('[title=\"Search\"]')\n        self.hide_elements(\"iframe\")\n\n        self.create_bootstrap_tour()  # OR self.create_tour(theme=\"bootstrap\")\n        self.add_tour_step(\"Welcome to Google!\", title=\"SeleniumBase Tours\")\n        self.add_tour_step(\"Type in your query here.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"Google\")\n        self.wait_for_element('[role=\"listbox\"]')  # Wait for autocomplete\n\n        self.create_bootstrap_tour()\n        self.add_tour_step(\"Then click to search.\", '[value=\"Google Search\"]')\n        self.add_tour_step(\"Or press [ENTER] after entry.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"GitHub\\n\")\n        self.ad_block()\n        self.wait_for_element(\"#search\")\n\n        self.create_bootstrap_tour()\n        self.add_tour_step(\"3-second autoplay...\")\n        self.add_tour_step(\"Here's the next tour:\")\n        self.play_tour(interval=3)  # Tour automatically continues after 3 sec\n\n        self.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\n        self.wait_for_element(\"#searchboxinput\", timeout=20)\n        self.wait_for_element(\"#minimap\", timeout=20)\n        self.wait_for_element(\"#zoom\", timeout=20)\n        self.wait_for_element('[aria-label=\"Zoom out\"]')\n        self.wait_for_element('[jsaction*=\"minimap.main;\"]')\n        self.sleep(0.5)\n\n        self.create_bootstrap_tour()\n        self.add_tour_step(\"Welcome to Google Maps\", title=\"SeleniumBase Tour\")\n        self.add_tour_step(\n            \"The location goes here.\", \"#searchboxinput\", title=\"Search Box\"\n        )\n        self.add_tour_step(\n            \"Then click here to show it on the map.\",\n            \"#searchbox-searchbutton\",\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Or click here to get driving directions.\",\n            'button[aria-label=\"Directions\"]',\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Use this button to switch to Satellite view.\",\n            'button[jsaction*=\"minimap.main;\"]',\n            alignment=\"right\",\n        )\n        self.add_tour_step(\n            \"Click here to zoom in.\",\n            '[aria-label=\"Zoom in\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Or click here to zoom out.\",\n            '[aria-label=\"Zoom out\"]',\n            alignment=\"left\",\n        )\n        if self.is_element_visible('button[jsaction*=\"settings.open;\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction*=\"settings.open;\"]',\n                alignment=\"right\",\n            )\n        elif self.is_element_visible('button[jsaction=\"navigationrail.more\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction=\"navigationrail.more\"]',\n                alignment=\"right\",\n            )\n        self.add_tour_step(\n            \"Or click here to see more Google apps.\",\n            '[aria-label=\"Google apps\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Thanks for using SeleniumBase Tours!\", title=\"End of Guided Tour\"\n        )\n        self.export_tour(filename=\"bootstrap_google_maps_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/bootstrap_xkcd_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_bootstrap_tour(self):\n        self.open(\"https://xkcd.com/1117/\")\n        self.assert_element('img[alt=\"My Sky\"]')\n        self.create_bootstrap_tour()\n        self.add_tour_step(\"Welcome to XKCD!\")\n        self.add_tour_step(\"This is the XKCD logo.\", \"#masthead img\")\n        self.add_tour_step(\"Here's the daily webcomic.\", \"#comic img\")\n        self.add_tour_step(\"This is the title.\", \"#ctitle\", alignment=\"top\")\n        self.add_tour_step(\"Click here for the next comic.\", 'a[rel=\"next\"]')\n        self.add_tour_step(\"Click here for the previous one.\", 'a[rel=\"prev\"]')\n        self.add_tour_step(\"Learn about the author here.\", 'a[rel=\"author\"]')\n        self.add_tour_step(\"Click for a random comic.\", 'a[href*=\"/random/\"]')\n        self.add_tour_step(\"Thanks for taking this tour!\")\n        self.export_tour(filename=\"bootstrap_xkcd_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/driverjs_maps_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_create_tour(self):\n        self.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\n        self.wait_for_element(\"#searchboxinput\", timeout=20)\n        self.wait_for_element(\"#minimap\", timeout=20)\n        self.wait_for_element(\"#zoom\", timeout=20)\n        self.wait_for_element(\"#widget-zoom-out\")\n        self.wait_for_element('[jsaction*=\"minimap.main;\"]')\n        self.sleep(0.5)\n\n        # Create a website tour using the DriverJS library\n        # Same as:  self.create_driverjs_tour()\n        self.create_tour(theme=\"driverjs\")\n        self.add_tour_step(\"Welcome to Google Maps\", title=\"SeleniumBase Tour\")\n        self.add_tour_step(\n            \"The location goes here.\", \"#searchboxinput\", title=\"Search Box\"\n        )\n        self.add_tour_step(\n            \"Then click here to show it on the map.\",\n            \"#searchbox-searchbutton\",\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Or click here to get driving directions.\",\n            'button[aria-label=\"Directions\"]',\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Use this button to switch to Satellite view.\",\n            'button[jsaction*=\"minimap.main;\"]',\n            alignment=\"right\",\n        )\n        self.add_tour_step(\n            \"Click here to zoom in.\", \"#widget-zoom-in\", alignment=\"left\"\n        )\n        self.add_tour_step(\n            \"Or click here to zoom out.\", \"#widget-zoom-out\", alignment=\"left\"\n        )\n        if self.is_element_visible('button[jsaction*=\"settings.open;\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction*=\"settings.open;\"]',\n                alignment=\"right\",\n            )\n        elif self.is_element_visible('button[jsaction=\"navigationrail.more\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction=\"navigationrail.more\"]',\n                alignment=\"right\",\n            )\n        self.add_tour_step(\n            \"Or click here to see more Google apps.\",\n            '[aria-label=\"Google apps\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Thanks for using SeleniumBase Tours!\", title=\"End of Guided Tour\"\n        )\n        self.export_tour()  # The default name for exports is \"my_tour.js\"\n        self.play_tour(interval=0)  # If interval > 0, autoplay after N seconds\n"
  },
  {
    "path": "examples/tour_examples/google_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--uc\")\n\n\nclass MyTourClass(BaseCase):\n    def test_google_tour(self):\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.open(\"https://google.com/ncr\")\n        self.wait_for_element('[title=\"Search\"]')\n        self.hide_elements(\"iframe\")\n\n        # Create a website tour using the ShepherdJS library with \"dark\" theme\n        # Same as:  self.create_shepherd_tour(theme=\"dark\")\n        self.create_tour(theme=\"dark\")\n        self.add_tour_step(\"Welcome to Google!\", title=\"SeleniumBase Tours\")\n        self.add_tour_step(\"Type in your query here.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"Google\")\n        self.wait_for_element('[role=\"listbox\"]')  # Wait for autocomplete\n\n        # Create a website tour using the ShepherdJS library with \"light\" theme\n        # Same as:  self.create_shepherd_tour(theme=\"light\")\n        self.create_tour(theme=\"light\")\n        self.add_tour_step(\"Then click to search.\", '[value=\"Google Search\"]')\n        self.add_tour_step(\"Or press [ENTER] after entry.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"GitHub\\n\")\n        self.ad_block()\n        self.wait_for_element(\"#search\")\n\n        # Create a website tour using the Bootstrap Tour JS library\n        # Same as:  self.create_bootstrap_tour()\n        self.create_tour(theme=\"bootstrap\")\n        self.add_tour_step(\"3-second autoplay...\")\n        self.add_tour_step(\"Here's the next tour:\")\n        self.play_tour(interval=3)  # Tour automatically continues after 3 sec\n\n        self.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\n        self.wait_for_element(\"#searchboxinput\")\n        self.wait_for_element(\"#minimap\")\n        self.wait_for_element(\"#zoom\")\n        self.wait_for_element('[aria-label=\"Zoom out\"]')\n        self.wait_for_element('[jsaction*=\"minimap.main;\"]')\n        self.sleep(0.5)\n\n        # Create a website tour using the IntroJS library\n        # Same as:  self.create_introjs_tour()\n        self.create_tour(theme=\"introjs\")\n        self.add_tour_step(\"Welcome to Google Maps\", title=\"SeleniumBase Tour\")\n        self.add_tour_step(\n            \"The location goes here.\", \"#searchboxinput\", title=\"Search Box\"\n        )\n        self.add_tour_step(\n            \"Then click here to show it on the map.\",\n            \"#searchbox-searchbutton\",\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Or click here to get driving directions.\",\n            'button[aria-label=\"Directions\"]',\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Use this button to switch to Satellite view.\",\n            'button[jsaction*=\"minimap.main;\"]',\n            alignment=\"right\",\n        )\n        self.add_tour_step(\n            \"Click here to zoom in.\",\n            '[aria-label=\"Zoom in\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Or click here to zoom out.\",\n            '[aria-label=\"Zoom out\"]',\n            alignment=\"left\",\n        )\n        if self.is_element_visible('button[jsaction*=\"settings.open;\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction*=\"settings.open;\"]',\n                alignment=\"right\",\n            )\n        elif self.is_element_visible('button[jsaction=\"navigationrail.more\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction=\"navigationrail.more\"]',\n                alignment=\"right\",\n            )\n        self.add_tour_step(\n            \"Or click here to see more Google apps.\",\n            '[aria-label=\"Google apps\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Thanks for using SeleniumBase Tours!\", title=\"End of Guided Tour\"\n        )\n        self.export_tour()  # The default name for exports is \"my_tour.js\"\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/hopscotch_google_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--uc\")\n\n\nclass MyTourClass(BaseCase):\n    def test_google_tour(self):\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.open(\"https://google.com/ncr\")\n        self.wait_for_element('[title=\"Search\"]')\n        self.hide_elements(\"iframe\")\n\n        self.create_hopscotch_tour()  # OR self.create_tour(theme=\"hopscotch\")\n        self.add_tour_step(\"Welcome to Google!\", title=\"SeleniumBase Tours\")\n        self.add_tour_step(\"Type in your query here.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"Google\")\n        self.wait_for_element('[role=\"listbox\"]')  # Wait for autocomplete\n\n        self.create_hopscotch_tour()\n        self.add_tour_step(\"Then click to search.\", '[value=\"Google Search\"]')\n        self.add_tour_step(\"Or press [ENTER] after entry.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"GitHub\\n\")\n        self.ad_block()\n        self.wait_for_element(\"#search\")\n\n        self.create_hopscotch_tour()\n        self.add_tour_step(\"3-second autoplay...\")\n        self.add_tour_step(\"Here's the next tour:\")\n        self.play_tour(interval=3)  # Tour automatically continues after 3 sec\n\n        self.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\n        self.wait_for_element(\"#searchboxinput\", timeout=20)\n        self.wait_for_element(\"#minimap\", timeout=20)\n        self.wait_for_element(\"#zoom\", timeout=20)\n        self.wait_for_element('[aria-label=\"Zoom out\"]')\n        self.wait_for_element('[jsaction*=\"minimap.main;\"]')\n        self.sleep(0.5)\n\n        self.create_hopscotch_tour()\n        self.add_tour_step(\"Welcome to Google Maps\", title=\"SeleniumBase Tour\")\n        self.add_tour_step(\n            \"The location goes here.\", \"#searchboxinput\", title=\"Search Box\"\n        )\n        self.add_tour_step(\n            \"Then click here to show it on the map.\",\n            \"#searchbox-searchbutton\",\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Or click here to get driving directions.\",\n            'button[aria-label=\"Directions\"]',\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Use this button to switch to Satellite view.\",\n            'button[jsaction*=\"minimap.main;\"]',\n            alignment=\"right\",\n        )\n        self.add_tour_step(\n            \"Click here to zoom in.\",\n            '[aria-label=\"Zoom in\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Or click here to zoom out.\",\n            '[aria-label=\"Zoom out\"]',\n            alignment=\"left\",\n        )\n        if self.is_element_visible('button[jsaction*=\"settings.open;\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction*=\"settings.open;\"]',\n                alignment=\"right\",\n            )\n        elif self.is_element_visible('button[jsaction=\"navigationrail.more\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction=\"navigationrail.more\"]',\n                alignment=\"right\",\n            )\n        self.add_tour_step(\n            \"Or click here to see more Google apps.\",\n            '[aria-label=\"Google apps\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Thanks for using SeleniumBase Tours!\", title=\"End of Guided Tour\"\n        )\n        self.export_tour(filename=\"hopscotch_google_maps_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/introjs_google_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--uc\")\n\n\nclass MyTourClass(BaseCase):\n    def test_google_tour(self):\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.open(\"https://google.com/ncr\")\n        self.wait_for_element('[title=\"Search\"]')\n        self.hide_elements(\"iframe\")\n\n        self.create_introjs_tour()  # OR self.create_tour(theme=\"introjs\")\n        self.add_tour_step(\"Welcome to Google!\", title=\"SeleniumBase Tours\")\n        self.add_tour_step(\"Type in your query here.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"Google\")\n        self.wait_for_element('[role=\"listbox\"]')  # Wait for autocomplete\n\n        self.create_introjs_tour()\n        self.add_tour_step(\"Then click to search.\", '[value=\"Google Search\"]')\n        self.add_tour_step(\"Or press [ENTER] after entry.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"GitHub\\n\")\n        self.ad_block()\n        self.wait_for_element(\"#search\")\n\n        self.create_introjs_tour()\n        self.add_tour_step(\"3-second autoplay...\")\n        self.add_tour_step(\"Here's the next tour:\")\n        self.play_tour(interval=3)  # Tour automatically continues after 3 sec\n\n        self.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\n        self.wait_for_element(\"#searchboxinput\", timeout=20)\n        self.wait_for_element(\"#minimap\", timeout=20)\n        self.wait_for_element(\"#zoom\", timeout=20)\n        self.wait_for_element('[aria-label=\"Zoom out\"]')\n        self.wait_for_element('[jsaction*=\"minimap.main;\"]')\n        self.sleep(0.5)\n\n        self.set_introjs_colors(\"#f26721\", \"#db5409\")\n        self.create_introjs_tour()\n        self.add_tour_step(\"Welcome to Google Maps\", title=\"SeleniumBase Tour\")\n        self.add_tour_step(\n            \"The location goes here.\", \"#searchboxinput\", title=\"Search Box\"\n        )\n        self.add_tour_step(\n            \"Then click here to show it on the map.\",\n            \"#searchbox-searchbutton\",\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Or click here to get driving directions.\",\n            'button[aria-label=\"Directions\"]',\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Use this button to switch to Satellite view.\",\n            'button[jsaction*=\"minimap.main;\"]',\n            alignment=\"right\",\n        )\n        self.add_tour_step(\n            \"Click here to zoom in.\",\n            '[aria-label=\"Zoom in\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Or click here to zoom out.\",\n            '[aria-label=\"Zoom out\"]',\n            alignment=\"left\",\n        )\n        if self.is_element_visible('button[jsaction*=\"settings.open;\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction*=\"settings.open;\"]',\n                alignment=\"right\",\n            )\n        elif self.is_element_visible('button[jsaction=\"navigationrail.more\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction=\"navigationrail.more\"]',\n                alignment=\"right\",\n            )\n        self.add_tour_step(\n            \"Or click here to see more Google apps.\",\n            '[aria-label=\"Google apps\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Thanks for using SeleniumBase Tours!\", title=\"End of Guided Tour\"\n        )\n        self.export_tour(filename=\"introjs_google_maps_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/maps_introjs_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTourClass(BaseCase):\n    def test_google_maps_tour(self):\n        self.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\n        self.wait_for_element(\"#searchboxinput\", timeout=20)\n        self.wait_for_element(\"#minimap\", timeout=20)\n        self.wait_for_element(\"#zoom\", timeout=20)\n        self.wait_for_element('[aria-label=\"Zoom out\"]')\n        self.wait_for_element('[jsaction*=\"minimap.main;\"]')\n\n        self.create_tour(theme=\"introjs\")\n        self.add_tour_step(\n            \"Welcome to Google Maps\", title=\"✅ SeleniumBase Tours 🌎\"\n        )\n        self.add_tour_step(\n            \"The location goes here.\", \"#searchboxinput\", title=\"Search Box\"\n        )\n        self.add_tour_step(\n            \"Then click here to show it on the map.\",\n            \"#searchbox-searchbutton\",\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Or click here to get driving directions.\",\n            'button[aria-label=\"Directions\"]',\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Use this button to switch to Satellite view.\",\n            'button[jsaction*=\"minimap.main;\"]',\n            alignment=\"right\",\n        )\n        self.add_tour_step(\n            \"Click here to zoom in.\",\n            '[aria-label=\"Zoom in\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Or click here to zoom out.\",\n            '[aria-label=\"Zoom out\"]',\n            alignment=\"left\",\n        )\n        if self.is_element_visible('button[jsaction*=\"settings.open;\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction*=\"settings.open;\"]',\n                alignment=\"right\",\n            )\n        elif self.is_element_visible('button[jsaction=\"navigationrail.more\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction=\"navigationrail.more\"]',\n                alignment=\"right\",\n            )\n        self.add_tour_step(\n            \"Or click here to see more Google apps.\",\n            '[aria-label=\"Google apps\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Thanks for using SeleniumBase Tours!\",\n            title=\"🚃 End of Guided Tour 🚃\",\n        )\n        self.export_tour(filename=\"maps_introjs_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/octocat_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTourClass(BaseCase):\n    def test_octocat_tour(self):\n        self.maximize_window()\n        self.open(\"https://seleniumbase.io/error_page/\")\n        self.wait_for_element(\"#parallax_octocat\")\n        self.create_tour(theme=\"bootstrap\")\n        self.add_tour_step(\"Welcome to the Octocat Tour!\")\n        self.add_tour_step(\"This is Octocat\", \"#parallax_octocat\")\n        self.add_tour_step(\"This is Octobi-Wan Catnobi\", \"#octobi_wan_catnobi\")\n        self.add_tour_step(\"<h1><b>Ooops!!!</b></h1>\", \"#parallax_error_text\")\n        self.add_tour_step(\"This is a Star Wars speeder.\", \"#speeder\")\n        self.add_tour_step(\"This is a sign with a 500-Error\", \"#parallax_sign\")\n        self.add_tour_step(\n            \"This is not the page you're looking for.\", 'img[alt*=\"404\"]'\n        )\n        self.add_tour_step(\"<b>Have a great day!</b>\", title=\"😃 ☀️ 😁\")\n        self.add_tour_step(\"<b>And may the Force be with you!</b>\", title=\"⭐\")\n        self.export_tour(filename=\"octocat_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/shepherd_google_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--uc\")\n\n\nclass MyTourClass(BaseCase):\n    def test_google_tour(self):\n        if not self.undetectable:\n            self.get_new_driver(undetectable=True)\n        self.open(\"https://google.com/ncr\")\n        self.wait_for_element('[title=\"Search\"]')\n        self.hide_elements(\"iframe\")\n\n        self.create_shepherd_tour(theme=\"dark\")\n        self.add_tour_step(\"Welcome to Google!\", title=\"SeleniumBase Tours\")\n        self.add_tour_step(\"Type in your query here.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"Google\")\n        self.wait_for_element('[role=\"listbox\"]')  # Wait for autocomplete\n\n        self.create_shepherd_tour(theme=\"light\")\n        self.add_tour_step(\"Then click to search.\", '[value=\"Google Search\"]')\n        self.add_tour_step(\"Or press [ENTER] after entry.\", '[title=\"Search\"]')\n        self.play_tour()\n\n        self.highlight_type('[title=\"Search\"]', \"GitHub\\n\")\n        self.ad_block()\n        self.wait_for_element(\"#search\")\n\n        self.create_shepherd_tour(theme=\"square-dark\")\n        self.add_tour_step(\"3-second autoplay...\")\n        self.add_tour_step(\"Here's the next tour:\")\n        self.play_tour(interval=3)  # Tour automatically continues after 3 sec\n\n        self.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\n        self.wait_for_element(\"#searchboxinput\", timeout=20)\n        self.wait_for_element(\"#minimap\", timeout=20)\n        self.wait_for_element(\"#zoom\", timeout=20)\n        self.wait_for_element('[aria-label=\"Zoom out\"]')\n        self.wait_for_element('[jsaction*=\"minimap.main;\"]')\n        self.sleep(0.5)\n\n        self.create_shepherd_tour(theme=\"dark\")\n        self.add_tour_step(\"Welcome to Google Maps!\")\n        self.add_tour_step(\n            \"Type in a location here.\", \"#searchboxinput\", title=\"Search Box\"\n        )\n        self.add_tour_step(\n            \"Then click here to show it on the map.\",\n            \"#searchbox-searchbutton\",\n            alignment=\"bottom\",\n        )\n        self.add_tour_step(\n            \"Or click here to get driving directions.\",\n            'button[aria-label=\"Directions\"]',\n            alignment=\"bottom\",\n            theme=\"dark\",\n        )\n        self.add_tour_step(\n            \"Use this button to switch to Satellite view.\",\n            'button[jsaction*=\"minimap.main;\"]',\n            alignment=\"right\",\n        )\n        self.add_tour_step(\n            \"Click here to zoom in.\",\n            '[aria-label=\"Zoom in\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Or click here to zoom out.\",\n            '[aria-label=\"Zoom out\"]',\n            alignment=\"left\",\n            theme=\"light\",\n        )\n        if self.is_element_visible('button[jsaction*=\"settings.open;\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction*=\"settings.open;\"]',\n                alignment=\"right\",\n            )\n        elif self.is_element_visible('button[jsaction=\"navigationrail.more\"]'):\n            self.add_tour_step(\n                \"Use the Menu button to see more options.\",\n                'button[jsaction=\"navigationrail.more\"]',\n                alignment=\"right\",\n            )\n        self.add_tour_step(\n            \"Or click here to see more Google apps.\",\n            '[aria-label=\"Google apps\"]',\n            alignment=\"left\",\n        )\n        self.add_tour_step(\n            \"Thanks for using SeleniumBase Tours!\",\n            title=\"End of Guided Tour\",\n            theme=\"light\",\n        )\n        self.export_tour(filename=\"shepherd_google_maps_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/tour_examples/xkcd_tour.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_create_tour(self):\n        self.open(\"https://xkcd.com/1117/\")\n        self.assert_element('img[alt=\"My Sky\"]')\n        self.create_tour(theme=\"dark\")\n        self.add_tour_step(\"Welcome to XKCD!\")\n        self.add_tour_step(\"This is the XKCD logo.\", \"#masthead img\")\n        self.add_tour_step(\"Here's the daily webcomic.\", \"#comic img\")\n        self.add_tour_step(\"This is the title.\", \"#ctitle\", alignment=\"top\")\n        self.add_tour_step(\"Click here for the next comic.\", 'a[rel=\"next\"]')\n        self.add_tour_step(\"Click here for the previous one.\", 'a[rel=\"prev\"]')\n        self.add_tour_step(\"Learn about the author here.\", 'a[rel=\"author\"]')\n        self.add_tour_step(\"Click here for the license.\", 'a[rel=\"license\"]')\n        self.add_tour_step(\"Click for a random comic.\", 'a[href*=\"/random/\"]')\n        self.add_tour_step(\"Thanks for taking this tour!\")\n        self.export_tour(filename=\"xkcd_tour.js\")\n        self.play_tour()\n"
  },
  {
    "path": "examples/translations/ReadMe.md",
    "content": "<a id=\"language_tests\"></a>\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> 🌏 Translated Tests 🈺</h2>\n\n<b>SeleniumBase</b> supports the following 10 languages: <i>English</i>, <i>Chinese</i>, <i>Dutch</i>, <i>French</i>, <i>Italian</i>, <i>Japanese</i>, <i>Korean</i>, <i>Portuguese</i>, <i>Russian</i>, and <i>Spanish</i>. (Examples can be found in <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples/translations\">SeleniumBase/examples/translations</a>)\n\nMulti-language tests run with **pytest** like other tests. Test methods have a one-to-one mapping to supported languages. Here's an example of a translated test:\n\n```python\n# Chinese Translation\nfrom seleniumbase.translate.chinese import 硒测试用例\n\nclass 我的测试类(硒测试用例):\n    def test_例子1(self):\n        self.开启(\"https://zh.wikipedia.org/wiki/\")\n        self.断言标题(\"维基百科，自由的百科全书\")\n        self.断言元素('a[title=\"Wikipedia:关于\"]')\n        self.如果可见请单击('button[aria-label=\"关闭\"]')\n        self.如果可见请单击('button[aria-label=\"關閉\"]')\n        self.断言元素('span:contains(\"创建账号\")')\n        self.断言元素('span:contains(\"登录\")')\n        self.输入文本('input[name=\"search\"]', \"舞龍\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言文本(\"舞龍\", \"#firstHeading\")\n        self.断言元素('img[src*=\"Chinese_draak.jpg\"]')\n```\n\nHere's another example:\n\n```python\n# Japanese Translation\nfrom seleniumbase.translate.japanese import セレニウムテストケース\n\nclass 私のテストクラス(セレニウムテストケース):\n    def test_例1(self):\n        self.を開く(\"https://ja.wikipedia.org/wiki/\")\n        self.テキストを確認する(\"ウィキペディア\")\n        self.要素を確認する('[title*=\"ウィキペディアへようこそ\"]')\n        self.JS入力('input[name=\"search\"]', \"アニメ\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"アニメ\", \"#firstHeading\")\n        self.JS入力('input[name=\"search\"]', \"寿司\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"寿司\", \"#firstHeading\")\n        self.要素を確認する('img[src*=\"Various_sushi\"]')\n```\n\n<a id=\"translation_api\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Translation API 🈺</h2>\n\nYou can use SeleniumBase to selectively translate the method names of any test from one language to another with the console scripts interface. Additionally, the ``import`` line at the top of the Python file will change to import the new ``BaseCase``. Example: ``BaseCase`` becomes ``CasoDeTeste`` when a test is translated into Portuguese.\n\n```zsh\nseleniumbase translate\n```\n\n```zsh\n* Usage:\nseleniumbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\n\n* Languages:\n``--en`` / ``--English``  |  ``--zh`` / ``--Chinese``\n``--nl`` / ``--Dutch``    |  ``--fr`` / ``--French``\n``--it`` / ``--Italian``  |  ``--ja`` / ``--Japanese``\n``--ko`` / ``--Korean``   |  ``--pt`` / ``--Portuguese``\n``--ru`` / ``--Russian``  |  ``--es`` / ``--Spanish``\n\n* Actions:\n``-p`` / ``--print``  (Print translation output to the screen)\n``-o`` / ``--overwrite``  (Overwrite the file being translated)\n``-c`` / ``--copy``  (Copy the translation to a new ``.py`` file)\n\n* Options:\n``-n``  (include line Numbers when using the Print action)\n\n* Examples:\nTranslate test_1.py into Chinese and only print the output:\n>>> seleniumbase translate test_1.py --zh  -p\nTranslate test_2.py into Portuguese and overwrite the file:\n>>> seleniumbase translate test_2.py --pt  -o\nTranslate test_3.py into Dutch and make a copy of the file:\n>>> seleniumbase translate test_3.py --nl  -c\n\n* Output:\nTranslates a SeleniumBase Python file into the language\nspecified. Method calls and ``import`` lines get swapped.\nBoth a language and an action must be specified.\nThe ``-p`` action can be paired with one other action.\nWhen running with ``-c`` (or ``--copy``) the new file name\nwill be the original name appended with an underscore\nplus the 2-letter language code of the new language.\n(Example: Translating ``test_1.py`` into Japanese with\n``-c`` will create a new file called ``test_1_ja.py``.)\n```\n\n--------\n\n<h3 align=\"left\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_m.png\" title=\"SeleniumBase\" width=\"280\" /></a></h3>\n"
  },
  {
    "path": "examples/translations/chinese_test_1.py",
    "content": "# Chinese Language Test\nfrom seleniumbase.translate.chinese import 硒测试用例\n硒测试用例.main(__name__, __file__)\n\n\nclass 我的测试类(硒测试用例):\n    def test_例子1(self):\n        self.开启(\"https://zh.wikipedia.org/wiki/\")\n        self.断言标题(\"维基百科，自由的百科全书\")\n        self.断言元素('a[title=\"Wikipedia:关于\"]')\n        self.如果可见请单击('button[aria-label=\"关闭\"]')\n        self.如果可见请单击('button[aria-label=\"關閉\"]')\n        self.断言元素('span:contains(\"创建账号\")')\n        self.断言元素('span:contains(\"登录\")')\n        self.输入文本('input[name=\"search\"]', \"舞龍\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言文本(\"舞龍\", \"#firstHeading\")\n        self.断言元素('img[src*=\"Chinese_draak.jpg\"]')\n        self.回去()\n        self.输入文本('input[name=\"search\"]', \"麻婆豆腐\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言文本(\"麻婆豆腐\", \"#firstHeading\")\n        self.断言元素('figure:contains(\"一家中餐館的麻婆豆腐\")')\n        self.回去()\n        self.输入文本('input[name=\"search\"]', \"精武英雄\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言元素('img[src*=\"Fist_of_legend.jpg\"]')\n        self.断言文本(\"李连杰\", 'li a[title=\"李连杰\"]')\n"
  },
  {
    "path": "examples/translations/dutch_test_1.py",
    "content": "# Dutch Language Test\nfrom seleniumbase.translate.dutch import Testgeval\nTestgeval.main(__name__, __file__)\n\n\nclass MijnTestklasse(Testgeval):\n    def test_voorbeeld_1(self):\n        self.openen(\"https://nl.wikipedia.org/wiki/Hoofdpagina\")\n        self.controleren_element('a[title*=\"Welkom voor nieuwkomers\"]')\n        self.controleren_tekst(\"Welkom op Wikipedia\", \"td.hp-welkom\")\n        self.typ(\"#searchform input\", \"Stroopwafel\")\n        self.klik(\"#searchform button\")\n        self.controleren_tekst(\"Stroopwafel\", \"#firstHeading\")\n        self.controleren_element('img[src*=\"Stroopwafels\"]')\n        self.typ(\"#searchform input\", \"Rijksmuseum Amsterdam\")\n        self.klik(\"#searchform button\")\n        self.controleren_tekst(\"Rijksmuseum\", \"#firstHeading\")\n        self.controleren_element('img[src*=\"Rijksmuseum\"]')\n        self.terug()\n        self.controleren_url_bevat(\"Stroopwafel\")\n        self.vooruit()\n        self.controleren_url_bevat(\"Rijksmuseum\")\n"
  },
  {
    "path": "examples/translations/english_test_1.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass MyTestClass(BaseCase):\n    def test_example_1(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n        self.assert_element(\"#inventory_container\")\n        self.assert_exact_text(\"Products\", \"span.title\")\n        self.click('button[name*=\"backpack\"]')\n        self.click(\"#shopping_cart_container a\")\n        self.assert_exact_text(\"Your Cart\", \"span.title\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click('button:contains(\"Remove\")')  # HTML innerText\n        self.assert_text_not_visible(\"Backpack\", \"div.cart_item\")\n        self.js_click(\"a#logout_sidebar_link\")\n"
  },
  {
    "path": "examples/translations/french_test_1.py",
    "content": "# French Language Test\nfrom seleniumbase.translate.french import CasDeBase\nCasDeBase.main(__name__, __file__)\n\n\nclass MaClasseDeTest(CasDeBase):\n    def test_exemple_1(self):\n        self.ouvrir(\"https://fr.wikipedia.org/wiki/\")\n        self.vérifier_texte(\"Wikipédia\")\n        self.vérifier_élément('[alt=\"Wikipédia\"]')\n        self.cliquer_si_affiché('button[aria-label=\"Fermer\"]')\n        self.js_taper(\"#searchform input\", \"Crème brûlée\")\n        self.cliquer(\"#searchform button\")\n        self.vérifier_texte(\"Crème brûlée\", \"#firstHeading\")\n        self.vérifier_élément('img[alt*=\"Crème brûlée\"]')\n        self.js_taper(\"#searchform input\", \"Jardin des Tuileries\")\n        self.cliquer(\"#searchform button\")\n        self.vérifier_texte(\"Jardin des Tuileries\", \"#firstHeading\")\n        self.vérifier_élément('img[alt*=\"Jardin des Tuileries\"]')\n        self.retour()\n        self.vérifier_url_contient(\"brûlée\")\n        self.en_avant()\n        self.vérifier_url_contient(\"Jardin\")\n"
  },
  {
    "path": "examples/translations/italian_test_1.py",
    "content": "# Italian Language Test\nfrom seleniumbase.translate.italian import CasoDiProva\nCasoDiProva.main(__name__, __file__)\n\n\nclass MiaClasseDiTest(CasoDiProva):\n    def test_esempio_1(self):\n        self.apri(\"https://it.wikipedia.org/wiki/\")\n        self.verificare_testo(\"Wikipedia\")\n        self.verificare_elemento('a[title=\"Lingua italiana\"]')\n        self.digitare('input[name=\"search\"]', \"Pizza\")\n        self.fare_clic(\"#searchform button\")\n        self.verificare_testo(\"Pizza\", \"#firstHeading\")\n        self.verificare_elemento('figure img[src*=\"pizza\"]')\n        self.digitare('input[name=\"search\"]', \"Colosseo\")\n        self.fare_clic(\"#searchform button\")\n        self.verificare_testo(\"Colosseo\", \"#firstHeading\")\n        self.verificare_elemento('figure img[src*=\"Colosseo\"]')\n        self.indietro()\n        self.verificare_url_contiene(\"Pizza\")\n        self.avanti()\n        self.verificare_url_contiene(\"Colosseo\")\n"
  },
  {
    "path": "examples/translations/japanese_test_1.py",
    "content": "# Japanese Language Test\nfrom seleniumbase.translate.japanese import セレニウムテストケース\nセレニウムテストケース.main(__name__, __file__)\n\n\nclass 私のテストクラス(セレニウムテストケース):\n    def test_例1(self):\n        self.を開く(\"https://ja.wikipedia.org/wiki/\")\n        self.テキストを確認する(\"ウィキペディア\")\n        self.要素を確認する('[title*=\"ウィキペディアへようこそ\"]')\n        self.JS入力('input[name=\"search\"]', \"アニメ\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"アニメ\", \"#firstHeading\")\n        self.JS入力('input[name=\"search\"]', \"寿司\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"寿司\", \"#firstHeading\")\n        self.要素を確認する('img[src*=\"Various_sushi\"]')\n        self.JS入力(\"#searchInput\", \"レゴランド・ジャパン\")\n        self.クリックして(\"#searchform button\")\n        self.要素を確認する('img[src*=\"LEGOLAND_JAPAN\"]')\n        self.リンクテキストを確認する(\"名古屋城\")\n        self.リンクテキストをクリックします(\"テーマパーク\")\n        self.テキストを確認する(\"テーマパーク\", \"#firstHeading\")\n"
  },
  {
    "path": "examples/translations/korean_test_1.py",
    "content": "# Korean Language Test\nfrom seleniumbase.translate.korean import 셀레늄_테스트_케이스\n셀레늄_테스트_케이스.main(__name__, __file__)\n\n\nclass 테스트_클래스(셀레늄_테스트_케이스):\n    def test_실시예_1(self):\n        self.열기(\"https://ko.wikipedia.org/wiki/\")\n        self.텍스트_확인(\"위키백과\")\n        self.요소_확인('[title=\"위키백과:소개\"]')\n        self.JS_입력(\"#searchform input\", \"김치\")\n        self.클릭(\"#searchform button\")\n        self.텍스트_확인(\"김치\", \"#firstHeading\")\n        self.요소_확인('img[src*=\"Various_kimchi.jpg\"]')\n        self.링크_텍스트_확인(\"한국 요리\")\n        self.JS_입력(\"#searchform input\", \"비빔밥\")\n        self.클릭(\"#searchform button\")\n        self.텍스트_확인(\"비빔밥\", \"#firstHeading\")\n        self.요소_확인('img[src*=\"Dolsot-bibimbap.jpg\"]')\n        self.링크_텍스트를_클릭합니다(\"돌솥비빔밥\")\n        self.텍스트_확인(\"돌솥비빔밥\", \"#firstHeading\")\n"
  },
  {
    "path": "examples/translations/portuguese_test_1.py",
    "content": "# Portuguese Language Test\nfrom seleniumbase.translate.portuguese import CasoDeTeste\nCasoDeTeste.main(__name__, __file__)\n\n\nclass MinhaClasseDeTeste(CasoDeTeste):\n    def test_exemplo_1(self):\n        self.abrir(\"https://pt.wikipedia.org/wiki/\")\n        self.verificar_texto(\"Wikipédia\")\n        self.verificar_elemento('[title=\"Língua portuguesa\"]')\n        self.digitar(\"#searchform input\", \"João Pessoa\")\n        self.clique(\"#searchform button\")\n        self.verificar_texto(\"João Pessoa\", \"#firstHeading\")\n        self.verificar_elemento('img[alt*=\"João Pessoa\"]')\n        self.digitar(\"#searchform input\", \"Florianópolis\")\n        self.clique(\"#searchform button\")\n        self.verificar_texto(\"Florianópolis\", \"h1#firstHeading\")\n        self.verificar_elemento('td:contains(\"Avenida Beira-Mar\")')\n        self.voltar()\n        self.verificar_url_contém(\"João_Pessoa\")\n        self.atualizar_a_página()\n        self.js_digitar(\"#searchform input\", \"Teatro Amazonas\")\n        self.clique(\"#searchform button\")\n        self.verificar_texto(\"Teatro Amazonas\", \"#firstHeading\")\n        self.verificar_texto_do_link(\"Festival Amazonas de Ópera\")\n"
  },
  {
    "path": "examples/translations/pytest.ini",
    "content": "[pytest]\n\n# Display console output. Disable cacheprovider:\naddopts = --capture=tee-sys -p no:cacheprovider\n\n# Skip these directories during test collection:\nnorecursedirs = .* build dist recordings temp assets\n\n# Ignore DeprecationWarning, PytestUnknownMarkWarning\nfilterwarnings =\n    ignore::pytest.PytestWarning\n    ignore:.*U.*mode is deprecated:DeprecationWarning\n\n# Configure the junit_family option explicitly:\njunit_family = legacy\n\n# Set pytest discovery rules:\n# (Most of the rules here are similar to the default rules.)\n# (Inheriting unittest.TestCase could override these rules.)\npython_files = test_*.py *_test.py *_tests.py *_suite.py *_test_*.py\npython_classes = Test* *Test* *Test *Tests *Suite\npython_functions = test_*\n\n# Common pytest markers used in examples:\n# (pytest may require marker registration to prevent warnings.)\n# (Future versions may turn those marker warnings into errors.)\nmarkers =\n    marker1: custom marker\n    marker2: custom marker\n    marker3: custom marker\n    marker_test_suite: custom marker\n    expected_failure: custom marker\n    local: custom marker\n    remote: custom marker\n    offline: custom marker\n    develop: custom marker\n    qa: custom marker\n    ci: custom marker\n    e2e: custom marker\n    ready: custom marker\n    smoke: custom marker\n    deploy: custom marker\n    active: custom marker\n    master: custom marker\n    release: custom marker\n    staging: custom marker\n    production: custom marker\n"
  },
  {
    "path": "examples/translations/russian_test_1.py",
    "content": "# Russian Language Test\nfrom seleniumbase.translate.russian import ТестНаСелен\nТестНаСелен.main(__name__, __file__)\n\n\nclass МойТестовыйКласс(ТестНаСелен):\n    def test_пример_1(self):\n        self.открыть(\"https://ru.wikipedia.org/wiki/\")\n        self.подтвердить_элемент('[title=\"Русский язык\"]')\n        self.подтвердить_текст(\"Википедия\", \"div.main-wikimedia-header\")\n        self.введите(\"#searchInput\", \"МГУ\")\n        self.нажмите(\"#searchButton\")\n        self.подтвердить_текст(\"университет\", \"#firstHeading\")\n        self.подтвердить_элемент('img[alt*=\"Главное здание МГУ\"]')\n        self.введите(\"#searchInput\", \"приключения Шурика\")\n        self.нажмите(\"#searchButton\")\n        self.подтвердить_текст(\"Операция «Ы» и другие приключения Шурика\")\n        self.подтвердить_элемент('img[alt=\"Постер фильма\"]')\n        self.назад()\n        self.подтвердить_URL_содержит(\"университет\")\n        self.вперед()\n        self.подтвердить_URL_содержит(\"Шурика\")\n"
  },
  {
    "path": "examples/translations/spanish_test_1.py",
    "content": "# Spanish Language Test\nfrom seleniumbase.translate.spanish import CasoDePrueba\nCasoDePrueba.main(__name__, __file__)\n\n\nclass MiClaseDePrueba(CasoDePrueba):\n    def test_ejemplo_1(self):\n        self.abrir(\"https://es.wikipedia.org/wiki/\")\n        self.verificar_texto(\"Wikipedia\")\n        self.verificar_elemento('[title=\"Wikipedia:Bienvenidos\"]')\n        self.escriba('[name=\"search\"]', \"Parque de Atracciones Tibidabo\")\n        self.haga_clic('button:contains(\"Buscar\")')\n        self.verificar_texto(\"Tibidabo\", \"#firstHeading\")\n        self.verificar_elemento('img[src*=\"Tibidabo\"]')\n        self.escriba('input[name=\"search\"]', \"Palma de Mallorca\")\n        self.haga_clic('button:contains(\"Buscar\")')\n        self.verificar_texto(\"Palma de Mallorca\", \"#firstHeading\")\n        self.verificar_elemento('img[src*=\"Palma\"]')\n        self.volver()\n        self.verificar_url_contiene(\"Tibidabo\")\n        self.adelante()\n        self.verificar_url_contiene(\"Mallorca\")\n"
  },
  {
    "path": "examples/uc_cdp_events.py",
    "content": "from rich.pretty import pprint\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__, \"--uc\", \"--uc-cdp\")\n\n\nclass CDPTests(BaseCase):\n    def add_cdp_listener(self):\n        # (To print everything, use \"*\". Otherwise select specific headers.)\n        # self.driver.add_cdp_listener(\"*\", lambda data: print(pformat(data)))\n        self.driver.add_cdp_listener(\n            \"Network.requestWillBeSentExtraInfo\",\n            lambda data: pprint(data)\n        )\n\n    def click_turnstile_and_verify(sb):\n        sb.uc_gui_handle_captcha()\n        sb.assert_element(\"img#captcha-success\", timeout=3)\n        sb.highlight(\"img#captcha-success\", loops=8)\n\n    def test_display_cdp_events(self):\n        if not (self.undetectable and self.uc_cdp_events):\n            self.get_new_driver(undetectable=True, uc_cdp_events=True)\n        url = \"seleniumbase.io/apps/turnstile\"\n        self.uc_open_with_reconnect(url, 2)\n        self.add_cdp_listener()\n        self.click_turnstile_and_verify()\n        self.sleep(1)\n        self.refresh()\n        self.sleep(1.2)\n"
  },
  {
    "path": "examples/unit_tests/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"24\" /> pytest-specific unit tests</h3>\n\nThe tests in this folder are for basic verification of the SeleniumBase framework with pytest.\n"
  },
  {
    "path": "examples/unit_tests/verify_framework.py",
    "content": "\"\"\" SeleniumBase Verification \"\"\"\r\nimport sys\r\n\r\nheadless = \"--headless\"\r\nif \"win32\" in sys.platform:\r\n    headless = \"--chs\"\r\n\r\nif __name__ == \"__main__\":\r\n    from pytest import main\r\n    main([__file__, \"-v\", \"-s\"])\r\n\r\n\r\ndef test_simple_cases(pytester):\r\n    \"\"\"Verify a simple passing test and a simple failing test.\r\n    The failing test is marked as xfail to have it skipped.\"\"\"\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        import pytest\r\n        from seleniumbase import BaseCase\r\n        class MyTestCase(BaseCase):\r\n            def test_passing(self):\r\n                self.assert_equal('yes', 'yes')\r\n            @pytest.mark.xfail\r\n            def test_failing(self):\r\n                self.assert_equal('yes', 'no')\r\n        \"\"\"\r\n    )\r\n    result = pytester.inline_run(headless, \"--rs\", \"-v\")\r\n    assert result.matchreport(\"test_passing\").passed\r\n    assert result.matchreport(\"test_failing\").skipped\r\n\r\n\r\ndef test_basecase(pytester):\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        from seleniumbase import BaseCase\r\n        class MyTest(BaseCase):\r\n            def test_basecase(self):\r\n                self.open(\"data:text/html,<p>Hello<br><input></p>\")\r\n                self.assert_element(\"html > body\")  # selector\r\n                self.assert_text(\"Hello\", \"body p\")  # text, selector\r\n                self.type(\"input\", \"Goodbye\")  # selector, text\r\n                self.click(\"body p\")  # selector\r\n        \"\"\"\r\n    )\r\n    result = pytester.inline_run(headless, \"-v\")\r\n    assert result.matchreport(\"test_basecase\").passed\r\n\r\n\r\ndef test_run_with_dashboard(pytester):\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        from seleniumbase import BaseCase\r\n        class MyTestCase(BaseCase):\r\n            def test_1_passing(self):\r\n                self.assert_equal('yes', 'yes')\r\n            def test_2_failing(self):\r\n                self.assert_equal('yes', 'no')\r\n            def test_3_skipped(self):\r\n                self.skip(\"Skip!\")\r\n        \"\"\"\r\n    )\r\n    result = pytester.inline_run(headless, \"--rs\", \"--dashboard\", \"-v\")\r\n    assert result.matchreport(\"test_1_passing\").passed\r\n    assert result.matchreport(\"test_2_failing\").failed\r\n    assert result.matchreport(\"test_3_skipped\").skipped\r\n\r\n\r\ndef test_sb_fixture(pytester):\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        def test_sb_fixture(sb):\r\n            sb.open(\"data:text/html,<p>Hello<br><input></p>\")\r\n            sb.assert_element(\"html > body\")  # selector\r\n            sb.assert_text(\"Hello\", \"body p\")  # text, selector\r\n            sb.type(\"input\", \"Goodbye\")  # selector, text\r\n            sb.click(\"body p\")  # selector\r\n        \"\"\"\r\n    )\r\n    result = pytester.inline_run(headless, \"-v\")\r\n    assert result.matchreport(\"test_sb_fixture\").passed\r\n\r\n\r\ndef test_request_sb_fixture(pytester):\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        def test_request_sb_fixture(request):\r\n            sb = request.getfixturevalue('sb')\r\n            sb.open(\"data:text/html,<p>Hello<br><input></p>\")\r\n            sb.assert_element(\"html > body\")  # selector\r\n            sb.assert_text(\"Hello\", \"body p\")  # text, selector\r\n            sb.type(\"input\", \"Goodbye\")  # selector, text\r\n            sb.click(\"body p\")  # selector\r\n            sb.tearDown()\r\n        \"\"\"\r\n    )\r\n    result = pytester.inline_run(headless, \"-v\")\r\n    assert result.matchreport(\"test_request_sb_fixture\").passed\r\n\r\n\r\ndef check_outcome_field(outcomes, field_name, expected_value):\r\n    field_value = outcomes.get(field_name, 0)\r\n    assert field_value == expected_value, (\r\n        \"outcomes.%s has an unexpected value! \"\r\n        'Expected \"%s\" but got \"%s\"!'\r\n        % (field_name, expected_value, field_value)\r\n    )\r\n\r\n\r\ndef assert_outcomes(\r\n    result,\r\n    passed=1,\r\n    skipped=0,\r\n    failed=0,\r\n    xfailed=0,\r\n    xpassed=0,\r\n    rerun=0,\r\n):\r\n    outcomes = result.parseoutcomes()\r\n    check_outcome_field(outcomes, \"passed\", passed)\r\n    check_outcome_field(outcomes, \"skipped\", skipped)\r\n    check_outcome_field(outcomes, \"failed\", failed)\r\n    check_outcome_field(outcomes, \"xfailed\", xfailed)\r\n    check_outcome_field(outcomes, \"xpassed\", xpassed)\r\n    check_outcome_field(outcomes, \"rerun\", rerun)\r\n\r\n\r\ndef test_rerun_failures(pytester):\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        from seleniumbase import BaseCase\r\n        class MyTestCase(BaseCase):\r\n            def test_passing(self):\r\n                self.assert_equal('yes', 'yes')\r\n            def test_failing(self):\r\n                self.assert_equal('yes', 'no')\r\n        \"\"\"\r\n    )\r\n    result = pytester.runpytest(headless, \"--reruns=1\", \"--rs\", \"-v\")\r\n    assert_outcomes(result, passed=1, failed=1, rerun=1)\r\n\r\n\r\ndef test_browser_launcher(pytester):\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        import sys\r\n        from seleniumbase import get_driver\r\n        b = None\r\n        if \"win32\" in sys.platform:\r\n            b = \"chs\"\r\n        def test_browser_launcher():\r\n            success = False\r\n            try:\r\n                driver = get_driver(\"chrome\", headless=True, binary_location=b)\r\n                driver.get(\"data:text/html,<p>Data URL</p>\")\r\n                source = driver.page_source\r\n                assert \"Data URL\" in source\r\n                success = True  # No errors\r\n            finally:\r\n                driver.quit()\r\n            assert success\r\n        \"\"\"\r\n    )\r\n    result = pytester.inline_run(headless, \"-v\")\r\n    assert result.matchreport(\"test_browser_launcher\").passed\r\n\r\n\r\ndef test_framework_components(pytester):\r\n    pytester.makepyfile(\r\n        \"\"\"\r\n        import sys\r\n        from seleniumbase import get_driver\r\n        from seleniumbase import js_utils\r\n        from seleniumbase import page_actions\r\n        b = None\r\n        if \"win32\" in sys.platform:\r\n            b = \"chs\"\r\n        def test_framework_components():\r\n            success = False\r\n            try:\r\n                driver = get_driver(\"chrome\", headless=True, binary_location=b)\r\n                driver.get('data:text/html,<h1 class=\"top\">Data URL</h2>')\r\n                source = driver.page_source\r\n                assert \"Data URL\" in source\r\n                assert page_actions.is_element_visible(driver, \"h1.top\")\r\n                js_utils.highlight_with_js(driver, \"h1.top\", 2, \"\")\r\n                success = True  # No errors\r\n            finally:\r\n                driver.quit()\r\n            assert success\r\n        \"\"\"\r\n    )\r\n    result = pytester.inline_run(headless, \"-v\", \"-s\")\r\n    assert result.matchreport(\"test_framework_components\").passed\r\n"
  },
  {
    "path": "examples/upgrade_chromedriver.py",
    "content": "\"\"\"This script installs the chromedriver version that matches your Chrome.\nOn newer versions of Python, you may replace \"testdir\" with \"pytester\".\n(Run with \"pytest\")\"\"\"\nimport subprocess\n\n\nclass TestUpgradeChromedriver:\n    def basic_run(self, testdir):\n        testdir.makepyfile(\n            \"\"\"\n            from seleniumbase import BaseCase\n            class MyTestCase(BaseCase):\n                def test_passing(self):\n                    pass\n            \"\"\"\n        )\n        return testdir\n\n    def upgrade_chromedriver(self, testdir):\n        testdir.makepyfile(\n            \"\"\"\n            import subprocess\n            from seleniumbase import BaseCase\n            class MyTestCase(BaseCase):\n                def test_upgrade(self):\n                    chrome_version = self.get_chrome_version()\n                    major_chrome_ver = chrome_version.split(\".\")[0]\n                    chromedriver_ver = self.get_chromedriver_version()\n                    major_chromedriver_ver = chromedriver_ver.split(\".\")[0]\n                    if major_chromedriver_ver != major_chrome_ver:\n                        subprocess.check_call(\n                            \"sbase get chromedriver %s\" % major_chrome_ver,\n                            shell=True\n                        )\n            \"\"\"\n        )\n        return testdir\n\n    def print_versions_of_chromedriver_and_chrome(self, testdir):\n        testdir.makepyfile(\n            \"\"\"\n            from seleniumbase import BaseCase\n            class MyTestCase(BaseCase):\n                def test_print_versions(self):\n                    chrome_version = self.get_chrome_version()\n                    major_chrome_ver = chrome_version.split(\".\")[0]\n                    chromedriver_ver = self.get_chromedriver_version()\n                    major_chromedriver_ver = chromedriver_ver.split(\".\")[0]\n                    print(\n                        \"\\\\n* Now using chromedriver %s with Chrome %s\"\n                        % (chromedriver_ver, chrome_version)\n                    )\n                    if major_chromedriver_ver == major_chrome_ver:\n                        print(\n                            \"* SUCCESS: \"\n                            \"The chromedriver version is compatible \"\n                            \"with Chrome!\"\n                        )\n                    elif major_chromedriver_ver < major_chrome_ver:\n                        print(\"* !!! Version Mismatch !!!\")\n                        print(\n                            \"* The version of chromedriver is too low!\\\\n\"\n                            \"* Try upgrading to chromedriver %s manually:\\\\n\"\n                            \"* >>> sbase get chromedriver %s <<<\"\n                            % (major_chrome_ver, major_chrome_ver)\n                        )\n                    else:\n                        print(\"* !!! Version Mismatch !!!\")\n                        print(\n                            \"* The version of chromedriver is too high!\\\\n\"\n                            \"* Try downgrading to chromedriver %s manually:\\\\n\"\n                            \"* >>> sbase get chromedriver %s <<<\"\n                            % (major_chrome_ver, major_chrome_ver)\n                        )\n            \"\"\"\n        )\n        return testdir\n\n    def test_upgrade_chromedriver(self, testdir):\n        # Find out if the installed chromedriver version works with Chrome\n        subprocess.check_call(\"seleniumbase get chromedriver\", shell=True)\n        testdir = self.basic_run(testdir)\n        result = testdir.inline_run(\"--headless\", \"-s\")  # Upgrades as needed\n        try:\n            assert result.matchreport(\"test_passing\").passed\n        except Exception:\n            # Install the compatibility version of chromedriver\n            install_command = \"seleniumbase get chromedriver 72.0.3626.69\"\n            subprocess.check_call(install_command, shell=True)\n            testdir = self.upgrade_chromedriver(testdir)\n            testdir.inline_run(\"--headless\", \"-s\")\n        # Print the final installed versions of chromedriver and Chrome\n        testdir = self.print_versions_of_chromedriver_and_chrome(testdir)\n        testdir.inline_run(\"--headless\", \"-s\")\n\n\nif __name__ == \"__main__\":\n    from pytest import main\n    main([__file__])\n"
  },
  {
    "path": "examples/upload_file_test.py",
    "content": "\"\"\"Testing the self.choose_file() and self.assert_attribute() methods.\"\"\"\nimport os\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass FileUploadButtonTests(BaseCase):\n    def test_file_upload_button(self):\n        self.open(\"https://seleniumbase.io/w3schools/file_upload\")\n        self.click(\"button#runbtn\")\n        self.switch_to_frame(\"iframeResult\")\n        zoom_in = 'input[type=\"file\"]{zoom: 1.6;-moz-transform: scale(1.6);}'\n        self.add_css_style(zoom_in)\n        self.highlight('input[type=\"file\"]')\n        dir_name = os.path.dirname(os.path.abspath(__file__))\n        my_file = \"screenshot.png\"\n        file_path = os.path.join(dir_name, \"example_logs/%s\" % my_file)\n        self.assert_attribute(\"#myFile\", \"value\", \"\")\n        self.choose_file('input[type=\"file\"]', file_path)\n        self.assert_attribute(\"#myFile\", \"value\", \"C:\\\\fakepath\\\\%s\" % my_file)\n        self.highlight('input[type=\"file\"]')\n"
  },
  {
    "path": "examples/user_agent_test.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass UserAgentTests(BaseCase):\n    def test_user_agent(self):\n        self.open(\"data:text/html,<h1></h1>\")\n        user_agent_detected = self.get_user_agent()\n        self.set_text_content(\"h1\", user_agent_detected)\n        self.highlight(\"h1\")\n        original_user_agent = user_agent_detected\n        if not self.user_agent:\n            # Using the built-in user-agent string\n            print(\"\\n\\nUser-Agent: %s\" % user_agent_detected)\n        else:\n            # User-agent was overridden using: --agent=STRING\n            print(\"\\n\\nUser-Agent override: %s\" % user_agent_detected)\n        if self.headed:\n            self.sleep(2.75)\n\n        # Now change the user-agent using \"execute_cdp_cmd()\"\n        if not self.is_chromium():\n            msg = \"\\n* execute_cdp_cmd() is only for Chromium browsers\"\n            print(msg)\n            self.skip(msg)\n        print(\"\\n--------------------------\")\n        try:\n            self.execute_cdp_cmd(\n                \"Network.setUserAgentOverride\",\n                {\n                    \"userAgent\": \"Mozilla/5.0 \"\n                    \"(Nintendo Switch; WifiWebAuthApplet) \"\n                    \"AppleWebKit/606.4 (KHTML, like Gecko) \"\n                    \"NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393\"\n                },\n            )\n            self.open(\"about:blank\")\n            self.sleep(0.1)  # Enough to see that page was refreshed\n            self.open(\"data:text/html,<h1></h1>\")\n            user_agent_detected = self.get_user_agent()\n            self.set_text_content(\"h1\", user_agent_detected)\n            self.highlight(\"h1\")\n            print(\"\\nUser-Agent override: %s\" % user_agent_detected)\n            if self.headed:\n                self.sleep(2.75)\n        finally:\n            # Reset the user-agent back to the original\n            self.execute_cdp_cmd(\n                \"Network.setUserAgentOverride\",\n                {\"userAgent\": original_user_agent},\n            )\n"
  },
  {
    "path": "examples/verify_undetected.py",
    "content": "\"\"\"Determine if your browser is detectable by anti-bot services.\r\nSome sites use scripts to detect Selenium, and then block you.\r\nTo evade detection, add --uc as a pytest command-line option.\"\"\"\r\nfrom seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__, \"--uc\")\r\n\r\n\r\nclass UndetectedTest(BaseCase):\r\n    def test_browser_is_undetected(self):\r\n        url = \"https://gitlab.com/users/sign_in\"\r\n        if not self.undetectable:\r\n            self.get_new_driver(undetectable=True)\r\n        self.uc_open_with_reconnect(url)\r\n        self.uc_gui_click_captcha()\r\n        self.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\r\n        self.post_message(\"SeleniumBase wasn't detected\", duration=4)\r\n        self._print(\"\\n Success! Website did not detect Selenium! \")\r\n"
  },
  {
    "path": "examples/visual_testing/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<p align=\"center\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_f6.png\" alt=\"SeleniumBase\" width=\"445\" /></a></p>\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Automated Visual Regression Testing</h2>\n\nAutomated Visual Regression Testing can help you detect when the layout of a web page has changed. Instead of comparing pixels from screenshots, layout differences can be detected by comparing HTML tags and attributes with a baseline. If a change is detected, it could mean that something broke, the web page was redesigned, or dynamic content changed.\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=erwkoiDeNzA\"><img src=\"http://img.youtube.com/vi/erwkoiDeNzA/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"285\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=erwkoiDeNzA\">Watch the tutorial on YouTube</a></b>)</p>\n\nTo handle automated visual testing, SeleniumBase uses the ``self.check_window()`` method, which can set visual baselines for comparison and then compare the latest versions of web pages to the existing baseline.\n\nThe first time a test calls ``self.check_window()`` with a unique ``name`` parameter, the visual baseline is set, which means a folder is created with the following files:\n\n<li><b>page_url.txt</b>  ->  The URL of the current window</li>\n<li><b>baseline.png</b>  ->  The baseline screenshot (PNG)</li>\n<li><b>tags_level1.txt</b>  ->  HTML tags from the window</li>\n<li><b>tags_level2.txt</b>  ->  HTML tags + attribute names</li>\n<li><b>tags_level3.txt</b>  ->  HTML tags + attribute names+values</li>\n\nAfter the first time ``self.check_window()`` is called, later calls will compare the HTML tags and attributes of the latest window to the ones from the first call (*or to the ones from the call when the baseline was last reset*). Additionally, a ``latest.png`` screenshot is saved in the same folder, which can help you determine if/when the existing baseline needs to be reset.\n\nHere's an example call:\n\n```python\nself.check_window(name=\"first_test)\", level=3)\n```\n\nOn the first run (<i>or if the baseline is being set/reset</i>) the \"level\" doesn't matter because that's only used for comparing the current layout to the existing baseline.\n\nHere's how the level system works:\n\n<li><b>level=0</b> ->\n    DRY RUN ONLY - Will perform a comparison to the baseline, and print out any differences that are found, but won't fail the test even if differences exist.</li>\n<li><b>level=1</b> ->\n    HTML tags are compared to tags_level1.txt</li>\n<li><b>level=2</b> ->\n    HTML tags and attribute names are compared to tags_level2.txt</li>\n<li><b>level=3</b> ->\n    HTML tags and attribute names+values are compared to tags_level3.txt</li>\n\nAs shown, Level-3 is the most strict, Level-1 is the least strict. If the comparisons from the latest window to the existing baseline don't match, the current test will fail, except for Level-0 checks, which print Level-3 results without failing the test.\n\nYou can reset the visual baseline on the command line by adding the following parameter at runtime:\n\n```zsh\n--visual_baseline\n```\n\nAs long as ``--visual_baseline`` is used on the command line while running tests, the ``self.check_window()`` method cannot fail because it will rebuild the visual baseline rather than comparing the html tags of the latest run to the existing baseline. If there are any expected layout changes to a website that you're testing, you'll need to reset the baseline to prevent unnecessary failures.\n\n``self.check_window()`` will fail with \"Page Domain Mismatch Failure\" if the domain of the current URL doesn't match the domain of the baseline URL.\n\nIf you want to use ``self.check_window()`` to compare a web page to a later version of itself in the same test, add the ``baseline=True`` parameter to your first ``self.check_window()`` call to use that as the baseline. (<i>This only makes sense if you're calling ``self.check_window()`` more than once with the same \"name\" parameter in the same test.</i>)\n\nAutomated Visual Testing with ``self.check_window()`` is not very effective for websites that have dynamic content because that changes the layout and structure of web pages. For those pages, you're much better off using regular SeleniumBase functional testing, unless you can remove the dynamic content before performing the comparison, (such as by using ``self.ad_block()`` to remove dynamic ad content on a web page).\n\nExample usage of ``self.check_window()`` with different levels:\n\n```python\n    self.check_window(name=\"testing\", level=0)\n    self.check_window(name=\"xkcd_home\", level=1)\n    self.check_window(name=\"github_page\", level=2)\n    self.check_window(name=\"wikipedia_page\", level=3)\n\n    self.check_window(name=\"helloworld\", baseline=True)\n    ### Do something that may change the web page\n    self.check_window(name=\"helloworld\", level=3)\n```\n\nHere's an example where clicking a button makes a hidden element visible:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass VisualLayoutTest(BaseCase):\n    def test_applitools_layout_change_failure(self):\n        self.open('https://applitools.com/helloworld?diff1')\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        self.check_window(name=\"helloworld\", baseline=True)\n        # Click a button that changes the text of an element\n        self.click('a[href=\"?diff1\"]')\n        # Click a button that makes a hidden element visible\n        self.click(\"button\")\n        self.check_window(name=\"helloworld\", level=3)\n```\n\nHere's the output of that: (<i>Text changes do not impact visual comparisons</i>)\n\n```\nAssertionError:\nFirst differing element 39:\n['div', [['class', ['section', 'hidden-section', 'image-section']]]]\n['div', [['class', ['section', 'image-section']]]]\n\n-  ['div', [['class', ['section', 'hidden-section', 'image-section']]]],\n?                                ------------------\n+  ['div', [['class', ['section', 'image-section']]]],\n*\n*** Exception: <Level 3> Visual Diff Failure:\n* HTML tag attribute values don't match the baseline!\n```\n\nHere's an example where a button is removed from a web page:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass VisualLayoutTest(BaseCase):\n    def test_python_home_layout_change_failure(self):\n        self.open('https://python.org/')\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        self.check_window(name=\"python_home\", baseline=True)\n        # Remove the \"Donate\" button\n        self.remove_element('a.donate-button')\n        self.check_window(name=\"python_home\", level=3)\n```\n\nHere's the output of that:\n\n```\nAssertionError:\nFirst differing element 33:\n['a', [['class', ['donate-button']], ['href', '/psf/donations/']]]\n['div', [['class', ['options-bar']]]]\n\n-  ['a', [['class', ['donate-button']], ['href', '/psf/donations/']]],\n-     'display: list-item; opacity: 0.995722;']]],\n?                         -------------------\n+     'display: list-item;']]],\n*\n*** Exception: <Level 3> Visual Diff Failure:\n* HTML tag attribute values don't match the baseline!\n```\n\nHere's the ``side_by_side.html`` file for that, (from the ``./latest_logs/`` folder), which shows a visual comparison of the two screenshots as a result of the missing \"Donate\" button:\n\n<img style=\"border: 1px solid #222222;\" src=\"https://seleniumbase.github.io/cdn/img/visual_comparison.png\" title=\"SeleniumBase Visual Comparison\" />\n\nHere's another example, where a web site logo is resized:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass VisualLayoutTest(BaseCase):\n    def test_xkcd_layout_change_failure(self):\n        self.open('https://xkcd.com/554/')\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        self.check_window(name=\"xkcd_554\", baseline=True)\n        # Change height: (83 -> 130) , Change width: (185 -> 120)\n        self.set_attribute('[alt=\"xkcd.com logo\"]', \"height\", \"130\")\n        self.set_attribute('[alt=\"xkcd.com logo\"]', \"width\", \"120\")\n        self.check_window(name=\"xkcd_554\", level=3)\n```\n\nHere's the output of that:\n\n```\nAssertionError:\nFirst differing element 22:\n['img[30 chars]['height', '83'], ['src', '/s/0b7742.png'], ['width', '185']]]\n['img[30 chars]['height', '130'], ['src', '/s/0b7742.png'], ['width', '120']]]\n\n-    ['height', '83'],\n?                ^\n+    ['height', '130'],\n?                ^ +\n-    ['width', '185']]],\n?                ^^\n+    ['width', '120']]],\n?                ^^\n*\n*** Exception: <Level 3> Visual Diff Failure:\n* HTML tag attribute values don't match the baseline!\n```\n\nTo run the example (from [examples/visual_testing/](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/visual_testing/)) with a pytest HTML Report, use:\n\n```zsh\npytest test_layout_fail.py --html=report.html\n```\n\nHere's what the pytest HTML Report looks like:<br />\n[<img src=\"https://seleniumbase.github.io/cdn/img/visual_testing_report_2.png\" title=\"Test Report\">](https://seleniumbase.github.io/cdn/img/visual_testing_report_2.png)\n\n--------\n\nIn conclusion, open source automated visual testing tools are being built directly into test frameworks, and this trend is growing. Just like many years ago when free Wi-Fi at coffee shops replaced Internet cafes that charged money for Internet access, open source tools for visual testing will replace their paid counterparts in time. You'll remember this next time you're sipping your Starbucks® Pumpkin Spice Latte with your free Internet access, instead of paying for Internet at cybercafes.\n"
  },
  {
    "path": "examples/visual_testing/__init__.py",
    "content": ""
  },
  {
    "path": "examples/visual_testing/case_plans/layout_test.VisualLayoutTests.test_applitools_layout_change.md",
    "content": "``layout_test.py::VisualLayoutTests::test_applitools_layout_change``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://applitools.com/helloworld?diff1. <br /> Call ``check_window()`` with ``baseline=True``. <br /> Click the button that changes the text of an element. <br /> Call ``check_window()`` three times for ``level=1``, ``level=2``, and ``level=3``. | No issues are detected because a text change should not affect ``check_window()`` |\r\n| 2 | Click the button that makes a hidden element visible. <br /> Call ``check_window()`` three times for ``level=1``, ``level=2``, and ``level=3``, but wrap the third call with ``self.assert_raises(Exception):``. | No exceptions are raised because the first two calls should pass and the third one was wrapped with ``self.assert_raises(Exception):``. |\r\n"
  },
  {
    "path": "examples/visual_testing/case_plans/python_home_test.VisualLayoutTests.test_python_home_layout_change.md",
    "content": "``python_home_test.py::VisualLayoutTests::test_python_home_layout_change``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://python.org/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Remove the ``Donate`` button using ``remove_element(SELECTOR)``. <br /> Call ``check_window()`` with ``level=0``. | The test detects that the ``Donate`` button was removed. The test does not fail because the check was set to ``level=0`` (print-only). <br /> A ``side_by_side_NAME.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n"
  },
  {
    "path": "examples/visual_testing/case_plans/test_layout_fail.VisualLayoutFailureTests.test_applitools_change.md",
    "content": "``test_layout_fail.py::VisualLayoutFailureTests::test_applitools_change``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://applitools.com/helloworld?diff1. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Click the button that makes a hidden element visible. <br /> Call ``check_window()`` with ``level=3``. | The test fails because the element attribute has changed. <br /> A ``side_by_side.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n"
  },
  {
    "path": "examples/visual_testing/case_plans/test_layout_fail.VisualLayoutFailureTests.test_xkcd_logo_change.md",
    "content": "``test_layout_fail.py::VisualLayoutFailureTests::test_xkcd_logo_change``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://xkcd.com/554/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Resize the logo using ``set_attribute()``. <br /> Call ``check_window()`` with ``level=3``. | The test fails because the logo has changed. <br /> A ``side_by_side.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n"
  },
  {
    "path": "examples/visual_testing/case_plans/test_layout_fail.VisualLayout_FixtureTests.test_python_home_change.md",
    "content": "``test_layout_fail.py::VisualLayout_FixtureTests::test_python_home_change``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://python.org/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Remove the ``Donate`` button using ``remove_element(SELECTOR)``. <br /> Call ``check_window()`` with ``level=3``. | The test fails because the ``Donate`` button was removed. <br /> A ``side_by_side.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n"
  },
  {
    "path": "examples/visual_testing/case_plans/xkcd_visual_test.VisualLayoutTests.test_xkcd_layout_change.md",
    "content": "``xkcd_visual_test.py::VisualLayoutTests::test_xkcd_layout_change``\r\n---\r\n| # | Step Description | Expected Result |\r\n| - | ---------------- | --------------- |\r\n| 1 | Open https://xkcd.com/554/. <br /> Call ``check_window()`` with ``baseline=True``. | |\r\n| 2 | Resize the logo using ``set_attribute()``. <br /> Call ``check_window()`` with ``level=0``. | The test detects that the logo has changed. The test does not fail because the check was set to ``level=0`` (print-only). <br /> A ``side_by_side_NAME.html`` file appears in the specific ``latest_logs/`` folder of the test. |\r\n"
  },
  {
    "path": "examples/visual_testing/layout_test.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass VisualLayoutTests(BaseCase):\n    def test_xkcd_layout_change(self):\n        self.demo_mode = False  # (It would interfere with html comparisons)\n        self.open(\"https://xkcd.com/1424/\")\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        self.sleep(0.08)\n        self.check_window(name=\"xkcd\", baseline=True)\n        # Go to a different comic\n        self.open(\"https://xkcd.com/1425/\")\n        # Verify html tags match the baseline\n        self.check_window(name=\"xkcd\", level=1)\n        # Verify html tags and attribute names match the baseline\n        self.check_window(name=\"xkcd\", level=2)\n        # Verify html tags and attribute values don't match the baseline\n        with self.assert_raises(Exception):\n            self.check_window(name=\"xkcd\", level=3)\n        # Now that we know the Exception was raised as expected,\n        # let's print out the comparison results by running a Level-0 check.\n        # (NOTE: Running with level-0 will print but NOT raise an Exception.)\n        self.check_window(name=\"xkcd\", level=0)\n"
  },
  {
    "path": "examples/visual_testing/python_home_test.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass VisualLayoutTests(BaseCase):\n    def test_python_home_layout_change(self):\n        self.open(\"https://python.org/\")\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        self.check_window(name=\"python_home\", baseline=True)\n        # Remove the \"Donate\" button\n        self.remove_element(\"a.donate-button\")\n        self.check_window(name=\"python_home\", level=0)\n"
  },
  {
    "path": "examples/visual_testing/test_layout_fail.py",
    "content": "\"\"\"Visual Layout Testing with different Syntax Formats\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass VisualLayout_FixtureTests:\n    def test_python_home_change(self, sb):\n        sb.open(\"https://python.org/\")\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        sb.check_window(name=\"python_home\", baseline=True)\n        # Remove the \"Donate\" button\n        sb.remove_element(\"a.donate-button\")\n        print(\"(This test should fail)\")  # due to missing button\n        sb.check_window(name=\"python_home\", level=3)\n\n\nclass VisualLayoutFailureTests(BaseCase):\n    def test_xkcd_logo_change(self):\n        self.open(\"https://xkcd.com/554/\")\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        self.check_window(name=\"xkcd_554\", baseline=True)\n        # Change height: (83 -> 110) , Change width: (185 -> 120)\n        self.set_attribute('[alt=\"xkcd.com logo\"]', \"height\", \"110\")\n        self.set_attribute('[alt=\"xkcd.com logo\"]', \"width\", \"120\")\n        print(\"(This test should fail)\")  # due to a resized logo\n        self.check_window(name=\"xkcd_554\", level=3)\n"
  },
  {
    "path": "examples/visual_testing/xkcd_visual_test.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass VisualLayoutTests(BaseCase):\n    def test_xkcd_layout_change(self):\n        self.open(\"https://xkcd.com/554/\")\n        print('\\nCreating baseline in \"visual_baseline\" folder.')\n        self.check_window(name=\"xkcd_554\", baseline=True)\n        # Change height: (83 -> 130) , Change width: (185 -> 120)\n        self.set_attribute('[alt=\"xkcd.com logo\"]', \"height\", \"130\")\n        self.set_attribute('[alt=\"xkcd.com logo\"]', \"width\", \"120\")\n        self.check_window(name=\"xkcd_554\", level=0)\n"
  },
  {
    "path": "examples/wordle_test.py",
    "content": "\"\"\"Solve Wordle with SeleniumBase.\"\"\"\r\nimport ast\r\nimport random\r\nimport requests\r\nfrom seleniumbase import BaseCase\r\n\r\n\r\nclass WordleTests(BaseCase):\r\n    word_list = []\r\n\r\n    def initialize_word_list(self):\r\n        txt_file = \"https://seleniumbase.github.io/cdn/txt/wordle_words.txt\"\r\n        word_string = requests.get(txt_file, timeout=3).text\r\n        self.word_list = ast.literal_eval(word_string)\r\n\r\n    def modify_word_list(self, word, letter_status):\r\n        new_word_list = []\r\n        correct_letters = []\r\n        present_letters = []\r\n        for i in range(len(word)):\r\n            if letter_status[i] == \"correct\":\r\n                correct_letters.append(word[i])\r\n                for w in self.word_list:\r\n                    if w[i] == word[i]:\r\n                        new_word_list.append(w)\r\n                self.word_list = new_word_list\r\n                new_word_list = []\r\n        for i in range(len(word)):\r\n            if letter_status[i] == \"present\":\r\n                present_letters.append(word[i])\r\n                for w in self.word_list:\r\n                    if word[i] in w and word[i] != w[i]:\r\n                        new_word_list.append(w)\r\n                self.word_list = new_word_list\r\n                new_word_list = []\r\n        for i in range(len(word)):\r\n            if letter_status[i] == \"absent\":\r\n                if (\r\n                    word[i] not in correct_letters\r\n                    and word[i] not in present_letters\r\n                ):\r\n                    for w in self.word_list:\r\n                        if word[i] not in w:\r\n                            new_word_list.append(w)\r\n                else:\r\n                    for w in self.word_list:\r\n                        if word[i] != w[i]:\r\n                            new_word_list.append(w)\r\n                self.word_list = new_word_list\r\n                new_word_list = []\r\n\r\n    def test_wordle(self):\r\n        if self.headless:\r\n            self.open_if_not_url(\"about:blank\")\r\n            self.skip(\"Skip this test in headless mode!\")\r\n        self.open(\"https://www.nytimes.com/games/wordle/index.html\")\r\n        self.click_if_visible(\"button.purr-blocker-card__button\", timeout=2)\r\n        self.click_if_visible('button:contains(\"Play\")', timeout=2)\r\n        self.click_if_visible('svg[data-testid=\"icon-close\"]', timeout=2)\r\n        self.remove_elements(\"div.place-ad\")\r\n        self.initialize_word_list()\r\n        random.seed()\r\n        word = random.choice(self.word_list)\r\n        num_attempts = 0\r\n        found_word = False\r\n        for attempt in range(6):\r\n            num_attempts += 1\r\n            if len(self.word_list) == 0:\r\n                self.fail(\"Today's word was not found in my dictionary!\")\r\n            word = random.choice(self.word_list)\r\n            letters = []\r\n            for letter in word:\r\n                letters.append(letter)\r\n                button = 'button[data-key=\"%s\"]' % letter\r\n                self.click(button)\r\n            button = 'button[class*=\"oneAndAHalf\"]'\r\n            self.click(button)\r\n            row = (\r\n                'div[class*=\"Board\"] div[class*=\"Row-module\"]:nth-of-type(%s) '\r\n                % num_attempts\r\n            )\r\n            tile = row + 'div:nth-child(%s) div[class*=\"module_tile__\"]'\r\n            self.wait_for_element(tile % \"5\" + '[data-state$=\"t\"]')\r\n            self.wait_for_element(tile % \"5\" + '[data-animation=\"idle\"]')\r\n            letter_status = []\r\n            for i in range(1, 6):\r\n                letter_eval = self.get_attribute(tile % str(i), \"data-state\")\r\n                letter_status.append(letter_eval)\r\n            if letter_status.count(\"correct\") == 5:\r\n                found_word = True\r\n                break\r\n            self.word_list.remove(word)\r\n            self.modify_word_list(word, letter_status)\r\n\r\n        self.save_screenshot_to_logs()\r\n        if found_word:\r\n            print('\\nWord: \"%s\"\\nAttempts: %s' % (word.upper(), num_attempts))\r\n        else:\r\n            print('Final guess: \"%s\" (Not the correct word!)' % word.upper())\r\n            self.fail(\"Unable to solve for the correct word in 6 attempts!\")\r\n        self.sleep(3)\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    from pytest import main\r\n    main([__file__, \"-s\"])\r\n"
  },
  {
    "path": "examples/xpath_test.py",
    "content": "\"\"\"Test that SeleniumBase can autodetect and use xpath selectors.\"\"\"\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass XPathTests(BaseCase):\n    def test_xpath(self):\n        self.open(\"https://seleniumbase.io/demo_page\")\n        self.assert_element('//h1[(text()=\"Demo Page\")]')\n        self.type('//*[@id=\"myTextInput\"]', \"XPath Test!\")\n        self.click('//button[starts-with(text(),\"Click Me\")]')\n        self.assert_element('//button[contains(., \"Purple\")]')\n        self.assert_text(\"SeleniumBase\", \"//table/tbody/tr[1]/td[2]/h2\")\n"
  },
  {
    "path": "examples/youtube_search_test.py",
    "content": "from seleniumbase import BaseCase\r\nBaseCase.main(__name__, __file__)\r\n\r\n\r\nclass YouTubeSearchTests(BaseCase):\r\n    def test_youtube_autocomplete_results(self):\r\n        \"\"\"Verify YouTube autocomplete search results.\"\"\"\r\n        if self.headless or self.browser == \"safari\":\r\n            self.open_if_not_url(\"about:blank\")\r\n            print(\"\\n  Unsupported mode for this test.\")\r\n            self.skip(\"Unsupported mode for this test.\")\r\n        self.open(\"https://www.youtube.com/c/MichaelMintz\")\r\n        search_term = \"seleniumbase\"\r\n        search_selector = 'input[name=\"search_query\"]'\r\n        results_selector = '[role=\"listbox\"]'\r\n        self.click_if_visible('button[aria-label=\"Close\"]')\r\n        self.sleep(0.25)\r\n        self.double_click(search_selector)\r\n        self.sleep(0.25)\r\n        self.type(search_selector, search_term)\r\n        self.sleep(0.25)\r\n        # First verify that an autocomplete result exists\r\n        self.assert_element(results_selector)\r\n        self.sleep(0.25)\r\n        top_results = self.get_text(results_selector)\r\n        # Now verify that the autocomplete result is good\r\n        self.assert_true(\r\n            search_term in top_results,\r\n            'Expected text \"%s\" not found in top results! '\r\n            'Actual text was \"%s\"!' % (search_term, top_results),\r\n        )\r\n        self.sleep(1)\r\n"
  },
  {
    "path": "help_docs/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<p align=\"center\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_word_cloud.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"330\" /></a></p>\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Help Docs</h2>\n\n<p align=\"left\">\n<a href=\"https://seleniumbase.io/#python_installation\">🚀 Start</a> |\n<a href=\"https://seleniumbase.io/examples/example_logs/ReadMe/\">📊 Dashboard</a>\n<br />\n<a href=\"https://seleniumbase.io/help_docs/features_list/\">🏰 Features</a> |\n<a href=\"https://seleniumbase.io/help_docs/customizing_test_runs/\">🎛️ Options</a>\n<br />\n<a href=\"https://seleniumbase.io/examples/ReadMe/\">📚 Examples</a> |\n<a href=\"https://seleniumbase.io/help_docs/mobile_testing/\">📱 Emulator</a>\n<br />\n<a href=\"https://seleniumbase.io/seleniumbase/console_scripts/ReadMe/\">🪄 Console Scripts</a> |\n<a href=\"https://seleniumbase.io/seleniumbase/utilities/selenium_grid/ReadMe/\">🌐 Grid</a>\n<br />\n<a href=\"https://seleniumbase.io/help_docs/method_summary/\">📘 Methods / APIs</a> |\n<a href=\"https://seleniumbase.io/examples/tour_examples/ReadMe/\">🚎 Tours</a>\n<br />\n<a href=\"https://seleniumbase.io/help_docs/syntax_formats/\">🔠 Syntax Formats</a> |\n<a href=\"https://seleniumbase.io/integrations/github/workflows/ReadMe/\">🤖 CI/CD</a>\n<br />\n<a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples/boilerplates\">♻️ Boilerplates</a> |\n<a href=\"https://seleniumbase.io/help_docs/locale_codes/\">🗾 Locale Codes</a>\n<br />\n<a href=\"https://seleniumbase.io/help_docs/js_package_manager/\">❇️ JS Manager</a> |\n<a href=\"https://seleniumbase.io/examples/visual_testing/ReadMe/\">🖼️ Visual Testing</a>\n<br />\n<a href=\"https://seleniumbase.io/help_docs/translations/\">🌏 Translator</a> |\n<a href=\"https://seleniumbase.io/examples/dialog_boxes/ReadMe/\">🛂 Dialog Boxes</a>\n<br />\n<a href=\"https://seleniumbase.io/help_docs/recorder_mode/\">🔴 Recorder</a> |\n<a href=\"https://seleniumbase.io/devices/?url=seleniumbase.io\">🖥️ Device Farm</a>\n<br />\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/playwright/ReadMe.md\">🎭 Stealthy Playwright Mode</a>\n<br />\n<a href=\"https://seleniumbase.io/examples/presenter/ReadMe/\">🎞️ Slides</a> |\n<a href=\"https://seleniumbase.io/examples/chart_maker/ReadMe/\">📶 Chart Maker</a>\n<br / >\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/commander.md\">🎖️ GUI</a> |\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md\">👤 UC Mode</a>\n<br / >\n<a href=\"https://seleniumbase.io/examples/cdp_mode/ReadMe/\">🐙 CDP Mode</a>\n</p>\n\n--------\n\n<h3>Table of Contents (<a href=\"https://seleniumbase.io\">seleniumbase.io</a>)</h3>\n\n<div><a href=\"https://seleniumbase.io/help_docs/features_list/\"><b>Features List</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/customizing_test_runs/\"><b>Command Line Tutorial</b></a></div>\n<div><a href=\"https://seleniumbase.io/examples/ReadMe/\"><b>Usage Examples</b></a></div>\n<div><a href=\"https://seleniumbase.io/demo_page\"><b>Demo Page for Tests</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/how_it_works/\"><b>How SeleniumBase Works</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/install_python_pip_git/\"><b>Installing Python, Pip, & Git</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/virtualenv_instructions/\"><b>Python Virtual Env Tutorial</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/install/\"><b>SeleniumBase Installation</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/webdriver_installation/\"><b>Webdriver Installation</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/verify_webdriver/\"><b>Verify Webdriver Works</b></a></div>\n<div><a href=\"https://seleniumbase.io/seleniumbase/console_scripts/ReadMe/\"><b>Console Scripts Tutorial</b></a></div>\n<div><a href=\"https://seleniumbase.io/examples/example_logs/ReadMe/\"><b>The Dashboard</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/recorder_mode/\"><b>Recorder Mode</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/commander/\"><b>pytest Commander</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/method_summary/\"><b>Method Summary</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/syntax_formats/\"><b>Syntax Formats</b></a></div>\n<div><a href=\"https://seleniumbase.io/examples/behave_bdd/ReadMe/\"><b>Behave BDD</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/behave_gui/\"><b>Behave Commander</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/mobile_testing/\"><b>Mobile Device Testing</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/case_plans/\"><b>Case Plans</b></a></div>\n<div><a href=\"https://seleniumbase.io/examples/chart_maker/ReadMe/\"><b>Chart Maker</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/translations/\"><b>Language Translations</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/locale_codes/\"><b>Language Locale Codes</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/js_package_manager/\"><b>JS Package Manager</b></a></div>\n<div><a href=\"https://seleniumbase.io/examples/tour_examples/ReadMe/\"><b>Tour Maker</b></a></div>\n<div><a href=\"https://seleniumbase.io/examples/presenter/ReadMe/\"><b>Presentation Maker</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/handling_iframes/\"><b>Handling iframes</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/uc_mode/\"><b>Undetected Mode (UC Mode)</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/mysql_installation/\"><b>MySQL Installation Overview</b></a></div>\n<div><a href=\"https://seleniumbase.io/seleniumbase/utilities/selenium_grid/ReadMe/\"><b>Using the Selenium Grid</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/desired_capabilities/\"><b>Browser Desired Capabilities</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/using_safari_driver/\"><b>Safari Driver Detailed Info</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/hidden_files_info/\"><b>Seeing Hidden Files on macOS</b></a></div>\n<div><a href=\"https://seleniumbase.io/help_docs/happy_customers/\"><b>Case Studies</b></a></div>\n\n--------\n\n<h3>Demo Pages / Web Examples</h3>\n\n<div><a href=\"https://seleniumbase.io/coffee/\"><b>Coffee Cart (Test Page)</b></a></div>\n<div><a href=\"https://seleniumbase.io/demo_page\"><b>Demo Page (Test Page)</b></a></div>\n<div><a href=\"https://seleniumbase.io/simple/login\"><b>Simple App (Test Page)</b></a></div>\n<div><a href=\"https://seleniumbase.io/realworld/login\"><b>MFA Login (Test Page)</b></a></div>\n<div><a href=\"https://seleniumbase.io/tinymce/\"><b>TinyMCE (Test Page)</b></a></div>\n<div><a href=\"https://seleniumbase.io/error_page/\"><b>Error Page (Test Page)</b></a></div>\n<div><a href=\"https://seleniumbase.io/other/drag_and_drop\"><b>Drag & Drop (Test Page)</b></a></div>\n<div><a href=\"https://seleniumbase.io/devices/\"><b>Device Farm (Virtual)</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/\"><b>HTML Playground Page</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/sbase\"><b>SeleniumBase in iframe</b></a></div>\n<div><a href=\"https://seleniumbase.io/other/broken_page\"><b>Page with broken links</b></a></div>\n<div><a href=\"https://seleniumbase.io/other/shadow_dom\"><b>Shadow DOM/Root</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/iframes\"><b>W3Schools iframes</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/file_upload\"><b>W3Schools file upload</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/double_click\"><b>W3Schools doubleclick</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/drag_drop\"><b>W3Schools drag & drop</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/checkboxes\"><b>W3Schools checkboxes</b></a></div>\n<div><a href=\"https://seleniumbase.io/w3schools/radio_buttons\"><b>W3Schools radio buttons</b></a></div>\n\n--------\n\n<h3>Presentations</h3>\n\n<div><a href=\"https://seleniumbase.io/other/presenter.html\"><b>Presenter Demo</b></a></div>\n<div><a href=\"https://seleniumbase.io/other/core_presentation.html\"><b>Core Presentation</b></a></div>\n<div><a href=\"https://seleniumbase.io/other/chart_presentation.html\"><b>Chart Maker Demo</b></a></div>\n<div><a href=\"https://seleniumbase.io/other/py_virtual_envs.html\"><b>Python Virtual Envs</b></a></div>\n\n--------\n\n<h3>GitHub Pages (<a href=\"https://seleniumbase.dev\">seleniumbase.dev</a>)</h3>\n\n<div><a href=\"https://seleniumbase.dev/help_docs/features_list\"><b>Features List</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/customizing_test_runs\"><b>Command Line Tutorial</b></a></div>\n<div><a href=\"https://seleniumbase.dev/examples/\"><b>Usage Examples</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/how_it_works\"><b>How SeleniumBase Works</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/install_python_pip_git\"><b>Installing Python, Pip, & Git</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/virtualenv_instructions\"><b>Python Virtual Env Tutorial</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/install\"><b>SeleniumBase Installation</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/webdriver_installation\"><b>Webdriver Installation</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/verify_webdriver\"><b>Verify Webdriver Works</b></a></div>\n<div><a href=\"https://seleniumbase.dev/seleniumbase/console_scripts/\"><b>Console Scripts Tutorial</b></a></div>\n<div><a href=\"https://seleniumbase.dev/examples/example_logs/\"><b>The Dashboard</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/recorder_mode\"><b>Recorder Mode</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/commander\"><b>pytest Commander</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/syntax_formats\"><b>Syntax Formats</b></a></div>\n<div><a href=\"https://seleniumbase.dev/examples/behave_bdd/\"><b>Behave BDD</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/behave_gui\"><b>Behave Commander</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/mobile_testing\"><b>Mobile Device Testing</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/method_summary\"><b>Method Summary (API Ref)</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/case_plans\"><b>Case Plans</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/translations\"><b>Language Translations</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/locale_codes\"><b>Language Locale Codes</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/js_package_manager\"><b>JS Package Manager</b></a></div>\n<div><a href=\"https://seleniumbase.dev/examples/tour_examples/\"><b>Tour Examples</b></a></div>\n<div><a href=\"https://seleniumbase.dev/examples/presenter/\"><b>Presentation Maker</b></a></div>\n<div><a href=\"https://seleniumbase.dev/examples/chart_maker/\"><b>Chart Maker</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/mysql_installation\"><b>Handling iframes</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/handling_iframes\"><b>MySQL Installation Overview</b></a></div>\n<div><a href=\"https://seleniumbase.dev/seleniumbase/utilities/selenium_grid/\"><b>Using the Selenium Grid</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/desired_capabilities\"><b>Browser Desired Capabilities</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/using_safari_driver\"><b>Safari Driver Detailed Info</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/hidden_files_info\"><b>Seeing Hidden Files on macOS</b></a></div>\n<div><a href=\"https://seleniumbase.dev/help_docs/happy_customers\"><b>Case Studies</b></a></div>\n\n--------\n\n<p align=\"center\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\">\n<img src=\"https://seleniumbase.github.io/img/sb_logo_10.png\" alt=\"SeleniumBase\" width=\"260\" />\n</a></p>\n<!-- View on GitHub -->\n"
  },
  {
    "path": "help_docs/behave_gui.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> SeleniumBase Behave GUI / Commander 🐝🎖️</h2>\n\n🐝🎖️ The <b translate=\"no\">SeleniumBase Behave GUI</b> lets you run <code translate=\"no\">behave</code> scripts from a Desktop GUI.<br>\n\n🐝🎖️ To launch it, call ``sbase behave-gui`` or ``sbase gui-behave``:\n\n```zsh\n> sbase behave-gui\n* Starting the SeleniumBase Behave Commander GUI App...\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sbase_behave_gui_wide_5.png\" title=\"SeleniumBase Behave GUI\" width=\"600\">\n\n🐝🎖️ <b translate=\"no\">SeleniumBase Behave GUI</b> loads the same tests that are found by:\n\n```zsh\nbehave -d\n```\n\n🐝🎖️ You can customize which tests are loaded by passing additional args:\n\n```zsh\nsbase behave-gui [OPTIONAL PATH or TEST FILE]\n```\n\n🐝🎖️ Here are examples of customizing test collection:\n\n```zsh\nsbase behave-gui  # all tests\nsbase behave-gui -i=calculator  # tests with \"calculator\" in the name\nsbase behave-gui features/  # tests located in the \"features/\" folder\nsbase behave-gui features/calculator.feature  # tests in that feature\n```\n\n🐝🎖️ Once launched, you can further customize which tests to run and what settings to use. There are various controls for changing settings, modes, and other \"behave\" command line options that are specific to SeleniumBase. You can also set additional options that don't have a visible toggle. When you're ready to run the selected tests with the specified options, click on the <code translate=\"no\">Run Selected Tests</code> button.\n\n🐝⚪ With the Dashboard enabled, you'll get one of these:\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_behave_dashboard.png\" title=\"SeleniumBase\" width=\"600\">\n\n--------\n\n<div>To learn more about SeleniumBase, check out the Docs Site:</div>\n<a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a>\n\n<div>All the code is on GitHub:</div>\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase on GitHub\" /></a>\n"
  },
  {
    "path": "help_docs/case_plans.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> SeleniumBase Case Plans 🗂️</h2>\n\n<img src=\"https://seleniumbase.github.io/cdn/img/cp/sb_case_plans.png\" title=\"SeleniumBase Case Plans Summary\" width=\"625\">\n\n🗂️ <b>SeleniumBase Case Plans</b> is Test Case Management Software that uses Markdown tables for displaying test plans directly in GitHub (and other source code management systems that support Markdown format).\n\n🗂️ The ``case_summary.md`` file is generated from individual Case Plans that exist in the ``case_plans/`` folders of your repository. (See the example below to learn how the Case Summary file may look.)\n\n--------\n\n> **Example of a ``case_summary.md`` file:**\n\n<h2>Summary of existing Case Plans</h2>\n\n|   |    |   |\n| - | -: | - |\n| 🔵 | 8 | Case Plans with customized tables |\n| ⭕ | 2 | Case Plans using boilerplate code |\n| 🚧 | 1 | Case Plan that is missing a table |\n\n--------\n\n<h3>🔎 (Click rows to expand) 🔍</h3>\n\n<details>\n<summary> 🔵 <code><b>basic_test.py::MyTestClass::test_basics</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\n| 2 | Click on the ``Backpack`` ``ADD TO CART`` button. | The button text changed to ``REMOVE``. |\n| 3 | Click on the cart icon. | The ``Backpack`` is seen in the cart. |\n| 4 | Remove the ``Backpack`` from the cart. | The ``Backpack`` is no longer in the cart. |\n| 5 | Log out from the website. | Logout was successful. |\n\n</details>\n\n<details>\n<summary> 🔵 <code><b>list_assert_test.py::MyTestClass::test_assert_list_of_elements</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Open https://seleniumbase.io/demo_page. | |\n| 2 | Use ``self.assert_elements_present(\"head\", \"style\", \"script\")`` to verify that multiple elements are present in the HTML. | The assertion is successful. |\n| 3 | Use ``self.assert_elements(\"h1\", \"h2\", \"h3\")`` to verify that multiple elements are visible. | The assertion is successful. |\n| 4 | Use ``self.assert_elements([\"#myDropdown\", \"#myButton\", \"#svgRect\"])`` to verify that multiple elements are visible. | The assertion is successful. |\n\n</details>\n\n<details>\n<summary> ⭕ <code><b>locale_code_test.py::LocaleCodeTests::test_locale_code</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Perform Action 1 | Verify Action 1 |\n| 2 | Perform Action 2 | Verify Action 2 |\n\n</details>\n\n<details>\n<summary> 🔵 <code><b>my_first_test.py::MyTestClass::test_swag_labs</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\n| 2 | Click on the ``Backpack`` ``ADD TO CART`` button. | The button text changed to ``REMOVE``. |\n| 3 | Click on the cart icon. | The ``Backpack`` is seen in the cart. |\n| 4 | Click on the ``CHECKOUT`` button. <br /> Enter user details and click ``CONTINUE``. | The ``Backpack`` is seen in the cart on the ``CHECKOUT: OVERVIEW`` page. |\n| 5 | Click on the ``FINISH`` button. | There is a ``Thank You`` message and a ``Pony Express`` delivery logo. |\n| 6 | Log out from the website. | Logout was successful. |\n\n</details>\n\n<details>\n<summary> ⭕ <code><b>proxy_test.py::ProxyTests::test_proxy</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Perform Action 1 | Verify Action 1 |\n| 2 | Perform Action 2 | Verify Action 2 |\n\n</details>\n\n<details>\n<summary> 🔵 <code><b>shadow_root_test.py::ShadowRootTest::test_shadow_root</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Open https://seleniumbase.io/other/shadow_dom. <br /> Click each tab and verify the text contained within the Shadow Root sections. | Tab 1 text: ``Content Panel 1`` <br /> Tab 2 text: ``Content Panel 2`` <br /> Tab 3 text: ``Content Panel 3`` |\n\n</details>\n\n🚧 <code><b>test_agent.py::UserAgentTests::test_user_agent</b></code>\n\n<details>\n<summary> 🔵 <code><b>test_calculator.py::CalculatorTests::test_6_times_7_plus_12_equals_54</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Open https://seleniumbase.io/apps/calculator. <br /> Perform the following calculation: ``6 × 7 + 12`` | The output is ``54`` after pressing ``=`` |\n\n</details>\n\n<details>\n<summary> 🔵 <code><b>test_demo_site.py::DemoSiteTests::test_demo_site</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Open https://seleniumbase.io/demo_page |  |\n| 2 | Assert the title of the current web page. <br /> Assert that a given element is visible on the page. <br /> Assert that a text substring appears in an element's text. | The assertions were successful. |\n| 3 | Type text into various text fields and then verify. | The assertions were successful. |\n| 4 | Verify that a hover dropdown link changes page text. | The assertion was successful. |\n| 5 | Verify that a button click changes text on the page. | The assertion was successful. |\n| 6 | Verify that an SVG element is located on the page. | The assertion was successful. |\n| 7 | Verify that a slider control updates a progress bar. | The assertion was successful. |\n| 8 | Verify that a \"select\" option updates a meter bar. | The assertion was successful. |\n| 9 | Assert an element located inside an iFrame. | The assertion was successful. |\n| 10 | Assert text located inside an iFrame. | The assertion was successful. |\n| 11 | Verify that clicking a radio button selects it. | The assertion was successful. |\n| 12 | Verify that clicking an empty checkbox makes it selected. | The assertion was successful. |\n| 13 | Verify clicking on multiple elements with one call. | The assertions were successful. |\n| 14 | Verify that clicking an iFrame checkbox selects it. | The assertions were successful. |\n| 15 | Verify that Drag and Drop works. | The assertion was successful. |\n| 16 | Assert link text. | The assertion was successful. |\n| 17 | Verify clicking on link text. | The action was successful. |\n| 18 | Assert exact text in an element. | The assertion was successful. |\n| 19 | Highlight a page element. | The action was successful. |\n| 20 | Verify that Demo Mode works. | The assertion was successful. |\n\n</details>\n\n<details>\n<summary> 🔵 <code><b>test_login.py::SwagLabsLoginTests::test_swag_labs_login</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\n| 2 | Log out from the website. | Logout was successful. |\n\n</details>\n\n<details>\n<summary> 🔵 <code><b>test_mfa_login.py::TestMFALogin::test_mfa_login</b></code></summary>\n\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Open https://seleniumbase.io/realworld/login <br /> Enter credentials and Sign In. | Sign In was successful. |\n| 2 | Click the ``This Page`` button. <br /> Save a screenshot to the logs. | |\n| 3 | Click to Sign Out | Sign Out was successful. |\n\n</details>\n\n--------\n\n🗂️ Before you can generate a ``case_summary.md`` file that includes your existing Case Plans, first you'll need to select which existing tests you want to create boilerplate Case Plans from. For that, you can use the SeleniumBase Case Plans GUI:\n\n```zsh\nsbase caseplans\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/cp/case_plan_boilerplate_gen.png\" title=\"SeleniumBase Case Plans GUI\" width=\"525\">\n\n🗂️ Once you are running the Case Plans GUI, select the existing tests that need Case Plans, and then click: ``Generate boilerplate Case Plans for selected tests missing them``. For each selected test that didn't already have a Case Plan file, one will be generated. Each new Case Plan file starts with default boilerplate code with a Markdown table. Eg:\n\n```zsh\n``proxy_test.py::ProxyTests::test_proxy``\n---\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Perform Action 1 | Verify Action 1 |\n| 2 | Perform Action 2 | Verify Action 2 |\n\n```\n\n🗂️ When rendered as a Markdown table, the result looks like this:\n\n``proxy_test.py::ProxyTests::test_proxy``\n---\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Perform Action 1 | Verify Action 1 |\n| 2 | Perform Action 2 | Verify Action 2 |\n\n🗂️ Markdown tables are flexible, but must be constructed correctly to be displayed. For a Markdown table to render, it's important that you place pipes (``|``), dashes (``-``), and spaces in the correct locations. If you want a line break in a step, use ``<br />``. If you want an empty step, put a space between pipes, eg: ``| |``.\n\n🗂️ Here's an example of a Case Plan for [my_first_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py):\n\n``my_first_test.py::MyTestClass::test_swag_labs``\n---\n| # | Step Description | Expected Result |\n| - | ---------------- | --------------- |\n| 1 | Log in to https://www.saucedemo.com with ``standard_user``. | Login was successful. |\n| 2 | Click on the ``Backpack`` ``ADD TO CART`` button. | The button text changed to ``REMOVE``. |\n| 3 | Click on the cart icon. | The ``Backpack`` is seen in the cart. |\n| 4 | Click on the ``CHECKOUT`` button. <br /> Enter user details and click ``CONTINUE``. | The ``Backpack`` is seen in the cart on the ``CHECKOUT: OVERVIEW`` page. |\n| 5 | Click on the ``FINISH`` button. | There is a ``Thank you`` message. |\n| 6 | Log out from the website. | Logout was successful. |\n\n🗂️ After you've created some Case Plans, you can use the ``Generate Summary of existing Case Plans`` button in the Case Plans GUI to generate the Case Plans Summary file.\n\n<img src=\"https://seleniumbase.github.io/cdn/img/cp/case_plan_summary_gen.png\" title=\"SeleniumBase Case Plans GUI\" width=\"550\">\n\n🗂️ The generated Case Plans summary file, ``case_summary.md``, gets created in the same location where the Case Plans GUI was launched. This is NOT the same location where individual Case Plan boilerplates are generated, which is in the ``case_plans/`` folders. The ``case_plans/`` folders are generated where individual tests live, which means that if you have your tests in multiple folders, then you could also have multiple ``case_plans/`` folders. A ``case_summary.md`` file may look like this when rendered:\n\n<img src=\"https://seleniumbase.github.io/cdn/img/cp/case_plan_summary.png\" title=\"SeleniumBase Case Plans Summary\" width=\"550\">\n\n🗂️ When calling ``sbase caseplans``, you can provide additional arguments to limit the tests that appear in the list. The same discovery rules apply as when using ``pytest``. Eg:\n\n```zsh\nsbase caseplans\nsbase caseplans -k agent\nsbase caseplans -m marker2\nsbase caseplans test_suite.py\nsbase caseplans offline_examples/\n```\n\n--------\n\n<div>To learn more about SeleniumBase, check out the Docs Site:</div>\n<a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a>\n\n<div>All the code is on GitHub:</div>\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/✅%20View%20Code-on%20GitHub%20🌎-02A79E.svg\" alt=\"SeleniumBase on GitHub\" /></a>\n"
  },
  {
    "path": "help_docs/cdp_mode_methods.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> SeleniumBase CDP Mode Methods (CDP Mode API Reference)</h2>\n\nHere's a list of SeleniumBase CDP Mode method definitions, which are defined in **[sb_cdp.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/core/sb_cdp.py)**\n\n### 🐙 <b translate=\"no\">CDP Mode</b> API / Methods\n\n```python\nsb.cdp.get(url, **kwargs)\nsb.cdp.open(url, **kwargs)  # Same as sb.cdp.get(url, **kwargs)\nsb.cdp.reload(ignore_cache=True, script_to_evaluate_on_load=None)\nsb.cdp.refresh(*args, **kwargs)\nsb.cdp.get_event_loop()\nsb.cdp.get_rd_host()  # Returns the remote-debugging host\nsb.cdp.get_rd_port()  # Returns the remote-debugging port\nsb.cdp.get_rd_url()  # Returns the remote-debugging URL\nsb.cdp.get_endpoint_url()  # Same as sb.cdp.get_rd_url()\nsb.cdp.get_port()  # Same as sb.cdp.get_rd_port()\nsb.cdp.get_websocket_url()  # Returns the websocket URL\nsb.cdp.add_handler(event, handler)\nsb.cdp.find_element(selector, best_match=False, timeout=None)\nsb.cdp.find(selector, best_match=False, timeout=None)\nsb.cdp.locator(selector, best_match=False, timeout=None)\nsb.cdp.find_element_by_text(text, tag_name=None, timeout=None)\nsb.cdp.find_all(selector, timeout=None)\nsb.cdp.find_elements_by_text(text, tag_name=None)\nsb.cdp.select(selector, timeout=None)\nsb.cdp.select_all(selector, timeout=None)\nsb.cdp.find_elements(selector, timeout=None)\nsb.cdp.find_visible_elements(selector, timeout=None)\nsb.cdp.click(selector, timeout=None)\nsb.cdp.click_if_visible(selector)\nsb.cdp.click_visible_elements(selector, limit=0)\nsb.cdp.click_nth_element(selector, number)\nsb.cdp.click_nth_visible_element(selector, number)\nsb.cdp.click_with_offset(selector, x, y, center=False)\nsb.cdp.click_link(link_text)\nsb.cdp.go_back()\nsb.cdp.go_forward()\nsb.cdp.get_navigation_history()\nsb.cdp.tile_windows(windows=None, max_columns=0)\nsb.cdp.grant_permissions(permissions, origin=None)\nsb.cdp.grant_all_permissions()\nsb.cdp.reset_permissions()\nsb.cdp.get_all_cookies(*args, **kwargs)\nsb.cdp.set_all_cookies(*args, **kwargs)\nsb.cdp.save_cookies(*args, **kwargs)\nsb.cdp.load_cookies(*args, **kwargs)\nsb.cdp.clear_cookies()\nsb.cdp.sleep(seconds)\nsb.cdp.bring_active_window_to_front()\nsb.cdp.bring_to_front()\nsb.cdp.get_active_element()\nsb.cdp.get_active_element_css()\nsb.cdp.click_active_element()\nsb.cdp.mouse_click(selector, timeout=None)\nsb.cdp.nested_click(parent_selector, selector)\nsb.cdp.get_nested_element(parent_selector, selector)\nsb.cdp.select_option_by_text(dropdown_selector, option)\nsb.cdp.select_option_by_index(dropdown_selector, option)\nsb.cdp.select_option_by_value(dropdown_selector, option)\nsb.cdp.flash(selector, duration=1, color=\"44CC88\", pause=0)\nsb.cdp.highlight(selector)\nsb.cdp.focus(selector)\nsb.cdp.highlight_overlay(selector)\nsb.cdp.get_parent(element)\nsb.cdp.remove_element(selector)\nsb.cdp.remove_from_dom(selector)\nsb.cdp.remove_elements(selector)\nsb.cdp.send_keys(selector, text, timeout=None)\nsb.cdp.press_keys(selector, text, timeout=None)\nsb.cdp.type(selector, text, timeout=None)\nsb.cdp.set_value(selector, text, timeout=None)\nsb.cdp.clear_input(selector, timeout=None)\nsb.cdp.clear(selector, timeout=None)\nsb.cdp.submit(selector)\nsb.cdp.evaluate(expression)\nsb.cdp.execute_script(expression)\nsb.cdp.js_dumps(obj_name)\nsb.cdp.maximize()\nsb.cdp.minimize()\nsb.cdp.medimize()\nsb.cdp.set_window_rect(x, y, width, height)\nsb.cdp.reset_window_size()\nsb.cdp.open_new_window(url=None, switch_to=True)\nsb.cdp.switch_to_window(window)\nsb.cdp.switch_to_newest_window()\nsb.cdp.open_new_tab(url=None, switch_to=True)\nsb.cdp.switch_to_tab(tab)\nsb.cdp.switch_to_newest_tab()\nsb.cdp.close_active_tab()\nsb.cdp.get_active_tab()\nsb.cdp.get_tabs()\nsb.cdp.get_window()\nsb.cdp.get_text(selector)\nsb.cdp.get_title()\nsb.cdp.get_current_url()\nsb.cdp.get_origin()\nsb.cdp.get_html(include_shadow_dom=True)\nsb.cdp.get_page_source(include_shadow_dom=True)\nsb.cdp.get_user_agent()\nsb.cdp.get_cookie_string()\nsb.cdp.get_locale_code()\nsb.cdp.get_local_storage_item(key)\nsb.cdp.get_session_storage_item(key)\nsb.cdp.get_screen_rect()\nsb.cdp.get_window_rect()\nsb.cdp.get_window_size()\nsb.cdp.get_window_position()\nsb.cdp.get_element_rect(selector, timeout=None)\nsb.cdp.get_element_size(selector, timeout=None)\nsb.cdp.get_element_position(selector, timeout=None)\nsb.cdp.get_gui_element_rect(selector, timeout=None)\nsb.cdp.get_gui_element_center(selector, timeout=None)\nsb.cdp.get_document()\nsb.cdp.get_flattened_document()\nsb.cdp.get_element_attributes(selector)\nsb.cdp.get_element_attribute(selector, attribute)\nsb.cdp.get_attribute(selector, attribute)\nsb.cdp.get_element_html(selector)\nsb.cdp.get_mfa_code(totp_key=None)\nsb.cdp.enter_mfa_code(selector, totp_key=None, timeout=None)\nsb.cdp.activate_messenger()\nsb.cdp.set_messenger_theme(theme=\"default\", location=\"default\")\nsb.cdp.post_message(message, duration=None, pause=True, style=\"info\")\nsb.cdp.set_locale(locale)\nsb.cdp.set_local_storage_item(key, value)\nsb.cdp.set_session_storage_item(key, value)\nsb.cdp.set_attributes(selector, attribute, value)\nsb.cdp.is_attribute_present(selector, attribute, value=None)\nsb.cdp.is_online()\nsb.cdp.solve_captcha()\nsb.cdp.click_captcha()\nsb.cdp.gui_press_key(key)\nsb.cdp.gui_press_keys(keys)\nsb.cdp.gui_write(text)\nsb.cdp.gui_click_x_y(x, y, timeframe=0.25)\nsb.cdp.gui_click_element(selector, timeframe=0.25)\nsb.cdp.gui_click_with_offset(selector, x, y, timeframe=0.25, center=False)\nsb.cdp.gui_click_captcha()\nsb.cdp.gui_drag_drop_points(x1, y1, x2, y2, timeframe=0.35)\nsb.cdp.gui_drag_and_drop(drag_selector, drop_selector, timeframe=0.35)\nsb.cdp.gui_click_and_hold(selector, timeframe=0.35)\nsb.cdp.gui_hover_x_y(x, y)\nsb.cdp.gui_hover_element(selector)\nsb.cdp.gui_hover_and_click(hover_selector, click_selector)\nsb.cdp.hover_element(selector)\nsb.cdp.hover_and_click(hover_selector, click_selector)\nsb.cdp.internalize_links()\nsb.cdp.is_checked(selector)\nsb.cdp.is_selected(selector)\nsb.cdp.check_if_unchecked(selector)\nsb.cdp.select_if_unselected(selector)\nsb.cdp.uncheck_if_checked(selector)\nsb.cdp.unselect_if_selected(selector)\nsb.cdp.is_element_present(selector)\nsb.cdp.is_element_visible(selector)\nsb.cdp.is_text_visible(text, selector=\"body\")\nsb.cdp.is_exact_text_visible(text, selector=\"body\")\nsb.cdp.wait_for_text(text, selector=\"body\", timeout=None)\nsb.cdp.wait_for_text_not_visible(text, selector=\"body\", timeout=None)\nsb.cdp.wait_for_element_visible(selector, timeout=None)\nsb.cdp.wait_for_element(selector, timeout=None)\nsb.cdp.wait_for_element_not_visible(selector, timeout=None)\nsb.cdp.wait_for_element_absent(selector, timeout=None)\nsb.cdp.wait_for_any_of_elements_visible(*args, **kwargs)\nsb.cdp.wait_for_any_of_elements_present(*args, **kwargs)\nsb.cdp.assert_any_of_elements_visible(*args, **kwargs)\nsb.cdp.assert_any_of_elements_present(*args, **kwargs)\nsb.cdp.assert_element(selector, timeout=None)\nsb.cdp.assert_element_visible(selector, timeout=None)\nsb.cdp.assert_element_present(selector, timeout=None)\nsb.cdp.assert_element_absent(selector, timeout=None)\nsb.cdp.assert_element_not_visible(selector, timeout=None)\nsb.cdp.assert_element_attribute(selector, attribute, value=None)\nsb.cdp.assert_title(title)\nsb.cdp.assert_title_contains(substring)\nsb.cdp.assert_url(url)\nsb.cdp.assert_url_contains(substring)\nsb.cdp.assert_text(text, selector=\"html\", timeout=None)\nsb.cdp.assert_exact_text(text, selector=\"html\", timeout=None)\nsb.cdp.assert_text_not_visible(text, selector=\"body\", timeout=None)\nsb.cdp.assert_true()\nsb.cdp.assert_false()\nsb.cdp.assert_equal(first, second)\nsb.cdp.assert_not_equal(first, second)\nsb.cdp.assert_in(first, second)\nsb.cdp.assert_not_in(first, second)\nsb.cdp.scroll_into_view(selector)\nsb.cdp.scroll_to_y(y)\nsb.cdp.scroll_by_y(y)\nsb.cdp.scroll_to_top()\nsb.cdp.scroll_to_bottom()\nsb.cdp.scroll_up(amount=25)\nsb.cdp.scroll_down(amount=25)\nsb.cdp.save_page_source(name, folder=None)\nsb.cdp.save_as_html(name, folder=None)\nsb.cdp.save_screenshot(name, folder=None, selector=None)\nsb.cdp.print_to_pdf(name, folder=None)\nsb.cdp.save_as_pdf(name, folder=None)\n```\n\nℹ️ When available, calling `sb.METHOD()` redirects to `sb.cdp.METHOD()` when CDP Mode is active. From Pure CDP Mode, always call these methods with `sb.METHOD()` instead of `sb.cdp.METHOD()`.\n\n--------\n\n<a id=\"Pure_CDP_Mode\"></a>\n\n### 🐙 <b translate=\"no\">Pure CDP Mode</b> (<code translate=\"no\">sb_cdp</code>)\n\n<b translate=\"no\">Pure CDP Mode</b> doesn't use WebDriver for anything. The browser is launched using CDP, and all browser actions are performed using CDP (or <code>PyAutoGUI</code>). Initialization:\n\n```python\nfrom seleniumbase import sb_cdp\n\nsb = sb_cdp.Chrome(url=None, **kwargs)\n```\n\n<b translate=\"no\">Pure CDP Mode</b> includes all methods from regular CDP Mode, except that they're called directly from <code>sb</code> instead of <code>sb.cdp</code>. Eg: <code>sb.gui_click_captcha()</code>. To quit a CDP-launched browser, use `sb.driver.stop()`.\n\nBasic example from [SeleniumBase/examples/cdp_mode/raw_cdp_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_turnstile.py):\n\n```python\nfrom seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/apps/turnstile\"\nsb = sb_cdp.Chrome(url)\nsb.solve_captcha()\nsb.assert_element(\"img#captcha-success\")\nsb.set_messenger_theme(location=\"top_left\")\nsb.post_message(\"SeleniumBase wasn't detected\", duration=3)\nsb.driver.stop()\n```\n\nAnother example: ([SeleniumBase/examples/cdp_mode/raw_cdp_methods.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_methods.py))\n\n```python\nfrom seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/demo_page\"\nsb = sb_cdp.Chrome(url)\nsb.press_keys(\"input\", \"Text\")\nsb.highlight(\"button\")\nsb.type(\"textarea\", \"Here are some words\")\nsb.click(\"button\")\nsb.set_value(\"input#mySlider\", \"100\")\nsb.click_visible_elements(\"input.checkBoxClassB\")\nsb.select_option_by_text(\"#mySelect\", \"Set to 75%\")\nsb.gui_hover_and_click(\"#myDropdown\", \"#dropOption2\")\nsb.gui_click_element(\"#checkBox1\")\nsb.gui_drag_and_drop(\"img#logo\", \"div#drop2\")\nsb.nested_click(\"iframe#myFrame3\", \".fBox\")\nsb.sleep(2)\nsb.driver.stop()\n```\n\nℹ️ Even if you don't call `sb.driver.stop()`, the browser still quits after the script goes out-of-scope.\n\n--------\n\n### 🐙 <b translate=\"no\">CDP Mode</b> Async API / Methods\n\nInitialization:\n\n```python\nfrom seleniumbase import cdp_driver\n\ndriver = await cdp_driver.start_async()\ntab = await driver.get(url, **kwargs)\n```\n\nMethods: (Sometimes `tab` is named `page` in examples)\n\n```python\nawait tab.get(url=\"about:blank\")\nawait tab.open(url=\"about:blank\")\nawait tab.find(text, best_match=False, timeout=10)  # text can be selector\nawait tab.find_all(text, timeout=10)  # text can be selector\nawait tab.select(selector, timeout=10)\nawait tab.select_all(selector, timeout=10, include_frames=False)\nawait tab.query_selector(selector)\nawait tab.query_selector_all(selector)\nawait tab.find_element_by_text(text, best_match=False)\nawait tab.find_elements_by_text(text)\nawait tab.reload(ignore_cache=True, script_to_evaluate_on_load=None)\nawait tab.evaluate(expression)\nawait tab.js_dumps(obj_name)\nawait tab.back()\nawait tab.forward()\nawait tab.get_window()\nawait tab.get_content()\nawait tab.maximize()\nawait tab.minimize()\nawait tab.fullscreen()\nawait tab.medimize()\nawait tab.set_window_size(left=0, top=0, width=1280, height=1024)\nawait tab.set_window_rect(left=0, top=0, width=1280, height=1024)\nawait tab.activate()\nawait tab.bring_to_front()\nawait tab.set_window_state(\n    left=0, top=0, width=1280, height=720, state=\"normal\")\nawait tab.get_navigation_history()\nawait tab.get_user_agent()\nawait tab.get_cookie_string()\nawait tab.get_locale_code()\nawait tab.is_online()\nawait tab.open_external_inspector()  # Open separate browser for debugging\nawait tab.close()\nawait tab.scroll_down(amount=25)\nawait tab.scroll_up(amount=25)\nawait tab.wait_for(selector=\"\", text=\"\", timeout=10)\nawait tab.set_attributes(selector, attribute, value)\nawait tab.internalize_links()\nawait tab.download_file(url, filename=None)\nawait tab.save_screenshot(\n    filename=\"auto\", format=\"png\", full_page=False)\nawait tab.print_to_pdf(filename=\"auto\")\nawait tab.set_download_path(path)\nawait tab.get_all_linked_sources()\nawait tab.get_all_urls(absolute=True)\nawait tab.get_html()\nawait tab.get_page_source()\nawait tab.is_element_present(selector)\nawait tab.is_element_visible(selector)\nawait tab.get_element_rect(selector, timeout=5)  # (window-based)\nawait tab.get_window_rect()\nawait tab.get_gui_element_rect(selector, timeout=5)  # (screen-based)\nawait tab.get_title()\nawait tab.get_current_url()\nawait tab.get_origin()\nawait tab.send_keys(selector, text, timeout=5)\nawait tab.type(selector, text, timeout=5)\nawait tab.click(selector, timeout=5)\nawait tab.click_if_visible(selector, timeout=0)\nawait tab.click_with_offset(selector, x, y, center=False, timeout=5)\nawait tab.solve_captcha()\nawait tab.click_captcha()  # Same as solve_captcha()\nawait tab.get_document()\nawait tab.get_flattened_document()\nawait tab.get_local_storage()\nawait tab.set_local_storage(items)\n```\n\n--------\n\n### 🐙 <b translate=\"no\">CDP Mode</b> WebElement API / Methods\n\nAfter finding an element in CDP Mode, you can access `WebElement` methods:\n\n(Eg. After `element = sb.find_element(selector)`)\n\n```python\nelement.clear_input()\nelement.click()\nelement.click_with_offset(x, y, center=False)\nelement.flash(duration=0.5, color=\"EE4488\")\nelement.focus()\nelement.gui_click(timeframe=0.25)\nelement.highlight_overlay()\nelement.mouse_click()\nelement.mouse_drag(destination)\nelement.mouse_move()\nelement.press_keys(text)\nelement.query_selector(selector)\nelement.querySelector(selector)\nelement.query_selector_all(selector)\nelement.querySelectorAll(selector)\nelement.remove_from_dom()\nelement.save_screenshot(*args, **kwargs)\nelement.save_to_dom()\nelement.scroll_into_view()\nelement.select_option()\nelement.send_file(*file_paths)\nelement.send_keys(text)\nelement.set_text(value)\nelement.type(text)\nelement.get_position()\nelement.get_html()\nelement.get_js_attributes()\nelement.get_attribute(attribute)\nelement.get_parent()\n```\n\n--------\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_text_f.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" align=\"center\" width=\"335\">\n\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_gs.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"335\" /></a></div>\n"
  },
  {
    "path": "help_docs/commander.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> SeleniumBase Commander 🎖️</h2>\n\n🎖️ <b translate=\"no\">SeleniumBase Commander</b> lets you run <code translate=\"no\">pytest</code> scripts from a Desktop GUI.<br>\n\n🎖️ To launch it, call ``sbase commander`` or ``sbase gui``:\n\n```zsh\nsbase gui\n* Starting the SeleniumBase Commander Desktop App...\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sbase_commander_wide.png\" title=\"SeleniumBase Commander\" width=\"600\">\n\n🎖️ <b translate=\"no\">SeleniumBase Commander</b> loads the same tests that are found by:\n\n```zsh\npytest --co -q\n```\n\n🎖️ You can customize which tests are loaded by passing additional args:\n\n```zsh\nsbase commander [OPTIONAL PATH or TEST FILE]\nsbase gui [OPTIONAL PATH or TEST FILE]\n```\n\n🎖️ Here are examples of customizing test collection:\n\n```zsh\nsbase gui\nsbase gui -k agent\nsbase gui -m marker2\nsbase gui test_suite.py\nsbase gui offline_examples/\n```\n\n🎖️ Once launched, you can further customize which tests to run and what settings to use. There are various controls for changing settings, modes, and other pytest command line options that are specific to SeleniumBase. You can also set additional options that don't have a visible toggle. When you're ready to run the selected tests with the specified options, click on the <code translate=\"no\">Run Selected Tests</code> button.\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Other SeleniumBase Commanders:</h3>\n\n* 🐝🎖️ [SeleniumBase Behave GUI / Commander](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/behave_gui.md)\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/behave_gui.md\"><img src=\"https://seleniumbase.github.io/cdn/img/sbase_behave_gui_wide_5.png\" title=\"SeleniumBase Behave GUI\" width=\"400\"></a>\n\n--------\n\n<div>To learn more about SeleniumBase, check out the Docs Site:</div>\n<a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a>\n\n<div>All the code is on GitHub:</div>\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase on GitHub\" /></a>\n"
  },
  {
    "path": "help_docs/customizing_test_runs.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<a id=\"customizing_test_runs\"></a>\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"40\"> Options / Customization</h2>\n\n<h3>🎛️ SeleniumBase has different ways of setting options, depending on which format you're using. Options can be set via the command-line or method call.</h3>\n\n<blockquote>\n<p dir=\"auto\"></p>\n<ul dir=\"auto\">\n<li><a href=\"#pytest_options\"><strong>01. <code>pytest</code> command-line options</strong></a></li>\n<li><a href=\"#sb_options\"><strong>02. <code>SB()</code> method options</strong></a></li>\n<li><a href=\"#driver_options\"><strong>03. <code>Driver()</code> method options</strong></a></li>\n<li><a href=\"#sb_cdp_chrome_options\"><strong>04. <code>sb_cdp.Chrome()</code> method options</strong></a></li>\n<li><a href=\"#activate_cdp_mode_options\"><strong>05. <code>activate_cdp_mode()</code> method options</strong></a></li>\n</ul>\n</blockquote>\n\n--------\n\n<a id=\"pytest_options\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> 01. <code>pytest</code> command-line options</h2>\n\n🎛️ SeleniumBase's [pytest plugin](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/plugins/pytest_plugin.py) lets you customize test runs from the CLI (Command-Line Interface), which adds options for setting/enabling the browser type, Dashboard Mode, Demo Mode, Headless Mode, Mobile Mode, Multi-threading Mode, Recorder Mode, UC Mode (stealth), reuse-session mode, Proxy Mode, and more.\n\n🎛️ Here are some examples of configuring tests, which can be run from the [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder:\n\n```zsh\n# Run a test in Chrome (default browser)\npytest my_first_test.py\n\n# Run a test in Edge\npytest test_swag_labs.py --edge\n\n# Run a test in Demo Mode (highlight assertions)\npytest test_demo_site.py --demo\n\n# Run a test in Headless Mode (invisible browser)\npytest test_demo_site.py --headless\n\n# Run tests multi-threaded using [n] threads\npytest test_suite.py -n4\n\n# Reuse the browser session for all tests (\"--reuse-session\")\npytest test_suite.py --rs\n\n# Reuse the browser session, but erase cookies between tests\npytest test_suite.py --rs --crumbs\n\n# Create a real-time dashboard for test results\npytest test_suite.py --dashboard\n\n# Create a pytest-html report after tests are done\npytest test_suite.py --html=report.html\n\n# Rerun failing tests more times\npytest test_suite.py --reruns=1\n\n# Activate Debug Mode at the start (\"n\": next. \"c\": continue)\npytest test_null.py --trace -s\n\n# Activate Debug Mode on failures (\"n\": next. \"c\": continue)\npytest test_fail.py --pdb -s\n\n# Activate Debug Mode at the end (\"n\": next. \"c\": continue)\npytest test_fail.py --ftrace -s\n\n# Activate Recorder/Debug Mode as the test begins (\"c\" to continue)\npytest test_null.py --recorder --trace -s\n\n# Pass extra data into tests (retrieve by calling self.data)\npytest my_first_test.py --data=\"ABC\"\n\n# Run tests on a local Selenium Grid\npytest test_suite.py --server=\"127.0.0.1\"\n\n# Run tests on a remote Selenium Grid\npytest test_suite.py --server=IP_ADDRESS --port=4444\n\n# Run tests on a remote Selenium Grid with authentication\npytest test_suite.py --server=USERNAME:KEY@IP_ADDRESS --port=80\n\n# Run tests through a proxy server\npytest proxy_test.py --proxy=IP_ADDRESS:PORT\n\n# Run tests through a proxy server with authentication\npytest proxy_test.py --proxy=USERNAME:PASSWORD@IP_ADDRESS:PORT\n\n# Run tests while setting the web browser's User Agent\npytest user_agent_test.py --agent=\"USER-AGENT-STRING\"\n\n# Run tests using Chrome's mobile device emulator (default settings)\npytest test_swag_labs.py --mobile\n\n# Run mobile tests specifying CSS Width, CSS Height, and Pixel-Ratio\npytest test_swag_labs.py --mobile --metrics=\"360,640,2\"\n\n# Run tests using UC Mode to evade bot-detection services\npytest verify_undetected.py --uc\n\n# Run tests while changing SeleniumBase default settings\npytest my_first_test.py --settings-file=custom_settings.py\n```\n\n🎛️ You can interchange `pytest` with `nosetests` for most tests, but using `pytest` is recommended. (`chrome` is the default browser if not specified.)\n\n🎛️ If you're using `pytest` for running tests outside of the SeleniumBase repo, you'll want a copy of [pytest.ini](https://github.com/seleniumbase/SeleniumBase/blob/master/pytest.ini) at the base of the new folder structure. If using `nosetests`, the same applies for [setup.cfg](https://github.com/seleniumbase/SeleniumBase/blob/master/setup.cfg).\n\n🎛️ Here are some useful command-line options that come with `pytest`:\n\n```zsh\n-v  # Verbose mode. Prints the full name of each test and shows more details.\n-q  # Quiet mode. Print fewer details in the console output when running tests.\n-x  # Stop running the tests after the first failure is reached.\n--html=report.html  # Creates a detailed pytest-html report after tests finish.\n--co | --collect-only  # Show what tests would get run. (Without running them)\n--co -q  # (Both options together!) - Do a dry run with full test names shown.\n-n=NUM  # Multithread the tests using that many threads. (Speed up test runs!)\n-s  # See print statements. (Should be on by default with pytest.ini present.)\n--junit-xml=report.xml  # Creates a junit-xml report after tests finish.\n--pdb  # If a test fails, enter Post Mortem Debug Mode. (Don't use with CI!)\n--trace  # Enter Debug Mode at the beginning of each test. (Don't use with CI!)\n-m=MARKER  # Run tests with the specified pytest marker.\n```\n\n🎛️ SeleniumBase provides additional `pytest` command-line options for tests:\n\n```zsh\n--browser=BROWSER  # (The web browser to use. Default: \"chrome\".)\n--chrome  # (Shortcut for \"--browser=chrome\". On by default.)\n--edge  # (Shortcut for \"--browser=edge\".)\n--firefox  # (Shortcut for \"--browser=firefox\".)\n--safari  # (Shortcut for \"--browser=safari\".)\n--settings-file=FILE  # (Override default SeleniumBase settings.)\n--env=ENV  # (Set the test env. Access with \"self.env\" in tests.)\n--account=STR  # (Set account. Access with \"self.account\" in tests.)\n--data=STRING  # (Extra test data. Access with \"self.data\" in tests.)\n--var1=STRING  # (Extra test data. Access with \"self.var1\" in tests.)\n--var2=STRING  # (Extra test data. Access with \"self.var2\" in tests.)\n--var3=STRING  # (Extra test data. Access with \"self.var3\" in tests.)\n--variables=DICT  # (Extra test data. Access with \"self.variables\".)\n--user-data-dir=DIR  # (Set the Chrome user data directory to use.)\n--protocol=PROTOCOL  # (The Selenium Grid protocol: http|https.)\n--server=SERVER  # (The Selenium Grid server/IP used for tests.)\n--port=PORT  # (The Selenium Grid port used by the test server.)\n--cap-file=FILE  # (The web browser's desired capabilities to use.)\n--cap-string=STRING  # (The web browser's desired capabilities to use.)\n--proxy=SERVER:PORT  # (Connect to a proxy server:port as tests are running)\n--proxy=USERNAME:PASSWORD@SERVER:PORT  # (Use an authenticated proxy server)\n--proxy-bypass-list=STRING # (\";\"-separated hosts to bypass, Eg \"*.foo.com\")\n--proxy-pac-url=URL  # (Connect to a proxy server using a PAC_URL.pac file.)\n--proxy-pac-url=USERNAME:PASSWORD@URL  # (Authenticated proxy with PAC URL.)\n--proxy-driver  # (If a driver download is needed, will use: --proxy=PROXY.)\n--multi-proxy  # (Allow multiple authenticated proxies when multi-threaded.)\n--agent=STRING  # (Modify the web browser's User-Agent string.)\n--mobile  # (Use the mobile device emulator while running tests.)\n--metrics=STRING  # (Set mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\".)\n--chromium-arg=\"ARG=N,ARG2\"  # (Set Chromium args, \",\"-separated, no spaces.)\n--firefox-arg=\"ARG=N,ARG2\"  # (Set Firefox args, comma-separated, no spaces.)\n--firefox-pref=SET  # (Set a Firefox preference:value set, comma-separated.)\n--extension-zip=ZIP  # (Load a Chrome Extension .zip|.crx, comma-separated.)\n--extension-dir=DIR  # (Load a Chrome Extension directory, comma-separated.)\n--disable-features=\"F1,F2\"  # (Disable features, comma-separated, no spaces.)\n--binary-location=PATH  # (Set path of the Chromium browser binary to use.)\n--driver-version=VER  # (Set the chromedriver or uc_driver version to use.)\n--sjw  # (Skip JS Waits for readyState to be \"complete\" or Angular to load.)\n--wfa  # (Wait for AngularJS to be done loading after specific web actions.)\n--pls=PLS  # (Set pageLoadStrategy on Chrome: \"normal\", \"eager\", or \"none\".)\n--headless  # (The default headless mode. Linux uses this mode by default.)\n--headless1  # (Use Chrome's old headless mode. Fast, but has limitations.)\n--headless2  # (Use Chrome's new headless mode, which supports extensions.)\n--headed  # (Run tests in headed/GUI mode on Linux OS, where not default.)\n--xvfb  # (Run tests using the Xvfb virtual display server on Linux OS.)\n--xvfb-metrics=STRING  # (Set Xvfb display size on Linux: \"Width,Height\".)\n--locale=LOCALE_CODE  # (Set the Language Locale Code for the web browser.)\n--interval=SECONDS  # (The autoplay interval for presentations & tour steps)\n--start-page=URL  # (The starting URL for the web browser when tests begin.)\n--archive-logs  # (Archive existing log files instead of deleting them.)\n--archive-downloads  # (Archive old downloads instead of deleting them.)\n--time-limit=SECONDS  # (Safely fail any test that exceeds the time limit.)\n--slow  # (Slow down the automation. Faster than using Demo Mode.)\n--demo  # (Slow down and visually see test actions as they occur.)\n--demo-sleep=SECONDS  # (Set the wait time after Slow & Demo Mode actions.)\n--highlights=NUM  # (Number of highlight animations for Demo Mode actions.)\n--message-duration=SECONDS  # (The time length for Messenger alerts.)\n--check-js  # (Check for JavaScript errors after page loads.)\n--ad-block  # (Block some types of display ads from loading.)\n--host-resolver-rules=RULES  # (Set host-resolver-rules, comma-separated.)\n--block-images  # (Block images from loading during tests.)\n--do-not-track  # (Indicate to websites that you don't want to be tracked.)\n--verify-delay=SECONDS  # (The delay before MasterQA verification checks.)\n--ee | --esc-end  # (Lets the user end the current test via the ESC key.)\n--recorder  # (Enables the Recorder for turning browser actions into code.)\n--rec-behave  # (Same as Recorder Mode, but also generates behave-gherkin.)\n--rec-sleep  # (If the Recorder is enabled, also records self.sleep calls.)\n--rec-print  # (If the Recorder is enabled, prints output after tests end.)\n--disable-cookies  # (Disable Cookies on websites. Pages might break!)\n--disable-js  # (Disable JavaScript on websites. Pages might break!)\n--disable-csp  # (Disable the Content Security Policy of websites.)\n--disable-ws  # (Disable Web Security on Chromium-based browsers.)\n--enable-ws  # (Enable Web Security on Chromium-based browsers.)\n--enable-sync  # (Enable \"Chrome Sync\" on websites.)\n--uc | --undetected  # (Use undetected-chromedriver to evade bot-detection.)\n--uc-cdp-events  # (Capture CDP events when running in \"--undetected\" mode.)\n--log-cdp  # (\"goog:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"})\n--remote-debug  # (Sync to Chrome Remote Debugger chrome://inspect/#devices)\n--ftrace | --final-trace  # (Debug Mode after each test. Don't use with CI!)\n--dashboard  # (Enable the SeleniumBase Dashboard. Saved at: dashboard.html)\n--dash-title=STRING  # (Set the title shown for the generated dashboard.)\n--enable-3d-apis  # (Enables WebGL and 3D APIs.)\n--swiftshader  # (Chrome \"--use-gl=angle\" / \"--use-angle=swiftshader-webgl\")\n--incognito  # (Enable Chrome's Incognito mode.)\n--guest  # (Enable Chrome's Guest mode.)\n--dark  # (Enable Chrome's Dark mode.)\n--devtools  # (Open Chrome's DevTools when the browser opens.)\n--rs | --reuse-session  # (Reuse browser session for all tests.)\n--rcs | --reuse-class-session  # (Reuse session for tests in class.)\n--crumbs  # (Delete all cookies between tests reusing a session.)\n--disable-beforeunload  # (Disable the \"beforeunload\" event on Chrome.)\n--window-position=X,Y  # (Set the browser's starting window position.)\n--window-size=WIDTH,HEIGHT  # (Set the browser's starting window size.)\n--maximize  # (Start tests with the browser window maximized.)\n--screenshot  # (Save a screenshot at the end of each test.)\n--no-screenshot  # (No screenshots saved unless tests directly ask it.)\n--visual-baseline  # (Set the visual baseline for Visual/Layout tests.)\n--wire  # (Use selenium-wire's webdriver for replacing selenium webdriver.)\n--external-pdf  # (Set Chromium \"plugins.always_open_pdf_externally\":True.)\n--timeout-multiplier=MULTIPLIER  # (Multiplies the default timeout values.)\n--list-fail-page  # (After each failing test, list the URL of the failure.)\n```\n\n(For more details, see the full list of command-line options **[here](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/plugins/pytest_plugin.py)**.)\n\n🎛️ You can also view a list of popular `pytest` options for SeleniumBase by typing:\n\n```zsh\nseleniumbase options\n```\n\nOr the short form:\n\n```zsh\nsbase options\n```\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Example tests using Logging:</h3>\n\nTo see logging abilities, you can run a test suite that includes tests that fail on purpose:\n\n```zsh\npytest test_suite.py\n```\n\n🔵 During test failures, logs and screenshots from the most recent test run will get saved to the `latest_logs/` folder. If `--archive-logs` is specified (or if ARCHIVE_EXISTING_LOGS is set to True in [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py)), test logs will also get archived to the `archived_logs/` folder. Otherwise, the log files will be cleaned out when the next test run begins (by default).\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Demo Mode:</h3>\n\n🔵 If any test is moving too fast for your eyes to see what's going on, you can run it in **Demo Mode** by adding `--demo` on the command line, which pauses the browser briefly between actions, highlights page elements being acted on, and lets you know what test assertions are happening in real-time:\n\n```zsh\npytest my_first_test.py --demo\n```\n\n🔵 You can override the default wait time by either updating [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) or by using `--demo-sleep=NUM` when using Demo Mode. (NOTE: If you use `--demo-sleep=NUM` without using `--demo`, nothing will happen.)\n\n```zsh\npytest my_first_test.py --demo --demo-sleep=1.2\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Passing additional data to tests:</h3>\n\nIf you want to pass additional data from the command line to your tests, you can use `--data=STRING`. Now inside your tests, you can use `self.data` to access that.\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Running tests multithreaded:</h3>\n\nTo run `pytest` with multiple processes, add `-n=NUM`, `-n NUM`, or `-nNUM` on the command line, where `NUM` is the number of CPUs you want to use.\n\n```zsh\npytest -n=8\npytest -n 8\npytest -n8\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> How to retry failing tests automatically:</h3>\n\n<p>You can use <code translate=\"no\">pytest --reruns=NUM</code> to retry failing tests that many times. Add <code translate=\"no\">--reruns-delay=SECONDS</code> to wait that many seconds between retries. Example:</p>\n\n```zsh\npytest --reruns=1 --reruns-delay=1\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Debugging tests:</h3>\n\n🔵 You can use the following calls in your scripts to help you debug issues:\n\n```python\nimport time; time.sleep(5)  # Makes the test wait and do nothing for 5 seconds.\nimport pdb; pdb.set_trace()  # Debug Mode. n: next, c: continue, s: step, u: up, d: down.\nimport pytest; pytest.set_trace()  # Debug Mode. n: next, c: continue, s: step, u: up, d: down.\n```\n\n🔵 To pause an active test that throws an exception or error, (*and keep the browser window open while **Debug Mode** begins in the console*), add **`--pdb`** as a `pytest` option:\n\n```zsh\npytest test_fail.py --pdb\n```\n\n🔵 To start tests in Debug Mode, add **`--trace`** as a `pytest` option:\n\n```zsh\npytest test_coffee_cart.py --trace\n```\n\n(**`pdb`** commands: `n`, `c`, `s`, `u`, `d` => `next`, `continue`, `step`, `up`, `down`).\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Combinations of options:</h3>\n\n🎛️ There are times when you'll want to combine various command-line options for added effect.\nFor instance, the multi-process option, `-n8`, can be customized by adding:\n`--dist=loadscope` or `--dist=loadfile` to it.\nThere's more info on that here: [pytest-xdist](https://pypi.org/project/pytest-xdist/2.5.0/):\n\n* `-n8 --dist=loadscope`: Tests are grouped by module for test functions and by class for test methods. Groups are distributed to available workers as whole units. This guarantees that all tests in a group run in the same process. This can be useful if you have expensive module-level or class-level fixtures. Grouping by class takes priority over grouping by module.\n\n* `-n8 --dist=loadfile`: Tests are grouped by their containing file. Groups are distributed to available workers as whole units. This guarantees that all tests in a file run in the same worker.\n\n<details>\n<summary> ▶️ <code translate=\"no\">-n8 --dist=loadgroup</code> (<b>click to expand</b>)</summary>\n<div>\n\n<ul><li>Tests are grouped by the <code translate=\"no\">xdist_group</code> mark. Groups are distributed to available workers as whole units. This guarantees that all tests with the same <code translate=\"no\">xdist_group</code> name run in the same worker.</li></ul>\n\n```python\n@pytest.mark.xdist_group(name=\"group1\")\ndef test_1():\n    pass\n\nclass Test:\n    @pytest.mark.xdist_group(\"group1\")\n    def test_2():\n        pass\n```\n\n<blockquote><p>This makes <code translate=\"no\">test_1</code> and <code translate=\"no\">Test::test_2</code> run in the same worker. Tests without the <code translate=\"no\">xdist_group</code> mark are distributed normally.</p></blockquote>\n\n</div>\n</details>\n\n🎛️ You might also want to combine multiple options at once. For example:\n\n```zsh\npytest --headless -n8 --dashboard --html=report.html -v --rs --crumbs\n```\n\nThe above not only runs tests in parallel processes, but it also tells tests in the same process to share the same browser session, runs the tests in headless mode, displays the full name of each test on a separate line, creates a real-time dashboard of the test results, and creates a full report after all tests complete.\n\n--------\n\n🎛️ For extra speed, run your tests using `chrome-headless-shell`:\n\nFirst, get `chrome-headless-shell` if you don't already have it:\n\n```zsh\nsbase get chs\n```\n\nThen, run scripts with `--chs` / `chs=True`:\n\n```zsh\npytest --chs -n8 --dashboard --html=report.html -v --rs\n```\n\nThat makes your tests run very quickly in headless mode.\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> The SeleniumBase Dashboard:</h3>\n\n🔵 The `--dashboard` option for pytest generates a SeleniumBase Dashboard located at `dashboard.html`, which updates automatically as tests run and produce results. Example:\n\n```zsh\npytest --dashboard --rs --headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dashboard_1.png\" alt=\"The SeleniumBase Dashboard\" title=\"The SeleniumBase Dashboard\" width=\"360\" />\n\n🔵 Additionally, you can host your own SeleniumBase Dashboard Server on a port of your choice. Here's an example of that using Python 3's `http.server`:\n\n```zsh\npython -m http.server 1948\n```\n\n🔵 Now you can navigate to `http://localhost:1948/dashboard.html` in order to view the dashboard as a web app. This requires two different terminal windows: one for running the server, and another for running the tests, which should be run from the same directory. (Use <kbd>Ctrl+C</kbd> to stop the http server.)\n\n🔵 Here's a full example of what the SeleniumBase Dashboard may look like:\n\n```zsh\npytest test_suite.py --dashboard --rs --headless\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dashboard_2.png\" alt=\"The SeleniumBase Dashboard\" title=\"The SeleniumBase Dashboard\" width=\"480\" />\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Pytest Reports:</h3>\n\n🔵 Using `--html=report.html` gives you a fancy report of the name specified after your test suite completes.\n\n```zsh\npytest test_suite.py --html=report.html\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/html_report.png\" alt=\"Example Pytest Report\" title=\"Example Pytest Report\" width=\"520\" />\n\n🔵 When combining pytest html reports with SeleniumBase Dashboard usage, the pie chart from the Dashboard will get added to the html report. Additionally, if you set the html report URL to be the same as the Dashboard URL when also using the dashboard, (example: `--dashboard --html=dashboard.html`), then the Dashboard will become an advanced html report when all the tests complete.\n\n🔵 Here's an example of an upgraded html report:\n\n```zsh\npytest test_suite.py --dashboard --html=report.html\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/dash_report.jpg\" alt=\"Dashboard Pytest HTML Report\" title=\"Dashboard Pytest HTML Report\" width=\"520\" />\n\nIf viewing pytest html reports in [Jenkins](https://www.jenkins.io/), you may need to [configure Jenkins settings](https://stackoverflow.com/a/46197356/7058266) for the html to render correctly. This is due to [Jenkins CSP changes](https://www.jenkins.io/doc/book/system-administration/security/configuring-content-security-policy/).\n\nYou can also use `--junit-xml=report.xml` to get an xml report instead. Jenkins can use this file to display better reporting for your tests.\n\n```zsh\npytest test_suite.py --junit-xml=report.xml\n```\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Nosetest Reports:</h3>\n\nThe `--report` option gives you a fancy report after your test suite completes.\n\n```zsh\nnosetests test_suite.py --report\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/nose_report.png\" alt=\"Example Nosetest Report\" title=\"Example Nosetest Report\" width=\"320\" />\n\n(NOTE: You can add `--show_report` to immediately display Nosetest reports after the test suite completes. Only use `--show_report` when running tests locally because it pauses the test run.)\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Language Locale Codes</h3>\n\nYou can specify a Language Locale Code to customize web pages on supported websites. With SeleniumBase, you can change the web browser's Locale on the command line by doing this:\n\n```zsh\npytest --locale=CODE  # Example: --locale=ru\n```\n\nVisit <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/locale_codes.md\"><b>🗾 Locales</b></a> for a full list of codes.\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Changing the default driver version:</h3>\n\n🔵 By default, SeleniumBase will make sure that the major driver version matches the major browser version for Chromium tests. (Eg. If Chrome `117.X` is installed and you have chromedriver `117.X`, then nothing happens, but if you had chromedriver `116.X` instead, then SeleniumBase would download chromedriver `117.X` to match the browser version.)\n\n🎛️ To change this default behavior, you can use:\n\n```zsh\npytest --driver-version=VER\n```\n\nThe `VER` in `--driver-version=VER` can be:\n* A major driver version. Eg. `117`. (milestone)\n* An exact driver version. Eg. `117.0.5938.92`.\n* `\"browser\"` (exact match on browser version)\n* `\"keep\"` (keep using the driver you already have)\n* `\"latest\"` / `\"stable\"` (latest stable version)\n* `\"previous\"` / `\"latest-1\"` (latest minus one)\n* `\"beta\"` (latest beta version)\n* `\"dev\"` (latest dev version)\n* `\"canary\"` (latest canary version)\n* `\"mlatest\"` (latest version for the milestone)\n\nNote that different options could lead to the same result. (Eg. If you have the latest version of a browser for a milestone, then `\"browser\"` and `\"mlatest\"` should give you the same driver if the latest driver version for that milestone matches the browser version.)\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Setting the binary location:</h3>\n\n🔵 By default, SeleniumBase uses the browser binary detected on the System PATH.\n\n🎛️ To change this default behavior, you can use:\n\n```zsh\npytest --binary-location=PATH\n```\n\nThe `PATH` in `--binary-location=PATH` / `--bl=PATH` can be:\n* A relative or exact path to the browser binary.\n* `\"cft\"` as a special option for `Chrome for Testing`.\n* `\"chs\"` as a special option for `Chrome-Headless-Shell`.\n\nBefore using the `\"cft\"` / `\"chs\"` options, call `sbase get cft` / `sbase get chs` in order to download the specified binaries into the `seleniumbase/drivers` folder. The default version is the latest stable version on https://googlechromelabs.github.io/chrome-for-testing/. You can change that by specifying the arg as a parameter. (Eg. `sbase get cft 131`, `sbase get chs 132`, etc.)\n\nWith the `SB()` and `Driver()` formats, the binary location is set via the `binary_location` parameter.\n\n--------\n\n🎛️ To use the special `Chromium` binary:\n\n```zsh\nsbase get chromium\n```\n\nThen, run scripts with `--use-chromium` / `use_chromium=True`:\n\n```zsh\npytest --use-chromium -n8 --dashboard --html=report.html -v --rs --headless\n```\n\n--------\n\n🎛️ To use the special `Chrome for Testing` binary:\n\n```zsh\nsbase get cft\n```\n\nThen, run scripts with `--cft` / `cft=True`:\n\n```zsh\npytest --cft -n8 --dashboard --html=report.html -v --rs --headless\n```\n\n--------\n\n(Note that `--chs` / `chs=True` activates `Chrome-Headless-Shell`)\n\n`Chrome-Headless-Shell` is the fastest version of Chrome, designed specifically for headless automation. (This mode is NOT compatible with UC Mode!)\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Customizing default settings:</h3>\n\n🎛️ An easy way to override [seleniumbase/config/settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) is by using a custom settings file.\nHere's the command-line option to add to tests: (See [examples/custom_settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/custom_settings.py))\n\n```zsh\npytest --settings-file=custom_settings.py\n```\n\n(Settings include default timeout values, a two-factor auth key, DB credentials, S3 credentials, and other important settings used by tests.)\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Running tests on a remote Selenium Grid:</h3>\n\n🌐 SeleniumBase lets you run tests on remote Selenium Grids such as [BrowserStack](https://www.browserstack.com/automate#)'s Selenium Grid, [Sauce Labs](https://saucelabs.com/products/platform-configurator)'s Selenium Grid, other Grids, and even your own Grid:\n\n🌐 For setting browser desired capabilities while running Selenium remotely, see the ReadMe located here: https://github.com/seleniumbase/SeleniumBase/tree/master/examples/capabilities\n\nHere's how to connect to a BrowserStack Selenium Grid server for running tests:\n\n```zsh\npytest test_demo_site.py --server=USERNAME:KEY@hub.browserstack.com --port=80\n```\n\nHere's how to connect to a Sauce Labs Selenium Grid server for running tests:\n\n```zsh\npytest test_demo_site.py --server=USERNAME:KEY@ondemand.us-east-1.saucelabs.com --port=443 --protocol=https\n```\n\n🌐 Or you can create your own Selenium Grid for test distribution. ([See this ReadMe for details](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/utilities/selenium_grid/ReadMe.md))\n\n🌐 To use a server on the `https` protocol, add `--protocol=https`: (*Now automatic if the port is 443.*)\n\n```zsh\npytest test_demo_site.py --protocol=https --server=IP_ADDRESS --port=PORT\n```\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Using a Proxy Server:</h3>\n\n🌐 If you wish to use a proxy server for your browser tests (Chromium or Firefox), you can add `--proxy=IP_ADDRESS:PORT` as an argument on the command line.\n\n```zsh\npytest proxy_test.py --proxy=IP_ADDRESS:PORT\n```\n\n🌐 If the proxy server that you wish to use requires authentication, you can do the following (Chromium only):\n\n```zsh\npytest proxy_test.py --proxy=USERNAME:PASSWORD@IP_ADDRESS:PORT\n```\n\n🌐 SeleniumBase also supports SOCKS4 and SOCKS5 proxies:\n\n```zsh\npytest proxy_test.py --proxy=\"socks4://IP_ADDRESS:PORT\"\n\npytest proxy_test.py --proxy=\"socks5://IP_ADDRESS:PORT\"\n```\n\nTo make things easier, you can add your frequently-used proxies to PROXY_LIST in [proxy_list.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/proxy_list.py), and then use `--proxy=KEY_FROM_PROXY_LIST` to use the IP_ADDRESS:PORT of that key.\n\n```zsh\npytest proxy_test.py --proxy=proxy1\n```\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Changing the User-Agent:</h3>\n\n🔤 If you wish to change the User-Agent for your browser tests (Chrome and Firefox only), you can add `--agent=\"USER-AGENT-STRING\"` as an argument on the command line.\n\n```zsh\npytest user_agent_test.py --agent=\"Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7412.EU\"\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/green_logo.png\" title=\"SeleniumBase\" width=\"32\" /> Mobile Device Testing:</h3>\n\n📱 Use `--mobile` to quickly run your tests using Chrome's mobile device emulator with default values for device metrics (CSS Width, CSS Height, Pixel-Ratio) and a default value set for the user agent. To configure the mobile device metrics, use `--metrics=\"CSS_Width,CSS_Height,Pixel_Ratio\"` to set those values. You'll also be able to set the user agent with `--agent=\"USER-AGENT-STRING\"` (a default user agent will be used if not specified). To find real values for device metrics, [see this GitHub Gist](https://gist.github.com/sidferreira/3f5fad525e99b395d8bd882ee0fd9d00). For a list of available user agent strings, [check out this page](https://developers.whatismybrowser.com/useragents/explore/).\n\n```zsh\n# Run tests using Chrome's mobile device emulator (default settings)\npytest test_swag_labs.py --mobile\n\n# Run mobile tests specifying CSS Width, CSS Height, and Pixel-Ratio\npytest test_swag_labs.py --mobile --metrics=\"412,732,3\"\n\n# Run mobile tests specifying the user agent\npytest test_swag_labs.py --mobile --agent=\"Mozilla/5.0 (Linux; Android 9; Pixel 3 XL)\"\n```\n\n--------\n\n<a id=\"sb_options\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> 02. <code>SB()</code> method options</h2>\n\n```python\ntest=None  # Test Mode: Output, Logging, Continue on failure unless \"rtf\".\nrtf=None  # Shortcut / Duplicate of \"raise_test_failure\".\nraise_test_failure=None  # If \"test\" mode, raise Exception on 1st failure.\nbrowser=None  # Choose from \"chrome\", \"edge\", \"firefox\", or \"safari\".\nheadless=None  # Use the default headless mode for Chromium and Firefox.\nheadless1=None  # Use Chromium's old headless mode. (Fast, but limited)\nheadless2=None  # Use Chromium's new headless mode. (Has more features)\nlocale_code=None  # Set the Language Locale Code for the web browser.\nprotocol=None  # The Selenium Grid protocol: \"http\" or \"https\".\nservername=None  # The Selenium Grid server/IP used for tests.\nport=None  # The Selenium Grid port used by the test server.\nproxy=None  # Use proxy. Format: \"SERVER:PORT\" or \"USER:PASS@SERVER:PORT\".\nproxy_bypass_list=None  # Skip proxy when using the listed domains.\nproxy_pac_url=None  # Use PAC file. (Format: URL or USERNAME:PASSWORD@URL)\nmulti_proxy=None  # Allow multiple proxies with auth when multi-threaded.\nagent=None  # Modify the web browser's User-Agent string.\ncap_file=None  # The desired capabilities to use with a Selenium Grid.\ncap_string=None  # The desired capabilities to use with a Selenium Grid.\nrecorder_ext=None  # Enables the SeleniumBase Recorder Chromium extension.\ndisable_cookies=None  # Disable Cookies on websites. (Pages might break!)\ndisable_js=None  # Disable JavaScript on websites. (Pages might break!)\ndisable_csp=None  # Disable the Content Security Policy of websites.\nenable_ws=None  # Enable Web Security on Chromium-based browsers.\nenable_sync=None  # Enable \"Chrome Sync\" on websites.\nuse_auto_ext=None  # Use Chrome's automation extension.\nundetectable=None  # Use undetected-chromedriver to evade bot-detection.\nuc_cdp_events=None  # Capture CDP events in undetected-chromedriver mode.\nuc_subprocess=None  # Use undetected-chromedriver as a subprocess.\nlog_cdp_events=None  # Capture {\"performance\": \"ALL\", \"browser\": \"ALL\"}\nincognito=None  # Enable Chromium's Incognito mode.\nguest_mode=None  # Enable Chromium's Guest mode.\ndark_mode=None  # Enable Chromium's Dark mode.\ndevtools=None  # Open Chromium's DevTools when the browser opens.\nremote_debug=None  # Enable Chrome's Debugger on \"http://localhost:9222\".\nenable_3d_apis=None  # Enable WebGL and 3D APIs.\nswiftshader=None  # Chrome: --use-gl=angle / --use-angle=swiftshader-webgl\nad_block_on=None  # Block some types of display ads from loading.\nhost_resolver_rules=None  # Set host-resolver-rules, comma-separated.\nblock_images=None  # Block images from loading during tests.\ndo_not_track=None  # Tell websites that you don't want to be tracked.\nchromium_arg=None  # \"ARG=N,ARG2\" (Set Chromium args, \",\"-separated.)\nfirefox_arg=None  # \"ARG=N,ARG2\" (Set Firefox args, comma-separated.)\nfirefox_pref=None  # SET (Set Firefox PREFERENCE:VALUE set, \",\"-separated)\nuser_data_dir=None  # Set the Chrome user data directory to use.\nextension_zip=None  # Load a Chrome Extension .zip|.crx, comma-separated.\nextension_dir=None  # Load a Chrome Extension directory, comma-separated.\ndisable_features=None  # \"F1,F2\" (Disable Chrome features, \",\"-separated.)\nbinary_location=None  # Set path of the Chromium browser binary to use.\ndriver_version=None  # Set the chromedriver or uc_driver version to use.\nskip_js_waits=None  # Skip JS Waits (readyState==\"complete\" and Angular).\nwait_for_angularjs=None  # Wait for AngularJS to load after some actions.\nuse_wire=None  # Use selenium-wire's webdriver over selenium webdriver.\nexternal_pdf=None  # Set Chrome \"plugins.always_open_pdf_externally\":True.\nwindow_position=None  # Set the browser's starting window position: \"X,Y\"\nwindow_size=None  # Set the browser's starting window size: \"Width,Height\"\nis_mobile=None  # Use the mobile device emulator while running tests.\nmobile=None  # Shortcut / Duplicate of \"is_mobile\".\ndevice_metrics=None  # Set mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\"\nxvfb=None  # Run tests using the Xvfb virtual display server on Linux OS.\nxvfb_metrics=None  # Set Xvfb display size on Linux: \"Width,Height\".\nstart_page=None  # The starting URL for the web browser when tests begin.\nrec_print=None  # If Recorder is enabled, prints output after tests end.\nrec_behave=None  # Like Recorder Mode, but also generates behave-gherkin.\nrecord_sleep=None  # If Recorder enabled, also records self.sleep calls.\ndata=None  # Extra test data. Access with \"self.data\" in tests.\nvar1=None  # Extra test data. Access with \"self.var1\" in tests.\nvar2=None  # Extra test data. Access with \"self.var2\" in tests.\nvar3=None  # Extra test data. Access with \"self.var3\" in tests.\nvariables=None  # DICT (Extra test data. Access with \"self.variables\")\naccount=None  # Set account. Access with \"self.account\" in tests.\nenvironment=None  # Set the test env. Access with \"self.env\" in tests.\nheaded=None  # Run tests in headed/GUI mode on Linux, where not default.\nmaximize=None  # Start tests with the browser window maximized.\ndisable_ws=None  # Reverse of \"enable_ws\". (None and False are different)\ndisable_beforeunload=None  # Disable the \"beforeunload\" event on Chromium.\nsettings_file=None  # A file for overriding default SeleniumBase settings.\nposition=None  # Shortcut / Duplicate of \"window_position\".\nsize=None  # Shortcut / Duplicate of \"window_size\".\nuc=None  # Shortcut / Duplicate of \"undetectable\".\nundetected=None  # Shortcut / Duplicate of \"undetectable\".\nuc_cdp=None  # Shortcut / Duplicate of \"uc_cdp_events\".\nuc_sub=None  # Shortcut / Duplicate of \"uc_subprocess\".\nlocale=None  # Shortcut / Duplicate of \"locale_code\".\nlog_cdp=None  # Shortcut / Duplicate of \"log_cdp_events\".\nad_block=None  # Shortcut / Duplicate of \"ad_block_on\".\nserver=None  # Shortcut / Duplicate of \"servername\".\nguest=None  # Shortcut / Duplicate of \"guest_mode\".\nwire=None  # Shortcut / Duplicate of \"use_wire\".\npls=None  # Shortcut / Duplicate of \"page_load_strategy\".\nsjw=None  # Shortcut / Duplicate of \"skip_js_waits\".\nwfa=None  # Shortcut / Duplicate of \"wait_for_angularjs\".\ncft=None  # Use \"Chrome for Testing\"\nchs=None  # Use \"Chrome-Headless-Shell\"\nuse_chromium=None  # Use base \"Chromium\"\nsave_screenshot=None  # Save a screenshot at the end of each test.\nno_screenshot=None  # No screenshots saved unless tests directly ask it.\npage_load_strategy=None  # Set Chrome PLS to \"normal\", \"eager\", or \"none\".\ntimeout_multiplier=None  # Multiplies the default timeout values.\njs_checking_on=None  # Check for JavaScript errors after page loads.\nslow=None  # Slow down the automation. Faster than using Demo Mode.\ndemo=None  # Slow down and visually see test actions as they occur.\ndemo_sleep=None  # SECONDS (Set wait time after Slow & Demo Mode actions.)\nmessage_duration=None  # SECONDS (The time length for Messenger alerts.)\nhighlights=None  # Number of highlight animations for Demo Mode actions.\ninterval=None  # SECONDS (Autoplay interval for SB Slides & Tour steps.)\ntime_limit=None  # SECONDS (Safely fail tests that exceed the time limit.)\n```\n\nExample: [SeleniumBase/examples/raw_robot.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_robot.py)\n\n--------\n\n<a id=\"driver_options\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> 03. <code>Driver()</code> method options</h2>\n\n```python\nbrowser=None  # Choose from \"chrome\", \"edge\", \"firefox\", or \"safari\".\nheadless=None  # Use the default headless mode for Chromium and Firefox.\nheadless1=None  # Use Chromium's old headless mode. (Fast, but limited)\nheadless2=None  # Use Chromium's new headless mode. (Has more features)\nheaded=None  # Run tests in headed/GUI mode on Linux, where not default.\nlocale_code=None  # Set the Language Locale Code for the web browser.\nprotocol=None  # The Selenium Grid protocol: \"http\" or \"https\".\nservername=None  # The Selenium Grid server/IP used for tests.\nport=None  # The Selenium Grid port used by the test server.\nproxy=None  # Use proxy. Format: \"SERVER:PORT\" or \"USER:PASS@SERVER:PORT\".\nproxy_bypass_list=None  # Skip proxy when using the listed domains.\nproxy_pac_url=None  # Use PAC file. (Format: URL or USERNAME:PASSWORD@URL)\nmulti_proxy=None  # Allow multiple proxies with auth when multi-threaded.\nagent=None  # Modify the web browser's User-Agent string.\ncap_file=None  # The desired capabilities to use with a Selenium Grid.\ncap_string=None  # The desired capabilities to use with a Selenium Grid.\nrecorder_ext=None  # Enables the SeleniumBase Recorder Chromium extension.\ndisable_cookies=None  # Disable Cookies on websites. (Pages might break!)\ndisable_js=None  # Disable JavaScript on websites. (Pages might break!)\ndisable_csp=None  # Disable the Content Security Policy of websites.\nenable_ws=None  # Enable Web Security on Chromium-based browsers.\ndisable_ws=None  # Reverse of \"enable_ws\". (None and False are different)\nenable_sync=None  # Enable \"Chrome Sync\" on websites.\nuse_auto_ext=None  # Use Chrome's automation extension.\nundetectable=None  # Use undetected-chromedriver to evade bot-detection.\nuc_cdp_events=None  # Capture CDP events in undetected-chromedriver mode.\nuc_subprocess=None  # Use undetected-chromedriver as a subprocess.\nlog_cdp_events=None  # Capture {\"performance\": \"ALL\", \"browser\": \"ALL\"}\nno_sandbox=None  # (DEPRECATED) - \"--no-sandbox\" is always used now.\ndisable_gpu=None  # (DEPRECATED) - GPU is disabled if not \"swiftshader\".\nincognito=None  # Enable Chromium's Incognito mode.\nguest_mode=None  # Enable Chromium's Guest mode.\ndark_mode=None  # Enable Chromium's Dark mode.\ndevtools=None  # Open Chromium's DevTools when the browser opens.\nremote_debug=None  # Enable Chrome's Debugger on \"http://localhost:9222\".\nenable_3d_apis=None  # Enable WebGL and 3D APIs.\nswiftshader=None  # Chrome: --use-gl=angle / --use-angle=swiftshader-webgl\nad_block_on=None  # Block some types of display ads from loading.\nhost_resolver_rules=None  # Set host-resolver-rules, comma-separated.\nblock_images=None  # Block images from loading during tests.\ndo_not_track=None  # Tell websites that you don't want to be tracked.\nchromium_arg=None  # \"ARG=N,ARG2\" (Set Chromium args, \",\"-separated.)\nfirefox_arg=None  # \"ARG=N,ARG2\" (Set Firefox args, comma-separated.)\nfirefox_pref=None  # SET (Set Firefox PREFERENCE:VALUE set, \",\"-separated)\nuser_data_dir=None  # Set the Chrome user data directory to use.\nextension_zip=None  # Load a Chrome Extension .zip|.crx, comma-separated.\nextension_dir=None  # Load a Chrome Extension directory, comma-separated.\ndisable_features=None  # \"F1,F2\" (Disable Chrome features, \",\"-separated.)\nbinary_location=None  # Set path of the Chromium browser binary to use.\ndriver_version=None  # Set the chromedriver or uc_driver version to use.\npage_load_strategy=None  # Set Chrome PLS to \"normal\", \"eager\", or \"none\".\nuse_wire=None  # Use selenium-wire's webdriver over selenium webdriver.\nexternal_pdf=None  # Set Chrome \"plugins.always_open_pdf_externally\":True.\nwindow_position=None  # Set the browser's starting window position: \"X,Y\"\nwindow_size=None  # Set the browser's starting window size: \"Width,Height\"\nis_mobile=None  # Use the mobile device emulator while running tests.\nmobile=None  # Shortcut / Duplicate of \"is_mobile\".\nd_width=None  # Set device width\nd_height=None  # Set device height\nd_p_r=None  # Set device pixel ratio\nposition=None  # Shortcut / Duplicate of \"window_position\".\nsize=None  # Shortcut / Duplicate of \"window_size\".\nuc=None  # Shortcut / Duplicate of \"undetectable\".\nundetected=None  # Shortcut / Duplicate of \"undetectable\".\nuc_cdp=None  # Shortcut / Duplicate of \"uc_cdp_events\".\nuc_sub=None  # Shortcut / Duplicate of \"uc_subprocess\".\nlocale=None  # Shortcut / Duplicate of \"locale_code\".\nlog_cdp=None  # Shortcut / Duplicate of \"log_cdp_events\".\nad_block=None  # Shortcut / Duplicate of \"ad_block_on\".\nserver=None  # Shortcut / Duplicate of \"servername\".\nguest=None  # Shortcut / Duplicate of \"guest_mode\".\nwire=None  # Shortcut / Duplicate of \"use_wire\".\npls=None  # Shortcut / Duplicate of \"page_load_strategy\".\nuse_chromium=None  # Use base \"Chromium\"\ncft=None  # Use \"Chrome for Testing\"\nchs=None  # Use \"Chrome-Headless-Shell\"\n```\n\nExample: [SeleniumBase/examples/raw_driver_manager.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_driver_manager.py)\n\n--------\n\n<a id=\"sb_cdp_chrome_options\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> 04. <code>sb_cdp.Chrome()</code> method options</h2>\n\n```python\nurl: Optional[str] = None\nuser_data_dir: Optional[PathLike] = None\nheadless: Optional[bool] = False\nincognito: Optional[bool] = False\nguest: Optional[bool] = False\nbrowser_executable_path: Optional[PathLike] = None\nbrowser_args: Optional[List[str]] = None\nxvfb_metrics: Optional[List[str]] = None  # \"Width,Height\" for Linux\nad_block: Optional[bool] = False\nsandbox: Optional[bool] = True\nlang: Optional[str] = None  # Set the Language Locale Code\nhost: Optional[str] = None  # Chrome remote-debugging-host\nport: Optional[int] = None  # Chrome remote-debugging-port\nxvfb: Optional[int] = None  # Use a special virtual display on Linux\nheaded: Optional[bool] = None  # Override default Xvfb mode on Linux\nexpert: Optional[bool] = None  # Open up closed Shadow-root elements\nagent: Optional[str] = None  # Set the user-agent string\nproxy: Optional[str] = None  # \"host:port\" or \"user:pass@host:port\"\ntzone: Optional[str] = None  # Eg \"America/New_York\", \"Asia/Kolkata\"\ngeoloc: Optional[list | tuple] = None  # Eg (48.87645, 2.26340)\nextension_dir: Optional[str] = None  # Chrome extension directory\nplatform: Optional[str] = None  # Set the Platform. Eg: \"MacIntel\"\n```\n\nExample: [SeleniumBase/examples/cdp_mode/raw_geolocation.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_geolocation.py)\n\n--------\n\n<a id=\"activate_cdp_mode_options\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> 05. <code>activate_cdp_mode()</code> method options</h2>\n\n```python\nurl: Optional[str] = None  # The URL to navigate to\nlang: Optional[str] = None  # Set the Language Locale Code\nagent: Optional[str] = None  # Set the user-agent string\ntzone: Optional[str] = None  # Eg \"America/New_York\", \"Asia/Kolkata\"\ngeoloc: Optional[list | tuple] = None  # Eg (48.87645, 2.26340)\nplatform: Optional[str] = None  # Set the Platform. Eg: \"MacIntel\"\n```\n\nExample: [SeleniumBase/examples/cdp_mode/raw_geolocation_sb.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_geolocation_sb.py)\n\nNote that if CDP Mode is already active, the options above can also be used when calling `sb.cdp.open()`. (The `url` arg is required in this case.)\n\n--------\n\n[<img src=\"https://seleniumbase.github.io/cdn/img/fancy_logo_14.png\" title=\"SeleniumBase\" width=\"290\">](https://github.com/seleniumbase/SeleniumBase)\n"
  },
  {
    "path": "help_docs/demo_mode.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Demo Mode 🎦</h2>\n\n<p align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/gif/xkcd_vid.gif\" width=\"480\" alt=\"SeleniumBase Example\" title=\"SeleniumBase Example\" /></p>\n\n<p align=\"left\">🔵 <b translate=\"no\">Demo Mode</b> helps you see what a test is doing.</p>\n\n<p align=\"left\">🏇💨 👀 If a test runs too fast for your eyes, use <b translate=\"no\">Demo Mode</b> to slow it down, highlight actions, and display assertions. Example usage:</p>\n\n```zsh\ncd examples/\npytest test_coffee_cart.py --demo\n```\n\n<p align=\"left\"><a href=\"https://seleniumbase.io/coffee/\" target=\"_blank\"><img src=\"https://seleniumbase.github.io/cdn/gif/coffee_cart.gif\" width=\"480\" alt=\"SeleniumBase Coffee Cart Test\" title=\"SeleniumBase Coffee Cart Test\" /></a></p>\n\n> <p>(<code translate=\"no\">--demo</code> mode slows down tests and highlights actions)</p>\n\n--------\n\nAnother example:\n\n```zsh\npytest my_first_test.py --demo\n```\n\n--------\n\n<p align=\"left\">Here's how to run <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_swag_labs.py\" target=\"_blank\">test_swag_labs.py</a> from <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples\" target=\"_blank\">examples/</a> in <b>Demo Mode</b>:</p>\n\n\n```zsh\npytest test_swag_labs.py --demo\n```\n\n<p align=\"left\"><a href=\"https://www.saucedemo.com/\" target=\"_blank\"><img src=\"https://seleniumbase.github.io/cdn/gif/swag_demo_2.gif\" width=\"450\" alt=\"SeleniumBase Example\" title=\"SeleniumBase Example\" /></a></p>\n\n--------\n\n<p>Here's an example that only uses the <code translate=\"no\">highlight()</code> method for highlighting browser actions:</p>\n\n\n<p align=\"left\">(<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_error_page.py\" target=\"_blank\">test_error_page.py</a> from <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples\" target=\"_blank\">examples/</a>)</p>\n\n```zsh\npytest test_error_page.py\n```\n\n<p align=\"left\"><a href=\"https://seleniumbase.io/error_page/\" target=\"_blank\"><img src=\"https://seleniumbase.github.io/cdn/gif/error_page.gif\" width=\"450\" alt=\"SeleniumBase Example\" title=\"SeleniumBase Example\" /></a></p>\n\n--------\n\nHere's an example of a mobile test in <b translate=\"no\">Demo Mode</b>:\n\n<p align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/gif/skype_mobile_test_2.gif\" width=\"450\" alt=\"SeleniumBase Example\" title=\"SeleniumBase Example\" /></p>\n"
  },
  {
    "path": "help_docs/desired_capabilities.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Using Desired Capabilities</h2>\n\nYou can specify browser capabilities when running SeleniumBase tests on a remote Selenium Grid server (such as <a href=\"https://www.browserstack.com/automate/capabilities\" target=\"_blank\">BrowserStack</a> or <a href=\"https://saucelabs.com/products/platform-configurator\" target=\"_blank\">Sauce Labs</a>).\n\nSample run commands may look like this when run from the [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder: (The browser is now specified in the capabilities file.)\n\n```zsh\npytest test_demo_site.py --browser=remote --server=USERNAME:KEY@hub.browserstack.com --port=80 --cap_file=capabilities/sample_cap_file_BS.py\n```\n\n```zsh\npytest test_demo_site.py --browser=remote --server=USERNAME:KEY@ondemand.us-east-1.saucelabs.com --port=443 --protocol=https --cap_file=capabilities/sample_cap_file_SL.py\n```\n\n(Parameters: ``--browser=remote``, ``--server=SERVER``, ``--port=PORT``, and ``--cap_file=CAP_FILE.py``)\n\nHere's an example desired capabilities file for BrowserStack using the newer SDK format in a `.yml` / `.yaml` file:\n\n```yml\nplatforms:\n  - browserName: safari\n    osVersion: 17\n    deviceName: iPhone 15 Pro Max\nbuildIdentifier: ${BUILD_NUMBER}\nparallelsPerPlatform: 1\nprojectName: My Project\nbrowserstackLocal: true\ndebug: true\nnetworkLogs: true\n```\n\nHere's an example desired capabilities file for BrowserStack using the legacy JSONWP format in a `.py` file:\n\n```python\ndesired_cap = {\n    \"browser\": \"Chrome\",\n    \"os\": \"Windows\",\n    \"os_version\": \"11\",\n    \"browser_version\": \"latest\",\n    \"browserstack.console\": \"info\",\n    \"browserstack.debug\": \"true\",\n    \"browserstack.networkLogs\": \"true\",\n    \"browserstack.local\": \"true\",\n}\n```\n\nHere's an example desired capabilities file for Sauce Labs:\n\n```python\ncapabilities = {\n    \"browserName\": \"chrome\",\n    \"browserVersion\": \"latest\",\n    \"platformName\": \"macOS 10.14\",\n    \"sauce:options\": {},\n}\n```\n\n(Note that the browser is now being specified in the capabilities file, rather than with ``--BROWSER`` when using a **remote** Selenium Grid. If using a **local** Selenium Grid, specify the browser, eg: ``--firefox``.)\n\n<div><b>You can generate specific desired capabilities using:</b></div>\n\n<ul>\n    <li><a href=\"https://www.browserstack.com/docs/automate/capabilities\" target=\"_blank\">BrowserStack desired capabilities</a></li>\n    <li><a href=\"https://saucelabs.com/products/platform-configurator\" target=\"_blank\">Sauce Labs desired capabilities</a></li>\n</ul>\n\n<div><b>Parsing desired capabilities:</b></div>\n\nSeleniumBase has a desired capabilities parser that can capture all lines from the specified file in the following formats:\n\n```python\n'KEY': 'VALUE'\n'KEY': True\n'KEY': False\ncaps['KEY'] = \"VALUE\"\ncaps['KEY'] = True\ncaps['KEY'] = False\n```\n\n(Each pair must be on a separate line. You can interchange single and double quotes.)\n\nYou can also swap ``--browser=remote`` with an actual browser, eg ``--browser=chrome``, which will combine the default SeleniumBase desired capabilities with those that were specified in the capabilities file when using ``--cap_file=FILE.py``. Capabilities will override other parameters, so if you set the browser to one thing and the capabilities browser to another, SeleniumBase will use the capabilities browser.\n\nYou'll need default SeleniumBase capabilities for:\n* Using a proxy server (not the same as a Selenium Grid server)\n* Downloading files to a desired folder\n* Disabling some warnings on Chrome\n* Overriding a website's Content Security Policy on Chrome\n* Other possible reasons\n\nYou can also set browser desired capabilities from a command-line string. Eg:\n\n```zsh\npytest test_swag_labs.py --cap-string='{\"browserName\":\"chrome\",\"name\":\"test1\"}' --server=\"127.0.0.1\" --browser=remote\n```\n\n(Enclose cap-string in single quotes. Enclose parameter keys in double quotes.)\n\nIf you pass ``\"*\"`` into the ``\"name\"`` field of ``--cap-string``, the name will become the test identifier. Eg:\n\n```zsh\npytest my_first_test.py --cap-string='{\"browserName\":\"chrome\",\"name\":\"*\"}' --server=\"127.0.0.1\" --browser=chrome\n```\n\nExample name: ``\"my_first_test.MyTestClass.test_basics\"``\n\n<h3>Using a local Selenium Grid</h3>\n\nIf using a local Selenium Grid with SeleniumBase, start up the Grid Hub and nodes first:\n\n```zsh\nsbase grid-hub start\nsbase grid-node start\n```\n\n(The Selenium Server JAR file will be automatically downloaded for first-time Grid users. You'll also need Java installed to start up the Grid.)\n"
  },
  {
    "path": "help_docs/features_list.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=EablmOazy-k\"><img src=\"http://img.youtube.com/vi/EablmOazy-k/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"365\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=EablmOazy-k\">Watch the SeleniumBase tutorial from Selenium-Conf-2023 on YouTube</a></b>)</p>\n\n<a id=\"feature_list\"></a>\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> SeleniumBase Features: 🏰</h2>\n\n* A powerful Python framework for browser automation and E2E UI testing.\n* Includes [Recorder Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md) for instantly generating browser tests in Python.\n* Supports multiple browsers, tabs, iframes, and proxies in the same test.\n* Includes [Test Case Management Software](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md) with Markdown technology.\n* Automatic smart-waiting improves reliability and prevents flaky tests.\n* Supports [pytest](https://docs.pytest.org/en/latest/), [unittest](https://docs.python.org/3/library/unittest.html), [nose](http://nose.readthedocs.io/en/latest/), and [behave](https://behave.readthedocs.io/en/stable/index.html) for finding/running tests.\n* All the code is open source. Look inside to learn about any feature.\n* Powerful logging tools for [dashboards, reports, and screenshots](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md).\n* Can run tests in Headless Mode to hide the browser. (``--headless``)\n* Can run tests multithreaded from parallel browsers. (``-n NUM_THREADS``)\n* Can run tests from a shared browser session. (``--reuse-session``/``--rs``)\n* Can run tests using [Chromium's mobile device emulator](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/mobile_testing.md). (``--mobile``)\n* Can run tests through a proxy server. (``--proxy=IP_ADDRESS:PORT``)\n* Can run tests with proxy settings via PAC URL. (``--proxy-pac-url=URL.pac``)\n* Can run tests through an authenticated proxy server. (``--proxy=USER:PASS@HOST:PORT``)\n* Can run tests with proxy+auth via PAC URL. (``--proxy-pac-url=USER:PASS@URL.pac``)\n* Can run tests with a customized browser user agent. (``--agent=USER_AGENT_STRING``)\n* Can set a Chromium User Data Directory/Profile to load. (``--user-data-dir=DIR``)\n* Can [avoid detection](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md) by sites that try to block Selenium. (``--undetected``/``--uc``)\n* Can [use CDP Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md) for advanced stealth abilities. (``activate_cdp_mode(url)``)\n* Can integrate with [selenium-wire](https://github.com/wkeeling/selenium-wire) for inspecting browser requests. (``--wire``)\n* Can load Chrome Extension ZIP files. (``--extension-zip=ZIP``)\n* Can load Chrome Extension folders. (``--extension-dir=DIR``)\n* Powerful [console scripts](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/ReadMe.md). (Type **``seleniumbase``** or **``sbase``** to use.)\n* Has the ability to translate tests into [multiple spoken languages](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/translations).\n* Has a flexible [command-line interface](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md) for customizing test runs.\n* Has a [global config file](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) for configuring settings as needed.\n* Includes a tool for [creating interactive web presentations](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/ReadMe.md).\n* Includes [Chart Maker](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/chart_maker/ReadMe.md), a tool for creating interactive charts.\n* Includes a [dialog box builder](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/dialog_boxes/ReadMe.md) to allow user-input during automation.\n* Includes a [website tour builder](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md) for creating interactive walkthroughs.\n* Includes a GUI for running pytest scripts: [SeleniumBase Commander](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/commander.md).\n* Includes integrations for [GitHub Actions](https://seleniumbase.io/integrations/github/workflows/ReadMe/), [Google Cloud](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/google_cloud/ReadMe.md), [Azure](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/azure/jenkins/ReadMe.md), [S3](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/plugins/s3_logging_plugin.py), and [Docker](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/docker/ReadMe.md).\n* Can handle Google Authenticator logins with [Python's one-time password library](https://pyotp.readthedocs.io/en/latest/).\n* Can load and make assertions on PDF files from websites or the local file system.\n* Can inspect HTML to find issues and points of interest with the [HTML Inspector](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/html_inspector.md).\n* Is backwards-compatible with Python [WebDriver](https://www.selenium.dev/projects/) methods. (Use: ``self.driver``)\n* Can execute JavaScript code from Python calls. (Use: ``self.execute_script()``)\n* Can pierce through [Shadow DOM selectors](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/shadow_dom.md). (Add ``::shadow`` to CSS fragments.)\n* Includes a hybrid-automation solution, [MasterQA](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/masterqa/ReadMe.md), to speed up manual testing.\n* Includes useful [Python decorators and password obfuscation methods](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/ReadMe.md).\n\n--------\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=yEQeAU_mrg0\"><img src=\"http://img.youtube.com/vi/yEQeAU_mrg0/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"365\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=yEQeAU_mrg0\">Have fun with test automation!</a></b>)</p>\n\n(<b><a href=\"https://www.youtube.com/watch?v=Sjzq9kU5kOw\">Watch the original tutorial on YouTube</a></b>)\n\n<p align=\"left\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_10.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"240\"></a></p>\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb.png\" title=\"SeleniumBase\" width=\"240\"></a>\n"
  },
  {
    "path": "help_docs/handling_iframes.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> How to handle iframes</h2>\n\n🖼️ <b>iframes</b> follow the same principle as new windows: You must first switch to the iframe if you want to perform actions in there:\n\n```python\nself.switch_to_frame(\"iframe\")\n# ... Now perform actions inside the iframe\nself.switch_to_parent_frame()  # Exit the current iframe\n```\n\nTo exit from multiple iframes, use ``self.switch_to_default_content()``. (If inside a single iframe, this has the same effect as ``self.switch_to_parent_frame()``.)\n\n```python\nself.switch_to_frame('iframe[name=\"frame1\"]')\nself.switch_to_frame('iframe[name=\"frame2\"]')\n# ... Now perform actions inside the inner iframe\nself.switch_to_default_content()  # Back to the main page\n```\n\n🖼️ You can also use a context manager to act inside iframes:\n\n```python\nwith self.frame_switch(\"iframe\"):\n    # ... Now perform actions while inside the code block\n# You have left the iframe\n```\n\nThis also works with nested iframes:\n\n```python\nwith self.frame_switch('iframe[name=\"frame1\"]'):\n    with self.frame_switch('iframe[name=\"frame2\"]'):\n        # ... Now perform actions while inside the code block\n    # You are now back inside the first iframe\n# You have left all the iframes\n```\n\n🖼️ In special cases, you may want to set the page to the content of an iframe:\n\n```python\nself.set_content_to_frame(\"iframe\")\n```\n\nTo back out of one call of that, use:\n\n```python\nself.set_content_to_parent()\n```\n\nTo back out of all nested calls of that, use:\n\n```python\nself.set_content_to_default()\n```\n\n🖼️ See [SeleniumBase/examples/iframe_tests.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/iframe_tests.py) for tests that use all available iframe commands.\n\n--------\n\n<p align=\"center\"><div align=\"center\"><a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20seleniumbase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase.io Docs\" /></a></div></p>\n"
  },
  {
    "path": "help_docs/happy_customers.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h3 align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_b.png\" alt=\"SeleniumBase\" width=\"340\" /></h3>\n\n### Businesses who have used SeleniumBase:\n\n* [MIT](https://web.mit.edu/)\n* [Intel](https://www.intel.com/)\n* [Sony](https://www.sony.com/)\n* [Tesla](https://www.tesla.com/)\n* [iboss](https://www.iboss.com/)\n* [Apple](https://www.apple.com/)\n* [Oracle](https://www.oracle.com/)\n* [Akamai](https://www.akamai.com/)\n* [Allstate](https://www.allstate.com/)\n* [Amazon](https://www.amazon.com/)\n* [Sequoia](https://www.sequoia.com/)\n* [HubSpot](https://www.hubspot.com/)\n* [Streamlit](https://streamlit.io/)\n* [Autodesk](https://www.autodesk.com/)\n* [Veracode](https://www.veracode.com/)\n* [Microsoft](https://www.microsoft.com/)\n* [Medtronic](https://www.medtronic.com/)\n* [Broadcom](https://www.broadcom.com/)\n* [Snowflake](https://www.snowflake.com/)\n* [PlayStation](https://www.playstation.com/)\n* [Bright Data](https://brightdata.com/)\n* [GlobalLogic](https://www.globallogic.com/)\n* [Thoma Bravo](https://www.thomabravo.com/)\n* [ASICS Digital](https://www.asicsdigital.com/)\n* [Expedia Group](https://lifeatexpediagroup.com/)\n* [Deutsche Bank](https://www.db.com/)\n* [The Very Group](https://www.theverygroup.com/)\n* [ActiveCampaign](https://www.activecampaign.com/)\n* [Blue Trail Software](https://www.bluetrail.software/)\n* [Ness Technologies](https://www.ness-tech.co.il/)\n* [Johnson & Johnson](https://www.jnj.com/)\n* [AutomationAnywhere](https://www.automationanywhere.com/)\n* [Harvard Medical School](https://hms.harvard.edu/)\n* [Singapore Institute of Tech](https://www.singaporetech.edu.sg)\n* [Mississippi State University](https://www.msstate.edu/)\n* [The New York Public Libary](https://www.nypl.org/)\n* [CCC Intelligent Solutions](https://www.cccis.com/)\n* [Medical Home Network](https://www.medicalhomenetwork.org/)\n* [Boehringer Ingelheim](https://www.boehringer-ingelheim.com/)\n* [Queen's University](https://www.queensu.ca/)\n* [L1NNA Laboratory](https://l1nna.com/)\n* [Canfield Scientific](https://www.canfieldsci.com/)\n* [Thomson Reuters](https://www.thomsonreuters.com/)\n* [Wellesley College](https://www.wellesley.edu/)\n* [CA Technologies](https://tinyurl.com/ca-technologies)\n* [Momin Solutions](https://www.mominsolutions.com/)\n* [Optum Financial](https://www.optum.com/financial-services.html)\n* [ActiveCampaign](https://www.activecampaign.com/)\n* [Sphere Partners](https://www.sphereinc.com/)\n* [MadeiraMadeira](https://www.madeiramadeira.com.br/)\n* [Raid The Room](https://raidtheroom.com/)\n* [First American](https://www.firstam.com/)\n* [Origin Energy](https://www.originenergy.com.au/)\n* [InterSystems](https://www.intersystems.com/)\n* [Develop Soft](https://www.developsoft.com/)\n* [AstraZeneca](https://www.astrazeneca.com/)\n* [Ping Identity](https://www.pingidentity.com/)\n* [Scale Media](https://scalemedia.com/)\n* [CaptivateIQ](https://www.captivateiq.com/)\n* [StreamSets](https://streamsets.com/)\n* [Visual Data](https://www.visualdatamedia.com/)\n* [Betterteem](https://www.betterteem.com/)\n* [SenseTime](https://www.sensetime.com/en)\n* [Rubiscape](https://www.rubiscape.com/)\n* [Aeturnum](https://aeturnum.com/)\n* [Cellebrite](https://www.cellebrite.com/en/home/)\n* [RaySecur](https://www.raysecur.com/)\n* [Payability](https://www.payability.com/)\n* [Ben Fatto](https://benfatto.net.br/en/)\n* [BW Legal](https://www.bwlegal.co.uk/)\n* [GeoNode](http://geonode.org/)\n* [Empower](https://empower.me/)\n* [CardFree](https://cardfree.com/)\n* [Softrams](https://www.softrams.com/)\n* [Invensity](https://www.invensity.com/)\n* [SunCorp](https://www.suncorpgroup.com.au/)\n* [Logitech](https://www.logitech.com/)\n* [Modulos](https://www.modulos.ai/)\n* [Igrowker](https://igrowker.com/)\n* [axxessio](https://www.axxessio.com/en/)\n* [StorkJet](https://storkjet.com/)\n* [Networx](https://www.networx.com/)\n* [Elevatus](https://www.elevatus.io/)\n* [VMware](https://www.vmware.com/)\n* [Rakuten](https://global.rakuten.com/corp/about/)\n* [Nagarro](https://www.nagarro.com/en)\n* [Ark PES](https://www.arkpes.com/)\n* [SkillsVR](https://skillsvr.com/)\n* [Evereve](https://evereve.com/)\n* [Planeks](https://www.planeks.net/)\n* [Upwork](https://www.upwork.com/)\n* [Yandex](https://yandex.ru/)\n* [Picsart](https://picsart.com/)\n* [Serquo](https://serquo.com/)\n* [WPILib](https://wpilib.org/)\n* [QBurst](https://www.qburst.com/)\n* [Kinetik](https://kinetik.care/)\n* [Exadel](https://exadel.com/)\n* [netLex](https://netlex.io/en/)\n* [XP Inc](https://www.xpinc.com/)\n* [Roche](https://www.roche.com/)\n* [Alokin](https://alokin.in/)\n* [Stride](https://stride.ai/)\n* [Cubic](https://www.cubic.com/)\n* [Baidu](https://www.baidu.com/)\n* [Intive](https://intive.com/)\n* [Seek](https://www.seek.com.au/)\n* [Vultr](https://www.vultr.com/)\n* [HqO](https://www.hqo.co/)\n* [PDS](https://www.pdsinc.com/)\n* [Pico](https://trypico.com/)\n* [Iver](https://www.iver.com/)\n* And more...\n\n--------\n\n### Case Study:\n\n> **HubSpot**:\n\nIn addition to using SeleniumBase for testing the UI of their content management system, HubSpot used SeleniumBase to automate the migration of website pages from their old CMS to their new one, which saved them over one million USD and a significant amount of time.\n\nLearn how HubSpot uses SeleniumBase for website testing by reading: [Automated Testing with Selenium](https://dev.hubspot.com/blog/bid/88880/Automated-Integration-Testing-with-Selenium-at-HubSpot#hs_cos_wrapper_name)\n\nFor more reading about automation at HubSpot, see: [The Classic \"QA Team\" is Obsolete](https://product.hubspot.com/blog/the-classic-qa-team-is-obsolete#hs_cos_wrapper_name)\n\n--------\n\n### How is this list generated?\n\nMost of these rows come from [LinkedIn search results](https://www.linkedin.com/search/results/people/?keywords=seleniumbase&origin=SWITCH_SEARCH_VERTICAL) of profile descriptions, where employees mentioned that they have used SeleniumBase at their company. These details may also be found in other public networks and social media sites, such as GitHub (where organizations could have public repos that use SeleniumBase).\n"
  },
  {
    "path": "help_docs/hidden_files_info.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## Showing hidden files on macOS\n\nDepending on your macOS settings, some files may be hidden from view in your Finder window, such as ``.gitignore``.\n\n* On newer versions of macOS, use the following in a Finder window to view hidden files:\n\nPress the **“Command” + “Shift” + “.” (period)** keys at the same time.\n\n(The hidden files will show up as translucent in the folder. If you want to obscure the files again, press the same “Command” + “Shift” + “.” (period) combination.)\n\n* On older versions of macOS, use the following command in a Terminal window to view hidden files, and then reopen the Finder window:\n\n```zsh\ndefaults write com.apple.finder AppleShowAllFiles -bool true\n```\n\nMore info on that can be found here:<ul>\n<li><a href=\"https://www.defaults-write.com/show-hidden-files-in-os-x-finder/\">https://www.defaults-write.com/show-hidden-files-in-os-x-finder/</a></li>\n<li><a href=\"https://www.macworld.co.uk/how-to/mac-software/hidden-files-mac-3520878/\">https://www.macworld.co.uk/how-to/mac-software/hidden-files-mac-3520878/</a></li>\n<li><a href=\"https://setapp.com/how-to/show-hidden-files-on-mac\">https://setapp.com/how-to/show-hidden-files-on-mac</a></li>\n</ul>\n"
  },
  {
    "path": "help_docs/how_it_works.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> How SeleniumBase Works 👁️</h2>\n\n<a id=\"how_seleniumbase_works\"></a>\n\n👁️🔎 The primary [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) works by extending [pytest](https://docs.pytest.org/en/latest/) as a direct plugin. SeleniumBase automatically spins up web browsers for tests, and then gives those tests access to the SeleniumBase libraries through the [BaseCase class](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py). Tests are also given access to [SeleniumBase command-line options](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md) and [SeleniumBase methods](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md).\n\n👁️🔎 ``pytest`` uses a feature called test discovery to automatically find and run Python methods that start with ``test_`` when those methods are located in Python files that start with ``test_`` or end with ``_test.py``.\n\n👁️🔎 The primary [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) starts by importing ``BaseCase``:\n\n```python\nfrom seleniumbase import BaseCase\n```\n\n👁️🔎 This next line activates ``pytest`` when a file is called directly with ``python`` by accident:\n\n```python\nBaseCase.main(__name__, __file__)\n```\n\n👁️🔎 Classes can inherit ``BaseCase`` to gain SeleniumBase functionality:\n\n```python\nclass MyTestClass(BaseCase):\n```\n\n👁️🔎 Test methods inside ``BaseCase`` classes become SeleniumBase tests: (These tests automatically launch a web browser before starting, and quit the web browser after ending. Default settings can be changed via command-line options.)\n\n```python\nclass MyTestClass(BaseCase):\n    def test_abc(self):\n        # ...\n```\n\n👁️🔎 [SeleniumBase APIs](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md) can be called from tests via ``self``:\n\n```python\nclass MyTestClass(BaseCase):\n    def test_abc(self):\n        self.open(\"https://example.com\")\n```\n\n👁️🔎 Here's what a full test might look like:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass TestSimpleLogin(BaseCase):\n    def test_simple_login(self):\n        self.open(\"seleniumbase.io/simple/login\")\n        self.type(\"#username\", \"demo_user\")\n        self.type(\"#password\", \"secret_pass\")\n        self.click('a:contains(\"Sign in\")')\n        self.assert_exact_text(\"Welcome!\", \"h1\")\n        self.assert_element(\"img#image1\")\n        self.highlight(\"#image1\")\n        self.click_link(\"Sign out\")\n        self.assert_text(\"signed out\", \"#top_message\")\n```\n\n(See the example, [test_simple_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_simple_login.py), for reference.)\n\n👁️🔎 Here are some examples of running tests with ``pytest``:\n\n```zsh\npytest test_mfa_login.py\npytest --headless -n8 --dashboard --html=report.html -v --rs --crumbs\npytest -m marker2\npytest -k agent\npytest offline_examples/\n```\n\n👁️🔎 Here's a [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) that uses the raw `driver`. Unlike the format mentioned earlier, it can be run with `python` instead of `pytest`. The `driver` includes original `driver` methods and new ones added by SeleniumBase:\n\n```python\nfrom seleniumbase import Driver\n\ndriver = Driver()\ntry:\n    driver.open(\"seleniumbase.io/simple/login\")\n    driver.type(\"#username\", \"demo_user\")\n    driver.type(\"#password\", \"secret_pass\")\n    driver.click('a:contains(\"Sign in\")')\n    driver.assert_exact_text(\"Welcome!\", \"h1\")\n    driver.assert_element(\"img#image1\")\n    driver.highlight(\"#image1\")\n    driver.click_link(\"Sign out\")\n    driver.assert_text(\"signed out\", \"#top_message\")\nfinally:\n    driver.quit()\n```\n\n(See the example, [raw_login_driver.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_login_driver.py), for reference.)\n\n👁️🔎 Note that regular SeleniumBase formats (ones that use `BaseCase`, the `SB` context manager, or the `sb` `pytest` fixture) have more methods than the improved `driver` format. The regular formats also have more features. Some features, (such as the [SeleniumBase dashboard](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md)), require a `pytest` format.\n\n--------\n\n### ✅ No More Flaky Tests!\n\n<p>SeleniumBase methods automatically wait for page elements to finish loading before interacting with them (<i>up to a timeout limit</i>). This means <b>you no longer need random <span><code>time.sleep()</code></span> statements</b> in your scripts.</p>\n<img src=\"https://img.shields.io/badge/Flaky%20Tests%3F-%20NO%21-11BBDD.svg\" alt=\"NO MORE FLAKY TESTS!\" />\n\n**There are three layers of protection that provide reliability for tests using SeleniumBase:**\n\n* **(1)**: Selenium's default ``pageLoadStrategy`` is ``normal``: This strategy causes Selenium to wait for the full page to load, with HTML content and sub-resources downloaded and parsed.\n\n* **(2)**: SeleniumBase includes methods such as ``wait_for_ready_state_complete()``, which run inside other SeleniumBase methods to ensure that it's safe to proceed with the next command.\n\n* **(3)**: SeleniumBase methods automatically wait for elements to be visible and interactable before interacting with those elements.\n\n**If you want to speed up your tests and you think the third level of protection is enough by itself, you can use command-line options to remove the first, the second, or both of those first two levels of protection:**\n\n* ``--pls=none`` --> Set ``pageLoadStrategy`` to ``\"none\"``: This strategy causes Selenium to return immediately after the initial HTML content is fully received by the browser.\n\n* ``--sjw`` --> Skip JS Waits, such as ``wait_for_ready_state_complete()``.\n\n--------\n\n<p><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"300\" /></a></p>\n<p><a href=\"https://www.python.org/downloads/\" target=\"_blank\"><img src=\"https://img.shields.io/pypi/pyversions/seleniumbase.svg?color=22AAEE&logo=python&logoColor=FEDC54\" title=\"Supported Python Versions\" /></a></p>\n"
  },
  {
    "path": "help_docs/html_inspector.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> The HTML Inspector 🕵️</h2>\n\n🕵️ <b>HTML Inspector</b> provides useful info about a web page.\n\n🕵️ (<i>Based on: [github.com/philipwalton/html-inspector](https://github.com/philipwalton/html-inspector)</i>)\n\n🕵️ Example: [examples/test_inspect_html.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_inspect_html.py) (Chromium-only)\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass HtmlInspectorTests(BaseCase):\n    def test_html_inspector(self):\n        self.open(\"https://xkcd.com/1144/\")\n        self.inspect_html()\n```\n\n--------\n\n```zsh\npytest test_inspect_html.py\n============== test session starts ==============\n\n* HTML Inspection Results: https://xkcd.com/1144/\n⚠️  'property' is not a valid attribute of the <meta> element.\n⚠️  Do not use <div> or <span> elements without any attributes.\n⚠️  'srcset' is not a valid attribute of the <img> element.\n⚠️  The 'border' attribute is no longer valid on the <img> element.\n⚠️  The <center> element is obsolete.\n⚠️  The id 'comicLinks' appears more than once in the document.\n* (See the Console output for details!)\n```\n"
  },
  {
    "path": "help_docs/install.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> SeleniumBase Installation</h2>\n\n<h4>If installing <code>seleniumbase</code> directly from <a href=\"https://pypi.python.org/pypi/seleniumbase\">PyPI</a>, (the Python Package Index), use:</h4>\n\n```zsh\npip install seleniumbase\n```\n\n<h4>To upgrade an existing <code>seleniumbase</code> install from PyPI:</h4>\n\n```zsh\npip install -U seleniumbase\n```\n\n<h4>If installing <code>seleniumbase</code> from a Git clone, use:</h4>\n\n```zsh\ngit clone https://github.com/seleniumbase/SeleniumBase.git\ncd SeleniumBase/\npip install .\n```\n\n<h4>For a development mode install in editable mode, use:</h4>\n\n```zsh\ngit clone https://github.com/seleniumbase/SeleniumBase.git\ncd SeleniumBase/\npip install -e .\n```\n\n<h4>To upgrade an existing <code>seleniumbase</code> install from GitHub:</h4>\n\n```zsh\ngit pull  # To pull the latest version\npip install -e .  # Or \"pip install .\"\n```\n\n<h4>If installing <code>seleniumbase</code> from a <a href=\"https://github.com/seleniumbase/SeleniumBase\">GitHub branch</a>, use:</h4>\n\n```zsh\npip install git+https://github.com/seleniumbase/SeleniumBase.git@master#egg=seleniumbase\n```\n\n<h3><code>pip install</code> can be customized:</h3>\n\n* (Add ``--upgrade`` OR ``-U`` to upgrade SeleniumBase.)\n* (Add ``--force-reinstall`` to upgrade indirect libraries.)\n\n(If you're not using a virtual environment, you may need to add ``--user`` to your ``pip`` command if you're seeing errors during installation.)\n\n--------\n\n[<img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_10t.png\" title=\"SeleniumBase\" width=\"290\" />](https://github.com/seleniumbase/SeleniumBase/)\n"
  },
  {
    "path": "help_docs/install_python_pip_git.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## Installation instructions for ``Git``, ``Python``, and ``pip``\n\n### [Git](http://www.git-scm.com)\n\nYou can [download Git from here](http://git-scm.com/downloads).\n\n(<i>A Git GUI tool like [SourceTree](https://www.sourcetreeapp.com/) or [GitHub Desktop](https://desktop.github.com/) can help you with Git commands.</i>)\n\n(You can also download SeleniumBase from GitHub without using git-related commands.)\n\n### [Python](https://www.python.org)\n\nYou can download Python from [https://www.python.org/downloads/](https://www.python.org/downloads/) if it's not already preinstalled on your machine.\n\n### [pip](https://en.wikipedia.org/wiki/Pip_%28package_manager%29)\n\n**``pip`` already comes with Python!** (It lets you install packages, such as ``seleniumbase``.)\n\n⚠️ If something went wrong with your ``pip`` installation, try this:\n\n```zsh\npython -m ensurepip --default-pip\n```\n\nIf your existing version of pip is old, upgrade to the latest version:\n\n```zsh\npython -m pip install --upgrade pip setuptools\n```\n\nOn CentOS 7 and some versions of Linux, you may need to install pip with ``yum``:\n\n```zsh\nyum -y update\nyum -y install python-pip\n```\n\nIf you're having any trouble getting pip, you can [GET PIP HERE](https://pip.pypa.io/en/latest/installation/).\n\nWhen done, make sure the location of pip is on your path, which is ``$PATH`` for macOS/Linux. (On Windows, it's the System Variables ``Path`` within System Environment Variables.)\n\nYou can also get pip (or fix pip) by using:\n\n```zsh\ncurl https://bootstrap.pypa.io/get-pip.py | python\n```\n\n* (If you get SSL errors while trying to install packages with pip, see [this Stackoverflow post](https://stackoverflow.com/questions/49768770/not-able-to-install-python-packages-ssl-tlsv1-alert-protocol-version), which tells you to run the above command.)\n\n**Keep Pip and Setuptools up-to-date:**\n\n```zsh\npython -m pip install -U pip setuptools\n```\n\n* (Depending on your user permissions, you may need to add ``--user`` to the command if you're not inside a [Python virtual environment](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/virtualenv_instructions.md), or use \"[sudo](https://en.wikipedia.org/wiki/Sudo)\" on a UNIX-based OS if you're getting errors during installation.)\n"
  },
  {
    "path": "help_docs/js_package_manager.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"> JS Package Manager and Code Generators</h2>\n\n<h3>❇️ SeleniumBase lets you load JavaScript packages from any CDN link into any website via Python.</h3>\n\n<b>🎨 The following SeleniumBase solutions utilize this feature:</b>\n\n🎦 (<a href=\"https://seleniumbase.io/help_docs/demo_mode/\">Demo Mode</a>)\n\n🚎 (<a href=\"https://seleniumbase.io/examples/tour_examples/ReadMe/\">Website Tours</a>)\n\n🎞️ (<a href=\"https://seleniumbase.io/examples/presenter/ReadMe/\">Presentation Maker</a>)\n\n📊 (<a href=\"https://seleniumbase.io/examples/chart_maker/ReadMe/\">Chart Maker</a> / <a href=\"https://seleniumbase.io/examples/example_logs/ReadMe/\">Dashboard</a>)\n\n🛂 (<a href=\"https://seleniumbase.io/examples/dialog_boxes/ReadMe/\">Dialog Boxes</a> / <a href=\"https://seleniumbase.io/examples/master_qa/ReadMe/\">MasterQA</a>)\n\n--------\n\n<p><div>🗺️ Here's an example of loading a website-tour library into the browser for a Google Maps tour:</div></p>\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/introjs_tour.gif\" title=\"SeleniumBase Tour of Google\" /><br />\n\n<p>🗺️ This example is from <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/maps_introjs_tour.py\">maps_introjs_tour.py</a>. (The <code>--interval=1</code> makes the tour go automatically to the next step after 1 second.)</p>\n\n```zsh\ncd examples/tour_examples\npytest maps_introjs_tour.py --interval=1\n```\n\n<p>❇️ SeleniumBase includes powerful JS code generators for converting Python into JavaScript for using the supported JS packages. A few lines of Python in your tests might generate hundreds of lines of JavaScript.</p>\n\n<p>🗺️ Here is some tour code in Python from <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/maps_introjs_tour.py\">maps_introjs_tour.py</a> that expands into a lot of JavaScript.</p>\n\n```python\nself.open(\"https://www.google.com/maps/@42.3591234,-71.0915634,15z\")\nself.create_tour(theme=\"introjs\")\nself.add_tour_step(\"Welcome to Google Maps!\", title=\"SeleniumBase Tours\")\nself.add_tour_step(\"Enter Location\", \"#searchboxinput\", title=\"Search Box\")\nself.add_tour_step(\"See it\", \"#searchbox-searchbutton\", alignment=\"bottom\")\nself.add_tour_step(\"Thanks for using Tours!\", title=\"End of Guided Tour\")\nself.export_tour(filename=\"maps_introjs_tour.js\")\nself.play_tour()\n```\n\n<p><div>❇️ For existing features, SeleniumBase already takes care of loading all the necessary JS and CSS files into the web browser. To load other packages, here are a few useful methods that you should know about:</div></p>\n\n```python\nself.add_js_link(js_link)\n```\n\n<p><div>❇️ This example loads the <a href=\"https://introjs.com/\">IntroJS</a> JavaScript library:</div></p>\n\n```python\nself.add_js_link(\"https://cdn.jsdelivr.net/npm/intro.js@5.1.0/intro.min.js\")\n```\n\n<div>❇️ You can load any JS package this way as long as you know the URL.</div>\n\n<p>❇️ If you're wondering how SeleniumBase does this, here's the full Python code from <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/js_utils.py\">js_utils.py</a>, which uses WebDriver's <code>execute_script()</code> method for making JS calls after escaping quotes with backslashes as needed:</p>\n\n```python\ndef add_js_link(driver, js_link):\n    script_to_add_js = (\n        \"\"\"function injectJS(link) {\n              var body = document.getElementsByTagName(\"body\")[0];\n              var script = document.createElement(\"script\");\n              script.src = link;\n              script.defer;\n              script.type=\"text/javascript\";\n              script.crossorigin = \"anonymous\";\n              script.onload = function() { null };\n              body.appendChild(script);\n           }\n           injectJS(\"%s\");\"\"\")\n    js_link = escape_quotes_if_needed(js_link)\n    driver.execute_script(script_to_add_js % js_link)\n```\n\n<p>❇️ Now that you've loaded JavaScript into the browser, you may also want to load some CSS to go along with it:</p>\n\n```python\nself.add_css_link(css_link)\n```\n\n<p>❇️ Here's code that loads the <a href=\"https://introjs.com/\">IntroJS</a> CSS:</p>\n\n```python\nself.add_css_link(\"https://cdnjs.cloudflare.com/ajax/libs/intro.js/2.9.3/introjs.css\")\n```\n\n<p>❇️ And here's the Python WebDriver code that makes this possible:</p>\n\n```python\ndef add_css_link(driver, css_link):\n    script_to_add_css = (\n        \"\"\"function injectCSS(css) {\n              var head = document.getElementsByTagName(\"head\")[0];\n              var link = document.createElement(\"link\");\n              link.rel = \"stylesheet\";\n              link.type = \"text/css\";\n              link.href = css;\n              link.crossorigin = \"anonymous\";\n              head.appendChild(link);\n           }\n           injectCSS(\"%s\");\"\"\")\n    css_link = escape_quotes_if_needed(css_link)\n    driver.execute_script(script_to_add_css % css_link)\n```\n\n<div>❇️ Website tours are just one of the many uses of the JS Package Manager.</div>\n<p><div>🛂 The following example shows the <a href=\"https://github.com/craftpip/jquery-confirm\">JqueryConfirm</a> package loaded into a website for creating fancy dialog boxes:</div></p>\n\n<img src=\"https://seleniumbase.github.io/cdn/img/emoji_sports_dialog.png\" alt=\"SeleniumBase\" width=\"480\" />\n\n<h4>↕️ (<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/dialog_boxes/dialog_box_tour.py\">Example: dialog_box_tour.py</a>) ↕️</h4>\n\n<img src=\"https://seleniumbase.github.io/cdn/gif/sports_dialog.gif\" alt=\"SeleniumBase\" width=\"480\" />\n\n<h4>Here's how to run that example:</h4>\n\n```zsh\ncd examples/dialog_boxes\npytest test_dialog_boxes.py\n```\n\n<p><div>(Example from the <a href=\"https://seleniumbase.io/examples/dialog_boxes/ReadMe/\">Dialog Boxes ReadMe</a>)</div></p>\n\n<div>❇️ Since packages are loaded directly from a CDN link, you won't need other package managers like NPM, Bower, or Yarn to get the packages that you need into the websites that you want.</div>\n\n--------\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb.png\" title=\"SeleniumBase\" width=\"220\"></a>\n\n<div>To learn more about SeleniumBase, check out the Docs Site:</div>\n<a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a>\n\n<div>All the code is on GitHub:</div>\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase on GitHub\" /></a>\n"
  },
  {
    "path": "help_docs/locale_codes.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Language Locale Codes</h2>\n\nYou can specify a Language Locale Code to customize web pages on supported websites. With SeleniumBase, you can change the web browser's Locale on the command-line by doing this:\n\n```zsh\npytest --locale=CODE  # Example: --locale=ru\n```\n\nFrom the ``SB()`` and ``Driver()`` formats, you can also set the ``locale_code`` arg like this:\n\n```python\nlocale_code=\"CODE\"  # Example: SB(locale_code=\"en\")\n```\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"25\"> List of Language Locale Codes:</h3>\n\n<table>\n    <thead><tr><th>Language</th><th>Code</th></tr></thead>\n    <tbody class=\"list\">\n      <tr><td>Afrikaans</td><td><code translate=\"no\" dir=\"ltr\">af</code></td></tr>\n      <tr><td>Amharic</td><td><code translate=\"no\" dir=\"ltr\">am</code></td></tr>\n      <tr><td>Arabic</td><td><code translate=\"no\" dir=\"ltr\">ar</code></td></tr>\n      <tr><td>Arabic (Egypt)</td><td><code translate=\"no\" dir=\"ltr\">ar_<wbr>eg</code></td></tr>\n      <tr><td>Arabic (Saudi Arabia)</td><td><code translate=\"no\" dir=\"ltr\">ar_<wbr>sa</code></td></tr>\n      <tr><td>Basque</td><td><code translate=\"no\" dir=\"ltr\">eu</code></td></tr>\n      <tr><td>Belarusian</td><td><code translate=\"no\" dir=\"ltr\">be</code></td></tr>\n      <tr><td>Bengali</td><td><code translate=\"no\" dir=\"ltr\">bn</code></td></tr>\n      <tr><td>Bulgarian</td><td><code translate=\"no\" dir=\"ltr\">bg</code></td></tr>\n      <tr><td>Catalan</td><td><code translate=\"no\" dir=\"ltr\">ca</code></td></tr>\n      <tr><td>Chinese</td><td><code translate=\"no\" dir=\"ltr\">zh</code></td></tr>\n      <tr><td>Chinese (China Mainland)</td><td><code translate=\"no\" dir=\"ltr\">zh_<wbr>cn</code></td></tr>\n      <tr><td>Chinese (Hong Kong)</td><td><code translate=\"no\" dir=\"ltr\">zh_<wbr>hk</code></td></tr>\n      <tr><td>Chinese (Taiwan)</td><td><code translate=\"no\" dir=\"ltr\">zh_<wbr>tw</code></td></tr>\n      <tr><td>Croatian</td><td><code translate=\"no\" dir=\"ltr\">hr</code></td></tr>\n      <tr><td>Czech</td><td><code translate=\"no\" dir=\"ltr\">cs</code></td></tr>\n      <tr><td>Danish</td><td><code translate=\"no\" dir=\"ltr\">da</code></td></tr>\n      <tr><td>Dutch</td><td><code translate=\"no\" dir=\"ltr\">nl</code></td></tr>\n      <tr><td>English</td><td><code translate=\"no\" dir=\"ltr\">en</code></td></tr>\n      <tr><td>English (United States)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>us</code></td></tr>\n      <tr><td>English (Australia)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>au</code></td></tr>\n      <tr><td>English (Canada)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>ca</code></td></tr>\n      <tr><td>English (United Kingdom)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>gb</code></td></tr>\n      <tr><td>English (Ireland)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>ie</code></td></tr>\n      <tr><td>English (India)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>in</code></td></tr>\n      <tr><td>English (Singapore)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>sg</code></td></tr>\n      <tr><td>English (South Africa)</td><td><code translate=\"no\" dir=\"ltr\">en_<wbr>za</code></td></tr>\n      <tr><td>Estonian</td><td><code translate=\"no\" dir=\"ltr\">et</code></td></tr>\n      <tr><td>Farsi</td><td><code translate=\"no\" dir=\"ltr\">fa</code></td></tr>\n      <tr><td>Filipino</td><td><code translate=\"no\" dir=\"ltr\">fil</code></td></tr>\n      <tr><td>Finnish</td><td><code translate=\"no\" dir=\"ltr\">fi</code></td></tr>\n      <tr><td>French</td><td><code translate=\"no\" dir=\"ltr\">fr</code></td></tr>\n      <tr><td>French (Canada)</td><td><code translate=\"no\" dir=\"ltr\">fr_<wbr>ca</code></td></tr>\n      <tr><td>French (Switzerland)</td><td><code translate=\"no\" dir=\"ltr\">fr_<wbr>ch</code></td></tr>\n      <tr><td>Galician</td><td><code translate=\"no\" dir=\"ltr\">gl</code></td></tr>\n      <tr><td>German</td><td><code translate=\"no\" dir=\"ltr\">de</code></td></tr>\n      <tr><td>German (Austria)</td><td><code translate=\"no\" dir=\"ltr\">de_<wbr>at</code></td></tr>\n      <tr><td>Greek</td><td><code translate=\"no\" dir=\"ltr\">el</code></td></tr>\n      <tr><td>Gujarati</td><td><code translate=\"no\" dir=\"ltr\">gu</code></td></tr>\n      <tr><td>Hebrew</td><td><code translate=\"no\" dir=\"ltr\">he</code></td></tr>\n      <tr><td>Hindi</td><td><code translate=\"no\" dir=\"ltr\">hi</code></td></tr>\n      <tr><td>Hungarian</td><td><code translate=\"no\" dir=\"ltr\">hu</code></td></tr>\n      <tr><td>Icelandic</td><td><code translate=\"no\" dir=\"ltr\">is</code></td></tr>\n      <tr><td>Indonesian</td><td><code translate=\"no\" dir=\"ltr\">id</code></td></tr>\n      <tr><td>Italian</td><td><code translate=\"no\" dir=\"ltr\">it</code></td></tr>\n      <tr><td>Japanese</td><td><code translate=\"no\" dir=\"ltr\">ja</code></td></tr>\n      <tr><td>Kannada</td><td><code translate=\"no\" dir=\"ltr\">kn</code></td></tr>\n      <tr><td>Korean</td><td><code translate=\"no\" dir=\"ltr\">ko</code></td></tr>\n      <tr><td>Lao</td><td><code translate=\"no\" dir=\"ltr\">lo</code></td></tr>\n      <tr><td>Latvian</td><td><code translate=\"no\" dir=\"ltr\">lv</code></td></tr>\n      <tr><td>Lingala</td><td><code translate=\"no\" dir=\"ltr\">ln</code></td></tr>\n      <tr><td>Lithuanian</td><td><code translate=\"no\" dir=\"ltr\">lt</code></td></tr>\n      <tr><td>Malay</td><td><code translate=\"no\" dir=\"ltr\">ms</code></td></tr>\n      <tr><td>Malayalam</td><td><code translate=\"no\" dir=\"ltr\">ml</code></td></tr>\n      <tr><td>Marathi</td><td><code translate=\"no\" dir=\"ltr\">mr</code></td></tr>\n      <tr><td>Norwegian</td><td><code translate=\"no\" dir=\"ltr\">no</code></td></tr>\n      <tr><td>Polish</td><td><code translate=\"no\" dir=\"ltr\">pl</code></td></tr>\n      <tr><td>Portuguese</td><td><code translate=\"no\" dir=\"ltr\">pt</code></td></tr>\n      <tr><td>Portuguese (Brazil)</td><td><code translate=\"no\" dir=\"ltr\">pt_<wbr>br</code></td></tr>\n      <tr><td>Portuguese (Portugal)</td><td><code translate=\"no\" dir=\"ltr\">pt_<wbr>pt</code></td></tr>\n      <tr><td>Romanian</td><td><code translate=\"no\" dir=\"ltr\">ro</code></td></tr>\n      <tr><td>Russian</td><td><code translate=\"no\" dir=\"ltr\">ru</code></td></tr>\n      <tr><td>Serbian</td><td><code translate=\"no\" dir=\"ltr\">sr</code></td></tr>\n      <tr><td>Slovak</td><td><code translate=\"no\" dir=\"ltr\">sk</code></td></tr>\n      <tr><td>Slovenian</td><td><code translate=\"no\" dir=\"ltr\">sl</code></td></tr>\n      <tr><td>Spanish</td><td><code translate=\"no\" dir=\"ltr\">es</code></td></tr>\n      <tr><td>Spanish (Latin America)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>419</code></td></tr>\n      <tr><td>Spanish (Argentina)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>ar</code></td></tr>\n      <tr><td>Spanish (Chile)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>cl</code></td></tr>\n      <tr><td>Spanish (Colombia)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>co</code></td></tr>\n      <tr><td>Spanish (Costa Rica)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>cr</code></td></tr>\n      <tr><td>Spanish (Dominican Rep.)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>do</code></td></tr>\n      <tr><td>Spanish (Ecuador)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>ec</code></td></tr>\n      <tr><td>Spanish (El Salvador)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>sv</code></td></tr>\n      <tr><td>Spanish (Guatemala)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>gt</code></td></tr>\n      <tr><td>Spanish (Honduras)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>hn</code></td></tr>\n      <tr><td>Spanish (Mexico)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>mx</code></td></tr>\n      <tr><td>Spanish (Nicaragua)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>ni</code></td></tr>\n      <tr><td>Spanish (Panama)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>pa</code></td></tr>\n      <tr><td>Spanish (Peru)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>pe</code></td></tr>\n      <tr><td>Spanish (Puerto Rico)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>pr</code></td></tr>\n      <tr><td>Spanish (Paraguay)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>py</code></td></tr>\n      <tr><td>Spanish (United States)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>us</code></td></tr>\n      <tr><td>Spanish (Uruguay)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>uy</code></td></tr>\n      <tr><td>Spanish (Venezuela)</td><td><code translate=\"no\" dir=\"ltr\">es_<wbr>ve</code></td></tr>\n      <tr><td>Swahili</td><td><code translate=\"no\" dir=\"ltr\">sw</code></td></tr>\n      <tr><td>Swedish</td><td><code translate=\"no\" dir=\"ltr\">sv</code></td></tr>\n      <tr><td>Swiss German</td><td><code translate=\"no\" dir=\"ltr\">gsw</code></td></tr>\n      <tr><td>Tagalog</td><td><code translate=\"no\" dir=\"ltr\">tl</code></td></tr>\n      <tr><td>Tamil</td><td><code translate=\"no\" dir=\"ltr\">ta</code></td></tr>\n      <tr><td>Telugu</td><td><code translate=\"no\" dir=\"ltr\">te</code></td></tr>\n      <tr><td>Thai</td><td><code translate=\"no\" dir=\"ltr\">th</code></td></tr>\n      <tr><td>Turkish</td><td><code translate=\"no\" dir=\"ltr\">tr</code></td></tr>\n      <tr><td>Ukrainian</td><td><code translate=\"no\" dir=\"ltr\">uk</code></td></tr>\n      <tr><td>Urdu</td><td><code translate=\"no\" dir=\"ltr\">ur</code></td></tr>\n      <tr><td>Vietnamese</td><td><code translate=\"no\" dir=\"ltr\">vi</code></td></tr>\n      <tr><td>Zulu</td><td><code translate=\"no\" dir=\"ltr\">zu</code></td></tr>\n    </tbody>\n  </table>\n"
  },
  {
    "path": "help_docs/method_summary.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> SeleniumBase Methods (API Reference)</h2>\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=_yNJlHnp2JA\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_api_youtube.png\" title=\"SeleniumBase on YouTube\" width=\"285\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=_yNJlHnp2JA\">Common API Methods on YouTube</a></b>)</p>\n\nHere's a list of SeleniumBase method definitions, which are defined in **[base_case.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py)**\n\nFor backwards compatibility, older versions of method names have remained to keep older scripts working. *(E.g: wait_for_element_visible was shortened to wait_for_element and then to find_element.)*\n\n```python\nself.open(url)\n# Duplicates: self.open_url(url), self.visit(url), visit_url(url),\n#             self.goto(url), self.go_to(url)\nself.get(url)\n# If the url parameter is a URL: Perform self.open(url)\n# Otherwise: return self.get_element(URL_AS_A_SELECTOR)\nself.click(selector, by=\"css selector\", timeout=None, delay=0, scroll=True)\nself.slow_click(selector, by=\"css selector\", timeout=None)\nself.double_click(selector, by=\"css selector\", timeout=None)\nself.context_click(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.right_click(selector, by=\"css selector\", timeout=None)\nself.click_chain(selectors_list, by=\"css selector\", timeout=None, spacing=0)\nself.type(selector, text, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.update_text(selector, text, by=\"css selector\", timeout=None)\n# self.input(selector, text, by=\"css selector\", timeout=None)\n# self.fill(selector, text, by=\"css selector\", timeout=None)\n# self.write(selector, text, by=\"css selector\", timeout=None)\nself.send_keys(selector, text, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.add_text(selector, text, by=\"css selector\", timeout=None)\nself.press_keys(selector, text, by=\"css selector\", timeout=None)\nself.submit(selector, by=\"css selector\")\nself.clear(selector, by=\"css selector\", timeout=None)\nself.focus(selector, by=\"css selector\", timeout=None)\nself.refresh()\n# Duplicates: self.refresh_page(), self.reload_page(), self.reload()\nself.get_current_url()\nself.get_origin()\nself.get_html()\nself.get_page_source()\nself.get_title()\n# Duplicates: self.get_page_title()\nself.get_user_agent()\nself.get_locale_code()\nself.go_back()\nself.go_forward()\nself.open_start_page()\nself.open_if_not_url(url)\nself.is_element_present(selector, by=\"css selector\")\nself.is_element_visible(selector, by=\"css selector\")\nself.is_element_clickable(selector, by=\"css selector\")\nself.is_element_enabled(selector, by=\"css selector\")\nself.is_text_visible(text, selector=\"html\", by=\"css selector\")\nself.is_exact_text_visible(text, selector=\"html\", by=\"css selector\")\nself.is_non_empty_text_visible(selector=\"html\", by=\"css selector\")\nself.is_attribute_present(selector, attribute, value=None, by=\"css selector\")\nself.is_link_text_visible(link_text)\nself.is_partial_link_text_visible(partial_link_text)\nself.is_link_text_present(link_text)\nself.is_partial_link_text_present(link_text)\nself.get_link_attribute(link_text, attribute, hard_fail=True)\n# Duplicates:\n# self.get_link_text_attribute(link_text, attribute, hard_fail=True)\nself.get_partial_link_text_attribute(link_text, attribute, hard_fail=True)\nself.click_link(link_text, timeout=None)\n# Duplicates:\n# self.click_link_text(link_text, timeout=None)\nself.click_partial_link(partial_link_text, timeout=None)\n# Duplicates:\n# self.click_partial_link_text(partial_link_text, timeout=None)\nself.get_text(selector=\"html\", by=\"css selector\", timeout=None)\nself.get_attribute(selector, attribute, by=\"css selector\", timeout=None, hard_fail=True)\nself.set_attribute(selector, attribute, value, by=\"css selector\", timeout=None, scroll=False)\nself.set_attributes(selector, attribute, value, by=\"css selector\")\n# Duplicates:\n# self.set_attribute_all(selector, attribute, value, by=\"css selector\")\nself.remove_attribute(selector, attribute, by=\"css selector\", timeout=None)\nself.remove_attributes(selector, attribute, by=\"css selector\")\nself.internalize_links()\nself.get_parent(element, by=\"css selector\", timeout=None)\nself.get_property(selector, property, by=\"css selector\", timeout=None)\nself.get_text_content(selector=\"html\", by=\"css selector\", timeout=None)\nself.get_property_value(selector, property, by=\"css selector\", timeout=None)\nself.get_image_url(selector, by=\"css selector\", timeout=None)\nself.find_elements(selector, by=\"css selector\", limit=0)\n# Duplicates:\n# self.select_all(selector, by=\"css selector\", limit=0)\nself.find_visible_elements(selector, by=\"css selector\", limit=0)\nself.click_visible_elements(selector, by=\"css selector\", limit=0, timeout=None)\nself.click_nth_visible_element(selector, number, by=\"css selector\", timeout=None)\nself.click_if_visible(selector, by=\"css selector\", timeout=0)\nself.click_active_element()\nself.click_with_offset(\n    selector, x, y, by=\"css selector\", mark=None, timeout=None, center=None)\nself.double_click_with_offset(\n    selector, x, y, by=\"css selector\", mark=None, timeout=None, center=None)\nself.is_checked(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.is_selected(selector, by=\"css selector\", timeout=None)\nself.check_if_unchecked(selector, by=\"css selector\")\n# Duplicates:\n# self.select_if_unselected(selector, by=\"css selector\")\nself.uncheck_if_checked(selector, by=\"css selector\")\n# Duplicates:\n# self.unselect_if_selected(selector, by=\"css selector\")\nself.is_element_in_an_iframe(selector, by=\"css selector\")\nself.switch_to_frame_of_element(selector, by=\"css selector\")\nself.hover(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.hover_element(selector, by=\"css selector\", timeout=None)\n# self.hover_on_element(selector, by=\"css selector\", timeout=None)\n# self.hover_over_element(selector, by=\"css selector\", timeout=None)\nself.hover_and_click(\n    hover_selector, click_selector,\n    hover_by=\"css selector\", click_by=\"css selector\",\n    timeout=None, js_click=False)\nself.hover_and_js_click(\n    hover_selector, click_selector,\n    hover_by=\"css selector\", click_by=\"css selector\",\n    timeout=None)\nself.hover_and_double_click(\n    hover_selector, click_selector,\n    hover_by=\"css selector\", click_by=\"css selector\",\n    timeout=None)\nself.drag_and_drop(\n    drag_selector, drop_selector,\n    drag_by=\"css selector\", drop_by=\"css selector\",\n    timeout=None, jquery=False)\nself.drag_and_drop_with_offset(\n    selector, x, y, by=\"css selector\", timeout=None)\nself.select_option_by_text(\n    dropdown_selector, option, dropdown_by=\"css selector\", timeout=None)\nself.select_option_by_index(\n    dropdown_selector, option, dropdown_by=\"css selector\", timeout=None)\nself.select_option_by_value(\n    dropdown_selector, option, dropdown_by=\"css selector\", timeout=None)\nself.get_select_options(\n    dropdown_selector, attribute=\"text\", by=\"css selector\", timeout=None)\nself.load_html_string(html_string, new_page=True)\nself.set_content(html_string, new_page=False)\nself.load_html_file(html_file, new_page=True)\nself.open_html_file(html_file)\nself.evaluate(expression)\nself.execute_script(script, *args, **kwargs)\nself.execute_cdp_cmd(script, *args, **kwargs)\nself.execute_async_script(script, timeout=None)\nself.safe_execute_script(script, *args, **kwargs)\nself.get_element_at_x_y(x, y)\nself.get_gui_element_rect(selector, by=\"css selector\")\nself.get_gui_element_center(selector, by=\"css selector\")\nself.get_screen_rect()\nself.get_window_rect()\nself.get_window_size()\nself.get_window_position()\nself.set_window_rect(x, y, width, height)\nself.set_window_size(width, height)\nself.set_window_position(x, y)\nself.maximize_window()\n# Duplicates: self.maximize()\nself.minimize_window()\n# Duplicates: self.minimize()\nself.reset_window_size()\nself.switch_to_frame(frame=\"iframe\", timeout=None, invisible=False)\nself.switch_to_default_content()\nself.switch_to_parent_frame()\nwith self.frame_switch(frame, timeout=None):\n    # Indented Code Block for Context Manager (Must use \"with\")\nself.set_content_to_frame(frame, timeout=None)\nself.set_content_to_default(nested=False)\n# Duplicates: self.set_content_to_default_content(nested=False)\nself.set_content_to_parent()\n# Duplicates: self.set_content_to_parent_frame()\nself.open_new_window(switch_to=True)\n# Duplicates: self.open_new_tab(switch_to=True)\nself.switch_to_window(window, timeout=None)\n# Duplicates: self.switch_to_tab(tab, timeout=None)\nself.switch_to_default_window()\n# Duplicates: self.switch_to_default_tab()\nself.switch_to_newest_window()\n# Duplicates: self.switch_to_newest_tab()\nself.get_new_driver(\n    browser=None,\n    headless=None,\n    locale_code=None,\n    protocol=None,\n    servername=None,\n    port=None,\n    proxy=None,\n    proxy_bypass_list=None,\n    proxy_pac_url=None,\n    multi_proxy=None,\n    agent=None,\n    switch_to=True,\n    cap_file=None,\n    cap_string=None,\n    recorder_ext=None,\n    disable_cookies=None,\n    disable_js=None,\n    disable_csp=None,\n    enable_ws=None,\n    enable_sync=None,\n    use_auto_ext=None,\n    undetectable=None,\n    uc_cdp_events=None,\n    uc_subprocess=None,\n    log_cdp_events=None,\n    no_sandbox=None,\n    disable_gpu=None,\n    headless1=None,\n    headless2=None,\n    incognito=None,\n    guest_mode=None,\n    dark_mode=None,\n    devtools=None,\n    remote_debug=None,\n    enable_3d_apis=None,\n    swiftshader=None,\n    ad_block_on=None,\n    host_resolver_rules=None,\n    block_images=None,\n    do_not_track=None,\n    chromium_arg=None,\n    firefox_arg=None,\n    firefox_pref=None,\n    user_data_dir=None,\n    extension_zip=None,\n    extension_dir=None,\n    disable_features=None,\n    binary_location=None,\n    driver_version=None,\n    page_load_strategy=None,\n    use_wire=None,\n    external_pdf=None,\n    is_mobile=None,\n    d_width=None,\n    d_height=None,\n    d_p_r=None,\n)\nself.switch_to_driver(driver)\nself.switch_to_default_driver()\nself.save_screenshot(name, folder=None, selector=None, by=\"css selector\")\nself.save_screenshot_to_logs(name=None, selector=None, by=\"css selector\")\nself.save_as_pdf_to_logs(name=None)\nself.save_data_to_logs(data, file_name=None)\nself.append_data_to_logs(data, file_name=None)\nself.save_page_source_to_logs(name=None)\nself.save_page_source(name, folder=None)\n# Duplicates: self.save_as_html(name, folder=None)\nself.save_cookies(name=\"cookies.txt\")\nself.load_cookies(name=\"cookies.txt\", expiry=False)\nself.delete_all_cookies()\n# Duplicates: self.clear_all_cookies()\nself.delete_saved_cookies(name=\"cookies.txt\")\nself.get_saved_cookies(name=\"cookies.txt\")\nself.get_cookie(name)\nself.get_cookies()\nself.get_cookie_string()\nself.add_cookie(cookie_dict, expiry=False)\nself.add_cookies(cookies, expiry=False)\nself.wait_for_ready_state_complete(timeout=None)\nself.wait_for_angularjs(timeout=None)\nself.sleep(seconds)\n# Duplicates: self.wait(seconds)\nself.install_addon(xpi_file)\nself.activate_jquery()\nself.activate_demo_mode()\nself.deactivate_demo_mode()\nself.activate_design_mode()\nself.deactivate_design_mode()\nself.activate_recorder()\nself.save_recorded_actions()\nself.bring_active_window_to_front()\nself.bring_to_front(selector, by=\"css selector\")\nself.highlight_click(selector, by=\"css selector\", loops=3, scroll=True, timeout=None)\nself.highlight_type(selector, text, by=\"css selector\", loops=3, scroll=True, timeout=None)\n# Duplicates:\n# self.highlight_update_text(\n#     selector, text, by=\"css selector\", loops=3, scroll=True, timeout=None)\nself.highlight_if_visible(selector, by=\"css selector\", loops=4, scroll=True)\nself.highlight(selector, by=\"css selector\", loops=4, scroll=True, timeout=None)\nself.highlight_elements(selector, by=\"css selector\", loops=4, scroll=True, limit=0)\nself.press_up_arrow(selector=\"html\", times=1, by=\"css selector\")\nself.press_down_arrow(selector=\"html\", times=1, by=\"css selector\")\nself.press_left_arrow(selector=\"html\", times=1, by=\"css selector\")\nself.press_right_arrow(selector=\"html\", times=1, by=\"css selector\")\nself.scroll_to(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.scroll_to_element(selector, by=\"css selector\")\nself.slow_scroll_to(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.slow_scroll_to_element(selector, by=\"css selector\")\nself.scroll_into_view(selector, by=\"css selector\", timeout=None)\nself.scroll_to_top()\nself.scroll_to_bottom()\nself.scroll_to_y(y)\nself.scroll_by_y(y)\nself.scroll_up(amount=25)\nself.scroll_down(amount=25)\nself.click_xpath(xpath)\nself.js_click(selector, by=\"css selector\", all_matches=False, timeout=None, scroll=True)\nself.js_click_if_present(selector, by=\"css selector\", timeout=0)\nself.js_click_if_visible(selector, by=\"css selector\", timeout=0)\nself.js_click_all(selector, by=\"css selector\", timeout=None)\nself.jquery_click(selector, by=\"css selector\", timeout=None)\nself.jquery_click_all(selector, by=\"css selector\", timeout=None)\nself.hide_element(selector, by=\"css selector\")\nself.hide_elements(selector, by=\"css selector\")\nself.show_element(selector, by=\"css selector\")\nself.show_elements(selector, by=\"css selector\")\nself.remove_element(selector, by=\"css selector\")\nself.remove_elements(selector, by=\"css selector\")\nself.ad_block()\n# Duplicates: self.block_ads()\nself.show_file_choosers()\nself.disable_beforeunload()\nself.get_domain_url(url)\nself.get_active_element_css()\nself.get_beautiful_soup(source=None)\nself.get_unique_links()\nself.get_link_status_code(link, allow_redirects=False, timeout=5, verify=False)\nself.assert_link_status_code_is_not_404(link)\nself.assert_no_404_errors(multithreaded=True, timeout=None)\n# Duplicates:\n# self.assert_no_broken_links(multithreaded=True, timeout=None)\nself.print_unique_links_with_status_codes()\nself.get_pdf_text(\n    pdf, page=None, maxpages=None, password=None,\n    codec='utf-8', wrap=False, nav=False, override=False, caching=True)\nself.assert_pdf_text(\n    pdf, text, page=None, maxpages=None, password=None,\n    codec='utf-8', wrap=True, nav=False, override=False, caching=True)\nself.create_folder(folder)\nself.choose_file(selector, file_path, by=\"css selector\", timeout=None)\nself.save_element_as_image_file(selector, file_name, folder=None, overlay_text=\"\")\nself.download_file(file_url, destination_folder=None)\nself.save_file_as(file_url, new_file_name, destination_folder=None)\nself.save_data_as(data, file_name, destination_folder=None)\nself.append_data_to_file(data, file_name, destination_folder=None)\nself.get_file_data(file_name, folder=None)\nself.print_to_pdf(name, folder=None)\n# Duplicates: self.save_as_pdf(name, folder=None)\nself.get_downloads_folder()\nself.get_browser_downloads_folder()\nself.get_downloaded_files(regex=None, browser=False)\nself.get_path_of_downloaded_file(file, browser=False)\nself.get_data_from_downloaded_file(file, timeout=None, browser=False)\nself.is_downloaded_file_present(file, browser=False)\nself.is_downloaded_file_regex_present(regex, browser=False)\nself.delete_downloaded_file_if_present(file, browser=False)\n# Duplicates: self.delete_downloaded_file(file, browser=False)\nself.assert_downloaded_file(file, timeout=None, browser=False)\nself.assert_downloaded_file_regex(regex, timeout=None, browser=False)\nself.assert_data_in_downloaded_file(data, file, timeout=None, browser=False)\nself.assert_true(expr, msg=None)\nself.assert_false(expr, msg=None)\nself.assert_equal(first, second, msg=None)\nself.assert_not_equal(first, second, msg=None)\nself.assert_in(first, second, msg=None)\nself.assert_not_in(first, second, msg=None)\nself.assert_raises(*args, **kwargs)\nself.wait_for_attribute(selector, attribute, value=None, by=\"css selector\", timeout=None)\nself.assert_attribute(selector, attribute, value=None, by=\"css selector\", timeout=None)\nself.assert_title(title)\nself.assert_title_contains(substring)\nself.assert_url(url)\nself.assert_url_contains(substring)\nself.assert_no_js_errors(exclude=[])\nself.inspect_html()\nself.is_valid_url(url)\nself.is_alert_present()\nself.is_online()\nself.is_chromium()\nself.get_chrome_version()\nself.get_chromium_version()\nself.get_chromedriver_version()\nself.get_chromium_driver_version()\nself.get_mfa_code(totp_key=None)\n# Duplicates:\n# self.get_totp_code(totp_key=None)\n# self.get_google_auth_password(totp_key=None)\n# self.get_google_auth_code(totp_key=None)\nself.enter_mfa_code(selector, totp_key=None, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.enter_totp_code(selector, totp_key=None, by=\"css selector\", timeout=None)\nself.convert_css_to_xpath(css)\nself.convert_xpath_to_css(xpath)\nself.convert_to_css_selector(selector, by)\nself.set_value(selector, text, by=\"css selector\", timeout=None, scroll=True)\nself.js_update_text(selector, text, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.js_type(selector, text, by=\"css selector\", timeout=None)\n# self.set_text(selector, text, by=\"css selector\", timeout=None)\nself.set_text_content(selector, text, by=\"css selector\", timeout=None, scroll=False)\nself.jquery_update_text(selector, text, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.jquery_type(selector, text, by=\"css selector\", timeout=None)\nself.get_value(selector, by=\"css selector\", timeout=None)\nself.set_time_limit(time_limit)\nself.set_default_timeout(timeout)\nself.reset_default_timeout()\nself.fail(msg=None)\nself.skip(reason=\"\")\n\n############\n\nself.start_recording_console_logs()\nself.console_log_string(string)\nself.console_log_script(script)\nself.get_recorded_console_logs()\n\n############\n\nself.set_local_storage_item(key, value)\nself.get_local_storage_item(key)\nself.remove_local_storage_item(key)\nself.clear_local_storage()\n# Duplicates: self.delete_local_storage()\nself.get_local_storage_keys()\nself.get_local_storage_items()\nself.set_session_storage_item(key, value)\nself.get_session_storage_item(key)\nself.remove_session_storage_item(key)\nself.clear_session_storage()\n# Duplicates: self.delete_session_storage()\nself.get_session_storage_keys()\nself.get_session_storage_items()\n\n############\n\nself.set_wire_proxy(string)  # Requires \"--wire\"!\n\n############\n\nself.add_css_link(css_link)\nself.add_js_link(js_link)\nself.add_css_style(css_style)\nself.add_js_code_from_link(js_link)\nself.add_js_code(js_code)\nself.add_meta_tag(http_equiv=None, content=None)\n\n############\n\nself.create_presentation(name=None, theme=\"default\", transition=\"default\")\nself.add_slide(\n    content=None, image=None, code=None, iframe=None,\n    content2=None, notes=None, transition=None, name=None)\nself.save_presentation(name=None, filename=None, show_notes=False, interval=0)\nself.begin_presentation(name=None, filename=None, show_notes=False, interval=0)\n\n############\n\nself.create_pie_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, libs=True,\n    labels=True, legend=True)\nself.create_bar_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, libs=True,\n    labels=True, legend=True)\nself.create_column_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, libs=True,\n    labels=True, legend=True)\nself.create_line_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, zero=False, libs=True,\n    labels=True, legend=True)\nself.create_area_chart(\n    chart_name=None, title=None, subtitle=None,\n    data_name=None, unit=None, zero=False, libs=True,\n    labels=True, legend=True)\nself.add_series_to_chart(data_name=None, chart_name=None)\nself.add_data_point(label, value, color=None, chart_name=None)\nself.save_chart(chart_name=None, filename=None, folder=None)\nself.display_chart(chart_name=None, filename=None, interval=0)\nself.extract_chart(chart_name=None)\n\n############\n\nself.create_tour(name=None, theme=None)\nself.create_shepherd_tour(name=None, theme=None)\nself.create_bootstrap_tour(name=None)\nself.create_hopscotch_tour(name=None)\nself.create_introjs_tour(name=None)\nself.set_introjs_colors(theme_color=None, hover_color=None)\nself.add_tour_step(message, selector=None, name=None, title=None, theme=None, alignment=None)\nself.play_tour(name=None, interval=0)\n# Duplicates: self.start_tour(name=None, interval=0):\nself.export_tour(name=None, filename=\"my_tour.js\", url=None)\n\n############\n\nself.activate_jquery_confirm()\nself.set_jqc_theme(theme, color=None, width=None)\nself.reset_jqc_theme()\nself.get_jqc_button_input(message, buttons, options=None)\nself.get_jqc_text_input(message, button=None, options=None)\nself.get_jqc_form_inputs(message, buttons, options=None)\n\n############\n\nself.activate_messenger()\nself.post_message(message, duration=None, pause=True, style=\"info\")\nself.post_message_and_highlight(message, selector, by=\"css selector\")\nself.post_success_message(message, duration=None, pause=True)\nself.post_error_message(message, duration=None, pause=True)\nself.set_messenger_theme(theme=\"default\", location=\"default\", max_messages=\"default\")\n\n############\n\nself.generate_referral(start_page, destination_page, selector=None)\nself.generate_traffic(start_page, destination_page, loops=1, selector=None)\nself.generate_referral_chain(pages)\nself.generate_traffic_chain(pages, loops=1)\n\n############\n\nself.get_element(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.wait_for_selector(selector, by=\"css selector\", timeout=None)\n# self.locator(selector, by=\"css selector\", timeout=None)\n# self.wait_for_element_present(selector, by=\"css selector\", timeout=None)\nself.wait_for_query_selector(selector, by=\"css selector\", timeout=None)\nself.assert_element_present(selector, by=\"css selector\", timeout=None)\nself.assert_elements_present(*args, **kwargs)\n\n############\n\nself.find_element(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.wait_for_element(selector, by=\"css selector\", timeout=None)\n# self.wait_for_element_visible(selector, by=\"css selector\", timeout=None)\nself.assert_element(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.assert_element_visible(selector, by=\"css selector\", timeout=None)\nself.assert_elements(*args, **kwargs)\n# Duplicates:\n# self.assert_elements_visible(*args, **kwargs)\n\n############\n\nself.wait_for_any_of_elements_visible(*args, **kwargs)\nself.wait_for_any_of_elements_present(*args, **kwargs)\nself.assert_any_of_elements_visible(*args, **kwargs)\nself.assert_any_of_elements_present(*args, **kwargs)\n\n############\n\nself.find_text(text, selector=\"html\", by=\"css selector\", timeout=None)\n# Duplicates:\n# self.wait_for_text(text, selector=\"html\", by=\"css selector\", timeout=None)\n# self.wait_for_text_visible(text, selector=\"html\", by=\"css selector\", timeout=None)\nself.find_exact_text(text, selector=\"html\", by=\"css selector\", timeout=None)\n# Duplicates:\n# self.wait_for_exact_text(text, selector=\"html\", by=\"css selector\", timeout=None)\n# self.wait_for_exact_text_visible(text, selector=\"html\", by=\"css selector\", timeout=None)\nself.find_non_empty_text(selector=\"html\", by=\"css selector\", timeout=None)\n# Duplicates:\n# self.wait_for_non_empty_text(selector=\"html\", by=\"css selector\", timeout=None)\n# self.wait_for_non_empty_text_visible(selector=\"html\", by=\"css selector\", timeout=None)\nself.assert_text(text, selector=\"html\", by=\"css selector\", timeout=None)\n# Duplicates:\n# self.assert_text_visible(text, selector=\"html\", by=\"css selector\", timeout=None)\nself.assert_exact_text(text, selector=\"html\", by=\"css selector\", timeout=None)\n\n############\n\nself.wait_for_link_text_present(link_text, timeout=None)\nself.wait_for_partial_link_text_present(link_text, timeout=None)\nself.find_link_text(link_text, timeout=None)\n# Duplicates:\n# self.wait_for_link_text(link_text, timeout=None)\n# self.wait_for_link_text_visible(link_text, timeout=None)\nself.assert_link_text(link_text, timeout=None)\n# Duplicates: self.assert_link(link_text, timeout=None)\n\n############\n\nself.find_partial_link_text(partial_link_text, timeout=None)\n# Duplicates:\n# self.wait_for_partial_link_text(partial_link_text, timeout=None)\nself.assert_partial_link_text(partial_link_text, timeout=None)\n\n############\n\nself.wait_for_element_absent(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.wait_for_element_not_present(selector, by=\"css selector\", timeout=None)\nself.assert_element_absent(selector, by=\"css selector\", timeout=None)\n# Duplicates:\n# self.assert_element_not_present(selector, by=\"css selector\", timeout=None)\n\n############\n\nself.wait_for_element_clickable(selector, by=\"css selector\", timeout=None)\n\n############\n\nself.wait_for_element_not_visible(selector, by=\"css selector\", timeout=None)\nself.assert_element_not_visible(selector, by=\"css selector\", timeout=None)\n\n############\n\nself.wait_for_text_not_visible(text, selector=\"html\", by=\"css selector\", timeout=None)\nself.wait_for_exact_text_not_visible(text, selector=\"html\", by=\"css selector\", timeout=None)\nself.assert_text_not_visible(text, selector=\"html\", by=\"css selector\", timeout=None)\nself.assert_exact_text_not_visible(text, selector=\"html\", by=\"css selector\", timeout=None)\nself.assert_non_empty_text(selector=\"html\", by=\"css selector\", timeout=None)\n\n############\n\nself.wait_for_attribute_not_present(\n    selector, attribute, value=None, by=\"css selector\", timeout=None)\nself.assert_attribute_not_present(\n    selector, attribute, value=None, by=\"css selector\", timeout=None)\n\n############\n\nself.accept_alert(timeout=None)\n# Duplicates: self.wait_for_and_accept_alert(timeout=None)\nself.dismiss_alert(timeout=None)\n# Duplicates: self.wait_for_and_dismiss_alert(timeout=None)\nself.switch_to_alert(timeout=None)\n# Duplicates: self.wait_for_and_switch_to_alert(timeout=None)\n\n############\n\nself.quit_extra_driver(driver=None)\n\n############\n\nself.check_window(name=\"default\", level=0, baseline=False, check_domain=True, full_diff=False)\n\n############\n\nself.deferred_assert_element(selector, by=\"css selector\", timeout=None, fs=False)\n# Duplicates:\n# self.delayed_assert_element(\n#     selector, by=\"css selector\", timeout=None, fs=False)\nself.deferred_assert_element_present(selector, by=\"css selector\", timeout=None, fs=False)\n# Duplicates:\n# self.delayed_assert_element_present(\n#     selector, by=\"css selector\", timeout=None, fs=False)\nself.deferred_assert_text(text, selector=\"html\", by=\"css selector\", timeout=None, fs=False)\n# Duplicates:\n# self.delayed_assert_text(\n#     text, selector=\"html\", by=\"css selector\", timeout=None, fs=False)\nself.deferred_assert_exact_text(\n    text, selector=\"html\", by=\"css selector\", timeout=None, fs=False)\n# Duplicates:\n# self.delayed_assert_exact_text(\n#     text, selector=\"html\", by=\"css selector\", timeout=None, fs=False)\nself.deferred_assert_non_empty_text(\n    selector=\"html\", by=\"css selector\", timeout=None, fs=False)\n# Duplicates:\n# self.delayed_assert_non_empty_text(\n#     selector=\"html\", by=\"css selector\", timeout=None, fs=False)\nself.deferred_check_window(\n    name=\"default\", level=0, baseline=False, check_domain=True, full_diff=False, fs=False)\n# Duplicates:\n# self.delayed_check_window(\n#     name=\"default\", level=0, baseline=False,\n#     check_domain=True, full_diff=False, fs=False)\nself.process_deferred_asserts(print_only=False)\n# Duplicates: self.process_delayed_asserts(print_only=False)\n\n############\n\nself.fail(msg=None)  # Inherited from \"unittest\"\nself._check_browser()  # Fails test cleanly if the active window is closed\nself._print(TEXT)  # Calls Python's print() / Allows for translations\n\n############\n\n# **** UC Mode methods. (uc=True / --uc) ****\n\n# (Mainly for CDP Mode)  -  (For all CDP methods, see the CDP Mode Docs)\n\nself.activate_cdp_mode(url=None, **kwargs)  # Activate CDP Mode on URL\nself.reconnect(timeout=0.1)  # disconnect() + sleep(timeout) + connect()\nself.disconnect()  # Stops the webdriver service to prevent detection\nself.connect()  # Starts the webdriver service to allow actions again\n\n# (For regular UC Mode)\n\nself.uc_open(url)  # (Open in same tab with default reconnect_time)\nself.uc_open_with_tab(url)  # (New tab with default reconnect_time)\nself.uc_open_with_reconnect(url, reconnect_time=None)  # (New tab)\nself.uc_open_with_disconnect(url, timeout=None)  # New tab + sleep()\nself.uc_click(selector)  # A stealthy click for evading bot-detection\nself.uc_gui_press_key(key)  # Use PyAutoGUI to press the keyboard key\nself.uc_gui_press_keys(keys)  # Use PyAutoGUI to press a list of keys\nself.uc_gui_write(text)  # Similar to uc_gui_press_keys(), but faster\nself.uc_gui_click_x_y(x, y, timeframe=0.25)  # PyAutoGUI click screen\nself.uc_gui_click_captcha(frame=\"iframe\", retry=False, blind=False)\nself.uc_gui_handle_captcha(frame=\"iframe\")\n\n############\n\n# \"driver\"-specific methods added (or modified) by SeleniumBase\n\ndriver.default_get(url)  # Because driver.get(url) works differently in UC Mode\ndriver.open(url)  # Like driver.get(), but allows partial URLs without protocol\ndriver.click(selector)\ndriver.click_link(link_text)\ndriver.click_if_visible(selector)\ndriver.click_active_element()\ndriver.send_keys(selector, text)\ndriver.press_keys(selector, text)\ndriver.type(selector, text)\ndriver.submit(selector)\ndriver.assert_element(selector)\ndriver.assert_element_present(selector)\ndriver.assert_element_not_visible(selector)\ndriver.assert_text(text, selector)\ndriver.assert_exact_text(text, selector)\ndriver.find_element(selector)\ndriver.find_elements(selector)\ndriver.wait_for_element(selector)\ndriver.wait_for_element_visible(selector)\ndriver.wait_for_element_present(selector)\ndriver.wait_for_selector(selector)\ndriver.wait_for_text(text, selector)\ndriver.wait_for_exact_text(text, selector)\ndriver.wait_for_and_accept_alert()\ndriver.wait_for_and_dismiss_alert()\ndriver.is_element_present(selector)\ndriver.is_element_visible(selector)\ndriver.is_text_visible(text, selector)\ndriver.is_exact_text_visible(text, selector)\ndriver.is_attribute_present(selector, attribute)\ndriver.get_text(selector)\ndriver.js_click(selector)\ndriver.get_active_element_css()\ndriver.get_locale_code()\ndriver.get_origin()\ndriver.get_user_agent()\ndriver.highlight(selector)\ndriver.highlight_click(selector)\ndriver.highlight_if_visible(selector)\ndriver.sleep(seconds)\ndriver.locator(selector)\ndriver.get_attribute(selector, attribute)\ndriver.get_parent(element)\ndriver.get_current_url()\ndriver.get_page_source()\ndriver.get_title()\ndriver.switch_to_frame(frame=\"iframe\")\ndriver.is_cdp_mode_active()\ndriver.is_connected()  # UC / CDP Mode can disconnect WebDriver\n\n############\n\n# \"driver\"-specific methods added (or modified) by SeleniumBase for UC Mode:\n\ndriver.get(url)  # If UC Mode and site detects bots, then uc_open_with_tab(url)\ndriver.uc_open(url)  # (Open in same tab with default reconnect_time)\ndriver.uc_open_with_tab(url)  # (New tab with default reconnect_time)\ndriver.uc_open_with_reconnect(url, reconnect_time=None)  # (New tab)\ndriver.uc_open_with_disconnect(url, timeout=None)  # New tab + sleep()\ndriver.uc_activate_cdp_mode(url=None, **kwargs)  # Activate CDP Mode on URL\ndriver.reconnect(timeout=0.1)  # disconnect() + sleep(timeout) + connect()\ndriver.disconnect()  # Stops the webdriver service to prevent detection\ndriver.connect()  # Starts the webdriver service to allow actions again\ndriver.uc_click(selector)  # A stealthy click for evading bot-detection\ndriver.uc_gui_press_key(key)  # Use PyAutoGUI to press the keyboard key\ndriver.uc_gui_press_keys(keys)  # Use PyAutoGUI to press a list of keys\ndriver.uc_gui_write(text)  # Similar to uc_gui_press_keys(), but faster\ndriver.uc_gui_click_x_y(x, y, timeframe=0.25)  # PyAutoGUI click screen\ndriver.uc_gui_click_captcha(frame=\"iframe\", retry=False, blind=False)\ndriver.uc_gui_handle_captcha(frame=\"iframe\")\n```\n\n--------\n\n<h2>🔵 Examples</h2>\n\n✅ Test Folder: [SeleniumBase/examples](https://github.com/seleniumbase/SeleniumBase/tree/master/examples)\n\n* [my_first_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py)\n* [test_demo_site.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py)\n* [test_coffee_cart.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_coffee_cart.py)\n* [coffee_cart_tests.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/coffee_cart_tests.py)\n* [parameterized_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/parameterized_test.py)\n* [test_deferred_asserts.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_deferred_asserts.py)\n* [test_error_page.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_error_page.py)\n* [test_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_login.py)\n* [test_markers.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_markers.py)\n* [test_swag_labs.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_swag_labs.py)\n* [test_simple_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_simple_login.py)\n* [test_suite.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_suite.py)\n* [test_tinymce.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_tinymce.py)\n* And many more...\n\n[<img src=\"https://seleniumbase.github.io/cdn/img/sb_text_f.png\" title=\"SeleniumBase\" align=\"center\" width=\"280\">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)\n"
  },
  {
    "path": "help_docs/mobile_testing.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Mobile Mode / Mobile Testing</h2>\n\nUse ``--mobile`` to run SeleniumBase tests using Chrome's mobile device emulator with default values for Device Metrics and User-Agent.\n\n<b>Here's an example mobile test:</b>\n\n[SeleniumBase/examples/test_roblox_mobile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_roblox_mobile.py)\n\n```zsh\npytest test_roblox_mobile.py --mobile\n```\n\nTo configure Device Metrics, use:\n\n```zsh\n--metrics=\"CSS_Width,CSS_Height,Pixel_Ratio\"\n```\n\nTo configure the User-Agent, use:\n\n```zsh\n--agent=\"USER-AGENT-STRING\"\n```\n\nTo find real values for Device Metrics, see:\n\n* [Device Metrics List](https://gist.github.com/sidferreira/3f5fad525e99b395d8bd882ee0fd9d00)\n\nTo find real User-Agent strings, see:\n\n* [User Agent Strings List](https://developers.whatismybrowser.com/useragents/explore/)\n\n--------\n\n<b>Here's another example of a mobile test:</b>\n\n[SeleniumBase/examples/test_swag_labs.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_swag_labs.py)\n\n```zsh\npytest test_swag_labs.py --mobile\n```\n\n<a href=\"https://seleniumbase.github.io/cdn/gif/swag_mobile.gif\"><img src=\"https://seleniumbase.github.io/cdn/gif/swag_mobile_2.gif\" alt=\"SeleniumBase Mobile Testing\" title=\"SeleniumBase Mobile Testing\"></a>\n\n<b>Here's an example of configuring mobile settings for that test:</b>\n\n```zsh\n# Run tests using Chrome's mobile device emulator (default settings)\npytest test_swag_labs.py --mobile\n\n# Run mobile tests specifying CSS Width, CSS Height, and Pixel-Ratio\npytest test_swag_labs.py --mobile --metrics=\"360,640,2\"\n\n# Run mobile tests specifying the user agent\npytest test_swag_labs.py --mobile --agent=\"Mozilla/5.0 (Linux; Android 9; Pixel 3 XL)\"\n```\n\n--------\n\nFor some [SeleniumBase Syntax Formats](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md), you can also use `mobile=True` to run tests in Mobile Mode:\n\n```python\nfrom seleniumbase import Driver\n\ndriver = Driver(mobile=True)\ntry:\n    driver.open(\"https://www.roblox.com/\")\n    driver.assert_element(\"#download-the-app-container\")\n    driver.assert_text(\"Roblox for Android\")\n    driver.highlight('span:contains(\"Roblox for Android\")', loops=8)\n    driver.highlight('span:contains(\"Continue in App\")', loops=8)\nfinally:\n    driver.quit()\n```\n\n--------\n\n<p align=\"center\"><div align=\"center\"><a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20seleniumbase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a> <a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase.io Docs\" /></a></div></p>\n"
  },
  {
    "path": "help_docs/mysql_installation.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## MySQL Installation Instructions\n\n\n### [MySQL](http://www.mysql.com/) (OPTIONAL)\n\n(NOTE: If you don't plan on using the SeleniumBase MySQL DB feature, then you can skip this section.)\n\n--------\n\n### GitHub Actions Ubuntu Linux MySQL Setup:\n\n```zsh\nsudo /etc/init.d/mysql start\nmysql -e 'CREATE DATABASE IF NOT EXISTS test_db;' -uroot -proot\nwget https://raw.githubusercontent.com/seleniumbase/SeleniumBase/master/seleniumbase/core/create_db_tables.sql\nsudo mysql -h 127.0.0.1 -uroot -proot test_db < create_db_tables.sql\nsudo mysql -e 'ALTER USER \"root\"@\"localhost\" IDENTIFIED BY \"test\";' -uroot -proot\nsudo service mysql restart\n```\n\nHave SeleniumBase tests write to the MySQL DB:\n\n```zsh\npytest --with-db_reporting\n```\n\nQuery MySQL DB Results:\n\n```zsh\nmysql -e 'select test_address,browser,state,start_time,runtime from test_db.test_run_data;' -uroot -ptest\n```\n\n--------\n\n### Standard Ubuntu Linux MySQL Setup:\n\n```zsh\nsudo apt update\nsudo apt install mysql-server\nsudo mysql_secure_installation\nsudo mysql -e 'CREATE DATABASE IF NOT EXISTS test_db;'\nsudo mysql -h 127.0.0.1 -u root test_db < seleniumbase/core/create_db_tables.sql\nsudo service mysql restart\n```\n\nTo change the password from `root` to `test`:\n\n```zsh\nmysqladmin -u root -p'root' password 'test'\nsudo service mysql restart\n```\n\n### MacOS MySQL Setup:\n\n```zsh\nbrew install mysql\n```\n\nThen start the MySQL service:\n\n```zsh\nbrew services start mysql\n```\n\n(Continue with additional steps below to set up your DB.)\n\n### Windows MySQL Setup:\n\n[Download MySQL here](http://dev.mysql.com/downloads/windows/)\nFollow the steps from the MySQL Downloads page.\n\n(Continue with additional steps below to set up your DB.)\n\n### Access your MySQL DB:\n\nIf you want a visual tool to help make your MySQL life easier, [try MySQL Workbench](http://dev.mysql.com/downloads/workbench/).\n\n### Prepare your MySQL DB:\n\nUse the [create_db_tables.sql](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/core/create_db_tables.sql) file to create the necessary tables for storing test data.\n\n### Configure your MySQL DB for SeleniumBase:\n\nUpdate your [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) file with your MySQL DB credentials so that tests can write to the database when they run.\n\n### Have SeleniumBase tests write to your MySQL DB:\n\nAdd the ``--with-db_reporting`` argument on the command-line when you want tests to write to your MySQL database. Example:\n\n```zsh\npytest --with-db_reporting\n```\n"
  },
  {
    "path": "help_docs/recorder_mode.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"> Recorder Mode 🔴/⏺️</h2>\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=eKN5nq7YbdM\"><img src=\"http://img.youtube.com/vi/eKN5nq7YbdM/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"285\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=eKN5nq7YbdM\">Watch the tutorial on YouTube</a></b>)</p>\n\n🔴 <b>SeleniumBase Recorder Mode</b> lets you record & export browser actions into test automation scripts.<br>\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_recorder_notification.png\" title=\"SeleniumBase\" width=\"380\">\n\n⏺️ Recorder Mode can be activated from the command-line interface or the Recorder Desktop App.\n\n⏺️ To make a new recording from the command-line interface, use ``sbase mkrec``, ``sbase codegen``, or ``sbase record``:\n\n```zsh\nsbase mkrec TEST_NAME.py --url=URL\n```\n\nIf the file already exists, you'll get an error. If no URL is provided, you'll start on a blank page and will need to navigate somewhere for the Recorder to activate. (The Recorder captures events on URLs that start with ``https``, ``http``, or ``file``.) The command above runs an empty test that stops at a breakpoint so that you can perform manual browser actions for the Recorder. When you have finished recording, type \"``c``\" on the command-line and press ``[ENTER]`` to continue from the breakpoint. The test will complete and a file called ``TEST_NAME_rec.py`` will be automatically created in the ``./recordings`` folder. That file will get copied back to the original folder with the name you gave it. (You can run with Edge instead of Chrome by adding ``--edge`` to the command above. For headed Linux machines, add ``--gui`` to prevent the default headless mode on Linux.)\n\nExample:\n\n```zsh\nsbase mkrec new_test.py --url=wikipedia.org\n\n* RECORDING initialized: new_test.py\n\npytest new_test.py --rec -q -s --url=wikipedia.org\n\n>>>>>>>>>>>>>>>>>> PDB set_trace >>>>>>>>>>>>>>>>>\n\n> PATH_TO_YOUR_CURRENT_DIRECTORY/new_test.py(9)\n   .\n   5         def test_recording(self):\n   6             if self.recorder_ext:\n   7                 # When done recording actions,\n   8                 # type \"c\", and press [Enter].\n   9  ->             import pdb; pdb.set_trace()\n return None\n(Pdb+) c\n\n>>>>>>>>>>>>>>>>>> PDB continue >>>>>>>>>>>>>>>>>>\n\n>>> RECORDING SAVED as: recordings/new_test_rec.py\n**************************************************\n\n*** RECORDING COPIED to: new_test.py\n```\n\n🔴 You can also activate Recorder Mode from the Recorder Desktop App:\n\n```zsh\nsbase recorder\n* Starting the SeleniumBase Recorder Desktop App...\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/recorder_desktop_2.png\" title=\"SeleniumBase\" width=\"340\">\n\n⏺️ While a recording is in progress, you can press the ``[ESC]`` key to pause the Recorder. To resume the recording, you can hit the ``[~`]`` key, which is located directly below the ``[ESC]`` key on most keyboards.\n\n⏺️ From within Recorder Mode there are two additional modes: \"Assert Element Mode\" and \"Assert Text Mode\". To switch into \"Assert Element Mode\", press the ``[^]-key (SHIFT+6 on standard QWERTY keyboards)``: The border will become purple, and you'll be able to click on elements to assert from your test. To switch into \"Assert Text Mode\", press the ``[&]-key (SHIFT+7 on standard QWERTY keyboards)``: The border will become teal, and you'll be able to click on elements for asserting text from your test.\n\n⏺️ While using either of the two special Assertion Modes, certain actions such as clicking on links won't have any effect. This lets you make assertions on elements without navigating away from the page, etc. To add an assertion for buttons without triggering default \"click\" behavior, mouse-down on the button and then mouse-up somewhere else. (This prevents a detected click while still recording the assert.) To return back to the original Recorder Mode, press any key other than ``[SHIFT]`` or ``[BACKSPACE]`` (Eg: Press ``[CONTROL]``, etc.). Press ``[ESC]`` once to leave the Assertion Modes, but it'll stop the Recorder if you press it again.\n\n⏺️ For extra flexibility, the ``sbase mkrec`` command can be split into four separate commands:\n\n```zsh\nsbase mkfile TEST_NAME.py --rec\n\npytest TEST_NAME.py --rec -q -s\n\nsbase print ./recordings/TEST_NAME_rec.py -n\n\ncp ./recordings/TEST_NAME_rec.py ./TEST_NAME.py\n```\n\nThe first command creates a boilerplate test with a breakpoint; the second command runs the test with the Recorder activated; the third command prints the completed test to the console; and the fourth command replaces the initial boilerplate with the completed test. If you're just experimenting with the Recorder, you can run the second command as many times as you want, and it'll override previous recordings saved to ``./recordings/TEST_NAME_rec.py``. (Note that ``-s`` is needed to allow breakpoints, unless you already have a ``pytest.ini`` file present where you set it. The ``-q`` is optional, which shortens ``pytest`` console output.)\n\n⏺️ You can also use the Recorder to add code to an existing test. To do that, you'll first need to create a breakpoint in your code to insert manual browser actions:\n\n```python\nimport pdb; pdb.set_trace()\n```\n\nNow you'll be able to run your test with ``pytest``, and it will stop at the breakpoint for you to add in actions: (Press ``c`` and ``ENTER`` on the command-line to continue from the breakpoint.)\n\n```zsh\npytest TEST_NAME.py --rec -s\n```\n\n⏺️ You can also set a breakpoint at the start of your test by adding ``--trace`` as a ``pytest`` command-line option: (This is useful when running Recorder Mode without any ``pdb`` breakpoints.)\n\n```zsh\npytest TEST_NAME.py --trace --rec -s\n```\n\n⏺️ After the test completes, a file called ``TEST_NAME_rec.py`` will be automatically created in the ``./recordings`` folder, which will include the actions performed by the test, and the manual actions that you added in.\n\n⏺️ Here's a command-line notification for a completed recording:\n\n```zsh\n>>> RECORDING SAVED as: recordings/TEST_NAME_rec.py\n***************************************************\n```\n\n⏺️ When running additional tests from the same Python module, Recordings will get added to the file that was created from the first test:\n\n```zsh\n>>> RECORDING ADDED to: recordings/TEST_NAME_rec.py\n***************************************************\n```\n\n⏺️ Recorder Mode works by saving your recorded actions into the browser's sessionStorage. SeleniumBase then reads from the browser's sessionStorage to take the raw data and generate a full test from it. Keep in mind that sessionStorage is only present while the browser tab remains in the same domain/origin. (The sessionStorage of that tab goes away if you leave that domain/origin.) To compensate, links to web pages of different domain/origin will automatically open a new tab for you in Recorder Mode.\n\n⏺️ Additionally, the SeleniumBase <code>self.open(URL)</code> method will also open a new tab for you in Recorder Mode if the domain/origin is different from the current URL. If you need to navigate to a different domain/origin from within the same tab, call <code>self.save_recorded_actions()</code> first, which saves the recorded data for later. When a recorded test completes, SeleniumBase scans the sessionStorage data of all open browser tabs for generating the completed script.\n\n⏺️ As an alternative to activating Recorder Mode with the <code>--rec</code> command-line arg, you can also call <code>self.activate_recorder()</code> from your tests. Using the Recorder this way is only useful for tests that stay on the same URL. This is because the standard Recorder Mode functions as a Chrome extension and persists wherever the browser goes. (This version only stays on the page where called.)\n\n⏺️ (Note that <b>same domain/origin</b> is not the same as <b>same URL</b>. Example: <a href=\"https://xkcd.com/353/\" target=\"_blank\">https://xkcd.com/353</a> and <a href=\"https://xkcd.com/1537/\" target=\"_blank\">https://xkcd.com/1537</a> are two different URLs with the <b>same domain/origin</b>. That means both URLs share the same sessionStorage, and that changes persist to different URLs of the same domain/origin. If you want to find out a website's origin during a test, just call: <code>self.get_origin()</code>, which returns the value of <code>window.location.origin</code> from the browser's console.)\n\n⏺️ Inside recorded tests, you might find the <code>self.open_if_not_url(URL)</code> method, which opens the URL given if the browser is not currently on that page. SeleniumBase uses this method in recorded scripts when the Recorder detects that a browser action changed the current URL. This method prevents an unnecessary page load and shows what page the test visited after a browser action.\n\n⏺️ By launching the Recorder App with <code>sbase recorder --ee</code>, you can end the recording by pressing {<code>SHIFT</code>+<code>ESC</code>} instead of the usual way of ending the recording by typing <code>c</code> from a <code>breakpoint()</code> and pressing <code>Enter</code>. Those buttons don't need to be pressed at the same time, but <code>SHIFT</code> must be pressed directly before <code>ESC</code>.\n\n⏺️ Use <code>sbase recorder --uc</code> to launch the Recorder App with UC Mode enabled. (The driver will be disconnected from Chrome, but the Recorder extension will still capture any browser actions.)\n\n--------\n\n<div>To learn more about SeleniumBase, check out the Docs Site:</div>\n<a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a>\n\n<div>All the code is on GitHub:</div>\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase on GitHub\" /></a>\n"
  },
  {
    "path": "help_docs/shadow_dom.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h3 align=\"left\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_b.png\" alt=\"SeleniumBase\" width=\"300\" /></h3>\n\n## Shadow DOM support / Shadow-root interaction\n\n🔵 SeleniumBase lets you pierce through open Shadow DOM selectors (to interact with elements inside) by adding ``::shadow`` to CSS fragments that include a shadow-root element. For multi-layered shadow-roots, you must individually pierce through each shadow-root element that you want to get through.\n\n🔵 Here are some examples of Shadow DOM selectors:\n\n```python\ncss_1 = \"downloads-manager::shadow #no-downloads\"\n\ncss_2 = \"downloads-manager::shadow #downloadsList downloads-item::shadow #file-link\"\n\ncss_3 = \"downloads-manager::shadow downloads-toolbar::shadow cr-toolbar::shadow cr-toolbar-search-field::shadow cr-icon-button\"\n\ncss_4 = \"downloads-manager::shadow downloads-toolbar::shadow cr-toolbar::shadow cr-toolbar-search-field::shadow #searchInput\"\n\ncss_5 = \"downloads-manager::shadow downloads-toolbar::shadow cr-toolbar::shadow cr-toolbar-search-field::shadow #clearSearch\"\n```\n\n🔵 The shadow-root (``::shadow``) elements are transitional, and therefore cannot be the final part of your CSS selectors. Complete your CSS selectors by including an element that's inside a shadow-root.\n\n🔵 NOTE: ``::shadow`` selectors only exist within SeleniumBase. (They are not part of standard CSS.)\n\n🔵 Here are some examples of tests that interact with Shadow DOM elements:\n* [examples/shadow_root_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/shadow_root_test.py)\n* [examples/test_shadow_dom.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_shadow_dom.py)\n* [examples/old_wordle_script.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/old_wordle_script.py)\n"
  },
  {
    "path": "help_docs/syntax_formats.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<a id=\"syntax_formats\"></a>\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"40\"> The 25 Syntax Formats / Design Patterns</h2>\n\n<h3>🔠 SeleniumBase supports multiple ways of structuring tests:</h3>\n\n<blockquote>\n<p dir=\"auto\"></p>\n<ul dir=\"auto\">\n<li><a href=\"#sb_sf_01\"><strong>01. BaseCase direct class inheritance</strong></a></li>\n<li><a href=\"#sb_sf_02\"><strong>02. BaseCase subclass inheritance</strong></a></li>\n<li><a href=\"#sb_sf_03\"><strong>03. The \"sb\" pytest fixture (no class)</strong></a></li>\n<li><a href=\"#sb_sf_04\"><strong>04. The \"sb\" pytest fixture (in class)</strong></a></li>\n<li><a href=\"#sb_sf_05\"><strong>05. Page Object Model with BaseCase</strong></a></li>\n<li><a href=\"#sb_sf_06\"><strong>06. Page Object Model with the \"sb\" fixture</strong></a></li>\n<li><a href=\"#sb_sf_07\"><strong>07. Using \"request\" to get \"sb\" (no class)</strong></a></li>\n<li><a href=\"#sb_sf_08\"><strong>08. Using \"request\" to get \"sb\" (in class)</strong></a></li>\n<li><a href=\"#sb_sf_09\"><strong>09. Overriding the driver via BaseCase</strong></a></li>\n<li><a href=\"#sb_sf_10\"><strong>10. Overriding the driver via \"sb\" fixture</strong></a></li>\n<li><a href=\"#sb_sf_11\"><strong>11. BaseCase with Chinese translations</strong></a></li>\n<li><a href=\"#sb_sf_12\"><strong>12. BaseCase with Dutch translations</strong></a></li>\n<li><a href=\"#sb_sf_13\"><strong>13. BaseCase with French translations</strong></a></li>\n<li><a href=\"#sb_sf_14\"><strong>14. BaseCase with Italian translations</strong></a></li>\n<li><a href=\"#sb_sf_15\"><strong>15. BaseCase with Japanese translations</strong></a></li>\n<li><a href=\"#sb_sf_16\"><strong>16. BaseCase with Korean translations</strong></a></li>\n<li><a href=\"#sb_sf_17\"><strong>17. BaseCase with Portuguese translations</strong></a></li>\n<li><a href=\"#sb_sf_18\"><strong>18. BaseCase with Russian translations</strong></a></li>\n<li><a href=\"#sb_sf_19\"><strong>19. BaseCase with Spanish translations</strong></a></li>\n<li><a href=\"#sb_sf_20\"><strong>20. Gherkin syntax with \"behave\" BDD runner</strong></a></li>\n<li><a href=\"#sb_sf_21\"><strong>21. SeleniumBase SB (Python context manager)</strong></a></li>\n<li><a href=\"#sb_sf_22\"><strong>22. The driver manager (via context manager)</strong></a></li>\n<li><a href=\"#sb_sf_23\"><strong>23. The driver manager (via direct import)</strong></a></li>\n<li><a href=\"#sb_sf_24\"><strong>24. Pure CDP Mode (Async API. No Selenium)</strong></a></li>\n<li><a href=\"#sb_sf_25\"><strong>25. Pure CDP Mode (Sync API. No Selenium)</strong></a></li>\n</ul>\n</blockquote>\n\n--------\n\n<a id=\"sb_sf_01\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 1. BaseCase direct class inheritance</h2>\n\nIn this format, (which is used by most of the tests in the <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples\">SeleniumBase examples folder</a>), <code translate=\"no\">BaseCase</code> is imported at the top of a Python file, followed by a Python class inheriting <code translate=\"no\">BaseCase</code>. Then, any test method defined in that class automatically gains access to SeleniumBase methods, including the <code translate=\"no\">setUp()</code> and <code translate=\"no\">tearDown()</code> methods that are automatically called for opening and closing web browsers at the start and end of tests.\n\nTo run a test of this format, use **``pytest``** or ``pynose``. Adding ``BaseCase.main(__name__, __file__)`` enables ``python`` to run ``pytest`` on your file indirectly. Here's an example:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass MyTestClass(BaseCase):\n    def test_demo_site(self):\n        self.open(\"https://seleniumbase.io/demo_page\")\n        self.type(\"#myTextInput\", \"This is Automated\")\n        self.click(\"#myButton\")\n        self.assert_element(\"tbody#tbodyId\")\n        self.assert_text(\"Automation Practice\", \"h3\")\n        self.click_link(\"SeleniumBase Demo Page\")\n        self.assert_exact_text(\"Demo Page\", \"h1\")\n        self.assert_no_js_errors()\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py\">examples/test_demo_site.py</a> for the full test.)\n\nUsing ``BaseCase`` inheritance is a great starting point for anyone learning SeleniumBase, and it follows good object-oriented programming principles.\n\n<a id=\"sb_sf_02\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 2. BaseCase subclass inheritance</h2>\n\nThere are situations where you may want to customize the <code translate=\"no\">setUp</code> and <code translate=\"no\">tearDown</code> of your tests. Maybe you want to have all your tests login to a specific web site first, or maybe you want to have your tests report results through an API call depending on whether a test passed or failed. <b>This can be done by creating a subclass of <code translate=\"no\">BaseCase</code> and then carefully creating custom <code translate=\"no\">setUp()</code> and <code translate=\"no\">tearDown()</code> methods that don't overwrite the critical functionality of the default SeleniumBase <code translate=\"no\">setUp()</code> and <code translate=\"no\">tearDown()</code> methods.</b> Afterwards, your test classes will inherit the subclass of <code translate=\"no\">BaseCase</code> with the added functionality, rather than directly inheriting <code translate=\"no\">BaseCase</code> itself. Here's an example of that:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass BaseTestCase(BaseCase):\n    def setUp(self):\n        super().setUp()\n        # <<< Run custom setUp() code for tests AFTER the super().setUp() >>>\n\n    def tearDown(self):\n        self.save_teardown_screenshot()  # On failure or \"--screenshot\"\n        if self.has_exception():\n            # <<< Run custom code if the test failed. >>>\n            pass\n        else:\n            # <<< Run custom code if the test passed. >>>\n            pass\n        # (Wrap unreliable tearDown() code in a try/except block.)\n        # <<< Run custom tearDown() code BEFORE the super().tearDown() >>>\n        super().tearDown()\n\n    def login(self):\n        # <<< Placeholder. Add your code here. >>>\n        # Reduce duplicate code in tests by having reusable methods like this.\n        # If the UI changes, the fix can be applied in one place.\n        pass\n\n    def example_method(self):\n        # <<< Placeholder. Add your code here. >>>\n        pass\n\nclass MyTests(BaseTestCase):\n    def test_example(self):\n        self.login()\n        self.example_method()\n        self.type(\"input\", \"Name\")\n        self.click(\"form button\")\n        # ...\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/base_test_case.py\">examples/boilerplates/base_test_case.py</a> for more info.)\n\n<a id=\"sb_sf_03\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 3. The \"sb\" pytest fixture (no class)</h2>\n\nThe pytest framework comes with a unique system called fixtures, which replaces import statements at the top of Python files by importing libraries directly into test definitions. More than just being an import, a pytest fixture can also automatically call predefined <code translate=\"no\">setUp()</code> and <code translate=\"no\">tearDown()</code> methods at the beginning and end of test methods. To work, <code translate=\"no\">sb</code> is added as an argument to each test method definition that needs SeleniumBase functionality. This means you no longer need import statements in your Python files to use SeleniumBase. <b>If using other pytest fixtures in your tests, you may need to use the SeleniumBase fixture (instead of <code translate=\"no\">BaseCase</code> class inheritance) for compatibility reasons.</b> Here's an example of the <code translate=\"no\">sb</code> fixture in a test that does not use Python classes:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\ndef test_sb_fixture_with_no_class(sb: BaseCase):\n    sb.open(\"seleniumbase.io/help_docs/install/\")\n    sb.type('input[aria-label=\"Search\"]', \"GUI Commander\")\n    sb.click('mark:contains(\"Commander\")')\n    sb.assert_title_contains(\"GUI / Commander\")\n```\n\n(See the top of <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_sb_fixture.py\">examples/test_sb_fixture.py</a> for the test.)\n\n<a id=\"sb_sf_04\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 4. The \"sb\" pytest fixture (in class)</h2>\n\nThe <code translate=\"no\">sb</code> pytest fixture can also be used inside of a class. There is a slight change to the syntax because that means test methods must also include <code translate=\"no\">self</code> in their argument definitions when test methods are defined. (The <code translate=\"no\">self</code> argument represents the class object, and is used in every test method that lives inside of a class.) Once again, no import statements are needed in your Python files for this to work. Here's an example of using the <code translate=\"no\">sb</code> fixture in a test method that lives inside of a Python class:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass Test_SB_Fixture:\n    def test_sb_fixture_inside_class(self, sb: BaseCase):\n        sb.open(\"seleniumbase.io/help_docs/install/\")\n        sb.type('input[aria-label=\"Search\"]', \"GUI Commander\")\n        sb.click('mark:contains(\"Commander\")')\n        sb.assert_title_contains(\"GUI / Commander\")\n```\n\n(See the bottom of <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_sb_fixture.py\">examples/test_sb_fixture.py</a> for the test.)\n\n<a id=\"sb_sf_05\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 5. Page Object Model with BaseCase</h2>\n\nWith SeleniumBase, you can use Page Objects to break out code from tests, but remember, the <code translate=\"no\">self</code> variable (from test methods that inherit <code translate=\"no\">BaseCase</code>) contains the driver and all other framework-specific variable definitions. Therefore, that <code translate=\"no\">self</code> must be passed as an arg into any outside class method in order to call SeleniumBase methods from there. In the example below, the <code translate=\"no\">self</code> variable from the test method is passed into the <code translate=\"no\">sb</code> arg of the Page Object class method because the <code translate=\"no\">self</code> arg of the Page Object class method is already being used for its own class. Every Python class method definition must include the <code translate=\"no\">self</code> as the first arg.\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass LoginPage:\n    def login_to_swag_labs(self, sb: BaseCase, username):\n        sb.open(\"https://www.saucedemo.com\")\n        sb.type(\"#user-name\", username)\n        sb.type(\"#password\", \"secret_sauce\")\n        sb.click('input[type=\"submit\"]')\n\nclass MyTests(BaseCase):\n    def test_swag_labs_login(self):\n        LoginPage().login_to_swag_labs(self, \"standard_user\")\n        self.assert_element(\"div.inventory_list\")\n        self.assert_element('div:contains(\"Sauce Labs Backpack\")')\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/samples/swag_labs_test.py\">examples/boilerplates/samples/swag_labs_test.py</a> for the full test.)\n\n<a id=\"sb_sf_06\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 6. Page Object Model with the \"sb\" fixture</h2>\n\nThis is similar to the classic Page Object Model with <code translate=\"no\">BaseCase</code> inheritance, except that this time we pass the <code translate=\"no\">sb</code> pytest fixture from the test into the <code translate=\"no\">sb</code> arg of the page object class method, (instead of passing <code translate=\"no\">self</code>). Now that you're using <code translate=\"no\">sb</code> as a pytest fixture, you no longer need to import <code translate=\"no\">BaseCase</code> anywhere in your code. See the example below:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass LoginPage:\n    def login_to_swag_labs(self, sb: BaseCase, username):\n        sb.open(\"https://www.saucedemo.com\")\n        sb.type(\"#user-name\", username)\n        sb.type(\"#password\", \"secret_sauce\")\n        sb.click('input[type=\"submit\"]')\n\nclass MyTests:\n    def test_swag_labs_login(self, sb: BaseCase):\n        LoginPage().login_to_swag_labs(sb, \"standard_user\")\n        sb.assert_element(\"div.inventory_list\")\n        sb.assert_element('div:contains(\"Sauce Labs Backpack\")')\n        sb.js_click(\"a#logout_sidebar_link\")\n        sb.assert_element(\"div#login_button_container\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/samples/sb_swag_test.py\">examples/boilerplates/samples/sb_swag_test.py</a> for the full test.)\n\n<a id=\"sb_sf_07\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 7. Using \"request\" to get \"sb\" (no class)</h2>\n\nThe pytest <code translate=\"no\">request</code> fixture can be used to retrieve other pytest fixtures from within tests, such as the <code translate=\"no\">sb</code> fixture. This allows you to have more control over when fixtures get initialized because the fixture no longer needs to be loaded at the very beginning of test methods. This is done by calling <code translate=\"no\">request.getfixturevalue('sb')</code> from the test. Here's an example of using the pytest <code translate=\"no\">request</code> fixture to load the <code translate=\"no\">sb</code> fixture in a test method that does not use Python classes:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\ndef test_request_sb_fixture(request):\n    sb: BaseCase = request.getfixturevalue(\"sb\")\n    sb.open(\"https://seleniumbase.io/demo_page\")\n    sb.assert_text(\"SeleniumBase\", \"#myForm h2\")\n    sb.assert_element(\"input#myTextInput\")\n    sb.type(\"#myTextarea\", \"This is me\")\n    sb.click(\"#myButton\")\n    sb.tearDown()\n```\n\n(See the top of <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_request_sb_fixture.py\">examples/test_request_sb_fixture.py</a> for the test.)\n\n<a id=\"sb_sf_08\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 8. Using \"request\" to get \"sb\" (in class)</h2>\n\nThe pytest <code translate=\"no\">request</code> fixture can also be used to get the <code translate=\"no\">sb</code> fixture from inside a Python class. Here's an example of that:\n\n```python\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass Test_Request_Fixture:\n    def test_request_sb_fixture_in_class(self, request):\n        sb: BaseCase = request.getfixturevalue(\"sb\")\n        sb.open(\"https://seleniumbase.io/demo_page\")\n        sb.assert_element(\"input#myTextInput\")\n        sb.type(\"#myTextarea\", \"Automated\")\n        sb.assert_text(\"This Text is Green\", \"#pText\")\n        sb.click(\"#myButton\")\n        sb.assert_text(\"This Text is Purple\", \"#pText\")\n        sb.tearDown()\n```\n\n(See the bottom of <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_request_sb_fixture.py\">examples/test_request_sb_fixture.py</a> for the test.)\n\n<a id=\"sb_sf_09\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 9. Overriding the driver via BaseCase</h2>\n\nWhen you want to use SeleniumBase methods via <code translate=\"no\">BaseCase</code>, but you want total freedom to control how you spin up your web browsers, this is the format you want. Although SeleniumBase gives you plenty of command-line options to change how your browsers are launched, this format gives you more control when the existing options aren't enough. Here's an example of that:\n\n```python\nfrom selenium import webdriver\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\nclass OverrideDriverTest(BaseCase):\n    def get_new_driver(self, *args, **kwargs):\n        \"\"\"This method overrides get_new_driver() from BaseCase.\"\"\"\n        options = webdriver.ChromeOptions()\n        options.add_argument(\"--disable-3d-apis\")\n        options.add_argument(\"--disable-notifications\")\n        if self.headless:\n            options.add_argument(\"--headless=new\")\n            options.add_argument(\"--disable-gpu\")\n        options.add_experimental_option(\n            \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"],\n        )\n        prefs = {\n            \"credentials_enable_service\": False,\n            \"profile.password_manager_enabled\": False,\n        }\n        options.add_experimental_option(\"prefs\", prefs)\n        return webdriver.Chrome(options=options)\n\n    def test_simple(self):\n        self.open(\"https://seleniumbase.io/demo_page\")\n        self.assert_text(\"Demo Page\", \"h1\")\n```\n\n(From <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_override_driver.py\">examples/test_override_driver.py</a>)\n\nThe above format lets you customize [selenium-wire](https://github.com/wkeeling/selenium-wire) for intercepting and inspecting requests and responses during SeleniumBase tests. Here's how a ``selenium-wire`` integration may look:\n\n```python\nfrom seleniumbase import BaseCase\nfrom seleniumwire import webdriver  # Requires \"pip install selenium-wire\"\nBaseCase.main(__name__, __file__)\n\nclass WireTestCase(BaseCase):\n    def get_new_driver(self, *args, **kwargs):\n        options = webdriver.ChromeOptions()\n        options.add_experimental_option(\n            \"excludeSwitches\", [\"enable-automation\"]\n        )\n        options.add_experimental_option(\"useAutomationExtension\", False)\n        return webdriver.Chrome(options=options)\n\n    def test_simple(self):\n        self.open(\"https://seleniumbase.io/demo_page\")\n        for request in self.driver.requests:\n            print(request.url)\n```\n\n(NOTE: The ``selenium-wire`` integration is now included with ``seleniumbase``: Add ``--wire`` as a ``pytest`` command-line option to activate.)\n\n<a id=\"sb_sf_10\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 10. Overriding the driver via \"sb\" fixture</h2>\n\nWhen you want to use SeleniumBase methods via the ``sb`` pytest fixture, but you want total freedom to control how you spin up your web browsers, this is the format you want. Although SeleniumBase gives you plenty of command-line options to change how your browsers are launched, this format gives you more control when the existing options aren't enough.\n\n```python\n\"\"\"Overriding the \"sb\" fixture to override the driver.\"\"\"\nimport pytest\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n@pytest.fixture()\ndef sb(request):\n    from selenium import webdriver\n    from seleniumbase import BaseCase\n    from seleniumbase import config as sb_config\n    from seleniumbase.core import session_helper\n\n    class BaseClass(BaseCase):\n        def get_new_driver(self, *args, **kwargs):\n            \"\"\"This method overrides get_new_driver() from BaseCase.\"\"\"\n            options = webdriver.ChromeOptions()\n            if self.headless:\n                options.add_argument(\"--headless=new\")\n                options.add_argument(\"--disable-gpu\")\n            options.add_experimental_option(\n                \"excludeSwitches\", [\"enable-automation\"],\n            )\n            return webdriver.Chrome(options=options)\n\n        def setUp(self):\n            super().setUp()\n\n        def base_method(self):\n            pass\n\n        def tearDown(self):\n            self.save_teardown_screenshot()  # On failure or \"--screenshot\"\n            super().tearDown()\n\n    if request.cls:\n        if sb_config.reuse_class_session:\n            the_class = str(request.cls).split(\".\")[-1].split(\"'\")[0]\n            if the_class != sb_config._sb_class:\n                session_helper.end_reused_class_session_as_needed()\n                sb_config._sb_class = the_class\n        request.cls.sb = BaseClass(\"base_method\")\n        request.cls.sb.setUp()\n        request.cls.sb._needs_tearDown = True\n        request.cls.sb._using_sb_fixture = True\n        request.cls.sb._using_sb_fixture_class = True\n        sb_config._sb_node[request.node.nodeid] = request.cls.sb\n        yield request.cls.sb\n        if request.cls.sb._needs_tearDown:\n            request.cls.sb.tearDown()\n            request.cls.sb._needs_tearDown = False\n    else:\n        sb = BaseClass(\"base_method\")\n        sb.setUp()\n        sb._needs_tearDown = True\n        sb._using_sb_fixture = True\n        sb._using_sb_fixture_no_class = True\n        sb_config._sb_node[request.node.nodeid] = sb\n        yield sb\n        if sb._needs_tearDown:\n            sb.tearDown()\n            sb._needs_tearDown = False\n\ndef test_override_fixture_no_class(sb: BaseCase):\n    sb.open(\"https://seleniumbase.io/demo_page\")\n    sb.type(\"#myTextInput\", \"This is Automated\")\n\nclass TestOverride:\n    def test_override_fixture_inside_class(self, sb: BaseCase):\n        sb.open(\"https://seleniumbase.io/demo_page\")\n        sb.type(\"#myTextInput\", \"This is Automated\")\n```\n\n(From <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_override_sb_fixture.py\">examples/test_override_sb_fixture.py</a>)\n\nHere's how the [selenium-wire](https://github.com/wkeeling/selenium-wire) integration may look when overriding the ``sb`` pytest fixture to override the driver:\n\n```python\nimport pytest\n\n@pytest.fixture()\ndef sb(request):\n    import sys\n    from seleniumbase import BaseCase\n    from seleniumbase import config as sb_config\n    from seleniumwire import webdriver  # Requires \"pip install selenium-wire\"\n\n    class BaseClass(BaseCase):\n        def get_new_driver(self, *args, **kwargs):\n            options = webdriver.ChromeOptions()\n            if \"linux\" in sys.platform:\n                options.add_argument(\"--headless=new\")\n            options.add_experimental_option(\n                \"excludeSwitches\", [\"enable-automation\"],\n            )\n            return webdriver.Chrome(options=options)\n\n        def setUp(self):\n            super().setUp()\n\n        def tearDown(self):\n            self.save_teardown_screenshot()  # On failure or \"--screenshot\"\n            super().tearDown()\n\n        def base_method(self):\n            pass\n\n    if request.cls:\n        request.cls.sb = BaseClass(\"base_method\")\n        request.cls.sb.setUp()\n        request.cls.sb._needs_tearDown = True\n        request.cls.sb._using_sb_fixture = True\n        request.cls.sb._using_sb_fixture_class = True\n        sb_config._sb_node[request.node.nodeid] = request.cls.sb\n        yield request.cls.sb\n        if request.cls.sb._needs_tearDown:\n            request.cls.sb.tearDown()\n            request.cls.sb._needs_tearDown = False\n    else:\n        sb = BaseClass(\"base_method\")\n        sb.setUp()\n        sb._needs_tearDown = True\n        sb._using_sb_fixture = True\n        sb._using_sb_fixture_no_class = True\n        sb_config._sb_node[request.node.nodeid] = sb\n        yield sb\n        if sb._needs_tearDown:\n            sb.tearDown()\n            sb._needs_tearDown = False\n\ndef test_wire_with_no_class(sb):\n    sb.open(\"https://seleniumbase.io/demo_page\")\n    for request in sb.driver.requests:\n        print(request.url)\n\nclass TestWire:\n    def test_wire_inside_class(self, sb):\n        sb.open(\"https://seleniumbase.io/demo_page\")\n        for request in sb.driver.requests:\n            print(request.url)\n```\n\n(NOTE: The ``selenium-wire`` integration is now included with ``seleniumbase``: Add ``--wire`` as a ``pytest`` command-line option to activate. If you need both ``--wire`` with ``--undetected`` modes together, you'll still need to override ``get_new_driver()``.)\n\n<a id=\"sb_sf_11\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 11. BaseCase with Chinese translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Chinese. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.chinese import 硒测试用例\n硒测试用例.main(__name__, __file__)\n\nclass 我的测试类(硒测试用例):\n    def test_例子1(self):\n        self.开启(\"https://zh.wikipedia.org/wiki/\")\n        self.断言标题(\"维基百科，自由的百科全书\")\n        self.断言元素('a[title=\"Wikipedia:关于\"]')\n        self.如果可见请单击('button[aria-label=\"关闭\"]')\n        self.如果可见请单击('button[aria-label=\"關閉\"]')\n        self.断言元素('span:contains(\"创建账号\")')\n        self.断言元素('span:contains(\"登录\")')\n        self.输入文本('input[name=\"search\"]', \"舞龍\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言文本(\"舞龍\", \"#firstHeading\")\n        self.断言元素('img[src*=\"Chinese_draak.jpg\"]')\n        self.回去()\n        self.输入文本('input[name=\"search\"]', \"麻婆豆腐\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言文本(\"麻婆豆腐\", \"#firstHeading\")\n        self.断言元素('figure:contains(\"一家中餐館的麻婆豆腐\")')\n        self.回去()\n        self.输入文本('input[name=\"search\"]', \"精武英雄\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言元素('img[src*=\"Fist_of_legend.jpg\"]')\n        self.断言文本(\"李连杰\", 'li a[title=\"李连杰\"]')\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/chinese_test_1.py\">examples/translations/chinese_test_1.py</a> for the Chinese test.)\n\n<a id=\"sb_sf_12\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 12. BaseCase with Dutch translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Dutch. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.dutch import Testgeval\nTestgeval.main(__name__, __file__)\n\nclass MijnTestklasse(Testgeval):\n    def test_voorbeeld_1(self):\n        self.openen(\"https://nl.wikipedia.org/wiki/Hoofdpagina\")\n        self.controleren_element('a[title*=\"Welkom voor nieuwkomers\"]')\n        self.controleren_tekst(\"Welkom op Wikipedia\", \"td.hp-welkom\")\n        self.typ(\"#searchform input\", \"Stroopwafel\")\n        self.klik(\"#searchform button\")\n        self.controleren_tekst(\"Stroopwafel\", \"#firstHeading\")\n        self.controleren_element('img[src*=\"Stroopwafels\"]')\n        self.typ(\"#searchform input\", \"Rijksmuseum Amsterdam\")\n        self.klik(\"#searchform button\")\n        self.controleren_tekst(\"Rijksmuseum\", \"#firstHeading\")\n        self.controleren_element('img[src*=\"Rijksmuseum\"]')\n        self.terug()\n        self.controleren_url_bevat(\"Stroopwafel\")\n        self.vooruit()\n        self.controleren_url_bevat(\"Rijksmuseum\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/dutch_test_1.py\">examples/translations/dutch_test_1.py</a> for the Dutch test.)\n\n<a id=\"sb_sf_13\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 13. BaseCase with French translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into French. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.french import CasDeBase\nCasDeBase.main(__name__, __file__)\n\nclass MaClasseDeTest(CasDeBase):\n    def test_exemple_1(self):\n        self.ouvrir(\"https://fr.wikipedia.org/wiki/\")\n        self.vérifier_texte(\"Wikipédia\")\n        self.vérifier_élément('[alt=\"Wikipédia\"]')\n        self.cliquer_si_affiché('button[aria-label=\"Fermer\"]')\n        self.js_taper(\"#searchform input\", \"Crème brûlée\")\n        self.cliquer(\"#searchform button\")\n        self.vérifier_texte(\"Crème brûlée\", \"#firstHeading\")\n        self.vérifier_élément('img[alt*=\"Crème brûlée\"]')\n        self.js_taper(\"#searchform input\", \"Jardin des Tuileries\")\n        self.cliquer(\"#searchform button\")\n        self.vérifier_texte(\"Jardin des Tuileries\", \"#firstHeading\")\n        self.vérifier_élément('img[alt*=\"Jardin des Tuileries\"]')\n        self.retour()\n        self.vérifier_url_contient(\"brûlée\")\n        self.en_avant()\n        self.vérifier_url_contient(\"Jardin\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/french_test_1.py\">examples/translations/french_test_1.py</a> for the French test.)\n\n<a id=\"sb_sf_14\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 14. BaseCase with Italian translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Italian. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.italian import CasoDiProva\nCasoDiProva.main(__name__, __file__)\n\nclass MiaClasseDiTest(CasoDiProva):\n    def test_esempio_1(self):\n        self.apri(\"https://it.wikipedia.org/wiki/\")\n        self.verificare_testo(\"Wikipedia\")\n        self.verificare_elemento('a[title=\"Lingua italiana\"]')\n        self.digitare('input[name=\"search\"]', \"Pizza\")\n        self.fare_clic(\"#searchform button\")\n        self.verificare_testo(\"Pizza\", \"#firstHeading\")\n        self.verificare_elemento('figure img[src*=\"pizza\"]')\n        self.digitare('input[name=\"search\"]', \"Colosseo\")\n        self.fare_clic(\"#searchform button\")\n        self.verificare_testo(\"Colosseo\", \"#firstHeading\")\n        self.verificare_elemento('figure img[src*=\"Colosseo\"]')\n        self.indietro()\n        self.verificare_url_contiene(\"Pizza\")\n        self.avanti()\n        self.verificare_url_contiene(\"Colosseo\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/italian_test_1.py\">examples/translations/italian_test_1.py</a> for the Italian test.)\n\n<a id=\"sb_sf_15\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 15. BaseCase with Japanese translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Japanese. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.japanese import セレニウムテストケース\nセレニウムテストケース.main(__name__, __file__)\n\nclass 私のテストクラス(セレニウムテストケース):\n    def test_例1(self):\n        self.を開く(\"https://ja.wikipedia.org/wiki/\")\n        self.テキストを確認する(\"ウィキペディア\")\n        self.要素を確認する('[title*=\"ウィキペディアへようこそ\"]')\n        self.JS入力('input[name=\"search\"]', \"アニメ\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"アニメ\", \"#firstHeading\")\n        self.JS入力('input[name=\"search\"]', \"寿司\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"寿司\", \"#firstHeading\")\n        self.要素を確認する('img[src*=\"Various_sushi\"]')\n        self.JS入力(\"#searchInput\", \"レゴランド・ジャパン\")\n        self.クリックして(\"#searchform button\")\n        self.要素を確認する('img[src*=\"LEGOLAND_JAPAN\"]')\n        self.リンクテキストを確認する(\"名古屋城\")\n        self.リンクテキストをクリックします(\"テーマパーク\")\n        self.テキストを確認する(\"テーマパーク\", \"#firstHeading\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/japanese_test_1.py\">examples/translations/japanese_test_1.py</a> for the Japanese test.)\n\n<a id=\"sb_sf_16\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 16. BaseCase with Korean translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Korean. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.korean import 셀레늄_테스트_케이스\n셀레늄_테스트_케이스.main(__name__, __file__)\n\nclass 테스트_클래스(셀레늄_테스트_케이스):\n    def test_실시예_1(self):\n        self.열기(\"https://ko.wikipedia.org/wiki/\")\n        self.텍스트_확인(\"위키백과\")\n        self.요소_확인('[title=\"위키백과:소개\"]')\n        self.JS_입력(\"#searchform input\", \"김치\")\n        self.클릭(\"#searchform button\")\n        self.텍스트_확인(\"김치\", \"#firstHeading\")\n        self.요소_확인('img[src*=\"Various_kimchi.jpg\"]')\n        self.링크_텍스트_확인(\"한국 요리\")\n        self.JS_입력(\"#searchform input\", \"비빔밥\")\n        self.클릭(\"#searchform button\")\n        self.텍스트_확인(\"비빔밥\", \"#firstHeading\")\n        self.요소_확인('img[src*=\"Dolsot-bibimbap.jpg\"]')\n        self.링크_텍스트를_클릭합니다(\"돌솥비빔밥\")\n        self.텍스트_확인(\"돌솥비빔밥\", \"#firstHeading\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/korean_test_1.py\">examples/translations/korean_test_1.py</a> for the Korean test.)\n\n<a id=\"sb_sf_17\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 17. BaseCase with Portuguese translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Portuguese. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.portuguese import CasoDeTeste\nCasoDeTeste.main(__name__, __file__)\n\nclass MinhaClasseDeTeste(CasoDeTeste):\n    def test_exemplo_1(self):\n        self.abrir(\"https://pt.wikipedia.org/wiki/\")\n        self.verificar_texto(\"Wikipédia\")\n        self.verificar_elemento('[title=\"Língua portuguesa\"]')\n        self.digitar(\"#searchform input\", \"João Pessoa\")\n        self.clique(\"#searchform button\")\n        self.verificar_texto(\"João Pessoa\", \"#firstHeading\")\n        self.verificar_elemento('img[alt*=\"João Pessoa\"]')\n        self.digitar(\"#searchform input\", \"Florianópolis\")\n        self.clique(\"#searchform button\")\n        self.verificar_texto(\"Florianópolis\", \"h1#firstHeading\")\n        self.verificar_elemento('td:contains(\"Avenida Beira-Mar\")')\n        self.voltar()\n        self.verificar_url_contém(\"João_Pessoa\")\n        self.atualizar_a_página()\n        self.js_digitar(\"#searchform input\", \"Teatro Amazonas\")\n        self.clique(\"#searchform button\")\n        self.verificar_texto(\"Teatro Amazonas\", \"#firstHeading\")\n        self.verificar_texto_do_link(\"Festival Amazonas de Ópera\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/portuguese_test_1.py\">examples/translations/portuguese_test_1.py</a> for the Portuguese test.)\n\n<a id=\"sb_sf_18\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 18. BaseCase with Russian translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Russian. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.russian import ТестНаСелен\nТестНаСелен.main(__name__, __file__)\n\nclass МойТестовыйКласс(ТестНаСелен):\n    def test_пример_1(self):\n        self.открыть(\"https://ru.wikipedia.org/wiki/\")\n        self.подтвердить_элемент('[title=\"Русский язык\"]')\n        self.подтвердить_текст(\"Википедия\", \"div.main-wikimedia-header\")\n        self.введите(\"#searchInput\", \"МГУ\")\n        self.нажмите(\"#searchButton\")\n        self.подтвердить_текст(\"университет\", \"#firstHeading\")\n        self.подтвердить_элемент('img[alt*=\"Главное здание МГУ\"]')\n        self.введите(\"#searchInput\", \"приключения Шурика\")\n        self.нажмите(\"#searchButton\")\n        self.подтвердить_текст(\"Операция «Ы» и другие приключения Шурика\")\n        self.подтвердить_элемент('img[alt=\"Постер фильма\"]')\n        self.назад()\n        self.подтвердить_URL_содержит(\"университет\")\n        self.вперед()\n        self.подтвердить_URL_содержит(\"Шурика\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/russian_test_1.py\">examples/translations/russian_test_1.py</a> for the Russian test.)\n\n<a id=\"sb_sf_19\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 19. BaseCase with Spanish translations</h2>\n\nThis format is similar to the English version with <code translate=\"no\">BaseCase</code> inheritance, but there's a different import statement, and method names have been translated into Spanish. Here's an example of that:\n\n```python\nfrom seleniumbase.translate.spanish import CasoDePrueba\nCasoDePrueba.main(__name__, __file__)\n\nclass MiClaseDePrueba(CasoDePrueba):\n    def test_ejemplo_1(self):\n        self.abrir(\"https://es.wikipedia.org/wiki/\")\n        self.verificar_texto(\"Wikipedia\")\n        self.verificar_elemento('[title=\"Wikipedia:Bienvenidos\"]')\n        self.escriba('[name=\"search\"]', \"Parque de Atracciones Tibidabo\")\n        self.haga_clic('button:contains(\"Buscar\")')\n        self.verificar_texto(\"Tibidabo\", \"#firstHeading\")\n        self.verificar_elemento('img[src*=\"Tibidabo\"]')\n        self.escriba('input[name=\"search\"]', \"Palma de Mallorca\")\n        self.haga_clic('button:contains(\"Buscar\")')\n        self.verificar_texto(\"Palma de Mallorca\", \"#firstHeading\")\n        self.verificar_elemento('img[src*=\"Palma\"]')\n        self.volver()\n        self.verificar_url_contiene(\"Tibidabo\")\n        self.adelante()\n        self.verificar_url_contiene(\"Mallorca\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/translations/spanish_test_1.py\">examples/translations/spanish_test_1.py</a> for the Spanish test.)\n\n<a id=\"sb_sf_20\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 20. Gherkin syntax with \"behave\" BDD runner</h2>\n\nWith [Behave's BDD Gherkin format](https://behave.readthedocs.io/en/stable/gherkin.html), you can use natural language to write tests that work with SeleniumBase methods. Behave tests are run by calling ``behave`` on the command-line. This requires some special files in a specific directory structure. Here's an example of that structure:\n\n```zsh\nfeatures/\n├── __init__.py\n├── behave.ini\n├── environment.py\n├── feature_file.feature\n└── steps/\n    ├── __init__.py\n    ├── imported.py\n    └── step_file.py\n```\n\nA ``*.feature`` file might look like this:\n\n```gherkin\nFeature: SeleniumBase scenarios for the RealWorld App\n\n  Scenario: Verify RealWorld App (log in / sign out)\n    Given Open \"seleniumbase.io/realworld/login\"\n    And Clear Session Storage\n    When Type \"demo_user\" into \"#username\"\n    And Type \"secret_pass\" into \"#password\"\n    And Do MFA \"GAXG2MTEOR3DMMDG\" into \"#totpcode\"\n    Then Assert exact text \"Welcome!\" in \"h1\"\n    And Highlight \"img#image1\"\n    And Click 'a:contains(\"This Page\")'\n    And Save screenshot to logs\n    When Click link \"Sign out\"\n    Then Assert element 'a:contains(\"Sign in\")'\n    And Assert text \"You have been signed out!\"\n```\n\n(From <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/features/realworld.feature\">examples/behave_bdd/features/realworld.feature</a>)\n\nYou'll need the ``environment.py`` file for tests to work. Here it is:\n\n```python\nfrom seleniumbase import BaseCase\nfrom seleniumbase.behave import behave_sb\nbehave_sb.set_base_class(BaseCase)  # Accepts a BaseCase subclass\nfrom seleniumbase.behave.behave_sb import before_all  # noqa\nfrom seleniumbase.behave.behave_sb import before_feature  # noqa\nfrom seleniumbase.behave.behave_sb import before_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import before_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import after_feature  # noqa\nfrom seleniumbase.behave.behave_sb import after_all  # noqa\n```\n\n(From <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/features/environment.py\">examples/behave_bdd/features/environment.py</a>)\n\nInside that file, you can use ``BaseCase`` (or a subclass) for the inherited class.\n\nFor your ``behave`` tests to have access to SeleniumBase Behave steps, you can create an ``imported.py`` file with the following line:\n\n```python\nfrom seleniumbase.behave import steps  # noqa\n```\n\nThat will allow you to use lines like this in your ``*.feature`` files:\n\n```gherkin\nFeature: SeleniumBase scenarios for the RealWorld App\n\n  Scenario: Verify RealWorld App (log in / sign out)\n    Given Open \"seleniumbase.io/realworld/login\"\n    And Clear Session Storage\n    When Type \"demo_user\" into \"#username\"\n    And Type \"secret_pass\" into \"#password\"\n    And Do MFA \"GAXG2MTEOR3DMMDG\" into \"#totpcode\"\n    Then Assert exact text \"Welcome!\" in \"h1\"\n    And Highlight \"img#image1\"\n    And Click 'a:contains(\"This Page\")'\n    And Save screenshot to logs\n```\n\nYou can also create your own step files (Eg. ``step_file.py``):\n\n```python\nfrom behave import step\n\n@step(\"Open the Swag Labs Login Page\")\ndef go_to_swag_labs(context):\n    sb = context.sb\n    sb.open(\"https://www.saucedemo.com\")\n    sb.clear_local_storage()\n\n@step(\"Login to Swag Labs with {user}\")\ndef login_to_swag_labs(context, user):\n    sb = context.sb\n    sb.type(\"#user-name\", user)\n    sb.type(\"#password\", \"secret_sauce\\n\")\n```\n\n(For more information, see the <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/ReadMe.md\">SeleniumBase Behave BDD ReadMe</a>.)\n\n<a id=\"sb_sf_21\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 21. SeleniumBase SB (Python context manager)</h2>\n\nThis format provides a pure Python way of using SeleniumBase without a test runner. Options can be passed via method instantiation or from the command-line. When setting the <code translate=\"no\">test</code> option to <code translate=\"no\">True</code> (or calling <code translate=\"no\">python --test</code>), then standard test logging will occur, such as screenshots and reports for failing tests. All the usual SeleniumBase options are available, such as customizing the browser settings, etc. Here are some examples:\n\n```python\nfrom seleniumbase import SB\n\nwith SB() as sb:\n    sb.open(\"seleniumbase.io/simple/login\")\n    sb.type(\"#username\", \"demo_user\")\n    sb.type(\"#password\", \"secret_pass\")\n    sb.click('a:contains(\"Sign in\")')\n    sb.assert_exact_text(\"Welcome!\", \"h1\")\n    sb.assert_element(\"img#image1\")\n    sb.highlight(\"#image1\")\n    sb.click_link(\"Sign out\")\n    sb.assert_text(\"signed out\", \"#top_message\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_login_sb.py\">examples/raw_login_sb.py</a> for the test.)\n\nHere's another example, which uses <code translate=\"no\">test</code> mode:\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    sb.open(\"https://google.com/ncr\")\n    sb.type('[name=\"q\"]', \"SeleniumBase on GitHub\\n\")\n    sb.highlight('a[href*=\"github.com/seleniumbase\"]')\n    sb.sleep(0.5)\n\nwith SB(test=True, rtf=True, demo=True) as sb:\n    sb.open(\"seleniumbase.github.io/demo_page\")\n    sb.type(\"#myTextInput\", \"This is Automated\")\n    sb.assert_text(\"This is Automated\", \"#myTextInput\")\n    sb.assert_text(\"This Text is Green\", \"#pText\")\n    sb.click('button:contains(\"Click Me\")')\n    sb.assert_text(\"This Text is Purple\", \"#pText\")\n    sb.click(\"#checkBox1\")\n    sb.assert_element_not_visible(\"div#drop2 img#logo\")\n    sb.drag_and_drop(\"img#logo\", \"div#drop2\")\n    sb.assert_element(\"div#drop2 img#logo\")\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_test_scripts.py\">examples/raw_test_scripts.py</a> for the test.)\n\nHere's another example, which uses [CDP Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md) from the SeleniumBase SB format:\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"www.planetminecraft.com/account/sign_in/\"\n    sb.activate_cdp_mode(url)\n    sb.sleep(2)\n    sb.solve_captcha()\n    sb.wait_for_element_absent(\"input[disabled]\")\n    sb.sleep(2)\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_planetmc.py\">examples/cdp_mode/raw_planetmc.py</a> for the test.)\n\n<a id=\"sb_sf_22\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 22. The driver manager (via context manager)</h2>\n\nThis pure Python format gives you a raw <code translate=\"no\">webdriver</code> instance in a <code translate=\"no\">with</code> block. The SeleniumBase Driver Manager will automatically make sure that your driver is compatible with your browser version. It gives you full access to customize driver options via method args or via the command-line. The driver will automatically call <code translate=\"no\">quit()</code> after the code leaves the <code translate=\"no\">with</code> block. Here are some examples:\n\n```python\n\"\"\"DriverContext() example. (Runs with \"python\").\"\"\"\nfrom seleniumbase import DriverContext\n\nwith DriverContext() as driver:\n    driver.open(\"seleniumbase.io/\")\n    driver.highlight('img[alt=\"SeleniumBase\"]', loops=6)\n\nwith DriverContext(browser=\"chrome\", incognito=True) as driver:\n    driver.open(\"seleniumbase.io/apps/calculator\")\n    driver.click('[id=\"4\"]')\n    driver.click('[id=\"2\"]')\n    driver.assert_text(\"42\", \"#output\")\n    driver.highlight(\"#output\", loops=6)\n\nwith DriverContext() as driver:\n    driver.open(\"seleniumbase.io/demo_page\")\n    driver.highlight(\"h2\")\n    driver.type(\"#myTextInput\", \"Automation\")\n    driver.click(\"#checkBox1\")\n    driver.highlight(\"img\", loops=6)\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_driver_context.py\">examples/raw_driver_context.py</a> for an example.)\n\n<a id=\"sb_sf_23\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 23. The driver manager (via direct import)</h2>\n\nAnother way of running Selenium tests with pure ``python`` (as opposed to using ``pytest`` or ``pynose``) is by using this format, which bypasses [BaseCase](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py) methods while still giving you a flexible driver with a manager. SeleniumBase includes helper files such as [page_actions.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/page_actions.py), which may help you get around some of the limitations of bypassing ``BaseCase``. Here's an example:\n\n```python\n\"\"\"Driver() example. (Runs with \"python\").\"\"\"\nfrom seleniumbase import Driver\n\ndriver = Driver()\ntry:\n    driver.open(\"seleniumbase.io/demo_page\")\n    driver.highlight(\"h2\")\n    driver.type(\"#myTextInput\", \"Automation\")\n    driver.click(\"#checkBox1\")\n    driver.highlight(\"img\", loops=6)\nfinally:\n    driver.quit()\n\ndriver = Driver(browser=\"chrome\", headless=False)\ntry:\n    driver.open(\"seleniumbase.io/apps/calculator\")\n    driver.click('[id=\"4\"]')\n    driver.click('[id=\"2\"]')\n    driver.assert_text(\"42\", \"#output\")\n    driver.highlight(\"#output\", loops=6)\nfinally:\n    driver.quit()\n```\n\n(From <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_driver_manager.py\">examples/raw_driver_manager.py</a>)\n\nHere's how the [selenium-wire](https://github.com/wkeeling/selenium-wire) integration may look when using the ``Driver()`` format:\n\n```python\nfrom seleniumbase import Driver\n\ndriver = Driver(wire=True, headless=True)\ntry:\n    driver.get(\"https://wikipedia.org\")\n    for request in driver.requests:\n        print(request.url)\nfinally:\n    driver.quit()\n```\n\nHere's another `selenium-wire` example with the `Driver()` format:\n\n```python\nfrom seleniumbase import Driver\n\ndef intercept_response(request, response):\n    print(request.headers)\n\ndriver = Driver(wire=True)\ntry:\n    driver.response_interceptor = intercept_response\n    driver.get(\"https://wikipedia.org\")\nfinally:\n    driver.quit()\n```\n\nHere's an example of basic login with the ``Driver()`` format:\n\n```python\nfrom seleniumbase import Driver\n\ndriver = Driver()\ntry:\n    driver.open(\"seleniumbase.io/simple/login\")\n    driver.type(\"#username\", \"demo_user\")\n    driver.type(\"#password\", \"secret_pass\")\n    driver.click('a:contains(\"Sign in\")')\n    driver.assert_exact_text(\"Welcome!\", \"h1\")\n    driver.assert_element(\"img#image1\")\n    driver.highlight(\"#image1\")\n    driver.click_link(\"Sign out\")\n    driver.assert_text(\"signed out\", \"#top_message\")\nfinally:\n    driver.quit()\n```\n\n(From <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_login_driver.py\">examples/raw_login_driver.py</a>)\n\nThe ``Driver()`` manager format can be used as a drop-in replacement for virtually every Python/selenium framework, as it uses the raw ``driver`` instance for handling commands. The ``Driver()`` method simplifies the work of managing drivers with optimal settings, and it can be configured with multiple args. The ``Driver()`` also accepts command-line options (such as ``python --headless``) so that you don't need to modify your tests directly to use different settings. These command-line options only take effect if the associated method args remain unset (or set to ``None``) for the specified options.\n\nWhen using the ``Driver()`` format, you may need to activate a Virtual Display on your own if you want to run headed tests in a headless Linux environment. (See https://github.com/mdmintz/sbVirtualDisplay for details.) One such example of this is using an authenticated proxy, which is configured via a Chrome extension that is generated at runtime. (Note that regular headless mode in Chrome doesn't support extensions.)\n\n<a id=\"sb_sf_24\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 24. Pure CDP Mode (Async API. No Selenium)</h2>\n\nThis format provides a pure CDP way of using SeleniumBase (without Selenium/WebDriver or a test runner). The <code>async</code>/<code>await</code> API is used. Here's an example:\n\n```python\nimport asyncio\nfrom seleniumbase import cdp_driver\n\nasync def main():\n    url = \"https://seleniumbase.io/simple/login\"\n    driver = await cdp_driver.start_async()\n    page = await driver.get(url, lang=\"en\")\n    print(await page.get_title())\n    await page.type(\"#username\", \"demo_user\")\n    await page.type(\"#password\", \"secret_pass\")\n    await page.click(\"#log-in\")\n    print(await page.get_title())\n    element = await page.select(\"h1\")\n    assert element.text == \"Welcome!\"\n    top_nav = await page.select(\"div.topnav\")\n    links = await top_nav.query_selector_all_async(\"a\")\n    for nav_item in links:\n        print(nav_item.text)\n    driver.stop()\n\nif __name__ == \"__main__\":\n    loop = asyncio.new_event_loop()\n    loop.run_until_complete(main())\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_basic_async.py\">examples/cdp_mode/raw_basic_async.py</a> for the test.)\n\n<a id=\"sb_sf_25\"></a>\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> 25. Pure CDP Mode (Sync API. No Selenium)</h2>\n\nThis format provides a pure CDP way of using SeleniumBase (without Selenium/WebDriver or a test runner). The expanded <code>sb_cdp</code> Sync API is used. Here's an example:\n\n```python\nfrom seleniumbase import sb_cdp\n\nurl = \"https://seleniumbase.io/simple/login\"\nsb = sb_cdp.Chrome(url)\nsb.type(\"#username\", \"demo_user\")\nsb.type(\"#password\", \"secret_pass\")\nsb.click('a:contains(\"Sign in\")')\nsb.assert_exact_text(\"Welcome!\", \"h1\")\nsb.assert_element(\"img#image1\")\nsb.highlight(\"#image1\")\ntop_nav = sb.find_element(\"div.topnav\")\nlinks = top_nav.query_selector_all(\"a\")\nfor nav_item in links:\n    print(nav_item.text)\nsb.click_link(\"Sign out\")\nsb.assert_text(\"signed out\", \"#top_message\")\nsb.driver.stop()\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_basic_cdp.py\">examples/cdp_mode/raw_basic_cdp.py</a> for the test.)\n\nHere's a Pure CDP Mode example that bypasses bot-detection to scrape data from a website:\n\n```python\nfrom seleniumbase import sb_cdp\n\ndef main():\n    url = \"https://www.priceline.com/\"\n    sb = sb_cdp.Chrome(url, lang=\"en\")\n    sb.sleep(2)\n    sb.internalize_links()  # Don't open links in a new tab\n    sb.click(\"#link_header_nav_experiences\")\n    sb.sleep(3)\n    sb.remove_elements(\"msm-cookie-banner\")\n    sb.sleep(1)\n    location = \"Amsterdam\"\n    where_to = 'div[data-automation*=\"experiences\"] input'\n    button = 'button[data-automation*=\"experiences-search\"]'\n    sb.wait_for_text(\"Where to?\")\n    sb.click(where_to)\n    sb.press_keys(where_to, location)\n    sb.sleep(1)\n    sb.click(button)\n    sb.sleep(2)\n    sb.click_if_visible('button[aria-label=\"Close\"]')\n    sb.sleep(1)\n    print(sb.get_title())\n    print(\"************\")\n    for i in range(8):\n        sb.scroll_down(50)\n        sb.sleep(0.2)\n    cards = sb.select_all('span[data-automation*=\"product-list-card\"]')\n    for card in cards:\n        print(\"* %s\" % card.text)\n    sb.driver.stop()\n\nif __name__ == \"__main__\":\n    main()\n```\n\n(See <a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp.py\">examples/cdp_mode/raw_cdp.py</a> for the test.)\n\n--------\n\n<h3 align=\"left\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/sb_logo_10.png\" title=\"SeleniumBase\" width=\"280\" /></a></h3>\n"
  },
  {
    "path": "help_docs/thank_you.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb.png\" title=\"SeleniumBase\" width=\"290\" /></a>\n\n\n<h3>🛬🛫 Thank you for flying with SeleniumBase! 🦅🚀</h3>\n\n--------\n\n<p><div><b>🛩️ SeleniumBase Playlist on YouTube:</b></div></p>\n\n<p><span><a href=\"https://www.youtube.com/playlist?list=PLp9uKicxkBc5UIlGi2BuE3aWC7JyXpD3m\"><img src=\"https://seleniumbase.github.io/cdn/img/youtube.png\" title=\"SeleniumBase Playlist on YouTube\" alt=\"SeleniumBase Playlist on YouTube\" width=\"76\" /></a></span></p>\n\n<p><div><b>🛩️ SeleniumBase GitHub Home Page:</b></div></p>\n\n<p><div><span><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/img/social/share_github.svg\" title=\"SeleniumBase on GitHub\" alt=\"SeleniumBase on GitHub\" width=\"74\" /></a></span></div></p>\n\n<p><div><b>🛩️ SeleniumBase Discord Channel:</b></div></p>\n\n<p><span><a href=\"https://discord.gg/EdhQTn3EyE\"><img src=\"https://seleniumbase.github.io/other/discord_icon.png\" title=\"SeleniumBase on Discord\" alt=\"SeleniumBase on Discord\" width=\"72\" /></a></span></p>\n\n<p><div><b>🛩️ SeleniumBase Gitter Chat:</b></div></p>\n\n<p><span><a href=\"https://gitter.im/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/img/social/share_gitter.svg\" title=\"SeleniumBase on Gitter\" alt=\"SeleniumBase on Gitter\" width=\"56\" /></a></span></p>\n\n<p><div><b>Other Social Media Links:</b></div></p>\n\n<div></p>\n<span><a href=\"https://www.facebook.com/SeleniumBase\"><img src=\"https://seleniumbase.github.io/img/social/share_facebook.svg\" title=\"SeleniumBase on Facebook\" alt=\"SeleniumBase on Facebook\" width=\"50\" /></a></span>\n<span><a href=\"https://twitter.com/seleniumbase\"><img src=\"https://seleniumbase.github.io/img/social/share_twitter.svg\" title=\"SeleniumBase on Twitter\" alt=\"SeleniumBase on Twitter\" width=\"50\" /></a></span>\n<span><a href=\"https://instagram.com/seleniumbase\"><img src=\"https://seleniumbase.github.io/img/social/share_instagram.svg\" title=\"SeleniumBase on Instagram\" alt=\"SeleniumBase on Instagram\" width=\"42\" /></a></span>\n</div></p>\n\n--------\n\n<p><div><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/sb_logo_10.png\" alt=\"SeleniumBase\" width=\"260\"></a></div></p>\n"
  },
  {
    "path": "help_docs/translations.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<a id=\"language_tests\"></a>\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> 🌏 Translated Tests 🈺</h2>\n\n<b>SeleniumBase</b> supports the following 10 languages: <i>English</i>, <i>Chinese</i>, <i>Dutch</i>, <i>French</i>, <i>Italian</i>, <i>Japanese</i>, <i>Korean</i>, <i>Portuguese</i>, <i>Russian</i>, and <i>Spanish</i>. (Examples can be found in <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples/translations\">SeleniumBase/examples/translations</a>)\n\nMulti-language tests run with **pytest** like other tests. Test methods have a one-to-one mapping to supported languages. Here's an example of a translated test:\n\n```python\n# Chinese Translation\nfrom seleniumbase.translate.chinese import 硒测试用例\n\nclass 我的测试类(硒测试用例):\n    def test_例子1(self):\n        self.开启(\"https://zh.wikipedia.org/wiki/\")\n        self.断言标题(\"维基百科，自由的百科全书\")\n        self.断言元素('a[title=\"Wikipedia:关于\"]')\n        self.如果可见请单击('button[aria-label=\"关闭\"]')\n        self.如果可见请单击('button[aria-label=\"關閉\"]')\n        self.断言元素('span:contains(\"创建账号\")')\n        self.断言元素('span:contains(\"登录\")')\n        self.输入文本('input[name=\"search\"]', \"舞龍\")\n        self.单击('button:contains(\"搜索\")')\n        self.断言文本(\"舞龍\", \"#firstHeading\")\n        self.断言元素('img[src*=\"Chinese_draak.jpg\"]')\n```\n\nHere's another example:\n\n```python\n# Japanese Translation\nfrom seleniumbase.translate.japanese import セレニウムテストケース\n\nclass 私のテストクラス(セレニウムテストケース):\n    def test_例1(self):\n        self.を開く(\"https://ja.wikipedia.org/wiki/\")\n        self.テキストを確認する(\"ウィキペディア\")\n        self.要素を確認する('[title*=\"ウィキペディアへようこそ\"]')\n        self.JS入力('input[name=\"search\"]', \"アニメ\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"アニメ\", \"#firstHeading\")\n        self.JS入力('input[name=\"search\"]', \"寿司\")\n        self.クリックして(\"#searchform button\")\n        self.テキストを確認する(\"寿司\", \"#firstHeading\")\n        self.要素を確認する('img[src*=\"Various_sushi\"]')\n```\n\n<a id=\"translation_api\"></a>\n<h2><img src=\"https://seleniumbase.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Translation API 🈺</h2>\n\nYou can use SeleniumBase to selectively translate the method names of any test from one language to another with the console scripts interface. Additionally, the ``import`` line at the top of the Python file will change to import the new ``BaseCase``. Example: ``BaseCase`` becomes ``CasoDeTeste`` when a test is translated into Portuguese.\n\n```zsh\nseleniumbase translate\n```\n\n```zsh\n* Usage:\nseleniumbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\n\n* Languages:\n``--en`` / ``--English``  |  ``--zh`` / ``--Chinese``\n``--nl`` / ``--Dutch``    |  ``--fr`` / ``--French``\n``--it`` / ``--Italian``  |  ``--ja`` / ``--Japanese``\n``--ko`` / ``--Korean``   |  ``--pt`` / ``--Portuguese``\n``--ru`` / ``--Russian``  |  ``--es`` / ``--Spanish``\n\n* Actions:\n``-p`` / ``--print``  (Print translation output to the screen)\n``-o`` / ``--overwrite``  (Overwrite the file being translated)\n``-c`` / ``--copy``  (Copy the translation to a new ``.py`` file)\n\n* Options:\n``-n``  (include line Numbers when using the Print action)\n\n* Examples:\nTranslate test_1.py into Chinese and only print the output:\n>>> seleniumbase translate test_1.py --zh  -p\nTranslate test_2.py into Portuguese and overwrite the file:\n>>> seleniumbase translate test_2.py --pt  -o\nTranslate test_3.py into Dutch and make a copy of the file:\n>>> seleniumbase translate test_3.py --nl  -c\n\n* Output:\nTranslates a SeleniumBase Python file into the language\nspecified. Method calls and ``import`` lines get swapped.\nBoth a language and an action must be specified.\nThe ``-p`` action can be paired with one other action.\nWhen running with ``-c`` (or ``--copy``) the new file name\nwill be the original name appended with an underscore\nplus the 2-letter language code of the new language.\n(Example: Translating ``test_1.py`` into Japanese with\n``-c`` will create a new file called ``test_1_ja.py``.)\n```\n\n--------\n\n<h3 align=\"left\"><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_m.png\" title=\"SeleniumBase\" width=\"280\" /></a></h3>\n"
  },
  {
    "path": "help_docs/uc_mode.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> UC Mode 👤</h2>\n\n👤 <b translate=\"no\">SeleniumBase</b> <b translate=\"no\">UC Mode</b> (Undetected-Chromedriver Mode) allows bots to appear human, which lets them evade detection from anti-bot services that try to block them or trigger CAPTCHAs on various websites.\n\n> ### (For the successor to plain UC Mode, see **[CDP Mode 🐙](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md)**)\n\n---\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=5dMFI3e85ig\"><img src=\"http://img.youtube.com/vi/5dMFI3e85ig/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"320\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=5dMFI3e85ig\">Watch the 1st UC Mode tutorial on YouTube! ▶️</a></b>)</p>\n\n----\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=2pTpBtaE7SQ\"><img src=\"http://img.youtube.com/vi/2pTpBtaE7SQ/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"320\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=2pTpBtaE7SQ\">Watch the 2nd UC Mode tutorial on YouTube! ▶️</a></b>)</p>\n\n----\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=-EpZlhGWo9k\"><img src=\"http://img.youtube.com/vi/-EpZlhGWo9k/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"320\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=-EpZlhGWo9k\">Watch the 3rd UC Mode tutorial on YouTube! ▶️</a></b>)</p>\n\n----\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=Mr90iQmNsKM\"><img src=\"http://img.youtube.com/vi/Mr90iQmNsKM/0.jpg\" title=\"SeleniumBase on YouTube\" width=\"320\" /></a>\n<p>(<b><a href=\"https://www.youtube.com/watch?v=Mr90iQmNsKM\">Watch the 4th UC Mode tutorial on YouTube! ▶️</a></b>)</p>\n\n----\n\n👤 <b translate=\"no\">UC Mode</b> is based on [undetected-chromedriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver). <span translate=\"no\">UC Mode</span> includes multiple updates, fixes, and improvements, such as having special <code>uc_*()</code> methods for bypassing CAPTCHAs.\n\n👤 Here's a simple example with the <b><code translate=\"no\">Driver</code></b> manager:\n\n```python\nfrom seleniumbase import Driver\n\ndriver = Driver(uc=True)\nurl = \"https://gitlab.com/users/sign_in\"\ndriver.uc_open_with_reconnect(url, 4)\ndriver.uc_gui_click_captcha()\ndriver.quit()\n```\n\n<img src=\"https://seleniumbase.github.io/other/gitlab_bypass.png\" title=\"SeleniumBase\" width=\"370\">\n\n👤 Here's an example with the <b><code translate=\"no\">SB</code></b> manager (which has more methods and functionality than the <b><code translate=\"no\">Driver</code></b> format):\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True) as sb:\n    url = \"https://gitlab.com/users/sign_in\"\n    sb.uc_open_with_reconnect(url, 4)\n    sb.uc_gui_click_captcha()\n```\n\n(Note: If running UC Mode scripts on headless Linux machines, then you'll need to use the <b><code translate=\"no\">SB</code></b> manager instead of the <b><code translate=\"no\">Driver</code></b> manager because the <b><code translate=\"no\">SB</code></b> manager includes a special virtual display that allows for <b><code translate=\"no\">PyAutoGUI</code></b> actions.)\n\n👤 Here's a longer example: (Note that <code translate=\"no\">sb.uc_gui_click_captcha()</code> performs a special click using <b><code translate=\"no\">PyAutoGUI</code></b> if a CAPTCHA is detected.)\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://gitlab.com/users/sign_in\"\n    sb.uc_open_with_reconnect(url, 4)\n    sb.uc_gui_click_captcha()\n    sb.assert_text(\"Username\", '[for=\"user_login\"]', timeout=3)\n    sb.assert_element('label[for=\"user_login\"]')\n    sb.highlight('button:contains(\"Sign in\")')\n    sb.highlight('h1:contains(\"GitLab\")')\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=4)\n```\n\n👤 Here's an example <b>where clicking the checkbox is required</b>, even for humans:<br />(Commonly seen on forms that are CAPTCHA-protected.)\n\n<img src=\"https://seleniumbase.github.io/other/cf_turnstile.png\" title=\"SeleniumBase\" width=\"260\">\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://seleniumbase.io/apps/turnstile\"\n    sb.uc_open_with_reconnect(url, reconnect_time=2)\n    sb.uc_gui_handle_captcha()\n    sb.assert_element(\"img#captcha-success\", timeout=3)\n    sb.set_messenger_theme(location=\"top_left\")\n    sb.post_message(\"SeleniumBase wasn't detected\", duration=3)\n```\n\n<img src=\"https://seleniumbase.github.io/other/turnstile_click.jpg\" title=\"SeleniumBase\" width=\"440\">\n\nIf running on a Linux server, `uc_gui_handle_captcha()` might not be good enough. Switch to `uc_gui_click_captcha()` to be more stealthy. Note that these methods auto-detect between CF Turnstile and Google reCAPTCHA.\n\nSometimes you need to add <code translate=\"no\">incognito=True</code> with <code translate=\"no\">uc=True</code> to maximize your anti-detection abilities. (Some websites can detect you if you don't do that.)\n\n👤 Here's an example <b>where the CAPTCHA appears after submitting a form</b>:\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True, incognito=True, locale=\"en\") as sb:\n    url = \"https://ahrefs.com/website-authority-checker\"\n    input_field = 'input[placeholder=\"Enter domain\"]'\n    submit_button = 'span:contains(\"Check Authority\")'\n    sb.uc_open_with_reconnect(url)  # The bot-check is later\n    sb.type(input_field, \"github.com/seleniumbase/SeleniumBase\")\n    sb.uc_click(submit_button, reconnect_time=3.25)\n    sb.uc_gui_click_captcha()\n    sb.wait_for_text_not_visible(\"Checking\", timeout=15)\n    sb.click_if_visible('button[data-cky-tag=\"close-button\"]')\n    sb.highlight('p:contains(\"github.com/seleniumbase/SeleniumBase\")')\n    sb.highlight('a:contains(\"Top 100 backlinks\")')\n    sb.set_messenger_theme(location=\"bottom_center\")\n    sb.post_message(\"SeleniumBase wasn't detected!\")\n```\n\n<img src=\"https://seleniumbase.github.io/other/ahrefs_bypass.png\" title=\"SeleniumBase\" width=\"540\">\n\n--------\n\n👤 <b>On Linux</b>, use `sb.uc_gui_click_captcha()` to handle CAPTCHAs (Cloudflare Turnstiles):\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://www.virtualmanager.com/en/login\"\n    sb.uc_open_with_reconnect(url, 4)\n    print(sb.get_page_title())\n    sb.uc_gui_click_captcha()  # Only used if needed\n    print(sb.get_page_title())\n    sb.assert_element('input[name*=\"email\"]')\n    sb.assert_element('input[name*=\"login\"]')\n    sb.set_messenger_theme(location=\"bottom_center\")\n    sb.post_message(\"SeleniumBase wasn't detected!\")\n```\n\n<a href=\"https://github.com/mdmintz/undetected-testing/actions/runs/9637461606/job/26576722411\"><img width=\"540\" alt=\"uc_gui_click_captcha on Linux\" src=\"https://github.com/seleniumbase/SeleniumBase/assets/6788579/6aceb2a3-2a32-4521-b30a-f79446d2ce28\"></a>\n\nThe 2nd <code translate=\"no\">print()</code> should output <code translate=\"no\">Virtual Manager</code>, which means that the automation successfully passed the Turnstile.\n\n(Note: <span translate=\"no\">UC Mode</span> is detectable in Headless Mode, so don't combine those options. Instead, use <code translate=\"no\">xvfb=True</code> / `--xvfb`on Linux for the special virtual display, which is enabled by default when not changing headed/headless settings.)\n\n--------\n\n👤 In <b translate=\"no\">UC Mode</b>, <code translate=\"no\">driver.get(url)</code> has been modified from its original version: If anti-bot services are detected from a <code translate=\"no\">requests.get(url)</code> call that's made before navigating to the website, then <code translate=\"no\">driver.uc_open_with_reconnect(url)</code> will be used instead. To open a URL normally in <b translate=\"no\">UC Mode</b>, use <code translate=\"no\">driver.default_get(url)</code>.\n\n--------\n\n### 👤 Here are some examples that use UC Mode:\n* [SeleniumBase/examples/verify_undetected.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/verify_undetected.py)\n* [SeleniumBase/examples/raw_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_turnstile.py)\n* [SeleniumBase/examples/raw_form_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_form_turnstile.py)\n* [SeleniumBase/examples/raw_uc_mode.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_uc_mode.py)\n\n<img src=\"https://seleniumbase.github.io/other/cf_bypass.png\" title=\"SeleniumBase\" width=\"260\">\n\n--------\n\n👤 Here's an example where <b><code translate=\"no\">incognito=True</code> is needed for bypassing detection</b>:\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, incognito=True, test=True) as sb:\n    sb.driver.uc_open_with_reconnect(\"https://pixelscan.net/\", 10)\n    sb.sleep(2)\n```\n\n<img src=\"https://seleniumbase.github.io/other/pixelscan.jpg\" title=\"SeleniumBase\" width=\"540\">\n\n--------\n\n### 👤 Here are the SeleniumBase UC Mode methods: (**`--uc`** / **`uc=True`**)\n\n```python\ndriver.uc_open(url)\n\ndriver.uc_open_with_tab(url)\n\ndriver.uc_open_with_reconnect(url, reconnect_time=None)\n\ndriver.uc_open_with_disconnect(url, timeout=None)\n\ndriver.reconnect(timeout)\n\ndriver.disconnect()\n\ndriver.connect()\n\ndriver.uc_click(\n    selector, by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT, reconnect_time=None)\n\ndriver.uc_gui_press_key(key)\n\ndriver.uc_gui_press_keys(keys)\n\ndriver.uc_gui_write(text)\n\ndriver.uc_gui_click_x_y(x, y, timeframe=0.25)\n\ndriver.uc_gui_click_captcha(frame=\"iframe\", retry=False, blind=False)\n# driver.uc_gui_click_cf(frame=\"iframe\", retry=False, blind=False)\n# driver.uc_gui_click_rc(frame=\"iframe\", retry=False, blind=False)\n\ndriver.uc_gui_handle_captcha(frame=\"iframe\")\n# driver.uc_gui_handle_cf(frame=\"iframe\")\n# driver.uc_gui_handle_rc(frame=\"iframe\")\n```\n\n(Note that the <b><code translate=\"no\">reconnect_time</code></b> is used to specify how long the driver should be disconnected from Chrome to prevent detection before reconnecting again.)\n\n👤 Since <b><code translate=\"no\">driver.get(url)</code></b> is slower in <span translate=\"no\">UC Mode</span> for bypassing detection, use <b><code translate=\"no\">driver.default_get(url)</code></b> for a standard page load instead:\n\n```python\ndriver.default_get(url)  # Faster, but Selenium can be detected\n```\n\n👤 Here are some examples of using those special <b translate=\"no\">UC Mode</b> methods: (Use <b><code translate=\"no\">self.driver</code></b> for <b><code translate=\"no\">BaseCase</code></b> formats. Use <b><code translate=\"no\">sb.driver</code></b> for <b><code translate=\"no\">SB()</code></b> formats):\n\n```python\nurl = \"https://gitlab.com/users/sign_in\"\ndriver.uc_open_with_reconnect(url, reconnect_time=3)\ndriver.uc_open_with_reconnect(url, 3)\n\ndriver.reconnect(5)\ndriver.reconnect(timeout=5)\n```\n\n👤 You can also set the <b><code translate=\"no\">reconnect_time</code></b> / <b><code translate=\"no\">timeout</code></b> to <b><code translate=\"no\">\"breakpoint\"</code></b> as a valid option. This allows the user to perform manual actions (until typing <b><code translate=\"no\">c</code></b> and pressing <b><code translate=\"no\">ENTER</code></b> to continue from the breakpoint):\n\n```python\nurl = \"https://gitlab.com/users/sign_in\"\ndriver.uc_open_with_reconnect(url, reconnect_time=\"breakpoint\")\ndriver.uc_open_with_reconnect(url, \"breakpoint\")\n\ndriver.reconnect(timeout=\"breakpoint\")\ndriver.reconnect(\"breakpoint\")\n```\n\n(Note that while the special <b><code translate=\"no\">UC Mode</code></b> breakpoint is active, you can't use <b><code translate=\"no\">Selenium</code></b> commands in the browser, and the browser can't detect <b><code translate=\"no\">Selenium</code></b>.)\n\n--------\n\n👤 <b>On Linux</b>, use <code translate=\"no\">xvfb=True</code> / `--xvfb` to activate a special virtual display. This allows you to run a regular browser in an environment that has no GUI. This is important for two reasons: One: <span translate=\"no\">UC Mode</span> is detectable in headless mode. Two: <code translate=\"no\">pyautogui</code> doesn't work in headless mode. (Note that some methods such as <code translate=\"no\">uc_gui_click_captcha()</code> require <code translate=\"no\">pyautogui</code> for performing special actions.)\n\n--------\n\n👤 <code translate=\"no\">uc_gui_click_captcha()</code> auto-detects the CAPTCHA type before trying to click it. This is a generic method for both CF Turnstile and Google reCAPTCHA. It will use the code from <code translate=\"no\">uc_gui_click_cf()</code> and <code translate=\"no\">uc_gui_click_rc()</code> as needed.\n\n👤 <code translate=\"no\">uc_gui_click_cf(frame=\"iframe\", retry=False, blind=False)</code> has three args. (All optional). The first one, <code translate=\"no\">frame</code>, lets you specify the selector above the <code translate=\"no\">iframe</code> in case the CAPTCHA is not located in the first <code translate=\"no\">iframe</code> on the page. (In the case of Shadow-DOM, specify the selector of an element that's above the Shadow-DOM.) The second one, <code translate=\"no\">retry</code>, lets you retry the click after reloading the page if the first one didn't work (and a CAPTCHA is still present after the page reload). The third arg, <code translate=\"no\">blind</code>, (if <code translate=\"no\">True</code>), will retry after a page reload (if the first click failed) by clicking at the last known coordinates of the CAPTCHA checkbox without confirming first with Selenium that a CAPTCHA is still on the page.\n\n👤 <code translate=\"no\">uc_gui_click_rc(frame=\"iframe\", retry=False, blind=False)</code> is for reCAPTCHA. This may only work a few times before not working anymore... not because Selenium was detected, but because reCAPTCHA uses advanced AI to detect unusual activity, unlike the CF Turnstile, which only uses basic detection.\n\n--------\n\n👤 To find out if <b translate=\"no\">UC Mode</b> will work at all on a specific site (before adjusting for timing), load your site with the following script:\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True) as sb:\n    sb.uc_open_with_reconnect(URL, reconnect_time=\"breakpoint\")\n```\n\n(If you remain undetected while loading the page and performing manual actions, then you know you can create a working script once you swap the breakpoint with a time and add special methods like <b><code translate=\"no\">sb.uc_click</code></b> as needed.)\n\n--------\n\n👤 <b>Multithreaded UC Mode:</b>\n\nIf you're using <b><code translate=\"no\">pytest</code></b> for multithreaded <b translate=\"no\">UC Mode</b> (which requires using one of the <b><code translate=\"no\">pytest</code></b> [syntax formats](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md)), then all you have to do is set the number of threads when your script runs. (<code translate=\"no\">-n NUM</code>) Eg:\n\n```zsh\npytest --uc -n 4\n```\n\n(Then <b><code translate=\"no\">pytest-xdist</code></b> is automatically used to spin up and process the threads.)\n\nIf you don't want to use <b><code translate=\"no\">pytest</code></b> for multithreading, then you'll need to do a little more work. That involves using a different multithreading library, (eg. <b><code translate=\"no\">concurrent.futures</code></b>), and making sure that thread-locking is done correctly for processes that share resources. To handle that thread-locking, include <b><code translate=\"no\">sys.argv.append(\"-n\")</code></b> in your <b>SeleniumBase</b> file.\n\nHere's a sample script that uses <b><code translate=\"no\">concurrent.futures</code></b> for spinning up multiple processes:\n\n```python\nimport sys\nfrom concurrent.futures import ThreadPoolExecutor\nfrom seleniumbase import Driver\nsys.argv.append(\"-n\")  # Tell SeleniumBase to do thread-locking as needed\n\ndef launch_driver(url):\n    driver = Driver(uc=True)\n    try:\n        driver.get(url=url)\n        driver.sleep(2)\n    finally:\n        driver.quit()\n\nurls = ['https://seleniumbase.io/demo_page' for i in range(3)]\nwith ThreadPoolExecutor(max_workers=len(urls)) as executor:\n    for url in urls:\n        executor.submit(launch_driver, url)\n```\n\n--------\n\n👤 <b>What makes UC Mode work?</b>\n\nHere are the 3 primary things that <b translate=\"no\">UC Mode</b> does to make bots appear human:\n\n<ul>\n<li>Modifies <b><code translate=\"no\">chromedriver</code></b> to rename <b translate=\"no\">Chrome DevTools Console</b> variables.</li>\n<li>Launches <b translate=\"no\">Chrome</b> browsers before attaching <b><code translate=\"no\">chromedriver</code></b> to them.</li>\n<li>Disconnects <b><code translate=\"no\">chromedriver</code></b> from <b translate=\"no\">Chrome</b> during stealthy actions.</li>\n</ul>\n\nFor example, if the <b translate=\"no\">Chrome DevTools Console</b> variables aren't renamed, you can expect to find them easily when using <b><code translate=\"no\">selenium</code></b> for browser automation:\n\n<img src=\"https://seleniumbase.github.io/other/cdc_args.png\" title=\"SeleniumBase\" width=\"390\">\n\n(If those variables are still there, then websites can easily detect your bots.)\n\nIf you launch <b translate=\"no\">Chrome</b> using <b><code translate=\"no\">chromedriver</code></b>, then there will be settings that make your browser look like a bot. (Instead, <b translate=\"no\">UC Mode</b> connects <b><code translate=\"no\">chromedriver</code></b> to <b translate=\"no\">Chrome</b> after the browser is launched, which makes <b translate=\"no\">Chrome</b> look like a normal, human-controlled web browser.)\n\nWhile <b><code translate=\"no\">chromedriver</code></b> is connected to <b translate=\"no\">Chrome</b>, website services can detect it. Thankfully, raw <b><code translate=\"no\">selenium</code></b> already includes <b><code translate=\"no\">driver.service.stop()</code></b> for stopping the <b><code translate=\"no\">chromedriver</code></b> service, <b><code translate=\"no\">driver.service.start()</code></b> for starting the <b><code translate=\"no\">chromedriver</code></b> service, and <b><code translate=\"no\">driver.start_session(capabilities)</code></b> for reviving the active browser session with the given capabilities. (<b translate=\"no\"><code>SeleniumBase</code> <span translate=\"no\">UC Mode</span></b> methods automatically use those raw <b><code translate=\"no\">selenium</code></b> methods as needed.)\n\nLinks to those <a href=\"https://github.com/SeleniumHQ/selenium\">raw <b>Selenium</b></a> method definitions have been provided for reference (but you don't need to call those methods directly):\n\n<ul>\n<li><b><code translate=\"no\"><a href=\"https://github.com/SeleniumHQ/selenium/blob/9c6ccdbf40356284fad342f70fbdc0afefd27bd3/py/selenium/webdriver/common/service.py#L135\">driver.service.stop()</a></code></b></li>\n<li><b><code translate=\"no\"><a href=\"https://github.com/SeleniumHQ/selenium/blob/9c6ccdbf40356284fad342f70fbdc0afefd27bd3/py/selenium/webdriver/common/service.py#L91\">driver.service.start()</a></code></b></li>\n<li><b><code translate=\"no\"><a href=\"https://github.com/SeleniumHQ/selenium/blob/9c6ccdbf40356284fad342f70fbdc0afefd27bd3/py/selenium/webdriver/remote/webdriver.py#L284\">driver.start_session(capabilities)</a></code></b></li>\n</ul>\n\nAlso note that <b><code translate=\"no\">chromedriver</code></b> isn't detectable in a browser tab if it never touches that tab. Here's a JS command that lets you open a URL in a new tab (from your current tab):\n\n<ul>\n<li><b><code translate=\"no\">window.open(\"URL\");</code></b> --> (Info: <a href=\"https://www.w3schools.com/jsref/met_win_open.asp\" target=\"_blank\">W3Schools</a>)</li>\n</ul>\n\nThe above JS method is used within <b translate=\"no\"><code>SeleniumBase</code></b> <b translate=\"no\">UC Mode</b> methods for opening URLs in a stealthy way. Since some websites try to detect if your browser is a bot on the initial page load, this allows you to bypass detection in those situations. After a few seconds (customizable), <b translate=\"no\">UC Mode</b> tells <b><code translate=\"no\">chromedriver</code></b> to connect to that tab so that automated commands can now be issued. At that point, <b><code translate=\"no\">chromedriver</code></b> could be detected if websites are looking for it (but generally websites only look for it during specific events, such as page loads, form submissions, and button clicks).\n\nAvoiding detection while clicking is easy if you schedule your clicks to happen at a future point when the <b><code translate=\"no\">chromedriver</code></b> service has been stopped. Here's a JS command that lets you schedule events (such as clicks) to happen in the future:\n\n<li><b><code translate=\"no\">window.setTimeout(function() { SCRIPT }, MS);</code></b> --> (Info: <a href=\"https://www.w3schools.com/jsref/met_win_settimeout.asp\" target=\"_blank\">W3Schools</a>)</li>\n\nThe above JS method is used within the <b><code translate=\"no\">SeleniumBase</code></b> <b translate=\"no\">UC Mode</b> method: <b><code translate=\"no\">sb.uc_click(selector)</code></b> so that clicking can be done in a stealthy way. <b translate=\"no\">UC Mode</b> schedules your click, disconnects <b><code translate=\"no\">chromedriver</code></b> from <b translate=\"no\">Chrome</b>, waits a little (customizable), and reconnects.\n\n--------\n\n🏆 <b>Choosing the right CAPTCHA service</b> for your business / website:\n\n<img src=\"https://seleniumbase.github.io/other/me_se_conf.jpg\" title=\"SeleniumBase\" width=\"370\">\n\nAs an ethical hacker / cybersecurity researcher who builds bots that bypass CAPTCHAs for sport, <b>the CAPTCHA service that I personally recommend</b> for keeping bots out is <b translate=\"no\">Google reCAPTCHA</b>:\n\n<img src=\"https://seleniumbase.github.io/other/g_recaptcha.png\" title=\"SeleniumBase\" width=\"315\">\n\nSince Google makes Chrome, Google's own <b translate=\"no\">reCAPTCHA</b> service has access to more data than other CAPTCHA services, and can therefore use that data to make better decisions about whether or not web activity is coming from real humans or automated bots.\n\n--------\n\n⚖️ <b>Legal implications of web-scraping</b>:\n\nBased on the following article, https://nubela.co/blog/meta-lost-the-scraping-legal-battle-to-bright-data/, (which outlines a court case where social-networking company: Meta lost the legal battle to data-scraping company: Bright Data), it was determined that web scraping is 100% legal in the eyes of the courts as long as:\n1. The scraping is only done with <b>public data</b> and <b>not private data</b>.\n2. The scraping isn’t done while logged in on the site being scraped.\n\nIf the above criteria are met, then scrape away! (According to the article)\n\n(Note: I'm not a lawyer, so I can't officially offer legal advice, but I can direct people to existing articles online where people can find their own answers.)\n\n--------\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_text_f.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" align=\"center\" width=\"335\">\n\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_gs.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"335\" /></a></div>\n"
  },
  {
    "path": "help_docs/useful_grep_commands.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## Useful grep commands\n\nThere are several useful **grep** commands for helping you find and/or replace text in multiple files. Examples:\n\n#### List all files containing ``self.get_new_driver(``, ignoring \".pyc\" files, from the current directory:\n\n``grep -rl \"self.get_new_driver(\" * --exclude=\\*.pyc``\nOR\n``grep -rl * -e \"self.get_new_driver(\" --exclude=\\*.pyc``\n\nTo only search ``.py`` files, use ``--include=\\*.py``:\n\n``grep -rl \"self.get_new_driver(\" * --include=\\*.py``\n\n--------\n\n#### Replace all occurrences of \"foo_abc\" with \"bar_xyz\" on Linux, for Python files from the current directory:\n\n``sed -i 's/foo_abc/bar_xyz/g' *.py``\n\n#### Replace all occurrences of \"foo_abc\" with \"bar_xyz\" on macOS, for Python files from the current directory:\n\n``sed -i '' 's/foo_abc/bar_xyz/g' *.py``\n\n--------\n\n#### Find all chromedriver processes (this combines ``ps`` with ``grep``):\n\n``ps -ef |grep chromedriver``\n\n--------\n\n#### References:\n* https://stackoverflow.com/questions/16956810/how-do-i-find-all-files-containing-specific-text-on-linux\n* https://stackoverflow.com/questions/11392478/how-to-replace-a-string-in-multiple-files-in-linux-command-line/20721292#20721292\n"
  },
  {
    "path": "help_docs/using_safari_driver.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## Using Safari's WebDriver for running browser tests on macOS\n\n*(NOTE: Safari's WebDriver requires macOS 10.13 \"High Sierra\" or later.)*\n\nYou can find the official Apple documentation regarding \"Testing with WebDriver in Safari\" on the following page: [https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari](https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari)\n\nRun ``safaridriver --enable`` once in a terminal to enable Safari's WebDriver. (If you’re upgrading from a previous macOS release, you may need to prefix the command with ``sudo``.)\n\nNow you can use ``--safari`` to run your **SeleniumBase** tests on Safari.\n"
  },
  {
    "path": "help_docs/verify_webdriver.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Verifying that web drivers are installed</h2>\n\nOn newer versions of SeleniumBase, the driver is automatically downloaded to the ``seleniumbase/drivers`` folder as needed, and does not need to be on the System Path when running tests.\n\nDrivers can be manually downloaded to the ``seleniumbase/drivers`` folder with commands such as:\n\n```zsh\nsbase get chromedriver\nsbase get geckodriver\nsbase get edgedriver\n```\n\n--------\n\nIf you want to check that you have the correct driver installed on your System PATH (which is no longer necessary unless using the Selenium Grid), then continue reading below:\n\n*This assumes you've already downloaded a driver to your **System PATH** with a command such as:*\n\n```zsh\nsbase get chromedriver --path\n```\n\n(The above ``--path`` addition is for Linux/Mac only, which uses ``/usr/local/bin/``. The \"Path\" is different on Windows, and you'll need to manually copy the driver to your System Path, which is defined in the Control Panel's System Environment Variables.)\n\n*You can verify that the correct drivers exist on your System Path by checking inside a Python command prompt.*\n\n#### Verifying ChromeDriver\n\n```zsh\npython\n```\n\n```python\n>>> from seleniumbase import get_driver\n>>> driver = get_driver(\"chrome\", headless=False)\n>>> driver.get(\"https://www.google.com/chrome\")\n>>> driver.quit()\n>>> exit()\n```\n\n#### Verifying Geckodriver (Firefox WebDriver)\n\n```zsh\npython\n```\n\n```python\n>>> from seleniumbase import get_driver\n>>> driver = get_driver(\"firefox\", headless=False)\n>>> driver.get(\"https://www.mozilla.org/firefox\")\n>>> driver.quit()\n>>> exit()\n```\n\n#### Verifying WebDriver for Safari\n\n```zsh\npython\n```\n\n```python\n>>> from seleniumbase import get_driver\n>>> driver = get_driver(\"safari\", headless=False)\n>>> driver.get(\"https://www.apple.com/safari\")\n>>> driver.quit()\n>>> exit()\n```\n"
  },
  {
    "path": "help_docs/virtualenv_instructions.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## Virtual Environment Tutorial\n\nThere are multiple ways of creating a **[Python virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment)**. This tutorial covers two of those:\n\n* The ``venv`` command (<i>included with Python 3+</i>).\n* The virtualenvwrapper ``mkvirtualenv`` command.\n\n``venv`` creates virtual environments in the location where run (<i>generally with Python projects</i>).\n\n``mkvirtualenv`` creates virtual environments in one place (<i>generally in your home directory</i>).\n\n(The [Python Software Foundation](https://www.python.org/psf/) recommends ``venv`` for creating virtual environments.)\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Option 1: Using \"venv\"</h3>\n\n> macOS/Linux terminal (``python3 -m venv ENV``)\n\n```zsh\npython3 -m venv sbase_env\nsource sbase_env/bin/activate\n```\n\n> Windows CMD prompt (``py -m venv ENV``):\n\n```zsh\npy -m venv sbase_env\ncall sbase_env\\\\Scripts\\\\activate\n```\n\nTo exit a virtual env, type ``deactivate``.\n\n--------\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Option 2: Using virtualenvwrapper</h3>\n\n> macOS/Linux terminal:\n\n```zsh\npython3 -m pip install virtualenvwrapper --force-reinstall\nexport WORKON_HOME=$HOME/.virtualenvs\nsource `which virtualenvwrapper.sh`\n```\n\n(*Shortcut*: Run ``source virtualenv_install.sh`` from the top-level SeleniumBase folder to perform the above steps.)\n\n(If you add ``source `which virtualenvwrapper.sh` `` to your local bash file (``~/.bash_profile`` on macOS, or ``~/.bashrc`` on Linux), virtualenvwrapper commands such as ``mkvirtualenv`` will be available whenever you open a new command prompt.)\n\n> Windows CMD prompt:\n\n```zsh\npy -m pip install virtualenvwrapper-win --force-reinstall --user\n```\n\n(*Shortcut*: Run ``win_virtualenv.bat`` from the top-level SeleniumBase folder to perform the above step.)\n\n\n<h3>Create a virtual environment:</h3>\n\n* ``mkvirtualenv ENV``:\n\n```zsh\nmkvirtualenv sbase_env\n```\n\n(If you have multiple versions of Python installed on your machine, and you want your virtual environment to use a specific Python version, add ``--python=PATH_TO_PYTHON_EXE`` to your ``mkvirtualenv`` command with the Python executable to use.)\n\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> virtualenvwrapper commands:</h3>\n\nCreating a virtual environment:\n\n```zsh\nmkvirtualenv sbase_env\n```\n\nLeaving your virtual environment:\n\n```zsh\ndeactivate\n```\n\nReturning to a virtual environment:\n\n```zsh\nworkon sbase_env\n```\n\nListing all virtual environments:\n\n```zsh\nworkon\n```\n\nDeleting a virtual environment:\n\n```zsh\nrmvirtualenv sbase_env\n```\n\n--------\n\nIf the ``python`` and ``python3`` versions don't match (*while in a virtualenv on macOS or Linux*), the following command will sync the versions:\n\n```zsh\nalias python=python3\n```\n\n(To remove an alias, use: ``unalias NAME``)\n\n--------\n\nTo verify the ``python`` version, use:\n\n```zsh\npython --version\n```\n\nTo see the PATH of your ``python`` (macOS/Linux), use:\n\n```zsh\nwhich python\n```\n\n--------\n\n> <i>[python-guide.org/en/latest/dev/virtualenvs](http://docs.python-guide.org/en/latest/dev/virtualenvs/) has more information about Python virtual environments. For specific details about VirtualEnv and VirtualEnvWrapper, see [http://virtualenv.readthedocs.org/en/latest/](http://virtualenv.readthedocs.org/en/latest/) and [http://virtualenvwrapper.readthedocs.org/en/latest/](http://virtualenvwrapper.readthedocs.org/en/latest/).</i>\n"
  },
  {
    "path": "help_docs/webdriver_installation.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Installing webdrivers</h2>\n\nTo run web automation, you need webdrivers for each browser you plan on using.  With SeleniumBase, drivers are downloaded automatically (as needed) into the SeleniumBase `drivers/` folder.\n\n🎛️ You can also download drivers manually with these commands:\n\n```zsh\nseleniumbase get chromedriver\nseleniumbase get geckodriver\nseleniumbase get edgedriver\n```\n\nAfter running the commands above, web drivers will get downloaded into the `seleniumbase/drivers/` folder. SeleniumBase uses those drivers during tests. (The drivers don't come with SeleniumBase by default.)\n\nIf the necessary driver is not found in this location while running tests, SeleniumBase will instead look for the driver on the System PATH. If the necessary driver is not on the System PATH either, SeleniumBase will automatically attempt to download the required driver.\n\n🎛️ You can also download specific versions of drivers. Examples:\n\n```zsh\nsbase get chromedriver 114\nsbase get chromedriver 114.0.5735.90\nsbase get chromedriver stable\nsbase get chromedriver beta\nsbase get chromedriver dev\nsbase get chromedriver canary\nsbase get chromedriver previous  # One major version before the stable version\nsbase get chromedriver mlatest  # Milestone latest version for detected browser\nsbase get edgedriver 115.0.1901.183\n```\n\n(NOTE: `sbase` is a shortcut for `seleniumbase`)\n\n--------\n\nIf you plan on using the [Selenium Grid integration](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/utilities/selenium_grid/ReadMe.md) (which allows for ``remote`` webdriver), you'll need to put the drivers on your System PATH. On macOS and Linux, ``/usr/local/bin`` is a good PATH spot. On Windows, you may need to set the System PATH under Environment Variables to include the location where you placed the driver files. As a shortcut, you could place the driver files into your Python ``Scripts/`` folder in the location where you have Python installed, which should already be on your System PATH.\n\nHere's where you can go to manually get web drivers from the source:\n\n* For Chrome, get [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) on your System PATH.\n\n* For Edge, get [Edge Driver (Microsoft WebDriver)](https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/) on your System PATH.\n\n* For Firefox, get [Geckodriver](https://github.com/mozilla/geckodriver/releases) on your System PATH.\n\n* For Safari, get [Safari Driver](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/using_safari_driver.md) on your System PATH.\n\n**macOS shortcuts**:\n\n🎛️ You can also install drivers by using ``brew`` (aka ``homebrew``):\n\n```zsh\nbrew install --cask chromedriver\n\nbrew install geckodriver\n```\n\n🎛️ You can also upgrade existing webdrivers:\n\n```zsh\nbrew upgrade --cask chromedriver\n\nbrew upgrade geckodriver\n```\n\n**Linux shortcuts**:\n\n🎛️ If you still need drivers, these scripts download `chromedriver` and `geckodriver` to a Linux machine:\n\n```zsh\nwget https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip\nunzip chromedriver_linux64.zip\nmv chromedriver /usr/local/bin/\nchmod +x /usr/local/bin/chromedriver\n```\n\n```zsh\nwget https://github.com/mozilla/geckodriver/releases/download/v0.35.0/geckodriver-v0.35.0-linux64.tar.gz\ntar xvfz geckodriver-v0.35.0-linux64.tar.gz\nmv geckodriver /usr/local/bin/\nchmod +x /usr/local/bin/geckodriver\n```\n\nTo verify that web drivers are working, **[follow these instructions](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/verify_webdriver.md)**.\n\n--------\n\n**Browser Binaries**:\n\n🎛️ Use the `sbase get` command to download the `Chrome for Testing` and `Chrome-Headless-Shell` browser binaries. Example:\n\n```zsh\nsbase get cft  # (For `Chrome for Testing`)\nsbase get chs  # (For `Chrome-Headless-Shell`)\n```\n\nThose commands download those binaries into the `seleniumbase/drivers` folder.\nTo use the binaries from there in SeleniumBase scripts, set the `binary_location` to `cft` or `chs`.\n\n(Source: https://googlechromelabs.github.io/chrome-for-testing/)\n\n--------\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_b.png\" title=\"SeleniumBase\" width=\"280\"></a>\n"
  },
  {
    "path": "install.sh",
    "content": "#!/usr/bin/env bash\npip install -e . --use-pep517 --config-settings=\"editable_mode=compat\"\n"
  },
  {
    "path": "integrations/__init__.py",
    "content": ""
  },
  {
    "path": "integrations/azure/azure_pipelines/ReadMe.md",
    "content": "### Running browser-based test automation with [Azure Pipelines](https://dev.azure.com/seleniumbase/seleniumbase/_build?definitionId=1&_a=summary) by using [SeleniumBase](https://github.com/seleniumbase/SeleniumBase)\n\n----------\n\n### Step 0. Fork the [SeleniumBase](https://github.com/seleniumbase/SeleniumBase) repo on GitHub to get started quickly.\n\n* **(You'll be using your own repository eventually.)**\n\n\n### Step 1. Get Azure Pipelines from the GitHub Marketplace\n\n#### Navigate to [https://github.com/marketplace/azure-pipelines](https://github.com/marketplace/azure-pipelines)\n\n* **Set up a new plan (it's free) and follow the steps...**\n\n![](https://seleniumbase.github.io/cdn/img/azure/github_azure_pipelines_1.png \"GitHub Azure Pipelines\")\n\n----------\n\n![](https://seleniumbase.github.io/cdn/img/azure/github_azure_pipelines_2.png \"GitHub Azure Pipelines\")\n\n----------\n\n![](https://seleniumbase.github.io/cdn/img/azure/github_azure_pipelines_3.png \"GitHub Azure Pipelines\")\n\n----------\n\n### Step 2. Go to Microsoft Azure DevOps to set up your environment\n\n* **Navigate to [https://azure.microsoft.com/en-us/services/devops/?nav=min](https://azure.microsoft.com/en-us/services/devops/?nav=min)**\n\n* **Follow the steps...**\n\n#### Select \"Start free with GitHub >\":\n\n![](https://seleniumbase.github.io/cdn/img/azure/azure_devops_1a.png \"Azure DevOps\")\n\n----------\n\n#### Give your new project a name and set visibility to public (for your SeleniumBase fork):\n\n![](https://seleniumbase.github.io/cdn/img/azure/azure_devops_2.png \"Azure DevOps\")\n\n----------\n\n#### Select that your code is hosted on GitHub:\n\n![](https://seleniumbase.github.io/cdn/img/azure/azure_devops_3.png \"Azure DevOps\")\n\n----------\n\n#### Select your fork of SeleniumBase as your repository:\n\n![](https://seleniumbase.github.io/cdn/img/azure/azure_devops_4.png \"Azure DevOps\")\n\n----------\n\n#### Copy the [azure-pipelines.yml](https://github.com/seleniumbase/SeleniumBase/blob/master/azure-pipelines.yml) file from SeleniumBase into the azure-pipelines.yml box to create your new pipeline:\n\n![](https://seleniumbase.github.io/cdn/img/azure/azure_devops_5.png \"Azure DevOps\")\n\n#### When you're done copying, click \"Run\".\n\n----------\n\n### Step 3. Congratulations! Your browser tests are now running!\n\n* **Here's what a SeleniumBase sample run in Azure Pipelines may look like:**\n\n[https://dev.azure.com/seleniumbase/seleniumbase/\\_build/results?buildId=234](https://dev.azure.com/seleniumbase/seleniumbase/_build/results?buildId=234)\n\n![](https://seleniumbase.github.io/cdn/img/azure/azure_devops_6.png \"Azure DevOps\")\n\n----------\n\n#### Every time you create a pull request now, Azure Pipelines will run your tests automatically.\n\n**To learn more, study [SeleniumBase](https://github.com/seleniumbase/SeleniumBase) and see how the [azure-pipelines.yml](https://github.com/seleniumbase/SeleniumBase/blob/master/azure-pipelines.yml) file works.**\n"
  },
  {
    "path": "integrations/azure/jenkins/ReadMe.md",
    "content": "### Building a browser-based test automation server with Jenkins on Azure by using SeleniumBase\n\n(For the official Microsoft tutorial, see [Get Started: Install Jenkins on an Azure Linux VM](https://docs.microsoft.com/en-us/azure/developer/jenkins/configure-on-linux-vm), and then continue with [Step 4](#step4) below to resume SeleniumBase setup after you've created your Jenkins instance.)\n\n----------\n\n### Step 0. Fork the [SeleniumBase](https://github.com/seleniumbase/SeleniumBase) repo on GitHub to get started quickly.\n\n* **(You'll be using your own repository eventually.)**\n\n### Step 1. Find Jenkins in the Azure Marketplace\n\n#### Search for [\"Jenkins\" in the Azure Marketplace](https://portal.azure.com/#blade/Microsoft_Azure_Marketplace/GalleryFeaturedMenuItemBlade/selectedMenuItemId/home/searchQuery/jenkins/resetMenuId/) and select the ``Jenkins (Publisher: Microsoft)`` result to get to the Jenkins Start page.\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_01.png \"Jenkins on Azure\")\n\n----------\n\n### Step 2. Launch a Jenkins instance\n\n#### Click \"Create\" and follow the steps...\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_02.png \"Jenkins on Azure\")\n\n----------\n\n#### Continue to \"Additional Settings\" when you're done with \"Basics\".\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_03.png \"Jenkins on Azure\")\n\n----------\n\n#### On the \"Additional Settings\" section, set the Size to \"B2s\":\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_04.png \"Jenkins on Azure\")\n\n----------\n\n#### Once you've reached Step 5, click \"Create\" to complete the setup.\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_05.png \"Jenkins on Azure\")\n\n----------\n\n### Step 3. Inspect your new Jenkins instance to SSH into the new machine\n\n#### Once your new Jenkins instance has finished launching, you should be able to see the main page:\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_06.png \"Jenkins on Azure\")\n\n----------\n\n#### On the main page, you should be able to find the Public IP Address.\n* **Use that IP Address to SSH into the machine:**\n\n```zsh\nssh USERNAME@IP_ADDRESS\n```\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_07.png \"Jenkins on Azure\")\n\n----------\n\n<a id=\"step4\"></a>\n\n### Step 4. Clone the SeleniumBase repository from the root (\"/\") directory.\n\n```zsh\ncd /\nsudo git clone https://github.com/seleniumbase/SeleniumBase.git\n```\n\n### Step 5. Enter the \"linux\" folder\n\n```zsh\ncd SeleniumBase/integrations/linux/\n```\n\n### Step 6. Give the \"jenkins\" user sudo access (See [jenkins_permissions.sh](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/linux/jenkins_permissions.sh) for details)\n\n```zsh\n./jenkins_permissions.sh\n```\n\n### Step 7. Become the \"jenkins\" user and enter a \"bash\" shell\n\n```zsh\nsudo su jenkins\nbash\n```\n\n### Step 8. Install dependencies (See [Linuxfile.sh](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/linux/Linuxfile.sh) for details)\n\n```zsh\n./Linuxfile.sh\n```\n\n### Step 9. Start up the headless browser display mechanism: Xvfb (See [Xvfb_launcher.sh](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/linux/Xvfb_launcher.sh) for details)\n\n```zsh\n./Xvfb_launcher.sh\n```\n\n### Step 10. Go to the SeleniumBase directory\n\n```zsh\ncd /SeleniumBase\n```\n\n### Step 11. Install the [requirements](https://github.com/seleniumbase/SeleniumBase/blob/master/requirements.txt) for SeleniumBase\n\n```zsh\nsudo pip install -r requirements.txt --upgrade\n```\n\n### Step 12. Install SeleniumBase (Make sure you already installed the requirements above)\n\n```zsh\nsudo python setup.py develop\n```\n\n### Step 13. Install chromedriver\n\n```zsh\nsudo seleniumbase install chromedriver\n```\n\n### Step 14. Run an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py) in Chrome to verify installation (May take up to 10 seconds)\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_08.png \"Jenkins on Azure\")\n\n----------\n\n```zsh\npytest examples/my_first_test.py --headless --browser=chrome\n```\n\n### Step 15. Secure your Jenkins machine\n\n#### Navigate to http://JENKINS_IP_ADDRESS/jenkins-on-azure/\n\n(Depending on your version of Jenkins, you may see the following screen, or nothing at all.)\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_09.png \"Jenkins on Azure\")\n\n----------\n\n#### Initially, Jenkins uses only ``http``, which makes it less secure.\n\n#### You'll need to set up SSH Port Forwarding in order to secure it.\n\n* **To do this, copy/paste the string and run it in a NEW command prompt on your local machine (NOT from an SSH terminal session), swapping out the username and DNS name with the ones you set up when creating the Jenkins instance in Azure.**\n\n``ssh -L 127.0.0.1:8080:localhost:8080 USERNAME@DNS_NAME``\n\n### Step 16. Login to Jenkins\n\n#### If you've correctly set up SSH Port Forwarding, the url will be ``http://127.0.0.1:8080/``\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_10.png \"Jenkins on Azure\")\n\n----------\n\n#### You'll need to get the password from the SSH terminal on the Linux machine to log in:\n\n```zsh\nsudo cat /var/lib/jenkins/secrets/initialAdminPassword\n```\n\n### Step 17. Customize Jenkins\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_11.png \"Jenkins on Azure\")\n\n----------\n\n### Step 18. Create an Admin user\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_12.png \"Jenkins on Azure\")\n\n----------\n\n#### Once Jenkins has finished loading, the top left of the page should look like this:\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_13.png \"Jenkins on Azure\")\n\n----------\n\n### Step 19. Create a new Jenkins job\n\n* **Click on \"New Item\"**\n* **Give your new Jenkins job a name (ex: \"Test1\")**\n* **Select \"Freestyle project\"**\n* **Click \"OK\"**\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_14.png \"Jenkins on Azure\")\n\n----------\n\n### Step 20. Setup your new Jenkins job\n\n* **Under \"Source Code Management\", select \"Git\".**\n* **For the \"Repository URL\", put: ``https://github.com/seleniumbase/SeleniumBase.git``. (You'll eventually be using your own clone of the repository here.)**\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_15.png \"Jenkins on Azure\")\n\n----------\n\n* **Under \"Build\", click the \"Add build step\" dropdown.**\n* **Select \"Execute shell\".**\n* **For the \"Command\", paste:**\n\n```zsh\ncd examples\npytest my_first_test.py --headless\n```\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_16.png \"Jenkins on Azure\")\n\n----------\n\n#### Click \"Save\" when you're done.\n\n* **You'll see the following page after that:**\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_18.png \"Jenkins on Azure\")\n\n----------\n\n### Step 21. Run your new Jenkins job\n\n* **Click on \"Build Now\"**\n* **(If everything was done correctly, you'll see a blue dot appear after a few seconds, indicating that the test job passed.)**\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_19.png \"Jenkins on Azure\")\n\n----------\n\n### Step 22. See the top Jenkins page for an overview of all jobs\n\n![](https://seleniumbase.github.io/cdn/img/azure/jenkins_on_azure_17.png \"Jenkins on Azure\")\n\n----------\n\n### Step 23. Future Work\n\nIf you have a web application that you want to test, you'll be able to create SeleniumBase tests and add them to Jenkins as you saw here. You may want to create a Deploy job, which downloads the latest version of your repository, and then kicks off all tests to run after that. You could then tell that Deploy job to auto-run whenever a change is pushed to your repository by using: \"Poll SCM\". All your tests would then be able to run by using: \"Build after other projects are built\". \n\n#### Congratulations! You're now well on your way to becoming a build & release / automation engineer!\n"
  },
  {
    "path": "integrations/behave/ReadMe.md",
    "content": "<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> 🐝 Behave test runner for SeleniumBase 🐝</h2>\n\n🐝 (Utilizes the [Behave BDD Python library](https://github.com/behave/behave). For more info, see the [Behave tutorial](https://behave.readthedocs.io/en/stable/tutorial.html) and read about [Behave's Gherkin model](https://behave.readthedocs.io/en/stable/gherkin.html).)\n\n🐝 Behave examples with SeleniumBase: [SeleniumBase/examples/behave_bdd](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd)\n\n```zsh\n> cd examples/behave_bdd/\n> behave features/realworld.feature -T -D dashboard -k\n\nDashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n********************************************************************************\nFeature: SeleniumBase scenarios for the RealWorld App # features/realworld.feature:1\n\n  Scenario: Verify RealWorld App (log in / sign out)  # features/realworld.feature:3\n    Given Open \"seleniumbase.io/realworld/login\"      # ../../sbase/steps.py:10\n    And Clear Session Storage                         # ../../sbase/steps.py:669\n    When Type \"demo_user\" into \"#username\"            # ../../sbase/steps.py:40\n    And Type \"secret_pass\" into \"#password\"           # ../../sbase/steps.py:40\n    And Do MFA \"GAXG2MTEOR3DMMDG\" into \"#totpcode\"    # ../../sbase/steps.py:322\n    Then Assert exact text \"Welcome!\" in \"h1\"         # ../../sbase/steps.py:157\n    And Highlight \"img#image1\"                        # ../../sbase/steps.py:184\n    And Click 'a:contains(\"This Page\")'               # ../../sbase/steps.py:27\n    And Save screenshot to logs                       # ../../sbase/steps.py:239\n    When Click link \"Sign out\"                        # ../../sbase/steps.py:195\n    Then Assert element 'a:contains(\"Sign in\")'       # ../../sbase/steps.py:120\n    And Assert text \"You have been signed out!\"       # ../../sbase/steps.py:145\n   ✅ Scenario Passed!\n\n- Dashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n--- LogPath: /Users/michael/github/SeleniumBase/examples/behave_bdd/latest_logs/\n==================================================================================\n1 feature passed, 0 failed, 0 skipped\n1 scenario passed, 0 failed, 0 skipped\n12 steps passed, 0 failed, 0 skipped, 0 undefined\nTook 0m4.682s\n```\n\n🐝 Another example, which uses higher-level Behave steps to simplify the ``.feature`` file:\n\n```zsh\n> cd examples/behave_bdd/\n> behave features/calculator.feature:61 -T -D dashboard -k\n\nDashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n********************************************************************************\nFeature: SeleniumBase scenarios for the Calculator App # features/calculator.feature:1\n\n  Background:   # features/calculator.feature:3\n\n  Scenario: 7.0 × (3 + 3) = 42        # features/calculator.feature:49\n    Given Open the Calculator App     # features/steps/calculator.py:4\n    When Press C                      # features/steps/calculator.py:9\n    And Press 7                       # features/steps/calculator.py:79\n    And Press .                       # features/steps/calculator.py:104\n    And Press 0                       # features/steps/calculator.py:94\n    And Press ×                       # features/steps/calculator.py:29\n    And Press (                       # features/steps/calculator.py:14\n    And Press 3                       # features/steps/calculator.py:59\n    And Press +                       # features/steps/calculator.py:39\n    And Press 3                       # features/steps/calculator.py:59\n    And Press )                       # features/steps/calculator.py:19\n    Then Verify output is \"7.0×(3+3)\" # features/steps/calculator.py:135\n    When Press =                      # features/steps/calculator.py:44\n    Then Verify output is \"42\"        # features/steps/calculator.py:135\n   ✅ Scenario Passed!\n\n- Dashboard: /Users/michael/github/SeleniumBase/examples/behave_bdd/dashboard.html\n--- LogPath: /Users/michael/github/SeleniumBase/examples/behave_bdd/latest_logs/\n==================================================================================\n1 feature passed, 0 failed, 0 skipped\n1 scenario passed, 0 failed, 8 skipped\n14 steps passed, 0 failed, 60 skipped, 0 undefined\nTook 0m1.672s\n```\n\n🐝⚪ With the Dashboard enabled, you'll get one of these:\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_behave_dashboard.png\" title=\"SeleniumBase\" width=\"600\">\n\n### 🐝 Behave-Gherkin files:\n\n🐝 The ``*.feature`` files can use any step seen from:\n\n```zsh\nbehave --steps-catalog\n```\n\n🐝 SeleniumBase includes several pre-made Behave steps, which you can use by creating a Python file with the following line in your ``features/steps/`` directory:\n\n```python\nfrom seleniumbase.behave import steps  # noqa\n```\n\n🐝 Inside your ``features/environment.py`` file, you should have the following:\n\n```python\nfrom seleniumbase import BaseCase\nfrom seleniumbase.behave import behave_sb\nbehave_sb.set_base_class(BaseCase)  # Accepts a BaseCase subclass\nfrom seleniumbase.behave.behave_sb import before_all  # noqa\nfrom seleniumbase.behave.behave_sb import before_feature  # noqa\nfrom seleniumbase.behave.behave_sb import before_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import before_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import after_feature  # noqa\nfrom seleniumbase.behave.behave_sb import after_all  # noqa\n```\n\n🐝 If you've already created a subclass of ``BaseCase`` with custom methods, you can swap ``BaseCase`` in with your own subclass, which will allow you to easily use your own custom methods in your Behave step definitions.\n\n🐝 Here's an example Python file in the ``features/steps/`` folder:\n\n```python\nfrom behave import step\n\n\n@step(\"Open the Swag Labs Login Page\")\ndef go_to_swag_labs(context):\n    sb = context.sb\n    sb.open(\"https://www.saucedemo.com\")\n    sb.clear_local_storage()\n\n\n@step(\"Login to Swag Labs with {user}\")\ndef login_to_swag_labs(context, user):\n    sb = context.sb\n    sb.type(\"#user-name\", user)\n    sb.type(\"#password\", \"secret_sauce\\n\")\n\n\n@step(\"Verify that the current user is logged in\")\ndef verify_logged_in(context):\n    sb = context.sb\n    sb.assert_element(\"#header_container\")\n    sb.assert_element(\"#react-burger-menu-btn\")\n    sb.assert_element(\"#shopping_cart_container\")\n\n\n@step('Add \"{item}\" to cart')\ndef add_item_to_cart(context, item):\n    sb = context.sb\n    sb.click('div.inventory_item:contains(\"%s\") button[name*=\"add\"]' % item)\n```\n\n🐝 A ``*.feature`` file could look like this:\n\n```gherkin\nFeature: SeleniumBase scenarios for the Swag Labs App\n\n  Background:\n    Given Open the Swag Labs Login Page\n\n  Scenario: User can order a backpack from the store\n    When Login to Swag Labs with standard_user\n    Then Verify that the current user is logged in\n    And Save price of \"Backpack\" to <item_price>\n    When Add \"Backpack\" to Cart\n    Then Verify shopping cart badge shows 1 item(s)\n    When Click on shopping cart icon\n    And Click Checkout\n    And Enter checkout info: First, Last, 12345\n    And Click Continue\n    Then Verify 1 \"Backpack\"(s) in cart\n    And Verify cost of \"Backpack\" is <item_price>\n    And Verify item total is $29.99\n    And Verify tax amount is $2.40\n    And Verify total cost is $32.39\n    When Click Finish\n    Then Verify order complete\n    When Logout from Swag Labs\n    Then Verify on Login page\n```\n\n🐝 Here's another example of a ``*.feature`` file:\n\n```gherkin\nFeature: SeleniumBase scenarios for the RealWorld App\n\n  Scenario: Verify RealWorld App (log in / sign out)\n    Given Open \"seleniumbase.io/realworld/login\"\n    And Clear Session Storage\n    When Type \"demo_user\" into \"#username\"\n    And Type \"secret_pass\" into \"#password\"\n    And Do MFA \"GAXG2MTEOR3DMMDG\" into \"#totpcode\"\n    Then Assert text \"Welcome!\" in \"h1\"\n    And Highlight element \"img#image1\"\n    And Click 'a:contains(\"This Page\")'\n    And Save screenshot to logs\n    When Click link \"Sign out\"\n    Then Assert element 'a:contains(\"Sign in\")'\n    And Assert text \"You have been signed out!\"\n```\n\n🐝 If there's a test failure, that's easy to spot:\n\n```zsh\nFeature: SeleniumBase scenarios for the Fail Page # features/fail_page.feature:1\n\n  Scenario: Fail test on purpose to see what happens  # features/fail_page.feature:3\n    When Open the Fail Page                           # features/steps/fail_page.py:4\n    Then Fail test on purpose                         # features/steps/fail_page.py:9\n      Assertion Failed: This test fails on purpose!\n      Captured stdout:\n      >>> STEP FAILED:  (#2) Fail test on purpose\n      Class / Feature:  SeleniumBase scenarios for the Fail Page\n      Test / Scenario:  Fail test on purpose to see what happens\n\n   ❌ Scenario Failed!\n```\n\n🐝🎖️ For convenience, the [SeleniumBase Behave GUI](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/behave_gui.md) lets you run ``behave`` scripts from a Desktop app.\n\n🐝🎖️ To launch it, call ``sbase behave-gui`` or ``sbase gui-behave``:\n\n```zsh\nsbase behave-gui\n* Starting the SeleniumBase Behave Commander GUI App...\n```\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sbase_behave_gui_wide_5.png\" title=\"SeleniumBase\" width=\"600\">\n\n🐝🎖️ You can customize the tests that show up there:\n\n```zsh\nsbase behave-gui  # all tests\nsbase behave-gui -i=calculator  # tests with \"calculator\" in the name\nsbase behave-gui features/  # tests located in the \"features/\" folder\nsbase behave-gui features/calculator.feature  # tests in that feature\n```\n\n--------\n\n<div>To learn more about SeleniumBase, check out the Docs Site:</div>\n<a href=\"https://seleniumbase.io\">\n<img src=\"https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg\" alt=\"SeleniumBase.io Docs\" /></a>\n\n<div>All the code is on GitHub:</div>\n<a href=\"https://github.com/seleniumbase/SeleniumBase\">\n<img src=\"https://img.shields.io/badge/✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀-02A79E.svg\" alt=\"SeleniumBase on GitHub\" /></a>\n"
  },
  {
    "path": "integrations/behave/behave.ini",
    "content": "[behave]\nshow_skipped=false\nshow_timings=false\n"
  },
  {
    "path": "integrations/behave/features/__init__.py",
    "content": ""
  },
  {
    "path": "integrations/behave/features/behave.ini",
    "content": "[behave]\nshow_skipped=false\nshow_timings=false\n"
  },
  {
    "path": "integrations/behave/features/calculator.feature",
    "content": "Feature: SeleniumBase scenarios for the Calculator App\n\n  Background:\n    Given Open the Calculator App\n\n  Scenario: Pressing \"C\" outputs \"0\"\n    When Press C\n    Then Verify output is \"0\"\n\n  Scenario: 1 + 2 + 3 + 4 + 5 = \"15\"\n    When Press C\n    And Press 1\n    And Press +\n    And Press 2\n    And Press +\n    And Press 3\n    And Press +\n    And Press 4\n    And Press +\n    And Press 5\n    Then Verify output is \"1+2+3+4+5\"\n    When Press =\n    Then Verify output is \"15\"\n\n  Scenario: 6 × 7 × 8 × 9 = \"3024\"\n    When Press C\n    And Press 6\n    And Press ×\n    And Press 7\n    And Press ×\n    And Press 8\n    And Press ×\n    And Press 9\n    Then Verify output is \"6×7×8×9\"\n    When Press =\n    Then Verify output is \"3024\"\n\n  Scenario: 44 - 11 = \"33\"\n    When Press C\n    And Press 4\n    And Press 4\n    And Press -\n    And Press 1\n    And Press 1\n    Then Verify output is \"44-11\"\n    When Press =\n    Then Verify output is \"33\"\n\n  Scenario: 7.0 × (3 + 3) = \"42\"\n    When Press C\n    And Press 7\n    And Press .\n    And Press 0\n    And Press ×\n    And Press (\n    And Press 3\n    And Press +\n    And Press 3\n    And Press )\n    Then Verify output is \"7.0×(3+3)\"\n    When Press =\n    Then Verify output is \"42\"\n\n  Scenario: 4.5 × 68 = \"306\"\n    When Press C\n    And Evaluate [4.5 × 68]\n    Then Verify output is \"306\"\n\n  Scenario Outline: <First> ÷ <Second> = <Result>\n    When Press C\n    And Press [<First>]\n    And Press ÷\n    And Press [<Second>]\n    And Press =\n    Then Verify output is \"<Result>\"\n    Examples:\n      | First | Second | Result |\n      | 1948  | 4      | 487    |\n      | 21    | 0      | Error  |\n\n  Scenario: Save calculator screenshot to logs\n    Given Press [1337]\n    Given Save calculator screenshot to logs\n"
  },
  {
    "path": "integrations/behave/features/environment.py",
    "content": "from seleniumbase import BaseCase\nfrom seleniumbase.behave import behave_sb\nbehave_sb.set_base_class(BaseCase)  # Accepts a BaseCase subclass\nfrom seleniumbase.behave.behave_sb import before_all  # noqa\nfrom seleniumbase.behave.behave_sb import before_feature  # noqa\nfrom seleniumbase.behave.behave_sb import before_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import before_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_step  # noqa\nfrom seleniumbase.behave.behave_sb import after_scenario  # noqa\nfrom seleniumbase.behave.behave_sb import after_feature  # noqa\nfrom seleniumbase.behave.behave_sb import after_all  # noqa\n"
  },
  {
    "path": "integrations/behave/features/fail_page.feature",
    "content": "Feature: SeleniumBase scenarios for the Fail Page\n\n  Scenario: Fail test on purpose to see what happens\n    When Open the Fail Page\n    Then Fail test on purpose\n"
  },
  {
    "path": "integrations/behave/features/realworld.feature",
    "content": "Feature: SeleniumBase scenarios for the RealWorld App\n\n  Scenario: Verify RealWorld App (log in / sign out)\n    Given Open the RealWorld Login Page\n    When Login to the RealWorld App\n    Then Assert exact text \"Welcome!\" in \"h1\"\n    When Highlight element \"img#image1\"\n    And Click element 'a:contains(\"This Page\")'\n    Then Save a screenshot to the logs\n    When Click link \"Sign out\"\n    Then Assert element 'a:contains(\"Sign in\")'\n    And Assert text \"You have been signed out!\"\n"
  },
  {
    "path": "integrations/behave/features/steps/__init__.py",
    "content": ""
  },
  {
    "path": "integrations/behave/features/steps/calculator.py",
    "content": "from behave import step\n\n\n@step(\"Open the Calculator App\")\ndef go_to_calculator(context):\n    context.sb.open(\"https://seleniumbase.io/apps/calculator\")\n\n\n@step(\"Press C\")\ndef press_c(context):\n    context.sb.click(\"button#clear\")\n\n\n@step(\"Press (\")\ndef press_open_paren(context):\n    context.sb.click('button[id=\"(\"]')\n\n\n@step(\"Press )\")\ndef press_close_paren(context):\n    context.sb.click('button[id=\")\"]')\n\n\n@step(\"Press ÷\")\ndef press_divide(context):\n    context.sb.click(\"button#divide\")\n\n\n@step(\"Press ×\")\ndef press_multiply(context):\n    context.sb.click(\"button#multiply\")\n\n\n@step(\"Press -\")\ndef press_subtract(context):\n    context.sb.click(\"button#subtract\")\n\n\n@step(\"Press +\")\ndef press_add(context):\n    context.sb.click(\"button#add\")\n\n\n@step(\"Press =\")\ndef press_equal(context):\n    context.sb.click(\"button#equal\")\n\n\n@step(\"Press 1\")\ndef press_1(context):\n    context.sb.click('button[id=\"1\"]')\n\n\n@step(\"Press 2\")\ndef press_2(context):\n    context.sb.click('button[id=\"2\"]')\n\n\n@step(\"Press 3\")\ndef press_3(context):\n    context.sb.click('button[id=\"3\"]')\n\n\n@step(\"Press 4\")\ndef press_4(context):\n    context.sb.click('button[id=\"4\"]')\n\n\n@step(\"Press 5\")\ndef press_5(context):\n    context.sb.click('button[id=\"5\"]')\n\n\n@step(\"Press 6\")\ndef press_6(context):\n    context.sb.click('button[id=\"6\"]')\n\n\n@step(\"Press 7\")\ndef press_7(context):\n    context.sb.click('button[id=\"7\"]')\n\n\n@step(\"Press 8\")\ndef press_8(context):\n    context.sb.click('button[id=\"8\"]')\n\n\n@step(\"Press 9\")\ndef press_9(context):\n    context.sb.click('button[id=\"9\"]')\n\n\n@step(\"Press 0\")\ndef press_0(context):\n    context.sb.click('button[id=\"0\"]')\n\n\n@step(\"Press ←\")\ndef press_delete(context):\n    context.sb.click(\"button#delete\")\n\n\n@step(\"Press .\")\ndef press_dot(context):\n    context.sb.click('button[id=\".\"]')\n\n\n@step(\"Press [{number}]\")\ndef enter_number_into_calc(context, number):\n    sb = context.sb\n    for digit in number:\n        sb.click('button[id=\"%s\"]' % digit)\n\n\n@step(\"Evaluate [{equation}]\")\ndef evaluate_equation(context, equation):\n    sb = context.sb\n    for key in equation:\n        if key == \" \":\n            continue\n        elif key == \"÷\":\n            sb.click(\"button#divide\")\n        elif key == \"×\":\n            sb.click(\"button#multiply\")\n        elif key == \"-\":\n            sb.click(\"button#subtract\")\n        elif key == \"+\":\n            sb.click(\"button#add\")\n        else:\n            sb.click('button[id=\"%s\"]' % key)\n    sb.click(\"button#equal\")\n\n\n@step('Verify output is \"{output}\"')\ndef verify_output(context, output):\n    sb = context.sb\n    sb.assert_exact_text(output, \"#output\")\n\n\n@step(\"Save calculator screenshot to logs\")\ndef save_calculator_screenshot_to_logs(context):\n    sb = context.sb\n    sb.save_screenshot_to_logs()\n"
  },
  {
    "path": "integrations/behave/features/steps/fail_page.py",
    "content": "from behave import step\r\n\r\n\r\n@step(\"Open the Fail Page\")\r\ndef go_to_error_page(context):\r\n    context.sb.open(\"https://seleniumbase.io/error_page/\")\r\n\r\n\r\n@step(\"Fail test on purpose\")\r\ndef fail_on_purpose(context):\r\n    context.sb.fail(\"This test fails on purpose!\")\r\n"
  },
  {
    "path": "integrations/behave/features/steps/real_world.py",
    "content": "from behave import step\r\n\r\n\r\n@step(\"Open the RealWorld Login Page\")\r\ndef go_to_realworld(context):\r\n    sb = context.sb\r\n    context.sb.open(\"https://seleniumbase.io/realworld/login\")\r\n    sb.clear_session_storage()\r\n\r\n\r\n@step(\"Login to the RealWorld App\")\r\ndef login_to_realworld(context):\r\n    sb = context.sb\r\n    sb.type(\"#username\", \"demo_user\")\r\n    sb.type(\"#password\", \"secret_pass\")\r\n    sb.enter_mfa_code(\"#totpcode\", \"GAXG2MTEOR3DMMDG\")  # 6-digit\r\n\r\n\r\n@step('Highlight element {selector}')\r\ndef highlight(context, selector):\r\n    if selector.startswith('\"') or selector.startswith(\"'\"):\r\n        selector = selector[1:]\r\n    if selector.endswith('\"') or selector.endswith(\"'\"):\r\n        selector = selector[:-1]\r\n    sb = context.sb\r\n    sb.highlight(selector)\r\n\r\n\r\n@step('Click element {selector}')\r\ndef click(context, selector):\r\n    if selector.startswith('\"') or selector.startswith(\"'\"):\r\n        selector = selector[1:]\r\n    if selector.endswith('\"') or selector.endswith(\"'\"):\r\n        selector = selector[:-1]\r\n    sb = context.sb\r\n    sb.click(selector)\r\n\r\n\r\n@step('Click link {link}')\r\ndef click_link(context, link):\r\n    if link.startswith('\"') or link.startswith(\"'\"):\r\n        link = link[1:]\r\n    if link.endswith('\"') or link.endswith(\"'\"):\r\n        link = link[:-1]\r\n    sb = context.sb\r\n    sb.click_link(link)\r\n\r\n\r\n@step('Save a screenshot to the logs')\r\ndef save_screenshot_to_logs(context):\r\n    sb = context.sb\r\n    sb.save_screenshot_to_logs()\r\n\r\n\r\n@step('Assert element {selector}')\r\ndef assert_element(context, selector):\r\n    if selector.startswith('\"') or selector.startswith(\"'\"):\r\n        selector = selector[1:]\r\n    if selector.endswith('\"') or selector.endswith(\"'\"):\r\n        selector = selector[:-1]\r\n    sb = context.sb\r\n    sb.assert_element(selector)\r\n\r\n\r\n@step('Assert text {text} in {selector}')\r\ndef assert_text_in_selector(context, text, selector):\r\n    if text.startswith('\"') or text.startswith(\"'\"):\r\n        text = text[1:]\r\n    if text.endswith('\"') or text.endswith(\"'\"):\r\n        text = text[:-1]\r\n    if selector.startswith('\"') or selector.startswith(\"'\"):\r\n        selector = selector[1:]\r\n    if selector.endswith('\"') or selector.endswith(\"'\"):\r\n        selector = selector[:-1]\r\n    sb = context.sb\r\n    sb.assert_text(text, selector)\r\n\r\n\r\n@step('Assert text {text}')\r\ndef assert_text(context, text):\r\n    if text.startswith('\"') or text.startswith(\"'\"):\r\n        text = text[1:]\r\n    if text.endswith('\"') or text.endswith(\"'\"):\r\n        text = text[:-1]\r\n    sb = context.sb\r\n    sb.assert_text(text)\r\n\r\n\r\n@step('Assert exact text {text} in {selector}')\r\ndef assert_exact_text(context, text, selector):\r\n    if text.startswith('\"') or text.startswith(\"'\"):\r\n        text = text[1:]\r\n    if text.endswith('\"') or text.endswith(\"'\"):\r\n        text = text[:-1]\r\n    if selector.startswith('\"') or selector.startswith(\"'\"):\r\n        selector = selector[1:]\r\n    if selector.endswith('\"') or selector.endswith(\"'\"):\r\n        selector = selector[:-1]\r\n    sb = context.sb\r\n    sb.assert_exact_text(text, selector)\r\n"
  },
  {
    "path": "integrations/behave/features/steps/swag_labs.py",
    "content": "from behave import step\n\n\n@step(\"Open the Swag Labs Login Page\")\ndef go_to_swag_labs(context):\n    sb = context.sb\n    sb.open(\"https://www.saucedemo.com\")\n    sb.clear_local_storage()\n\n\n@step(\"Login to Swag Labs with {user}\")\ndef login_to_swag_labs(context, user):\n    sb = context.sb\n    sb.type(\"#user-name\", user)\n    sb.type(\"#password\", \"secret_sauce\\n\")\n\n\n@step(\"Verify that the current user is logged in\")\ndef verify_logged_in(context):\n    sb = context.sb\n    sb.assert_element(\"#header_container\")\n    sb.assert_element(\"#react-burger-menu-btn\")\n    sb.assert_element(\"#shopping_cart_container\")\n\n\n@step('Add \"{item}\" to cart')\ndef add_item_to_cart(context, item):\n    sb = context.sb\n    sb.click('div.inventory_item:contains(\"%s\") button[name*=\"add\"]' % item)\n\n\n@step('Save price of \"{item}\" to <{var}>')\ndef save_price_of_item(context, item, var):\n    sb = context.sb\n    price = sb.get_text(\n        'div.inventory_item:contains(\"%s\") .inventory_item_price' % item\n    )\n    sb.variables[var] = price\n\n\n@step('Remove \"{item}\" from cart')\ndef remove_item_to_cart(context, item):\n    sb = context.sb\n    sb.click('div.inventory_item:contains(\"%s\") button[name*=\"remove\"]' % item)\n\n\n@step(\"Verify shopping cart badge shows {number} item(s)\")\ndef verify_badge_number(context, number):\n    sb = context.sb\n    sb.assert_exact_text(number, \"span.shopping_cart_badge\")\n\n\n@step(\"Verify shopping cart badge is missing\")\ndef verify_badge_missing(context):\n    sb = context.sb\n    sb.assert_element_not_visible(\"span.shopping_cart_badge\")\n\n\n@step(\"Click on shopping cart icon\")\ndef click_shopping_cart(context):\n    sb = context.sb\n    sb.click(\"#shopping_cart_container a\")\n\n\n@step(\"Click Checkout\")\ndef click_checkout(context):\n    sb = context.sb\n    sb.click(\"#checkout\")\n\n\n@step(\"Enter checkout info: {first_name}, {last_name}, {zip_code}\")\ndef enter_checkout_info(context, first_name, last_name, zip_code):\n    sb = context.sb\n    sb.type(\"#first-name\", first_name)\n    sb.type(\"#last-name\", last_name)\n    sb.type(\"#postal-code\", zip_code)\n\n\n@step(\"Click Continue\")\ndef click_continue(context):\n    sb = context.sb\n    sb.click(\"input#continue\")\n\n\n@step('Verify {quantity} \"{item}\"(s) in cart')\ndef verify_item_in_cart(context, quantity, item):\n    sb = context.sb\n    sb.assert_exact_text(\n        quantity,\n        'div.cart_item:contains(\"%s\") div.cart_quantity' % item\n    )\n\n\n@step('Verify cost of \"{item}\" is <{var}>')\ndef verify_cost_of_item(context, item, var):\n    sb = context.sb\n    earlier_price = sb.variables[var]\n    sb.assert_exact_text(\n        earlier_price,\n        'div.cart_item_label:contains(\"%s\") .inventory_item_price' % item\n    )\n\n\n@step(\"Verify item total is {item_total}\")\ndef verify_item_total(context, item_total):\n    sb = context.sb\n    sb.assert_exact_text(\n        \"Item total: %s\" % item_total,\n        \"div.summary_subtotal_label\",\n        timeout=1\n    )\n\n\n@step(\"Verify tax amount is {tax_amount}\")\ndef verify_tax_amount(context, tax_amount):\n    sb = context.sb\n    sb.assert_exact_text(\n        \"Tax: %s\" % tax_amount,\n        \"div.summary_tax_label\",\n        timeout=1\n    )\n\n\n@step(\"Verify total cost is {total_cost}\")\ndef verify_total_cost(context, total_cost):\n    sb = context.sb\n    sb.assert_exact_text(\n        \"Total: %s\" % total_cost,\n        \"div.summary_total_label\",\n        timeout=1\n    )\n\n\n@step(\"Click Finish\")\ndef click_finish(context):\n    sb = context.sb\n    sb.click(\"button#finish\")\n\n\n@step(\"Verify order complete\")\ndef verify_order_complete(context):\n    sb = context.sb\n    sb.assert_exact_text(\"Thank you for your order!\", \"h2\")\n    sb.assert_element('img[alt=\"Pony Express\"]')\n\n\n@step(\"Logout from Swag Labs\")\ndef logout_from_swag_labs(context):\n    sb = context.sb\n    sb.js_click(\"a#logout_sidebar_link\")\n\n\n@step(\"Verify on Login page\")\ndef verify_on_login_page(context):\n    sb = context.sb\n    sb.assert_element(\"#login-button\")\n\n\n@step(\"Sort items from Z to A\")\ndef sort_items_from_z_to_a(context):\n    sb = context.sb\n    sb.select_option_by_text(\"select.product_sort_container\", \"Name (Z to A)\")\n\n\n@step('Verify \"{item}\" on top')\ndef verify_item_on_top(context, item):\n    sb = context.sb\n    sb.assert_text(item, \"div.inventory_item_name\")\n"
  },
  {
    "path": "integrations/behave/features/swag_labs.feature",
    "content": "Feature: SeleniumBase scenarios for the Swag Labs App\n\n  Background:\n    Given Open the Swag Labs Login Page\n\n  Scenario: User can log in and log out successfully\n    When Login to Swag Labs with standard_user\n    Then Verify that the current user is logged in\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can order a backpack from the store\n    When Login to Swag Labs with standard_user\n    Then Verify that the current user is logged in\n    And Save price of \"Backpack\" to <item_price>\n    When Add \"Backpack\" to Cart\n    Then Verify shopping cart badge shows 1 item(s)\n    When Click on shopping cart icon\n    And Click Checkout\n    And Enter checkout info: First, Last, 12345\n    And Click Continue\n    Then Verify 1 \"Backpack\"(s) in cart\n    And Verify cost of \"Backpack\" is <item_price>\n    And Verify item total is $29.99\n    And Verify tax amount is $2.40\n    And Verify total cost is $32.39\n    When Click Finish\n    Then Verify order complete\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can order two items from the store\n    When Login to Swag Labs with standard_user\n    And Add \"Bike Light\" to Cart\n    And Add \"Fleece Jacket\" to Cart\n    Then Verify shopping cart badge shows 2 item(s)\n    When Click on shopping cart icon\n    And Click Checkout\n    And Enter checkout info: First, Last, 54321\n    And Click Continue\n    Then Verify 1 \"Bike Light\"(s) in cart\n    Then Verify 1 \"Fleece Jacket\"(s) in cart\n    And Verify item total is $59.98\n    And Verify tax amount is $4.80\n    And Verify total cost is $64.78\n    When Click Finish\n    Then Verify order complete\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can sort items by name from Z to A\n    When Login to Swag Labs with standard_user\n    And Sort items from Z to A\n    Then Verify \"Test.allTheThings() T-Shirt\" on top\n    When Logout from Swag Labs\n    Then Verify on Login page\n\n  Scenario: User can add & remove 6 items to/from cart\n    When Login to Swag Labs with standard_user\n    And Add \"Backpack\" to Cart\n    And Add \"Bike Light\" to Cart\n    And Add \"Bolt T-Shirt\" to Cart\n    And Add \"Fleece Jacket\" to Cart\n    And Add \"Onesie\" to Cart\n    And Add \"Test.allTheThings() T-Shirt\" to Cart\n    Then Verify shopping cart badge shows 6 item(s)\n    When Remove \"Backpack\" from Cart\n    And Remove \"Bike Light\" from Cart\n    And Remove \"Bolt T-Shirt\" from Cart\n    And Remove \"Fleece Jacket\" from Cart\n    And Remove \"Onesie\" from Cart\n    And Remove \"Test.allTheThings() T-Shirt\" from Cart\n    Then Verify shopping cart badge is missing\n    When Logout from Swag Labs\n    Then Verify on Login page\n"
  },
  {
    "path": "integrations/brython/ReadMe.md",
    "content": "## Getting Started with Brython\n\n* Brython was designed for replacing JavaScript with Python.\n* This tutorial will show you how to get started quickly.\n\n### Here's the web app you'll be creating:\n\n<img src=\"https://seleniumbase.github.io/cdn/img/brython_demo.png\" alt=\"Brython Demo\" title=\"Brython Demo\" width=\"410\" />\n\n### 0. Install ``brython``:\n\n```zsh\npip install brython\n```\n\n### 1. Get a web server up and running:\n\n<i>Run from [SeleniumBase/integrations/brython](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/brython)</i>:\n\n```zsh\npython -m http.server\n```\n\n(You can stop the server by using <kbd>Ctrl+C</kbd>)\n\n### 2. Navigate to [http://localhost:8000/](http://localhost:8000/)\n\nNow click on the examples to see Brython in action.\n\n### 3. For more info, see the following:\n\n* https://brython.info/\n* https://pypi.org/project/brython/\n* https://github.com/brython-dev/brython\n"
  },
  {
    "path": "integrations/brython/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=windows-1252\"><meta charset=\"iso-8859-1\">\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css\" />\n<style>\n    body,td,th,h1{\n        font-family:sans-serif;\n    }\n    td,h1 {\n        border-style:solid;\n        border-width: 6px 6px 6px 6px;\n        border-color: #FFF;\n        padding: 8px;\n    }\n    th {\n        border-style: none;\n        border-width: 6px;\n        border-color: #FFF;\n        background-color: #91E7B4;\n        padding: 8px;\n    }\n</style>\n<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/brython/3.10.4/brython.js\"></script>\n</head>\n<body onload=\"brython()\">\n<table>\n    <tbody>\n        <h1 id=\"topHeader\"></h1>\n        <td>\n            <th>\n                <h2>\n                    <a href=\"library.html\">Library / Indexed DB Example</a>\n                </h2>\n            </th>\n        </td>\n    </tbody>\n</table>\n<script type=\"text/python\">\n      import index\n      index.setup_page()\n</script>\n</body>\n</html>"
  },
  {
    "path": "integrations/brython/index.py",
    "content": "from browser import document\n\n\ndef setup_page():\n    document[\"topHeader\"].textContent = \"Brython Examples:\"\n"
  },
  {
    "path": "integrations/brython/library.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta http-equiv=\"content-type\" content=\"text/html; charset=windows-1252\"><meta charset=\"iso-8859-1\">\n        <style>\n            body,td,th{\n                font-family:sans-serif;\n                font-size:12px;\n            }\n            td {\n                border-style:solid;\n                border-width: 0px 0px 1px 0px;\n                border-color: #000;\n                padding:3px;\n            }\n            th {\n                border-style:solid;\n                border-width: 1px;\n                border-color: #000;\n                background-color: #61D7A4;\n                padding: 4px;\n            }\n        </style>\n        <script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/brython/3.10.4/brython.js\"></script>\n    </head>\n    <body onload=\"brython(1)\">\n        <script id=\"ascript\" type=\"text/python\">\n        from browser import document, window, html\n\n        IDB = window.indexedDB\n\n        def create_db(*args):\n              # The database did not previously exist, so create object stores and indexes.\n              print('create db')\n              db = request.result\n              store = db.createObjectStore(\"books\", {\"keyPath\": \"isbn\"})\n              titleIndex = store.createIndex(\"by_title\", \"title\", {\"unique\": True})\n              authorIndex = store.createIndex(\"by_author\", \"author\")\n\n              # Populate with initial data.\n              store.put({\"title\": \"Quarry Memories\", \"author\": \"Fred\", \"isbn\": 123456})\n              store.put({\"title\": \"Water Buffaloes\", \"author\": \"Fred\", \"isbn\": 234567})\n              store.put({\"title\": \"Bedrock Nights\", \"author\": \"Barney\", \"isbn\": 345678})\n\n\n        def btn_click(ev):\n            \"\"\"Generic callback function for buttons\n            \"\"\"\n            # The text on the button indicates the action: Add, Edit, Update or Delete\n            action = ev.target.text\n            \n            # table row of the clicked button\n            row = ev.target.parent.parent\n\n            if action == \"Delete\":\n                db = request.result\n                tx = db.transaction(\"books\", \"readwrite\")\n                store = tx.objectStore(\"books\")\n                cursor = store.delete(row.key)\n\n                # when record is deleted, update table\n                cursor.bind(\"success\", show)    \n\n            elif action == \"Add\":\n                values = [entry.value for entry in row.get(selector=\"INPUT\")]\n                data = dict(zip(['title', 'author', 'isbn'], values))\n                db = request.result\n                tx = db.transaction(\"books\", \"readwrite\")\n                store = tx.objectStore(\"books\")\n                if action == \"Add\":\n                    cursor = store.put(data)\n                else:\n                    cursor = store.put(data, row.key)\n                \n                # when record is added, update table\n                cursor.bind(\"success\", show)\n\n            elif action == \"Edit\":\n                # Replace cells for \"title\" and \"author\" by INPUT fields\n                # Since isbn is he keyPath it can't be edited\n                cells = row.get(selector=\"TD\")\n                for cell in cells[:-2]:\n                    value = cell.text\n                    cell.clear()\n                    cell <= html.INPUT(value=value)\n\n                # Replace buttons \"Edit\" and \"Delete\" by button \"Update\"\n                cells[-1].clear()\n                update_btn = html.BUTTON(\"Update\")\n                cells[-1] <= update_btn\n\n                # Bind its \"click\" event\n                update_btn.bind(\"click\", btn_click)\n\n            elif action == \"Update\":\n                values = [entry.value for entry in row.select(\"INPUT\")]\n                data = dict(zip([\"title\", \"author\"], values))\n                data['isbn'] = row.key # required for the \"store.put\" method below\n\n                db = request.result\n                tx = db.transaction(\"books\", \"readwrite\")\n                store = tx.objectStore(\"books\")\n                cursor = store.put(data)\n                \n                # When record is added, update table\n                cursor.bind(\"success\", show)\n\n\n        def show(ev):\n            \"\"\"Show the contents of store \"books\" in a table\"\"\"\n            db = request.result\n            tx = db.transaction(\"books\", \"readonly\")\n            store = tx.objectStore(\"books\")\n            cursor = store.openCursor()\n            \n            # clear table\n            document[\"table\"].clear()\n            \n            # headers\n            t = html.TABLE()\n            document[\"table\"] <= t\n            \n            t <= html.TR(html.TH(x) for x in [\"Title\", \"Author\", \"ISBN\", \"Actions\"])\n            \n            def add_row(ev):\n                \"\"\"Add a row to the table for each iteration on cursor\n                When cursor in empty, add a line for new record insertion\n                \"\"\"\n                res = ev.target.result\n                if res:\n                    v = res.value\n                    row = html.TR()\n                    row <= (html.TD(getattr(v, key)) \n                        for key in [\"title\", \"author\", \"isbn\"])\n                    row <= html.TD(html.BUTTON(\"Edit\")+\n                        html.BUTTON(\"Delete\"))\n                    row.key = res.key\n                    t <= row\n                    getattr(res, \"continue\")()\n                else:\n                    # add empty row\n                    row = html.TR()\n                    row <= (html.TD(html.INPUT(name=\"new_%s\" %key))\n                        for key in [\"title\", \"author\", \"isbn\"])\n                    row <= html.TD(html.BUTTON(\"Add\"))\n                    t <= row\n                    # bind all buttons\n                    for btn in t.get(selector=\"BUTTON\"):\n                        btn.bind(\"click\", btn_click)\n                \n            cursor.bind(\"success\", add_row)\n\n\n        request = IDB.open(\"library\")\n\n        # If database doesn't exist, create it\n        request.bind(\"upgradeneeded\", create_db)\n\n        # Else print a table with all elements in table \"books\"\n        request.bind(\"success\", show)\n        </script>\n\n        <h1>Library</h1>\n\n        <div id=\"table\"><table><tr><th>Title</th><th>Author</th><th>ISBN</th><th>Actions</th></tr><tr><td><input name=\"new_title\"></td><td><input name=\"new_author\"></td><td><input name=\"new_isbn\"></td><td><button>Add</button></td></tr></table></div>\n\n        <style>\n            /* colors for highlighted Python code */\n            span.python-string{\n                color: #27d;\n            }\n            span.python-comment{\n                color: #019;\n            }\n            span.python-keyword{\n                color: #950;\n            }\n            span.python-builtin{\n                color: #183;\n            }\n            em {\n              color:#339;\n              font-family:courier\n            }\n            strong {\n              color:#339;\n              font-family:courier;\n            }\n            button.nice{\n                margin-right: 15%;\n                color: #fff;\n                background: #7ae;\n                border-width: 2px;\n                border-style: solid;\n                border-radius: 5px;\n                border-color: #45b;\n                text-align: center;\n                font-size: 15px;\n                padding: 6px;\n            }\n        </style>\n    </body>\n</html>"
  },
  {
    "path": "integrations/docker/ReadMe.md",
    "content": "## Docker setup instructions for SeleniumBase\n\n#### 1. Install the Docker Desktop:\n\nYou can get that from here:\nhttps://www.docker.com/products/docker-desktop/\n\nYou might also want to install the Docker Engine:\nhttps://docs.docker.com/engine/install/\n\n#### 2. Go to the SeleniumBase home directory on the command line, which is where [Dockerfile](https://github.com/seleniumbase/SeleniumBase/blob/master/Dockerfile) is located. (This assumes you've already cloned the SeleniumBase repo.)\n\n#### 3. Create your Docker image from your Dockerfile: (Get ready to wait awhile)\n\n**(Windows / Linux / Intel macOS)**\n\n    docker build -t seleniumbase .\n\n**(Apple Silicon macOS, eg. M1/M2/M3/M4):**\n\nUsers should first [Enable Rosetta in Docker Desktop](https://stackoverflow.com/a/76586216/7058266). (Otherwise Chrome will crash on launch with errors such as: `\"InvalidSessionIdException\"` and `\"Unable to receive message from renderer\"`)\n\n<img width=\"420\" alt=\"Enable Rosetta\" src=\"https://seleniumbase.github.io/other/docker_rosetta.jpg\" />\n\nThen you can run these commands:\n\n    export DOCKER_DEFAULT_PLATFORM=linux/amd64\n\n    docker build --platform linux/amd64 -t seleniumbase .\n\n#### 4. Run [the example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py) with Chrome inside your Docker: (Once the test completes after a few seconds, you'll automatically exit the Docker shell)\n\n    docker run seleniumbase ./run_docker_test_in_chrome.sh\n\n#### 5. You can also enter Docker and stay inside the shell:\n\n    docker run -i -t seleniumbase\n\n#### 6. Now you can run the example test from inside the Docker shell:\n\n    ./run_docker_test_in_chrome.sh\n\n#### 7. When you're satisfied, you may exit the Docker shell:\n\n    exit\n\n#### 8. (Optional) Since Docker images and containers take up a lot of space, you may want to clean up your machine from time to time when they’re not being used:\n\nDetails on that can be found here:\nhttp://stackoverflow.com/questions/17236796/how-to-remove-old-docker-containers\n\nHere are a few of those cleanup commands:\n\n    docker container prune\n    docker system prune\n    docker images | grep \"<none>\" | awk '{print $3}' | xargs docker rmi\n    docker rm 'docker ps --no-trunc -aq'\n\nIf you want to completely remove all of your Docker containers and images, use these commands: (If there's nothing to delete, those commands will return an error.)\n\n    docker rm -f $(docker ps -a -q)\n    docker rmi -f $(docker images -q)\n\nFor more cleanup commands, check out:\nhttps://codefresh.io/blog/everyday-hacks-docker/\n\n#### 9. (Optional) More reading on Docker can be found here:\n* https://docs.docker.com\n* https://docs.docker.com/get-started/\n* https://docs.docker.com/docker-for-mac/\n"
  },
  {
    "path": "integrations/docker/docker-entrypoint.sh",
    "content": "#!/bin/bash\nset -e\necho \"***** SeleniumBase Docker Machine *****\"\nexec \"$@\"\n"
  },
  {
    "path": "integrations/docker/run_docker_test_in_chrome.sh",
    "content": "#!/bin/bash\nset -e\n# Run example test from inside Docker image\necho \"Running example SeleniumBase test from Docker with headless Chrome...\"\ncd /SeleniumBase/examples/ && pytest my_first_test.py --browser=chrome --headless\nexec \"$@\"\n"
  },
  {
    "path": "integrations/github/ReadMe.md",
    "content": "### <img src=\"https://seleniumbase.github.io/img/logo3a.png\" title=\"SeleniumBase\" width=\"32\" /> GitHub Integrations\n\n> **Table of Contents / Navigation:**\n> - [**Actions/Workflows**](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/github/workflows/ReadMe.md)\n> - [**Extras/Action-Integrations**](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/github/workflows/extras.md)\n"
  },
  {
    "path": "integrations/github/workflows/ReadMe.md",
    "content": "## Running browser tests on [GitHub Actions](https://github.com/seleniumbase/SeleniumBase/actions) with [SeleniumBase](https://github.com/seleniumbase/SeleniumBase)\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_7.png \"GitHub Actions\")\n\n----------\n\n### Step 0. Create a fork of [SeleniumBase](https://github.com/seleniumbase/SeleniumBase) on GitHub to help you get started.\n\n* **(You'll be using your own repo eventually.)**\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_2.png \"Create a fork of SeleniumBase\")\n\n----------\n\n### Step 1. From the GitHub Actions tab, choose to set up a Python package Workflow.\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_1.png \"GitHub Actions\")\n\n----------\n\n### Step 2. Add your workflow ``.yml`` script.\n\n* **(If using a SeleniumBase fork, the script from https://github.com/seleniumbase/SeleniumBase/blob/master/.github/workflows/python-package.yml already exists to help guide you.)**\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_9.png \"GitHub Actions\")\n\n### Step 3. Commit your changes to GitHub.\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_4.png \"GitHub Actions\")\n\n----------\n\n### Step 4. Your tests will now run on every pull request and on every commit to the ``master`` branch.\n\n* **(See https://github.com/seleniumbase/SeleniumBase/actions for the SeleniumBase example.)**\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_5.png \"GitHub Actions\")\n\n* **(You can click inside each build for more details.)**\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_6.png \"GitHub Actions\")\n\n* **(You can also see the specific steps being performed by each command.)**\n\n![](https://seleniumbase.github.io/cdn/img/gha/github_workflows_7.png \"GitHub Actions\")\n\n* **(You'll notice that web browsers such as Chrome and Firefox get installed for tests to use. SeleniumBase uses pytest for running tests while using Selenium to interact with web browsers.)**\n\n----------\n\n### Congratulations! You now know how to create and run browser tests with GitHub Actions!\n\n### **Study [SeleniumBase](https://github.com/seleniumbase/SeleniumBase) to learn more!**\n"
  },
  {
    "path": "integrations/github/workflows/extras.md",
    "content": "<h3><img src=\"https://seleniumbase.github.io/img/logo3a.png\" title=\"SeleniumBase\" width=\"32\" /> Integrations for GitHub Actions:</h3>\n\n### Uploading Artifacts:\n* Here's an example using [upload-artifact@v6](https://github.com/actions/upload-artifact) to push up a SeleniumBase-generated artifact.\n\n```yml\n    - uses: actions/upload-artifact@v6\n      with:\n        name: Click to download the presentation\n        path: saved_presentations/my_presentation.html\n```\n\n### Slack Notifications - [rtCamp/action-slack-notify](https://github.com/rtCamp/action-slack-notify) can be used to send notifications to Slack.\n\n<b>Usage:</b>\n* Create a slack integration webhook if you don't have one already.\n* Create a ``SLACK_WEBHOOK`` secret on your repository with the webhook token value.\n* For this particular action, ``SLACK_CHANNEL`` is an optional environment variable that defaults to the webhook token channel if not specified.\n* The following example shows how to put a link to your workflow as the ``SLACK_MESSAGE`` (Lets you see artifacts pushed up, such as from the SeleniumBase Presenter feature!):\n\n```yml\n    - name: Slack notification\n      uses: rtCamp/action-slack-notify@master\n      env:\n        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}\n        SLACK_CHANNEL: general\n        SLACK_ICON_EMOJI: rocket\n        SLACK_USERNAME: SeleniumBase\n        SLACK_MESSAGE: 'Actions workflow completed successful! :tada:  https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'\n```\n"
  },
  {
    "path": "integrations/google_cloud/ReadMe.md",
    "content": "### Building a browser-based test automation server on the [Google Cloud Platform](https://cloud.google.com/) by using [SeleniumBase](https://github.com/seleniumbase/SeleniumBase)\n\n(This tutorial, [from a previous Google Cloud Meetup](https://www.meetup.com/Boston-Google-Cloud-Meetup/events/230839686/?showDescription=true), will teach you how to setup a Linux server for running automated browser tests. The cost of running this server is about [$13.60/month on Google Cloud](https://console.cloud.google.com/launcher/details/bitnami-launchpad/jenkins) (enough to handle **5 parallel tests**). This is less expensive than using other platforms.)\n\n<!-- YouTube View --><a href=\"https://www.youtube.com/watch?v=n-sno20R9P0\"><img src=\"https://seleniumbase.github.io/other/gcp_video_thumb.png\" title=\"SeleniumBase on YouTube\" width=\"380\" /></a>\n<!-- GitHub Only --><p>(<b><a href=\"https://www.youtube.com/watch?v=n-sno20R9P0\">SeleniumBase Google Cloud Video</a></b>)</p>\n\n#### Step 1. Open the Google Cloud Platform Cloud Launcher\n\n* Navigate to [https://console.cloud.google.com/launcher](https://console.cloud.google.com/launcher)\n* (If you already have an active Google Cloud project, the Google Cloud Launcher will probably default to using that. If you don't, [sign up for the free trial of Google Cloud Platform here](https://console.cloud.google.com/freetrial) to get started.)\n\n#### Step 2. Launch a Jenkins instance\n\n![](https://seleniumbase.github.io/cdn/img/gcp/gcp_cloud_launcher_jenkins.png \"Finding Jenkins\")\n\n* Under \"Cloud Launcher\", Click on \"Jenkins Certified by Bitnami\"\n* Click on \"Launch on Compute Engine\"\n* Give the instance a name\n* Give the instance a zone\n* Click \"Create\"\n\n#### Step 3. Connect with your new Jenkins instance\n\n![](https://seleniumbase.github.io/cdn/img/gcp/gcp_ssh.png \"SSH into your Jenkins instance\")\n\n* SSH into your new instance by selecting: \"SSH\" => \"Open in browser window\" from the instance page.\n\n#### Step 4. Clone the SeleniumBase repository from the root (\"/\") directory.\n\n```zsh\ncd /\nsudo git clone https://github.com/seleniumbase/SeleniumBase.git\n```\n\n#### Step 5. Enter the \"linux\" folder\n\n```zsh\ncd SeleniumBase/integrations/linux/\n```\n\n#### Step 6. Give Jenkins (aka \"tomcat\" user) sudo access (See [tomcat_permissions.sh](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/linux/tomcat_permissions.sh) for details)\n\n```zsh\n./tomcat_permissions.sh\n```\n\n#### Step 7. Become \"tomcat\" (the Jenkins user) and enter a \"bash\" shell\n\n```zsh\nsudo su tomcat\nbash\n```\n\n#### Step 8. Install dependencies (See [Linuxfile.sh](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/linux/Linuxfile.sh) for details)\n\n```zsh\n./Linuxfile.sh\n```\n\n#### Step 9. Start up the headless browser display mechanism: Xvfb (See [Xvfb_launcher.sh](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/linux/Xvfb_launcher.sh) for details)\n\n```zsh\n./Xvfb_launcher.sh\n```\n\n#### Step 10. Go to the SeleniumBase directory\n\n```zsh\ncd /SeleniumBase\n```\n\n#### Step 11. Install the [requirements](https://github.com/seleniumbase/SeleniumBase/blob/master/requirements.txt) for SeleniumBase\n\n```zsh\nsudo pip install -r requirements.txt --upgrade\n```\n\n#### Step 12. Install SeleniumBase\n\n```zsh\nsudo python setup.py develop\n```\n\n#### Step 13. Run an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py) on Chrome to verify installation (May take up to 10 seconds)\n\n![](https://seleniumbase.github.io/cdn/img/gcp/gcp_bitnami.png \"Linux SSH Terminal\")\n\n```zsh\npytest examples/my_first_test.py --headless\n```\n\n#### Step 14. If you prefer using nosetests, that works too\n\n```zsh\nnosetests examples/my_first_test.py --headless\n```\n\n#### Step 15. You can also verify that the example test runs on Firefox\n\n```zsh\npytest examples/my_first_test.py --headless --browser=firefox\n```\n\n#### Step 16. Login to Jenkins\n\n* (The url, as well as username and password, should be accessible from your Google Cloud Platform VM instance page.)\n\n#### Step 17. Create a new Jenkins job\n\n![](https://seleniumbase.github.io/cdn/img/gcp/gcp_jenkins_new_job.png \"Create a Jenkins job\")\n\n* Click on \"New Item\"\n* Give your new Jenkins job a name (ex: \"My_First_Test\")\n* Select \"Freestyle project\"\n* Click \"OK\"\n\n#### Step 18. Setup your new Jenkins job\n\n* Under \"Source Code Management\", select \"Git\".\n* For the \"Repository URL\", put: ``https://github.com/seleniumbase/SeleniumBase.git``. (You'll eventually be using your own clone of the repository here.)\n* Under \"Build\", click the \"Add build step\" dropdown and then select \"Execute shell\".\n* For the \"Command\", put:\n\n```zsh\npytest examples/my_first_test.py --headless\n```\n\n* Click \"Save\" when you're done.\n\n#### Step 19. Run your new Jenkins job\n\n* Click on \"Build Now\"\n* (If all the setup was done correctly, you should see a blue dot appear after a few seconds, indicating that the test job passed.)\n\n#### Step 20. Future Work\n\nIf you have a web application that you want to test, you'll be able to create SeleniumBase tests and add them to Jenkins as you saw here. You may want to create a Deploy job, which downloads the latest version of your repository, and then kicks off all tests to run after that. You could then tell that Deploy job to auto-run whenever a change is pushed to your repository by using: \"Poll SCM\". All your tests would then be able to run by using: \"Build after other projects are built\". You can also use MySQL to save test results in the DB so that you can query the data at any time.\n\n#### Congratulations! You're now well on your way to becoming a build & release / automation engineer!\n\n### MySQL DB setup instructions\n\n#### Step 21. Return to the Google Cloud Launcher and launch a MySQL Instance\n\n![](https://seleniumbase.github.io/cdn/img/gcp/gcp_mysql.png \"Finding MySQL\")\n\n* Under \"Featured Solutions\", Click on \"MySQL\"\n* Click on \"Launch on Compute Engine\"\n* Give the instance a name\n* Give the instance a zone\n* Click \"Create\"\n\n#### Step 22. Get the Connection credentials for your new MySQL Instance\n\n* Under the Google Cloud Platform menu, go to \"Compute Engine\"\n* Find your new MySQL instance and then write down the value written in the \"External IP\" section.\n* Under the Google Cloud Platform menu, go to \"Deployment Manager\"\n* Find your new MySQL instance and then click on it.\n* Write down the values for Admin username and password. (Username should be \"root\")\n\n#### Step 23. Get a MySQL GUI tool so that you can connect to your MySQL Instance\n\n* You can download [MySQL Workbench](https://dev.mysql.com/downloads/tools/workbench/) for this.\n\n#### Step 24. Create a new connection to your MySQL Instance\n\n* Use the MySQL DB credentials that you saved in Step 21 for this.\n\n#### Step 25. Create a new database/schema in your MySQL Instance\n\n* You can name your database/schema ``test_db``.\n\n#### Step 26. Create the necessary tables in your MySQL database/schema\n\n* Run the [create_db_tables.sql](https://raw.githubusercontent.com/seleniumbase/SeleniumBase/master/seleniumbase/core/create_db_tables.sql) script in your MySQL database/schema to create all the required DB tables. \n\n#### Step 27. Have your local clone of SeleniumBase connect to your MySQL DB Instance\n\n* Update the MySQL connection details in your [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) file to use the credentials that you saved in Step 21.\n\n#### Step 28. Have your SeleniumBase Jenkins jobs use your MySQL DB Instance\n\n* For the \"Execute shell\", use the following as your updated \"Command\":\n\n```zsh\npytest examples/test_suite.py --headless --with-db_reporting\n```\n\n* Click \"Save\" when you're done.\n\n#### Step 29. Run your new Jenkins job\n\n* Click on \"Build Now\"\n* If all goes well, you should be seeing new rows appear in your MySQL DB tables.\n\n#### Step 30. Congratulations! You've successfully completed this tutorial!\n"
  },
  {
    "path": "integrations/katalon/ReadMe.md",
    "content": "### Converting Katalon recordings into SeleniumBase test scripts\n\n### (NOTE: **[SeleniumBase now has Recorder Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md)**, which is recommended over other record & playback tools.)\n\n--------\n\nKatalon Recorder (Selenium IDE) is a tool that allows you to record and playback actions performed inside a web browser. It's available as a [downloadable Chrome extension](https://chrome.google.com/webstore/detail/katalon-recorder-selenium/ljdobmomdgdljniojadhoplhkpialdid) and a [downloadable Firefox extension](https://addons.mozilla.org/en-US/firefox/addon/katalon-automation-record/). The Katalon Recorder comes with an option to export recordings as various WebDriver test scripts, one of which is ``Python 2 (WebDriver + unittest)``. Unfortunately, these natively-exported scripts can be very messy and don't always run reliably. The purpose of this converter is to clean up and improve the scripts so that they can be used in production-level environments.\n\n#### Step 1: Make a recording with the Katalon Recorder\n\n![](https://seleniumbase.io/cdn/img/katalon_recorder_2.png \"Katalon Recorder example\")\n\n#### Step 2: Export your recording as a Python 2 Webdriver script\n\n* ``{} Export`` => ``Python 2 (WebDriver + unittest)`` => ``Save As File``\n\n#### Step 3: Run ``seleniumbase convert`` on your exported Python file\n\n```zsh\nseleniumbase convert MY_TEST.py\n```\n\n* You should see a [MY_TEST_SB.py] file appear in the folder. (``_SB`` is added to the file name so that the original file stays intact in case you still need it.) This new clean & reliable SeleniumBase test script is ready to be added into your test suite for running.\n\n--------\n\n--------\n\nThe following is an example of a Katalon Recorder exported file (**WebDriver + unittest format**).\nIt is **messy** and has **unnecessary lines of code** to do the task that was recorded:\n\n```python\n# -*- coding: utf-8 -*-\nfrom selenium import webdriver\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.common.keys import Keys\nfrom selenium.webdriver.support.ui import Select\nfrom selenium.common.exceptions import NoSuchElementException\nfrom selenium.common.exceptions import NoAlertPresentException\nimport unittest, time, re\n\nclass Swag(unittest.TestCase):\n    def setUp(self):\n        self.driver = webdriver.Firefox()\n        self.driver.implicitly_wait(30)\n        self.base_url = \"https://www.google.com/\"\n        self.verificationErrors = []\n        self.accept_next_alert = True\n\n    def test_swag(self):\n        driver = self.driver\n        driver.get(\"https://www.saucedemo.com/\")\n        driver.find_element_by_id(\"user-name\").click()\n        driver.find_element_by_id(\"user-name\").clear()\n        driver.find_element_by_id(\"user-name\").send_keys(\"standard_user\")\n        driver.find_element_by_id(\"password\").click()\n        driver.find_element_by_id(\"password\").clear()\n        driver.find_element_by_id(\"password\").send_keys(\"secret_sauce\")\n        driver.find_element_by_id(\"login-button\").click()\n\n    def is_element_present(self, how, what):\n        try: self.driver.find_element(by=how, value=what)\n        except NoSuchElementException as e: return False\n        return True\n\n    def is_alert_present(self):\n        try: self.driver.switch_to_alert()\n        except NoAlertPresentException as e: return False\n        return True\n\n    def close_alert_and_get_its_text(self):\n        try:\n            alert = self.driver.switch_to_alert()\n            alert_text = alert.text\n            if self.accept_next_alert:\n                alert.accept()\n            else:\n                alert.dismiss()\n            return alert_text\n        finally: self.accept_next_alert = True\n\n    def tearDown(self):\n        self.driver.quit()\n        self.assertEqual([], self.verificationErrors)\n\nif __name__ == \"__main__\":\n    unittest.main()\n```\n\n<div><b>This can be improved on...</b></div>\n\n<b>After running <code>seleniumbase convert [FILE.py]</code> on it, here is the new result:</b>\n\n```python\n# -*- coding: utf-8 -*-\nfrom seleniumbase import BaseCase\n\n\nclass Swag(BaseCase):\n\n    def test_swag(self):\n        self.open('https://www.saucedemo.com/')\n        self.type('#user-name', 'standard_user')\n        self.type('#password', 'secret_sauce')\n        self.click('#login-button')\n```\n\n<b>This is much cleaner than the original version.\nIt also uses the more reliable SeleniumBase methods.</b>\n"
  },
  {
    "path": "integrations/linux/Linuxfile.sh",
    "content": "# SeleniumBase Debian Linux Dependency Installation\n# (Installs all required dependencies on Linux)\n\n# Make sure this script is only run on Linux\nvalue=\"$(uname)\"\nif [ $value == \"Linux\" ]\nthen\n  echo \"Initializing Requirements Setup...\"\nelse\n  echo \"Not on a Linux machine. Exiting...\"\n  exit\nfi\n\n# Go home\ncd ~\n\n# Configure apt-get resources\nsudo sh -c \"echo \\\"deb http://packages.linuxmint.com debian import\\\" >> /etc/apt/sources.list\"\nsudo sh -c \"echo \\\"deb http://downloads.sourceforge.net/project/ubuntuzilla/mozilla/apt all main\\\" >> /etc/apt/sources.list\"\n\n# Update aptitude\nsudo aptitude update\n\n# Install core dependencies\nsudo aptitude install -y --force-yes xserver-xorg-core\nsudo aptitude install -y --force-yes x11-xkb-utils\n\n# Install Xvfb (headless display system)\nsudo aptitude install -y --force-yes xvfb\n\n# Install fonts for web browsers\nsudo aptitude install -y --force-yes xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic\n\n# Install Python core dependencies\nsudo apt-get update\nsudo apt-get install -y --force-yes python-setuptools\n\n# Install Firefox\nsudo gpg --keyserver pgp.mit.edu --recv-keys 3EE67F3D0FF405B2\nsudo gpg --export 3EE67F3D0FF405B2 > 3EE67F3D0FF405B2.gpg\nsudo apt-key add ./3EE67F3D0FF405B2.gpg\nsudo rm ./3EE67F3D0FF405B2.gpg\nsudo apt-get -qy --no-install-recommends install -y --force-yes firefox\nsudo apt-get -qy --no-install-recommends install -y --force-yes $(apt-cache depends firefox | grep Depends | sed \"s/.*ends:\\ //\" | tr '\\n' ' ')\ncd /tmp\nsudo wget --no-check-certificate -O firefox-esr.tar.bz2 'https://download.mozilla.org/?product=firefox-esr-latest&os=linux32&lang=en-US'\nsudo tar -xjf firefox-esr.tar.bz2 -C /opt/\nsudo rm -rf /usr/bin/firefox\nsudo ln -s /opt/firefox/firefox /usr/bin/firefox\nsudo rm -f /tmp/firefox-esr.tar.bz2\nsudo apt-get -f install -y --force-yes firefox\n\n# Install more dependencies\nsudo apt-get update\nsudo apt-get install -y --force-yes curl\nsudo apt-get install -y --force-yes xvfb\nsudo apt-get install -y --force-yes build-essential chrpath libssl-dev libxft-dev\nsudo apt-get install -y --force-yes libfreetype6 libfreetype6-dev\nsudo apt-get install -y --force-yes libfontconfig1 libfontconfig1-dev\nsudo apt-get install -y --force-yes libmysqlclient-dev\nsudo apt-get install -y --force-yes python-dev\nsudo apt-get install -y --force-yes python-MySQLdb\n\n# Install Chrome\ncd /tmp\nsudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb\nsudo apt-get -f install -y --force-yes\nsudo dpkg -i google-chrome-stable_current_amd64.deb\n\n# Install Chromedriver\nCHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`\nsudo wget -N http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip -P ~/Downloads\nsudo unzip -o ~/Downloads/chromedriver_linux64.zip -d ~/Downloads\nsudo chmod +x ~/Downloads/chromedriver\nsudo rm -f /usr/local/share/chromedriver\nsudo rm -f /usr/local/bin/chromedriver\nsudo rm -f /usr/bin/chromedriver\nsudo mv -f ~/Downloads/chromedriver /usr/local/share/chromedriver\nsudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver\nsudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver\n\n# Finalize apt-get dependancies\nsudo apt-get -f install -y --force-yes\n\n# Get pip\nsudo easy_install pip\n"
  },
  {
    "path": "integrations/linux/ReadMe.md",
    "content": "## Running SeleniumBase on Debian GNU/Linux\n\nFiles in this folder are currently used with the [Jenkins on Microsoft Azure setup instructions for SeleniumBase](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/azure/jenkins/ReadMe.md), as well as with the [Jenkins on Google Cloud setup instructions for SeleniumBase](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/google_cloud/ReadMe.md). You can also use these files standalone with any Ubuntu/Debian GNU/Linux machine.\n"
  },
  {
    "path": "integrations/linux/Xvfb_launcher.sh",
    "content": "# Activate Headless Display (Xvfb)\n\nsudo Xvfb -ac :99 -screen 0 1280x1024x16 > /dev/null 2>&1 &\nexport DISPLAY=:99\nexec \"$@\"\n"
  },
  {
    "path": "integrations/linux/jenkins_permissions.sh",
    "content": "# This file will add \"jenkins\" to the sudoers file.\n\n# To become jenkins from a different user, use the following:\n# sudo su jenkins\n# bash\n\nsudo sh -c \"echo \\\"%jenkins ALL=(ALL:ALL) ALL\\\" >> /etc/sudoers\"\nsudo sh -c \"echo \\\"%jenkins ALL=(ALL) NOPASSWD: ALL\\\" >> /etc/sudoers\"\nsudo sh -c \"echo \\\"jenkins ALL=NOPASSWD: ALL\\\" >> /etc/sudoers\"\n"
  },
  {
    "path": "integrations/linux/tomcat_permissions.sh",
    "content": "# This file will add \"tomcat\" to the sudoers file.\n# \"tomcat\" is the Jenkins user name by default on Bitnami Jenkins machines\n\n# To become tomcat from a different user, use the following:\n# sudo su tomcat\n# bash\n\nsudo sh -c \"echo \\\"%tomcat ALL=(ALL:ALL) ALL\\\" >> /etc/sudoers\"\nsudo sh -c \"echo \\\"%tomcat ALL=(ALL) NOPASSWD: ALL\\\" >> /etc/sudoers\"\nsudo sh -c \"echo \\\"tomcat ALL=NOPASSWD: ALL\\\" >> /etc/sudoers\"\n"
  },
  {
    "path": "integrations/node_js/ReadMe.md",
    "content": "<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Creating a Test Runner with NodeJS + Express</h2>\n\nYou can create a customized web app for running SeleniumBase tests by using NodeJS and Express. (This tutorial assumes that you've already installed [SeleniumBase](https://github.com/seleniumbase/SeleniumBase).\n\n<img src=\"https://seleniumbase.github.io/other/node_runner.png\" title=\"Node Runner\" />\n\n#### 0. Clone SeleniumBase from GitHub\n\n* You'll need to work with the files located in the [integrations/node_js](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js) folder.\n\n#### 1. Install NodeJS (if not installed)\n\n* Navigate to [https://nodejs.org/en/](https://nodejs.org/en/)\n* Click to download and install NodeJS\n\n#### 2. Upgrade NodeJS (if using an older version)\n\n```zsh\nnpm install -g npm@latest\n```\n\n#### 3. Install the example Test Runner for SeleniumBase from [integrations/node_js](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js). (If dependencies were already installed, you can use `npm ci` for a speed improvement over `npm i` / `npm install` because `npm ci` uses `npm-shrinkwrap.json`, which is generated via ``npm shrinkwrap``.)\n\n```zsh\nnpm install\n```\n\n(You should see a `node_modules` folder appear in your `node_js` folder.)\n\n#### 4. Run the NodeJS server for your SeleniumBase Test Runner web app\n\n```zsh\nnode server.js\n```\n\n(You can stop the server by using <kbd>Ctrl+C</kbd>)\n\n#### 5. Open the SeleniumBase Test Runner web app\n\n* Navigate to [http://127.0.0.1:3000/](http://127.0.0.1:3000/)\n\n#### 6. Run an example test\n\nClick on a button to run a SeleniumBase example test.\n\n#### 7. Expand your web app\n\nNow that you have a web app for running SeleniumBase tests, you can expand it to run any script that you want after pressing a button.\n"
  },
  {
    "path": "integrations/node_js/__init__.py",
    "content": ""
  },
  {
    "path": "integrations/node_js/index.html",
    "content": "<html>\n  <head>\n    <link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/5.3.3/css/bootstrap.min.css\" crossorigin=\"anonymous\">\n    <style>\n      body {\n        font-family: Arial, Helvetica, sans-serif;\n        margin: 12px; padding: 12px;\n      }\n      h1 {color:#3366CC; font-size: 36px;}\n      input {\n        color:#009999; font-size: 28px;\n        box-shadow: 0 0 6px teal; margin: 2px;\n        padding-left: 6px; padding-right: 6px;\n      }\n    </style>\n  </head>\n  <body>\n    <script>0</script>\n    <div class=\"container\">\n    <h1>Select a script to run:</h1>\n    <form method=\"put\" action=\"/run_my_first_test\">\n      <input type=\"submit\" value=\"pytest my_first_test.py\">\n    </form>\n    <form method=\"put\" action=\"/run_test_demo_site\">\n      <input type=\"submit\" value=\"pytest test_demo_site.py\">\n    </form>\n    <form method=\"put\" action=\"/run_my_first_test_with_demo_mode\">\n      <input type=\"submit\" value=\"pytest my_first_test.py --demo\">\n    </form>\n    <form method=\"put\" action=\"/run_test_demo_site_with_demo_mode\">\n      <input type=\"submit\" value=\"pytest test_demo_site.py --demo\">\n    </form>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "integrations/node_js/my_first_test.py",
    "content": "from seleniumbase import BaseCase\n\n\nclass MyTestClass(BaseCase):\n    def test_swag_labs(self):\n        self.open(\"https://www.saucedemo.com\")\n        self.type(\"#user-name\", \"standard_user\")\n        self.type(\"#password\", \"secret_sauce\\n\")\n        self.assert_element(\"#inventory_container\")\n        self.assert_text(\"Products\", \"span.title\")\n        self.click('button[name*=\"backpack\"]')\n        self.click(\"#shopping_cart_container a\")\n        self.assert_text(\"Your Cart\", \"span.title\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click(\"button#checkout\")\n        self.type(\"#first-name\", \"SeleniumBase\")\n        self.type(\"#last-name\", \"Automation\")\n        self.type(\"#postal-code\", \"77123\")\n        self.click(\"input#continue\")\n        self.assert_text(\"Checkout: Overview\")\n        self.assert_text(\"Backpack\", \"div.cart_item\")\n        self.click(\"button#finish\")\n        self.assert_exact_text(\"Thank you for your order!\", \"h2\")\n        self.assert_element('img[alt=\"Pony Express\"]')\n        self.js_click(\"a#logout_sidebar_link\")\n"
  },
  {
    "path": "integrations/node_js/npm-shrinkwrap.json",
    "content": "{\n  \"name\": \"app\",\n  \"version\": \"0.0.0\",\n  \"lockfileVersion\": 3,\n  \"requires\": true,\n  \"packages\": {\n    \"\": {\n      \"name\": \"app\",\n      \"version\": \"0.0.0\",\n      \"dependencies\": {\n        \"express\": \"~4.21.0\"\n      }\n    },\n    \"node_modules/accepts\": {\n      \"version\": \"1.3.8\",\n      \"resolved\": \"https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz\",\n      \"integrity\": \"sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==\",\n      \"dependencies\": {\n        \"mime-types\": \"~2.1.34\",\n        \"negotiator\": \"0.6.3\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/array-flatten\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz\",\n      \"integrity\": \"sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==\"\n    },\n    \"node_modules/body-parser\": {\n      \"version\": \"1.20.3\",\n      \"resolved\": \"https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz\",\n      \"integrity\": \"sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==\",\n      \"dependencies\": {\n        \"bytes\": \"3.1.2\",\n        \"content-type\": \"~1.0.5\",\n        \"debug\": \"2.6.9\",\n        \"depd\": \"2.0.0\",\n        \"destroy\": \"1.2.0\",\n        \"http-errors\": \"2.0.0\",\n        \"iconv-lite\": \"0.4.24\",\n        \"on-finished\": \"2.4.1\",\n        \"qs\": \"6.13.0\",\n        \"raw-body\": \"2.5.2\",\n        \"type-is\": \"~1.6.18\",\n        \"unpipe\": \"1.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8\",\n        \"npm\": \"1.2.8000 || >= 1.4.16\"\n      }\n    },\n    \"node_modules/bytes\": {\n      \"version\": \"3.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz\",\n      \"integrity\": \"sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/call-bind\": {\n      \"version\": \"1.0.7\",\n      \"resolved\": \"https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz\",\n      \"integrity\": \"sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==\",\n      \"dependencies\": {\n        \"es-define-property\": \"^1.0.0\",\n        \"es-errors\": \"^1.3.0\",\n        \"function-bind\": \"^1.1.2\",\n        \"get-intrinsic\": \"^1.2.4\",\n        \"set-function-length\": \"^1.2.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/content-disposition\": {\n      \"version\": \"0.5.4\",\n      \"resolved\": \"https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz\",\n      \"integrity\": \"sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==\",\n      \"dependencies\": {\n        \"safe-buffer\": \"5.2.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/content-type\": {\n      \"version\": \"1.0.5\",\n      \"resolved\": \"https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz\",\n      \"integrity\": \"sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/cookie\": {\n      \"version\": \"0.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz\",\n      \"integrity\": \"sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/cookie-signature\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz\",\n      \"integrity\": \"sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==\"\n    },\n    \"node_modules/debug\": {\n      \"version\": \"2.6.9\",\n      \"resolved\": \"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz\",\n      \"integrity\": \"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==\",\n      \"dependencies\": {\n        \"ms\": \"2.0.0\"\n      }\n    },\n    \"node_modules/define-data-property\": {\n      \"version\": \"1.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz\",\n      \"integrity\": \"sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==\",\n      \"dependencies\": {\n        \"es-define-property\": \"^1.0.0\",\n        \"es-errors\": \"^1.3.0\",\n        \"gopd\": \"^1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/depd\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/depd/-/depd-2.0.0.tgz\",\n      \"integrity\": \"sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/destroy\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz\",\n      \"integrity\": \"sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==\",\n      \"engines\": {\n        \"node\": \">= 0.8\",\n        \"npm\": \"1.2.8000 || >= 1.4.16\"\n      }\n    },\n    \"node_modules/ee-first\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz\",\n      \"integrity\": \"sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==\"\n    },\n    \"node_modules/encodeurl\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz\",\n      \"integrity\": \"sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/es-define-property\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz\",\n      \"integrity\": \"sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==\",\n      \"dependencies\": {\n        \"get-intrinsic\": \"^1.2.4\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/es-errors\": {\n      \"version\": \"1.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz\",\n      \"integrity\": \"sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==\",\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/escape-html\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz\",\n      \"integrity\": \"sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==\"\n    },\n    \"node_modules/etag\": {\n      \"version\": \"1.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/etag/-/etag-1.8.1.tgz\",\n      \"integrity\": \"sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/express\": {\n      \"version\": \"4.21.0\",\n      \"resolved\": \"https://registry.npmjs.org/express/-/express-4.21.0.tgz\",\n      \"integrity\": \"sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==\",\n      \"dependencies\": {\n        \"accepts\": \"~1.3.8\",\n        \"array-flatten\": \"1.1.1\",\n        \"body-parser\": \"1.20.3\",\n        \"content-disposition\": \"0.5.4\",\n        \"content-type\": \"~1.0.4\",\n        \"cookie\": \"0.6.0\",\n        \"cookie-signature\": \"1.0.6\",\n        \"debug\": \"2.6.9\",\n        \"depd\": \"2.0.0\",\n        \"encodeurl\": \"~2.0.0\",\n        \"escape-html\": \"~1.0.3\",\n        \"etag\": \"~1.8.1\",\n        \"finalhandler\": \"1.3.1\",\n        \"fresh\": \"0.5.2\",\n        \"http-errors\": \"2.0.0\",\n        \"merge-descriptors\": \"1.0.3\",\n        \"methods\": \"~1.1.2\",\n        \"on-finished\": \"2.4.1\",\n        \"parseurl\": \"~1.3.3\",\n        \"path-to-regexp\": \"0.1.10\",\n        \"proxy-addr\": \"~2.0.7\",\n        \"qs\": \"6.13.0\",\n        \"range-parser\": \"~1.2.1\",\n        \"safe-buffer\": \"5.2.1\",\n        \"send\": \"0.19.0\",\n        \"serve-static\": \"1.16.2\",\n        \"setprototypeof\": \"1.2.0\",\n        \"statuses\": \"2.0.1\",\n        \"type-is\": \"~1.6.18\",\n        \"utils-merge\": \"1.0.1\",\n        \"vary\": \"~1.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.10.0\"\n      }\n    },\n    \"node_modules/finalhandler\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz\",\n      \"integrity\": \"sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==\",\n      \"dependencies\": {\n        \"debug\": \"2.6.9\",\n        \"encodeurl\": \"~2.0.0\",\n        \"escape-html\": \"~1.0.3\",\n        \"on-finished\": \"2.4.1\",\n        \"parseurl\": \"~1.3.3\",\n        \"statuses\": \"2.0.1\",\n        \"unpipe\": \"~1.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/forwarded\": {\n      \"version\": \"0.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz\",\n      \"integrity\": \"sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/fresh\": {\n      \"version\": \"0.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz\",\n      \"integrity\": \"sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/function-bind\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz\",\n      \"integrity\": \"sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==\",\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/get-intrinsic\": {\n      \"version\": \"1.2.4\",\n      \"resolved\": \"https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz\",\n      \"integrity\": \"sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==\",\n      \"dependencies\": {\n        \"es-errors\": \"^1.3.0\",\n        \"function-bind\": \"^1.1.2\",\n        \"has-proto\": \"^1.0.1\",\n        \"has-symbols\": \"^1.0.3\",\n        \"hasown\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/gopd\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz\",\n      \"integrity\": \"sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==\",\n      \"dependencies\": {\n        \"get-intrinsic\": \"^1.1.3\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/has-property-descriptors\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz\",\n      \"integrity\": \"sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==\",\n      \"dependencies\": {\n        \"es-define-property\": \"^1.0.0\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/has-proto\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz\",\n      \"integrity\": \"sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==\",\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/has-symbols\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz\",\n      \"integrity\": \"sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==\",\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/hasown\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz\",\n      \"integrity\": \"sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==\",\n      \"dependencies\": {\n        \"function-bind\": \"^1.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/http-errors\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz\",\n      \"integrity\": \"sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==\",\n      \"dependencies\": {\n        \"depd\": \"2.0.0\",\n        \"inherits\": \"2.0.4\",\n        \"setprototypeof\": \"1.2.0\",\n        \"statuses\": \"2.0.1\",\n        \"toidentifier\": \"1.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/iconv-lite\": {\n      \"version\": \"0.4.24\",\n      \"resolved\": \"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz\",\n      \"integrity\": \"sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==\",\n      \"dependencies\": {\n        \"safer-buffer\": \">= 2.1.2 < 3\"\n      },\n      \"engines\": {\n        \"node\": \">=0.10.0\"\n      }\n    },\n    \"node_modules/inherits\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz\",\n      \"integrity\": \"sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==\"\n    },\n    \"node_modules/ipaddr.js\": {\n      \"version\": \"1.9.1\",\n      \"resolved\": \"https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz\",\n      \"integrity\": \"sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==\",\n      \"engines\": {\n        \"node\": \">= 0.10\"\n      }\n    },\n    \"node_modules/media-typer\": {\n      \"version\": \"0.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz\",\n      \"integrity\": \"sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/merge-descriptors\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz\",\n      \"integrity\": \"sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==\",\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/sindresorhus\"\n      }\n    },\n    \"node_modules/methods\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/methods/-/methods-1.1.2.tgz\",\n      \"integrity\": \"sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/mime\": {\n      \"version\": \"1.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/mime/-/mime-1.6.0.tgz\",\n      \"integrity\": \"sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==\",\n      \"bin\": {\n        \"mime\": \"cli.js\"\n      },\n      \"engines\": {\n        \"node\": \">=4\"\n      }\n    },\n    \"node_modules/mime-db\": {\n      \"version\": \"1.52.0\",\n      \"resolved\": \"https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz\",\n      \"integrity\": \"sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/mime-types\": {\n      \"version\": \"2.1.35\",\n      \"resolved\": \"https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz\",\n      \"integrity\": \"sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==\",\n      \"dependencies\": {\n        \"mime-db\": \"1.52.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/ms\": {\n      \"version\": \"2.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz\",\n      \"integrity\": \"sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==\"\n    },\n    \"node_modules/negotiator\": {\n      \"version\": \"0.6.3\",\n      \"resolved\": \"https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz\",\n      \"integrity\": \"sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/object-inspect\": {\n      \"version\": \"1.13.2\",\n      \"resolved\": \"https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz\",\n      \"integrity\": \"sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==\",\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/on-finished\": {\n      \"version\": \"2.4.1\",\n      \"resolved\": \"https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz\",\n      \"integrity\": \"sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==\",\n      \"dependencies\": {\n        \"ee-first\": \"1.1.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/parseurl\": {\n      \"version\": \"1.3.3\",\n      \"resolved\": \"https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz\",\n      \"integrity\": \"sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/path-to-regexp\": {\n      \"version\": \"0.1.10\",\n      \"resolved\": \"https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz\",\n      \"integrity\": \"sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==\"\n    },\n    \"node_modules/proxy-addr\": {\n      \"version\": \"2.0.7\",\n      \"resolved\": \"https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz\",\n      \"integrity\": \"sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==\",\n      \"dependencies\": {\n        \"forwarded\": \"0.2.0\",\n        \"ipaddr.js\": \"1.9.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.10\"\n      }\n    },\n    \"node_modules/qs\": {\n      \"version\": \"6.13.0\",\n      \"resolved\": \"https://registry.npmjs.org/qs/-/qs-6.13.0.tgz\",\n      \"integrity\": \"sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==\",\n      \"dependencies\": {\n        \"side-channel\": \"^1.0.6\"\n      },\n      \"engines\": {\n        \"node\": \">=0.6\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/range-parser\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz\",\n      \"integrity\": \"sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==\",\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/raw-body\": {\n      \"version\": \"2.5.2\",\n      \"resolved\": \"https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz\",\n      \"integrity\": \"sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==\",\n      \"dependencies\": {\n        \"bytes\": \"3.1.2\",\n        \"http-errors\": \"2.0.0\",\n        \"iconv-lite\": \"0.4.24\",\n        \"unpipe\": \"1.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/safe-buffer\": {\n      \"version\": \"5.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz\",\n      \"integrity\": \"sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==\",\n      \"funding\": [\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/feross\"\n        },\n        {\n          \"type\": \"patreon\",\n          \"url\": \"https://www.patreon.com/feross\"\n        },\n        {\n          \"type\": \"consulting\",\n          \"url\": \"https://feross.org/support\"\n        }\n      ]\n    },\n    \"node_modules/safer-buffer\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz\",\n      \"integrity\": \"sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==\"\n    },\n    \"node_modules/send\": {\n      \"version\": \"0.19.0\",\n      \"resolved\": \"https://registry.npmjs.org/send/-/send-0.19.0.tgz\",\n      \"integrity\": \"sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==\",\n      \"dependencies\": {\n        \"debug\": \"2.6.9\",\n        \"depd\": \"2.0.0\",\n        \"destroy\": \"1.2.0\",\n        \"encodeurl\": \"~1.0.2\",\n        \"escape-html\": \"~1.0.3\",\n        \"etag\": \"~1.8.1\",\n        \"fresh\": \"0.5.2\",\n        \"http-errors\": \"2.0.0\",\n        \"mime\": \"1.6.0\",\n        \"ms\": \"2.1.3\",\n        \"on-finished\": \"2.4.1\",\n        \"range-parser\": \"~1.2.1\",\n        \"statuses\": \"2.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8.0\"\n      }\n    },\n    \"node_modules/send/node_modules/encodeurl\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz\",\n      \"integrity\": \"sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/send/node_modules/ms\": {\n      \"version\": \"2.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\",\n      \"integrity\": \"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"\n    },\n    \"node_modules/serve-static\": {\n      \"version\": \"1.16.2\",\n      \"resolved\": \"https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz\",\n      \"integrity\": \"sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==\",\n      \"dependencies\": {\n        \"encodeurl\": \"~2.0.0\",\n        \"escape-html\": \"~1.0.3\",\n        \"parseurl\": \"~1.3.3\",\n        \"send\": \"0.19.0\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.8.0\"\n      }\n    },\n    \"node_modules/set-function-length\": {\n      \"version\": \"1.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz\",\n      \"integrity\": \"sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==\",\n      \"dependencies\": {\n        \"define-data-property\": \"^1.1.4\",\n        \"es-errors\": \"^1.3.0\",\n        \"function-bind\": \"^1.1.2\",\n        \"get-intrinsic\": \"^1.2.4\",\n        \"gopd\": \"^1.0.1\",\n        \"has-property-descriptors\": \"^1.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/setprototypeof\": {\n      \"version\": \"1.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz\",\n      \"integrity\": \"sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==\"\n    },\n    \"node_modules/side-channel\": {\n      \"version\": \"1.0.6\",\n      \"resolved\": \"https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz\",\n      \"integrity\": \"sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==\",\n      \"dependencies\": {\n        \"call-bind\": \"^1.0.7\",\n        \"es-errors\": \"^1.3.0\",\n        \"get-intrinsic\": \"^1.2.4\",\n        \"object-inspect\": \"^1.13.1\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/statuses\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz\",\n      \"integrity\": \"sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/toidentifier\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz\",\n      \"integrity\": \"sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==\",\n      \"engines\": {\n        \"node\": \">=0.6\"\n      }\n    },\n    \"node_modules/type-is\": {\n      \"version\": \"1.6.18\",\n      \"resolved\": \"https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz\",\n      \"integrity\": \"sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==\",\n      \"dependencies\": {\n        \"media-typer\": \"0.3.0\",\n        \"mime-types\": \"~2.1.24\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.6\"\n      }\n    },\n    \"node_modules/unpipe\": {\n      \"version\": \"1.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz\",\n      \"integrity\": \"sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    },\n    \"node_modules/utils-merge\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz\",\n      \"integrity\": \"sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==\",\n      \"engines\": {\n        \"node\": \">= 0.4.0\"\n      }\n    },\n    \"node_modules/vary\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/vary/-/vary-1.1.2.tgz\",\n      \"integrity\": \"sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==\",\n      \"engines\": {\n        \"node\": \">= 0.8\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "integrations/node_js/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"node ./bin/www\"\n  },\n  \"dependencies\": {\n    \"express\": \"~4.21.0\"\n  }\n}"
  },
  {
    "path": "integrations/node_js/server.js",
    "content": "const http = require('http');\nconst express = require('express');\nconst path = require('path');\nconst app = express();\nconst exec = require('child_process').exec;\nvar server_info = '\\nServer running at http://127.0.0.1:3000/  (CTRL+C to stop)';\n\nfunction run_command(command) {\n    console.log(\"\\n\" + command);\n    exec(command, (err, stdout, stderr) => console.log(stdout, server_info));\n}\n\napp.get('/', function(req, res) {\n    res.sendFile(path.join(__dirname + '/index.html'));\n});\n\napp.get('/run_my_first_test', function(req, res) {\n    res.sendFile(path.join(__dirname + '/index.html'));\n    res.redirect('/');\n    run_command(\"pytest my_first_test.py\");\n});\n\napp.get('/run_test_demo_site', function(req, res) {\n    res.sendFile(path.join(__dirname + '/index.html'));\n    res.redirect('/');\n    run_command(\"pytest test_demo_site.py\");\n});\n\napp.get('/run_my_first_test_with_demo_mode', function(req, res) {\n    res.sendFile(path.join(__dirname + '/index.html'));\n    res.redirect('/');\n    run_command(\"pytest my_first_test.py --demo_mode\");\n});\n\napp.get('/run_test_demo_site_with_demo_mode', function(req, res) {\n    res.sendFile(path.join(__dirname + '/index.html'));\n    res.redirect('/');\n    run_command(\"pytest test_demo_site.py --demo_mode\");\n});\n\napp.listen(3000, \"127.0.0.1\", function() {\n    console.log(server_info);\n});\n"
  },
  {
    "path": "integrations/node_js/test_demo_site.py",
    "content": "from seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\n\n\nclass DemoSiteTests(BaseCase):\n    def test_demo_site(self):\n        # Open a web page in the active browser window\n        self.open(\"https://seleniumbase.io/demo_page\")\n\n        # Assert the title of the current web page\n        self.assert_title(\"Web Testing Page\")\n\n        # Assert that an element is visible on the page\n        self.assert_element(\"tbody#tbodyId\")\n\n        # Assert that a text substring appears in an element\n        self.assert_text(\"Demo Page\", \"h1\")\n\n        # Type text into various text fields and then assert\n        self.type(\"#myTextInput\", \"This is Automated\")\n        self.type(\"textarea.area1\", \"Testing Time!\\n\")\n        self.type('[name=\"preText2\"]', \"Typing Text!\")\n        self.assert_text(\"This is Automated\", \"#myTextInput\")\n        self.assert_text(\"Testing Time!\\n\", \"textarea.area1\")\n        self.assert_text(\"Typing Text!\", '[name=\"preText2\"]')\n\n        # Hover & click a dropdown element and assert results\n        self.assert_text(\"Automation Practice\", \"h3\")\n        try:\n            self.hover_and_click(\"#myDropdown\", \"#dropOption2\", timeout=1)\n        except Exception:\n            # Someone moved the mouse while the test ran\n            self.js_click(\"#dropOption2\")\n        self.assert_text(\"Link Two Selected\", \"h3\")\n\n        # Click a button and then verify the expected results\n        self.assert_text(\"This Text is Green\", \"#pText\")\n        self.click('button:contains(\"Click Me\")')\n        self.assert_text(\"This Text is Purple\", \"#pText\")\n\n        # Assert that the given SVG is visible on the page\n        self.assert_element('svg[name=\"svgName\"]')\n\n        # Verify that a slider control updates a progress bar\n        self.assert_element('progress[value=\"50\"]')\n        self.set_value(\"input#mySlider\", \"100\")\n        self.assert_element('progress[value=\"100\"]')\n\n        # Verify that a \"select\" option updates a meter bar\n        self.assert_element('meter[value=\"0.25\"]')\n        self.select_option_by_text(\"#mySelect\", \"Set to 75%\")\n        self.assert_element('meter[value=\"0.75\"]')\n\n        # Assert an element located inside an iframe\n        self.assert_false(self.is_element_visible(\"img\"))\n        self.switch_to_frame(\"#myFrame1\")\n        self.assert_true(self.is_element_visible(\"img\"))\n        self.switch_to_default_content()\n\n        # Assert text located inside an iframe\n        self.assert_false(self.is_text_visible(\"iFrame Text\"))\n        self.switch_to_frame(\"#myFrame2\")\n        self.assert_true(self.is_text_visible(\"iFrame Text\"))\n        self.switch_to_default_content()\n\n        # Verify that clicking a radio button selects it\n        self.assert_false(self.is_selected(\"#radioButton2\"))\n        self.click(\"#radioButton2\")\n        self.assert_true(self.is_selected(\"#radioButton2\"))\n\n        # Verify that clicking a checkbox makes it selected\n        self.assert_element_not_visible(\"img#logo\")\n        self.assert_false(self.is_selected(\"#checkBox1\"))\n        self.click(\"#checkBox1\")\n        self.assert_true(self.is_selected(\"#checkBox1\"))\n        self.assert_element(\"img#logo\")\n\n        # Verify clicking on multiple elements with one call\n        self.assert_false(self.is_selected(\"#checkBox2\"))\n        self.assert_false(self.is_selected(\"#checkBox3\"))\n        self.assert_false(self.is_selected(\"#checkBox4\"))\n        self.click_visible_elements(\"input.checkBoxClassB\")\n        self.assert_true(self.is_selected(\"#checkBox2\"))\n        self.assert_true(self.is_selected(\"#checkBox3\"))\n        self.assert_true(self.is_selected(\"#checkBox4\"))\n\n        # Verify that clicking an iframe checkbox selects it\n        self.assert_false(self.is_element_visible(\".fBox\"))\n        self.switch_to_frame(\"#myFrame3\")\n        self.assert_true(self.is_element_visible(\".fBox\"))\n        self.assert_false(self.is_selected(\".fBox\"))\n        self.click(\".fBox\")\n        self.assert_true(self.is_selected(\".fBox\"))\n        self.switch_to_default_content()\n\n        # Verify Drag and Drop\n        self.assert_element_not_visible(\"div#drop2 img#logo\")\n        self.drag_and_drop(\"img#logo\", \"div#drop2\")\n        self.assert_element(\"div#drop2 img#logo\")\n\n        # Assert link text\n        self.assert_link_text(\"seleniumbase.com\")\n        self.assert_link_text(\"SeleniumBase on GitHub\")\n        self.assert_link_text(\"seleniumbase.io\")\n\n        # Click link text\n        self.click_link(\"SeleniumBase Demo Page\")\n\n        # Assert exact text\n        self.assert_exact_text(\"Demo Page\", \"h1\")\n\n        # Highlight a page element (Also asserts visibility)\n        self.highlight(\"h2\")\n\n        # Actions with Demo Mode enabled\n        if self.headed:\n            self.activate_demo_mode()\n        self.type(\"input\", \"Have a Nice Day!\")\n        self.assert_text(\"SeleniumBase\", \"h2\")\n"
  },
  {
    "path": "integrations/selenium_grid/ReadMe.md",
    "content": "### The ReadMe for the Selenium Grid Hub Launcher has been moved to: [seleniumbase/utilities/selenium_grid/ReadMe.md](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/utilities/selenium_grid/ReadMe.md) and all related code has been moved to [seleniumbase/utilities/selenium_grid](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/utilities/selenium_grid)\n"
  },
  {
    "path": "integrations/selenium_ide/ReadMe.md",
    "content": "### Converting Katalon recordings into SeleniumBase test scripts\n\n### (NOTE: **[SeleniumBase now has Recorder Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md)**, which is recommended over other record & playback tools.)\n\n--------\n\nKatalon Recorder (Selenium IDE) is a tool that allows you to record and playback actions performed inside a web browser. It's available as a [downloadable Chrome extension](https://chrome.google.com/webstore/detail/katalon-recorder-selenium/ljdobmomdgdljniojadhoplhkpialdid) and a [downloadable Firefox extension](https://addons.mozilla.org/en-US/firefox/addon/katalon-automation-record/). The Katalon Recorder comes with an option to export recordings as various WebDriver test scripts, one of which is `Python 2 (WebDriver + unittest)`. Unfortunately, these natively-exported scripts can be very messy and don't always run reliably. The purpose of this converter is to clean up and improve the scripts so that they can be used in production-level environments.\n\n#### Step 1: Make a recording with the Katalon Recorder\n\n![](https://seleniumbase.io/cdn/img/katalon_recorder_2.png \"Katalon Recorder example\")\n\n#### Step 2: Export your recording as a Python 2 Webdriver script\n\n* `{} Export` => `Python 2 (WebDriver + unittest)` => `Save As File`\n\n#### Step 3: Run `seleniumbase convert` on your exported Python file\n\n```zsh\nseleniumbase convert MY_TEST.py\n```\n\n* You should see a [MY_TEST_SB.py] file appear in the folder. (`_SB` is added to the file name so that the original file stays intact in case you still need it.) This new clean & reliable SeleniumBase test script is ready to be added into your test suite for running.\n\n--------\n\n--------\n\nThe following is an example of a Katalon Recorder exported file (**WebDriver + unittest format**).\nIt is **messy** and has **unnecessary lines of code** to do the task that was recorded:\n\n```python\n# -*- coding: utf-8 -*-\nfrom selenium import webdriver\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.common.keys import Keys\nfrom selenium.webdriver.support.ui import Select\nfrom selenium.common.exceptions import NoSuchElementException\nfrom selenium.common.exceptions import NoAlertPresentException\nimport unittest, time, re\n\nclass Swag(unittest.TestCase):\n    def setUp(self):\n        self.driver = webdriver.Firefox()\n        self.driver.implicitly_wait(30)\n        self.base_url = \"https://www.google.com/\"\n        self.verificationErrors = []\n        self.accept_next_alert = True\n\n    def test_swag(self):\n        driver = self.driver\n        driver.get(\"https://www.saucedemo.com/\")\n        driver.find_element_by_id(\"user-name\").click()\n        driver.find_element_by_id(\"user-name\").clear()\n        driver.find_element_by_id(\"user-name\").send_keys(\"standard_user\")\n        driver.find_element_by_id(\"password\").click()\n        driver.find_element_by_id(\"password\").clear()\n        driver.find_element_by_id(\"password\").send_keys(\"secret_sauce\")\n        driver.find_element_by_id(\"login-button\").click()\n\n    def is_element_present(self, how, what):\n        try: self.driver.find_element(by=how, value=what)\n        except NoSuchElementException as e: return False\n        return True\n\n    def is_alert_present(self):\n        try: self.driver.switch_to_alert()\n        except NoAlertPresentException as e: return False\n        return True\n\n    def close_alert_and_get_its_text(self):\n        try:\n            alert = self.driver.switch_to_alert()\n            alert_text = alert.text\n            if self.accept_next_alert:\n                alert.accept()\n            else:\n                alert.dismiss()\n            return alert_text\n        finally: self.accept_next_alert = True\n\n    def tearDown(self):\n        self.driver.quit()\n        self.assertEqual([], self.verificationErrors)\n\nif __name__ == \"__main__\":\n    unittest.main()\n```\n\n<div><b>This can be improved on...</b></div>\n\n<b>After running <code>seleniumbase convert [FILE.py]</code> on it, here is the new result:</b>\n\n```python\n# -*- coding: utf-8 -*-\nfrom seleniumbase import BaseCase\n\n\nclass Swag(BaseCase):\n\n    def test_swag(self):\n        self.open('https://www.saucedemo.com/')\n        self.type('#user-name', 'standard_user')\n        self.type('#password', 'secret_sauce')\n        self.click('#login-button')\n```\n\n<b>This is much cleaner than the original version.\nIt also uses the more reliable SeleniumBase methods.</b>\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "# Project information\nsite_name: SeleniumBase Docs\nsite_url: https://seleniumbase.io\nsite_author: Michael Mintz\nsite_description: A complete framework for end-to-end testing with Python, pytest, behave-BDD, and WebDriver.\n# Repository information\nrepo_name: seleniumbase/SeleniumBase\nrepo_url: https://github.com/seleniumbase/SeleniumBase\nedit_uri: \"\"\nsite_dir: \"site\"\ndocs_dir: \"mkdocs_build\"\n# Copyright\ncopyright: Copyright &copy; 2014 - 2026 Michael Mintz\n# Extensions\nmarkdown_extensions:\n  - admonition\n  - md_in_html\n  - tables\n  - toc:\n      permalink: true\n  - pymdownx.highlight:\n      linenums: false\n  - pymdownx.tabbed:\n      alternate_style: true\n  - pymdownx.highlight\n  - pymdownx.superfences\n  - pymdownx.inlinehilite\n  # - pymdownx.details\n  # - pymdownx.snippets\n# Configuration\ntheme:\n  name: material\n  logo: img/logo3c.png\n  favicon: img/green_logo3.png\n  language: en\n  include_homepage_in_sidebar: true\n  sticky_navigation: true\n  collapse_navigation: true\n  # titles_only: false\n  include_search_page: false\n  search_index_only: true\n  static_templates:\n    - 404.html\n  features:\n    # - search.highlight\n    # - toc.integrate\n    - toc.hide\n    # - navigation.indexes\n    - toc.follow\n    - navigation.sections\n    # - navigation.expand\n    # - navigation.tabs\n    - navigation.top\n    - navigation.tracking\n    - navigation.instant\n  palette:\n    scheme: default\n    primary: blue\n    accent: green\n  font:\n    text: Roboto\n    code: Roboto Mono\n  icon:\n    logo: img/sb_logo_10.png\n# Plugins\nplugins:\n  - search:\n      separator: '[\\s]+'\n      lang: en\n  - exclude-search:\n      exclude:\n        - CODE_OF_CONDUCT.md\n        - CONTRIBUTING.md\n        - SECURITY.md\n        - examples/case_summary.md\n        - examples/migration/raw_selenium/ReadMe.md\n        - integrations/katalon/ReadMe.md\n        - help_docs/ReadMe.md\n        - help_docs/verify_webdriver.md\n        - help_docs/webdriver_installation.md\n        - seleniumbase/masterqa/ReadMe.md\n        - seleniumbase/utilities/selenium_ide/ReadMe.md\n        - seleniumbase/examples/chart_maker/ReadMe.md\n  # - minify:\n  #     minify_html: true\n  #     minify_css: true\n  #     minify_js: true\n  - mkdocs-simple-hooks:\n      hooks:\n        on_pre_build: mkdocs_build.prepare:main\n# Page tree\nnav:\n  - ✅ SeleniumBase README: README.md\n  - 🏰 List of Features: help_docs/features_list.md\n  - 📚 Running Example Tests: examples/ReadMe.md\n  - 🎛️ Command Line Options: help_docs/customizing_test_runs.md\n  - 🪄 Console Scripts: seleniumbase/console_scripts/ReadMe.md\n  - 📊 Dashboard / Reports: examples/example_logs/ReadMe.md\n  - 🔡 Syntax Formats: help_docs/syntax_formats.md\n  - 🎖️ GUI / Commander: help_docs/commander.md\n  - 🔴 Recorder Mode: help_docs/recorder_mode.md\n  - 📘 API Reference: help_docs/method_summary.md\n  - 📗 CDP Mode APIs: help_docs/cdp_mode_methods.md\n  - Python Setup / Install:\n    - 🐉 Get Python, pip, & git: help_docs/install_python_pip_git.md\n    - ⚙️ Virtualenv Instructions: help_docs/virtualenv_instructions.md\n    - 🏄 Install SeleniumBase: help_docs/install.md\n    - 👁️ How it Works: help_docs/how_it_works.md\n  - JS Manager / JS Tools:\n    - ❇️ JS Package Manager: help_docs/js_package_manager.md\n    - 🎦 Demo Mode: help_docs/demo_mode.md\n    - 🚎 Tour Maker: examples/tour_examples/ReadMe.md\n    - 🛂 Dialog Boxes: examples/dialog_boxes/ReadMe.md\n    - 🛂 MasterQA Mode: examples/master_qa/ReadMe.md\n    - 📶 Chart Maker: examples/chart_maker/ReadMe.md\n    - 🎞️ Presentation Maker: examples/presenter/ReadMe.md\n  - Integrations:\n    - 👤 UC Mode: help_docs/uc_mode.md\n    - 🐙 CDP Mode: examples/cdp_mode/ReadMe.md\n    - 🎭 Stealthy Playwright: examples/cdp_mode/playwright/ReadMe.md\n    - 🤖 GitHub CI: integrations/github/workflows/ReadMe.md\n    - 🛂 MasterQA: seleniumbase/masterqa/ReadMe.md\n    - 🗂️ Case Plans: help_docs/case_plans.md\n    - 📱 Mobile Mode: help_docs/mobile_testing.md\n    - 🌐 Selenium Grid: seleniumbase/utilities/selenium_grid/ReadMe.md\n    - 🖼️ Visual Testing: examples/visual_testing/ReadMe.md\n    - 🕵️ The HTML Inspector: help_docs/html_inspector.md\n    - 🤖 Azure Pipelines: integrations/azure/azure_pipelines/ReadMe.md\n    - 🤖 Jenkins on Azure: integrations/azure/jenkins/ReadMe.md\n    - 🤖 Jenkins on Google Cloud: integrations/google_cloud/ReadMe.md\n    - 🤖 NodeJS Test Runner: https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js\n  - Presentations:\n    - ✅ Core Presentation: https://seleniumbase.io/other/core_presentation.html\n    - 🎞️ Presenter Demo: https://seleniumbase.io/other/presenter.html\n    - 📊 Chart Maker Demo: https://seleniumbase.io/other/chart_presentation.html\n    - ⚙️ Python Virtual Envs: https://seleniumbase.io/other/py_virtual_envs.html\n    - 🔰 Fundamentals Demo: https://seleniumbase.io/other/fundamentals.html\n  - Demo Pages:\n    - 🍵 Coffee Cart (Test App): https://seleniumbase.io/coffee/\n    - 📑 Demo Page (Test Page): https://seleniumbase.io/demo_page\n    - 🔑 Simple App (Test Page): https://seleniumbase.io/simple/login\n    - 🔑 MFA Login (Test App): https://seleniumbase.io/realworld/login\n    - 📝 TinyMCE (Test Page): https://seleniumbase.io/tinymce/\n    - 🔢 Calculator (Test App): https://seleniumbase.io/apps/calculator\n    - 📱 Device Farm (Virtual): https://seleniumbase.io/devices/\n    - ⚠️ Error Page (Test Page): https://seleniumbase.io/error_page/\n    - ⚠️ Page with broken links: https://seleniumbase.io/other/broken_page\n    - ↔️ Drag & Drop (Test Page): https://seleniumbase.io/other/drag_and_drop\n    - 🖼️ Canvas Test Page One: https://seleniumbase.io/canvas/\n    - 🖼️ Canvas Test Page Two: https://seleniumbase.io/other/canvas\n    - 👤 Shadow DOM/Root Page: https://seleniumbase.io/other/shadow_dom\n    - 🖥️ SeleniumBase in iframe: https://seleniumbase.io/w3schools/sbase\n    - 🖥️ W3Schools Playground: https://seleniumbase.io/w3schools/\n    - 🖥️ W3Schools iframes: https://seleniumbase.io/w3schools/iframes\n    - 🗃️ W3Schools file upload: https://seleniumbase.io/w3schools/file_upload\n    - 🖲️ W3Schools doubleclick: https://seleniumbase.io/w3schools/double_click\n    - ↔️ W3Schools drag & drop: https://seleniumbase.io/w3schools/drag_drop\n    - ☑️ W3Schools checkboxes: https://seleniumbase.io/w3schools/checkboxes\n    - 🔘 W3Schools radio buttons: https://seleniumbase.io/w3schools/radio_buttons\n  - Pages with CAPTCHAs:\n    - 🔑 CF Turnstile Test: https://seleniumbase.io/apps/turnstile\n    - 🔑 CF Turnstile on Form: https://seleniumbase.io/apps/form_turnstile\n    - 🔐 reCAPTCHA v2 Test: https://seleniumbase.io/apps/recaptcha\n    - 🔐 reCAPTCHA v2 on Form: https://seleniumbase.io/apps/form_recaptcha\n    - 🔐 reCAPTCHA, invisible: https://seleniumbase.io/apps/invisible_recaptcha\n  - Additional Help Docs:\n    - 📑 Table of Contents: help_docs/ReadMe.md\n    - 🖼️ How to handle iframes: help_docs/handling_iframes.md\n    - 🔐 Decorators / Security: seleniumbase/common/ReadMe.md\n    - 🗂️ Case Plans (examples): examples/case_summary.md\n    - 🧭 Using Safari Driver: help_docs/using_safari_driver.md\n    - 🐳 Docker Start Guide: integrations/docker/ReadMe.md\n    - 👤 Shadow DOM Support: help_docs/shadow_dom.md\n    - 👥 macOS Hidden Files: help_docs/hidden_files_info.md\n    - 🗄️ MySQL Instructions: help_docs/mysql_installation.md\n    - 📃 Desired Capabilities: help_docs/desired_capabilities.md\n    - 📜 Useful grep commands: help_docs/useful_grep_commands.md\n    - ⚙️ Downloading drivers: help_docs/webdriver_installation.md\n    - ✅ Selenium Migration: examples/migration/raw_selenium/ReadMe.md\n    - ✔️ Verifying drivers: help_docs/verify_webdriver.md\n  - Behave-BDD Integration:\n    - 🐝 Behave-BDD ReadMe: examples/behave_bdd/ReadMe.md\n    - 🐝 Behave-BDD GUI App: help_docs/behave_gui.md\n  - Languages:\n    - 🌏 Translations: help_docs/translations.md\n    - 🗾 Locale Codes: help_docs/locale_codes.md\n  - Other:\n    - 📺 YouTube Link: https://www.youtube.com/playlist?list=PLp9uKicxkBc5UIlGi2BuE3aWC7JyXpD3m\n    - 📋 Case Studies: help_docs/happy_customers.md\n    - 🙏 Thank You: help_docs/thank_you.md\n# Google Analytics\nextra:\n  analytics:\n    provider: google\n    property: G-P5KFWRNLRN\n    # property: UA-167313767-1\n# google_analytics: ['G-P5KFWRNLRN', 'seleniumbase.io']"
  },
  {
    "path": "mkdocs_build/ReadMe.txt",
    "content": "Help Docs Home Page:\n\nhttps://seleniumbase.io\n"
  },
  {
    "path": "mkdocs_build/docs_instructions.txt",
    "content": "Preparing the SeleniumBase Docs website with \"mkdocs\"\n\nUsage:  (from the top-level SeleniumBase folder)\npip install -r mkdocs_build/requirements.txt\nmkdocs build  (OR \"mkdocs build --strict\" to fail on warnings)\n"
  },
  {
    "path": "mkdocs_build/index.txt",
    "content": "<!doctype html>\n<html lang=\"en\" class=\"no-js\">\n<head>\n      <link rel=\"shortcut icon\" href=\"https://seleniumbase.io/img/logo3a.png\">\n      <title>SeleniumBase</title>\n</head>\n<body>\n<p><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.io/img/sb_logo_10.png\" alt=\"SeleniumBase\" width=\"275\" /></a></p>\n<h1><img src=\"https://seleniumbase.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> Docs Home Page:</h1>\n\n<h2><img src=\"https://seleniumbase.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> <a href=\"https://seleniumbase.io\">https://seleniumbase.io</a></h2>\n</body>\n</html>\n"
  },
  {
    "path": "mkdocs_build/prepare.py",
    "content": "\"\"\" For preparing the mkdocs-generated seleniumbase.io website. \"\"\"\n\nimport os\nimport re\nfrom pathlib import Path\n\nGITHUB_URL = r\"https://github.com/seleniumbase/SeleniumBase/blob/master/\"\nROOT_DIR = Path(__file__).parents[1]\nURL_PATTERN = re.compile(\n    r\"(?:\\(|<a href=\\\")(?P<url>{}[\\w/.]+\\.md)(?:\\)|\\\")\".format(GITHUB_URL)\n)\nMD_PATH_PATTERN = re.compile(r\"\\[.*\\]\\((?P<path>[\\w\\\\._/]+\\.md)\\)\")\nHEADER_PATTERN = re.compile(\n    r\"^(?P<level>#+)\\s*(<[\\w\\s=\\\":/.]+>)?\\s*\\**(?P<header>.*[\\w`]):?\\**\\s*$\",\n    flags=re.MULTILINE,\n)\n\nPROCESSED_PATHS = set()\n\n\ndef normalize_path(path):\n    path = Path(path).absolute().relative_to(ROOT_DIR)\n    return str(path).replace(\"\\\\\", \"/\")\n\n\ndef read_file(file_name):\n    path = ROOT_DIR / file_name\n    with path.open() as file_handle:\n        content = file_handle.read()\n    return content\n\n\ndef process_file(file_name):\n    content = read_file(file_name)\n    urls = URL_PATTERN.findall(content)\n    # content = content.replace(\"<br />\", \"  \\n\")\n    content = re.sub(HEADER_PATTERN, r\"\\g<level> \\g<header>\", content)\n    directory = \"/\".join(normalize_path(file_name).split(\"/\")[:-1])\n\n    paths = set()\n\n    md_paths = MD_PATH_PATTERN.findall(content)\n    for md_path in md_paths:\n        path = md_path.lstrip(\"/\")\n        if (ROOT_DIR / directory / path).exists():\n            path = ROOT_DIR / directory / path\n        else:\n            path = ROOT_DIR / path\n        path = path.resolve().relative_to(ROOT_DIR)\n        paths.add(normalize_path(path))\n        content = content.replace(\"(\" + md_path + \")\", normalize_path(path))\n\n    for url in urls:\n        path = url[len(GITHUB_URL) :]  # noqa: E203\n        paths.add(path)\n        content = content.replace(\n            url, normalize_path(os.path.relpath(path, directory))\n        )\n\n    output_path = ROOT_DIR / \"mkdocs_build\" / file_name\n    if not output_path.parent.is_dir():\n        os.makedirs(output_path.parent)\n\n    with output_path.open(\"w+\") as output_file:\n        output_file.write(content)\n    PROCESSED_PATHS.add(normalize_path(file_name))\n\n    for path in paths:\n        if path not in PROCESSED_PATHS:\n            process_file(normalize_path(path))\n\n\ndef main(*args, **kwargs):\n    files_to_process = [\"README.md\"]\n    scanned_dir_list = []\n    scanned_dir_list.append(\"help_docs\")\n    scanned_dir_list.append(\"examples\")\n    scanned_dir_list.append(\"examples/cdp_mode\")\n    scanned_dir_list.append(\"examples/cdp_mode/playwright\")\n    scanned_dir_list.append(\"examples/master_qa\")\n    scanned_dir_list.append(\"examples/presenter\")\n    scanned_dir_list.append(\"examples/behave_bdd\")\n    scanned_dir_list.append(\"examples/chart_maker\")\n    scanned_dir_list.append(\"examples/example_logs\")\n    scanned_dir_list.append(\"examples/tour_examples\")\n    scanned_dir_list.append(\"examples/visual_testing\")\n    scanned_dir_list.append(\"integrations/google_cloud\")\n    scanned_dir_list.append(\"seleniumbase/console_scripts\")\n    scanned_dir_list.append(\"examples/migration/raw_selenium\")\n    for scanned_dir in scanned_dir_list:\n        for dir_ in os.listdir(ROOT_DIR / scanned_dir):\n            files_to_process.append(os.path.join(scanned_dir, dir_))\n\n    video_embed = (\n        '<figure class=\"wp-block-embed wp-block-embed-youtube is-type-video '\n        'is-provider-youtube\"><div class=\"wp-block-embed__wrapper\">'\n        '<div class=\"epyt-video-wrapper fluid-width-video-wrapper\" '\n        'style=\"padding-top: 3px !important;\"><iframe loading=\"lazy\" '\n        'id=\"_ytid_36718\" data-origwidth=\"1200\" data-origheight=\"675\" '\n        'src=\"https://www.youtube.com/embed/yt_code?enablejsapi=1&amp;'\n        \"origin=https://seleniumbase.io&amp;autoplay=0&amp;cc_load_policy=0\"\n        \"&amp;cc_lang_pref=&amp;iv_load_policy=1&amp;loop=0&amp;\"\n        \"modestbranding=1&amp;rel=0&amp;fs=1&amp;playsinline=0&amp;\"\n        'autohide=2&amp;theme=dark&amp;color=red&amp;controls=1&amp;\" '\n        'class=\"__youtube_prefs__ no-lazyload\" title=\"YouTube player\" '\n        'allow=\"autoplay; encrypted-media\" allowfullscreen=\"\" '\n        'data-no-lazy=\"1\" data-skipgform_ajax_framebjll=\"\">'\n        \"</iframe></div></div></figure>\"\n    )\n\n    updated_files_to_process = []\n    for file_ in files_to_process:\n        if file_.endswith(\".md\"):\n            updated_files_to_process.append(file_)\n\n    for file_ in updated_files_to_process:\n        process_file(file_)\n\n    for file_ in updated_files_to_process:\n        readme_file = \"./mkdocs_build/\" + file_\n        with open(readme_file, mode=\"r\", encoding=\"utf-8\") as f:\n            all_code = f.read()\n        code_lines = all_code.split(\"\\n\")\n\n        changed = False\n        seleniumbase_lines = []\n        for line in code_lines:\n            if ' href=\"' in line and '.md\"' in line:\n                changed = True\n                line = line.replace('.md\"', '/\"')\n            if \"<!-- SeleniumBase Docs -->\" in line:\n                changed = True\n                new_lines = []\n                new_lines.append(\"---\")\n                new_lines.append(\"hide:\")\n                new_lines.append(\"  - toc\")\n                new_lines.append(\"---\")\n                for line in new_lines:\n                    seleniumbase_lines.append(line)\n                continue\n            if \"<!-- View on GitHub -->\" in line:\n                changed = True\n                line = (\n                    r'<p align=\"center\"><div align=\"center\">'\n                    r'<a href=\"https://github.com/seleniumbase/SeleniumBase\">'\n                    r'<img src=\"https://img.shields.io/badge/'\n                    r\"✅%20💛%20View%20Code-on%20GitHub%20🌎%20🚀\"\n                    r'-02A79E.svg\" alt=\"SeleniumBase on GitHub\" />'\n                    r\"</a></div></p>\"\n                )\n            alt_link_badge = (\n                '<a href=\"https://seleniumbase.io\">'\n                '<img src=\"https://img.shields.io/badge/docs-seleniumbase.io'\n                '-11BBAA.svg\" alt=\"SeleniumBase Docs\" /></a>'\n            )\n            back_to_gh = (\n                r'<a href=\"https://github.com/seleniumbase/SeleniumBase\">'\n                r'<img src=\"https://img.shields.io/badge/'\n                r\"✅%20View%20Code-on%20GitHub%20🌎\"\n                r'-02A79E.svg\" alt=\"SeleniumBase on GitHub\" />'\n                r\"</a>\"\n            )\n            if alt_link_badge in line:\n                line = line.replace(alt_link_badge, back_to_gh)\n                changed = True\n            if \"/help_docs/uc_mode/\" in line and file_.count(\"/\") >= 2:\n                line = line.replace(\n                    \"/help_docs/uc_mode/\", \"/../help_docs/uc_mode/\"\n                )\n                changed = True\n            if \"<!-- GitHub Only -->\" in line:\n                changed = True\n                continue\n            if \"<!-- YouTube View -->\" in line and \"watch?v=\" in line:\n                start_pt = line.find(\"watch?v=\") + len(\"watch?v=\")\n                end_pt = line.find('\"', start_pt + 1)\n                yt_code = line[start_pt:end_pt]\n                changed = True\n                line = video_embed.replace(\"yt_code\", yt_code)\n            if \"<!-- SeleniumBase Header1 -->\" in line:\n                changed = True\n                line = (\n                    '<section align=\"center\"><div align=\"center\">'\n                    \"<h2>✅ Reliable Browser Testing</h2>\"\n                    \"</div></section>\"\n                )\n            if \"<!-- SeleniumBase Docs -->\" in line:\n                changed = True\n                line = (\n                    '<h2><img '\n                    'src=\"https://seleniumbase.github.io/img/logo3b.png\" '\n                    'title=\"SeleniumBase\" width=\"24\" /> SeleniumBase Docs '\n                    '<img '\n                    'src=\"https://seleniumbase.github.io/img/logo3b.png\" '\n                    'title=\"SeleniumBase\" width=\"24\" /></h2>'\n                )\n            seleniumbase_lines.append(line)\n        if changed:\n            out_file = open(readme_file, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(seleniumbase_lines))\n            out_file.close()\n"
  },
  {
    "path": "mkdocs_build/requirements.txt",
    "content": "# mkdocs dependencies for generating the seleniumbase.io website\n# Minimum Python version: 3.10 (for generating docs only)\n\nregex>=2026.2.28\npymdown-extensions>=10.21\npipdeptree>=2.31.0\npython-dateutil>=2.8.2\nMarkdown==3.10.2\nclick==8.3.1\nghp-import==2.1.0\nwatchdog==6.0.0\ncairocffi==1.7.1\npathspec==1.0.4\nBabel==2.18.0\npaginate==0.5.7\nmkdocs==1.6.1\nmkdocs-material==9.6.23\nmkdocs-exclude-search==0.6.6\nmkdocs-simple-hooks==0.1.5\nmkdocs-get-deps==0.2.0\nmkdocs-material-extensions==1.3.1\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\r\nrequires = [\"setuptools>=70.2.0\", \"wheel>=0.44.0\"]\r\nbuild-backend = \"setuptools.build_meta\"\r\n\r\n[project]\r\nname = \"seleniumbase\"\r\nreadme = \"README.md\"\r\ndynamic = [\r\n    \"version\",\r\n    \"license\",\r\n    \"authors\",\r\n    \"scripts\",\r\n    \"keywords\",\r\n    \"classifiers\",\r\n    \"description\",\r\n    \"maintainers\",\r\n    \"entry-points\",\r\n    \"dependencies\",\r\n    \"requires-python\",\r\n    \"optional-dependencies\",\r\n]\r\n\r\n[project.urls]\r\n\"Homepage\" = \"https://github.com/seleniumbase/SeleniumBase\"\r\n\"Changelog\" = \"https://github.com/seleniumbase/SeleniumBase/releases\"\r\n\"Download\" = \"https://pypi.org/project/seleniumbase/#files\"\r\n\"Blog\" = \"https://seleniumbase.com/\"\r\n\"Discord\" = \"https://discord.gg/EdhQTn3EyE\"\r\n\"PyPI\" = \"https://pypi.org/project/seleniumbase/\"\r\n\"Source\" = \"https://github.com/seleniumbase/SeleniumBase\"\r\n\"Repository\" = \"https://github.com/seleniumbase/SeleniumBase\"\r\n\"Documentation\" = \"https://seleniumbase.io/\"\r\n\r\n[tool.setuptools]\r\npackages = [\r\n    \"seleniumbase\",\r\n    \"sbase\",\r\n    \"seleniumbase.behave\",\r\n    \"seleniumbase.common\",\r\n    \"seleniumbase.config\",\r\n    \"seleniumbase.console_scripts\",\r\n    \"seleniumbase.core\",\r\n    \"seleniumbase.drivers\",\r\n    \"seleniumbase.drivers.cft_drivers\",\r\n    \"seleniumbase.drivers.chs_drivers\",\r\n    \"seleniumbase.drivers.opera_drivers\",\r\n    \"seleniumbase.drivers.brave_drivers\",\r\n    \"seleniumbase.drivers.comet_drivers\",\r\n    \"seleniumbase.drivers.atlas_drivers\",\r\n    \"seleniumbase.drivers.chromium_drivers\",\r\n    \"seleniumbase.extensions\",\r\n    \"seleniumbase.fixtures\",\r\n    \"seleniumbase.js_code\",\r\n    \"seleniumbase.masterqa\",\r\n    \"seleniumbase.plugins\",\r\n    \"seleniumbase.resources\",\r\n    \"seleniumbase.translate\",\r\n    \"seleniumbase.undetected\",\r\n    \"seleniumbase.undetected.cdp_driver\",\r\n    \"seleniumbase.utilities\",\r\n    \"seleniumbase.utilities.selenium_grid\",\r\n    \"seleniumbase.utilities.selenium_ide\",\r\n]\r\n\r\n[tool.pytest.ini_options]\r\naddopts = [\"--capture=tee-sys\", \"-p no:cacheprovider\"]\r\nnorecursedirs = [\".*\", \"build\", \"dist\", \"recordings\", \"temp\", \"assets\"]\r\nfilterwarnings = [\r\n    \"ignore::pytest.PytestWarning\",\r\n    \"ignore:.*U.*mode is deprecated:DeprecationWarning\",\r\n]\r\njunit_family = [\"legacy\"]\r\npython_files = [\"test_*.py\", \"*_test.py\", \"*_tests.py\", \"*_suite.py\"]\r\npython_classes = [\"Test*\", \"*Test*\", \"*Test\", \"*Tests\", \"*Suite\"]\r\npython_functions = [\"test_*\"]\r\nmarkers = [\r\n    \"marker1\", \"marker2\", \"marker3\", \"marker_test_suite\",\r\n    \"local\", \"remote\", \"offline\", \"expected_failure\",\r\n    \"qa\", \"ci\", \"e2e\", \"smoke\", \"ready\", \"master\", \"deploy\",\r\n    \"develop\", \"staging\", \"production\", \"release\", \"active\",\r\n]\r\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\n\n# Display console output. Disable cacheprovider:\naddopts = --capture=tee-sys -p no:cacheprovider\n\n# Skip these directories during test collection:\nnorecursedirs = .* build dist recordings temp assets\n\n# Ignore DeprecationWarning, PytestUnknownMarkWarning\nfilterwarnings =\n    ignore::pytest.PytestWarning\n    ignore:.*U.*mode is deprecated:DeprecationWarning\n\n# Configure the junit_family option explicitly:\njunit_family = legacy\n\n# Set pytest discovery rules:\n# (Most of the rules here are similar to the default rules.)\n# (Inheriting unittest.TestCase could override these rules.)\npython_files = test_*.py *_test.py *_tests.py *_suite.py\npython_classes = Test* *Test* *Test *Tests *Suite\npython_functions = test_*\n\n# Common pytest markers used in examples:\n# (pytest may require marker registration to prevent warnings.)\n# (Future versions may turn those marker warnings into errors.)\nmarkers =\n    marker1: custom marker\n    marker2: custom marker\n    marker3: custom marker\n    marker_test_suite: custom marker\n    expected_failure: custom marker\n    local: custom marker\n    remote: custom marker\n    offline: custom marker\n    develop: custom marker\n    qa: custom marker\n    ci: custom marker\n    e2e: custom marker\n    ready: custom marker\n    smoke: custom marker\n    deploy: custom marker\n    active: custom marker\n    master: custom marker\n    release: custom marker\n    staging: custom marker\n    production: custom marker\n"
  },
  {
    "path": "requirements.txt",
    "content": "pip>=26.0.1\npackaging>=26.0\nsetuptools~=70.2;python_version<\"3.10\"\nsetuptools>=82.0.1;python_version>=\"3.10\"\nwheel>=0.46.3\nattrs>=25.4.0\ncertifi>=2026.2.25\nexceptiongroup>=1.3.1\nwebsockets~=15.0.1;python_version<\"3.10\"\nwebsockets>=16.0;python_version>=\"3.10\"\nfilelock~=3.19.1;python_version<\"3.10\"\nfilelock>=3.25.2;python_version>=\"3.10\"\nfasteners>=0.20\nmycdp>=1.3.6\npynose>=1.5.5\nplatformdirs~=4.4.0;python_version<\"3.10\"\nplatformdirs>=4.9.4;python_version>=\"3.10\"\ntyping-extensions>=4.15.0\nsbvirtualdisplay>=1.4.0\nMarkupSafe>=3.0.3\nJinja2>=3.1.6\nsix>=1.17.0\nparse>=1.21.1\nparse-type>=0.6.6\ncolorama>=0.4.6\npyyaml>=6.0.3\npygments>=2.19.2\npyreadline3>=3.5.4;platform_system==\"Windows\"\ntabcompleter>=1.4.0\npdbp>=1.8.2\nidna>=3.11\ncharset-normalizer>=3.4.6,<4\nurllib3>=1.26.20,<2;python_version<\"3.10\"\nurllib3>=1.26.20,<3;python_version>=\"3.10\"\nrequests~=2.32.5\nsniffio==1.3.1\nh11==0.16.0\noutcome==1.3.0.post0\ntrio>=0.31.0,<1;python_version<\"3.10\"\ntrio>=0.33.0,<1;python_version>=\"3.10\"\ntrio-websocket~=0.12.2\nwsproto==1.2.0;python_version<\"3.10\"\nwsproto~=1.3.2;python_version>=\"3.10\"\nwebsocket-client~=1.9.0\nselenium==4.32.0;python_version<\"3.10\"\nselenium==4.41.0;python_version>=\"3.10\"\ncssselect==1.3.0;python_version<\"3.10\"\ncssselect>=1.4.0,<2;python_version>=\"3.10\"\nnest-asyncio==1.6.0\nsortedcontainers==2.4.0\nexecnet==2.1.1;python_version<\"3.10\"\nexecnet==2.1.2;python_version>=\"3.10\"\niniconfig==2.1.0;python_version<\"3.10\"\niniconfig==2.3.0;python_version>=\"3.10\"\npluggy==1.6.0\npytest==8.4.2;python_version<\"3.11\"\npytest==9.0.2;python_version>=\"3.11\"\npytest-html==4.0.2\npytest-metadata==3.1.1\npytest-ordering==0.6\npytest-rerunfailures==16.0.1;python_version<\"3.10\"\npytest-rerunfailures==16.1;python_version>=\"3.10\"\npytest-xdist==3.8.0\nparameterized==0.9.0\nbehave==1.2.6\nsoupsieve~=2.8.3\nbeautifulsoup4~=4.14.3\npyotp==2.9.0\npython-xlib==0.33;platform_system==\"Linux\"\nPyAutoGUI>=0.9.54;platform_system==\"Linux\"\nmarkdown-it-py==3.0.0;python_version<\"3.10\"\nmarkdown-it-py==4.0.0;python_version>=\"3.10\"\nmdurl==0.1.2\nrich>=14.3.3,<15\n\n# --- Testing Requirements --- #\n# (\"pip install -r requirements.txt\" also installs this, but \"pip install -e .\" won't.)\n\ncoverage>=7.10.7;python_version<\"3.10\"\ncoverage>=7.13.5;python_version>=\"3.10\"\npytest-cov>=7.0.0\nflake8==7.3.0\nmccabe==0.7.0\npyflakes==3.4.0\npycodestyle==2.14.0\n"
  },
  {
    "path": "sbase/ReadMe.txt",
    "content": "\"SBase\" is the short name of \"SeleniumBase\".\nUse with console scripts: \"python -m sbase\".\n"
  },
  {
    "path": "sbase/__init__.py",
    "content": "from seleniumbase import __version__  # noqa\nfrom seleniumbase import BaseCase  # noqa\nfrom seleniumbase import decorators  # noqa\nfrom seleniumbase import Driver  # noqa\nfrom seleniumbase import DriverContext  # noqa\nfrom seleniumbase import encryption  # noqa\nfrom seleniumbase import get_driver  # noqa\nfrom seleniumbase import js_utils  # noqa\nfrom seleniumbase import shared_utils  # noqa\nfrom seleniumbase import MasterQA  # noqa\nfrom seleniumbase import page_actions  # noqa\nfrom seleniumbase import page_utils  # noqa\nfrom seleniumbase import SB  # noqa\nfrom seleniumbase import translate  # noqa\n"
  },
  {
    "path": "sbase/__main__.py",
    "content": "import os\r\nimport sys\r\n\r\n# Remove \"\" and current working directory from the first entry\r\n# of sys.path (if present) to avoid using the current directory\r\n# in SeleniumBase commands when invoked as \"python -m sbase <command>\"\r\nif sys.path[0] in (\"\", os.getcwd()):\r\n    sys.path.pop(0)\r\n\r\nif __package__ == \"\":\r\n    path = os.path.dirname(os.path.dirname(__file__))\r\n    sys.path.insert(0, path)\r\n\r\nif __name__ == \"__main__\":\r\n    import warnings\r\n    from seleniumbase.console_scripts.run import main\r\n\r\n    warnings.filterwarnings(\r\n        \"ignore\", category=DeprecationWarning, module=\".*packaging\\\\.version\"\r\n    )\r\n    main()\r\n    sys.exit()\r\n"
  },
  {
    "path": "sbase/steps.py",
    "content": "from behave import step\r\n\r\n\r\ndef normalize_text(text):\r\n    text = text.replace(\"\\\\\\\\\", \"\\\\\").replace(\"\\\\t\", \"\\t\").replace(\"\\\\n\", \"\\n\")\r\n    text = text.replace('\\\\\"', '\"').replace(\"\\\\'\", \"'\")\r\n    return text\r\n\r\n\r\n@step(\"Open '{url}'\")\r\n@step('Open \"{url}\"')\r\n@step(\"Open URL '{url}'\")\r\n@step('Open URL \"{url}\"')\r\n@step(\"User opens '{url}'\")\r\n@step('User opens \"{url}\"')\r\n@step(\"User opens URL '{url}'\")\r\n@step('User opens URL \"{url}\"')\r\n@step(\"User goes to '{url}'\")\r\n@step('User goes to \"{url}\"')\r\n@step(\"User goes to URL '{url}'\")\r\n@step('User goes to URL \"{url}\"')\r\ndef open_url(context, url):\r\n    sb = context.sb\r\n    sb.open(url)\r\n\r\n\r\n@step(\"Click '{selector}'\")\r\n@step('Click \"{selector}\"')\r\n@step(\"Click element '{selector}'\")\r\n@step('Click element \"{selector}\"')\r\n@step(\"User clicks '{selector}'\")\r\n@step('User clicks \"{selector}\"')\r\n@step(\"User clicks element '{selector}'\")\r\n@step('User clicks element \"{selector}\"')\r\ndef click_element(context, selector):\r\n    sb = context.sb\r\n    sb.click(selector)\r\n\r\n\r\n@step(\"Type text '{text}' into '{selector}'\")\r\n@step('Type text \"{text}\" into \"{selector}\"')\r\n@step(\"Type text '{text}' into \\\"{selector}\\\"\")\r\n@step('Type text \"{text}\" into \\'{selector}\\'')\r\n@step(\"Type text '{text}' in '{selector}'\")\r\n@step('Type text \"{text}\" in \"{selector}\"')\r\n@step(\"Type text '{text}' in \\\"{selector}\\\"\")\r\n@step('Type text \"{text}\" in \\'{selector}\\'')\r\n@step(\"Type '{text}' into '{selector}'\")\r\n@step('Type \"{text}\" into \"{selector}\"')\r\n@step(\"Type '{text}' into \\\"{selector}\\\"\")\r\n@step('Type \"{text}\" into \\'{selector}\\'')\r\n@step(\"Type '{text}' in '{selector}'\")\r\n@step('Type \"{text}\" in \"{selector}\"')\r\n@step(\"Type '{text}' in \\\"{selector}\\\"\")\r\n@step('Type \"{text}\" in \\'{selector}\\'')\r\n@step(\"In '{selector}' type '{text}'\")\r\n@step('In \"{selector}\" type \"{text}\"')\r\n@step(\"In '{selector}' type \\\"{text}\\\"\")\r\n@step('In \"{selector}\" type \\'{text}\\'')\r\n@step(\"Into '{selector}' type '{text}'\")\r\n@step('Into \"{selector}\" type \"{text}\"')\r\n@step(\"Into '{selector}' type \\\"{text}\\\"\")\r\n@step('Into \"{selector}\" type \\'{text}\\'')\r\n@step(\"Find '{selector}' and type '{text}'\")\r\n@step('Find \"{selector}\" and type \"{text}\"')\r\n@step(\"Find '{selector}' and type \\\"{text}\\\"\")\r\n@step('Find \"{selector}\" and type \\'{text}\\'')\r\n@step(\"User types '{text}' in '{selector}'\")\r\n@step('User types \"{text}\" in \"{selector}\"')\r\n@step(\"User types '{text}' in \\\"{selector}\\\"\")\r\n@step('User types \"{text}\" in \\'{selector}\\'')\r\n@step(\"User types '{text}' into '{selector}'\")\r\n@step('User types \"{text}\" into \"{selector}\"')\r\n@step(\"User types '{text}' into \\\"{selector}\\\"\")\r\n@step('User types \"{text}\" into \\'{selector}\\'')\r\ndef type_text(context, selector, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.type(selector, text)\r\n\r\n\r\n@step(\"Add text '{text}' into '{selector}'\")\r\n@step('Add text \"{text}\" into \"{selector}\"')\r\n@step(\"Add text '{text}' into \\\"{selector}\\\"\")\r\n@step('Add text \"{text}\" into \\'{selector}\\'')\r\n@step(\"Add text '{text}' in '{selector}'\")\r\n@step('Add text \"{text}\" in \"{selector}\"')\r\n@step(\"Add text '{text}' in \\\"{selector}\\\"\")\r\n@step('Add text \"{text}\" in \\'{selector}\\'')\r\n@step(\"Add '{text}' into '{selector}'\")\r\n@step('Add \"{text}\" into \"{selector}\"')\r\n@step(\"Add '{text}' into \\\"{selector}\\\"\")\r\n@step('Add \"{text}\" into \\'{selector}\\'')\r\n@step(\"Add '{text}' in '{selector}'\")\r\n@step('Add \"{text}\" in \"{selector}\"')\r\n@step(\"Add '{text}' in \\\"{selector}\\\"\")\r\n@step('Add \"{text}\" in \\'{selector}\\'')\r\n@step(\"Into '{selector}' add '{text}'\")\r\n@step('Into \"{selector}\" add \"{text}\"')\r\n@step(\"Into '{selector}' add \\\"{text}\\\"\")\r\n@step('Into \"{selector}\" add \\'{text}\\'')\r\n@step(\"In '{selector}' add '{text}'\")\r\n@step('In \"{selector}\" add \"{text}\"')\r\n@step(\"In '{selector}' add \\\"{text}\\\"\")\r\n@step('In \"{selector}\" add \\'{text}\\'')\r\n@step(\"User adds '{text}' in '{selector}'\")\r\n@step('User adds \"{text}\" in \"{selector}\"')\r\n@step(\"User adds '{text}' in \\\"{selector}\\\"\")\r\n@step('User adds \"{text}\" in \\'{selector}\\'')\r\n@step(\"User adds '{text}' into '{selector}'\")\r\n@step('User adds \"{text}\" into \"{selector}\"')\r\n@step(\"User adds '{text}' into \\\"{selector}\\\"\")\r\n@step('User adds \"{text}\" into \\'{selector}\\'')\r\ndef add_text(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.add_text(selector, text)\r\n\r\n\r\n@step(\"Assert element '{selector}'\")\r\n@step('Assert element \"{selector}\"')\r\n@step(\"Assert element '{selector}' is visible\")\r\n@step('Assert element \"{selector}\" is visible')\r\n@step(\"Element '{selector}' should be visible\")\r\n@step('Element \"{selector}\" should be visible')\r\ndef assert_element(context, selector):\r\n    sb = context.sb\r\n    sb.assert_element(selector)\r\n\r\n\r\n@step(\"Assert text '{text}' in '{selector}'\")\r\n@step('Assert text \"{text}\" in \"{selector}\"')\r\n@step(\"Assert text '{text}' in \\\"{selector}\\\"\")\r\n@step('Assert text \"{text}\" in \\'{selector}\\'')\r\n@step(\"Text in '{selector}' should contain '{text}'\")\r\n@step('Text in \"{selector}\" should contain \"{text}\"')\r\n@step(\"Text in '{selector}' should contain \\\"{text}\\\"\")\r\n@step('Text in \"{selector}\" should contain \\'{text}\\'')\r\ndef assert_text_in_element(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_text(text, selector)\r\n\r\n\r\n@step(\"Assert text '{text}'\")\r\n@step('Assert text \"{text}\"')\r\n@step(\"Assert text '{text}' is visible\")\r\n@step('Assert text \"{text}\" is visible')\r\n@step(\"Text '{text}' should be visible\")\r\n@step('Text \"{text}\" should be visible')\r\ndef assert_text(context, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_text(text)\r\n\r\n\r\n@step(\"Assert exact text '{text}' in '{selector}'\")\r\n@step('Assert exact text \"{text}\" in \"{selector}\"')\r\n@step(\"Assert exact text '{text}' in \\\"{selector}\\\"\")\r\n@step('Assert exact text \"{text}\" in \\'{selector}\\'')\r\n@step(\"Text in '{selector}' should be '{text}'\")\r\n@step('Text in \"{selector}\" should be \"{text}\"')\r\n@step(\"Text in '{selector}' should be \\\"{text}\\\"\")\r\n@step('Text in \"{selector}\" should be \\'{text}\\'')\r\ndef assert_exact_text(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_exact_text(text, selector)\r\n\r\n\r\n@step(\"Assert non-empty text in '{selector}'\")\r\n@step('Assert non-empty text in \"{selector}\"')\r\n@step(\"Assert text in '{selector}' is not empty\")\r\n@step('Assert text in \"{selector}\" is not empty')\r\n@step(\"Text in '{selector}' should be non-empty\")\r\n@step('Text in \"{selector}\" should be non-empty')\r\n@step(\"Text in '{selector}' should not be empty\")\r\n@step('Text in \"{selector}\" should not be empty')\r\ndef assert_non_empty_text(context, selector):\r\n    sb = context.sb\r\n    sb.assert_non_empty_text(selector)\r\n\r\n\r\n@step(\"Highlight '{selector}'\")\r\n@step('Highlight \"{selector}\"')\r\n@step(\"Highlight element '{selector}'\")\r\n@step('Highlight element \"{selector}\"')\r\n@step(\"Use JS to highlight '{selector}'\")\r\n@step('Use JS to highlight \"{selector}\"')\r\ndef highlight_element(context, selector):\r\n    sb = context.sb\r\n    sb.highlight(selector)\r\n\r\n\r\n@step(\"Click link '{link}'\")\r\n@step('Click link \"{link}\"')\r\n@step(\"User clicks link '{link}'\")\r\n@step('User clicks link \"{link}\"')\r\ndef click_link(context, link):\r\n    sb = context.sb\r\n    sb.click_link(link)\r\n\r\n\r\n@step(\"JS click '{selector}'\")\r\n@step('JS click \"{selector}\"')\r\n@step(\"JS click element '{selector}'\")\r\n@step('JS click element \"{selector}\"')\r\n@step(\"Use JS to click '{selector}'\")\r\n@step('Use JS to click \"{selector}\"')\r\ndef js_click(context, selector):\r\n    sb = context.sb\r\n    sb.js_click(selector)\r\n\r\n\r\n@step(\"Save screenshot as '{name}'\")\r\n@step('Save screenshot as \"{name}\"')\r\n@step(\"User saves screenshot as '{name}'\")\r\n@step('User saves screenshot as \"{name}\"')\r\ndef save_screenshot_as(context, name):\r\n    sb = context.sb\r\n    name = normalize_text(name)\r\n    sb.save_screenshot(name)\r\n\r\n\r\n@step(\"Save screenshot to '{folder}' as '{name}'\")\r\n@step('Save screenshot to \"{folder}\" as \"{name}\"')\r\n@step(\"Save screenshot to '{folder}' as \\\"{name}\\\"\")\r\n@step('Save screenshot to \"{folder}\" as \\'{name}\\'')\r\n@step(\"User saves screenshot to '{folder}' as '{name}'\")\r\n@step('User saves screenshot to \"{folder}\" as \"{name}\"')\r\n@step(\"User saves screenshot to '{folder}' as \\\"{name}\\\"\")\r\n@step('User saves screenshot to \"{folder}\" as \\'{name}\\'')\r\ndef save_screenshot_to_folder_as(context, name, folder):\r\n    sb = context.sb\r\n    name = normalize_text(name)\r\n    sb.save_screenshot(name, folder)\r\n\r\n\r\n@step(\"Save screenshot to logs\")\r\n@step(\"Save a screenshot to the logs\")\r\n@step(\"User saves screenshot to logs\")\r\n@step(\"User saves a screenshot to the logs\")\r\ndef save_screenshot_to_logs(context):\r\n    sb = context.sb\r\n    sb.save_screenshot_to_logs()\r\n\r\n\r\n@step(\"Refresh page\")\r\n@step(\"Reload page\")\r\n@step(\"User refreshes the page\")\r\n@step(\"User reloads the page\")\r\ndef refresh_page(context):\r\n    sb = context.sb\r\n    sb.refresh_page()\r\n\r\n\r\n@step(\"Go back\")\r\n@step(\"User goes back\")\r\n@step(\"User navigates back\")\r\ndef go_back(context):\r\n    sb = context.sb\r\n    sb.go_back()\r\n\r\n\r\n@step(\"Go forward\")\r\n@step(\"User goes forward\")\r\n@step(\"User navigates forward\")\r\ndef go_forward(context):\r\n    sb = context.sb\r\n    sb.go_forward()\r\n\r\n\r\n@step(\"Set value of '{selector}' to '{text}'\")\r\n@step('Set value of \"{selector}\" to \"{text}\"')\r\n@step(\"Set value of \\\"{selector}\\\" to '{text}'\")\r\n@step('Set value of \\'{selector}\\' to \"{text}\"')\r\n@step(\"User sets value of '{selector}' to '{text}'\")\r\n@step('User sets value of \"{selector}\" to \"{text}\"')\r\n@step(\"User sets value of \\\"{selector}\\\" to '{text}'\")\r\n@step('User sets value of \\'{selector}\\' to \"{text}\"')\r\ndef set_value(context, selector, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.set_value(selector, text)\r\n\r\n\r\n@step(\"Switch to iframe '{frame}'\")\r\n@step('Switch to iframe \"{frame}\"')\r\n@step(\"Switch to frame '{frame}'\")\r\n@step('Switch to frame \"{frame}\"')\r\n@step(\"User switches to iframe '{frame}'\")\r\n@step('User switches to iframe \"{frame}\"')\r\n@step(\"User switches to frame '{frame}'\")\r\n@step('User switches to frame \"{frame}\"')\r\ndef switch_to_frame(context, frame):\r\n    sb = context.sb\r\n    sb.switch_to_frame(frame)\r\n\r\n\r\n@step(\"Switch to default content\")\r\n@step(\"Exit from iframes\")\r\n@step(\"Exit from frames\")\r\n@step(\"User switches to default content\")\r\n@step(\"User exits from iframes\")\r\n@step(\"User exits from frames\")\r\ndef switch_to_default_content(context):\r\n    sb = context.sb\r\n    sb.switch_to_default_content()\r\n\r\n\r\n@step(\"Switch to parent frame\")\r\n@step(\"Exit current iframe\")\r\n@step(\"Exit current frame\")\r\n@step(\"User switches to parent frame\")\r\n@step(\"User exits current iframe\")\r\n@step(\"User exits current frame\")\r\ndef switch_to_parent_frame(context):\r\n    sb = context.sb\r\n    sb.switch_to_parent_frame()\r\n\r\n\r\n@step(\"Into '{selector}' enter MFA code '{totp_key}'\")\r\n@step('Into \"{selector}\" enter MFA code \"{totp_key}\"')\r\n@step(\"Into '{selector}' enter MFA code \\\"{totp_key}\\\"\")\r\n@step('Into \"{selector}\" enter MFA code \\'{totp_key}\\'')\r\n@step(\"Into '{selector}' do MFA '{totp_key}'\")\r\n@step('Into \"{selector}\" do MFA \"{totp_key}\"')\r\n@step(\"Into '{selector}' do MFA \\\"{totp_key}\\\"\")\r\n@step('Into \"{selector}\" do MFA \\'{totp_key}\\'')\r\n@step(\"Do MFA '{totp_key}' into '{selector}'\")\r\n@step('Do MFA \"{totp_key}\" into \"{selector}\"')\r\n@step(\"Do MFA \\\"{totp_key}\\\" into '{selector}'\")\r\n@step('Do MFA \\'{totp_key}\\' into \"{selector}\"')\r\n@step(\"Enter MFA code '{totp_key}' into '{selector}'\")\r\n@step('Enter MFA code \"{totp_key}\" into \"{selector}\"')\r\n@step(\"Enter MFA code \\\"{totp_key}\\\" into '{selector}'\")\r\n@step('Enter MFA code \\'{totp_key}\\' into \"{selector}\"')\r\n@step(\"User enters MFA code '{totp_key}' into '{selector}'\")\r\n@step('User enters MFA code \"{totp_key}\" into \"{selector}\"')\r\n@step(\"User enters MFA code \\\"{totp_key}\\\" into '{selector}'\")\r\n@step('User enters MFA code \\'{totp_key}\\' into \"{selector}\"')\r\ndef enter_mfa_code(context, selector, totp_key):\r\n    sb = context.sb\r\n    sb.enter_mfa_code(selector, totp_key)\r\n\r\n\r\n@step(\"Open if not '{url}'\")\r\n@step('Open if not \"{url}\"')\r\n@step(\"Open if not URL '{url}'\")\r\n@step('Open if not URL \"{url}\"')\r\n@step(\"User opens '{url}' if not on page\")\r\n@step('User opens \"{url}\" if not on page')\r\n@step(\"User opens URL '{url}' if not on page\")\r\n@step('User opens URL \"{url}\" if not on page')\r\ndef open_if_not_url(context, url):\r\n    sb = context.sb\r\n    sb.open_if_not_url(url)\r\n\r\n\r\n@step(\"Select if unselected '{selector}'\")\r\n@step('Select if unselected \"{selector}\"')\r\n@step(\"Select '{selector}' if unselected\")\r\n@step('Select \"{selector}\" if unselected')\r\n@step(\"User selects '{selector}' if unselected\")\r\n@step('User selects \"{selector}\" if unselected')\r\ndef select_if_unselected(context, selector):\r\n    sb = context.sb\r\n    sb.select_if_unselected(selector)\r\n\r\n\r\n@step(\"Unselect if selected '{selector}'\")\r\n@step('Unselect if selected \"{selector}\"')\r\n@step(\"Unselect '{selector}' if selected\")\r\n@step('Unselect \"{selector}\" if selected')\r\n@step(\"User unselects '{selector}' if selected\")\r\n@step('User unselects \"{selector}\" if selected')\r\ndef unselect_if_selected(context, selector):\r\n    sb = context.sb\r\n    sb.unselect_if_selected(selector)\r\n\r\n\r\n@step(\"Check if unchecked '{selector}'\")\r\n@step('Check if unchecked \"{selector}\"')\r\n@step(\"Check '{selector}' if unchecked\")\r\n@step('Check \"{selector}\" if unchecked')\r\n@step(\"User checks '{selector}' if unchecked\")\r\n@step('User checks \"{selector}\" if unchecked')\r\ndef check_if_unchecked(context, selector):\r\n    sb = context.sb\r\n    sb.check_if_unchecked(selector)\r\n\r\n\r\n@step(\"Uncheck if checked '{selector}'\")\r\n@step('Uncheck if checked \"{selector}\"')\r\n@step(\"Uncheck '{selector}' if checked\")\r\n@step('Uncheck \"{selector}\" if checked')\r\n@step(\"User unchecks '{selector}' if checked\")\r\n@step('User unchecks \"{selector}\" if checked')\r\ndef uncheck_if_checked(context, selector):\r\n    sb = context.sb\r\n    sb.uncheck_if_checked(selector)\r\n\r\n\r\n@step(\"Drag '{drag_selector}' into '{drop_selector}'\")\r\n@step('Drag \"{drag_selector}\" into \"{drop_selector}\"')\r\n@step(\"Drag '{drag_selector}' into \\\"{drop_selector}\\\"\")\r\n@step('Drag \"{drag_selector}\" into \\'{drop_selector}\\'')\r\n@step(\"User drags '{drag_selector}' into '{drop_selector}'\")\r\n@step('User drags \"{drag_selector}\" into \"{drop_selector}\"')\r\n@step(\"User drags '{drag_selector}' into \\\"{drop_selector}\\\"\")\r\n@step('User drags \"{drag_selector}\" into \\'{drop_selector}\\'')\r\ndef drag_and_drop(context, drag_selector, drop_selector):\r\n    sb = context.sb\r\n    sb.drag_and_drop(drag_selector, drop_selector)\r\n\r\n\r\n@step(\"Hover '{hover_selector}' and click '{click_selector}'\")\r\n@step('Hover \"{hover_selector}\" and click \"{click_selector}\"')\r\n@step(\"Hover '{hover_selector}' and click \\\"{click_selector}\\\"\")\r\n@step('Hover \"{hover_selector}\" and click \\'{click_selector}\\'')\r\n@step(\"User hovers '{hover_selector}' and clicks '{click_selector}'\")\r\n@step('User hovers \"{hover_selector}\" and clicks \"{click_selector}\"')\r\n@step(\"User hovers '{hover_selector}' and clicks \\\"{click_selector}\\\"\")\r\n@step('User hovers \"{hover_selector}\" and clicks \\'{click_selector}\\'')\r\ndef hover_and_click(context, hover_selector, click_selector):\r\n    sb = context.sb\r\n    sb.hover_and_click(hover_selector, click_selector)\r\n\r\n\r\n@step(\"Find '{selector}' and select '{text}'\")\r\n@step('Find \"{selector}\" and select \"{text}\"')\r\n@step(\"Find '{selector}' and select \\\"{text}\\\"\")\r\n@step('Find \"{selector}\" and select \\'{text}\\'')\r\n@step(\"User selects '{text}' in '{selector}'\")\r\n@step('User selects \"{text}\" in \"{selector}\"')\r\n@step(\"User selects \\\"{text}\\\" in '{selector}'\")\r\n@step('User selects \\'{text}\\' in \"{selector}\"')\r\n@step(\"User finds '{selector}' and selects '{text}'\")\r\n@step('User finds \"{selector}\" and selects \"{text}\"')\r\n@step(\"User finds '{selector}' and selects \\\"{text}\\\"\")\r\n@step('User finds \"{selector}\" and selects \\'{text}\\'')\r\ndef select_option_by_text(context, selector, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.select_option_by_text(selector, text)\r\n\r\n\r\n@step(\"Find '{selector}' and select '{text}' by {option}\")\r\n@step('Find \"{selector}\" and select \"{text}\" by {option}')\r\n@step(\"Find '{selector}' and select \\\"{text}\\\" by {option}\")\r\n@step('Find \"{selector}\" and select \\'{text}\\' by {option}')\r\n@step(\"User finds '{selector}' and selects '{text}' by {option}\")\r\n@step('User finds \"{selector}\" and selects \"{text}\" by {option}')\r\n@step(\"User finds '{selector}' and selects \\\"{text}\\\" by {option}\")\r\n@step('User finds \"{selector}\" and selects \\'{text}\\' by {option}')\r\ndef select_option_by_option(context, selector, text, option):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    if option.startswith(\"'\") or option.startswith('\"'):\r\n        option = option[1:]\r\n    if option.endswith(\"'\") or option.endswith('\"'):\r\n        option = option[:-1]\r\n    if option == \"text\":\r\n        sb.select_option_by_text(selector, text)\r\n    elif option == \"index\":\r\n        sb.select_option_by_index(selector, text)\r\n    elif option == \"value\":\r\n        sb.select_option_by_value(selector, text)\r\n    else:\r\n        raise Exception(\"Unknown option: %s\" % option)\r\n\r\n\r\n@step(\"Wait for '{selector}' to be visible\")\r\n@step('Wait for \"{selector}\" to be visible')\r\n@step(\"Wait for element '{selector}'\")\r\n@step('Wait for element \"{selector}\"')\r\n@step(\"User waits for '{selector}' to be visible\")\r\n@step('User waits for \"{selector}\" to be visible')\r\n@step(\"User waits for element '{selector}'\")\r\n@step('User waits for element \"{selector}\"')\r\ndef wait_for_element(context, selector):\r\n    sb = context.sb\r\n    sb.wait_for_element(selector)\r\n\r\n\r\n@step(\"Wait for text '{text}' in '{selector}'\")\r\n@step('Wait for text \"{text}\" in \"{selector}\"')\r\n@step(\"Wait for text '{text}' in \\\"{selector}\\\"\")\r\n@step('Wait for text \"{text}\" in \\'{selector}\\'')\r\n@step(\"Wait for '{selector}' to have text '{text}'\")\r\n@step('Wait for \"{selector}\" to have text \"{text}\"')\r\n@step('Wait for \"{selector}\" to have text \\'{text}\\'')\r\n@step(\"Wait for '{selector}' to have text \\\"{text}\\\"\")\r\n@step(\"User waits for text '{text}' in '{selector}'\")\r\n@step('User waits for text \"{text}\" in \"{selector}\"')\r\n@step(\"User waits for text '{text}' in \\\"{selector}\\\"\")\r\n@step('User waits for text \"{text}\" in \\'{selector}\\'')\r\n@step(\"User waits for '{selector}' to have text '{text}'\")\r\n@step('User waits for \"{selector}\" to have text \"{text}\"')\r\n@step('User waits for \"{selector}\" to have text \\'{text}\\'')\r\n@step(\"User waits for '{selector}' to have text \\\"{text}\\\"\")\r\ndef wait_for_text_in_element(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.wait_for_text(text, selector)\r\n\r\n\r\n@step(\"Wait for exact text '{text}' in '{selector}'\")\r\n@step('Wait for exact text \"{text}\" in \"{selector}\"')\r\n@step(\"Wait for exact text '{text}' in \\\"{selector}\\\"\")\r\n@step('Wait for exact text \"{text}\" in \\'{selector}\\'')\r\n@step(\"Wait for '{selector}' to have exact text '{text}'\")\r\n@step('Wait for \"{selector}\" to have exact text \"{text}\"')\r\n@step('Wait for \"{selector}\" to have exact text \\'{text}\\'')\r\n@step(\"Wait for '{selector}' to have exact text \\\"{text}\\\"\")\r\n@step(\"User waits for exact text '{text}' in '{selector}'\")\r\n@step('User waits for exact text \"{text}\" in \"{selector}\"')\r\n@step(\"User waits for exact text '{text}' in \\\"{selector}\\\"\")\r\n@step('User waits for exact text \"{text}\" in \\'{selector}\\'')\r\n@step(\"User waits for '{selector}' to have exact text '{text}'\")\r\n@step('User waits for \"{selector}\" to have exact text \"{text}\"')\r\n@step('User waits for \"{selector}\" to have exact text \\'{text}\\'')\r\n@step(\"User waits for '{selector}' to have exact text \\\"{text}\\\"\")\r\ndef wait_for_exact_text_in_element(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.wait_for_exact_text(text, selector)\r\n\r\n\r\n@step(\"Wait for non-empty text in '{selector}'\")\r\n@step('Wait for non-empty text in \"{selector}\"')\r\n@step(\"Wait for '{selector}' to have non-empty text\")\r\n@step('Wait for \"{selector}\" to have non-empty text')\r\n@step(\"User waits for non-empty text in '{selector}'\")\r\n@step('User waits for non-empty text in \"{selector}\"')\r\n@step(\"User waits for '{selector}' to have non-empty text\")\r\n@step('User waits for \"{selector}\" to have non-empty text')\r\n@step(\"Wait for '{selector}' to not have text\")\r\n@step('Wait for \"{selector}\" to not have text')\r\n@step(\"Wait for text in '{selector}' to not be empty\")\r\n@step('Wait for text in \"{selector}\" to not be empty')\r\n@step(\"User waits for '{selector}' to not have text\")\r\n@step('User waits for \"{selector}\" to not have text')\r\n@step(\"User waits for text in '{selector}' to not be empty\")\r\n@step('User waits for text in \"{selector}\" to not be empty')\r\ndef wait_for_non_empty_text_in_element(context, selector):\r\n    sb = context.sb\r\n    sb.wait_for_non_empty_text(selector)\r\n\r\n\r\n@step(\"Wait for text '{text}'\")\r\n@step('Wait for text \"{text}\"')\r\n@step(\"User waits for text '{text}'\")\r\n@step('User waits for text \"{text}\"')\r\ndef wait_for_text(context, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.wait_for_text(text)\r\n\r\n\r\n@step(\"Double click '{selector}'\")\r\n@step('Double click \"{selector}\"')\r\n@step(\"Double click element '{selector}'\")\r\n@step('Double click element \"{selector}\"')\r\n@step(\"User double clicks '{selector}'\")\r\n@step('User double clicks \"{selector}\"')\r\n@step(\"User double clicks element '{selector}'\")\r\n@step('User double clicks element \"{selector}\"')\r\ndef double_click_element(context, selector):\r\n    sb = context.sb\r\n    sb.double_click(selector)\r\n\r\n\r\n@step(\"Slow click '{selector}'\")\r\n@step('Slow click \"{selector}\"')\r\n@step(\"Slow click element '{selector}'\")\r\n@step('Slow click element \"{selector}\"')\r\n@step(\"User slow clicks '{selector}'\")\r\n@step('User slow clicks \"{selector}\"')\r\n@step(\"User slow clicks element '{selector}'\")\r\n@step('User slow clicks element \"{selector}\"')\r\ndef slow_click_element(context, selector):\r\n    sb = context.sb\r\n    sb.slow_click(selector)\r\n\r\n\r\n@step(\"Clear text field '{selector}'\")\r\n@step('Clear text field \"{selector}\"')\r\n@step(\"Clear text in '{selector}'\")\r\n@step('Clear text in \"{selector}\"')\r\n@step(\"User clears text field '{selector}'\")\r\n@step('User clears text field \"{selector}\"')\r\n@step(\"User clears text in '{selector}'\")\r\n@step('User clears text in \"{selector}\"')\r\ndef clear_text_field(context, selector):\r\n    sb = context.sb\r\n    sb.clear(selector)\r\n\r\n\r\n@step(\"Maximize window\")\r\n@step(\"Maximize the window\")\r\n@step(\"User maximizes window\")\r\n@step(\"User maximizes the window\")\r\ndef maximize_window(context):\r\n    sb = context.sb\r\n    sb.maximize_window()\r\n\r\n\r\n@step(\"Get new driver\")\r\n@step(\"User gets new driver\")\r\ndef get_new_driver(context):\r\n    sb = context.sb\r\n    sb.get_new_driver()\r\n\r\n\r\n@step(\"Switch to default driver\")\r\n@step(\"User switches to default driver\")\r\ndef switch_to_default_driver(context):\r\n    sb = context.sb\r\n    sb.switch_to_default_driver()\r\n\r\n\r\n@step(\"Press up arrow\")\r\n@step(\"User presses up arrow\")\r\ndef press_up_arrow(context):\r\n    sb = context.sb\r\n    sb.press_up_arrow()\r\n\r\n\r\n@step(\"Press down arrow\")\r\n@step(\"User presses down arrow\")\r\ndef press_down_arrow(context):\r\n    sb = context.sb\r\n    sb.press_down_arrow()\r\n\r\n\r\n@step(\"Press left arrow\")\r\n@step(\"User presses left arrow\")\r\ndef press_left_arrow(context):\r\n    sb = context.sb\r\n    sb.press_left_arrow()\r\n\r\n\r\n@step(\"Press right arrow\")\r\n@step(\"User presses right arrow\")\r\ndef press_right_arrow(context):\r\n    sb = context.sb\r\n    sb.press_right_arrow()\r\n\r\n\r\n@step(\"Clear all cookies\")\r\n@step(\"Delete all cookies\")\r\n@step(\"User clears all cookies\")\r\n@step(\"User deletes all cookies\")\r\ndef delete_all_cookies(context):\r\n    sb = context.sb\r\n    sb.delete_all_cookies()\r\n\r\n\r\n@step(\"Clear Local Storage\")\r\n@step(\"Delete Local Storage\")\r\n@step(\"User clears Local Storage\")\r\n@step(\"User deletes Local Storage\")\r\ndef clear_local_storage(context):\r\n    sb = context.sb\r\n    sb.clear_local_storage()\r\n\r\n\r\n@step(\"Clear Session Storage\")\r\n@step(\"Delete Session Storage\")\r\n@step(\"User clears Session Storage\")\r\n@step(\"User deletes Session Storage\")\r\ndef clear_session_storage(context):\r\n    sb = context.sb\r\n    sb.clear_session_storage()\r\n\r\n\r\n@step(\"JS click all '{selector}'\")\r\n@step('JS click all \"{selector}\"')\r\n@step(\"Use JS to click all '{selector}'\")\r\n@step('Use JS to click all \"{selector}\"')\r\ndef js_click_all(context, selector):\r\n    sb = context.sb\r\n    sb.js_click_all(selector)\r\n\r\n\r\n@step(\"Click '{selector}' at ({px},{py})\")\r\n@step('Click \"{selector}\" at ({px},{py})')\r\n@step(\"Click '{selector}' at ({px}, {py})\")\r\n@step('Click \"{selector}\" at ({px}, {py})')\r\n@step(\"User clicks '{selector}' at ({px},{py})\")\r\n@step('User clicks \"{selector}\" at ({px},{py})')\r\n@step(\"User clicks '{selector}' at ({px}, {py})\")\r\n@step('User clicks \"{selector}\" at ({px}, {py})')\r\ndef click_with_offset(context, selector, px, py):\r\n    sb = context.sb\r\n    sb.click_with_offset(selector, px, py)\r\n\r\n\r\n@step(\"In '{selector}' choose file '{file_path}'\")\r\n@step('In \"{selector}\" choose file \"{file_path}\"')\r\n@step(\"In '{selector}' choose file \\\"{file_path}\\\"\")\r\n@step('In \"{selector}\" choose file \\'{file_path}\\'')\r\n@step(\"Into '{selector}' choose file '{file_path}'\")\r\n@step('Into \"{selector}\" choose file \"{file_path}\"')\r\n@step(\"Into '{selector}' choose file \\\"{file_path}\\\"\")\r\n@step('Into \"{selector}\" choose file \\'{file_path}\\'')\r\n@step(\"User chooses file '{file_path}' for '{selector}'\")\r\n@step('User chooses file \"{file_path}\" for \"{selector}\" ')\r\n@step(\"User chooses file \\\"{file_path}\\\" for '{selector}' \")\r\n@step('User chooses file \\'{file_path}\\' for \"{selector}\" ')\r\ndef choose_file(context, selector, file_path):\r\n    sb = context.sb\r\n    sb.choose_file(selector, file_path)\r\n\r\n\r\n@step(\"Set content to frame '{frame}'\")\r\n@step('Set content to frame \"{frame}\"')\r\n@step(\"User sets content to frame '{frame}'\")\r\n@step('User sets content to frame \"{frame}\"')\r\ndef set_content_to_frame(context, frame):\r\n    sb = context.sb\r\n    sb.set_content_to_frame(frame)\r\n\r\n\r\n@step(\"Set content to default\")\r\n@step(\"User sets content to default\")\r\ndef set_content_to_default(context):\r\n    sb = context.sb\r\n    sb.set_content_to_default()\r\n\r\n\r\n@step(\"Set content to parent\")\r\n@step(\"User sets content to parent\")\r\ndef set_content_to_parent(context):\r\n    sb = context.sb\r\n    sb.set_content_to_parent()\r\n\r\n\r\n@step(\"Assert element present '{selector}'\")\r\n@step('Assert element present \"{selector}\"')\r\n@step(\"Element '{selector}' should be present\")\r\n@step('Element \"{selector}\" should be present')\r\ndef assert_element_present(context, selector):\r\n    sb = context.sb\r\n    sb.assert_element_present(selector)\r\n\r\n\r\n@step(\"Assert element not visible '{selector}'\")\r\n@step('Assert element not visible \"{selector}\"')\r\n@step(\"Element '{selector}' should not be visible\")\r\n@step('Element \"{selector}\" should not be visible')\r\ndef assert_element_not_visible(context, selector):\r\n    sb = context.sb\r\n    sb.assert_element_not_visible(selector)\r\n\r\n\r\n@step(\"Assert link text '{text}'\")\r\n@step('Assert link text \"{text}\"')\r\n@step(\"Link text '{text}' should be visible\")\r\n@step('Link text \"{text}\" should be visible')\r\ndef assert_link_text(context, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_link_text(text)\r\n\r\n\r\n@step(\"Assert title '{title}'\")\r\n@step('Assert title \"{title}\"')\r\n@step(\"The title should be '{title}'\")\r\n@step('The title should be \"{title}\"')\r\ndef assert_title(context, title):\r\n    sb = context.sb\r\n    title = normalize_text(title)\r\n    sb.assert_title(title)\r\n\r\n\r\n@step(\"Assert downloaded file '{file}'\")\r\n@step('Assert downloaded file \"{file}\"')\r\n@step(\"File '{file}' should be in downloads\")\r\n@step('File \"{file}\" should be in downloads')\r\ndef assert_downloaded_file(context, file):\r\n    sb = context.sb\r\n    sb.assert_downloaded_file(file)\r\n\r\n\r\n@step(\"Download '{file}' to downloads\")\r\n@step('Download \"{file}\" to downloads')\r\n@step(\"Download file '{file}' to downloads\")\r\n@step('Download file \"{file}\" to downloads')\r\n@step(\"User downloads '{file}' to downloads\")\r\n@step('User downloads \"{file}\" to downloads')\r\ndef download_file(context, file):\r\n    sb = context.sb\r\n    sb.download_file(file)\r\n\r\n\r\n@step(\"Download '{file}' to '{destination}'\")\r\n@step('Download \"{file}\" to \"{destination}\"')\r\n@step(\"Download file '{file}' to '{destination}'\")\r\n@step('Download file \"{file}\" to \"{destination}\"')\r\n@step(\"User downloads '{file}' to '{destination}'\")\r\n@step('User downloads \"{file}\" to \"{destination}\"')\r\ndef download_file_to_destination(context, file, destination):\r\n    sb = context.sb\r\n    sb.download_file(file, destination)\r\n\r\n\r\n@step(\"In '{selector}' assert attribute \\'{attribute}\\'\")\r\n@step('In \"{selector}\" assert attribute \\\"{attribute}\\\"')\r\n@step(\"In \\\"{selector}\\\" assert attribute '{attribute}'\")\r\n@step('In \\'{selector}\\' assert attribute \"{attribute}\"')\r\ndef assert_attribute(context, selector, attribute):\r\n    sb = context.sb\r\n    sb.assert_attribute(selector, attribute)\r\n\r\n\r\n@step(\"In '{selector}' assert attribute/value '{attribute}'/'{value}'\")\r\n@step('In \"{selector}\" assert attribute/value \"{attribute}\"/\"{value}\"')\r\n@step(\"In \\\"{selector}\\\" assert attribute/value '{attribute}'/\\\"{value}\\\"\")\r\n@step('In \\'{selector}\\' assert attribute/value \"{attribute}\"/\\'{value}\\'')\r\n@step(\"In '{selector}' assert attribute/value '{attribute}'/\\\"{value}\\\"\")\r\n@step('In \"{selector}\" assert attribute/value \"{attribute}\"/\\'{value}\\'')\r\n@step(\"In \\\"{selector}\\\" assert attribute/value '{attribute}'/'{value}'\")\r\n@step('In \\'{selector}\\' assert attribute/value \"{attribute}\"/\"{value}\"')\r\ndef assert_attribute_has_value(context, selector, attribute, value):\r\n    sb = context.sb\r\n    value = normalize_text(value)\r\n    sb.assert_attribute(selector, attribute, value)\r\n\r\n\r\n@step(\"Show file choosers\")\r\n@step(\"Show hidden file choosers\")\r\n@step(\"Use JS to show file choosers\")\r\n@step(\"Use JS to show hidden file choosers\")\r\ndef show_file_choosers(context):\r\n    sb = context.sb\r\n    sb.show_file_choosers()\r\n\r\n\r\n@step(\"Sleep for {seconds} seconds\")\r\n@step(\"Wait for {seconds} seconds\")\r\n@step(\"User sleeps for {seconds} seconds\")\r\n@step(\"User waits for {seconds} seconds\")\r\ndef sleep(context, seconds):\r\n    sb = context.sb\r\n    sb.sleep(float(seconds))\r\n\r\n\r\n@step(\"Activate Demo Mode\")\r\n@step(\"User activates Demo Mode\")\r\ndef activate_demo_mode(context):\r\n    sb = context.sb\r\n    sb.activate_demo_mode()\r\n\r\n\r\n@step(\"Deactivate Demo Mode\")\r\n@step(\"User deactivates Demo Mode\")\r\ndef deactivate_demo_mode(context):\r\n    sb = context.sb\r\n    sb.deactivate_demo_mode()\r\n\r\n\r\n@step(\"Deferred assert element '{selector}'\")\r\n@step('Deferred assert element \"{selector}\"')\r\ndef deferred_assert_element(context, selector):\r\n    sb = context.sb\r\n    sb.deferred_assert_element(selector)\r\n\r\n\r\n@step(\"Deferred assert element present '{selector}'\")\r\n@step('Deferred assert element present \"{selector}\"')\r\ndef deferred_assert_element_present(context, selector):\r\n    sb = context.sb\r\n    sb.deferred_assert_element_present(selector)\r\n\r\n\r\n@step(\"Deferred assert text '{text}' in '{selector}'\")\r\n@step('Deferred assert text \"{text}\" in \"{selector}\"')\r\n@step(\"Deferred assert text '{text}' in \\\"{selector}\\\"\")\r\n@step('Deferred assert text \"{text}\" in \\'{selector}\\'')\r\ndef deferred_assert_text_in_element(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.deferred_assert_text(text, selector)\r\n\r\n\r\n@step(\"Deferred assert text '{text}'\")\r\n@step('Deferred assert text \"{text}\"')\r\ndef deferred_assert_text(context, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.deferred_assert_text(text)\r\n\r\n\r\n@step(\"Deferred assert exact text '{text}' in '{selector}'\")\r\n@step('Deferred assert exact text \"{text}\" in \"{selector}\"')\r\n@step(\"Deferred assert exact text '{text}' in \\\"{selector}\\\"\")\r\n@step('Deferred assert exact text \"{text}\" in \\'{selector}\\'')\r\ndef deferred_assert_exact_text(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.deferred_assert_exact_text(text, selector)\r\n\r\n\r\n@step(\"Deferred assert non-empty text in '{selector}'\")\r\n@step('Deferred assert non-empty text in \"{selector}\"')\r\ndef deferred_assert_non_empty_text(context, selector):\r\n    sb = context.sb\r\n    sb.deferred_assert_non_empty_text(selector)\r\n\r\n\r\n@step(\"Process deferred asserts\")\r\ndef process_deferred_asserts(context):\r\n    sb = context.sb\r\n    sb.process_deferred_asserts()\r\n\r\n\r\n@step(\"Assert text not visible '{text}' in '{selector}'\")\r\n@step('Assert text not visible \"{text}\" in \"{selector}\"')\r\n@step(\"Assert text not visible '{text}' in \\\"{selector}\\\"\")\r\n@step('Assert text not visible \"{text}\" in \\'{selector}\\'')\r\n@step(\"Text '{text}' should not be visible in '{selector}'\")\r\n@step('Text \"{text}\" should not be visible in \"{selector}\"')\r\n@step(\"Text '{text}' should not be visible in \\\"{selector}\\\"\")\r\n@step('Text \"{text}\" should not be visible in \\'{selector}\\'')\r\ndef assert_text_not_visible_in_element(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_text_not_visible(text, selector)\r\n\r\n\r\n@step(\"Assert text not visible '{text}'\")\r\n@step('Assert text not visible \"{text}\"')\r\n@step(\"Text '{text}' should not be visible\")\r\n@step('Text \"{text}\" should not be visible')\r\ndef assert_text_not_visible(context, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_text_not_visible(text)\r\n\r\n\r\n@step(\"Assert exact text not visible '{text}' in '{selector}'\")\r\n@step('Assert exact text not visible \"{text}\" in \"{selector}\"')\r\n@step(\"Assert exact text not visible '{text}' in \\\"{selector}\\\"\")\r\n@step('Assert exact text not visible \"{text}\" in \\'{selector}\\'')\r\n@step(\"Exact text '{text}' should not be visible in '{selector}'\")\r\n@step('Exact text \"{text}\" should not be visible in \"{selector}\"')\r\n@step(\"Exact text '{text}' should not be visible in \\\"{selector}\\\"\")\r\n@step('Exact text \"{text}\" should not be visible in \\'{selector}\\'')\r\ndef assert_exact_text_not_visible_in_element(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_exact_text_not_visible(text, selector)\r\n\r\n\r\n@step(\"Assert exact text not visible '{text}'\")\r\n@step('Assert exact text not visible \"{text}\"')\r\n@step(\"Exact text '{text}' should not be visible\")\r\n@step('Exact text \"{text}\" should not be visible')\r\ndef assert_exact_text_not_visible(context, text):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.assert_exact_text_not_visible(text)\r\n\r\n\r\n@step(\"Assert title contains '{substring}'\")\r\n@step('Assert title contains \"{substring}\"')\r\n@step(\"The title should contain '{substring}'\")\r\n@step('The title should contain \"{substring}\"')\r\ndef assert_title_contains(context, substring):\r\n    sb = context.sb\r\n    substring = normalize_text(substring)\r\n    sb.assert_title_contains(substring)\r\n\r\n\r\n@step(\"Open new tab\")\r\n@step(\"Open new window\")\r\n@step(\"User opens new tab\")\r\n@step(\"User opens new window\")\r\ndef open_new_window(context):\r\n    sb = context.sb\r\n    sb.open_new_window()\r\n\r\n\r\n@step(\"Accept alert\")\r\n@step(\"User accepts alert\")\r\ndef accept_alert(context):\r\n    sb = context.sb\r\n    sb.accept_alert()\r\n\r\n\r\n@step(\"Dismiss alert\")\r\n@step(\"User dismisses alert\")\r\ndef dismiss_alert(context):\r\n    sb = context.sb\r\n    sb.dismiss_alert()\r\n\r\n\r\n@step(\"Assert URL '{url}'\")\r\n@step('Assert URL \"{url}\"')\r\n@step(\"The URL should be '{url}'\")\r\n@step('The URL should be \"{url}\"')\r\ndef assert_url(context, url):\r\n    sb = context.sb\r\n    url = normalize_text(url)\r\n    sb.assert_url(url)\r\n\r\n\r\n@step(\"Assert URL contains '{substring}'\")\r\n@step('Assert URL contains \"{substring}\"')\r\n@step(\"The URL should contain '{substring}'\")\r\n@step('The URL should contain \"{substring}\"')\r\ndef assert_url_contains(context, substring):\r\n    sb = context.sb\r\n    substring = normalize_text(substring)\r\n    sb.assert_url_contains(substring)\r\n\r\n\r\n@step(\"Hover '{selector}'\")\r\n@step('Hover \"{selector}\"')\r\n@step(\"Hover over '{selector}'\")\r\n@step('Hover over \"{selector}\"')\r\n@step(\"Hover element '{selector}'\")\r\n@step('Hover element \"{selector}\"')\r\n@step(\"User hovers over '{selector}'\")\r\n@step('User hovers over \"{selector}\"')\r\n@step(\"User hovers over element '{selector}'\")\r\n@step('User hovers over element \"{selector}\"')\r\ndef hover(context, selector):\r\n    sb = context.sb\r\n    sb.hover(selector)\r\n\r\n\r\n@step(\"Context click '{selector}'\")\r\n@step('Context click \"{selector}\"')\r\n@step(\"Context click element '{selector}'\")\r\n@step('Context click element \"{selector}\"')\r\n@step(\"Right click '{selector}'\")\r\n@step('Right click \"{selector}\"')\r\n@step(\"Right click element '{selector}'\")\r\n@step('Right click element \"{selector}\"')\r\n@step(\"User right clicks '{selector}'\")\r\n@step('User right clicks \"{selector}\"')\r\n@step(\"User right clicks element '{selector}'\")\r\n@step('User right clicks element \"{selector}\"')\r\ndef context_click(context, selector):\r\n    sb = context.sb\r\n    sb.context_click(selector)\r\n\r\n\r\n@step(\"JS type '{text}' in '{selector}'\")\r\n@step('JS type \"{text}\" in \"{selector}\"')\r\n@step(\"JS type '{text}' in \\\"{selector}\\\"\")\r\n@step('JS type \"{text}\" in \\'{selector}\\'')\r\n@step(\"JS type '{text}' into '{selector}'\")\r\n@step('JS type \"{text}\" into \"{selector}\"')\r\n@step(\"JS type '{text}' into \\\"{selector}\\\"\")\r\n@step('JS type \"{text}\" into \\'{selector}\\'')\r\n@step(\"JS type text '{text}' in '{selector}'\")\r\n@step('JS type text \"{text}\" in \"{selector}\"')\r\n@step(\"JS type text '{text}' in \\\"{selector}\\\"\")\r\n@step('JS type text \"{text}\" in \\'{selector}\\'')\r\n@step(\"JS type text '{text}' into '{selector}'\")\r\n@step('JS type text \"{text}\" into \"{selector}\"')\r\n@step(\"JS type text '{text}' into \\\"{selector}\\\"\")\r\n@step('JS type text \"{text}\" into \\'{selector}\\'')\r\n@step(\"Use JS to type '{text}' in '{selector}'\")\r\n@step('Use JS to type \"{text}\" in \"{selector}\"')\r\n@step(\"Use JS to type '{text}' in \\\"{selector}\\\"\")\r\n@step('Use JS to type \"{text}\" in \\'{selector}\\'')\r\n@step(\"Use JS to type '{text}' into '{selector}'\")\r\n@step('Use JS to type \"{text}\" into \"{selector}\"')\r\n@step(\"Use JS to type '{text}' into \\\"{selector}\\\"\")\r\n@step('Use JS to type \"{text}\" into \\'{selector}\\'')\r\ndef js_type(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.js_type(selector, text)\r\n\r\n\r\n@step(\"jQuery click '{selector}'\")\r\n@step('jQuery click \"{selector}\"')\r\n@step(\"jQuery click element '{selector}'\")\r\n@step('jQuery click element \"{selector}\"')\r\n@step(\"Use jQuery to click '{selector}'\")\r\n@step('Use jQuery to click \"{selector}\"')\r\ndef jquery_click(context, selector):\r\n    sb = context.sb\r\n    sb.jquery_click(selector)\r\n\r\n\r\n@step(\"jQuery click all '{selector}'\")\r\n@step('jQuery click all \"{selector}\"')\r\n@step(\"Use jQuery to click all '{selector}'\")\r\n@step('Use jQuery to click all \"{selector}\"')\r\ndef jquery_click_all(context, selector):\r\n    sb = context.sb\r\n    sb.jquery_click_all(selector)\r\n\r\n\r\n@step(\"jQuery type '{text}' in '{selector}'\")\r\n@step('jQuery type \"{text}\" in \"{selector}\"')\r\n@step(\"jQuery type '{text}' in \\\"{selector}\\\"\")\r\n@step('jQuery type \"{text}\" in \\'{selector}\\'')\r\n@step(\"jQuery type '{text}' into '{selector}'\")\r\n@step('jQuery type \"{text}\" into \"{selector}\"')\r\n@step(\"jQuery type '{text}' into \\\"{selector}\\\"\")\r\n@step('jQuery type \"{text}\" into \\'{selector}\\'')\r\n@step(\"jQuery type text '{text}' in '{selector}'\")\r\n@step('jQuery type text \"{text}\" in \"{selector}\"')\r\n@step(\"jQuery type text '{text}' in \\\"{selector}\\\"\")\r\n@step('jQuery type text \"{text}\" in \\'{selector}\\'')\r\n@step(\"jQuery type text '{text}' into '{selector}'\")\r\n@step('jQuery type text \"{text}\" into \"{selector}\"')\r\n@step(\"jQuery type text '{text}' into \\\"{selector}\\\"\")\r\n@step('jQuery type text \"{text}\" into \\'{selector}\\'')\r\n@step(\"Use jQuery to type '{text}' in '{selector}'\")\r\n@step('Use jQuery to type \"{text}\" in \"{selector}\"')\r\n@step(\"Use jQuery to type '{text}' in \\\"{selector}\\\"\")\r\n@step('Use jQuery to type \"{text}\" in \\'{selector}\\'')\r\n@step(\"Use jQuery to type '{text}' into '{selector}'\")\r\n@step('Use jQuery to type \"{text}\" into \"{selector}\"')\r\n@step(\"Use jQuery to type '{text}' into \\\"{selector}\\\"\")\r\n@step('Use jQuery to type \"{text}\" into \\'{selector}\\'')\r\ndef jquery_type(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.jquery_type(selector, text)\r\n\r\n\r\n@step(\"Press keys '{text}' in '{selector}'\")\r\n@step('Press keys \"{text}\" in \"{selector}\"')\r\n@step(\"Press keys '{text}' in \\\"{selector}\\\"\")\r\n@step('Press keys \"{text}\" in \\'{selector}\\'')\r\n@step(\"Press keys '{text}' into '{selector}'\")\r\n@step('Press keys \"{text}\" into \"{selector}\"')\r\n@step(\"Press keys '{text}' into \\\"{selector}\\\"\")\r\n@step('Press keys \"{text}\" into \\'{selector}\\'')\r\n@step(\"In '{selector}' press keys '{text}'\")\r\n@step('In \"{selector}\" press keys \"{text}\"')\r\n@step(\"In '{selector}' press keys \\\"{text}\\\"\")\r\n@step('In \"{selector}\" press keys \\'{text}\\'')\r\n@step(\"Into '{selector}' press keys '{text}'\")\r\n@step('Into \"{selector}\" press keys \"{text}\"')\r\n@step(\"Into '{selector}' press keys \\\"{text}\\\"\")\r\n@step('Into \"{selector}\" press keys \\'{text}\\'')\r\n@step(\"Find '{selector}' and press keys '{text}'\")\r\n@step('Find \"{selector}\" and press keys \"{text}\"')\r\n@step(\"Find '{selector}' and press keys \\\"{text}\\\"\")\r\n@step('Find \"{selector}\" and press keys \\'{text}\\'')\r\n@step(\"User presses keys '{text}' in '{selector}'\")\r\n@step('User presses keys \"{text}\" in \"{selector}\"')\r\n@step(\"User presses keys '{text}' in \\\"{selector}\\\"\")\r\n@step('User presses keys \"{text}\" in \\'{selector}\\'')\r\n@step(\"User presses keys '{text}' into '{selector}'\")\r\n@step('User presses keys \"{text}\" into \"{selector}\"')\r\n@step(\"User presses keys '{text}' into \\\"{selector}\\\"\")\r\n@step('User presses keys \"{text}\" into \\'{selector}\\'')\r\ndef press_keys(context, text, selector):\r\n    sb = context.sb\r\n    text = normalize_text(text)\r\n    sb.press_keys(selector, text)\r\n\r\n\r\n@step(\"Find '{selector}' and set {attribute} to '{value}'\")\r\n@step('Find \"{selector}\" and set {attribute} to \"{value}\"')\r\n@step(\"Find '{selector}' and set {attribute} to \\\"{value}\\\"\")\r\n@step('Find \"{selector}\" and set {attribute} to \\'{value}\\'')\r\ndef set_attribute(context, selector, attribute, value):\r\n    sb = context.sb\r\n    value = normalize_text(value)\r\n    if attribute.startswith(\"'\") or attribute.startswith('\"'):\r\n        attribute = attribute[1:]\r\n    if attribute.endswith(\"'\") or attribute.endswith('\"'):\r\n        attribute = attribute[:-1]\r\n    sb.set_attribute(selector, attribute, value)\r\n\r\n\r\n@step(\"Find all '{selector}' and set {attribute} to '{value}'\")\r\n@step('Find all \"{selector}\" and set {attribute} to \"{value}\"')\r\n@step(\"Find all '{selector}' and set {attribute} to \\\"{value}\\\"\")\r\n@step('Find all \"{selector}\" and set {attribute} to \\'{value}\\'')\r\ndef set_attributes(context, selector, attribute, value):\r\n    sb = context.sb\r\n    value = normalize_text(value)\r\n    if attribute.startswith(\"'\") or attribute.startswith('\"'):\r\n        attribute = attribute[1:]\r\n    if attribute.endswith(\"'\") or attribute.endswith('\"'):\r\n        attribute = attribute[:-1]\r\n    sb.set_attributes(selector, attribute, value)\r\n\r\n\r\n@step(\"Save as PDF to logs\")\r\n@step(\"Save as PDF to the logs\")\r\n@step(\"User saves page as PDF to logs\")\r\n@step(\"User saves page as PDF to the logs\")\r\ndef save_as_pdf_to_logs(context):\r\n    sb = context.sb\r\n    sb.save_as_pdf_to_logs()\r\n\r\n\r\n@step(\"Save page source to logs\")\r\n@step(\"Save the page source to the logs\")\r\n@step(\"User saves page source to logs\")\r\n@step(\"User saves the page source to the logs\")\r\ndef save_page_source_to_logs(context):\r\n    sb = context.sb\r\n    sb.save_page_source_to_logs()\r\n\r\n\r\n@step(\"Activate CDP Mode\")\r\n@step(\"User activates CDP Mode\")\r\ndef activate_cdp_mode(context):\r\n    sb = context.sb\r\n    sb.activate_cdp_mode()\r\n"
  },
  {
    "path": "seleniumbase/ReadMe.md",
    "content": "<img src=\"https://seleniumbase.github.io/cdn/img/sb_word_cloud.png\" alt=\"SeleniumBase\" title=\"SeleniumBase\" width=\"330\" />\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo3b.png\" title=\"SeleniumBase\" width=\"32\" /> Framework Folders</h2>\n\n* <b>[fixtures](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/fixtures):</b> Includes [base_case.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py), where SeleniumBase test methods are defined.\n* <b>[core](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/core):</b> Includes [browser_launcher.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/core/browser_launcher.py), which is used for spinning up browsers for tests.\n* <b>[plugins](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/plugins):</b> Includes [pytest_plugin.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/plugins/pytest_plugin.py), which is used to add pytest command-line options.\n* <b>[console_scripts](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/console_scripts):</b> Includes [run.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/run.py), which is used to call SeleniumBase console scripts.\n* <b>[drivers](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/drivers):</b> This is the folder where web drivers get downloaded when installing them.\n* <b>[config](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/config):</b> Includes [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py), which provides default configuration options for tests.\n* <b>[js_code](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/js_code):</b> This folder contains JavaScript code for various SeleniumBase components.\n* <b>[undetected](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/undetected):</b> This folder contains code for preventing sites from detecting Selenium.\n* <b>[extensions](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/extensions):</b> This folder contains Chromium extensions that can be used by tests.\n* <b>[common](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/common):</b> This folder contains Python decorators that can be used by tests.\n* <b>[utilities](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/utilities):</b> This folder contains code for spinning up your own Selenium Grid.\n* <b>[resources](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/resources):</b> This folder contains copies of JavaScript resources used by tests.\n* <b>[translate](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/translate):</b> This folder contains code for translating tests into different languages.\n* <b>[behave](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/behave):</b> This folder contains code for integrating with the Behave BDD test runner.\n* <b>[masterqa](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/masterqa):</b> MasterQA is a tool for combining automation with manual verification.\n\n--------\n\n<img src=\"https://seleniumbase.github.io/cdn/img/sb_text_f.png\" title=\"SeleniumBase\" align=\"center\" width=\"360\">\n\n<div><a href=\"https://github.com/seleniumbase/SeleniumBase\"><img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_gs.png\" alt=\"SeleniumBase\" width=\"360\" /></a></div>\n"
  },
  {
    "path": "seleniumbase/__init__.py",
    "content": "import collections\nimport os\nimport pdb\nimport sys\nfrom contextlib import suppress\nfrom selenium import webdriver\nfrom seleniumbase.__version__ import __version__\nfrom seleniumbase.common import decorators  # noqa\nfrom seleniumbase.common import encryption  # noqa\nfrom seleniumbase.core import colored_traceback\nfrom seleniumbase.core import sb_cdp  # noqa\nfrom seleniumbase.core.browser_launcher import get_driver  # noqa\nfrom seleniumbase.fixtures import js_utils  # noqa\nfrom seleniumbase.fixtures import page_actions  # noqa\nfrom seleniumbase.fixtures import page_utils  # noqa\nfrom seleniumbase.fixtures import shared_utils\nfrom seleniumbase.fixtures.base_case import BaseCase  # noqa\nfrom seleniumbase.masterqa.master_qa import MasterQA  # noqa\nfrom seleniumbase.plugins.sb_manager import SB  # noqa\nfrom seleniumbase.plugins.driver_manager import Driver  # noqa\nfrom seleniumbase.plugins.driver_manager import DriverContext  # noqa\nfrom seleniumbase.undetected import cdp_driver  # noqa\nfrom seleniumbase import translate  # noqa\n\nwith suppress(Exception):\n    import colorama\n\nwith suppress(Exception):\n    import pdbp  # (Pdb+) --- Python Debugger Plus\n\nwith suppress(Exception):\n    shared_utils.fix_colorama_if_windows()\n    colorama.init(autoreset=True)\n\nif sys.version_info[0] < 3 and \"pdbp\" in locals():\n    # With Python3, \"import pdbp\" is all you need\n    for key in pdbp.__dict__.keys():\n        # Replace pdb with pdbp\n        pdb.__dict__[key] = pdbp.__dict__[key]\n    if hasattr(pdb, \"DefaultConfig\"):\n        # Here's how to customize Pdb+ options\n        pdb.DefaultConfig.filename_color = pdb.Color.fuchsia\n        pdb.DefaultConfig.line_number_color = pdb.Color.turquoise\n        pdb.DefaultConfig.truncate_long_lines = False\n        pdb.DefaultConfig.sticky_by_default = True\ncolored_traceback.add_hook()\nos.environ[\"SE_AVOID_STATS\"] = \"true\"  # Disable Selenium Manager stats\nwebdriver.TouchActions = None  # Lifeline for past selenium-wire versions\nif sys.version_info >= (3, 10):\n    collections.Callable = collections.abc.Callable  # Lifeline for nosetests\ndel collections  # Undo \"import collections\" / Simplify \"dir(seleniumbase)\"\ndel os  # Undo \"import os\" / Simplify \"dir(seleniumbase)\"\ndel sys  # Undo \"import sys\" / Simplify \"dir(seleniumbase)\"\ndel webdriver  # Undo \"import webdriver\" / Simplify \"dir(seleniumbase)\"\n\nversion_list = [int(i) for i in __version__.split(\".\") if i.isdigit()]\nversion_tuple = tuple(version_list)\nversion_info = version_tuple  # noqa\n"
  },
  {
    "path": "seleniumbase/__main__.py",
    "content": "import os\r\nimport sys\r\n\r\n# Remove \"\" and current working directory from the first entry\r\n# of sys.path (if present) to avoid using the current directory\r\n# in SeleniumBase commands when invoked as \"python -m seleniumbase <command>\"\r\nif sys.path[0] in (\"\", os.getcwd()):\r\n    sys.path.pop(0)\r\n\r\nif __package__ == \"\":\r\n    path = os.path.dirname(os.path.dirname(__file__))\r\n    sys.path.insert(0, path)\r\n\r\nif __name__ == \"__main__\":\r\n    import warnings\r\n    from seleniumbase.console_scripts.run import main\r\n\r\n    warnings.filterwarnings(\r\n        \"ignore\", category=DeprecationWarning, module=\".*packaging\\\\.version\"\r\n    )\r\n    main()\r\n    sys.exit()\r\n"
  },
  {
    "path": "seleniumbase/__version__.py",
    "content": "# seleniumbase package\n__version__ = \"4.47.4\"\n"
  },
  {
    "path": "seleniumbase/behave/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/behave/behave_helper.py",
    "content": "\"\"\"Generating Gherkin-formatted code from the Recorder.\"\"\"\n\n\ndef generate_gherkin(srt_actions):\n    sb_actions = []\n    for action in srt_actions:\n        if action[0] == \"begin\" or action[0] == \"_url_\":\n            if \"%\" in action[2]:\n                try:\n                    from urllib.parse import unquote\n\n                    action[2] = unquote(action[2], errors=\"strict\")\n                except Exception:\n                    pass\n            if '\"' not in action[2]:\n                sb_actions.append('Open \"%s\"' % action[2])\n            elif \"'\" not in action[2]:\n                sb_actions.append(\"Open '%s'\" % action[2])\n            else:\n                sb_actions.append('Open \"%s\"' % action[2].replace('\"', '\\\\\"'))\n        elif action[0] == \"f_url\":\n            if \"%\" in action[2]:\n                try:\n                    from urllib.parse import unquote\n\n                    action[2] = unquote(action[2], errors=\"strict\")\n                except Exception:\n                    pass\n            if '\"' not in action[2]:\n                sb_actions.append('Open if not \"%s\"' % action[2])\n            elif \"'\" not in action[2]:\n                sb_actions.append(\"Open if not '%s'\" % action[2])\n            else:\n                sb_actions.append(\n                    'Open if not \"%s\"' % action[2].replace('\"', '\\\\\"')\n                )\n        elif action[0] == \"click\":\n            if '\"' not in action[1]:\n                sb_actions.append('Click \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"Click '%s'\" % action[1])\n        elif action[0] == \"dbclk\":\n            if '\"' not in action[1]:\n                sb_actions.append('Double click \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"Double click '%s'\" % action[1])\n        elif action[0] == \"js_cl\":\n            if '\"' not in action[1]:\n                sb_actions.append('JS click \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"JS click '%s'\" % action[1])\n        elif action[0] == \"js_ca\":\n            if '\"' not in action[1]:\n                sb_actions.append('JS click all \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"JS click all '%s'\" % action[1])\n        elif action[0] == \"jq_cl\":\n            if '\"' not in action[1]:\n                sb_actions.append('jQuery click \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"jQuery click '%s'\" % action[1])\n        elif action[0] == \"jq_ca\":\n            if '\"' not in action[1]:\n                sb_actions.append('jQuery click all \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"jQuery click all '%s'\" % action[1])\n        elif action[0] == \"r_clk\":\n            if '\"' not in action[1]:\n                sb_actions.append('Context click \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"Context click '%s'\" % action[1])\n        elif action[0] == \"canva\":\n            selector = action[1][0]\n            p_x = action[1][1]\n            p_y = action[1][2]\n            if '\"' not in selector:\n                sb_actions.append(\n                    'Click \"%s\" at (%s, %s)' % (selector, p_x, p_y)\n                )\n            else:\n                sb_actions.append(\n                    \"Click '%s' at (%s, %s)\" % (selector, p_x, p_y)\n                )\n        elif action[0] == \"input\":\n            text = action[2].replace(\"\\n\", \"\\\\n\")\n            if '\"' not in text and '\"' not in action[1]:\n                sb_actions.append('Type \"%s\" into \"%s\"' % (text, action[1]))\n            elif '\"' in text and '\"' not in action[1]:\n                sb_actions.append('Type \\'%s\\' into \"%s\"' % (text, action[1]))\n            elif '\"' not in text and '\"' in action[1]:\n                sb_actions.append('Type \"%s\" into \\'%s\\'' % (text, action[1]))\n            elif '\"' in text and '\"' in action[1]:\n                sb_actions.append(\"Type '%s' into '%s'\" % (text, action[1]))\n        elif action[0] == \"js_ty\":\n            text = action[2].replace(\"\\n\", \"\\\\n\")\n            if '\"' not in text and '\"' not in action[1]:\n                sb_actions.append('JS type \"%s\" in \"%s\"' % (text, action[1]))\n            elif '\"' in text and '\"' not in action[1]:\n                sb_actions.append('JS type \\'%s\\' in \"%s\"' % (text, action[1]))\n            elif '\"' not in text and '\"' in action[1]:\n                sb_actions.append('JS type \"%s\" in \\'%s\\'' % (text, action[1]))\n            elif '\"' in text and '\"' in action[1]:\n                sb_actions.append(\"JS type '%s' in '%s'\" % (text, action[1]))\n        elif action[0] == \"jq_ty\":\n            text = action[2].replace(\"\\n\", \"\\\\n\")\n            if '\"' not in text and '\"' not in action[1]:\n                sb_actions.append(\n                    'jQuery type \"%s\" in \"%s\"' % (text, action[1])\n                )\n            elif '\"' in text and '\"' not in action[1]:\n                sb_actions.append(\n                    'jQuery type \\'%s\\' in \"%s\"' % (text, action[1])\n                )\n            elif '\"' not in text and '\"' in action[1]:\n                sb_actions.append(\n                    'jQuery type \"%s\" in \\'%s\\'' % (text, action[1])\n                )\n            elif '\"' in text and '\"' in action[1]:\n                sb_actions.append(\n                    \"jQuery type '%s' in '%s'\" % (text, action[1])\n                )\n        elif action[0] == \"pkeys\":\n            text = action[2].replace(\"\\n\", \"\\\\n\")\n            if '\"' not in text and '\"' not in action[1]:\n                sb_actions.append(\n                    'Press keys \"%s\" in \"%s\"' % (text, action[1])\n                )\n            elif '\"' in text and '\"' not in action[1]:\n                sb_actions.append(\n                    'Press keys \\'%s\\' in \"%s\"' % (text, action[1])\n                )\n            elif '\"' not in text and '\"' in action[1]:\n                sb_actions.append(\n                    'Press keys \"%s\" in \\'%s\\'' % (text, action[1])\n                )\n            elif '\"' in text and '\"' in action[1]:\n                sb_actions.append(\n                    \"Press keys '%s' in '%s'\" % (text, action[1])\n                )\n        elif action[0] == \"hover\":\n            if '\"' not in action[1]:\n                sb_actions.append('Hover \"%s\"' % action[1])\n            else:\n                sb_actions.append(\"Hover '%s'\" % action[1])\n        elif action[0] == \"e_mfa\":\n            text = action[2].replace(\"\\n\", \"\\\\n\")\n            if '\"' not in action[1] and '\"' not in text:\n                sb_actions.append('Into \"%s\" do MFA \"%s\"' % (action[1], text))\n            elif '\"' not in action[1] and '\"' in text:\n                sb_actions.append(\n                    'Into \"%s\" do MFA \\'%s\\'' % (action[1], text)\n                )\n            elif '\"' in action[1] and '\"' not in text:\n                sb_actions.append(\n                    'Into \\'%s\\' do MFA \"%s\"' % (action[1], text)\n                )\n            elif '\"' in action[1] and '\"' in text:\n                sb_actions.append(\"Into '%s' do MFA '%s'\" % (action[1], text))\n        elif action[0] == \"h_clk\":\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Hover \"%s\" and click \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'Hover \"%s\" and click \\'%s\\'' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Hover \\'%s\\' and click \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"Hover '%s' and click '%s'\" % (action[1], action[2])\n                )\n        elif action[0] == \"ddrop\":\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Drag \"%s\" into \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'Drag \"%s\" into \\'%s\\'' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Drag \\'%s\\' into \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"Drag '%s' into '%s'\" % (action[1], action[2])\n                )\n        elif action[0] == \"s_opt\":\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Find \"%s\" and select \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'Find \"%s\" and select \\'%s\\'' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Find \\'%s\\' and select \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"Find '%s' and select '%s'\" % (action[1], action[2])\n                )\n        elif action[0] == \"set_v\":\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Set value of \"%s\" to \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'Set value of \"%s\" to \\'%s\\'' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Set value of \\'%s\\' to \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"Set value of '%s' to '%s'\" % (action[1], action[2])\n                )\n        elif action[0] == \"cho_f\":\n            action[2] = action[2].replace(\"\\\\\", \"\\\\\\\\\")\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Into \"%s\" choose file \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'Into \"%s\" choose file \\'%s\\'' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'Into \\'%s\\' choose file \"%s\"' % (action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"Into '%s' choose file '%s'\" % (action[1], action[2])\n                )\n        elif action[0] == \"sw_fr\":\n            method = \"Switch to frame\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"sw_dc\":\n            sb_actions.append(\"Switch to default content\")\n        elif action[0] == \"sw_pf\":\n            sb_actions.append(\"Switch to parent frame\")\n        elif action[0] == \"s_c_f\":\n            method = \"Set content to frame\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"s_c_d\":\n            nested = action[1]\n            if nested:\n                sb_actions.append(\"Set content to parent\")\n            else:\n                sb_actions.append(\"Set content to default\")\n        elif action[0] == \"sleep\":\n            sb_actions.append(\"Sleep for %s seconds\" % action[1])\n        elif action[0] == \"wf_el\":\n            method = \"Wait for element\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"as_el\":\n            method = \"Assert element\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"as_ep\":\n            method = \"Assert element present\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"asenv\":\n            method = \"Assert element not visible\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"s_at_\" or action[0] == \"s_ats\":\n            start = \"Find\"\n            if action[0] == \"s_ats\":\n                start = \"Find all\"\n            if '\"' not in action[1][0]:\n                sb_actions.append(\n                    '%s \"%s\" and set %s to \"%s\"'\n                    % (start, action[1][0], action[1][1], action[1][2])\n                )\n            elif \"'\" not in action[1][0]:\n                sb_actions.append(\n                    \"%s '%s' and set %s to \\\"%s\\\"\"\n                    % (start, action[1][0], action[1][1], action[1][2])\n                )\n            else:\n                sb_actions.append(\n                    '%s \"%s\" and set %s to \"%s\")'\n                    % (\n                        start.replace('\"', '\\\\\"'),\n                        action[1][0], action[1][1], action[1][2]\n                    )\n                )\n        elif action[0] == \"acc_a\":\n            sb_actions.append(\"Accept alert\")\n        elif action[0] == \"dis_a\":\n            sb_actions.append(\"Dismiss alert\")\n        elif action[0] == \"hi_li\":\n            method = \"Highlight\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"as_lt\":\n            method = \"Assert link text\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"as_ti\":\n            method = \"Assert title\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"as_tc\":\n            method = \"Assert title contains\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"a_url\":\n            method = \"Assert URL\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"a_u_c\":\n            method = \"Assert URL contains\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"as_df\":\n            method = \"Assert downloaded file\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"do_fi\":\n            method = \"Download file\"\n            file_url = action[1][0]\n            dest = action[1][1]\n            if not dest:\n                sb_actions.append('%s \"%s\" to downloads' % (method, file_url))\n            else:\n                sb_actions.append('%s \"%s\" to \"%s\"' % (method, file_url, dest))\n        elif action[0] == \"as_at\":\n            if ('\"' not in action[1][0]) and action[1][2]:\n                sb_actions.append(\n                    'In \"%s\" assert attribute/value \"%s\"/\"%s\"'\n                    % (action[1][0], action[1][1], action[1][2])\n                )\n            elif ('\"' not in action[1][0]) and not action[1][2]:\n                sb_actions.append(\n                    'In \"%s\" assert attribute \"%s\"'\n                    % (action[1][0], action[1][1])\n                )\n            elif ('\"' in action[1][0]) and action[1][2]:\n                sb_actions.append(\n                    'In \\'%s\\' assert attribute/value \"%s\"/\"%s\"'\n                    % (action[1][0], action[1][1], action[1][2])\n                )\n            else:\n                sb_actions.append(\n                    'In \\'%s\\' assert attribute \"%s\"'\n                    % (action[1][0], action[1][1])\n                )\n        elif (\n            action[0] == \"as_te\"\n            or action[0] == \"as_et\"\n            or action[0] == \"astnv\"\n            or action[0] == \"aetnv\"\n            or action[0] == \"da_te\"\n            or action[0] == \"da_et\"\n        ):\n            import unicodedata\n\n            action[1][0] = unicodedata.normalize(\"NFKC\", action[1][0])\n            action[1][0] = action[1][0].replace(\"\\n\", \"\\\\n\")\n            action[1][0] = action[1][0].replace(\"\\u00B6\", \"\")\n            method = \"Assert text\"\n            if action[0] == \"as_et\":\n                method = \"Assert exact text\"\n            elif action[0] == \"astnv\":\n                method = \"Assert text not visible\"\n            elif action[0] == \"aetnv\":\n                method = \"Assert exact text not visible\"\n            elif action[0] == \"da_te\":\n                method = \"Deferred assert text\"\n            elif action[0] == \"da_et\":\n                method = \"Deferred assert exact text\"\n            if action[1][1] != \"html\":\n                if '\"' not in action[1][0] and '\"' not in action[1][1]:\n                    sb_actions.append(\n                        '%s \"%s\" in \"%s\"'\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif '\"' not in action[1][0] and '\"' in action[1][1]:\n                    sb_actions.append(\n                        '%s \"%s\" in \\'%s\\''\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif '\"' in action[1] and '\"' not in action[1][1]:\n                    sb_actions.append(\n                        '%s \\'%s\\' in \"%s\"'\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif '\"' in action[1] and '\"' in action[1][1]:\n                    sb_actions.append(\n                        \"%s '%s' in '%s'\"\n                        % (method, action[1][0], action[1][1])\n                    )\n            else:\n                if '\"' not in action[1][0]:\n                    sb_actions.append('%s \"%s\"' % (method, action[1][0]))\n                else:\n                    sb_actions.append(\"%s '%s'\" % (method, action[1][0]))\n        elif action[0] == \"asnet\":\n            method = \"Assert non-empty text in\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"da_el\":\n            method = \"Deferred assert element\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"da_ep\":\n            method = \"Deferred assert element present\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"danet\":\n            method = \"Deferred assert non-empty text in\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    \"%s '%s'\" % (method, action[1].replace(\"'\", \"\\\\'\"))\n                )\n        elif action[0] == \"s_scr\":\n            method = \"Save screenshot as\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n        elif action[0] == \"ss_tf\":\n            if '\"' not in action[2] and '\"' not in action[1]:\n                sb_actions.append(\n                    'Save screenshot to \"%s\" as \"%s\"'\n                    % (action[2], action[1])\n                )\n            elif '\"' not in action[2] and '\"' in action[1]:\n                sb_actions.append(\n                    'Save screenshot to \"%s\" as \\'%s\\''\n                    % (action[2], action[1])\n                )\n            elif '\"' in action[2] and '\"' not in action[1]:\n                sb_actions.append(\n                    'Save screenshot to \\'%s\\' as \"%s\"'\n                    % (action[2], action[1])\n                )\n            elif '\"' in action[2] and '\"' in action[1]:\n                sb_actions.append(\n                    \"Save screenshot to '%s' as '%s'\"\n                    % (action[2], action[1])\n                )\n        elif action[0] == \"ss_tl\":\n            sb_actions.append(\"Save screenshot to logs\")\n        elif action[0] == \"pdftl\":\n            sb_actions.append(\"Save as PDF to logs\")\n        elif action[0] == \"spstl\":\n            sb_actions.append(\"Save page source to logs\")\n        elif action[0] == \"sh_fc\":\n            sb_actions.append(\"Show file choosers\")\n        elif action[0] == \"pr_da\":\n            sb_actions.append(\"Process deferred asserts\")\n        elif action[0] == \"a_d_m\":\n            sb_actions.append(\"Activate Demo Mode\")\n        elif action[0] == \"d_d_m\":\n            sb_actions.append(\"Deactivate Demo Mode\")\n        elif action[0] == \"c_l_s\":\n            sb_actions.append(\"Clear Local Storage\")\n        elif action[0] == \"c_s_s\":\n            sb_actions.append(\"Clear Session Storage\")\n        elif action[0] == \"d_a_c\":\n            sb_actions.append(\"Delete all cookies\")\n        elif action[0] == \"go_bk\":\n            sb_actions.append(\"Go back\")\n        elif action[0] == \"go_fw\":\n            sb_actions.append(\"Go forward\")\n        elif action[0] == \"c_box\":\n            method = \"Check if unchecked\"\n            if action[2] == \"no\":\n                method = \"Uncheck if checked\"\n            if '\"' not in action[1]:\n                sb_actions.append('%s \"%s\"' % (method, action[1]))\n            else:\n                sb_actions.append(\"%s '%s'\" % (method, action[1]))\n    return sb_actions\n"
  },
  {
    "path": "seleniumbase/behave/behave_sb.py",
    "content": "\"\"\"\nThe SeleniumBase-Behave Connector configures command-line options.\n******************************************************************\nExamples:\nbehave -D browser=edge -D dashboard -D headless\nbehave -D rs -D dashboard\nbehave -D agent=\"User Agent String\" -D demo\n****************************************************************\n-D browser=BROWSER  (The web browser to use. Default: \"chrome\".)\n-D chrome  (Shortcut for \"-D browser=chrome\". Default.)\n-D edge  (Shortcut for \"-D browser=edge\".)\n-D firefox  (Shortcut for \"-D browser=firefox\".)\n-D safari  (Shortcut for \"-D browser=safari\".)\n-D cft  (Shortcut for using `Chrome for Testing`)\n-D chs  (Shortcut for using `Chrome-Headless-Shell`)\n-D settings-file=FILE  (Override default SeleniumBase settings.)\n-D env=ENV  (Set the test env. Access with \"self.env\" in tests.)\n-D account=STR  (Set account. Access with \"self.account\" in tests.)\n-D data=STRING  (Extra test data. Access with \"self.data\" in tests.)\n-D var1=STRING  (Extra test data. Access with \"self.var1\" in tests.)\n-D var2=STRING  (Extra test data. Access with \"self.var2\" in tests.)\n-D var3=STRING  (Extra test data. Access with \"self.var3\" in tests.)\n-D variables=DICT  (A test var dict. Access with \"self.variables\".)\n-D user-data-dir=DIR  (Set the Chrome user data directory to use.)\n-D protocol=PROTOCOL  (The Selenium Grid protocol: http|https.)\n-D server=SERVER  (The Selenium Grid server/IP used for tests.)\n-D port=PORT  (The Selenium Grid port used by the test server.)\n-D cap-file=FILE  (The web browser's desired capabilities to use.)\n-D cap-string=STRING  (The web browser's desired capabilities to use.)\n-D proxy=SERVER:PORT  (Connect to a proxy server:port as tests are running)\n-D proxy=USERNAME:PASSWORD@SERVER:PORT  (Use an authenticated proxy server)\n-D proxy-bypass-list=STRING (\";\"-separated hosts to bypass, Eg \"*.foo.com\")\n-D proxy-pac-url=URL  (Connect to a proxy server using a PAC_URL.pac file.)\n-D proxy-pac-url=USERNAME:PASSWORD@URL  (Authenticated proxy with PAC URL.)\n-D multi-proxy  (Allow multiple authenticated proxies when multi-threaded.)\n-D agent=STRING  (Modify the web browser's User-Agent string.)\n-D mobile  (Use the mobile device emulator while running tests.)\n-D metrics=STRING  (Set mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\".)\n-D chromium-arg=ARG  (Add a Chromium arg for Chrome/Edge, comma-separated.)\n-D firefox-arg=ARG  (Add a Firefox arg for Firefox, comma-separated.)\n-D firefox-pref=SET  (Set a Firefox preference:value set, comma-separated.)\n-D extension-zip=ZIP  (Load a Chrome Extension .zip|.crx, comma-separated.)\n-D extension-dir=DIR  (Load a Chrome Extension directory, comma-separated.)\n-D binary-location=PATH  (Set path of the Chromium browser binary to use.)\n-D driver-version=VER  (Set the chromedriver or uc_driver version to use.)\n-D sjw  (Skip JS Waits for readyState to be \"complete\" or Angular to load.)\n-D wfa  (Wait for AngularJS to be done loading after specific web actions.)\n-D pls=PLS  (Set pageLoadStrategy on Chrome: \"normal\", \"eager\", or \"none\".)\n-D headless  (The default headless mode. Linux uses this mode by default.)\n-D headless1  (Use Chrome's old headless mode. Fast, but has limitations.)\n-D headless2  (Use Chrome's new headless mode, which supports extensions.)\n-D headed  (Run tests in headed/GUI mode on Linux OS, where not default.)\n-D xvfb  (Run tests using the Xvfb virtual display server on Linux OS.)\n-D xvfb-metrics=STRING  (Set Xvfb display size on Linux: \"Width,Height\".)\n-D locale=LOCALE_CODE  (Set the Language Locale Code for the web browser.)\n-D pdb  (Activate Post Mortem Debug Mode if a test fails.)\n-D interval=SECONDS  (The autoplay interval for presentations & tour steps)\n-D start-page=URL  (The starting URL for the web browser when tests begin.)\n-D archive-logs  (Archive existing log files instead of deleting them.)\n-D archive-downloads  (Archive old downloads instead of deleting them.)\n-D time-limit=SECONDS  (Safely fail any test that exceeds the time limit.)\n-D slow  (Slow down the automation. Faster than using Demo Mode.)\n-D demo  (Slow down and visually see test actions as they occur.)\n-D demo-sleep=SECONDS  (Set the wait time after Slow & Demo Mode actions.)\n-D highlights=NUM  (Number of highlight animations for Demo Mode actions.)\n-D message-duration=SECONDS  (The time length for Messenger alerts.)\n-D check-js  (Check for JavaScript errors after page loads.)\n-D ad-block  (Block some types of display ads from loading.)\n-D block-images  (Block images from loading during tests.)\n-D do-not-track  (Indicate to websites that you don't want to be tracked.)\n-D verify-delay=SECONDS  (The delay before MasterQA verification checks.)\n-D recorder  (Enables the Recorder for turning browser actions into code.)\n-D rec-behave  (Same as Recorder Mode, but also generates behave-gherkin.)\n-D rec-sleep  (If the Recorder is enabled, also records self.sleep calls.)\n-D rec-print  (If the Recorder is enabled, prints output after tests end.)\n-D disable-cookies  (Disable Cookies on websites. Pages might break!)\n-D disable-js  (Disable JavaScript on websites. Pages might break!)\n-D disable-csp  (Disable the Content Security Policy of websites.)\n-D disable-ws  (Disable Web Security on Chromium-based browsers.)\n-D enable-ws  (Enable Web Security on Chromium-based browsers.)\n-D enable-sync  (Enable \"Chrome Sync\".)\n-D uc | -D undetected  (Use undetected-chromedriver to evade bot-detection)\n-D uc-cdp-events  (Capture CDP events when running in \"-D undetected\" mode)\n-D log-cdp  (\"goog:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"})\n-D remote-debug  (Sync to Chrome Remote Debugger chrome://inspect/#devices)\n-D dashboard  (Enable the SeleniumBase Dashboard. Saved at: dashboard.html)\n-D dash-title=STRING  (Set the title shown for the generated dashboard.)\n-D enable-3d-apis  (Enables WebGL and 3D APIs.)\n-D swiftshader  (Use Chrome's SwiftShader Graphics Library.)\n-D incognito  (Enable Chrome's Incognito mode.)\n-D guest  (Enable Chrome's Guest mode.)\n-D dark  (Enable Chrome's Dark mode.)\n-D devtools  (Open Chrome's DevTools when the browser opens.)\n-D rs | -D reuse-session  (Reuse browser session for all tests.)\n-D rcs | -D reuse-class-session  (Reuse session for tests in class/feature)\n-D crumbs  (Delete all cookies between tests reusing a session.)\n-D disable-beforeunload  (Disable the \"beforeunload\" event on Chrome.)\n-D window-position=X,Y  (Set the browser's starting window position.)\n-D window-size=WIDTH,HEIGHT  (Set the browser's starting window size.)\n-D maximize  (Start tests with the browser window maximized.)\n-D screenshot  (Save a screenshot at the end of each test.)\n-D no-screenshot  (No screenshots saved unless tests directly ask it.)\n-D visual-baseline  (Set the visual baseline for Visual/Layout tests.)\n-D wire  (Use selenium-wire's webdriver for replacing selenium webdriver.)\n-D external-pdf  (Set Chromium \"plugins.always_open_pdf_externally\":True.)\n-D timeout-multiplier=MULTIPLIER  (Multiplies the default timeout values.)\n\"\"\"\nimport ast\nimport colorama\nimport os\nimport re\nimport sys\nfrom contextlib import suppress\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import detect_b_ver\nfrom seleniumbase.core import download_helper\nfrom seleniumbase.core import log_helper\nfrom seleniumbase.core import proxy_helper\nfrom seleniumbase.core import session_helper\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import shared_utils\n\nis_linux = shared_utils.is_linux()\nis_windows = shared_utils.is_windows()\nsb_config.__base_class = None\n\n\ndef set_base_class(base_class):\n    \"\"\"\n    # You can override the base class from Behave's environment.py file.\n    # If not using SeleniumBase's BaseCase class, then use a subclass.\n        from seleniumbase import BaseCase\n        from seleniumbase.plugins import behave_plugin\n        behave_plugin.set_base_class(BaseCase)\n    \"\"\"\n    sb_config.__base_class = base_class\n\n\ndef get_configured_sb(context):\n    if not sb_config.__base_class:\n        from seleniumbase import BaseCase\n\n        sb_config.__base_class = BaseCase\n    sb_config.__base_class.test_method = {}\n    sb = sb_config.__base_class(\"test_method\")\n\n    # Set default values\n    sb.browser = \"chrome\"\n    sb.is_behave = True\n    sb.headless = False\n    sb.headless1 = False\n    sb.headless2 = False\n    sb.headless_active = False\n    sb.headed = False\n    sb.xvfb = False\n    sb.xvfb_metrics = None\n    sb.start_page = None\n    sb.locale_code = None\n    sb.pdb_option = False\n    sb.protocol = \"http\"\n    sb.servername = \"localhost\"\n    sb.port = 4444\n    sb.data = None\n    sb.var1 = None\n    sb.var2 = None\n    sb.var3 = None\n    sb.variables = {}\n    sb.account = None\n    sb.environment = \"test\"\n    sb.env = \"test\"\n    sb.user_agent = None\n    sb.incognito = False\n    sb.guest_mode = False\n    sb.dark_mode = False\n    sb.devtools = False\n    sb.mobile_emulator = False\n    sb.device_metrics = None\n    sb.extension_zip = None\n    sb.extension_dir = None\n    sb.binary_location = None\n    sb_config.binary_location = None\n    sb.driver_version = None\n    sb.page_load_strategy = None\n    sb.database_env = \"test\"\n    sb.log_path = constants.Logs.LATEST + os.sep\n    sb.archive_logs = False\n    sb.disable_cookies = False\n    sb.disable_js = False\n    sb.disable_csp = False\n    sb.disable_ws = False\n    sb.enable_ws = False\n    sb.enable_sync = False\n    sb.use_auto_ext = False\n    sb.undetectable = False\n    sb.uc_cdp_events = False\n    sb.uc_subprocess = False\n    sb.log_cdp_events = False\n    sb.no_sandbox = False\n    sb.disable_gpu = False\n    sb._multithreaded = False\n    sb._reuse_session = False\n    sb._reuse_class_session = False\n    sb._crumbs = False\n    sb._disable_beforeunload = False\n    sb.visual_baseline = False\n    sb.use_wire = False\n    sb.window_position = None\n    sb.window_size = None\n    sb.maximize_option = False\n    sb.is_context_manager = False\n    sb.save_screenshot_after_test = False\n    sb.no_screenshot_after_test = False\n    sb.timeout_multiplier = None\n    sb.pytest_html_report = None\n    sb.with_db_reporting = False\n    sb.with_s3_logging = False\n    sb.js_checking_on = False\n    sb.recorder_mode = False\n    sb.recorder_ext = False\n    sb.record_sleep = False\n    sb.rec_behave = False\n    sb.rec_print = False\n    sb.report_on = False\n    sb.is_pytest = False\n    sb.slow_mode = False\n    sb.demo_mode = False\n    sb.time_limit = None\n    sb.demo_sleep = None\n    sb.dashboard = False\n    sb.dash_title = None\n    sb._dash_initialized = False\n    sb.message_duration = None\n    sb.block_images = False\n    sb.do_not_track = False\n    sb.external_pdf = False\n    sb.remote_debug = False\n    sb.settings_file = None\n    sb.user_data_dir = None\n    sb.chromium_arg = None\n    sb.firefox_arg = None\n    sb.firefox_pref = None\n    sb.disable_features = None\n    sb.proxy_string = None\n    sb.proxy_bypass_list = None\n    sb.proxy_pac_url = None\n    sb.multi_proxy = False\n    sb.host_resolver_rules = None\n    sb.enable_3d_apis = False\n    sb.swiftshader = False\n    sb.ad_block_on = False\n    sb.is_nosetest = False\n    sb.highlights = None\n    sb.interval = None\n    sb.cap_file = None\n    sb.cap_string = None\n\n    # Set a few sb_config vars early in case parsing args fails\n    sb_config.dashboard = None\n    sb_config._has_logs = None\n    sb_config._has_exception = None\n    sb_config.save_screenshot = None\n    sb_config.reuse_class_session = None\n\n    browsers = set()  # To error if selecting more than one\n    valid_browsers = constants.ValidBrowsers.valid_browsers\n    valid_envs = constants.ValidEnvs.valid_envs\n    # Process command-line options\n    userdata = context.config.userdata\n    for key in userdata.keys():\n        # Convert --ARG to ARG, etc.\n        if key.startswith(\"--\"):\n            key = key[2:]\n        if key.startswith(\"-\"):\n            key = key[1:]\n        low_key = key.lower()\n        # Handle: -D browser=BROWSER\n        if low_key == \"browser\":\n            browser = userdata[key].lower()\n            if browser in valid_browsers:\n                sb.browser = browser\n                browsers.add(browser)\n            elif browser == \"true\":\n                raise Exception(\n                    '\\nThe \"browser\" argument requires a value!'\n                    \"\\nChoose from %s.\"\n                    '\\nEg. -D browser=\"edge\"' % valid_browsers\n                )\n            else:\n                raise Exception(\n                    '\\n\"%s\" is not a valid \"browser\" selection!'\n                    \"\\nChoose from %s.\"\n                    '\\nEg. -D browser=\"edge\"' % (browser, valid_browsers)\n                )\n            continue\n        # Handle: -D BROWSER\n        if low_key in valid_browsers:\n            browser = low_key\n            sb.browser = browser\n            browsers.add(browser)\n            continue\n        # Handle: -D headless\n        if low_key == \"headless\":\n            sb.headless = True\n            continue\n        # Handle: -D headless2\n        if low_key == \"headless1\":\n            sb.headless1 = True\n            sb.headless = True\n            continue\n        # Handle: -D headless2\n        if low_key == \"headless2\":\n            sb.headless2 = True\n            continue\n        # Handle: -D headed / gui\n        if low_key in [\"headed\", \"gui\"]:\n            sb.headed = True\n            continue\n        # Handle: -D xvfb\n        if low_key == \"xvfb\":\n            sb.xvfb = True\n            continue\n        # Handle: -D xvfb-metrics=STR / xvfb_metrics=STR\n        if low_key in [\"xvfb-metrics\", \"xvfb_metrics\"]:\n            xvfb_metrics = userdata[key]\n            if xvfb_metrics == \"true\":\n                xvfb_metrics = sb.xvfb_metrics  # revert to default\n            sb.xvfb_metrics = xvfb_metrics\n            continue\n        # Handle: -D start-page=URL / start_page=URL / url=URL\n        if low_key in [\"start-page\", \"start_page\", \"url\"]:\n            start_page = userdata[key]\n            if start_page == \"true\":\n                start_page = sb.start_page  # revert to default\n            sb.start_page = start_page\n            continue\n        # Handle: -D locale-code=CODE / locale_code=CODE / locale=CODE\n        if low_key in [\"locale-code\", \"locale_code\", \"locale\"]:\n            sb.start_page = userdata[key]\n            continue\n        # Handle: -D pdb / ipdb\n        if low_key in [\"pdb\", \"ipdb\"]:\n            sb.pdb_option = True\n            continue\n        # Handle: -D protocol=PROTOCOL\n        if low_key == \"protocol\":\n            protocol = userdata[key].lower()\n            if protocol in [\"http\", \"https\"]:\n                sb.protocol = protocol\n            elif protocol == \"true\":\n                raise Exception(\n                    '\\nThe Selenium Grid \"protocol\" argument requires a value!'\n                    '\\nChoose from [\"http\", \"https\"]'\n                    '\\nEg. -D protocol=\"https\"'\n                )\n            else:\n                raise Exception(\n                    '\\n\"%s\" is not a valid Selenium Grid \"protocol\" selection!'\n                    '\\nChoose from [\"http\", \"https\"]'\n                    '\\nEg. -D protocol=\"https\"' % protocol\n                )\n            continue\n        # Handle: -D server=SERVERNAME / servername=SERVERNAME\n        if low_key in [\"server\", \"servername\"]:\n            servername = userdata[key]\n            if servername == \"true\":\n                servername = sb.servername  # revert to default\n            sb.servername = servername\n            continue\n        # Handle: -D port=PORT\n        if low_key == \"port\":\n            port = int(userdata[key])\n            if port == \"true\":\n                port = sb.port  # revert to default\n            sb.port = port\n            continue\n        # Handle: -D data=DATA\n        if low_key == \"data\":\n            sb.data = userdata[key]\n            continue\n        # Handle: -D var1=DATA\n        if low_key == \"var1\":\n            sb.var1 = userdata[key]\n            continue\n        # Handle: -D var2=DATA\n        if low_key == \"var2\":\n            sb.var2 = userdata[key]\n            continue\n        # Handle: -D var3=DATA\n        if low_key == \"var3\":\n            sb.var3 = userdata[key]\n            continue\n        # Handle: -D variables=\"{'KEY':'VALUE','KEY2':'VALUE2'}\"\n        if low_key == \"variables\":\n            variables = userdata[key]\n            if variables and isinstance(variables, str) and len(variables) > 0:\n                bad_input = False\n                if (\n                    not variables.startswith(\"{\")\n                    or not variables.endswith(\"}\")\n                ):\n                    bad_input = True\n                else:\n                    try:\n                        variables = ast.literal_eval(variables)\n                        if not isinstance(variables, dict):\n                            bad_input = True\n                    except Exception:\n                        bad_input = True\n                if bad_input:\n                    raise Exception(\n                        '\\nExpecting a Python dictionary for \"variables\"!'\n                        \"\\nEg. -D variables=\\\"{'KEY':'VALUE', 'KEY2':123}\\\"\"\n                    )\n            else:\n                variables = {}\n            continue\n        # Handle: -D account=ACCOUNT\n        if low_key == \"account\":\n            sb.account = userdata[key]\n            continue\n        # Handle: -D env=ENVIRONMENT\n        if low_key == \"environment\":\n            environment = userdata[key].lower()\n            if environment in valid_envs:\n                sb.environment = environment\n                sb.env = environment\n            elif environment == \"true\":\n                raise Exception(\n                    '\\nThe \"env\" argument requires a value!'\n                    \"\\nChoose from %s.\"\n                    '\\nEg. -D env=\"production\"' % valid_envs\n                )\n            else:\n                raise Exception(\n                    '\\n\"%s\" is not a valid \"env\" selection!'\n                    \"\\nChoose from %s.\"\n                    '\\nEg. -D env=\"production\"' % (environment, valid_envs)\n                )\n            continue\n        # Handle: -D user-agent=STRING / user_agent=STRING / agent=STRING\n        if low_key in [\"user-agent\", \"user_agent\", \"agent\"]:\n            user_agent = userdata[key]\n            if user_agent == \"true\":\n                user_agent = sb.user_agent  # revert to default\n            sb.user_agent = user_agent\n            continue\n        # Handle: -D incognito / incognito-mode / incognito_mode\n        if low_key in [\"incognito\", \"incognito-mode\", \"incognito_mode\"]:\n            sb.incognito = True\n            continue\n        # Handle: -D guest / guest-mode / guest_mode\n        if low_key in [\"guest\", \"guest-mode\", \"guest_mode\"]:\n            sb.guest_mode = True\n            continue\n        # Handle: -D dark / dark-mode / dark_mode\n        if low_key in [\"dark\", \"dark-mode\", \"dark_mode\"]:\n            sb.dark_mode = True\n            continue\n        # Handle: -D devtools / open-devtools / open_devtools\n        if low_key in [\"devtools\", \"open-devtools\", \"open_devtools\"]:\n            sb.devtools = True\n            continue\n        # Handle: -D mobile / mobile-emulator / mobile_emulator\n        if low_key in [\"mobile\", \"mobile-emulator\", \"mobile_emulator\"]:\n            sb.mobile_emulator = True\n            continue\n        # Handle: -D metrics=STR / device-metrics=STR / device_metrics=STR\n        if low_key in [\"metrics\", \"device-metrics\", \"device_metrics\"]:\n            device_metrics = userdata[key]\n            if device_metrics == \"true\":\n                device_metrics = sb.device_metrics  # revert to default\n            sb.device_metrics = device_metrics\n            continue\n        # Handle: -D crx=ZIP / extension-zip=ZIP / extension_zip=ZIP\n        if low_key in [\"crx\", \"extension-zip\", \"extension_zip\"]:\n            extension_zip = userdata[key]\n            if extension_zip == \"true\":\n                extension_zip = sb.extension_zip  # revert to default\n            sb.extension_zip = extension_zip\n            continue\n        # Handle: -D extension-dir=DIR / extension_dir=DIR\n        if low_key in [\"extension-dir\", \"extension_dir\"]:\n            extension_dir = userdata[key]\n            if extension_dir == \"true\":\n                extension_dir = sb.extension_dir  # revert to default\n            sb.extension_dir = extension_dir\n            continue\n        # Handle: -D binary-location=PATH / binary_location=PATH / bl=PATH\n        if low_key in [\"binary-location\", \"binary_location\", \"bl\"]:\n            binary_location = userdata[key]\n            if binary_location == \"true\":\n                binary_location = sb.binary_location  # revert to default\n            sb.binary_location = binary_location\n            sb_config.binary_location = binary_location\n            continue\n        # Handle: -D use-chromium\n        if low_key in [\"use-chromium\"] and not sb_config.binary_location:\n            binary_location = \"_chromium_\"\n            sb.binary_location = binary_location\n            sb_config.binary_location = binary_location\n            continue\n        # Handle: -D cft\n        if low_key in [\"cft\"] and not sb_config.binary_location:\n            binary_location = \"cft\"\n            sb.binary_location = binary_location\n            sb_config.binary_location = binary_location\n            continue\n        # Handle: -D chs\n        if low_key in [\"chs\"] and not sb_config.binary_location:\n            binary_location = \"chs\"\n            sb.binary_location = binary_location\n            sb_config.binary_location = binary_location\n            continue\n        # Handle: -D driver-version=VER / driver_version=VER\n        if low_key in [\"driver-version\", \"driver_version\"]:\n            driver_version = userdata[key]\n            if driver_version == \"true\":\n                driver_version = sb.driver_version  # revert to default\n            sb.driver_version = driver_version\n            continue\n        # Handle: -D pls=PLS / page-load-strategy=PLS / page_load_strategy=PLS\n        if low_key in [\"pls\", \"page-load-strategy\", \"page_load_strategy\"]:\n            page_load_strategy = userdata[key].lower()\n            if page_load_strategy in [\"normal\", \"eager\", \"none\"]:\n                sb.page_load_strategy = page_load_strategy\n            elif page_load_strategy == \"true\":\n                raise Exception(\n                    '\\nThe \"pls\" / \"page-load-strategy\" arg requires a value!'\n                    '\\nChoose from [\"normal\", \"eager\", \"none\"]'\n                    '\\nEg. -D pls=\"none\"'\n                )\n            else:\n                raise Exception(\n                    '\\n\"%s\" is not a valid \"pls\" / \"page-load-strategy\" value!'\n                    '\\nChoose from [\"normal\", \"eager\", \"none\"]'\n                    '\\nEg. -D pls=\"none\"' % page_load_strategy\n                )\n            continue\n        # Handle: -D database-env=ENVIRONMENT / database_env=ENVIRONMENT\n        if low_key in [\"database-env\", \"database_env\"]:\n            database_env = userdata[key].lower()\n            if database_env in valid_envs:\n                sb.database_env = database_env\n            elif database_env == \"true\":\n                raise Exception(\n                    '\\nThe \"database-env\" argument requires a value!'\n                    \"\\nChoose from %s.\"\n                    '\\nEg. -D database-env=\"production\"' % valid_envs\n                )\n            else:\n                raise Exception(\n                    '\\n\"%s\" is not a valid \"database-env\" selection!'\n                    \"\\nChoose from %s.\"\n                    '\\nEg. -D database-env=\"production\"'\n                    % (environment, valid_envs)\n                )\n            continue\n        # Handle: -D archive-logs / archive_logs\n        if low_key in [\"archive-logs\", \"archive_logs\"]:\n            sb.archive_logs = True\n            continue\n        # Handle: -D disable-cookies / disable_cookies\n        if low_key in [\"disable-cookies\", \"disable_cookies\"]:\n            sb.disable_cookies = True\n            continue\n        # Handle: -D disable-js / disable_js\n        if low_key in [\"disable-js\", \"disable_js\"]:\n            sb.disable_js = True\n            continue\n        # Handle: -D disable-csp / disable_csp / dcsp\n        if low_key in [\"disable-csp\", \"disable_csp\", \"dcsp\"]:\n            sb.disable_csp = True\n            continue\n        # Handle: -D disable-ws / disable_ws / dws\n        if low_key in [\"disable-ws\", \"disable_ws\", \"dws\"]:\n            sb.disable_ws = True\n            continue\n        # Handle: -D enable-ws / enable_ws\n        if low_key in [\"enable-ws\", \"enable_ws\"]:\n            sb.enable_ws = True\n            continue\n        # Handle: -D enable-sync / enable_sync\n        if low_key in [\"enable-sync\", \"enable_sync\"]:\n            sb.enable_sync = True\n            continue\n        # Handle: -D use-auto-ext / use_auto_ext / auto-ext\n        if low_key in [\"use-auto-ext\", \"use_auto_ext\", \"auto-ext\"]:\n            sb.use_auto_ext = True\n            continue\n        # Handle: -D undetected / undetectable / uc\n        if low_key in [\"undetected\", \"undetectable\", \"uc\"]:\n            sb.undetectable = True\n            continue\n        # Handle: -D uc-cdp-events / uc_cdp_events / uc-cdp\n        if low_key in [\"uc-cdp-events\", \"uc_cdp_events\", \"uc-cdp\"]:\n            sb.uc_cdp_events = True\n            sb.undetectable = True\n            continue\n        # Handle: -D uc-subprocess / uc_subprocess / uc-sub\n        if low_key in [\"uc-subprocess\", \"uc_subprocess\", \"uc-sub\"]:\n            sb.uc_subprocess = True\n            sb.undetectable = True\n            continue\n        # Handle: -D log-cdp-events / log_cdp_events / log-cdp\n        if low_key in [\"log-cdp-events\", \"log_cdp_events\", \"log-cdp\"]:\n            sb.log_cdp_events = True\n            continue\n        # Handle: -D no-sandbox / no_sandbox\n        if low_key in [\"no-sandbox\", \"no_sandbox\"]:\n            sb.no_sandbox = True\n            continue\n        # Handle: -D disable-gpu / disable_gpu\n        if low_key in [\"disable-gpu\", \"disable_gpu\"]:\n            sb.disable_gpu = True\n            continue\n        # Handle: -D rs / reuse-session / reuse_session\n        if low_key in [\"rs\", \"reuse-session\", \"reuse_session\"]:\n            sb._reuse_session = True\n            continue\n        # Handle: -D rcs / rfs / reuse-class-session / reuse-feature-session\n        if low_key in [\n            \"rcs\", \"rfs\", \"reuse-class-session\", \"reuse-feature-session\"\n        ]:\n            sb._reuse_session = True\n            sb._reuse_class_session = True\n            continue\n        # Handle: -D crumbs\n        if low_key == \"crumbs\":\n            sb._crumbs = True\n            continue\n        # Handle: -D disable-beforeunload / disable_beforeunload\n        if low_key in [\"disable-beforeunload\", \"disable_beforeunload\"]:\n            sb._disable_beforeunload = True\n            continue\n        # Handle: -D sjw / skip-js-waits / skip_js_waits\n        if low_key in [\"sjw\", \"skip-js-waits\", \"skip_js_waits\"]:\n            settings.SKIP_JS_WAITS = True\n            continue\n        # Handle: -D wfa / wait-for-angularjs / wait_for_angularjs\n        if low_key in [\"wfa\", \"wait-for-angularjs\", \"wait_for_angularjs\"]:\n            settings.WAIT_FOR_ANGULARJS = True\n            continue\n        # Handle: -D visual-baseline / visual_baseline\n        if low_key in [\"visual-baseline\", \"visual_baseline\"]:\n            sb.visual_baseline = True\n            continue\n        # Handle: -D wire\n        if low_key == \"wire\":\n            sb.use_wire = True\n            continue\n        # Handle: -D window-position=X,Y / window_position=X,Y\n        if low_key in [\"window-position\", \"window_position\"]:\n            window_position = userdata[key]\n            if window_position == \"true\":\n                window_position = sb.window_position  # revert to default\n            sb.window_position = window_position\n            continue\n        # Handle: -D window-size=Width,Height / window_size=Width,Height\n        if low_key in [\"window-size\", \"window_size\"]:\n            window_size = userdata[key]\n            if window_size == \"true\":\n                window_size = sb.window_size  # revert to default\n            sb.window_size = window_size\n            continue\n        # Handle: -D maximize / fullscreen / maximize-window\n        if low_key in [\n            \"maximize\", \"fullscreen\", \"maximize-window\", \"maximize_window\"\n        ]:\n            sb.maximize_option = True\n            continue\n        # Handle: -D screenshot / save-screenshot / save_screenshot / ss\n        if low_key in [\n            \"screenshot\", \"save-screenshot\", \"save_screenshot\", \"ss\"\n        ]:\n            sb.save_screenshot_after_test = True\n            continue\n        # Handle: -D no-screenshot / no_screenshot / ns\n        if low_key in [\"no-screenshot\", \"no_screenshot\", \"ns\"]:\n            sb.no_screenshot_after_test = True\n            continue\n        # Handle: -D timeout-multiplier=FLOAT / timeout_multiplier=FLOAT\n        if low_key in [\"timeout-multiplier\", \"timeout_multiplier\"]:\n            timeout_multiplier = userdata[key]\n            if timeout_multiplier == \"true\":\n                timeout_multiplier = sb.timeout_multiplier  # revert to default\n            sb.timeout_multiplier = timeout_multiplier\n            continue\n        # Handle: -D with-db-reporting / with-db_reporting\n        if low_key in [\"with-db-reporting\", \"with-db_reporting\"]:\n            sb.with_db_reporting = True\n            continue\n        # Handle: -D with-s3-logging / with-s3_logging\n        if low_key in [\"with-s3-logging\", \"with-s3_logging\"]:\n            sb.with_s3_logging = True\n            continue\n        # Handle: -D check-js / check_js\n        if low_key in [\"check-js\", \"check_js\"]:\n            sb.js_checking_on = True\n            continue\n        # Handle: -D recorder / record / rec / codegen\n        if low_key in [\"recorder\", \"record\", \"rec\", \"codegen\"]:\n            sb.recorder_mode = True\n            sb.recorder_ext = True\n            continue\n        # Handle: -D rec-behave / rec-gherkin\n        if low_key in [\"rec-behave\", \"rec-gherkin\"]:\n            sb.rec_behave = True\n            sb.recorder_mode = True\n            sb.recorder_ext = True\n            continue\n        # Handle: -D record-sleep / record_sleep / rec-sleep / rec_sleep\n        if low_key in [\"record-sleep\", \"rec-sleep\"]:\n            sb.record_sleep = True\n            sb.recorder_mode = True\n            sb.recorder_ext = True\n            continue\n        # Handle: -D rec-print\n        if low_key in [\"rec-print\"]:\n            sb.rec_print = True\n            sb.recorder_mode = True\n            sb.recorder_ext = True\n            continue\n        # Handle: -D slow / slowmo / slow-mode / slow_mode\n        if low_key in [\"slow\", \"slowmo\", \"slow-mode\", \"slow_mode\"]:\n            sb.slow_mode = True\n            continue\n        # Handle: -D demo / demo-mode / demo_mode\n        if low_key in [\"demo\", \"demo-mode\", \"demo_mode\"]:\n            sb.demo_mode = True\n            continue\n        # Handle: -D time-limit / time_limit / timelimit\n        if low_key in [\"time-limit\", \"time_limit\", \"timelimit\"]:\n            time_limit = userdata[key]\n            if time_limit == \"true\":\n                time_limit = sb.time_limit  # revert to default\n            sb.time_limit = time_limit\n            continue\n        # Handle: -D demo-sleep / demo_sleep\n        if low_key in [\"demo-sleep\", \"demo_sleep\"]:\n            demo_sleep = userdata[key]\n            if demo_sleep == \"true\":\n                demo_sleep = sb.demo_sleep  # revert to default\n            sb.demo_sleep = demo_sleep\n            continue\n        # Handle: -D dashboard\n        if low_key == \"dashboard\":\n            sb.dashboard = True\n            continue\n        # Handle: -D dash-title=TITLE / dash_title=TITLE\n        if low_key in [\"dash-title\", \"dash_title\"]:\n            sb.dash_title = userdata[key]\n            continue\n        # Handle: -D message-duration / message_duration\n        if low_key in [\"message-duration\", \"message_duration\"]:\n            message_duration = userdata[key]\n            if message_duration == \"true\":\n                message_duration = sb.message_duration  # revert to default\n            sb.message_duration = message_duration\n            continue\n        # Handle: -D block-images / block_images\n        if low_key in [\"block-images\", \"block_images\"]:\n            sb.block_images = True\n            continue\n        # Handle: -D do-not-track / do_not_track\n        if low_key in [\"do-not-track\", \"do_not_track\"]:\n            sb.do_not_track = True\n            continue\n        # Handle: -D external-pdf / external_pdf\n        if low_key in [\"external-pdf\", \"external_pdf\"]:\n            sb.external_pdf = True\n            continue\n        # Handle: -D remote-debug / remote_debug / remote-debugger\n        if low_key in [\"remote-debug\", \"remote_debug\", \"remote-debugger\"]:\n            sb.remote_debug = True\n            continue\n        # Handle: -D settings=FILE / settings-file=FILE / settings_file=FILE\n        if low_key in [\"settings\", \"settings-file\", \"settings_file\"]:\n            settings_file = userdata[key]\n            if settings_file == \"true\":\n                settings_file = sb.settings_file  # revert to default\n            sb.settings_file = settings_file\n            continue\n        # Handle: -D user-data-dir=DIR / user_data_dir=DIR\n        if low_key in [\"user-data-dir\", \"user_data_dir\"]:\n            user_data_dir = userdata[key]\n            if user_data_dir == \"true\":\n                user_data_dir = sb.user_data_dir  # revert to default\n            sb.user_data_dir = user_data_dir\n            continue\n        # Handle: -D chromium-arg=\"ARG=N,ARG2\" / chromium_arg=\"ARG=N,ARG2\"\n        if low_key in [\"chromium-arg\", \"chromium_arg\"]:\n            chromium_arg = userdata[key]\n            if chromium_arg == \"true\":\n                chromium_arg = sb.chromium_arg  # revert to default\n            sb.chromium_arg = chromium_arg\n            continue\n        # Handle: -D firefox-arg=\"ARG=N,ARG2\" / firefox_arg=\"ARG=N,ARG2\"\n        if low_key in [\"firefox-arg\", \"firefox_arg\"]:\n            firefox_arg = userdata[key]\n            if firefox_arg == \"true\":\n                firefox_arg = sb.firefox_arg  # revert to default\n            sb.firefox_arg = firefox_arg\n            continue\n        # Handle: -D firefox-pref=\"PREF:VAL\" / firefox_pref=\"PREF:VAL\"\n        if low_key in [\"firefox-pref\", \"firefox_pref\"]:\n            firefox_pref = userdata[key]\n            if firefox_pref == \"true\":\n                firefox_pref = sb.firefox_pref  # revert to default\n            sb.firefox_pref = firefox_pref\n            continue\n        # Handle: -D disable-features=\"F1,F2\" / disable_features=\"F1,F2\"\n        if low_key in [\"disable-features\", \"disable_features\"]:\n            disable_features = userdata[key]\n            if disable_features == \"true\":\n                disable_features = sb.disable_features  # revert to default\n            sb.disable_features = disable_features\n            continue\n        # Handle: -D proxy=SERVER:PORT / proxy=USERNAME:PASSWORD@SERVER:PORT\n        if low_key in [\"proxy\", \"proxy-server\", \"proxy-string\"]:\n            proxy_string = userdata[key]\n            if proxy_string == \"true\":\n                proxy_string = sb.proxy_string  # revert to default\n            sb.proxy_string = proxy_string\n            continue\n        # Handle: -D proxy-bypass-list=\"DOMAIN1;D2\" / proxy_bypass_list=\"D1;D2\"\n        if low_key in [\"proxy-bypass-list\", \"proxy_bypass_list\"]:\n            proxy_bypass_list = userdata[key]\n            if proxy_bypass_list == \"true\":\n                proxy_bypass_list = sb.proxy_bypass_list  # revert to default\n            sb.proxy_bypass_list = proxy_bypass_list\n            continue\n        # Handle: -D proxy-pac-url=URL / proxy-pac-url=USERNAME:PASSWORD@URL\n        if low_key in [\"proxy-pac-url\", \"pac-url\"]:\n            proxy_pac_url = userdata[key]\n            if proxy_pac_url == \"true\":\n                proxy_pac_url = sb.proxy_pac_url  # revert to default\n            sb.proxy_pac_url = proxy_pac_url\n            continue\n        # Handle: -D multi-proxy / multi_proxy\n        if low_key in [\"multi-proxy\", \"multi_proxy\"]:\n            sb.multi_proxy = True\n            continue\n        # Handle: -D host-resolver-rules=RULES / host_resolver_rules=RULES\n        if low_key in [\"host-resolver-rules\", \"host_resolver_rules\"]:\n            host_resolver_rules = userdata[key]\n            if host_resolver_rules == \"true\":\n                host_resolver_rules = sb.host_resolver_rules\n            sb.host_resolver_rules = host_resolver_rules\n            continue\n        # Handle: -D enable-3d-apis / enable_3d_apis\n        if low_key in [\"enable-3d-apis\", \"enable_3d_apis\"]:\n            sb.enable_3d_apis = True\n            continue\n        # Handle: -D swiftshader\n        if low_key == \"swiftshader\":\n            sb.swiftshader = True\n            continue\n        # Handle: -D adblock / ad-block / ad_block / block-ads / block_ads\n        if low_key in [\n            \"adblock\", \"ad-block\", \"ad_block\", \"block-ads\", \"block_ads\"\n        ]:\n            sb.ad_block_on = True\n            continue\n        # Handle: -D highlights=NUM\n        if low_key == \"highlights\":\n            highlights = userdata[key]\n            if highlights == \"true\":\n                highlights = sb.highlights  # revert to default\n            sb.highlights = highlights\n            continue\n        # Handle: -D interval=SECONDS\n        if low_key == \"interval\":\n            interval = userdata[key]\n            if interval == \"true\":\n                interval = sb.interval  # revert to default\n            sb.interval = interval\n            continue\n        # Handle: -D cap-file=FILE / cap_file=FILE\n        if low_key in [\"cap-file\", \"cap_file\"]:\n            cap_file = userdata[key]\n            if cap_file == \"true\":\n                cap_file = sb.cap_file  # revert to default\n            sb.cap_file = cap_file\n            continue\n        # Handle: -D cap-string=STRING / cap_string=STRING\n        if low_key == \"cap_string\":\n            cap_string = userdata[key]\n            if cap_string == \"true\":\n                cap_string = sb.cap_string  # revert to default\n            sb.cap_string = cap_string\n            continue\n\n    # Fail immediately if trying to set more than one default browser.\n    if len(browsers) > 1:\n        raise Exception(\n            \"\\nOnly ONE default browser is allowed!\\n\"\n            \"%s browsers were selected: %s\" % (len(browsers), browsers)\n        )\n    if sb.browser in [\"opera\", \"brave\", \"comet\", \"atlas\"]:\n        bin_loc = detect_b_ver.get_binary_location(sb.browser)\n        if bin_loc and os.path.exists(bin_loc):\n            sb_config._cdp_browser = sb.browser\n            sb_config._cdp_bin_loc = bin_loc\n            sb_config.binary_location = bin_loc\n            sb.binary_location = bin_loc\n    # Recorder Mode can still optimize scripts in \"-D headless2\" mode.\n    if sb.recorder_ext and sb.headless:\n        sb.headless = False\n        sb.headless1 = False\n        sb.headless2 = True\n    if sb.headless2 and sb.browser == \"firefox\":\n        sb.headless2 = False  # Only for Chromium browsers\n        sb.headless = True  # Firefox has regular headless\n    elif sb.browser not in [\"chrome\", \"edge\"]:\n        sb.headless2 = False  # Only for Chromium browsers\n    if (\n        sb.binary_location\n        and sb.binary_location.lower() == \"chs\"\n        and sb.browser == \"chrome\"\n    ):\n        sb.headless = True\n        sb.headless1 = False\n        sb.headless2 = False\n    # Recorder Mode only supports Chromium browsers.\n    if sb.recorder_ext and (sb.browser not in [\"chrome\", \"edge\"]):\n        raise Exception(\n            \"\\n\\n  Recorder Mode ONLY supports Chrome and Edge!\"\n            '\\n  (Your browser choice was: \"%s\")\\n' % sb.browser\n        )\n    # The Xvfb virtual display server is for Linux OS Only.\n    if sb.xvfb and not is_linux:\n        sb.xvfb = False\n    if (\n        is_linux\n        and not sb.headed\n        and not sb.headless\n        and not sb.headless2\n        and not sb.xvfb\n    ):\n        if not sb.undetectable:\n            print(\n                '(Linux uses \"-D headless\" by default. '\n                'To override, use \"-D headed\" / \"-D gui\". '\n                'For Xvfb mode instead, use \"-D xvfb\". '\n                \"Or you can hide this info by using\"\n                '\"-D headless\" / \"-D headless2\" / \"-D uc\".)'\n            )\n            sb.headless = True\n        else:\n            sb.xvfb = True\n    # Recorder Mode can still optimize scripts in --headless2 mode.\n    if sb.recorder_mode and sb.headless:\n        sb.headless = False\n        sb.headless1 = False\n        sb.headless2 = True\n    if not sb.headless and not sb.headless2:\n        sb.headed = True\n    if sb.browser == \"safari\" and sb.headless:\n        sb.headless = False  # Safari doesn't support headless mode\n        sb.headless1 = False\n    if sb.save_screenshot_after_test and sb.no_screenshot_after_test:\n        sb.save_screenshot_after_test = False  # \"no_screenshot\" has priority\n    if sb.servername != \"localhost\":\n        # Using Selenium Grid\n        # (Set -D server=\"127.0.0.1\" for localhost Grid)\n        # If the port is \"443\", the protocol is \"https\"\n        if str(sb.port) == \"443\":\n            sb.protocol = \"https\"\n    if (\n        (sb.enable_ws is None and sb.disable_ws is None)\n        or (sb.disable_ws is not None and not sb.disable_ws)\n        or (sb.enable_ws is not None and sb.enable_ws)\n    ):\n        sb.enable_ws = True\n        sb.disable_ws = False\n    else:\n        sb.enable_ws = False\n        sb.disable_ws = True\n    if sb.window_position:\n        window_position = sb.window_position\n        if window_position.count(\",\") != 1:\n            message = (\n                '\\n\\n  window_position expects an \"x,y\" string!'\n                '\\n  (Your input was: \"%s\")\\n' % window_position\n            )\n            raise Exception(message)\n        window_position = window_position.replace(\" \", \"\")\n        win_x = None\n        win_y = None\n        try:\n            win_x = int(window_position.split(\",\")[0])\n            win_y = int(window_position.split(\",\")[1])\n        except Exception:\n            message = (\n                '\\n\\n  Expecting integer values for \"x,y\"!'\n                '\\n  (window_position input was: \"%s\")\\n'\n                % window_position\n            )\n            raise Exception(message)\n        settings.WINDOW_START_X = win_x\n        settings.WINDOW_START_Y = win_y\n    if sb.window_size:\n        window_size = sb.window_size\n        if window_size.count(\",\") != 1:\n            message = (\n                '\\n\\n  window_size expects a \"width,height\" string!'\n                '\\n  (Your input was: \"%s\")\\n' % window_size\n            )\n            raise Exception(message)\n        window_size = window_size.replace(\" \", \"\")\n        width = None\n        height = None\n        try:\n            width = int(window_size.split(\",\")[0])\n            height = int(window_size.split(\",\")[1])\n        except Exception:\n            message = (\n                '\\n\\n  Expecting integer values for \"width,height\"!'\n                '\\n  (window_size input was: \"%s\")\\n' % window_size\n            )\n            raise Exception(message)\n        settings.CHROME_START_WIDTH = width\n        settings.CHROME_START_HEIGHT = height\n        settings.HEADLESS_START_WIDTH = width\n        settings.HEADLESS_START_HEIGHT = height\n\n    # Set sb_config\n    sb_config.browser = sb.browser\n    sb_config.headless = sb.headless\n    sb_config.headless_active = False\n    sb_config.headed = sb.headed\n    sb_config.is_behave = True\n    sb_config.is_pytest = False\n    sb_config.is_nosetest = False\n    sb_config.is_context_manager = False\n    sb_config.window_position = sb.window_position\n    sb_config.window_size = sb.window_size\n    sb_config.maximize_option = sb.maximize_option\n    sb_config.xvfb = sb.xvfb\n    sb_config.xvfb_metrics = sb.xvfb_metrics\n    sb_config.reuse_class_session = sb._reuse_class_session\n    sb_config.save_screenshot = sb.save_screenshot_after_test\n    sb_config.no_screenshot = sb.no_screenshot_after_test\n    sb_config._has_logs = False\n    sb_config.variables = sb.variables\n    sb_config.dashboard = sb.dashboard\n    sb_config.dash_title = sb.dash_title\n    sb_config.pdb_option = sb.pdb_option\n    sb_config.rec_behave = sb.rec_behave\n    sb_config.rec_print = sb.rec_print\n    sb_config.disable_cookies = sb.disable_cookies\n    sb_config.disable_js = sb.disable_js\n    sb_config.disable_csp = sb.disable_csp\n    sb_config.record_sleep = sb.record_sleep\n    sb_config._is_timeout_changed = False\n    sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT\n    sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT\n    sb_config._recorded_actions = {}\n    sb_config._behave_recorded_actions = {}\n    # Dashboard-specific variables\n    sb_config._results = {}  # SBase Dashboard test results\n    sb_config._duration = {}  # SBase Dashboard test duration\n    sb_config._display_id = {}  # SBase Dashboard display ID\n    sb_config._d_t_log_path = {}  # SBase Dashboard test log path\n    sb_config._dash_html = None  # SBase Dashboard HTML copy\n    sb_config._test_id = None  # SBase Dashboard test id\n    sb_config._latest_display_id = None  # The latest SBase display id\n    sb_config._dashboard_initialized = False  # Becomes True after init\n    sb_config._has_exception = False  # This becomes True if any test fails\n    sb_config._multithreaded = False  # This becomes True if multithreading\n    sb_config._only_unittest = True  # If any test uses BaseCase, becomes False\n    sb_config._sbase_detected = False  # Becomes True during SeleniumBase tests\n    sb_config._extra_dash_entries = []  # Dashboard entries for non-SBase tests\n    sb_config._using_html_report = False  # Becomes True when using html report\n    sb_config._dash_is_html_report = False  # Dashboard becomes the html report\n    sb_config._saved_dashboard_pie = None  # Copy of pie chart for html report\n    sb_config._dash_final_summary = None  # Dash status to add to html report\n    sb_config._html_report_name = None  # The name of the pytest html report\n\n    if sb_config.dash_title:\n        constants.Dashboard.TITLE = sb_config.dash_title.replace(\"_\", \" \")\n\n    log_helper.log_folder_setup(\n        constants.Logs.LATEST + \"/\", sb.archive_logs\n    )\n    download_helper.reset_downloads_folder()\n    proxy_helper.remove_proxy_zip_if_present()\n    return sb\n\n\ndef calculate_test_id(file_name, scenario_name):\n    file_name = file_name.replace(\"/\", \".\").replace(\"\\\\\", \".\")\n    scenario_name = re.sub(r\"[^\\w\" + r\"_ \" + r\"]\", \"\", scenario_name)\n    scenario_name = scenario_name.replace(\" \", \"_\")\n    if \" -- @\" in scenario_name:\n        scenario_name = scenario_name.split(\" # \")[0].rstrip()\n    test_id = \"%s.%s\" % (file_name, scenario_name)\n    return test_id\n\n\ndef calculate_display_id(file_name, line_num, scenario_name):\n    if \" -- @\" in scenario_name:\n        scenario_name = scenario_name.split(\" # \")[0].rstrip()\n    display_id = \"%s:%s => %s\" % (file_name, line_num, scenario_name)\n    return display_id\n\n\ndef get_test_id():\n    file_name = sb_config.behave_scenario.filename\n    file_name = file_name.replace(\"/\", \".\").replace(\"\\\\\", \".\")\n    scenario_name = sb_config.behave_scenario.name\n    if \" -- @\" in scenario_name:\n        scenario_name = scenario_name.split(\" # \")[0].rstrip()\n    scenario_name = re.sub(r\"[^\\w\" + r\"_ \" + r\"]\", \"\", scenario_name)\n    scenario_name = scenario_name.replace(\" \", \"_\")\n    test_id = \"%s.%s\" % (file_name, scenario_name)\n    return test_id\n\n\ndef get_display_id():\n    file_name = sb_config.behave_scenario.filename\n    line_num = str(sb_config.behave_scenario.line)\n    scenario_name = sb_config.behave_scenario.name\n    if \" -- @\" in scenario_name:\n        scenario_name = scenario_name.split(\" # \")[0].rstrip()\n    display_id = \"%s:%s => %s\" % (file_name, line_num, scenario_name)\n    return display_id\n\n\ndef _get_test_ids_():\n    test_id = get_test_id()\n    display_id = get_display_id()\n    return test_id, display_id\n\n\ndef dashboard_pre_processing():\n    import subprocess\n\n    command_args = sys.argv[1:]\n    command_string = \" \".join(command_args)\n    command_string = command_string.replace(\"--quiet\", \"\")\n    command_string = command_string.replace(\"-q\", \"\")\n    proc = subprocess.Popen(\n        \"behave -d %s --show-source\" % command_string,\n        stdout=subprocess.PIPE,\n        shell=True,\n    )\n    (output, error) = proc.communicate()\n    filename_count = 0\n    filename_list = []\n    feature_count = 0\n    feature_list = []\n    scenario_count = 0\n    scenario_list = []\n    sb_config.item_count = 0\n    sb_config.item_count_passed = 0\n    sb_config.item_count_failed = 0\n    sb_config.item_count_skipped = 0\n    sb_config.item_count_untested = 0\n    filename = None\n    feature_name = None\n    scenario_name = None\n    if is_windows:\n        output = output.decode(\"latin1\")\n    else:\n        output = output.decode(\"utf-8\")\n    for row in output.replace(\"\\r\", \"\").split(\"\\n\"):\n        if row.startswith(\"Feature: \"):\n            filename_count += 1\n            feature_count += 1\n            feature_name = row.split(\"Feature: \")[1]\n            if \" # features/\" in feature_name:\n                filename = feature_name.split(\" # features/\")[-1]\n                filename = \"features/\" + filename.split(\":\")[0]\n                feature_name = feature_name.split(\" # features/\")[0]\n            elif \" # features\\\\\" in feature_name:\n                filename = feature_name.split(\" # features\\\\\")[-1]\n                filename = \"features\\\\\" + filename.split(\":\")[0]\n                feature_name = feature_name.split(\" # features\\\\\")[0]\n            else:\n                filename = feature_name.split(\" # \")[-1]\n                filename = filename.split(\":\")[0]\n                feature_name = feature_name.split(\" # \")[-1]\n            filename = filename.strip()\n            filename_list.append(filename)\n            feature_name = feature_name.strip()\n            feature_list.append(feature_name)  # Maybe filename is good enough\n        elif (\n            row.startswith(\"  Scenario: \")\n            or row.startswith(\"  Scenario Outline: \")\n        ):\n            line_num = row.split(\":\")[-1]\n            scenario_count += 1\n            scenario_name = None\n            if row.startswith(\"  Scenario: \"):\n                scenario_name = row.split(\"  Scenario: \")[-1]\n            else:\n                scenario_name = row.split(\"  Scenario Outline: \")[-1]\n            if \" -- @\" in scenario_name:\n                scenario_name = scenario_name.split(\" # \")[0].rstrip()\n            elif \" # features/\" in scenario_name:\n                scenario_name = scenario_name.split(\" # features/\")[0]\n            else:\n                scenario_name = scenario_name.split(\" # \")[0]\n            scenario_name = scenario_name.strip()\n            scenario_list.append(scenario_name)\n            # Dashboard row preparation\n            test_id = calculate_test_id(filename, scenario_name)\n            display_id = calculate_display_id(\n                filename, line_num, scenario_name\n            )\n            sb_config._results[test_id] = \"Untested\"\n            sb_config._duration[test_id] = \"-\"\n            sb_config._display_id[test_id] = display_id\n            sb_config._d_t_log_path[test_id] = None\n    # Set the total number of dashboard entries\n    sb_config.item_count = scenario_count\n\n\ndef _create_dashboard_assets_():\n    from seleniumbase.js_code.live_js import live_js\n    from seleniumbase.core.style_sheet import get_pytest_style\n\n    abs_path = os.path.abspath(\".\")\n    assets_folder = os.path.join(abs_path, \"assets\")\n    if not os.path.exists(assets_folder):\n        os.makedirs(assets_folder)\n    pytest_style_css = os.path.join(assets_folder, \"pytest_style.css\")\n    add_pytest_style_css = True\n    if os.path.exists(pytest_style_css):\n        existing_pytest_style = None\n        with open(pytest_style_css, mode=\"r\") as f:\n            existing_pytest_style = f.read()\n        if existing_pytest_style == get_pytest_style():\n            add_pytest_style_css = False\n    if add_pytest_style_css:\n        out_file = open(pytest_style_css, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(get_pytest_style())\n        out_file.close()\n    live_js_file = os.path.join(assets_folder, \"live.js\")\n    add_live_js_file = True\n    if os.path.exists(live_js_file):\n        existing_live_js = None\n        with open(live_js_file, mode=\"r\") as f:\n            existing_live_js = f.read()\n        if existing_live_js == live_js:\n            add_live_js_file = False\n    if add_live_js_file:\n        out_file = open(live_js_file, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(live_js)\n        out_file.close()\n\n\ndef behave_dashboard_prepare():\n    \"\"\"Print the dashboard path if at least one test runs.\"\"\"\n    if sb_config.item_count > 0:\n        _create_dashboard_assets_()\n        # Output Dashboard info to the console\n        sb_config.item_count_untested = sb_config.item_count\n        dash_path = os.path.join(os.getcwd(), \"dashboard.html\")\n        star_len = len(\"Dashboard: \") + len(dash_path)\n        with suppress(Exception):\n            terminal_size = os.get_terminal_size().columns\n            if terminal_size > 30 and star_len > terminal_size:\n                star_len = terminal_size\n        stars = \"*\" * star_len\n        c1 = \"\"\n        cr = \"\"\n        if not is_linux:\n            c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n            cr = colorama.Style.RESET_ALL\n        print(\"Dashboard: %s%s%s\\n%s\" % (c1, dash_path, cr, stars))\n\n\ndef _perform_behave_unconfigure_():\n    if hasattr(sb_config, \"multi_proxy\") and not sb_config.multi_proxy:\n        proxy_helper.remove_proxy_zip_if_present()\n    if hasattr(sb_config, \"reuse_session\") and sb_config.reuse_session:\n        # Close the shared browser session\n        if sb_config.shared_driver:\n            try:\n                if (\n                    not is_windows\n                    or sb_config.browser == \"ie\"\n                    or sb_config.shared_driver.service.process\n                ):\n                    sb_config.shared_driver.quit()\n            except AttributeError:\n                pass\n            except Exception:\n                pass\n        sb_config.shared_driver = None\n    if hasattr(sb_config, \"archive_logs\"):\n        log_helper.archive_logs_if_set(\n            constants.Logs.LATEST + \"/\", sb_config.archive_logs\n        )\n    log_helper.clear_empty_logs()\n    # Dashboard post-processing: Disable time-based refresh and stamp complete\n    if not hasattr(sb_config, \"dashboard\") or not sb_config.dashboard:\n        # Done with \"behave_unconfigure\" unless using the Dashboard\n        return\n    stamp = \"\\n<!--Test Run Complete-->\"\n    find_it = constants.Dashboard.META_REFRESH_HTML\n    swap_with = \"\"  # Stop refreshing the page after the run is done\n    find_it_2 = \"Awaiting results... (Refresh the page for updates)\"\n    swap_with_2 = (\n        \"Test Run ENDED: Some results UNREPORTED due to skipped tearDown()\"\n    )\n    find_it_3 = '<td class=\"col-result\">Untested</td>'\n    swap_with_3 = '<td class=\"col-result\">Unreported</td>'\n    # These use caching to prevent extra method calls\n    DASH_PIE_PNG_1 = constants.Dashboard.get_dash_pie_1()\n    DASH_PIE_PNG_2 = constants.Dashboard.get_dash_pie_2()\n    find_it_4 = 'href=\"%s\"' % DASH_PIE_PNG_1\n    swap_with_4 = 'href=\"%s\"' % DASH_PIE_PNG_2\n    try:\n        abs_path = os.path.abspath(\".\")\n        dashboard_path = os.path.join(abs_path, \"dashboard.html\")\n        # Part 1: Finalizing the dashboard / integrating html report\n        if os.path.exists(dashboard_path):\n            the_html_d = None\n            with open(dashboard_path, mode=\"r\", encoding=\"utf-8\") as f:\n                the_html_d = f.read()\n            if sb_config._multithreaded and \"-c\" in sys.argv:\n                # Threads have \"-c\" in sys.argv, except for the last\n                raise Exception('Break out of \"try\" block.')\n            if sb_config._multithreaded:\n                dash_pie_loc = constants.Dashboard.DASH_PIE\n                pie_path = os.path.join(abs_path, dash_pie_loc)\n                if os.path.exists(pie_path):\n                    import json\n\n                    with open(pie_path, mode=\"r\") as f:\n                        dash_pie = f.read().strip()\n                    sb_config._saved_dashboard_pie = json.loads(dash_pie)\n            # If the test run doesn't complete by itself, stop refresh\n            the_html_d = the_html_d.replace(find_it, swap_with)\n            the_html_d = the_html_d.replace(find_it_2, swap_with_2)\n            the_html_d = the_html_d.replace(find_it_3, swap_with_3)\n            the_html_d = the_html_d.replace(find_it_4, swap_with_4)\n            the_html_d += stamp\n            with open(dashboard_path, mode=\"w\", encoding=\"utf-8\") as f:\n                f.write(the_html_d)  # Finalize the dashboard\n    except KeyboardInterrupt:\n        pass\n    except Exception:\n        pass\n\n\ndef do_final_driver_cleanup_as_needed():\n    with suppress(Exception):\n        if hasattr(sb_config, \"last_driver\") and sb_config.last_driver:\n            if (\n                not is_windows\n                or sb_config.browser == \"ie\"\n                or sb_config.last_driver.service.process\n            ):\n                sb_config.last_driver.quit()\n\n\ndef _perform_behave_terminal_summary_():\n    latest_logs_dir = os.path.join(\n        os.getcwd(), constants.Logs.LATEST + os.sep\n    )\n    dash_path = os.path.join(os.getcwd(), \"dashboard.html\")\n    equals_len = len(\"Dashboard: \") + len(dash_path)\n    with suppress(Exception):\n        terminal_size = os.get_terminal_size().columns\n        if terminal_size > 30 and equals_len > terminal_size:\n            equals_len = terminal_size\n    equals = \"=\" * (equals_len + 2)\n    c2 = \"\"\n    cr = \"\"\n    if not is_linux:\n        c2 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX\n        cr = colorama.Style.RESET_ALL\n    if sb_config.dashboard:\n        # Print link a second time because the first one may be off-screen\n        print(\"%s- Dashboard:%s %s\" % (c2, cr, dash_path))\n    if (\n        sb_config._has_exception\n        or sb_config.save_screenshot\n        or sb_config._has_logs\n    ):\n        # Log files are generated during test failures and Screenshot Mode\n        print(\"%s--- LogPath:%s %s\" % (c2, cr, latest_logs_dir))\n    if (\n        sb_config.dashboard\n        and not (sb_config._has_exception or sb_config.save_screenshot)\n    ):\n        print(\"%s\" % equals)\n    elif (\n        not sb_config.dashboard\n        and (sb_config._has_exception or sb_config.save_screenshot)\n    ):\n        print(\"%s\" % equals[2:])\n    elif (\n        sb_config.dashboard\n        and (sb_config._has_exception or sb_config.save_screenshot)\n    ):\n        print(\"%s\" % equals[2:])\n\n\n###########################################\n\n\ndef before_all(context):\n    context.sb = get_configured_sb(context)\n    if context.sb.dashboard:\n        dashboard_pre_processing()\n        behave_dashboard_prepare()\n\n\ndef before_feature(context, feature):\n    sb_config.behave_feature = feature\n    session_helper.end_reused_class_session_as_needed()\n\n\ndef before_scenario(context, scenario):\n    sb_config.behave_context = context\n    sb_config.behave_scenario = scenario\n    sb_config.behave_line_num = scenario.line\n    sb_config.behave_step_count = 0\n    context.sb.setUp()\n\n\ndef before_step(context, step):\n    sb_config.behave_step_count += 1\n    sb_config.behave_step = step\n\n\ndef after_step(context, step):\n    sb_config.behave_step = step\n    if step.status == \"failed\":\n        number = sb_config.behave_step_count\n        print(\">>> STEP FAILED:  (#%s) %s\" % (number, step.name))\n        print(\"Class / Feature: \", sb_config.behave_feature.name)\n        print(\"Test / Scenario: \", sb_config.behave_scenario.name)\n\n\ndef after_scenario(context, scenario):\n    sb = context.sb\n    sb_config.last_driver = sb.driver\n    sb_config.behave_context = context\n    sb_config.behave_scenario = scenario\n    sb.tearDown()\n\n\ndef after_feature(context, feature):\n    sb_config.feature = feature\n    session_helper.end_reused_class_session_as_needed()\n\n\ndef after_all(context):\n    _perform_behave_unconfigure_()\n    do_final_driver_cleanup_as_needed()\n    _perform_behave_terminal_summary_()\n"
  },
  {
    "path": "seleniumbase/behave/steps.py",
    "content": "from sbase import steps  # noqa\r\n\"\"\"\r\nThis is a proxy for importing SeleniumBase-Behave steps.\r\nA short path name shortens the output of \"behave\" tests.\r\nSee \"../../sbase/steps.py\" for the actual list of steps.\r\nEg.\r\n    BEHAVE STEP NAME  # ../../sbase/steps.py:11 0.111s\r\n    BEHAVE STEP NAME  # ../../sbase/steps.py:74 0.550s\r\n    BEHAVE STEP NAME  # ../../sbase/steps.py:67 0.385s\r\n\"\"\"\r\n"
  },
  {
    "path": "seleniumbase/common/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## [seleniumbase/common](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common) decorators and security.\n\n### Part 1: Decorators - (from [decorators.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/decorators.py))\n\n#### Use these Python decorators with your test methods as needed:\n\n* ``@print_runtime(description=None, limit=None)``\n\n* ``@runtime_limit(limit, description=None)``\n\n* ``@retry_on_exception(tries=6, delay=1, backoff=2, max_delay=32)``\n\n* ``@rate_limited(max_per_second)``\n\nExample demonstrating a rate-limited printing functionality:\n\n```python\nimport unittest\nfrom seleniumbase import decorators\n\nclass MyTestClass(unittest.TestCase):\n\n    @decorators.rate_limited(3.5)  # The arg is max calls per second\n    def print_item(self, item):\n        print(item)\n\n    def test_rate_limited_printing(self):\n        print(\"\\nRunning rate-limited print test:\")\n        for item in range(1, 11):\n            self.print_item(item)\n```\n\n### Part 2: String/Password Obfuscation, Encryption, and Decryption\n\n#### Intro:\n\nOften in your tests, you may need to login to a website to perform testing. This generally means storing passwords in plaintext formats. For security reasons, that may not be an optimal solution. For this reason, encryption/obfuscation tools have been built here to help you mask your passwords in your tests. It's not a bulletproof solution, but it can keep anyone looking over your shoulder during test creation from getting your login passwords if they don't have your encryption key, which is stored in a separate file.\n\n#### Usage:\n\n* First, set your custom encryption/decryption key in your local clone of [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py). (If you modify the key later, you'll need to encrypt all your passwords again.)\n\n* Next, use [obfuscate.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/obfuscate.py) to obfuscate/encrypt passwords into coded strings:\n\n```zsh\npython obfuscate.py\n\nEnter password to obfuscate: (CTRL+C to exit)\nPassword: *********\nVerify password:\nPassword: *********\n\nHere is the obfuscated password:\n$^*ENCRYPT=RXlYMSJWTz8HSwM=?&#$\n```\n\n(You can also use [unobfuscate.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/unobfuscate.py) to encrypt passwords without having them masked while typing them. Or you can use it to decrypt an obfuscated password.)\n\n* Finally, in your tests you can now decrypt obfuscated passwords for use in login methods like this:\n\n```python\nfrom seleniumbase import encryption\n...\npassword = encryption.decrypt('$^*ENCRYPT=RXlYMSJWTz8HSwM=?&#$')\n```\n\n(You'll notice that encrypted strings have a common start token and end token. This is to help tell them apart from non-encrypted strings. You can customize these tokens in [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py). The current default setting is `$^*ENCRYPT=` for the start token and `?&#$` for the end token.)\n\nSee [test_decryption.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_decryption.py) for an example of decrypting encrypted passwords in tests.\n"
  },
  {
    "path": "seleniumbase/common/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/common/decorators.py",
    "content": "import colorama\nimport logging\nimport math\nimport sys\nimport time\nimport warnings\nfrom contextlib import contextmanager\nfrom functools import wraps\nfrom seleniumbase.common.exceptions import TimeoutException\n\nc1 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\ncr = colorama.Style.RESET_ALL\nif \"linux\" in sys.platform:\n    c1 = cr = \"\"\n\n\n@contextmanager\ndef print_runtime(description=None, limit=None):\n    \"\"\"Print the runtime duration of a method or \"with\"-block after completion.\n    If limit, fail if the runtime duration exceeds the limit after completion.\n\n    Method / Function example usage ->\n        from seleniumbase import decorators\n\n        @decorators.print_runtime(\"My Method\")\n        def my_method():\n            # code ...\n            # code ...\n\n    \"with\"-block example usage ->\n        from seleniumbase import decorators\n\n        with decorators.print_runtime(\"My Code Block\"):\n            # code ...\n            # code ... \"\"\"\n    if not description:\n        description = \"Code Block\"\n    description = str(description)\n    if limit:\n        limit = float(\"%.2f\" % limit)\n        if limit < 0.01:\n            limit = 0.01  # Minimum runtime limit\n    exception = None\n    start_time = time.time()\n    try:\n        yield\n    except Exception as e:\n        exception = e\n        raise\n    finally:\n        end_time = time.time()\n        run_time = end_time - start_time\n        name = description\n        info = c1 + \"<info>\" + cr\n        # Print times with a statistically significant number of decimal places\n        if run_time < 0.00001:\n            print(\"%s - {%s} ran for %.8f seconds.\" % (info, name, run_time))\n        elif run_time < 0.0001:\n            print(\"%s - {%s} ran for %.7f seconds.\" % (info, name, run_time))\n        elif run_time < 0.001:\n            print(\"%s - {%s} ran for %.6f seconds.\" % (info, name, run_time))\n        elif run_time < 0.01:\n            print(\"%s - {%s} ran for %.5f seconds.\" % (info, name, run_time))\n        elif run_time < 0.1:\n            print(\"%s - {%s} ran for %.4f seconds.\" % (info, name, run_time))\n        elif run_time < 1:\n            print(\"%s - {%s} ran for %.3f seconds.\" % (info, name, run_time))\n        else:\n            print(\"%s - {%s} ran for %.2f seconds.\" % (info, name, run_time))\n        if limit and limit > 0 and run_time > limit:\n            message = (\n                \"\\n {%s} duration of %.2fs exceeded the time limit of %.2fs!\"\n                % (name, run_time, limit)\n            )\n            if exception:\n                message = exception.msg + \"\\nAND \" + message\n            raise TimeoutException(message)\n\n\n@contextmanager\ndef runtime_limit(limit, description=None):\n    \"\"\"Fail if the runtime duration of a method or \"with\"-block exceeds limit.\n    (The failure won't occur until after the method or \"with\"-block completes.)\n\n    Method / Function example usage ->\n        from seleniumbase import decorators\n\n        @decorators.runtime_limit(4.5)\n        def my_method():\n            # code ...\n            # code ...\n\n    \"with\"-block example usage ->\n        from seleniumbase import decorators\n\n        with decorators.runtime_limit(32):\n            # code ...\n            # code ... \"\"\"\n    limit = float(\"%.2f\" % limit)\n    if limit < 0.01:\n        limit = 0.01  # Minimum runtime limit\n    if not description:\n        description = \"Code Block\"\n    description = str(description)\n    exception = None\n    start_time = time.time()\n    try:\n        yield\n    except Exception as e:\n        exception = e\n        raise\n    finally:\n        end_time = time.time()\n        run_time = end_time - start_time\n        # Fail if the runtime of the code block exceeds the limit\n        if limit and limit > 0 and run_time > limit:\n            message = (\n                \"\\n {%s} duration of %.2fs exceeded the time limit of %.2fs!\"\n                % (description, run_time, limit)\n            )\n            if exception:\n                message = exception.msg + \"\\nAND \" + message\n            raise TimeoutException(message)\n\n\ndef retry_on_exception(tries=6, delay=1, backoff=2, max_delay=32):\n    \"\"\"Decorator for implementing exponential backoff for retrying on failures.\n\n    tries: Max number of tries to execute the wrapped function before failing.\n    delay: Delay time in seconds before the FIRST retry.\n    backoff: Multiplier to extend the initial delay by for each retry.\n    max_delay: Max time in seconds to wait between retries.\"\"\"\n    tries = math.floor(tries)\n    if tries < 1:\n        raise ValueError('\"tries\" must be greater than or equal to 1.')\n    if delay < 0:\n        raise ValueError('\"delay\" must be greater than or equal to 0.')\n    if backoff < 1:\n        raise ValueError('\"backoff\" must be greater than or equal to 1.')\n    if max_delay < delay:\n        raise ValueError('\"max_delay\" must be greater than or equal to delay.')\n\n    def decorated_function_with_retry(func):\n        @wraps(func)\n        def function_to_retry(*args, **kwargs):\n            local_tries, local_delay = tries, delay\n            while local_tries > 1:\n                try:\n                    return func(*args, **kwargs)\n                except Exception as e:\n                    if local_delay > max_delay:\n                        local_delay = max_delay\n                    logging.exception(\n                        \"%s: Retrying in %d seconds...\" % (str(e), local_delay)\n                    )\n                    time.sleep(local_delay)\n                    local_tries -= 1\n                    local_delay *= backoff\n            return func(*args, **kwargs)\n\n        return function_to_retry\n\n    return decorated_function_with_retry\n\n\ndef rate_limited(max_per_second):\n    \"\"\"This decorator limits how often a method can get called in a second.\n    If the limit is exceeded, the call will be held in a queue until\n    enough time has passed.\n    Useful when trying to avoid overloading a system with rapid calls.\"\"\"\n    import threading\n\n    min_interval = 1.0 / float(max_per_second)\n\n    def decorate(func):\n        last_time_called = [0.0]\n        rate_lock = threading.Lock()  # To support multi-threading\n\n        def rate_limited_function(*args, **kargs):\n            try:\n                rate_lock.acquire(True)\n                elapsed = None\n                elapsed = time.process_time() - last_time_called[0]\n                wait_time_remaining = min_interval - elapsed\n                if wait_time_remaining > 0:\n                    time.sleep(wait_time_remaining)\n                last_time_called[0] = time.process_time()\n            finally:\n                rate_lock.release()\n            return func(*args, **kargs)\n\n        return rate_limited_function\n\n    return decorate\n\n\ndef deprecated(message=None):\n    \"\"\"This decorator marks methods as deprecated.\n    A warning is displayed if the method is called.\"\"\"\n    import inspect\n\n    def decorated_method_to_deprecate(func):\n        if inspect.isclass(func):\n            # Handle a deprecated class differently from a deprecated method\n            msg = \"Class {}() is DEPRECATED!\".format(func.__name__)\n            if message:\n                msg += \" *** %s ***\" % message\n            warnings.simplefilter(\"always\", DeprecationWarning)  # See Warnings\n            warnings.warn(msg, category=DeprecationWarning, stacklevel=2)\n            warnings.simplefilter(\"default\", DeprecationWarning)  # Set Default\n            return func\n\n        @wraps(func)\n        def new_func(*args, **kwargs):\n            msg = \"Method {}() is DEPRECATED!\".format(func.__name__)\n            if message:\n                msg += \" *** %s ***\" % message\n            warnings.simplefilter(\"always\", DeprecationWarning)  # See Warnings\n            warnings.warn(msg, category=DeprecationWarning, stacklevel=2)\n            warnings.simplefilter(\"default\", DeprecationWarning)  # Set Default\n            return func(*args, **kwargs)\n\n        return new_func\n\n    return decorated_method_to_deprecate\n"
  },
  {
    "path": "seleniumbase/common/encryption.py",
    "content": "\"\"\"This is mainly for string obfuscation.\"\"\"\n\nimport base64\nimport codecs\nimport hashlib\nfrom seleniumbase.config import settings\n\n\ndef str_xor(string, key):\n    if len(key) < 1:\n        raise Exception(\"2nd arg of str_xor() must be a string of length > 0!\")\n    if len(string) > len(key):\n        difference = len(string) - len(key)\n        key = key + (((difference / len(key)) * key) + key)\n    result = None\n    try:\n        result = \"\".join(\n            [chr(ord(c1) ^ ord(c2)) for (c1, c2) in zip(string, key)]\n        )\n    except Exception:\n        string = string.decode(\"utf-8\")\n        result = \"\".join(\n            [chr(ord(c1) ^ ord(c2)) for (c1, c2) in zip(string, key)]\n        )\n    return result\n\n\ndef is_obfuscated(string):\n    # Based on settings, determines if a string has already been obfuscated.\n    # Obfuscated strings have a common predefined start token and end token.\n    start_token = settings.OBFUSCATION_START_TOKEN\n    end_token = settings.OBFUSCATION_END_TOKEN\n    return string.startswith(start_token) and string.endswith(end_token)\n\n\ndef shuffle_string(string):\n    if len(string) < 2:\n        return string\n    return string[1::2] + string[::2]\n\n\ndef reverse_shuffle_string(string):\n    if len(string) < 2:\n        return string\n    new_string = \"\"\n    odd = len(string) % 2 == 1\n    part1 = string[: int(len(string) / 2) : 1]  # noqa: E203\n    part2 = string[int(len(string) / 2) :: 1]  # noqa: E203\n    for c in range(len(part1)):\n        new_string += part2[c]\n        new_string += part1[c]\n    if odd:\n        new_string += part2[-1]\n    return new_string\n\n\ndef blend_strings(string1, string2):\n    smallest_length = min(len(string1), len(string2))\n    new_string = \"\"\n    for c in range(smallest_length):\n        new_string += string1[c]\n        new_string += string2[c]\n    if len(string1) > len(string2):\n        new_string += string1[smallest_length:]\n    elif len(string2) > len(string1):\n        new_string += string2[smallest_length:]\n    else:\n        # Equal length strings\n        pass\n    return new_string\n\n\ndef rotate(string, n):\n    return string[n:] + string[:n]\n\n\ndef ord_string_sum(string):\n    count = 0\n    try:\n        for c in string:\n            count += ord(c)\n    except Exception:\n        string = string.decode(\"utf-8\")\n        for c in string:\n            count += ord(c)\n    return count\n\n\ndef decrypt(string):\n    # Password/String obfuscation/de-obfuscation\n    # Used for both encryption and decryption\n    # If you update the algorithm, you must re-encrypt all encrypted passwords!\n    encryption_key = settings.ENCRYPTION_KEY\n    start_token = settings.OBFUSCATION_START_TOKEN\n    end_token = settings.OBFUSCATION_END_TOKEN\n    already_encrypted = False\n    if is_obfuscated(string):\n        already_encrypted = True\n        string = string[len(start_token) : -len(end_token)]  # noqa: E203\n        string = base64.b64decode(codecs.encode(string))\n    # Obfuscate the key used for string obfuscation\n    hd1 = hashlib.sha256(str(encryption_key).encode(\"utf-8\")).hexdigest()\n    hd2 = hashlib.sha256(str(encryption_key[::-1]).encode(\"utf-8\")).hexdigest()\n    b64_key = base64.b64encode(codecs.encode(encryption_key * 8))\n    xor_key = \"\".join(\n        [\n            chr(ord(str(c3)) - int(c1, 16) - int(c2, 16))\n            for (c1, c2, c3) in zip(hd1, hd2, b64_key.decode(\"utf-8\"))\n        ]\n    )\n    xor_key = blend_strings(xor_key, encryption_key)\n    if len(xor_key) % 7 == 0:\n        xor_key = xor_key + encryption_key[-1]\n    xor_key = shuffle_string((xor_key * 8)[::7])\n    # Use the str_xor method for the main string obfuscation / de-obfuscation\n    if not already_encrypted:\n        if len(string) > 0:\n            rem1 = (ord_string_sum(string)) % 3\n            rem2 = (ord_string_sum(string)) % 4\n            rem3 = (ord_string_sum(string)) % 2\n            rem4 = (len(string) + ord_string_sum(string)) % 2\n        if len(string) % 2 != 0:\n            if rem3 == 1:\n                string = (\n                    chr(ord(string[-1]) - 5 - rem1)\n                    + string\n                    + chr(ord(string[-1]) - 13 - rem1)\n                )\n            else:\n                string = (\n                    chr(ord(string[-1]) - 11 - rem1)\n                    + string\n                    + chr(ord(string[-1]) - 23 - rem1)\n                )\n        elif len(string) > 1:\n            if rem4 == 1:\n                string = (\n                    chr(ord(string[0]) - 19 + rem2)\n                    + string\n                    + chr(ord(string[0]) - 7 - rem2)\n                )\n            else:\n                string = (\n                    chr(ord(string[0]) - 26 + rem2)\n                    + string\n                    + chr(ord(string[0]) - 12 - rem2)\n                )\n        rem5 = (len(string) + ord_string_sum(string)) % 23\n        string = rotate(string, rem5)\n        result = str_xor(shuffle_string(string)[::-1], xor_key)\n        rem6 = (len(result) + ord_string_sum(result)) % 17\n        result = rotate(result, rem6)\n    else:\n        rem6 = (len(string) + ord_string_sum(string)) % 17\n        string = rotate(string, -rem6)\n        result = reverse_shuffle_string(str_xor(string, xor_key)[::-1])\n        if len(result) > 2:\n            rem5 = (len(result) + ord_string_sum(result)) % 23\n            result = rotate(result, -rem5)\n            result = result[1:-1]\n    # Finalize encryption of non-encrypted string\n    if not already_encrypted:\n        result = base64.b64encode(codecs.encode(result))\n        result = start_token + result.decode(\"utf-8\") + end_token\n    return result\n"
  },
  {
    "path": "seleniumbase/common/exceptions.py",
    "content": "\"\"\" SeleniumBase Exceptions\n    LinkTextNotFoundException => Called when expected link text is not visible.\n    NoSuchFileException => Called when self.assert_downloaded_file(...) fails.\n    NoSuchOptionException => Called when select_option_by_*() lacks the option.\n    NotConnectedException => Called when Internet is not reachable when needed.\n    NotUsingChromeException => Used by Chrome-only methods if not using Chrome.\n    NotUsingChromiumException => Used by Chromium-only methods if not Chromium.\n    OutOfScopeException => Used by BaseCase methods when setUp() is skipped.\n    ProxyConnectionException => Called when the proxy connection failed.\n    TextNotVisibleException => Called when the expected text is not visible.\n    TimeLimitExceededException => Called when exceeding \"--time-limit=SECONDS\".\n    TimeoutException => Called when some timeout limit has been exceeded.\n    VisualException => Called when there's a Visual Diff Assertion Failure.\n\"\"\"\n\n\nclass LinkTextNotFoundException(Exception):\n    pass\n\n\nclass NoSuchFileException(Exception):\n    pass\n\n\nclass NoSuchOptionException(Exception):\n    pass\n\n\nclass NotConnectedException(Exception):\n    pass\n\n\nclass NotUsingChromeException(Exception):\n    pass\n\n\nclass NotUsingChromiumException(Exception):\n    pass\n\n\nclass OutOfScopeException(Exception):\n    pass\n\n\nclass ProxyConnectionException(Exception):\n    pass\n\n\nclass TextNotVisibleException(Exception):\n    pass\n\n\nclass TimeLimitExceededException(Exception):\n    pass\n\n\nclass TimeoutException(Exception):\n    pass\n\n\nclass VisualException(Exception):\n    pass\n\n\n\"\"\" Selenium Exceptions (Simplified for SeleniumBase) \"\"\"\n\n\nclass WebDriverException(Exception):\n    \"\"\"Base webdriver exception.\"\"\"\n\n    def __init__(self, msg=None, screen=None, stacktrace=None):\n        super().__init__()\n        self.msg = msg\n        self.screen = screen\n        self.stacktrace = stacktrace\n\n    def __str__(self):\n        exception_msg = \"Message: %s\\n\" % self.msg\n        if self.screen:\n            exception_msg += \"Screenshot: available via screen\\n\"\n        if self.stacktrace:\n            stacktrace = \"\\n\".join(self.stacktrace)\n            exception_msg += \"Stacktrace:\\n%s\" % stacktrace\n        return exception_msg\n\n\nclass InvalidSwitchToTargetException(WebDriverException):\n    \"\"\"Thrown when frame or window target to be switched doesn't exist.\"\"\"\n\n\nclass NoSuchFrameException(InvalidSwitchToTargetException):\n    \"\"\"Thrown when frame target to be switched doesn't exist.\"\"\"\n\n\nclass NoSuchWindowException(InvalidSwitchToTargetException):\n    \"\"\"Thrown when window target to be switched doesn't exist.\"\"\"\n\n\nclass NoSuchElementException(WebDriverException):\n    \"\"\"Thrown when element could not be found.\"\"\"\n\n\nclass NoSuchAttributeException(WebDriverException):\n    \"\"\"Thrown when the attribute of element could not be found.\"\"\"\n\n\nclass InvalidElementStateException(WebDriverException):\n    \"\"\"Thrown when a command could not be completed because the element is in\n    an invalid state.\"\"\"\n\n\nclass NoAlertPresentException(WebDriverException):\n    \"\"\"Thrown when switching to no presented alert.\"\"\"\n\n\nclass ElementNotVisibleException(InvalidElementStateException):\n    \"\"\"Thrown when an element is present in the DOM, but not visible.\"\"\"\n"
  },
  {
    "path": "seleniumbase/common/obfuscate.py",
    "content": "\"\"\"\nObfuscates a string/password into a string that can be decrypted later on.\n\nUsage:\npython obfuscate.py\nThen enter the password.\nThe result is an encrypted password.\n\"\"\"\n\nfrom seleniumbase.common import encryption\nimport getpass\nimport time\n\n\ndef main():\n    try:\n        while 1:\n            print(\"\\nEnter password to obfuscate: (CTRL+C to exit)\")\n            password = getpass.getpass()\n            print(\"Verify password:\")\n            verify_password = getpass.getpass()\n            if password != verify_password:\n                print(\"*** ERROR: Passwords don't match! .. Please try again!\")\n                continue\n            print(\"\\nHere is the obfuscated password:\")\n            time.sleep(0.2)\n            encrypted_password = encryption.decrypt(password)\n            print(encrypted_password)\n            time.sleep(0.2)\n            print(\"\\nInside a test, use the following to decrypt it:\\n\")\n            time.sleep(0.2)\n            print(\"    from seleniumbase import encryption\")\n            print('    encryption.decrypt(\"%s\")' % encrypted_password)\n            time.sleep(0.2)\n    except KeyboardInterrupt:\n        print(\"\\nExiting...\\n\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "seleniumbase/common/unobfuscate.py",
    "content": "\"\"\"\nUnobfuscates an encrypted string/password into a plaintext string/password.\n\nUsage:\npython unobfuscate.py\nThen enter the encrypted string/password.\nThe result is a plaintext string/password.\nWorks the same as obfuscate.py, but doesn't mask the input.\n\"\"\"\n\nfrom seleniumbase.common import encryption\nimport time\n\n\ndef main():\n    input_method = input\n    try:\n        while 1:\n            code = input_method(\n                \"\\nEnter obfuscated/encrypted string: (CTRL+C to exit):\\n\"\n            )\n            print(\"\\nHere is the unobfuscated string/password:\")\n            time.sleep(0.07)\n            print(encryption.decrypt(code))\n            time.sleep(0.21)\n    except KeyboardInterrupt:\n        print(\"\\nExiting...\\n\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "seleniumbase/config/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/config/ad_block_list.py",
    "content": "\"\"\"\nFor use with SeleniumBase ad_block functionality.\n\nUsage:\n    On the command line:\n    \"pytest SOME_TEST.py --ad_block\"\n\n    From inside a test:\n    self.ad_block()\n\nIf using the command line version, the ad_block functionality gets\nactivated after \"self.wait_for_ready_state_complete()\" is called,\nwhich is always run after page loads, unless changed in \"settings.py\".\nUsing ad_block will slow down test runs a little. (Use only if necessary.)\n\nFormat: A CSS Selector that's ready for JavaScript's querySelectorAll()\n\"\"\"\n\nAD_BLOCK_LIST = [\n    '[aria-label=\"Ads\"]',\n    '[src*=\"adservice.\"]',\n    '[src*=\"adclick\"]',\n    '[src*=\"doubleclick\"]',\n    '[src*=\"snigelweb.com\"]',\n    '[src*=\"tagservices.com\"]',\n    '[src*=\"adsby\"]',\n    '[src*=\"adroll.com\"]',\n    '[src*=\"pagead\"]',\n    '[src*=\"3lift\"]',\n    '[src*=\"smartads.\"]',\n    '[src*=\"ad_nexus\"]',\n    '[src*=\"/ads/\"]',\n    '[src*=\"moatads.com\"]',\n    '[src*=\"adsystem\"]',\n    '[src*=\"connectad\"]',\n    '[src*=\"/adservice.\"]',\n    '[src*=\"syndication.com\"]',\n    '[src*=\"/ads.\"]',\n    '[src*=\"lijit\"]',\n    '[src*=\"pagead\"]',\n    '[src*=\"adnxs.com\"]',\n    '[src*=\"onetag-sys.com\"]',\n    '[src*=\"indexww.com\"]',\n    '[src*=\"3lift.com\"]',\n    '[src*=\"rubiconproject.com\"]',\n    '[src*=\"brealtime.com\"]',\n    '[src*=\"33across.com\"]',\n    '[src*=\"adsrvr\"]',\n    '[type=\"data-doubleclick\"]',\n    \"iframe[data-google-container-id]\",\n    'iframe[src*=\"doubleclick\"]',\n    'iframe[src*=\"/AdServer/\"]',\n    'iframe[src*=\"openx.net\"]',\n    'iframe[onload*=\"doWithAds\"]',\n    'iframe[id*=\"_ads_frame\"]',\n    'iframe[style=\"height:0px;width:0px;display:none;\"]',\n    '[aria-label=\"Ad\"]',\n    '[aria-label=\"Timeline: Trending now\"]',\n    '[aria-label=\"Timeline: Carousel\"]',\n    '[aria-roledescription=\"carousel\"]',\n    '[aria-label=\"Who to follow\"]',\n    '[class*=\"sponsored-content\"]',\n    '[class*=\"adsbygoogle\"]',\n    '[class^=\"adroll\"]',\n    '[data-ad-details*=\"Advertisement\"]',\n    '[data-native_ad*=\"placement\"]',\n    '[data-provider=\"dianomi\"]',\n    '[data-type=\"ad\"]',\n    '[data-track-event-label*=\"-taboola-\"]',\n    '[data-ad-feedback-beacon*=\"AD_\"]',\n    \"[data-ad-feedback-beacon]\",\n    '[data-dcm-click-tracker*=\"/adclick.\"]',\n    \"[data-google-av-adk]\",\n    \"[data-google-query-id]\",\n    '[data-ylk*=\"sponsored_cluster\"]',\n    \"[data-google-av-cxn]\",\n    \"[data-ad-client]\",\n    \"[data-ad-slot]\",\n    '[href*=\"doubleclick\"]',\n    '[href*=\"amazon-adsystem\"]',\n    '[alt=\"Advertisement\"]',\n    '[alt$=\" Ad\"]',\n    '[id*=\"-ad-\"]',\n    '[id*=\"_ads_\"]',\n    '[id*=\"AdFrame\"]',\n    '[id*=\"carbonads\"]',\n    '[id^=\"ad-\"]',\n    '[id^=\"my-ads\"]',\n    '[id^=\"outbrain_widget\"]',\n    '[id^=\"taboola-\"]',\n    '[id^=\"google_ads_frame\"]',\n    '[id^=\"google_ads_iframe\"]',\n    '[id=\"tryitLeaderboard\"]',\n    '[id=\"dianomiRightRail\"]',\n    '[allow*=\"advertising.com\"]',\n    \"ins.adsby\",\n    \"li.strm-ad-clusters\",\n    \"li.js-stream-ad\",\n    \"div.after_ad\",\n    \"div.ad-container\",\n    \"div.ad_module\",\n    \"div.ad-subnav-container\",\n    \"div.ad-wrapper\",\n    \"div.adroll-block\",\n    \"div.data-ad-container\",\n    \"div.GoogleActiveViewElement\",\n    \"div.l-ad\",\n    \"div.right-ad\",\n    \"div.wx-adWrapper\",\n    'div.image > a > img[src*=\"HomepageAd\"]',\n    'img[src*=\"HomepageAd\"]',\n    \"img.img_ad\",\n    'link[href*=\"/adservice.\"]',\n    \"section.dianomi-ad\",\n    \"ytd-promoted-video-renderer\",\n    \"ytd-video-masthead-ad-v3-renderer\",\n]\n"
  },
  {
    "path": "seleniumbase/config/proxy_list.py",
    "content": "\"\"\"\nProxy Server \"Phone Book\".\n\nSimplify running browser tests through a proxy server\nby adding your frequently-used proxies here.\n\nNow you can do something like this on the command line:\n\"pytest SOME_TEST.py --proxy=proxy1\"\n\nFormat of PROXY_LIST server entries:\n* \"ip_address:port\"  OR  \"username:password@ip_address:port\"\n* \"server:port\"  OR  \"username:password@server:port\"\n(Do NOT include the http:// or https:// in your proxy string!)\n\nExample proxies in PROXY_LIST below are not guaranteed to be active or secure.\nIf you don't already have a proxy server to connect to,\nyou can try finding one from one of following sites:\n* https://www.sslproxies.org/\n* https://bit.ly/36GtZa1\n* https://www.us-proxy.org/\n* https://hidemy.name/en/proxy-list/\n* http://free-proxy.cz/en/proxylist/country/all/https/ping/all\n\"\"\"\n\nPROXY_LIST = {\n    \"example1\": \"98.8.195.160:443\",  # (Example) - set your own proxy here\n    \"example2\": \"200.174.198.86:8888\",  # (Example)\n    \"example3\": \"socks5://184.178.172.5:15303\",  # (Example)\n    \"proxy1\": None,\n    \"proxy2\": None,\n    \"proxy3\": None,\n    \"proxy4\": None,\n    \"proxy5\": None,\n}\n"
  },
  {
    "path": "seleniumbase/config/settings.py",
    "content": "\"\"\"\nYou'll probably want to customize this to your own environment and needs.\n\nFor changes to take effect immediately, use Python's Develop Mode.\nDevelop Mode Install: \"pip install -e .\"  (from the top-level directory)\n\"\"\"\n\n\n# #####>>>>>----- REQUIRED/IMPORTANT SETTINGS -----<<<<<#####\n\n# Default maximum time (in seconds) to wait for page elements to appear.\n# Different methods/actions in base_case.py use different timeouts.\n# If the element to be acted on does not appear in time, the test fails.\nMINI_TIMEOUT = 2\nSMALL_TIMEOUT = 7\nLARGE_TIMEOUT = 10\nEXTREME_TIMEOUT = 30\n\n# Default page load timeout.\n# If a page takes longer than this to load, you'll get the following error:\n#     selenium.common.exceptions.TimeoutException:\n#     Message: timeout: Timed out receiving message from renderer: PLT\n# In global Selenium settings, this value is set to 300 seconds by default.\nPAGE_LOAD_TIMEOUT = 120\n\n# Default page load strategy.\n# [\"normal\", \"eager\", \"none\"]\n# Selenium default = \"normal\"\nPAGE_LOAD_STRATEGY = \"normal\"\n\n# If True, existing logs from past test runs will be saved and take up space.\n# If False, only the logs from the most recent test run will be saved locally.\n# You can also archive existing logs on the command line with: \"--archive_logs\"\nARCHIVE_EXISTING_LOGS = False\n\n# If True, existing downloads from past runs will be saved and take up space.\n# If False, only the downloads from the most recent run will be saved locally.\nARCHIVE_EXISTING_DOWNLOADS = False\n\n# If True, the last page screenshot will include the <body> and background.\n# If False, the last page screenshot will only include the <body> section.\n# Depending on the screen size, including a background could make the <body>\n# appear very small in the screenshot, which may require manually zooming in\n# on the <body> to see page details if you decide to include the background.\nSCREENSHOT_WITH_BACKGROUND = False\n\n# Default names for files saved during test failures.\n# (These files will get saved to the \"latest_logs/\" folder.)\nSCREENSHOT_NAME = \"screenshot.png\"\nBASIC_INFO_NAME = \"basic_test_info.txt\"\nPAGE_SOURCE_NAME = \"page_source.html\"\n\n# Default names for files and folders saved when using nosetests reports.\n# Usage: \"--report\". (NOSETESTS only)\nLATEST_REPORT_DIR = \"latest_report\"\nREPORT_ARCHIVE_DIR = \"archived_reports\"\nHTML_REPORT = \"report.html\"\nRESULTS_TABLE = \"results_table.csv\"\n\n\"\"\"\nIf True, switch to new tabs/windows automatically if a click opens a new one.\n(This switch only happens if the initial tab is still on same URL as before,\nwhich prevents a situation where a click opens up a new URL in the same tab,\nwhere a pop-up might open up a new tab on its own, leading to a double open.\nIf False, the browser will stay on the current tab where the click happened.\n\"\"\"\nSWITCH_TO_NEW_TABS_ON_CLICK = True\n\n\"\"\"\nThese methods add global waits, such as self.wait_for_ready_state_complete(),\nwhich waits for document.readyState to be \"complete\" after browser actions.\n\"\"\"\n# Called after self.open(URL), NOT driver.get(URL)\nWAIT_FOR_RSC_ON_PAGE_LOADS = True\n# Called after self.click(selector), NOT element.click()\nWAIT_FOR_RSC_ON_CLICKS = False\n# Wait for AngularJS calls to complete after various browser actions.\nWAIT_FOR_ANGULARJS = True\n# Skip all calls to wait_for_ready_state_complete() and wait_for_angularjs().\nSKIP_JS_WAITS = False\n\n# Default time to wait after each browser action performed during Demo Mode.\n# Use Demo Mode when you want others to see what your automation is doing.\n# Usage: \"--demo_mode\". (Can be overwritten by using \"--demo_sleep=TIME\".)\nDEFAULT_DEMO_MODE_TIMEOUT = 0.5\n\n# Number of times to repeat the demo_mode highlight animation.\n# Each loop is about 0.18 seconds. (Override by using \"--highlights=TIMES\".)\nHIGHLIGHTS = 4\n\n# Default time to keep messenger notifications visible (in seconds).\n# Messenger notifications appear when reaching assert statements in Demo Mode.\nDEFAULT_MESSAGE_DURATION = 2.55\n\n# If True, the Content Security Policy will be disabled on Firefox.\n# If False, each website's default Content Security Policy will be used.\n# (A website's CSP may prevent SeleniumBase from loading custom JavaScript.)\n# If using demo_mode or MasterQA, this value will become True regardless.\n# You can also disable the CSP on the command line by using \"--disable_csp\".\nDISABLE_CSP_ON_FIREFOX = True\n\n# If True, the Content Security Policy will be disabled on Chrome.\n# If False, each website's default Content Security Policy will be used.\n# (A website's CSP may prevent SeleniumBase from loading custom JavaScript.)\n# You can also disable the CSP on the command line by using \"--disable_csp\".\nDISABLE_CSP_ON_CHROME = False\n\n# If True, an Exception is raised immediately for invalid proxy string syntax.\n# If False, a Warning will appear after the test, with no proxy server used.\n# (This applies when using --proxy=[PROXY_STRING] for using a proxy server.)\nRAISE_INVALID_PROXY_STRING_EXCEPTION = True\n\n# Default browser coordinates when opening new windows for tests.\nWINDOW_START_X = 20\nWINDOW_START_Y = 54\n\n# Default browser resolutions when opening new windows for tests.\n# (Headless resolutions take priority, and include all browsers.)\n# (Firefox starts maximized by default when running in GUI Mode.)\nCHROME_START_WIDTH = 1280\nCHROME_START_HEIGHT = 840\nHEADLESS_START_WIDTH = 1440\nHEADLESS_START_HEIGHT = 1880\n\n# If True, hides messages related to downloading drivers.\n# If False, you'll see details about downloading drivers.\n# (This only affects driver downloads coming from tests.)\n# (If calling \"sbase get chromedriver\", then won't hide.)\nHIDE_DRIVER_DOWNLOADS = False\n\n# #####>>>>>----- MasterQA SETTINGS -----<<<<<#####\n# ##### (Used when importing MasterQA as the parent class)\n\n# The default message that appears when you don't specify a custom message\nMASTERQA_DEFAULT_VALIDATION_MESSAGE = \"Does the page look good?\"\n\n# The time delay (in seconds) before the MasterQA validation pop-up appears.\n# This value can be overwritten on the command line. Ex: --verify_delay=1.0\nMASTERQA_WAIT_TIME_BEFORE_VERIFY = 0.5\n\n# If True, the automation will start in full-screen mode\nMASTERQA_START_IN_FULL_SCREEN_MODE = False\n\n# The maximum idle time allowed (in seconds) before timing out and exiting\nMASTERQA_MAX_IDLE_TIME_BEFORE_QUIT = 600\n\n\n# #####>>>>>----- RECOMMENDED SETTINGS -----<<<<<#####\n# ##### (For multi-factor auth, DB/cloud logging, and password encryption)\n\n# Google Authenticator\n# (For 2-factor authentication using a time-based one-time password algorithm)\n# (See https://github.com/pyotp/pyotp and https://pypi.org/project/pyotp/ )\n# (Also works with Authy and other compatible apps.)\n# Usage: \"self.get_google_auth_password()\"  (output based on timestamp)\n# Usage with override: \"self.get_google_auth_password(totp_key=TOTP_KEY)\"\nTOTP_KEY = \"base32secretABCD\"\n\n\n# MySQL DB Credentials\n# (For saving data from tests to a MySQL DB)\n# Usage: \"--with-db_reporting\"\nDB_HOST = \"127.0.0.1\"\nDB_PORT = 3306\nDB_USERNAME = \"root\"\nDB_PASSWORD = \"test\"\nDB_SCHEMA = \"test_db\"\n\n\n# Amazon S3 Bucket Credentials\n# (For saving screenshots and other log files from tests)\n# (Bucket names are unique across all existing bucket names in Amazon S3)\n# Usage: \"--with-s3_logging\"\nS3_LOG_BUCKET = \"[S3 BUCKET NAME]\"\nS3_BUCKET_URL = \"https://s3.amazonaws.com/[S3 BUCKET NAME]/\"\nS3_SELENIUM_ACCESS_KEY = \"[S3 ACCESS KEY]\"\nS3_SELENIUM_SECRET_KEY = \"[S3 SECRET KEY]\"\n\n\n# ENCRYPTION SETTINGS\n# (Used for string/password obfuscation)\n# (You should reset the Encryption Key for every clone of SeleniumBase)\nENCRYPTION_KEY = \"Pg^.l!8UdJ+Y7dMIe&fl*%!p9@ej]/#tL~3E4%6?\"\n# These tokens are added to the beginning and end of obfuscated passwords.\n# Helps identify which strings/passwords have been obfuscated.\nOBFUSCATION_START_TOKEN = \"$^*ENCRYPT=\"\nOBFUSCATION_END_TOKEN = \"?&#$\"\n"
  },
  {
    "path": "seleniumbase/console_scripts/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<h2><a href=\"https://github.com/seleniumbase/SeleniumBase/\"><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\"></a> Console Scripts 🪄</h2>\n\n🌟 SeleniumBase console scripts can do many things, such as downloading web drivers, creating test directories with config files, activating the SeleniumBase Recorder, launching the SeleniumBase Commander, translating tests into other languages, running a Selenium Grid, and more.\n\n* Usage: `seleniumbase [COMMAND] [PARAMETERS]`\n\n* (simplified): `sbase [COMMAND] [PARAMETERS]`\n\n* To list all commands: `seleniumbase --help`\n\n(<i>For running tests, [use <b>pytest</b> with SeleniumBase](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md).</i>)\n\n```zsh\nCOMMANDS:\n      get / install    [DRIVER] [OPTIONS]\n      methods          (List common Python methods)\n      options          (List common pytest options)\n      behave-options   (List common behave options)\n      gui / commander  [OPTIONAL PATH or TEST FILE]\n      behave-gui       (SBase Commander for Behave)\n      caseplans        [OPTIONAL PATH or TEST FILE]\n      mkdir            [DIRECTORY] [OPTIONS]\n      mkfile           [FILE.py] [OPTIONS]\n      mkrec / codegen  [FILE.py] [OPTIONS]\n      recorder         (Open Recorder Desktop App.)\n      record           (If args: mkrec. Else: App.)\n      mkpres           [FILE.py] [LANG]\n      mkchart          [FILE.py] [LANG]\n      print            [FILE] [OPTIONS]\n      translate        [SB_FILE.py] [LANG] [ACTION]\n      convert          [WEBDRIVER_UNITTEST_FILE.py]\n      extract-objects  [SB_FILE.py]\n      inject-objects   [SB_FILE.py] [OPTIONS]\n      objectify        [SB_FILE.py] [OPTIONS]\n      revert-objects   [SB_FILE.py] [OPTIONS]\n      encrypt / obfuscate\n      decrypt / unobfuscate\n      proxy            (Start a basic proxy server)\n      download server  (Get Selenium Grid JAR file)\n      grid-hub         [start|stop] [OPTIONS]\n      grid-node        [start|stop] --hub=[HOST/IP]\n * (EXAMPLE: \"sbase get chromedriver\") *\n\n    Type \"sbase help [COMMAND]\" for specific command info.\n    For info on all commands, type: \"seleniumbase --help\".\n    Use \"pytest\" for running tests.\n```\n\n<h3><code>get</code> / <code>install</code></h3>\n\n* Usage:\n\n```zsh\nsbase get [DRIVER] [OPTIONS]\nsbase install [DRIVER] [OPTIONS]\n```\n\n* Examples:\n\n```zsh\nsbase get chromedriver\nsbase get geckodriver\nsbase get edgedriver\nsbase get chromedriver 114\nsbase get chromedriver 114.0.5735.90\nsbase get chromedriver stable\nsbase get chromedriver beta\nsbase get chromedriver -p\nsbase get chromium\nsbase get cft 131\nsbase get chs\n```\n\n* Drivers:\n\n```zsh\n`chromedriver` , `cft`, `uc_driver`\n`edgedriver` , `chs` , `geckodriver`\n```\n\n* Options:\n\n```zsh\nNUM: A specific driver version or major version integer.\n     If not set, the driver version matches the browser.\n\n`-p` / `--path`: Also copy the driver to /usr/local/bin.\n```\n\n* Output:\n\nDownloads the webdriver to `seleniumbase/drivers/`\n\n(`chromedriver` is required for Chrome automation)<br />\n(`geckodriver` is required for Firefox automation)<br />\n(`edgedriver` is required for Edge automation)\n\n<h3><code>methods</code></h3>\n\n* Usage:\n\n```zsh\nsbase methods\n```\n\n* Output:\n\nDisplays common SeleniumBase Python methods.\n\n<h3><code>options</code></h3>\n\n* Usage:\n\n```zsh\nsbase options\n```\n\n* Output:\n\nDisplays common pytest command-line options\nthat are available when using SeleniumBase.\n\n```zsh\n--browser=BROWSER  (The web browser to use. Default is \"chrome\")\n--edge / --firefox / --safari  (Shortcut for browser selection.)\n--headless  (Run tests headlessly. Default mode on Linux OS.)\n--demo  (Slow down and visually see test actions as they occur.)\n--slow  (Slow down the automation. Faster than using Demo Mode.)\n--rs / --reuse-session  (Reuse browser session between tests.)\n--crumbs  (Clear all cookies between tests reusing a session.)\n--maximize  (Start tests with the web browser window maximized.)\n--dashboard  (Enable SeleniumBase\\'s Dashboard at dashboard.html)\n--incognito  (Enable Chromium\\'s Incognito mode.)\n--guest  (Enable Chromium\\'s Guest Mode.)\n--dark  (Enable Chromium\\'s Dark Mode.)\n--uc  (Use undetected-chromedriver to evade detection.)\n-m=MARKER  (Run tests with the specified pytest marker.)\n-n=NUM  (Multithread the tests using that many threads.)\n-v  (Verbose mode. Print the full names of each test run.)\n--html=report.html  (Create a detailed pytest-html report.)\n--collect-only / --co  (Only show discovered tests. No run.)\n--co -q  (Only show full names of discovered tests. No run.)\n-x  (Stop running tests after the first failure is reached.)\n--pdb  (Enter the Post Mortem Debug Mode after any test fails.)\n--trace  (Enter Debug Mode immediately after starting any test.)\n      | Debug Mode Commands  >>>   help / h: List all commands. |\n      |   n: Next line of method. s: Step through. c: Continue. |\n      |  return / r: Run until method returns. j: Jump to line. |\n      | where / w: Show stack spot. u: Up stack. d: Down stack. |\n      | longlist / ll: See code. dir(): List namespace objects. |\n--help / -h  (Display list of all available pytest options.)\n--final-debug  (Enter Final Debug Mode after each test ends.)\n--recorder / --rec  (Save browser actions as Python scripts.)\n--rec-behave / --rec-gherkin  (Save actions as Gherkin code.)\n--rec-print  (Display recorded scripts when they are created.)\n--save-screenshot  (Save a screenshot at the end of each test.)\n--archive-logs  (Archive old log files instead of deleting them.)\n--check-js  (Check for JavaScript errors after page loads.)\n--start-page=URL  (The browser start page when tests begin.)\n--agent=STRING  (Modify the web browser\\'s User-Agent string.)\n--mobile  (Use Chromium\\'s mobile device emulator during tests.)\n--metrics=STRING  (Set mobile \"CSSWidth,CSSHeight,PixelRatio\".)\n--ad-block  (Block some types of display ads after page loads.)\n--settings-file=FILE  (Override default SeleniumBase settings.)\n--env=ENV  (Set the test env. Access with \"self.env\" in tests.)\n--data=DATA  (Extra test data. Access with \"self.data\" in tests.)\n--disable-csp  (Disable the Content Security Policy of websites.)\n--remote-debug  (Sync to Ch-R-Debugger chrome://inspect/#devices)\n--server=SERVER  (The Selenium Grid server/IP used for tests.)\n--port=PORT  (The Selenium Grid port used by the test server.)\n--proxy=SERVER:PORT  (Connect to a proxy server:port for tests.)\n--proxy=USER:PASS@SERVER:PORT  (Use authenticated proxy server.)\n\nFor the full list of command-line options, type: \"pytest --help\".\n```\n\n<h3><code>behave-options</code></h3>\n\n* Usage:\n\n```zsh\nsbase behave-options\n```\n\n* Output:\n\nDisplays common Behave command-line options\nthat are available when using SeleniumBase.\n\n```zsh\n-D browser=BROWSER  (The web browser to use. Default is \"chrome\")\n-D headless  (Run tests headlessly. Default mode on Linux OS.)\n-D demo  (Slow down and visually see test actions as they occur.)\n-D slow  (Slow down the automation. Faster than using Demo Mode.)\n-D reuse-session / -D rs  (Reuse browser session between tests.)\n-D crumbs  (Clear all cookies between tests reusing a session.)\n-D maximize  (Start tests with the web browser window maximized.)\n-D dashboard  (Enable SeleniumBase\\'s Dashboard at dashboard.html)\n-D incognito  (Enable Chromium\\'s Incognito Mode.)\n-D guest  (Enable Chromium\\'s Guest Mode.)\n-D dark  (Enable Chromium\\'s Dark Mode.)\n-D uc  (Use undetected-chromedriver to evade detection.)\n--no-snippets / -q  (Quiet mode. Don\\'t print snippets.)\n--dry-run / -d  (Dry run. Only show discovered tests.)\n--stop  (Stop running tests after the first failure is reached.)\n-D pdb  (Enter the Post Mortem Debug Mode after any test fails.)\n      | Debug Mode Commands  >>>   help / h: List all commands. |\n      |   n: Next line of method. s: Step through. c: Continue. |\n      |  return / r: Run until method returns. j: Jump to line. |\n      | where / w: Show stack spot. u: Up stack. d: Down stack. |\n      | longlist / ll: See code. dir(): List namespace objects. |\n-D recorder  (Record browser actions to generate test scripts.)\n-D rec-print  (Display recorded scripts when they are created.)\n-D save-screenshot  (Save a screenshot at the end of each test.)\n-D archive-logs  (Archive old log files instead of deleting them.)\n-D check-js  (Check for JavaScript errors after page loads.)\n-D start-page=URL  (The browser start page when tests begin.)\n-D agent=STRING  (Modify the web browser\\'s User-Agent string.)\n-D mobile  (Use Chromium\\'s mobile device emulator during tests.)\n-D metrics=STRING  (Set mobile \"CSSWidth,CSSHeight,PixelRatio\".)\n-D ad-block  (Block some types of display ads after page loads.)\n-D settings-file=FILE  (Override default SeleniumBase settings.)\n-D env=ENV  (Set the test env. Access with \"self.env\" in tests.)\n-D data=DATA  (Extra test data. Access with \"self.data\" in tests.)\n-D disable-csp  (Disable the Content Security Policy of websites.)\n-D remote-debug  (Sync to Ch-R-Debugger chrome://inspect/#devices)\n-D server=SERVER  (The Selenium Grid server/IP used for tests.)\n-D port=PORT  (The Selenium Grid port used by the test server.)\n-D proxy=SERVER:PORT  (Connect to a proxy server:port for tests.)\n-D proxy=USER:PASS@SERVER:PORT  (Use authenticated proxy server.)\n\nFor the full list of command-line options, type: \"behave --help\".\n```\n\n<h3><code>gui</code> / <code>commander</code></h3>\n\n* Usage:\n\n```zsh\nsbase gui [OPTIONAL PATH or TEST FILE]\nsbase commander [OPTIONAL PATH or TEST FILE]\n```\n\n<h3><code>behave-gui</code></h3>\n\n* Usage:\n\n```zsh\nsbase behave-gui [OPTIONAL PATH or TEST FILE]\nsbase gui-behave [OPTIONAL PATH or TEST FILE]\n```\n\n* Examples:\n\n```zsh\nsbase behave-gui\nsbase behave-gui -i=calculator\nsbase behave-gui features/\nsbase behave-gui features/calculator.feature\n```\n\n* Output:\n\nLaunches SeleniumBase Commander / GUI for Behave.\n\n<h3><code>caseplans</code></h3>\n\n* Usage:\n\n```zsh\nsbase caseplans [OPTIONAL PATH or TEST FILE]\n```\n\n* Examples:\n\n```zsh\nsbase caseplans\nsbase caseplans -k agent\nsbase caseplans -m marker2\nsbase caseplans test_suite.py\nsbase caseplans offline_examples/\n```\n\n* Output:\n\nLaunches the SeleniumBase Case Plans Generator.\n\n<h3><code>mkdir</code></h3>\n\n* Usage:\n\n```zsh\nsbase mkdir [DIRECTORY] [OPTIONS]\n```\n\n* Example:\n\n```zsh\nsbase mkdir ui_tests\n```\n\n* Options:\n\n```zsh\n-b / --basic  (Only config files. No tests added.)\n--gha  (Include GitHub Actions YML with defaults.)\n```\n\n* Output:\n\nCreates a new folder for running SBase scripts.\nThe new folder contains default config files,\nsample tests for helping new users get started,\nand Python boilerplates for setting up customized\ntest frameworks.\n\n```zsh\nui_tests/\n├── __init__.py\n├── my_first_test.py\n├── parameterized_test.py\n├── pytest.ini\n├── requirements.txt\n├── setup.cfg\n├── test_demo_site.py\n└── boilerplates/\n    ├── __init__.py\n    ├── base_test_case.py\n    ├── boilerplate_test.py\n    ├── classic_obj_test.py\n    ├── page_objects.py\n    ├── sb_fixture_test.py\n    └── samples/\n        ├── __init__.py\n        ├── google_objects.py\n        ├── google_test.py\n        ├── sb_swag_test.py\n        └── swag_labs_test.py\n```\n\nIf running with the `-b` or `--basic` option:\n\n```zsh\nui_tests/\n├── __init__.py\n├── pytest.ini\n├── requirements.txt\n└── setup.cfg\n```\n\nIf running with the `--gha` option, this is added:\n\n```zsh\nui_tests/\n└── .github                    \n    └── workflows/             \n        └── python-package.yml\n```\n\n<h3><code>mkfile</code></h3>\n\n* Usage:\n\n```zsh\nsbase mkfile [FILE.py] [OPTIONS]\n```\n\n* Example:\n\n```zsh\nsbase mkfile new_test.py\n```\n\n* Options:\n\n```zsh\n--uc  (UC Mode boilerplate using SB context manager)\n-b / --basic  (Basic boilerplate / single-line test)\n-r / --rec  (Adds Pdb+ breakpoint for Recorder Mode)\n--url=URL  (Makes the test start on a specific page)\n```\n\n* Language Options:\n\n```zsh\n--en / --English    |    --zh / --Chinese\n--nl / --Dutch      |    --fr / --French\n--it / --Italian    |    --ja / --Japanese\n--ko / --Korean     |    --pt / --Portuguese\n--ru / --Russian    |    --es / --Spanish\n```\n\n* Syntax Formats:\n\n```zsh\n--bc / --basecase      (BaseCase class inheritance)\n--pf / --pytest-fixture         (sb pytest fixture)\n--cf / --class-fixture  (class + sb pytest fixture)\n--cm / --context-manager       (SB context manager)\n--dc / --driver-context     (DriverContext manager)\n--dm / --driver-manager            (Driver manager)\n```\n\n* Output:\n\nCreates a new SBase test file with boilerplate code.\nIf the file already exists, an error is raised.\nBy default, uses English with BaseCase inheritance,\nand creates a boilerplate with common SeleniumBase\nmethods: \"open\", \"type\", \"click\", \"assert_element\",\nand \"assert_text\". If using the basic boilerplate\noption, only the \"open\" method is included. Only the\nBaseCase format supports Languages or Recorder Mode.\nUC Mode automatically uses English with SB() format.\n\n<h3><code>mkrec</code> / <code>record</code> / <code>codegen</code></h3>\n\n* Usage:\n\n```zsh\nsbase mkrec [FILE.py] [OPTIONS]\nsbase codegen [FILE.py] [OPTIONS]\n```\n\n* Examples:\n\n```zsh\nsbase mkrec new_test.py\nsbase mkrec new_test.py --url=seleniumbase.io\nsbase codegen new_test.py\nsbase codegen new_test.py --url=wikipedia.org\n```\n\n* Options:\n\n```zsh\n--url=URL  (Sets the initial start page URL.)\n--edge  (Use Edge browser instead of Chrome.)\n--gui / --headed  (Use headed mode on Linux.)\n--uc / --undetected  (Use undetectable mode.)\n--ee  (Use SHIFT + ESC to end the recording.)\n--overwrite  (Overwrite file when it exists.)\n--behave  (Also output Behave/Gherkin files.)\n```\n\n* Output:\n\nCreates a new SeleniumBase test using the Recorder.\nIf the filename already exists, an error is raised.\n\n<h3><code>recorder</code></h3>\n\n* Usage:\n\n```zsh\nsbase recorder [OPTIONS]\n```\n\n* Options:\n\n```zsh\n--uc / --undetected  (Use undetectable mode.)\n--behave  (Also output Behave/Gherkin files.)\n```\n\n* Output:\n\nLaunches the SeleniumBase Recorder Desktop App.\n\n<h3><code>mkpres</code></h3>\n\n* Usage:\n\n```zsh\nsbase mkpres [FILE.py] [LANG]\n```\n\n* Example:\n\n```zsh\nsbase mkpres new_presentation.py --en\n```\n\n* Language Options:\n\n```zsh\n--en / --English    |    --zh / --Chinese\n--nl / --Dutch      |    --fr / --French\n--it / --Italian    |    --ja / --Japanese\n--ko / --Korean     |    --pt / --Portuguese\n--ru / --Russian    |    --es / --Spanish\n```\n\n* Output:\n\nCreates a new presentation with 3 example slides.\nIf the file already exists, an error is raised.\nBy default, the slides are written in English,\nand use \"serif\" theme with \"slide\" transition.\nThe slides can be used as a basic boilerplate.\n\n<h3><code>mkchart</code></h3>\n\n* Usage:\n\n```zsh\nsbase mkchart [FILE.py] [LANG]\n```\n\n* Example:\n\n```zsh\nsbase mkchart new_chart.py --en\n```\n\n* Language Options:\n\n```zsh\n--en / --English    |    --zh / --Chinese\n--nl / --Dutch      |    --fr / --French\n--it / --Italian    |    --ja / --Japanese\n--ko / --Korean     |    --pt / --Portuguese\n--ru / --Russian    |    --es / --Spanish\n```\n\n* Output:\n\nCreates a new SeleniumBase chart presentation.\nIf the file already exists, an error is raised.\nBy default, the slides are written in English,\nand use a \"sky\" theme with \"slide\" transition.\nThe chart can be used as a basic boilerplate.\n\n<h3><code>print</code></h3>\n\n* Usage:\n\n```zsh\nsbase print [FILE] [OPTIONS]\n```\n\n* Options:\n\n```zsh\n-n (Add line Numbers to the rows)\n```\n\n* Output:\n\nPrints the code/text of any file\nwith syntax-highlighting.\n\n<h3><code>translate</code></h3>\n\n* Usage:\n\n```zsh\nsbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\n```\n\n* Languages:\n\n```zsh\n--en / --English    |    --zh / --Chinese\n--nl / --Dutch      |    --fr / --French\n--it / --Italian    |    --ja / --Japanese\n--ko / --Korean     |    --pt / --Portuguese\n--ru / --Russian    |    --es / --Spanish\n```\n\n* Actions:\n\n```zsh\n-p / --print  (Print translation output to the screen)\n-o / --overwrite  (Overwrite the file being translated)\n-c / --copy  (Copy the translation to a new `.py` file)\n```\n\n* Options:\n\n```zsh\n-n  (include line Numbers when using the Print action)\n```\n\n* Output:\n\nTranslates a SeleniumBase Python file into the language\nspecified. Method calls and \"import\" lines get swapped.\nBoth a language and an action must be specified.\nThe `-p` action can be paired with one other action.\nWhen running with `-c` (or `--copy`), the new file name\nwill be the original name appended with an underscore\nplus the 2-letter language code of the new language.\n(Example: Translating \"test_1.py\" into Japanese with\n`-c` will create a new file called \"test_1_ja.py\".)\n\n<h3><code>extract-objects</code></h3>\n\n* Usage:\n\n```zsh\nsbase extract-objects [SB_FILE.py]\n```\n\n* Output:\n\nCreates page objects based on selectors found in a\nseleniumbase Python file and saves those objects to the\n\"page_objects.py\" file in the same folder as the tests.\n\n<h3><code>inject-objects</code></h3>\n\n* Usage:\n\n```zsh\nsbase inject-objects [SB_FILE.py] [OPTIONS]\n```\n\n* Options:\n\n```zsh\n-c / --comments  (Add object selectors to the comments.)\n```\n\n* Output:\n\nTakes the page objects found in the \"page_objects.py\"\nfile and uses those to replace matching selectors in\nthe selected seleniumbase Python file.\n\n<h3><code>objectify</code></h3>\n\n* Usage:\n\n```zsh\nsbase objectify [SB_FILE.py] [OPTIONS]\n```\n\n* Options:\n\n```zsh\n-c / --comments  (Add object selectors to the comments.)\n```\n\n* Output:\n\nA modified version of the file where the selectors\nhave been replaced with variable names defined in\n\"page_objects.py\", supporting the Page Object Pattern.\n(This has the same outcome as combining\n`extract-objects` with `inject-objects`)\n\n<h3><code>revert-objects</code></h3>\n\n* Usage:\n\n```zsh\nsbase revert-objects [SB_FILE.py] [OPTIONS]\n```\n\n* Options:\n\n```zsh\n-c / --comments  (Keep existing comments for the lines.)\n```\n\n* Output:\n\nReverts the changes made by `seleniumbase objectify ...` or\n`seleniumbase inject-objects ...` when run against a\nseleniumbase Python file. Objects will get replaced by\nselectors stored in the \"page_objects.py\" file.\n\n<h3><code>convert</code></h3>\n\n* Usage:\n\n```zsh\nsbase convert [WEBDRIVER_UNITTEST_FILE.py]\n```\n\n* Output:\n\nConverts a Selenium IDE exported WebDriver unittest\nfile into a SeleniumBase file. Adds `_SB` to the\nnew filename while keeping the original file intact.\nWorks on both Selenium IDE & Katalon Recorder scripts.\n\n<h3><code>encrypt</code> / <code>obfuscate</code></h3>\n\n* Usage:\n\n`sbase encrypt`  /  `sbase obfuscate`\n\n* Output:\n\nRuns the password encryption/obfuscation tool.\n(Where you can enter a password to encrypt/obfuscate.)\n\n<h3><code>decrypt</code> / <code>unobfuscate</code></h3>\n\n* Usage:\n\n`sbase decrypt`  /  `sbase unobfuscate`\n\n* Output:\n\nRuns the password decryption/unobfuscation tool.\n(Where you can enter an encrypted password to decrypt.)\n\n<h3><code>proxy</code></h3>\n\n* Usage:\n\n```zsh\nsbase proxy [OPTIONS]\n```\n\n* Options:\n\n```zsh\n--hostname=HOSTNAME  (Set `hostname`) (Default: `127.0.0.1`)\n--port=PORT          (Set `port`)     (Default: `8899`)\n--help / -h      (Display available `proxy` options.)\n```\n\n* Output:\n\nLaunch a basic proxy server on the current machine.\n(Uses `127.0.0.1:8899` as the default address.)\n\n<h3><code>download</code></h3>\n\n* Usage:\n\n```zsh\nsbase download server\n```\n\n* Output:\n\nDownloads the Selenium Server JAR file for Grid usage.\n(That JAR file is required when using a Selenium Grid)\n\n<h3><code>grid-hub</code></h3>\n\n* Usage:\n\n```zsh\nsbase grid-hub [start|stop|restart] [OPTIONS]\n```\n\n* Options:\n\n```zsh\n-v / --verbose  (Increases verbosity of logging output.)\n--timeout=TIMEOUT  (Close idle browser windows after TIMEOUT seconds.)\n```\n\n* Output:\n\nControls the Selenium Grid Hub server, which allows\nfor running tests on multiple machines in parallel\nto speed up test runs and reduce the total time\nof test suite execution.\nYou can start, restart, or stop the Grid Hub server.\n\n<h3><code>grid-node</code></h3>\n\n* Usage:\n\n```zsh\nsbase grid-node [start|stop|restart] [OPTIONS]\n```\n\n* Options:\n\n```zsh\n--hub=HUB_IP (Grid Hub IP Address. Default: `127.0.0.1`)\n-v / --verbose  (Increases verbosity of logging output.)\n```\n\n* Output:\n\nControls the Selenium Grid node, which serves as a\nworker machine for your Selenium Grid Hub server.\nYou can start, restart, or stop the Grid node.\n\n--------\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/README.md\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb.png\" title=\"SeleniumBase\" width=\"290\"></a>\n"
  },
  {
    "path": "seleniumbase/console_scripts/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/console_scripts/logo_helper.py",
    "content": "\"\"\" SeleniumBase Logo Processing  (for the console scripts interface)\n    Logo generated from:\n    http://www.patorjk.com/software/taag/#p=display&f=Slant&t=SeleniumBase \"\"\"\n\nimport colorama\nimport os\nfrom contextlib import suppress\n\nr\"\"\"\n   ______     __           _                  ____\n  / ____/__  / /__  ____  (_)_  ______ ___   / _  \\____  ________\n  \\__ \\/ _ \\/ / _ \\/ __ \\/ / / / / __ `__ \\ / /_) / __ \\/ ___/ _ \\\n ___/ /  __/ /  __/ / / / / /_/ / / / / / // /_) / (_/ /__  /  __/\n/____/\\___/_/\\___/_/ /_/_/\\__,_/_/ /_/ /_//_____/\\__,_/____/\\___/\n\"\"\"\n\n\ndef get_seleniumbase_logo():\n    c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Back.CYAN\n    c4 = colorama.Back.GREEN\n    cr = colorama.Style.RESET_ALL\n    sb = \" \"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \"   ______     __           _                  \"\n    sb += c2\n    sb += \"____                \"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \"  / ____/__  / /__  ____  (_)_  ______ ___   \"\n    sb += c2\n    sb += \"/ _  \\\\____  ________ \"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \"  \\\\__ \\\\/ _ \\\\/ / _ \\\\/ __ \\\\/ / / / / __ `__ \\\\ \"\n    sb += c2\n    sb += \"/ /_) / __ \\\\/ ___/ _ \\\\\"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \" ___/ /  __/ /  __/ / / / / /_/ / / / / / /\"\n    sb += c2\n    sb += \"/ /_) / (_/ /__  /  __/\"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \"/____/\\\\___/_/\\\\___/_/ /_/_/\\\\__,_/_/ /_/ /_/\"\n    sb += c2\n    sb += \"/_____/\\\\__,_/____/\\\\___/ \"\n    sb += cr\n    sb += \"\\n\"\n    sb += c3\n    sb += \"                                          \"\n    sb += c4\n    sb += \"                        \"\n    sb += cr\n    sb += cr\n    with suppress(Exception):\n        terminal_width = os.get_terminal_size().columns\n        if isinstance(terminal_width, int) and terminal_width >= 66:\n            return sb\n\n    # If the logo is wider than the screen width, use a smaller one:\n    r\"\"\"\n     ___      _          _             ___\n    / __| ___| |___ _ _ (_)_  _ _ __  | _ ) __ _ ______\n    \\__ \\/ -_) / -_) ' \\| | \\| | '  \\ | _ \\/ _` (_-< -_)\n    |___/\\___|_\\___|_||_|_|\\_,_|_|_|_\\|___/\\__,_/__|___|\n    \"\"\"\n    sb = \" \"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \" ___      _          _            \"\n    sb += c2\n    sb += \" ___              \"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \"/ __| ___| |___ _ _ (_)_  _ _ __  \"\n    sb += c2\n    sb += \"| _ ) __ _ ______ \"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \"\\\\__ \\\\/ -_) / -_) ' \\\\| | \\\\| | '  \\\\ \"\n    sb += c2\n    sb += \"| _ \\\\/ _` (_-< -_)\"\n    sb += cr\n    sb += \"\\n\"\n    sb += c1\n    sb += \"|___/\\\\___|_\\\\___|_||_|_|\\\\_,_|_|_|_\\\\\"\n    sb += c2\n    sb += \"|___/\\\\__,_/__|___|\"\n    sb += cr\n    sb += \"\\n\"\n    sb += c3\n    sb += \"                                  \"\n    sb += c4\n    sb += \"                  \"\n    sb += cr\n    sb += cr\n    return sb\n"
  },
  {
    "path": "seleniumbase/console_scripts/rich_helper.py",
    "content": "from rich.console import Console\nfrom rich.markdown import Markdown\nfrom rich.syntax import Syntax\n\n\ndef process_syntax(code, lang, theme, line_numbers, code_width, word_wrap):\n    syntax = Syntax(\n        code,\n        lang,\n        theme=theme,\n        line_numbers=line_numbers,\n        code_width=code_width,\n        word_wrap=word_wrap,\n    )\n    return syntax\n\n\ndef get_code_without_tag(code, tag):\n    # Remove TAG from Code/HTML, but keep inner text.\n    # Eg: <a href=\"LINK\">Hello!</a> becomes: \"Hello!\"\n    tag_start = \"<%s \" % tag\n    tag_solo = \"<%s>\" % tag\n    tag_end = \"</%s>\" % tag\n    while tag_start in code:\n        start = code.find(tag_start)\n        if start == -1:\n            break\n        end = code.find(\">\", start + 1) + 1\n        if end == 0:\n            break\n        code = code[:start] + code[end:]\n    code = code.replace(tag_solo, \"\")\n    code = code.replace(tag_end, \"\")\n    return code\n\n\ndef display_markdown(code):\n    try:\n        markdown = Markdown(code)\n        console = Console()\n        console.print(markdown)\n        return True  # Success\n    except Exception:\n        return False  # Failure\n\n\ndef display_code(code):\n    try:\n        console = Console()\n        console.print(code)\n        return True  # Success\n    except Exception:\n        return False  # Failure\n\n\ndef fix_emoji_spacing(code):\n    try:\n        # Fix the display width of certain emojis that take up two spaces\n        double_width_emojis = [\n            \"👁️\",\n            \"🗺️\",\n            \"🖼️\",\n            \"🗄️\",\n            \"♻️\",\n            \"🗂️\",\n            \"🖥️\",\n            \"🕹️\",\n            \"🎞️\",\n            \"🎛️\",\n            \"🎖️\",\n            \"☀️\",\n            \"⏺️\",\n            \"▶️\",\n            \"↘️\",\n            \"⬇️\",\n            \"↙️\",\n            \"⬅️\",\n            \"↖️\",\n            \"⬆️\",\n            \"↗️\",\n            \"➡️\",\n        ]\n        for emoji in double_width_emojis:\n            if emoji in code:\n                code = code.replace(emoji, emoji + \" \")\n        code = code.replace(\"✅<\", \"✅ <\")\n        code = code.replace(\"❌<\", \"❌ <\")\n    except Exception:\n        pass\n    return code\n"
  },
  {
    "path": "seleniumbase/console_scripts/run.py",
    "content": "\"\"\"\nSeleniumBase console scripts runner\n\nUsage:\nseleniumbase [COMMAND] [PARAMETERS]\n  OR   sbase [COMMAND] [PARAMETERS]\n\nExamples:\nsbase get chromedriver\nsbase methods\nsbase options\nsbase commander\nsbase behave-gui\nsbase behave-options\nsbase caseplans\nsbase mkdir ui_tests\nsbase mkfile new_test.py\nsbase mkrec new_test.py\nsbase mkrec new_test.py --url=wikipedia.org\nsbase codegen new_test.py --url=wikipedia.org\nsbase recorder\nsbase record new_test.py\nsbase record\nsbase mkpres new_presentation.py\nsbase mkchart new_chart.py\nsbase convert webdriver_unittest_file.py\nsbase print my_first_test.py -n\nsbase translate my_first_test.py --zh -p\nsbase extract-objects my_first_test.py\nsbase inject-objects my_first_test.py\nsbase objectify my_first_test.py\nsbase revert-objects my_first_test.py\nsbase encrypt\nsbase decrypt\nsbase proxy\nsbase proxy --hostname=127.0.0.1 --port=8899\nsbase download server\nsbase grid-hub start\nsbase grid-node start --hub=127.0.0.1\n\"\"\"\nimport colorama\nimport sys\nimport time\nfrom contextlib import suppress\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import shared_utils\n\n\ndef show_basic_usage():\n    from seleniumbase.console_scripts import logo_helper\n\n    seleniumbase_logo = logo_helper.get_seleniumbase_logo()\n    print(seleniumbase_logo)\n    time.sleep(0.035)\n    print(\"\")\n    time.sleep(0.031)\n    show_package_location()\n    time.sleep(0.031)\n    show_version_info()\n    time.sleep(0.031)\n    print(\"\")\n    time.sleep(0.555)  # Enough time to see the logo & version\n    sc = \"\"\n    sc += \"╭──────────────────────────────────────────────────╮\\n\"\n    sc += '│  * USAGE: \"seleniumbase [COMMAND] [PARAMETERS]\"  │\\n'\n    sc += '│  *    OR:        \"sbase [COMMAND] [PARAMETERS]\"  │\\n'\n    sc += \"│                                                  │\\n\"\n    sc += \"│ COMMANDS:        PARAMETERS / DESCRIPTIONS:      │\\n\"\n    sc += \"│    get / install    [DRIVER_NAME] [OPTIONS]      │\\n\"\n    sc += \"│    methods          (List common Python methods) │\\n\"\n    sc += \"│    options          (List common pytest options) │\\n\"\n    sc += \"│    behave-options   (List common behave options) │\\n\"\n    sc += \"│    gui / commander  [OPTIONAL PATH or TEST FILE] │\\n\"\n    sc += \"│    behave-gui       (SBase Commander for Behave) │\\n\"\n    sc += \"│    caseplans        [OPTIONAL PATH or TEST FILE] │\\n\"\n    sc += \"│    mkdir            [DIRECTORY] [OPTIONS]        │\\n\"\n    sc += \"│    mkfile           [FILE.py] [OPTIONS]          │\\n\"\n    sc += \"│    mkrec / codegen  [FILE.py] [OPTIONS]          │\\n\"\n    sc += \"│    recorder         (Open Recorder Desktop App.) │\\n\"\n    sc += \"│    record           (If args: mkrec. Else: App.) │\\n\"\n    sc += \"│    mkpres           [FILE.py] [LANG]             │\\n\"\n    sc += \"│    mkchart          [FILE.py] [LANG]             │\\n\"\n    sc += \"│    print            [FILE] [OPTIONS]             │\\n\"\n    sc += \"│    translate        [SB_FILE.py] [LANG] [ACTION] │\\n\"\n    sc += \"│    convert          [WEBDRIVER_UNITTEST_FILE.py] │\\n\"\n    sc += \"│    extract-objects  [SB_FILE.py]                 │\\n\"\n    sc += \"│    inject-objects   [SB_FILE.py] [OPTIONS]       │\\n\"\n    sc += \"│    objectify        [SB_FILE.py] [OPTIONS]       │\\n\"\n    sc += \"│    revert-objects   [SB_FILE.py] [OPTIONS]       │\\n\"\n    sc += \"│    encrypt / obfuscate                           │\\n\"\n    sc += \"│    decrypt / unobfuscate                         │\\n\"\n    sc += \"│    proxy            (Start a basic proxy server) │\\n\"\n    sc += \"│    download server  (Get Selenium Grid JAR file) │\\n\"\n    sc += \"│    grid-hub         [start|stop] [OPTIONS]       │\\n\"\n    sc += \"│    grid-node        [start|stop] --hub=[HOST/IP] │\\n\"\n    sc += \"│                                                  │\\n\"\n    sc += '│ *  EXAMPLE => \"sbase get chromedriver stable\"    │\\n'\n    sc += '│ *  For command info => \"sbase help [COMMAND]\"    │\\n'\n    sc += '│ *  For info on all commands => \"sbase --help\"    │\\n'\n    sc += \"╰──────────────────────────────────────────────────╯\"\n    sc += \"\"\n    bordered_sc = sc\n    if \"linux\" not in sys.platform:\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n        c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n        c4 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX\n        cr = colorama.Style.RESET_ALL\n        sc = sc.replace(\"seleniumbase\", c1 + \"selenium\" + c2 + \"base\" + cr)\n        sc = sc.replace(\"sbase\", c1 + \"s\" + c2 + \"base\" + cr)\n        sc = sc.replace(\"[COMMAND]\", c3 + \"[COMMAND]\" + cr)\n        sc = sc.replace(\"--help\", c4 + \"--help\" + cr)\n        sc = sc.replace(\"help\", c4 + \"help\" + cr)\n    with suppress(Exception):\n        print(sc)\n        return\n    sc = bordered_sc.replace(\"╮\\n\", \"\")\n    sc = sc.replace(\"╭\", \"\").replace(\"╮\", \"\").replace(\"│\", \"\")\n    sc = sc.replace(\"╰\", \"\").replace(\"╯\", \"\").replace(\"─\", \"\")\n    if \"linux\" not in sys.platform:\n        sc = sc.replace(\"seleniumbase\", c1 + \"selenium\" + c2 + \"base\" + cr)\n        sc = sc.replace(\"sbase\", c1 + \"s\" + c2 + \"base\" + cr)\n        sc = sc.replace(\"[COMMAND]\", c3 + \"[COMMAND]\" + cr)\n        sc = sc.replace(\"--help\", c4 + \"--help\" + cr)\n        sc = sc.replace(\"help\", c4 + \"help\" + cr)\n    print(sc)\n\n\ndef show_install_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"get / install\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase install [DRIVER_NAME] [OPTIONS]\")\n    print(\"     OR: seleniumbase get [DRIVER_NAME] [OPTIONS]\")\n    print(\"     OR:    sbase install [DRIVER_NAME] [OPTIONS]\")\n    print(\"     OR:        sbase get [DRIVER_NAME] [OPTIONS]\")\n    print(\"         (Drivers: chromedriver, cft, uc_driver,\")\n    print(\"                   edgedriver, chs, geckodriver)\")\n    print(\"  Options:\")\n    print(\"     VERSION    Specify the version to download.\")\n    print(\"                Tries to detect the needed version.\")\n    print(\"                If using chromedriver or edgedriver,\")\n    print(\"                you can use the major version integer.\")\n    print()\n    print(\"     -p / --path    Also copy driver to /usr/local/bin\")\n    print(\"  Examples:\")\n    print(\"     sbase get chromedriver\")\n    print(\"     sbase get geckodriver\")\n    print(\"     sbase get edgedriver\")\n    print(\"     sbase get chromedriver 114\")\n    print(\"     sbase get chromedriver 114.0.5735.90\")\n    print(\"     sbase get chromedriver stable\")\n    print(\"     sbase get chromedriver beta\")\n    print(\"     sbase get chromedriver -p\")\n    print(\"     sbase get cft 131\")\n    print(\"     sbase get chs\")\n    print(\"  Output:\")\n    print(\"     Downloads the webdriver to seleniumbase/drivers/\")\n    print(\"     (chromedriver is required for Chrome automation)\")\n    print(\"     (geckodriver is required for Firefox automation)\")\n    print(\"     (edgedriver is required for MS__Edge automation)\")\n    print(\"     (cft is for the `Chrome for Testing` binary exe)\")\n    print(\"     (chs is for the `Chrome-Headless-Shell` binary.)\")\n    print(\"\")\n\n\ndef show_commander_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"commander\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase commander [OPTIONAL PATH or TEST FILE]\")\n    print(\"     OR:    sbase commander [OPTIONAL PATH or TEST FILE]\")\n    print(\"     OR:   seleniumbase gui [OPTIONAL PATH or TEST FILE]\")\n    print(\"     OR:          sbase gui [OPTIONAL PATH or TEST FILE]\")\n    print(\"  Examples:\")\n    print(\"     sbase gui\")\n    print(\"     sbase gui -k agent\")\n    print(\"     sbase gui -m marker2\")\n    print(\"     sbase gui test_suite.py\")\n    print(\"     sbase gui offline_examples/\")\n    print(\"  Output:\")\n    print(\"     Launches SeleniumBase Commander | GUI for pytest.\")\n    print(\"\")\n\n\ndef show_behave_gui_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"behave-gui\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase behave-gui [OPTIONAL PATH or TEST FILE]\")\n    print(\"     seleniumbase gui-behave [OPTIONAL PATH or TEST FILE]\")\n    print(\"     OR:    sbase behave-gui [OPTIONAL PATH or TEST FILE]\")\n    print(\"     OR:    sbase gui-behave [OPTIONAL PATH or TEST FILE]\")\n    print(\"  Examples:\")\n    print(\"     sbase behave-gui\")\n    print(\"     sbase behave-gui features/\")\n    print(\"     sbase behave-gui features/calculator.feature\")\n    print(\"  Output:\")\n    print(\"     Launches SeleniumBase Commander | GUI for Behave.\")\n    print(\"\")\n\n\ndef show_caseplans_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"caseplans\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase caseplans [OPTIONAL PATH or TEST FILE]\")\n    print(\"     OR:    sbase caseplans [OPTIONAL PATH or TEST FILE]\")\n    print(\"  Examples:\")\n    print(\"     sbase caseplans\")\n    print(\"     sbase caseplans -k agent\")\n    print(\"     sbase caseplans -m marker2\")\n    print(\"     sbase caseplans test_suite.py\")\n    print(\"     sbase caseplans offline_examples/\")\n    print(\"  Output:\")\n    print(\"     Launches the SeleniumBase Case Plans Generator.\")\n    print(\"\")\n\n\ndef show_mkdir_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"mkdir\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase mkdir [DIRECTORY] [OPTIONS]\")\n    print(\"     OR:    sbase mkdir [DIRECTORY] [OPTIONS]\")\n    print(\"  Example:\")\n    print(\"     sbase mkdir ui_tests\")\n    print(\"  Options:\")\n    print(\"     -b / --basic  (Only config files. No tests added.)\")\n    print(\"     --gha  (Include GitHub Actions YML with defaults.)\")\n    print(\"  Output:\")\n    print(\"     Creates a new folder for running SBase scripts.\")\n    print(\"     The new folder contains default config files,\")\n    print(\"     sample tests for helping new users get started,\")\n    print(\"     and Python boilerplates for setting up customized\")\n    print(\"     test frameworks.\")\n    print(\"\")\n\n\ndef show_mkfile_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"mkfile\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase mkfile [FILE.py] [OPTIONS]\")\n    print(\"     OR:    sbase mkfile [FILE.py] [OPTIONS]\")\n    print(\"  Example:\")\n    print(\"     sbase mkfile new_test.py\")\n    print(\"  Options:\")\n    print(\"     --uc  (UC Mode boilerplate using SB context manager)\")\n    print(\"     -b / --basic  (Basic boilerplate / single-line test)\")\n    print(\"     -r / --rec  (Adds Pdb+ breakpoint for Recorder Mode)\")\n    print(\"     --url=URL  (Makes the test start on a specific page)\")\n    print(\"  Language Options:\")\n    print(\"     --en / --English    |    --zh / --Chinese\")\n    print(\"     --nl / --Dutch      |    --fr / --French\")\n    print(\"     --it / --Italian    |    --ja / --Japanese\")\n    print(\"     --ko / --Korean     |    --pt / --Portuguese\")\n    print(\"     --ru / --Russian    |    --es / --Spanish\")\n    print(\"  Syntax Formats:\")\n    print(\"     --bc / --basecase       (BaseCase class inheritance)\")\n    print(\"     --pf / --pytest-fixture          (sb pytest fixture)\")\n    print(\"     --cf / --class-fixture   (class + sb pytest fixture)\")\n    print(\"     --cm / --context-manager        (SB context manager)\")\n    print(\"     --dc / --driver-context      (DriverContext manager)\")\n    print(\"     --dm / --driver-manager             (Driver manager)\")\n    print(\"  Output:\")\n    print(\"     Creates a new SBase test file with boilerplate code.\")\n    print(\"     If the file already exists, an error is raised.\")\n    print(\"     By default, uses English with BaseCase inheritance,\")\n    print(\"     and creates a boilerplate with common SeleniumBase\")\n    print('     methods: \"open\", \"type\", \"click\", \"assert_element\",')\n    print('     and \"assert_text\". If using the basic boilerplate')\n    print('     option, only the \"open\" method is included. Only the')\n    print(\"     BaseCase format supports Languages or Recorder Mode.\")\n    print(\"     UC Mode automatically uses English with SB() format.\")\n    print(\"\")\n\n\ndef show_mkrec_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"mkrec\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase mkrec [FILE.py] [OPTIONS]\")\n    print(\"     OR:    sbase mkrec [FILE.py] [OPTIONS]\")\n    print(\"  Examples:\")\n    print(\"     sbase mkrec new_test.py\")\n    print(\"     sbase mkrec new_test.py --url=wikipedia.org\")\n    print(\"  Options:\")\n    print(\"     --url=URL  (Sets the initial start page URL.)\")\n    print(\"     --edge  (Use Edge browser instead of Chrome.)\")\n    print(\"     --gui / --headed  (Use headed mode on Linux.)\")\n    print(\"     --uc / --undetected  (Use undetectable mode.)\")\n    print(\"     --ee  (Use SHIFT + ESC to end the recording.)\")\n    print(\"     --overwrite  (Overwrite file when it exists.)\")\n    print(\"     --behave  (Also output Behave/Gherkin files.)\")\n    print(\"  Output:\")\n    print(\"     Creates a new SeleniumBase test using the Recorder.\")\n    print(\"     If the filename already exists, an error is raised.\")\n    print(\"\")\n\n\ndef show_codegen_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"codegen\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase codegen [FILE.py] [OPTIONS]\")\n    print(\"     OR:    sbase codegen [FILE.py] [OPTIONS]\")\n    print(\"  Examples:\")\n    print(\"     sbase codegen new_test.py\")\n    print(\"     sbase codegen new_test.py --url=wikipedia.org\")\n    print(\"  Options:\")\n    print(\"     --url=URL  (Sets the initial start page URL.)\")\n    print(\"     --edge  (Use Edge browser instead of Chrome.)\")\n    print(\"     --gui / --headed  (Use headed mode on Linux.)\")\n    print(\"     --uc / --undetected  (Use undetectable mode.)\")\n    print(\"     --ee  (Use SHIFT + ESC to end the recording.)\")\n    print(\"     --overwrite  (Overwrite file when it exists.)\")\n    print(\"     --behave  (Also output Behave/Gherkin files.)\")\n    print(\"  Output:\")\n    print(\"     Creates a new SeleniumBase test using the Recorder.\")\n    print(\"     If the filename already exists, an error is raised.\")\n    print(\"\")\n\n\ndef show_recorder_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"recorder\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase recorder [OPTIONS]\")\n    print(\"     OR:    sbase recorder [OPTIONS]\")\n    print(\"  Options:\")\n    print(\"     --uc / --undetected  (Use undetectable mode.)\")\n    print(\"     --behave  (Also output Behave/Gherkin files.)\")\n    print(\"  Output:\")\n    print(\"     Launches the SeleniumBase Recorder Desktop App.\")\n    print(\"\")\n\n\ndef show_mkpres_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"mkpres\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase mkpres [FILE.py] [LANG]\")\n    print(\"     OR:    sbase mkpres [FILE.py] [LANG]\")\n    print(\"  Example:\")\n    print(\"     sbase mkpres new_presentation.py --en\")\n    print(\"  Language Options:\")\n    print(\"     --en / --English    |    --zh / --Chinese\")\n    print(\"     --nl / --Dutch      |    --fr / --French\")\n    print(\"     --it / --Italian    |    --ja / --Japanese\")\n    print(\"     --ko / --Korean     |    --pt / --Portuguese\")\n    print(\"     --ru / --Russian    |    --es / --Spanish\")\n    print(\"  Output:\")\n    print(\"     Creates a new presentation with 3 example slides.\")\n    print(\"     If the file already exists, an error is raised.\")\n    print(\"     By default, the slides are written in English,\")\n    print('     and use \"serif\" theme with \"slide\" transition.')\n    print(\"     The slides can be used as a basic boilerplate.\")\n    print(\"\")\n\n\ndef show_mkchart_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"mkchart\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase mkchart [FILE.py] [LANG]\")\n    print(\"     OR:    sbase mkchart [FILE.py] [LANG]\")\n    print(\"  Example:\")\n    print(\"     sbase mkchart new_chart.py --en\")\n    print(\"  Language Options:\")\n    print(\"     --en / --English    |    --zh / --Chinese\")\n    print(\"     --nl / --Dutch      |    --fr / --French\")\n    print(\"     --it / --Italian    |    --ja / --Japanese\")\n    print(\"     --ko / --Korean     |    --pt / --Portuguese\")\n    print(\"     --ru / --Russian    |    --es / --Spanish\")\n    print(\"  Output:\")\n    print(\"     Creates a new SeleniumBase chart presentation.\")\n    print(\"     If the file already exists, an error is raised.\")\n    print(\"     By default, the slides are written in English,\")\n    print('     and use a \"sky\" theme with \"slide\" transition.')\n    print(\"     The chart can be used as a basic boilerplate.\")\n    print(\"\")\n\n\ndef show_convert_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"convert\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase convert [WEBDRIVER_UNITTEST_FILE.py]\")\n    print(\"     OR:    sbase convert [WEBDRIVER_UNITTEST_FILE.py]\")\n    print(\"  Output:\")\n    print(\"     Converts a Selenium IDE exported WebDriver unittest\")\n    print(\"     file into a SeleniumBase file. Adds _SB to the new\")\n    print(\"     file name while keeping the original file intact.\")\n    print(\"     (Works with Katalon Recorder Selenium scripts.)\")\n    print(\"\")\n\n\ndef show_print_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"print\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase print [FILE] [OPTIONS]\")\n    print(\"     OR:    sbase print [FILE] [OPTIONS]\")\n    print(\"  Options:\")\n    print(\"     -n   (Add line Numbers to the rows)\")\n    print(\"  Output:\")\n    print(\"     Prints the code/text of any file\")\n    print(\"     with syntax-highlighting.\")\n    print(\"\")\n\n\ndef show_translate_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"translate\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase translate [SB_FILE.py] [LANG] [ACTION]\")\n    print(\"     OR:    sbase translate [SB_FILE.py] [LANG] [ACTION]\")\n    print(\"  Languages:\")\n    print(\"     --en / --English    |    --zh / --Chinese\")\n    print(\"     --nl / --Dutch      |    --fr / --French\")\n    print(\"     --it / --Italian    |    --ja / --Japanese\")\n    print(\"     --ko / --Korean     |    --pt / --Portuguese\")\n    print(\"     --ru / --Russian    |    --es / --Spanish\")\n    print(\"  Actions:\")\n    print(\"     -p / --print  (Print translation output to the screen)\")\n    print(\"     -o / --overwrite  (Overwrite the file being translated)\")\n    print(\"     -c / --copy  (Copy the translation to a new .py file)\")\n    print(\"  Options:\")\n    print(\"     -n  (include line Numbers when using the Print action)\")\n    print(\"  Output:\")\n    print(\"     Translates a SeleniumBase Python file into the language\")\n    print('     specified. Method calls and \"import\" lines get swapped.')\n    print(\"     Both a language and an action must be specified.\")\n    print('     The \"-p\" action can be paired with one other action.')\n    print('     When running with \"-c\" (or \"--copy\"), the new file name')\n    print(\"     will be the original name appended with an underscore\")\n    print(\"     plus the 2-letter language code of the new language.\")\n    print('     (Example: Translating \"test_1.py\" into Japanese with')\n    print('      \"-c\" will create a new file called \"test_1_ja.py\".)')\n    print(\"\")\n\n\ndef show_extract_objects_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"extract-objects\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase extract-objects [SB_FILE.py]\")\n    print(\"     OR:    sbase extract-objects [SB_FILE.py]\")\n    print(\"  Output:\")\n    print(\"     Creates page objects based on selectors found in a\")\n    print(\"     seleniumbase Python file and saves those objects to the\")\n    print('     \"page_objects.py\" file in the same folder as the tests.')\n    print(\"\")\n\n\ndef show_inject_objects_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"inject-objects\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase inject-objects [SB_FILE.py] [OPTIONS]\")\n    print(\"     OR:    sbase inject-objects [SB_FILE.py] [OPTIONS]\")\n    print(\"  Options:\")\n    print(\"     -c, --comments  (Add object selectors to the comments.)\")\n    print(\"                     (Default: No added comments.)\")\n    print(\"  Output:\")\n    print('     Takes the page objects found in the \"page_objects.py\"')\n    print(\"     file and uses those to replace matching selectors in\")\n    print(\"     the selected seleniumbase Python file.\")\n    print(\"\")\n\n\ndef show_objectify_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"objectify\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase objectify [SB_FILE.py] [OPTIONS]\")\n    print(\"     OR:    sbase objectify [SB_FILE.py] [OPTIONS]\")\n    print(\"  Options:\")\n    print(\"     -c, --comments  (Add object selectors to the comments.)\")\n    print(\"                     (Default: No added comments.)\")\n    print(\"  Output:\")\n    print(\"     A modified version of the file where the selectors\")\n    print(\"     have been replaced with variable names defined in\")\n    print('     \"page_objects.py\", supporting the Page Object Pattern.')\n    print(\"\")\n    print('     (seleniumbase \"objectify\" has the same outcome as')\n    print('      combining \"extract-objects\" with \"inject-objects\")')\n    print(\"\")\n\n\ndef show_revert_objects_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"revert-objects\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase revert-objects [SB_FILE.py] [OPTIONS]\")\n    print(\"     OR:    sbase revert-objects [SB_FILE.py] [OPTIONS]\")\n    print(\"  Options:\")\n    print(\"     -c, --comments  (Keep existing comments for the lines.)\")\n    print(\"                     (Default: No comments are kept.)\")\n    print(\"  Output:\")\n    print('     Reverts the changes made by \"seleniumbase objectify\" or')\n    print('     \"seleniumbase inject-objects\" when run against a')\n    print(\"     seleniumbase Python file. Objects will get replaced by\")\n    print('     selectors stored in the \"page_objects.py\" file.')\n    print(\"\")\n\n\ndef show_encrypt_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"encrypt OR obfuscate\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase encrypt   ||   seleniumbase obfuscate\")\n    print(\"                          --OR--\")\n    print(\"            sbase encrypt   ||          sbase obfuscate\")\n    print(\"  Output:\")\n    print(\"     Runs the password encryption/obfuscation tool.\")\n    print(\"     (Where you can enter a password to encrypt/obfuscate.)\")\n    print(\"\")\n\n\ndef show_decrypt_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"decrypt OR unobfuscate\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase decrypt   ||   seleniumbase unobfuscate\")\n    print(\"                          --OR--\")\n    print(\"            sbase decrypt   ||          sbase unobfuscate\")\n    print(\"  Output:\")\n    print(\"     Runs the password decryption/unobfuscation tool.\")\n    print(\"     (Where you can enter an encrypted password to decrypt.)\")\n    print(\"\")\n\n\ndef show_download_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"download\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase download server\")\n    print(\"     OR:    sbase download server\")\n    print(\"  Output:\")\n    print(\"     Downloads the Selenium Standalone Server.\")\n    print(\"     (Server is required for using your own Selenium Grid.)\")\n    print(\"\")\n\n\ndef show_grid_hub_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"grid-hub\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase grid-hub {start|stop|restart} [OPTIONS]\")\n    print(\"     OR:    sbase grid-hub {start|stop|restart} [OPTIONS]\")\n    print(\"  Options:\")\n    print(\"     -v, --verbose  (Increase verbosity of logging output.)\")\n    print(\"                    (Default: Quiet logging / not verbose.)\")\n    print(\"     --timeout=TIMEOUT  (Close idle browser after TIMEOUT.)\")\n    print(\"                        (The default TIMEOUT: 230 seconds.)\")\n    print(\"                        (Use --timeout=0 to skip timeouts.)\")\n    print(\"  Example:\")\n    print(\"     seleniumbase grid-hub start\")\n    print(\"  Output:\")\n    print(\"     Controls the Selenium Grid Hub Server, which allows\")\n    print(\"     for running tests on multiple machines in parallel\")\n    print(\"     to speed up test runs and reduce the total time\")\n    print(\"     of test suite execution.\")\n    print('     You can \"start\" or \"stop\" the Grid Hub server.')\n    print(\"\")\n\n\ndef show_grid_node_usage():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"  \" + c2 + \"** \" + c3 + \"grid-node\" + c2 + \" **\" + cr\n    print(sc)\n    print(\"\")\n    print(\"  Usage:\")\n    print(\"     seleniumbase grid-node {start|stop|restart} [OPTIONS]\")\n    print(\"     OR:    sbase grid-node {start|stop|restart} [OPTIONS]\")\n    print(\"  Options:\")\n    print(\"     --hub=[HOST/IP]  (The Grid Hub Hostname / IP Address.)\")\n    print(\"                           (Default: 127.0.0.1 if not set.)\")\n    print(\"     -v, --verbose  (Increase verbosity of logging output.)\")\n    print(\"                    (Default: Quiet logging / Not verbose.)\")\n    print(\"  Example:\")\n    print(\"     seleniumbase grid-node start --hub=127.0.0.1\")\n    print(\"  Output:\")\n    print(\"     Controls the Selenium Grid node, which serves as a\")\n    print(\"     worker machine for your Selenium Grid Hub server.\")\n    print('     You can \"start\" or \"stop\" the Grid node.')\n    print(\"\")\n\n\ndef get_version_info():\n    # from pkg_resources import get_distribution\n    # version = get_distribution(\"seleniumbase\").version\n    from seleniumbase import __version__\n\n    version_info = None\n    c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sb_text = c1 + \"selenium\" + c2 + \"base\" + cr\n    version_info = \"%s %s%s%s\" % (sb_text, c3, __version__, cr)\n    return version_info\n\n\ndef show_version_info():\n    version_info = get_version_info()\n    print(\"%s\" % version_info)\n\n\ndef get_package_location():\n    # from pkg_resources import get_distribution\n    # location = get_distribution(\"seleniumbase\").location\n    import os\n    import seleniumbase\n\n    location = os.path.dirname(os.path.realpath(seleniumbase.__file__))\n    if location.endswith(\"seleniumbase\"):\n        location = location[0 : -len(\"seleniumbase\")]  # noqa: E203\n    return location\n\n\ndef show_package_location():\n    location = get_package_location()\n    print(\"%s\" % location)\n\n\ndef show_methods():\n    c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    c4 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX\n    c5 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTGREEN_EX\n    cr = colorama.Style.RESET_ALL\n    sc = (\n        \"\\n \" + c2 + \" ** \" + c3 + \" SeleniumBase Python Methods \"\n        \"\" + c2 + \" ** \" + cr\n    )\n    print(sc)\n    print(\"\")\n    line = \"Here are some common methods that come with SeleniumBase:\"\n    line = c1 + line + cr\n    print(line)\n    line = \"(Some optional args are not shown here)\"\n    print(line)\n    print(\"\")\n    sbm = \"\"\n    sbm += \"*.open(url) => Navigate the browser window to the URL.\\n\"\n    sbm += \"*.type(selector, text) => Update the field with the text.\\n\"\n    sbm += \"*.click(selector) => Click the element with the selector.\\n\"\n    sbm += \"*.click_link(link_text) => Click the link containing text.\\n\"\n    sbm += \"*.check_if_unchecked(selector) => Check checkbox if unchecked.\\n\"\n    sbm += \"*.uncheck_if_checked(selector) => Uncheck checkbox if checked.\\n\"\n    sbm += \"*.select_option_by_text(dropdown_selector, option)\\n\"\n    sbm += \"*.hover_and_click(hover_selector, click_selector)\\n\"\n    sbm += \"*.drag_and_drop(drag_selector, drop_selector)\\n\"\n    sbm += \"*.choose_file(selector, file_path) => Choose a file to upload.\\n\"\n    sbm += \"*.get_text(selector) => Get the text from the element.\\n\"\n    sbm += \"*.get_current_url() => Get the URL of the current page.\\n\"\n    sbm += \"*.get_page_source() => Get the HTML of the current page.\\n\"\n    sbm += \"*.get_attribute(selector, attribute) => Get element attribute.\\n\"\n    sbm += \"*.get_title() => Get the title of the current page.\\n\"\n    sbm += \"*.go_back() => Navigate to the previous page in history.\\n\"\n    sbm += \"*.switch_to_frame(frame) => Switch into the iframe container.\\n\"\n    sbm += \"*.switch_to_default_content() => Exit all iframe containers.\\n\"\n    sbm += \"*.switch_to_parent_frame() => Exit from the current iframe.\\n\"\n    sbm += \"*.open_new_window() => Open a new window in the same browser.\\n\"\n    sbm += \"*.switch_to_window(window) => Switch to the browser window.\\n\"\n    sbm += \"*.switch_to_default_window() => Switch to the original window.\\n\"\n    sbm += \"*.get_new_driver(OPTIONS) => Open a new driver with OPTIONS.\\n\"\n    sbm += \"*.switch_to_driver(driver) => Switch to the browser driver.\\n\"\n    sbm += \"*.switch_to_default_driver() => Switch to the original driver.\\n\"\n    sbm += \"*.wait_for_element(selector) => Wait until element is visible.\\n\"\n    sbm += \"*.wait_for_element_present(selector) => Until element in HTML.\\n\"\n    sbm += \"*.is_element_visible(selector) => Return element visibility.\\n\"\n    sbm += \"*.is_element_present(selector) => Return element is in HTML.\\n\"\n    sbm += \"*.is_text_visible(text, selector) => Return text visibility.\\n\"\n    sbm += \"*.is_checked(selector) => Return whether the box is checked.\\n\"\n    sbm += \"*.sleep(seconds) => Do nothing for the given amount of time.\\n\"\n    sbm += \"*.save_screenshot(name) => Save a screenshot in .png format.\\n\"\n    sbm += \"*.assert_element(selector) => Verify the element is visible.\\n\"\n    sbm += \"*.assert_text(text, selector) => Verify text in the element.\\n\"\n    sbm += \"*.assert_exact_text(text, selector) => Verify text is exact.\\n\"\n    sbm += \"*.assert_url(url) => Verify that the current URL is the URL.\\n\"\n    sbm += \"*.assert_url_contains(substring) => Verify substring in URL.\\n\"\n    sbm += \"*.assert_title(title) => Verify the title of the web page.\\n\"\n    sbm += \"*.assert_title_contains(substring) => Verify STR in title.\\n\"\n    sbm += \"*.assert_downloaded_file(file) => Verify file was downloaded.\\n\"\n    sbm += \"*.assert_no_404_errors() => Verify there are no broken links.\\n\"\n    sbm += \"*.assert_no_js_errors() => Verify there are no JS errors.\\n\"\n    sbm = sbm.replace(\"*.\", \"self.\" + c1).replace(\"(\", cr + \"(\")\n    sbm = sbm.replace(\"self.\", c2 + \"self\" + c5 + \".\" + cr)\n    sbm = sbm.replace(\"(\", c3 + \"(\" + c4)\n    sbm = sbm.replace(\")\", c3 + \")\" + cr)\n    print(sbm)\n\n\ndef show_options():\n    c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    c4 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX\n    c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"\\n \" + c2 + \" ** \" + c3 + \" pytest CLI Options \" + c2 + \" ** \" + cr\n    print(sc)\n    print(\"\")\n    line = \"Here are some common pytest options to use with SeleniumBase:\"\n    line = c1 + line + cr\n    print(line)\n    line = '(Some options are Chromium-specific, e.g. \"--guest --mobile\")'\n    print(line)\n    op = \"\\n\"\n    op += '--browser=BROWSER  (Choice of web browser. Default is \"chrome\")\\n'\n    op += \"--edge / --firefox / --safari  (Shortcut for browser selection)\\n\"\n    op += \"--headless  (Run tests headlessly. Default setting on Linux OS)\\n\"\n    op += \"--demo  (Slow down and visually see test actions as they occur)\\n\"\n    op += \"--slow  (Slow down the automation. Faster than using Demo Mode)\\n\"\n    op += \"--rs / --reuse-session  (Reuse browser session between tests.)\\n\"\n    op += \"--reuse-class-session / --rcs  (RS, but for class tests only.)\\n\"\n    op += \"--crumbs  (Clear all cookies between tests reusing a session.)\\n\"\n    op += \"--maximize  (Start tests with the browser window maximized)\\n\"\n    op += \"--dashboard  (Enable SeleniumBase Dashboard at dashboard.html)\\n\"\n    op += \"--incognito  (Enable Chromium's Incognito Mode.)\\n\"\n    op += \"--guest  (Enable Chromium's Guest Mode.)\\n\"\n    op += \"--dark  (Enable Chromium's Dark Mode.)\\n\"\n    op += \"--uc  (Use undetected-chromedriver to evade detection.)\\n\"\n    op += \"-m=MARKER  (Run tests with the specified pytest marker.)\\n\"\n    op += \"-n=NUM  (Multithread the tests using that many threads.)\\n\"\n    op += \"-v  (Verbose mode. Print the full names of each test run.)\\n\"\n    op += \"--html=report.html  (Create a detailed pytest-html report.)\\n\"\n    op += \"--collect-only / --co  (Only show discovered tests. No run.)\\n\"\n    op += \"--co -q  (Only show full names of discovered tests. No run.)\\n\"\n    op += \"-x  (Stop running tests after the first failure is reached.)\\n\"\n    op += \"--pdb  (Enter Post Mortem Debug Mode after any test fails.)\\n\"\n    op += \"--trace  (Enter Debug Mode immediately after starting tests.)\\n\"\n    op += \" | Debug Mode Commands >>>      help / h: List all commands. |\\n\"\n    op += \" | n: Next line of method.      s: Step into.   c: Continue. |\\n\"\n    op += \" | where / w: Show stack spot.  u: Up stack.  d: Down stack. |\\n\"\n    op += \" | return / r: Run until method returns.    j: Jump to line. |\\n\"\n    op += \" | longlist / ll: See code.   dir(): List namespace objects. |\\n\"\n    op += \"--help / -h  (Display list of all available pytest options.)\\n\"\n    op += \"--ftrace / --final-trace  (Enter Debug Mode after tests end.)\\n\"\n    op += \"--recorder / --rec  (Save browser actions as Python scripts.)\\n\"\n    op += \"--rec-behave / --rec-gherkin  (Save actions as Gherkin code.)\\n\"\n    op += \"--rec-print  (Display recorded scripts when they are created.)\\n\"\n    op += \"--save-screenshot  (Save a screenshot at the end of each test.)\\n\"\n    op += \"--archive-logs  (Archive logs after tests to prevent deletion.)\\n\"\n    op += \"--check-js  (Check for JavaScript errors after page loads.)\\n\"\n    op += \"--start-page=URL  (The browser start page when tests begin.)\\n\"\n    op += \"--agent=STRING  (Modify the web browser's User-Agent string.)\\n\"\n    op += \"--mobile  (Use Chromium's mobile device emulator during tests.)\\n\"\n    op += '--metrics=STRING  (Set mobile \"CSSWidth,CSSHeight,PixelRatio\".)\\n'\n    op += \"--ad-block  (Block certain types of iframe ads from appearing.)\\n\"\n    op += \"--settings-file=FILE  (Override default SeleniumBase settings.)\\n\"\n    op += '--env=ENV  (Set the test env. Use \"self.env\" to access.)\\n'\n    op += '--data=DATA  (Extra test data. Use \"self.data\" to access.)\\n'\n    op += \"--disable-csp  (Disable the Content Security Policy of sites.)\\n\"\n    op += \"--remote-debug  (Sync to Ch_Debugger chrome://inspect/#devices)\\n\"\n    op += \"--server=SERVER  (The Selenium Grid server/IP used for tests.)\\n\"\n    op += \"--port=PORT  (The Selenium Grid port used by the test server.)\\n\"\n    op += \"--proxy=SERVER:PORT  (Connect to a proxy server:port for tests)\\n\"\n    op += \"--proxy=USER:PASS@SERVER:PORT  (Use authenticated proxy server)\\n\"\n    op += cr\n    op = op.replace(\"\\n-\", \"\\n\" + c1 + \"-\").replace(\"  (\", cr + \"  (\")\n    op = op.replace(\" / -\", cr + \" / \" + c1 + \"-\")\n    op = op.replace(\"=\", c2 + \"=\" + c3)\n    op = op.replace(\" | \", \" |\" + c3 + \" \").replace(\"|\\n\", cr + \"|\\n\")\n    op = op.replace(\": \", c5 + \":\" + c3 + \" \")\n    op = op.replace(\"Debug Mode Commands\", c5 + \"Debug Mode Commands\" + c3)\n    op = op.replace(\">>>\", c4 + \">>>\" + c3)\n    print(op)\n    line = \"To view all \" + c3 + \"pytest\" + cr\n    line += \" \" + c2 + \"command-line options\" + cr\n    line += ', type: \"' + c3 + \"pytest\" + cr + \" \" + c1 + \"--help\" + cr + '\".'\n    print(line)\n    print(\"\")\n\n\ndef show_behave_options():\n    c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    c4 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX\n    c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n    cr = colorama.Style.RESET_ALL\n    sc = \"\\n \" + c2 + \" ** \" + c3 + \" Behave CLI Options \" + c2 + \" ** \" + cr\n    print(sc)\n    print(\"\")\n    line = 'Here are some common \"behave\" options to use with SeleniumBase:'\n    line = c1 + line + cr\n    print(line)\n    line = '(Some options are Chromium-specific, e.g. \"-D guest -D mobile\")'\n    print(line)\n    op = \"\\n\"\n    op += '-D browser=BROWSER  (Choice of web browser. Default is \"chrome\")\\n'\n    op += \"-D headless  (Run tests headlessly. Default mode on Linux OS.)\\n\"\n    op += \"-D demo  (Slow down and visually see test actions as they occur)\\n\"\n    op += \"-D slow  (Slow down the automation. Faster than using Demo Mode)\\n\"\n    op += \"-D reuse-session / -D rs  (Reuse browser session between tests.)\\n\"\n    op += \"-D crumbs  (Clear all cookies between tests reusing a session.)\\n\"\n    op += \"-D maximize  (Start tests with the web browser window maximized)\\n\"\n    op += \"-D dashboard  (Enable SeleniumBase Dashboard at dashboard.html)\\n\"\n    op += \"-D incognito  (Enable Chromium's Incognito Mode.)\\n\"\n    op += \"-D guest  (Enable Chromium's Guest Mode.)\\n\"\n    op += \"-D dark  (Enable Chromium's Dark Mode.)\\n\"\n    op += \"-D uc  (Use undetected-chromedriver to evade detection.)\\n\"\n    op += \"--no-snippets / -q  (Quiet mode. Don't print snippets.)\\n\"\n    op += \"--dry-run / -d  (Dry run. Only show discovered tests.)\\n\"\n    op += \"--stop  (Stop running tests after the first failure is reached.)\\n\"\n    op += \"-D pdb  (Enter the Post Mortem Debug Mode after any test fails.)\\n\"\n    op += \"     | Debug Mode Commands  >>>   help / h: List all commands. |\\n\"\n    op += \"     |   n: Next line of method. s: Step through. c: Continue. |\\n\"\n    op += \"     |  return / r: Run until method returns. j: Jump to line. |\\n\"\n    op += \"     | where / w: Show stack spot. u: Up stack. d: Down stack. |\\n\"\n    op += \"     | longlist / ll: See code. dir(): List namespace objects. |\\n\"\n    op += \"-D recorder  (Record browser actions to generate test scripts.)\\n\"\n    op += \"-D rec-print  (Display recorded scripts when they are created.)\\n\"\n    op += \"-D save-screenshot  (Save a screenshot at the end of each test.)\\n\"\n    op += \"-D archive-logs  (Archive log files instead of deleting them.)\\n\"\n    op += \"-D check-js  (Check for JavaScript errors after page loads.)\\n\"\n    op += \"-D start-page=URL  (The browser start page when tests begin.)\\n\"\n    op += \"-D agent=STRING  (Modify the web browser's User-Agent string.)\\n\"\n    op += \"-D mobile  (Use Chromium's mobile device emulator during tests.)\\n\"\n    op += '-D metrics=STRING  (Set mobile \"CSSWidth,CSSHeight,PixelRatio\".)\\n'\n    op += \"-D ad-block  (Block some types of display ads after page loads.)\\n\"\n    op += \"-D settings-file=FILE  (Override default SeleniumBase settings.)\\n\"\n    op += '-D env=ENV  (Set the test env. Access using \"self.env\" in tests)\\n'\n    op += '-D data=DATA  (Extra test data. Access using \"self.data\".)\\n'\n    op += \"-D disable-csp  (Disable the Content Security Policy of sites.)\\n\"\n    op += \"-D remote-debug  (Sync Ch-R-Debugger chrome://inspect/#devices)\\n\"\n    op += \"-D server=SERVER  (The Selenium Grid server/IP used for tests.)\\n\"\n    op += \"-D port=PORT  (The Selenium Grid port used by the test server.)\\n\"\n    op += \"-D proxy=SERVER:PORT  (Connect to a proxy server:port for tests)\\n\"\n    op += \"-D proxy=USER:PASS@SERVER:PORT  (Use authenticated proxy server)\\n\"\n    op += cr\n    op = op.replace(\"\\n-\", \"\\n\" + c1 + \"-\").replace(\"  (\", cr + \"  (\")\n    op = op.replace(\" / -\", cr + \" / \" + c1 + \"-\")\n    op = op.replace(\"=\", c2 + \"=\" + c3)\n    op = op.replace(\" | \", \" |\" + c3 + \" \").replace(\"|\\n\", cr + \"|\\n\")\n    op = op.replace(\": \", c5 + \":\" + c3 + \" \")\n    op = op.replace(\"Debug Mode Commands\", c5 + \"Debug Mode Commands\" + c3)\n    op = op.replace(\">>>\", c4 + \">>>\" + c3)\n    print(op)\n    line = \"For the full list of \" + c2 + \"command-line options\" + cr\n    line += ', type: \"' + c3 + \"behave\" + cr + \" \" + c1 + \"--help\" + cr + '\".'\n    print(line)\n    print(\"\")\n\n\ndef show_detailed_help():\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    c6 = colorama.Back.CYAN\n    cr = colorama.Style.RESET_ALL\n    show_basic_usage()\n    print(c6 + \"              \" + c2 + \"  Commands:  \" + c6 + \"              \")\n    print(cr)\n    show_install_usage()\n    show_commander_usage()\n    show_behave_gui_usage()\n    show_caseplans_usage()\n    show_mkdir_usage()\n    show_mkfile_usage()\n    show_mkrec_usage()\n    show_codegen_usage()\n    show_recorder_usage()\n    show_mkpres_usage()\n    show_mkchart_usage()\n    show_convert_usage()\n    show_print_usage()\n    show_translate_usage()\n    show_extract_objects_usage()\n    show_inject_objects_usage()\n    show_objectify_usage()\n    show_revert_objects_usage()\n    show_encrypt_usage()\n    show_decrypt_usage()\n    show_download_usage()\n    show_grid_hub_usage()\n    show_grid_node_usage()\n    print(\n        '* (Use \"' + c3 + \"pytest\" + cr + '\" or \"' + c3 + ''\n        '' + \"python\" + cr + '\" for running tests) *\\n'\n    )\n\n\ndef main():\n    command = None\n    command_args = None\n    num_args = len(sys.argv)\n    if num_args == 1:\n        show_basic_usage()\n        return\n    elif num_args == 2:\n        command = sys.argv[1]\n        command_args = []\n    elif num_args > 2:\n        command = sys.argv[1]\n        command_args = sys.argv[2:]\n    command = command.lower()\n\n    if command == \"get\" or command == \"install\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_install\n\n            need_retry = False\n            need_another_retry = False\n            retry_msg_1 = \"* Unable to download driver! Retrying in 3s...\"\n            retry_msg_2 = \"** Unable to download driver! Retrying in 5s...\"\n            if \" --proxy=\" in \" \".join(sys.argv):\n                from seleniumbase.core import proxy_helper\n                for arg in sys.argv:\n                    if arg.startswith(\"--proxy=\"):\n                        proxy_string = arg.split(\"--proxy=\")[1]\n                        if \"@\" in proxy_string:\n                            proxy_string = proxy_string.split(\"@\")[1]\n                        proxy_helper.validate_proxy_string(proxy_string)\n                        break\n            try:\n                settings.HIDE_DRIVER_DOWNLOADS = False\n                sb_install.main()\n            except Exception as e:\n                invalid_run_cmd = constants.Warnings.INVALID_RUN_COMMAND\n                if invalid_run_cmd in str(e):\n                    raise\n                print()\n                print(retry_msg_1)\n                time.sleep(3)\n                print()\n                need_retry = True\n            if need_retry:\n                try:\n                    sb_install.main()\n                except Exception:\n                    print(retry_msg_2)\n                    time.sleep(5)\n                    print()\n                    need_another_retry = True\n            if need_another_retry:\n                sb_install.main()\n        else:\n            show_basic_usage()\n            show_install_usage()\n    elif command == \"commander\" or command == \"gui\":\n        from seleniumbase.console_scripts import sb_commander\n        sb_commander.main()\n    elif command == \"behave-gui\" or command == \"gui-behave\":\n        from seleniumbase.console_scripts import sb_behave_gui\n        sb_behave_gui.main()\n    elif (\n        command == \"caseplans\"\n        or command == \"case-plans\"\n        or command == \"case_plans\"\n    ):\n        from seleniumbase.console_scripts import sb_caseplans\n        sb_caseplans.main()\n    elif (\n        command == \"recorder\"\n        or (command == \"record\" and len(command_args) == 0)\n    ):\n        from seleniumbase.console_scripts import sb_recorder\n        sb_recorder.main()\n    elif (\n        command == \"mkrec\"\n        or command == \"codegen\"\n        or (command == \"record\" and len(command_args) >= 1)\n    ):\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_mkrec\n            sb_mkrec.main()\n        else:\n            show_basic_usage()\n            if command == \"codegen\":\n                show_codegen_usage()\n            else:\n                show_mkrec_usage()\n    elif command == \"mkdir\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_mkdir\n            sb_mkdir.main()\n        else:\n            show_basic_usage()\n            show_mkdir_usage()\n    elif command == \"mkfile\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_mkfile\n            sb_mkfile.main()\n        else:\n            show_basic_usage()\n            show_mkfile_usage()\n    elif command == \"mkpres\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_mkpres\n            sb_mkpres.main()\n        else:\n            show_basic_usage()\n            show_mkpres_usage()\n    elif command == \"mkchart\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_mkchart\n            sb_mkchart.main()\n        else:\n            show_basic_usage()\n            show_mkchart_usage()\n    elif command == \"convert\":\n        if len(command_args) == 1:\n            from seleniumbase.utilities.selenium_ide import convert_ide\n            convert_ide.main()\n        else:\n            show_basic_usage()\n            show_convert_usage()\n    elif command == \"print\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_print\n            sb_print.main()\n        else:\n            show_basic_usage()\n            show_print_usage()\n    elif command == \"translate\":\n        if len(command_args) >= 1:\n            from seleniumbase.translate import translator\n            translator.main()\n        else:\n            show_basic_usage()\n            show_translate_usage()\n    elif command == \"extract-objects\" or command == \"extract_objects\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_objectify\n            sb_objectify.extract_objects()\n        else:\n            show_basic_usage()\n            show_extract_objects_usage()\n    elif command == \"inject-objects\" or command == \"inject_objects\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_objectify\n\n            sb_objectify.inject_objects()\n        else:\n            show_basic_usage()\n            show_inject_objects_usage()\n    elif command == \"objectify\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_objectify\n            sb_objectify.objectify()\n        else:\n            show_basic_usage()\n            show_objectify_usage()\n    elif command == \"revert-objects\" or command == \"revert_objects\":\n        if len(command_args) >= 1:\n            from seleniumbase.console_scripts import sb_objectify\n            sb_objectify.revert_objects()\n        else:\n            show_basic_usage()\n            show_revert_objects_usage()\n    elif command == \"encrypt\" or command == \"obfuscate\":\n        if len(command_args) >= 0:\n            from seleniumbase.common import obfuscate\n            obfuscate.main()\n        else:\n            show_basic_usage()\n            show_encrypt_usage()\n    elif command == \"decrypt\" or command == \"unobfuscate\":\n        if len(command_args) >= 0:\n            from seleniumbase.common import unobfuscate\n            unobfuscate.main()\n        else:\n            show_basic_usage()\n            show_decrypt_usage()\n    elif command == \"download\":\n        if len(command_args) >= 1 and command_args[0].lower() == \"server\":\n            from seleniumbase.utilities.selenium_grid import (\n                download_selenium_server,\n            )\n            download_selenium_server.main(force_download=True)\n        else:\n            show_basic_usage()\n            show_download_usage()\n    elif command == \"grid-hub\" or command == \"grid_hub\":\n        if len(command_args) >= 1:\n            from seleniumbase.utilities.selenium_grid import grid_hub\n            grid_hub.main()\n        else:\n            show_basic_usage()\n            show_grid_hub_usage()\n    elif command == \"grid-node\" or command == \"grid_node\":\n        if len(command_args) >= 1:\n            from seleniumbase.utilities.selenium_grid import grid_node\n            grid_node.main()\n        else:\n            show_basic_usage()\n            show_grid_node_usage()\n    elif command == \"version\" or command == \"--version\" or command == \"-v\":\n        if len(command_args) == 0:\n            from seleniumbase.console_scripts import logo_helper\n            seleniumbase_logo = logo_helper.get_seleniumbase_logo()\n            print(seleniumbase_logo)\n            print(\"\")\n            show_package_location()\n            show_version_info()\n            print(\"\")\n        else:\n            show_basic_usage()\n    elif command == \"methods\" or command == \"--methods\":\n        show_methods()\n    elif command == \"options\" or command == \"--options\":\n        show_options()\n    elif command == \"behave-options\" or command == \"--behave-options\":\n        show_behave_options()\n    elif command == \"proxy\" or command == \"--proxy\":\n        import fasteners\n        import os\n        import warnings\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n            pip_find_lock = fasteners.InterProcessLock(\n                constants.PipInstall.FINDLOCK\n            )\n            with pip_find_lock:\n                try:\n                    from proxy import proxy  # noqa: F401\n                except Exception:\n                    shared_utils.pip_install(\n                        \"proxy.py\", version=constants.ProxyPy.VER\n                    )\n            os.system(\"proxy %s\" % \" \".join(sys.argv[2:]))\n    elif command == \"help\" or command == \"--help\" or command == \"-h\":\n        if len(command_args) >= 1:\n            if command_args[0] == \"get\":\n                print(\"\")\n                show_install_usage()\n                return\n            elif command_args[0] == \"install\":\n                print(\"\")\n                show_install_usage()\n                return\n            elif command_args[0] == \"commander\":\n                print(\"\")\n                show_commander_usage()\n                return\n            elif command_args[0] == \"gui\":\n                print(\"\")\n                show_commander_usage()\n                return\n            elif command_args[0] == \"behave-gui\":\n                print(\"\")\n                show_behave_gui_usage()\n                return\n            elif command_args[0] == \"gui-behave\":\n                print(\"\")\n                show_behave_gui_usage()\n                return\n            elif command_args[0] == \"caseplans\":\n                print(\"\")\n                show_caseplans_usage()\n                return\n            elif command_args[0] == \"case-plans\":\n                print(\"\")\n                show_caseplans_usage()\n                return\n            elif command_args[0] == \"case_plans\":\n                print(\"\")\n                show_caseplans_usage()\n                return\n            elif command_args[0] == \"mkdir\":\n                print(\"\")\n                show_mkdir_usage()\n                return\n            elif command_args[0] == \"mkfile\":\n                print(\"\")\n                show_mkfile_usage()\n                return\n            elif command_args[0] == \"mkrec\":\n                print(\"\")\n                show_mkrec_usage()\n                return\n            elif command_args[0] == \"codegen\":\n                print(\"\")\n                show_codegen_usage()\n                return\n            elif command_args[0] == \"recorder\":\n                print(\"\")\n                show_recorder_usage()\n                return\n            elif command_args[0] == \"mkpres\":\n                print(\"\")\n                show_mkpres_usage()\n                return\n            elif command_args[0] == \"mkchart\":\n                print(\"\")\n                show_mkchart_usage()\n                return\n            elif command_args[0] == \"convert\":\n                print(\"\")\n                show_convert_usage()\n                return\n            elif command_args[0] == \"print\":\n                print(\"\")\n                show_print_usage()\n                return\n            elif command_args[0] == \"translate\":\n                print(\"\")\n                show_translate_usage()\n                return\n            elif command_args[0] == \"extract-objects\":\n                print(\"\")\n                show_extract_objects_usage()\n                return\n            elif command_args[0] == \"inject-objects\":\n                print(\"\")\n                show_inject_objects_usage()\n                return\n            elif command_args[0] == \"objectify\":\n                print(\"\")\n                show_objectify_usage()\n                return\n            elif command_args[0] == \"revert-objects\":\n                print(\"\")\n                show_revert_objects_usage()\n                return\n            elif command_args[0] == \"encrypt\":\n                print(\"\")\n                show_encrypt_usage()\n                return\n            elif command_args[0] == \"obfuscate\":\n                print(\"\")\n                show_encrypt_usage()\n                return\n            elif command_args[0] == \"decrypt\":\n                print(\"\")\n                show_decrypt_usage()\n                return\n            elif command_args[0] == \"unobfuscate\":\n                print(\"\")\n                show_decrypt_usage()\n                return\n            elif command_args[0] == \"download\":\n                print(\"\")\n                show_download_usage()\n                return\n            elif command_args[0] == \"grid-hub\":\n                print(\"\")\n                show_grid_hub_usage()\n                return\n            elif command_args[0] == \"grid-node\":\n                print(\"\")\n                show_grid_node_usage()\n                return\n        show_detailed_help()\n    else:\n        show_basic_usage()\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n        cr = colorama.Style.RESET_ALL\n        invalid_cmd = \"===> INVALID COMMAND: >> %s <<\\n\" % command\n        invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n        invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n        invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n        invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n        print(invalid_cmd)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_behave_gui.py",
    "content": "\"\"\"\nLaunches SeleniumBase Behave Commander | GUI for Behave.\n\nUsage:\n      seleniumbase behave-gui [OPTIONAL PATH or TEST FILE]\n             sbase behave-gui [OPTIONAL PATH or TEST FILE]\n\nExamples:\n      sbase behave-gui\n      sbase behave-gui features/\n      sbase behave-gui features/calculator.feature\n\nOutput:\n      Launches SeleniumBase Behave Commander | GUI for Behave.\n\"\"\"\nimport colorama\nimport subprocess\nimport sys\nimport tkinter as tk\nfrom seleniumbase.fixtures import shared_utils\nfrom tkinter.scrolledtext import ScrolledText\n\n\ndef set_colors(use_colors):\n    c0 = \"\"\n    c1 = \"\"\n    c2 = \"\"\n    c3 = \"\"\n    c4 = \"\"\n    c5 = \"\"\n    c6 = \"\"\n    cr = \"\"\n    if use_colors:\n        c0 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n        c2 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c3 = colorama.Fore.BLACK + colorama.Back.LIGHTGREEN_EX\n        c4 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c6 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX\n        cr = colorama.Style.RESET_ALL\n    return c0, c1, c2, c3, c4, c5, c6, cr\n\n\ndef send_window_to_front(root):\n    root.lift()\n    root.attributes(\"-topmost\", True)\n    root.after_idle(root.attributes, \"-topmost\", False)\n\n\ndef do_behave_run(\n    root,\n    tests,\n    selected_tests,\n    command_string,\n    browser_string,\n    rs_string,\n    quiet_mode,\n    demo_mode,\n    mobile_mode,\n    dashboard,\n    headless,\n    save_screenshots,\n    additional_options,\n):\n    total_tests = len(tests)\n    total_selected_tests = 0\n    test_to_run = None\n    for selected_test in selected_tests:\n        if selected_tests[selected_test].get():\n            total_selected_tests += 1\n\n    full_run_command = '\"%s\" -m behave' % sys.executable\n    if total_selected_tests == 0 or total_tests == total_selected_tests:\n        if command_string:\n            full_run_command += \" \"\n            full_run_command += command_string\n    else:\n        for test_number, test in enumerate(tests):\n            if selected_tests[test_number].get():\n                full_run_command += \" \"\n                test_to_run = test\n                if test.startswith(\"(GROUP)  \"):\n                    test_to_run = test.split(\"(GROUP)  \")[1]\n                    full_run_command += test_to_run.split(\" => \")[0]\n                else:\n                    full_run_command += test.split(\" => \")[0]\n\n    if \"(-D edge)\" in browser_string:\n        full_run_command += \" -D edge\"\n    elif \"(-D firefox)\" in browser_string:\n        full_run_command += \" -D firefox\"\n    elif \"(-D safari)\" in browser_string:\n        full_run_command += \" -D safari\"\n\n    if \"(-D rs)\" in rs_string:\n        full_run_command += \" -D rs\"\n    elif \"(-D rs -D crumbs)\" in rs_string:\n        full_run_command += \" -D rs -D crumbs\"\n    elif \"(-D rcs)\" in rs_string:\n        full_run_command += \" -D rcs\"\n    elif \"(-D rcs -D crumbs)\" in rs_string:\n        full_run_command += \" -D rcs -D crumbs\"\n\n    if quiet_mode:\n        full_run_command += \" --quiet\"\n\n    if demo_mode:\n        full_run_command += \" -D demo\"\n\n    if mobile_mode:\n        full_run_command += \" -D mobile\"\n\n    if dashboard:\n        full_run_command += \" -D dashboard\"\n\n    if headless:\n        full_run_command += \" -D headless\"\n    elif shared_utils.is_linux():\n        full_run_command += \" -D gui\"\n\n    if save_screenshots:\n        full_run_command += \" -D screenshot\"\n\n    additional_options_list = additional_options.split(\" \")\n    dash_T_needed = False\n    if (\n        \"-T\" not in additional_options_list\n        and \"--no-timings\" not in additional_options_list\n        and \"--show-timings\" not in additional_options_list\n    ):\n        dash_T_needed = True\n    dash_k_needed = False\n    if (\n        \"-k\" not in additional_options_list\n        and \"--no-skipped\" not in additional_options_list\n        and \"--show-skipped\" not in additional_options_list\n    ):\n        dash_k_needed = True\n    additional_options = additional_options.strip()\n    if additional_options:\n        full_run_command += \" \"\n        full_run_command += additional_options\n    if dash_T_needed:\n        full_run_command += \" -T\"\n    if dash_k_needed:\n        full_run_command += \" -k\"\n\n    print(full_run_command)\n    if not additional_options or \" \" not in additional_options:\n        subprocess.Popen(full_run_command, shell=True)\n    else:\n        proc = subprocess.Popen(\n            full_run_command, stderr=subprocess.PIPE, shell=True\n        )\n        (output, error) = proc.communicate()\n        if error and proc.returncode == 2:\n            if str(error).startswith(\"b'\") and str(error).endswith(\"\\\\n'\"):\n                error = str(error)[2:-3]\n            elif str(error).startswith(\"b'\") and str(error).endswith(\"'\"):\n                error = str(error)[2:-1]\n            else:\n                error = str(error)\n            error = error.replace(\"\\\\n\", \"\\n\")\n            print(error)\n    send_window_to_front(root)\n\n\ndef create_tkinter_gui(tests, command_string, t_count, f_count, s_tests):\n    root = tk.Tk()\n    root.title(\"SeleniumBase Behave Commander | GUI for Behave\")\n    if shared_utils.is_windows():\n        root.minsize(820, 640)\n    else:\n        root.minsize(820, 656)\n    tk.Label(root, text=\"\").pack()\n\n    options_list = [\n        \"Use Chrome Browser  (Default)\",\n        \"Use Edge Browser  (-D edge)\",\n        \"Use Firefox Browser  (-D firefox)\",\n    ]\n    if shared_utils.is_mac():\n        options_list.append(\"Use Safari Browser  (-D safari)\")\n    brx = tk.StringVar(root)\n    brx.set(options_list[0])\n    question_menu = tk.OptionMenu(root, brx, *options_list)\n    question_menu.pack()\n\n    options_list = [\n        \"New Session Per Test  (Default)\",\n        \"Reuse Session for ALL the tests  (-D rs)\",\n        \"Reuse Session and clear cookies  (-D rs -D crumbs)\",\n        \"Reuse Session in the SAME class/feature  (-D rcs)\",\n        \"Reuse Session in class and clear cookies  (-D rcs -D crumbs)\",\n    ]\n    rsx = tk.StringVar(root)\n    rsx.set(options_list[0])\n    question_menu = tk.OptionMenu(root, rsx, *options_list)\n    question_menu.pack()\n\n    qmx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Quiet Mode  (--quiet)\", variable=qmx, pady=0\n    )\n    chk.pack()\n\n    dmx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Demo Mode  (-D demo)\", variable=dmx, pady=0\n    )\n    chk.pack()\n\n    mmx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Mobile Mode  (-D mobile)\", variable=mmx, pady=0\n    )\n    chk.pack()\n\n    dbx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Dashboard  (-D dashboard)\", variable=dbx, pady=0\n    )\n    chk.pack()\n    chk.select()\n\n    hbx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Headless Browser  (-D headless)\", variable=hbx, pady=0\n    )\n    chk.pack()\n\n    ssx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Save Screenshots  (-D screenshot)\", variable=ssx, pady=0\n    )\n    chk.pack()\n\n    tk.Label(root, text=\"\").pack()\n    plural = \"s\"\n    if f_count == 1:\n        plural = \"\"\n    run_display = (\n        \"Select from %s rows (%s feature%s with %s scenarios):  \"\n        \"(All tests will run if none are selected)\"\n        % (len(tests), f_count, plural, t_count)\n    )\n    if t_count == 1:\n        run_display = \"Only ONE TEST was found and will be run:\"\n        tests = s_tests\n    tk.Label(root, text=run_display, bg=\"yellow\", fg=\"magenta\").pack()\n    text_area = ScrolledText(\n        root, width=100, height=12, wrap=\"word\", state=tk.DISABLED\n    )\n    text_area.pack(side=tk.TOP, fill=tk.BOTH, expand=True)\n    count = 0\n    ara = {}\n    for row in tests:\n        row += \" \" * 200\n        ara[count] = tk.IntVar()\n        cb = None\n        if shared_utils.is_windows():\n            cb = tk.Checkbutton(\n                text_area,\n                text=(row),\n                bg=\"white\",\n                fg=\"black\",\n                anchor=\"w\",\n                pady=0,\n                borderwidth=1,\n                highlightthickness=1,\n                variable=ara[count],\n            )\n        else:\n            cb = tk.Checkbutton(\n                text_area,\n                text=(row),\n                bg=\"white\",\n                fg=\"black\",\n                anchor=\"w\",\n                pady=0,\n                variable=ara[count],\n            )\n        text_area.window_create(\"end\", window=cb)\n        text_area.insert(\"end\", \"\\n\")\n        count += 1\n\n    tk.Label(root, text=\"\").pack()\n    additional_options = \"\"\n    aopts = tk.StringVar(value=additional_options)\n    tk.Label(\n        root,\n        text='Additional \"behave\" Options:  (Eg. \"-D incognito --junit\")',\n        bg=\"yellow\", fg=\"blue\",\n    ).pack()\n    entry = tk.Entry(root, textvariable=aopts)\n    entry.pack()\n    entry.focus()\n    entry.bind(\n        \"<Return>\",\n        (\n            lambda _: do_behave_run(\n                root,\n                tests,\n                ara,\n                command_string,\n                brx.get(),\n                rsx.get(),\n                qmx.get(),\n                dmx.get(),\n                mmx.get(),\n                dbx.get(),\n                hbx.get(),\n                ssx.get(),\n                aopts.get(),\n            )\n        ),\n    )\n    tk.Button(\n        root,\n        text=\"Run Selected Tests\",\n        fg=\"green\",\n        command=lambda: do_behave_run(\n            root,\n            tests,\n            ara,\n            command_string,\n            brx.get(),\n            rsx.get(),\n            qmx.get(),\n            dmx.get(),\n            mmx.get(),\n            dbx.get(),\n            hbx.get(),\n            ssx.get(),\n            aopts.get(),\n        ),\n    ).pack()\n    tk.Label(root, text=\"\\n\").pack()\n\n    # Bring form window to front\n    send_window_to_front(root)\n    # Use decoy to set correct focus on main window\n    decoy = tk.Tk()\n    decoy.geometry(\"1x1\")\n    decoy.iconify()\n    decoy.update()\n    decoy.deiconify()\n    decoy.destroy()\n    # Start tkinter\n    root.mainloop()\n\n\ndef main():\n    use_colors = True\n    if shared_utils.is_linux():\n        use_colors = False\n    c0, c1, c2, c3, c4, c5, c6, cr = set_colors(use_colors)\n    command_args = sys.argv[2:]\n    command_string = \" \".join(command_args)\n    message = \"\"\n    message += c2\n    message += \"*\"\n    message += c4\n    message += \" Starting the \"\n    message += c0\n    message += \"Selenium\"\n    message += c1\n    message += \"Base\"\n    message += c2\n    message += \" \"\n    message += c6\n    message += \"Behave\"\n    message += c4\n    message += \" \"\n    message += c3\n    message += \"Commander\"\n    message += c4\n    message += \" GUI App\"\n    message += c2\n    message += \"...\"\n    message += cr\n    print(message)\n    command_string = command_string.replace(\"--quiet\", \"\")\n    command_string = command_string.replace(\"-q\", \"\")\n    proc = subprocess.Popen(\n        '\"%s\" -m behave -d %s --show-source'\n        % (sys.executable, command_string),\n        stdout=subprocess.PIPE,\n        shell=True,\n    )\n    (output, error) = proc.communicate()\n    if error:\n        error_msg = \"Error collecting tests: %s\" % str(error)\n        error_msg = c5 + error_msg + cr\n        print(error_msg)\n        return\n    filename = None\n    feature_name = None\n    scenario_name = None\n    f_tests = []  # Features\n    s_tests = []  # Scenarios\n    tests = []  # All tests\n    file_scenario_count = {}\n    f_count = 0\n    s_count = 0\n    t_count = 0\n    if shared_utils.is_windows():\n        output = output.decode(\"latin1\")\n    else:\n        output = output.decode(\"utf-8\")\n    for row in output.replace(\"\\r\", \"\").split(\"\\n\"):\n        if row.startswith(\"Feature: \"):\n            if f_count > 0:\n                file_scenario_count[str(f_count)] = s_count\n            f_count += 1\n            s_count = 0\n        elif row.startswith(\"  Scenario: \"):\n            s_count += 1\n            file_scenario_count[str(f_count)] = s_count\n        elif row.startswith(\"  Scenario Outline: \"):\n            s_count += 1\n            file_scenario_count[str(f_count)] = s_count\n    file_scenario_count[str(f_count)] = s_count\n    f_count = 0\n    s_count = 0\n    for row in output.replace(\"\\r\", \"\").split(\"\\n\"):\n        if row.startswith(\"Feature: \"):\n            f_count += 1\n            feature_name = row.split(\"Feature: \")[1]\n            if \" # features/\" in feature_name:\n                filename = feature_name.split(\" # features/\")[-1]\n                filename = \"features/\" + filename.split(\":\")[0]\n                feature_name = feature_name.split(\" # features/\")[0]\n            elif \" # features\\\\\" in feature_name:\n                filename = feature_name.split(\" # features\\\\\")[-1]\n                filename = \"features\\\\\" + filename.split(\":\")[0]\n                feature_name = feature_name.split(\" # features\\\\\")[0]\n            else:\n                filename = feature_name.split(\" # \")[-1]\n                filename = filename.split(\":\")[0]\n                feature_name = feature_name.split(\" # \")[-1]\n            s_count = file_scenario_count[str(f_count)]\n            filename = filename.strip()\n            t_name = \"(GROUP)  %s => %s\" % (filename, feature_name)\n            t_name += \"  <>  (%s Total)\" % s_count\n            f_tests.append(t_name)\n        elif (\n            row.startswith(\"  Scenario: \")\n            or row.startswith(\"  Scenario Outline: \")\n        ):\n            t_count += 1\n            line_num = row.split(\":\")[-1]\n            scenario_name = None\n            if row.startswith(\"  Scenario: \"):\n                scenario_name = row.split(\"  Scenario: \")[-1]\n            else:\n                scenario_name = row.split(\"  Scenario Outline: \")[-1]\n            if \" -- @\" in scenario_name:\n                scenario_name = scenario_name.split(\" # \")[0].rstrip()\n            elif \" # features/\" in scenario_name:\n                scenario_name = scenario_name.split(\" # features/\")[0]\n            else:\n                scenario_name = scenario_name.split(\" # \")[0]\n            scenario_name = scenario_name.strip()\n            s_tests.append(\"%s:%s => %s\" % (filename, line_num, scenario_name))\n    tests = f_tests + s_tests\n    if not tests:\n        err_msg_0 = c5 + \"ERROR:\" + cr + \"\\n\"\n        err_msg_1 = '  No \"behave\" tests found! Expecting \"*.feature\" files!'\n        err_msg_1 = c6 + err_msg_1 + cr + \"\\n\"\n        err_msg_2 = '  \"*.feature\" files would live in a \"features/\" folder.'\n        err_msg_2 = c6 + err_msg_2 + cr + \"\\n\"\n        err_msg_3 = \"Exiting SBase Behave Commander...\"\n        err_msg_3 = c5 + err_msg_3 + cr\n        error_msg = err_msg_0 + err_msg_1 + err_msg_2 + err_msg_3\n        print(error_msg)\n        return\n\n    create_tkinter_gui(tests, command_string, t_count, f_count, s_tests)\n\n\nif __name__ == \"__main__\":\n    print('To open SBase Behave Commander, type \"sbase behave-gui\"')\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_caseplans.py",
    "content": "\"\"\"\nLaunches the SeleniumBase Case Plans Generator.\n\nUsage:\n      seleniumbase caseplans [OPTIONAL PATH or TEST FILE]\n             sbase caseplans [OPTIONAL PATH or TEST FILE]\n\nExamples:\n      sbase caseplans\n      sbase caseplans -k agent\n      sbase caseplans -m marker2\n      sbase caseplans test_suite.py\n      sbase caseplans offline_examples/\n\nOutput:\n      Launches the SeleniumBase Case Plans Generator.\n\"\"\"\nimport colorama\nimport os\nimport subprocess\nimport sys\nimport tkinter as tk\nfrom seleniumbase.fixtures import shared_utils\nfrom tkinter import messagebox\nfrom tkinter.scrolledtext import ScrolledText\n\n\ndef set_colors(use_colors):\n    c0 = \"\"\n    c1 = \"\"\n    c2 = \"\"\n    c3 = \"\"\n    c4 = \"\"\n    c5 = \"\"\n    cr = \"\"\n    if use_colors:\n        c0 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n        c2 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c3 = colorama.Fore.BLACK + colorama.Back.LIGHTCYAN_EX\n        c4 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        cr = colorama.Style.RESET_ALL\n    return c0, c1, c2, c3, c4, c5, cr\n\n\ndef send_window_to_front(root):\n    root.lift()\n    root.attributes(\"-topmost\", True)\n    root.after_idle(root.attributes, \"-topmost\", False)\n\n\ndef show_no_case_plans_warning():\n    messagebox.showwarning(\n        \"No existing Case Plans found!\",\n        \"\\nNo existing Case Plans found!!\\n\\nCreate some boilerplates first!\",\n    )\n\n\ndef get_test_id(display_id):\n    \"\"\"The id used in various places such as the test log path.\"\"\"\n    return (\n        display_id.replace(\".py::\", \".\").replace(\"::\", \".\").replace(\" \", \"_\")\n    )\n\n\ndef generate_case_plan_boilerplates(\n    root,\n    tests,\n    selected_tests,\n    tests_with_case_plan,\n    tests_without_case_plan,\n):\n    total_tests = len(tests)\n    total_selected_tests = 0\n    for selected_test in selected_tests:\n        if selected_tests[selected_test].get():\n            total_selected_tests += 1\n\n    test_cases = []\n    case_plans_to_create = []\n    if total_selected_tests == 0:\n        messagebox.showwarning(\n            \"No tests were selected!\",\n            \"\\nℹ️ No tests were selected!\\nSelect tests for Case Plans!\",\n        )\n        send_window_to_front(root)\n        return\n    elif total_tests == total_selected_tests:\n        for test in tests:\n            test_cases.append(test)\n    else:\n        for test_number, test in enumerate(tests):\n            if selected_tests[test_number].get():\n                test_cases.append(test)\n\n    for test_case in test_cases:\n        if (\n            test_case in tests_without_case_plan\n            and test_case not in tests_with_case_plan\n        ):\n            case_plans_to_create.append(test_case)\n\n    new_plans = 0\n    for case_plan in case_plans_to_create:\n        parts = case_plan.split(\"/\")\n        test_address = None\n        folder_path = None\n        if len(parts) == 1:\n            test_address = parts[0]\n        if len(parts) > 1:\n            test_address = parts[-1]\n            folder_path = \"/\".join(parts[0:-1])\n        test_id = get_test_id(test_address)\n        case_id = test_id + \".md\"\n        full_folder_path = None\n        if len(parts) == 1:\n            full_folder_path = \"case_plans\"\n            if not os.path.exists(full_folder_path):\n                os.makedirs(full_folder_path)\n        else:\n            full_folder_path = os.path.join(folder_path, \"case_plans\")\n            if not os.path.exists(full_folder_path):\n                os.makedirs(full_folder_path)\n\n        data = []\n        data.append(\"``%s``\" % test_address)\n        data.append(\"---\")\n        data.append(\"| # | Step Description | Expected Result |\")\n        data.append(\"| - | ---------------- | --------------- |\")\n        data.append(\"| 1 | Perform Action 1 | Verify Action 1 |\")\n        data.append(\"| 2 | Perform Action 2 | Verify Action 2 |\")\n        data.append(\"\")\n        file_name = case_id\n        file_path = os.path.join(full_folder_path, file_name)\n        if not os.path.exists(file_path):\n            out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            new_plans += 1\n            print(\"Created %s\" % file_path)\n\n    if new_plans == 1:\n        messagebox.showinfo(\n            \"A new Case Plan was generated!\",\n            '\\n✅ %s new boilerplate Case Plan was generated!' % new_plans,\n        )\n    elif new_plans >= 2:\n        messagebox.showinfo(\n            \"New Case Plans were generated!\",\n            '\\n✅ %s new boilerplate Case Plans were generated!' % new_plans,\n        )\n    else:\n        messagebox.showwarning(\n            \"No new Case Plans were generated!\",\n            \"\\nℹ️ No new boilerplates were generated!\\n\\n\"\n            \"The selected tests already have Case Plans!\",\n        )\n    send_window_to_front(root)\n\n\ndef view_summary_of_existing_case_plans(root, tests):\n    case_data_storage = []\n    case_to_test_hash = {}\n    full_t = []\n    for test_index, test in enumerate(tests):\n        full_t.append(test)\n        parts = test.strip().split(\"/\")\n        test_address = None\n        folder_path = None\n        if len(parts) == 1:\n            test_address = parts[0]\n            folder_path = \".\"\n        if len(parts) > 1:\n            test_address = parts[-1]\n            folder_path = \"/\".join(parts[0:-1])\n        test_id = get_test_id(test_address)\n        case_id = test_id + \".md\"\n        case_path = None\n        if len(parts) == 1:\n            case_path = os.path.join(\"case_plans\", case_id)\n        else:\n            case_path = os.path.join(folder_path, \"case_plans\", case_id)\n        if os.path.exists(case_path):\n            f = open(case_path, mode=\"r\")\n            case_data = f.read()\n            f.close()\n            case_data_storage.append(case_data)\n            case_to_test_hash[len(case_data_storage) - 1] = test_index\n\n    full_plan = []\n    if len(case_data_storage) > 0:\n        full_plan.append(\n            \"<h2>Summary of existing Case Plans</h2>\"\n        )\n        full_plan.append(\"\")\n        full_plan.append(\"|   |   |\")\n        full_plan.append(\"| - | - |\")\n        full_plan.append(\"|  🔵  | Plans with customized step definitions. |\")\n        full_plan.append(\"|  ⭕  | Plans using default boilerplate code. |\")\n        full_plan.append(\"|  🚧  | Plans under construction with no table. |\")\n        full_plan.append(\"\")\n        full_plan.append(\"--------\")\n    else:\n        show_no_case_plans_warning()\n        send_window_to_front(root)\n        return\n    full_plan.append(\"\")\n    full_plan.append(\"<h3>🔎 (Click rows to expand) 🔍</h3>\")\n    full_plan.append(\"\")\n\n    full_plan = []\n    num_ready_cases = 0\n    num_boilerplate = 0\n    num_in_progress = 0\n    for case_index, case_data in enumerate(case_data_storage):\n        icon = \"🔵\"\n        table_missing = False\n        if \"| 1 | Perform Action 1 | Verify Action 1 |\" in case_data:\n            # Still using raw boilerplate code. (Missing real test steps)\n            icon = \"⭕\"\n        if case_data.count(\"|\") < 9 or case_data.count(\"-\") < 3:\n            # Not enough characters for a minimal Markdown case plan file.\n            # The dash(es) on line 2, and the Markdown table are required.\n            # This is what a minimal case plan file might look like:\n            \"\"\"\n            TEST_ADDRESS\n            -\n            | Steps | Results |\n            |   -   |    -    |\n            | Step1 | Result1 |\n            \"\"\"\n            icon = \"🚧\"\n            table_missing = True\n        lines = case_data.split(\"\\n\")\n        if len(lines) >= 3 and not table_missing:\n            first_line = lines[0]\n            first_line = first_line.strip()\n            if not (first_line.startswith(\"``\") and first_line.endswith(\"``\")):\n                first_line = \"``%s``\" % tests[case_to_test_hash[case_index]]\n                lines.insert(0, first_line)\n            else:\n                first_line = \"``%s``\" % tests[case_to_test_hash[case_index]]\n                lines[0] = first_line\n            lines.insert(0, \"<details>\")\n            lines[1] = (\n                \"<summary> %s <code><b>\" % icon\n                + first_line[2:-2]\n                + \"</b></code></summary>\"\n            )\n            if (\n                lines[2].strip().startswith(\"-\")\n                and lines[2].strip().endswith(\"-\")\n            ):\n                lines[2] = \"\"\n            elif lines[2].strip() != \"\":\n                lines.insert(2, \"\")\n            if lines[-1].strip() != \"\":\n                lines.append(\"\")\n            lines.append(\"</details>\")\n            full_plan.append(\"\\r\\n\".join(lines))\n        else:\n            # No existing Case Plan found. / File is missing boilerplate.\n            icon = \"🚧\"\n            lines = []\n            first_line = tests[case_to_test_hash[case_index]]\n            first_line = \"%s <code><b>%s</b></code>\" % (icon, first_line)\n            lines.insert(0, first_line)\n            full_plan.append(\"\\r\\n\".join(lines))\n        full_plan.append(\"\")\n        if icon == \"🔵\":\n            num_ready_cases += 1\n        elif icon == \"⭕\":\n            num_boilerplate += 1\n        elif icon == \"🚧\":\n            num_in_progress += 1\n\n    msg_ready_cases = \"%s Case Plans with customized tables\" % num_ready_cases\n    if num_ready_cases == 1:\n        msg_ready_cases = \"1 Case Plan with a customized table\"\n    msg_boilerplate = \"%s Case Plans using boilerplate code\" % num_boilerplate\n    if num_boilerplate == 1:\n        msg_boilerplate = \"1 Case Plan using boilerplate code\"\n    msg_in_progress = \"%s Case Plans that are missing tables\" % num_in_progress\n    if num_in_progress == 1:\n        msg_in_progress = \"1 Case Plan that is missing a table\"\n\n    msg_r = \" \".join(msg_ready_cases.split(\" \")[1:])\n    msg_b = \" \".join(msg_boilerplate.split(\" \")[1:])\n    msg_i = \" \".join(msg_in_progress.split(\" \")[1:])\n\n    plan_head = []\n    if len(case_data_storage) > 0:\n        plan_head.append(\n            \"<h2>Summary of existing Case Plans</h2>\"\n        )\n        plan_head.append(\"\")\n        plan_head.append(\"|   |    |   |\")\n        plan_head.append(\"| - | -: | - |\")\n        plan_head.append(\"| 🔵 | %s | %s |\" % (num_ready_cases, msg_r))\n        plan_head.append(\"| ⭕ | %s | %s |\" % (num_boilerplate, msg_b))\n        plan_head.append(\"| 🚧 | %s | %s |\" % (num_in_progress, msg_i))\n        plan_head.append(\"\")\n        plan_head.append(\"--------\")\n    else:\n        show_no_case_plans_warning()\n        send_window_to_front(root)\n        return\n    plan_head.append(\"\")\n    plan_head.append(\"<h3>🔎 (Click rows to expand) 🔍</h3>\")\n    plan_head.append(\"\")\n\n    for row in full_plan:\n        plan_head.append(row)\n    full_plan = plan_head\n\n    file_path = \"case_summary.md\"\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(full_plan))\n    file.close()\n\n    if num_ready_cases < 10:\n        msg_ready_cases = \" %s\" % msg_ready_cases\n    if num_ready_cases < 100:\n        msg_ready_cases = \" %s\" % msg_ready_cases\n    if num_boilerplate < 10:\n        msg_boilerplate = \" %s\" % msg_boilerplate\n    if num_boilerplate < 100:\n        msg_boilerplate = \" %s\" % msg_boilerplate\n    if num_in_progress < 10:\n        msg_in_progress = \" %s\" % msg_in_progress\n    if num_in_progress < 100:\n        msg_in_progress = \" %s\" % msg_in_progress\n    gen_message = (\n        '🗂️  Summary generated at \"case_summary.md\":'\n        '\\n🔵 %s'\n        '\\n⭕ %s'\n        '\\n🚧 %s'\n        % (msg_ready_cases, msg_boilerplate, msg_in_progress)\n    )\n    print(gen_message)\n    if num_ready_cases < 10:\n        msg_ready_cases = \" %s\" % msg_ready_cases\n    if num_ready_cases < 100:\n        msg_ready_cases = \" %s\" % msg_ready_cases\n    if num_boilerplate < 10:\n        msg_boilerplate = \" %s\" % msg_boilerplate\n    if num_boilerplate < 100:\n        msg_boilerplate = \" %s\" % msg_boilerplate\n    if num_in_progress < 10:\n        msg_in_progress = \" %s\" % msg_in_progress\n    if num_in_progress < 100:\n        msg_in_progress = \" %s\" % msg_in_progress\n    messagebox.showinfo(\n        \"Case Plans Summary generated!\",\n        '\\nSummary generated at \"case_summary.md\"'\n        '\\n🔵 %s'\n        '\\n⭕ %s'\n        '\\n🚧 %s'\n        % (msg_ready_cases, msg_boilerplate, msg_in_progress)\n    )\n    send_window_to_front(root)\n\n\ndef create_tkinter_gui(tests, command_string):\n    root = tk.Tk()\n    root.title(\"SeleniumBase Case Plans Generator\")\n    if shared_utils.is_windows():\n        root.minsize(820, 618)\n    else:\n        root.minsize(820, 652)\n    tk.Label(root, text=\"\").pack()\n    run_display = (\n        \"Select from %s tests found:  \"\n        \"(Boilerplate Case Plans will be generated as needed)\"\n        % len(tests)\n    )\n    if len(tests) == 1:\n        run_display = (\n            \"Select from 1 test found:  \"\n            \"(Boilerplate Case Plans will be generated as needed)\"\n        )\n    run_display_2 = \"(Tests with existing Case Plans are already checked)\"\n    tk.Label(root, text=run_display, bg=\"yellow\", fg=\"green\").pack()\n    tk.Label(root, text=run_display_2, bg=\"yellow\", fg=\"magenta\").pack()\n    text_area = ScrolledText(\n        root, width=100, height=12, wrap=\"word\", state=tk.DISABLED\n    )\n    text_area.pack(side=tk.TOP, fill=tk.BOTH, expand=True)\n    count = 0\n    ara = {}\n    tests_with_case_plan = []\n    tests_without_case_plan = []\n    for row in tests:\n        row += \" \" * 200\n        ara[count] = tk.IntVar()\n        cb = None\n        if shared_utils.is_windows():\n            cb = tk.Checkbutton(\n                text_area,\n                text=(row),\n                bg=\"white\",\n                fg=\"black\",\n                anchor=\"w\",\n                pady=0,\n                borderwidth=1,\n                highlightthickness=1,\n                variable=ara[count],\n            )\n        else:\n            cb = tk.Checkbutton(\n                text_area,\n                text=(row),\n                bg=\"white\",\n                fg=\"black\",\n                anchor=\"w\",\n                pady=0,\n                variable=ara[count],\n            )\n        parts = row.strip().split(\"/\")\n        test_address = None\n        folder_path = None\n        if len(parts) == 1:\n            test_address = parts[0]\n        if len(parts) > 1:\n            test_address = parts[-1]\n            folder_path = \"/\".join(parts[0:-1])\n        test_id = get_test_id(test_address)\n        case_id = test_id + \".md\"\n        case_path = None\n        if len(parts) == 1:\n            case_path = os.path.join(\"case_plans\", case_id)\n        else:\n            case_path = os.path.join(folder_path, \"case_plans\", case_id)\n        if os.path.exists(case_path):\n            cb.select()\n            tests_with_case_plan.append(row.strip())\n        else:\n            tests_without_case_plan.append(row.strip())\n        text_area.window_create(\"end\", window=cb)\n        text_area.insert(\"end\", \"\\n\")\n        count += 1\n\n    tk.Label(root, text=\"\").pack()\n    tk.Button(\n        root,\n        text=(\n            \"Generate boilerplate Case Plans \"\n            \"for selected tests missing them\"),\n        fg=\"green\",\n        command=lambda: generate_case_plan_boilerplates(\n            root,\n            tests,\n            ara,\n            tests_with_case_plan,\n            tests_without_case_plan,\n        ),\n    ).pack()\n\n    tk.Label(root, text=\"\").pack()\n    try:\n        tk.Button(\n            root,\n            text=(\"Generate Summary of existing Case Plans\"),\n            fg=\"teal\",\n            command=lambda: view_summary_of_existing_case_plans(root, tests),\n        ).pack()\n    except Exception:\n        tk.Button(\n            root,\n            text=(\"Generate Summary of existing Case Plans\"),\n            fg=\"green\",\n            command=lambda: view_summary_of_existing_case_plans(root, tests),\n        ).pack()\n    tk.Label(root, text=\"\\n\").pack()\n\n    # Bring form window to front\n    send_window_to_front(root)\n    # Use decoy to set correct focus on main window\n    decoy = tk.Tk()\n    decoy.geometry(\"1x1\")\n    decoy.iconify()\n    decoy.update()\n    decoy.deiconify()\n    decoy.destroy()\n    # Start tkinter\n    root.mainloop()\n\n\ndef main():\n    use_colors = True\n    if shared_utils.is_linux():\n        use_colors = False\n    c0, c1, c2, c3, c4, c5, cr = set_colors(use_colors)\n    command_args = sys.argv[2:]\n    command_string = \" \".join(command_args)\n    message = \"\"\n    message += c2\n    message += \"*\"\n    message += c4\n    message += \" Starting the \"\n    message += c0\n    message += \"Selenium\"\n    message += c1\n    message += \"Base\"\n    message += c2\n    message += \" \"\n    message += c3\n    message += \"Case Plans\"\n    message += c4\n    message += \" Generator\"\n    message += c2\n    message += \"...\"\n    message += cr\n    print(message)\n\n    proc = subprocess.Popen(\n        '\"%s\" -m pytest --collect-only -q --rootdir=\"./\" %s'\n        % (sys.executable, command_string),\n        stdout=subprocess.PIPE,\n        shell=True,\n    )\n    (output, error) = proc.communicate()\n    if error:\n        error_msg = \"Error collecting tests: %s\" % str(error)\n        error_msg = c5 + error_msg + cr\n        print(error_msg)\n        return\n    tests = []\n    if shared_utils.is_windows():\n        output = output.decode(\"latin1\")\n    else:\n        output = output.decode(\"utf-8\")\n    for row in output.replace(\"\\r\", \"\").split(\"\\n\"):\n        if (\"::\") in row:\n            tests.append(row)\n    if not tests:\n        error_msg = \"No tests found! Exiting the Case Plans Generator...\"\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        print(error_msg)\n        return\n\n    create_tkinter_gui(tests, command_string)\n\n\nif __name__ == \"__main__\":\n    print('To open the Case Plans Generator, type \"sbase caseplans\"')\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_commander.py",
    "content": "\"\"\"\nLaunches SeleniumBase Commander | GUI for pytest.\n\nUsage:\n      seleniumbase commander [OPTIONAL PATH or TEST FILE]\n             sbase commander [OPTIONAL PATH or TEST FILE]\n            seleniumbase gui [OPTIONAL PATH or TEST FILE]\n                   sbase gui [OPTIONAL PATH or TEST FILE]\n\nExamples:\n      sbase gui\n      sbase gui -k agent\n      sbase gui -m marker2\n      sbase gui test_suite.py\n      sbase gui offline_examples/\n\nOutput:\n      Launches SeleniumBase Commander | GUI for pytest.\n\"\"\"\nimport colorama\nimport os\nimport subprocess\nimport sys\nimport tkinter as tk\nfrom seleniumbase.fixtures import shared_utils\nfrom tkinter.scrolledtext import ScrolledText\n\n\ndef set_colors(use_colors):\n    c0 = \"\"\n    c1 = \"\"\n    c2 = \"\"\n    c3 = \"\"\n    c4 = \"\"\n    c5 = \"\"\n    cr = \"\"\n    if use_colors:\n        c0 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n        c2 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c3 = colorama.Fore.BLACK + colorama.Back.LIGHTGREEN_EX\n        c4 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        cr = colorama.Style.RESET_ALL\n    return c0, c1, c2, c3, c4, c5, cr\n\n\ndef send_window_to_front(root):\n    root.lift()\n    root.attributes(\"-topmost\", True)\n    root.after_idle(root.attributes, \"-topmost\", False)\n\n\ndef do_pytest_run(\n    root,\n    tests,\n    selected_tests,\n    command_string,\n    browser_string,\n    rs_string,\n    thread_string,\n    verbose,\n    demo_mode,\n    mobile_mode,\n    dashboard,\n    html_report,\n    headless,\n    save_screenshots,\n    additional_options,\n):\n    cleaned_tests = []\n    for test in tests:\n        if test.startswith(\"(FILE)  \"):\n            clean_test = test.split(\"(FILE)  \")[1].split(\"  =>  \")[0]\n            cleaned_tests.append(clean_test)\n        else:\n            cleaned_tests.append(test)\n    tests = cleaned_tests\n    total_tests = len(tests)\n    total_selected_tests = 0\n    for selected_test in selected_tests:\n        if selected_tests[selected_test].get():\n            total_selected_tests += 1\n\n    full_run_command = '\"%s\" -m pytest' % sys.executable\n    if total_selected_tests == 0 or total_tests == total_selected_tests:\n        if command_string:\n            full_run_command += \" \"\n            full_run_command += command_string\n    else:\n        for test_number, test in enumerate(tests):\n            if selected_tests[test_number].get():\n                full_run_command += \" \"\n                if ' ' not in test:\n                    full_run_command += test\n                elif '\"' not in test:\n                    full_run_command += '\"%s\"' % test\n                else:\n                    full_run_command += test.replace(\" \", \"\\\\ \")\n\n    if \"(--edge)\" in browser_string:\n        full_run_command += \" --edge\"\n    elif \"(--firefox)\" in browser_string:\n        full_run_command += \" --firefox\"\n    elif \"(--safari)\" in browser_string:\n        full_run_command += \" --safari\"\n\n    if \"(--rs)\" in rs_string:\n        full_run_command += \" --rs\"\n    elif \"(--rs --crumbs)\" in rs_string:\n        full_run_command += \" --rs --crumbs\"\n    elif \"(--rcs)\" in rs_string:\n        full_run_command += \" --rcs\"\n    elif \"(--rcs --crumbs)\" in rs_string:\n        full_run_command += \" --rcs --crumbs\"\n\n    if \"(-n=2)\" in thread_string:\n        full_run_command += \" -n=2\"\n    elif \"(-n=3)\" in thread_string:\n        full_run_command += \" -n=3\"\n    elif \"(-n=4)\" in thread_string:\n        full_run_command += \" -n=4\"\n    elif \"(-n=5)\" in thread_string:\n        full_run_command += \" -n=5\"\n    elif \"(-n=6)\" in thread_string:\n        full_run_command += \" -n=6\"\n    elif \"(-n=7)\" in thread_string:\n        full_run_command += \" -n=7\"\n    elif \"(-n=8)\" in thread_string:\n        full_run_command += \" -n=8\"\n\n    if demo_mode:\n        full_run_command += \" --demo\"\n\n    if mobile_mode:\n        full_run_command += \" --mobile\"\n\n    if dashboard:\n        full_run_command += \" --dashboard\"\n\n    if html_report:\n        full_run_command += \" --html=report.html\"\n\n    if headless:\n        full_run_command += \" --headless\"\n    elif shared_utils.is_linux():\n        full_run_command += \" --gui\"\n\n    if save_screenshots:\n        full_run_command += \" --screenshot\"\n\n    capture_needed = False\n    if \"--capture\" not in additional_options:\n        capture_needed = True\n\n    additional_options = additional_options.strip()\n    if additional_options:\n        full_run_command += \" \"\n        full_run_command += additional_options\n\n    if verbose:\n        full_run_command += \" -v\"\n\n    if capture_needed:\n        full_run_command += \" --capture=tee-sys\"\n\n    print(full_run_command)\n    subprocess.Popen(full_run_command, shell=True)\n    send_window_to_front(root)\n\n\ndef create_tkinter_gui(tests, command_string, files, solo_tests):\n    root = tk.Tk()\n    root.title(\"SeleniumBase Commander | GUI for pytest\")\n    if shared_utils.is_windows():\n        root.minsize(820, 696)\n    else:\n        root.minsize(820, 702)\n    tk.Label(root, text=\"\").pack()\n\n    options_list = [\n        \"Use Chrome Browser  (Default)\",\n        \"Use Edge Browser  (--edge)\",\n        \"Use Firefox Browser  (--firefox)\",\n    ]\n    if shared_utils.is_mac():\n        options_list.append(\"Use Safari Browser  (--safari)\")\n    brx = tk.StringVar(root)\n    brx.set(options_list[0])\n    question_menu = tk.OptionMenu(root, brx, *options_list)\n    question_menu.pack()\n\n    options_list = [\n        \"New Session Per Test  (Default)\",\n        \"Reuse Session for ALL tests in thread  (--rs)\",\n        \"Reuse Session and also clear cookies  (--rs --crumbs)\",\n        \"Reuse Session for tests with same CLASS  (--rcs)\",\n        \"Reuse Session for class and clear cookies  (--rcs --crumbs)\",\n    ]\n    rsx = tk.StringVar(root)\n    rsx.set(options_list[0])\n    question_menu = tk.OptionMenu(root, rsx, *options_list)\n    question_menu.pack()\n\n    options_list = [\n        \"Number of Threads: 1  (Default)\",\n        \"Number of Threads: 2  (-n=2)\",\n        \"Number of Threads: 3  (-n=3)\",\n        \"Number of Threads: 4  (-n=4)\",\n    ]\n    try:\n        if int(os.cpu_count()) >= 8:\n            options_list.append(\"Number of Threads: 5  (-n=5)\")\n            options_list.append(\"Number of Threads: 6  (-n=6)\")\n            options_list.append(\"Number of Threads: 7  (-n=7)\")\n            options_list.append(\"Number of Threads: 8  (-n=8)\")\n    except Exception:\n        pass\n\n    ntx = tk.StringVar(root)\n    ntx.set(options_list[0])\n    question_menu = tk.OptionMenu(root, ntx, *options_list)\n    question_menu.pack()\n\n    vox = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Verbose Output  (-v)\", variable=vox, pady=0\n    )\n    chk.pack()\n    chk.select()\n\n    dmx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Demo Mode  (--demo)\", variable=dmx, pady=0\n    )\n    chk.pack()\n\n    mmx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Mobile Mode  (--mobile)\", variable=mmx, pady=0\n    )\n    chk.pack()\n\n    dbx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Dashboard  (--dashboard)\", variable=dbx, pady=0\n    )\n    chk.pack()\n    chk.select()\n\n    hrx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Report  (--html=report.html)\", variable=hrx, pady=0\n    )\n    chk.pack()\n    chk.select()\n\n    hbx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Headless Browser  (--headless)\", variable=hbx, pady=0\n    )\n    chk.pack()\n\n    ssx = tk.IntVar()\n    chk = tk.Checkbutton(\n        root, text=\"Save Screenshots  (--screenshot)\", variable=ssx, pady=0\n    )\n    chk.pack()\n\n    tk.Label(root, text=\"\").pack()\n    run_display = (\n        \"Select from %s rows (%s files with %s tests):  \"\n        \"(All tests will run if none are selected)\"\n        % (len(tests), len(files), len(solo_tests))\n    )\n    if len(solo_tests) == 1:\n        run_display = \"Only ONE TEST was found and will be run:\"\n        tests = solo_tests\n    elif len(files) == 1:\n        run_display = (\n            \"Select from %s tests:  \"\n            \"(All tests will run if none are selected)\"\n            % (len(solo_tests))\n        )\n        tests = solo_tests\n    tk.Label(root, text=run_display, bg=\"yellow\", fg=\"magenta\").pack()\n    text_area = ScrolledText(\n        root, width=100, height=12, wrap=\"word\", state=tk.DISABLED\n    )\n    text_area.pack(side=tk.TOP, fill=tk.BOTH, expand=True)\n    count = 0\n    ara = {}\n    for row in tests:\n        row += \" \" * 200\n        ara[count] = tk.IntVar()\n        cb = None\n        if shared_utils.is_windows():\n            cb = tk.Checkbutton(\n                text_area,\n                text=(row),\n                bg=\"white\",\n                fg=\"black\",\n                anchor=\"w\",\n                pady=0,\n                borderwidth=1,\n                highlightthickness=1,\n                variable=ara[count],\n            )\n        else:\n            cb = tk.Checkbutton(\n                text_area,\n                text=(row),\n                bg=\"white\",\n                fg=\"black\",\n                anchor=\"w\",\n                pady=0,\n                variable=ara[count],\n            )\n        text_area.window_create(\"end\", window=cb)\n        text_area.insert(\"end\", \"\\n\")\n        count += 1\n\n    tk.Label(root, text=\"\").pack()\n    additional_options = \"\"\n    aopts = tk.StringVar(value=additional_options)\n    tk.Label(\n        root,\n        text='Additional \"pytest\" Options:  (Eg. \"--incognito --slow\")',\n        bg=\"yellow\", fg=\"blue\",\n    ).pack()\n    entry = tk.Entry(root, textvariable=aopts)\n    entry.pack()\n    entry.focus()\n    entry.bind(\n        \"<Return>\",\n        (\n            lambda _: do_pytest_run(\n                root,\n                tests,\n                ara,\n                command_string,\n                brx.get(),\n                rsx.get(),\n                ntx.get(),\n                vox.get(),\n                dmx.get(),\n                mmx.get(),\n                dbx.get(),\n                hrx.get(),\n                hbx.get(),\n                ssx.get(),\n                aopts.get(),\n            )\n        ),\n    )\n    tk.Button(\n        root,\n        text=\"Run Selected Tests\",\n        fg=\"green\",\n        command=lambda: do_pytest_run(\n            root,\n            tests,\n            ara,\n            command_string,\n            brx.get(),\n            rsx.get(),\n            ntx.get(),\n            vox.get(),\n            dmx.get(),\n            mmx.get(),\n            dbx.get(),\n            hrx.get(),\n            hbx.get(),\n            ssx.get(),\n            aopts.get(),\n        ),\n    ).pack()\n    tk.Label(root, text=\"\\n\").pack()\n\n    # Bring form window to front\n    send_window_to_front(root)\n    # Use decoy to set correct focus on main window\n    decoy = tk.Tk()\n    decoy.geometry(\"1x1\")\n    decoy.iconify()\n    decoy.update()\n    decoy.deiconify()\n    decoy.destroy()\n    # Start tkinter\n    root.mainloop()\n\n\ndef main():\n    use_colors = True\n    if shared_utils.is_linux():\n        use_colors = False\n    c0, c1, c2, c3, c4, c5, cr = set_colors(use_colors)\n    command_args = sys.argv[2:]\n    command_string = \" \".join(command_args)\n    message = \"\"\n    message += c2\n    message += \"*\"\n    message += c4\n    message += \" Starting the \"\n    message += c0\n    message += \"Selenium\"\n    message += c1\n    message += \"Base\"\n    message += c2\n    message += \" \"\n    message += c3\n    message += \"Commander\"\n    message += c4\n    message += \" Desktop App\"\n    message += c2\n    message += \"...\"\n    message += cr\n    print(message)\n\n    proc = subprocess.Popen(\n        '\"%s\" -m pytest --collect-only -q --rootdir=\"./\" %s'\n        % (sys.executable, command_string),\n        stdout=subprocess.PIPE,\n        shell=True,\n    )\n    (output, error) = proc.communicate()\n    if error:\n        error_msg = \"Error collecting tests: %s\" % str(error)\n        error_msg = c5 + error_msg + cr\n        print(error_msg)\n        return\n    tests = []\n    if shared_utils.is_windows():\n        output = output.decode(\"latin1\")\n    else:\n        output = output.decode(\"utf-8\")\n    for row in output.replace(\"\\r\", \"\").split(\"\\n\"):\n        if (\"::\") in row:\n            tests.append(row)\n    if not tests:\n        error_msg = \"No tests found! Exiting SeleniumBase Commander...\"\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        print(error_msg)\n        return\n    groups = []\n    for row in tests:\n        if row.count(\"::\") >= 1:\n            g_name = \"(FILE)  %s\" % row.split(\"::\")[0]\n            groups.append(g_name)\n    files = []\n    used_files = []\n    for row in groups:\n        if row not in used_files:\n            used_files.append(row)\n            plural = \"s\"\n            if groups.count(row) == 1:\n                plural = \"\"\n            f_row = \"%s  =>  (%s Test%s)\" % (row, groups.count(row), plural)\n            files.append(f_row)\n    solo_tests = tests\n    tests = [*files, *tests]\n\n    create_tkinter_gui(tests, command_string, files, solo_tests)\n\n\nif __name__ == \"__main__\":\n    print('To open SBase Commander, type \"sbase commander\" or \"sbase gui\"')\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_install.py",
    "content": "\"\"\"\nDownloads the specified webdriver to \"seleniumbase/drivers/\"\n\nUsage:\n    sbase get {chromedriver|geckodriver|edgedriver|\n               iedriver|uc_driver|cft|chs} [OPTIONS]\nOptions:\n    VERSION         Specify the version.\n                    Tries to detect the needed version.\n                    If using chromedriver or edgedriver,\n                    you can use the major version integer.\n    -p OR --path    Also copy the driver to /usr/local/bin\nExamples:\n    sbase get chromedriver\n    sbase get geckodriver\n    sbase get edgedriver\n    sbase get chromedriver 114\n    sbase get chromedriver 114.0.5735.90\n    sbase get chromedriver stable\n    sbase get chromedriver beta\n    sbase get chromedriver -p\n    sbase get chromium\n    sbase get cft 131\n    sbase get chs\nOutput:\n    Downloads the webdriver to seleniumbase/drivers/\n    (chromedriver is required for Chrome automation)\n    (geckodriver is required for Firefox automation)\n    (edgedriver is required for MS__Edge automation)\n\"\"\"\nimport colorama\nimport logging\nimport os\nimport platform\nimport requests\nimport shutil\nimport subprocess\nimport sys\nimport time\nimport tarfile\nimport urllib3\nimport zipfile\nfrom contextlib import suppress\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import shared_utils\nfrom seleniumbase import config as sb_config\nfrom seleniumbase import drivers  # webdriver storage folder for SeleniumBase\nfrom seleniumbase.drivers import cft_drivers  # chrome-for-testing\nfrom seleniumbase.drivers import chs_drivers  # chrome-headless-shell\nfrom seleniumbase.drivers import chromium_drivers  # base chromium\n\nurllib3.disable_warnings()\nARCH = platform.architecture()[0]\nIS_ARM_MAC = shared_utils.is_arm_mac()\nIS_MAC = shared_utils.is_mac()\nIS_ARM_LINUX = shared_utils.is_arm_linux()\nIS_LINUX = shared_utils.is_linux()\nIS_WINDOWS = shared_utils.is_windows()\nDRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))\nDRIVER_DIR_CFT = os.path.dirname(os.path.realpath(cft_drivers.__file__))\nDRIVER_DIR_CHS = os.path.dirname(os.path.realpath(chs_drivers.__file__))\nDRIVER_DIR_CHROMIUM = os.path.dirname(\n    os.path.realpath(chromium_drivers.__file__)\n)\nLOCAL_PATH = \"/usr/local/bin/\"  # On Mac and Linux systems\nDEFAULT_CHROMEDRIVER_VERSION = \"114.0.5735.90\"  # (If can't find LATEST_STABLE)\nDEFAULT_GECKODRIVER_VERSION = \"v0.36.0\"\nDEFAULT_EDGEDRIVER_VERSION = \"115.0.1901.183\"  # (If can't find LATEST_STABLE)\n\n\ndef invalid_run_command():\n    exp = \"  ** get / install **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase install [DRIVER_NAME] [OPTIONS]\\n\"\n    exp += \"     OR     sbase install [DRIVER_NAME] [OPTIONS]\\n\"\n    exp += \"     OR  seleniumbase get [DRIVER_NAME] [OPTIONS]\\n\"\n    exp += \"     OR         sbase get [DRIVER_NAME] [OPTIONS]\\n\"\n    exp += \"         (Drivers: chromedriver, cft, uc_driver,\\n\"\n    exp += \"                   edgedriver, chs, geckodriver)\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     VERSION    Specify the version.\\n\"\n    exp += \"                Tries to detect the needed version.\\n\"\n    exp += \"                If using chromedriver or edgedriver,\\n\"\n    exp += \"                you can use the major version integer.\\n\"\n    exp += \"\\n\"\n    exp += \"     -p OR --path   Also copy the driver to /usr/local/bin\\n\"\n    exp += \"  Examples:\\n\"\n    exp += \"     sbase get chromedriver\\n\"\n    exp += \"     sbase get geckodriver\\n\"\n    exp += \"     sbase get edgedriver\\n\"\n    exp += \"     sbase get chromedriver 114\\n\"\n    exp += \"     sbase get chromedriver 114.0.5735.90\\n\"\n    exp += \"     sbase get chromedriver stable\\n\"\n    exp += \"     sbase get chromedriver beta\\n\"\n    exp += \"     sbase get chromedriver -p\\n\"\n    exp += \"     sbase get cft 131\\n\"\n    exp += \"     sbase get chs\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Downloads the webdriver to seleniumbase/drivers/\\n\"\n    exp += \"     (chromedriver is required for Chrome automation)\\n\"\n    exp += \"     (geckodriver is required for Firefox automation)\\n\"\n    exp += \"     (edgedriver is required for MS__Edge automation)\\n\"\n    exp += \"     (cft is for the `Chrome for Testing` binary exe)\\n\"\n    exp += \"     (chs is for the `Chrome-Headless-Shell` binary.)\\n\"\n    print(\"\")\n    raise Exception(\"%s\\n\\n%s\" % (constants.Warnings.INVALID_RUN_COMMAND, exp))\n\n\ndef make_executable(file_path):\n    # Set permissions to: \"If you can read it, you can execute it.\"\n    mode = os.stat(file_path).st_mode\n    mode |= (mode & 0o444) >> 2  # copy R bits to X\n    os.chmod(file_path, mode)\n\n\ndef get_proxy_info():\n    use_proxy = None\n    protocol = \"http\"\n    proxy_string = None\n    user_and_pass = None\n    if \" --proxy=\" in \" \".join(sys.argv):\n        from seleniumbase.core import proxy_helper\n        for arg in sys.argv:\n            if arg.startswith(\"--proxy=\"):\n                proxy_string = arg.split(\"--proxy=\")[1]\n                if \"@\" in proxy_string:\n                    # Format => username:password@hostname:port\n                    try:\n                        user_and_pass = proxy_string.split(\"@\")[0]\n                        proxy_string = proxy_string.split(\"@\")[1]\n                    except Exception:\n                        raise Exception(\n                            \"The format for using a proxy server with auth \"\n                            'is: \"username:password@hostname:port\". If not '\n                            'using auth, the format is: \"hostname:port\".'\n                        )\n                if proxy_string.endswith(\":443\"):\n                    protocol = \"https\"\n                elif \"socks4\" in proxy_string:\n                    protocol = \"socks4\"\n                elif \"socks5\" in proxy_string:\n                    protocol = \"socks5\"\n                proxy_string = proxy_helper.validate_proxy_string(proxy_string)\n                if user_and_pass:\n                    proxy_string = \"%s@%s\" % (user_and_pass, proxy_string)\n                use_proxy = True\n                break\n    return (use_proxy, protocol, proxy_string)\n\n\ndef requests_get(url):\n    use_proxy, protocol, proxy_string = get_proxy_info()\n    proxies = None\n    response = None\n    if use_proxy:\n        proxies = {protocol: proxy_string}\n    try:\n        response = requests.get(url, proxies=proxies, timeout=1.25)\n    except Exception:\n        # Prevent SSLCertVerificationError / CERTIFICATE_VERIFY_FAILED\n        url = url.replace(\"https://\", \"http://\")\n        time.sleep(0.04)\n        response = requests.get(url, proxies=proxies, timeout=2.75)\n    return response\n\n\ndef requests_get_with_retry(url):\n    use_proxy, protocol, proxy_string = get_proxy_info()\n    proxies = None\n    response = None\n    if use_proxy:\n        proxies = {protocol: proxy_string}\n    try:\n        response = requests.get(url, proxies=proxies, timeout=1.35)\n    except Exception:\n        time.sleep(1)\n        try:\n            response = requests.get(url, proxies=proxies, timeout=2.45)\n        except Exception:\n            time.sleep(1)\n            response = requests.get(url, proxies=proxies, timeout=3.55)\n    return response\n\n\ndef get_cft_known_good_versions():\n    if getattr(sb_config, \"cft_kgv_json\", None):\n        return sb_config.cft_kgv_json\n    cft_ngv_url = (\n        \"https://googlechromelabs.github.io/\"\n        \"chrome-for-testing/known-good-versions.json\"\n    )\n    sb_config.cft_kgv_json = requests_get(cft_ngv_url)\n    return sb_config.cft_kgv_json\n\n\ndef get_cft_latest_versions_per_milestone():\n    if getattr(sb_config, \"cft_lvpm_json\", None):\n        return sb_config.cft_lvpm_json\n    cft_lvpm_url = (\n        \"https://googlechromelabs.github.io/\"\n        \"chrome-for-testing/latest-versions-per-milestone.json\"\n    )\n    sb_config.cft_lvpm_json = requests_get(cft_lvpm_url)\n    return sb_config.cft_lvpm_json\n\n\ndef get_cft_latest_version_from_milestone(milestone):\n    url_request = get_cft_latest_versions_per_milestone()\n    return url_request.json()[\"milestones\"][milestone][\"version\"]\n\n\ndef get_chromium_channel_revision(platform_code, channel):\n    \"\"\"Snapshots only exist for revisions where a build occurred.\n    Therefore, not all found revisions will lead to snapshots.\"\"\"\n    platform_key = None\n    if platform_code in [\"Mac_Arm\", \"Mac\"]:\n        platform_key = \"Mac\"\n    elif platform_code in [\"Linux_x64\"]:\n        platform_key = \"Linux\"\n    elif platform_code in [\"Win_x64\"]:\n        platform_key = \"Windows\"\n    elif platform_code in [\"Win\"]:\n        platform_key = \"Win32\"\n    channel_key = None\n    if channel.lower() == \"stable\":\n        channel_key = \"Stable\"\n    elif channel.lower() == \"beta\":\n        channel_key = \"Beta\"\n    elif channel.lower() == \"dev\":\n        channel_key = \"Dev\"\n    elif channel.lower() == \"canary\":\n        channel_key = \"Canary\"\n    base_url = \"https://chromiumdash.appspot.com/fetch_releases\"\n    url = f\"{base_url}?channel={channel_key}&platform={platform_key}&num=1\"\n    url_request = requests_get_with_retry(url)\n    data = None\n    if url_request.ok:\n        data = url_request.text\n    else:\n        raise Exception(\"Could not determine Chromium revision!\")\n    if data:\n        try:\n            import ast\n\n            result = ast.literal_eval(data)\n            revision = result[0][\"chromium_main_branch_position\"]\n            return str(revision)\n        except Exception:\n            return get_latest_chromedriver_version(platform_code)\n    else:\n        return get_latest_chromedriver_version(platform_code)\n\n\ndef get_chromium_latest_revision(platform_code):\n    base_url = \"https://storage.googleapis.com/chromium-browser-snapshots\"\n    url = f\"{base_url}/{platform_code}/LAST_CHANGE\"\n    url_request = requests_get_with_retry(url)\n    if url_request.ok:\n        latest_revision = url_request.text\n    else:\n        raise Exception(\"Could not determine latest Chromium revision!\")\n    return latest_revision\n\n\ndef get_latest_chromedriver_version(channel=\"Stable\"):\n    try:\n        if getattr(sb_config, \"cft_lkgv_json\", None):\n            return sb_config.cft_lkgv_json[\"channels\"][channel][\"version\"]\n        req = requests_get(\n            \"https://googlechromelabs.github.io/\"\n            \"chrome-for-testing/last-known-good-versions.json\"\n        )\n        if req and req.ok:\n            sb_config.cft_lkgv_json = req.json()\n            return req.json()[\"channels\"][channel][\"version\"]\n    except Exception:\n        pass\n    # If a problem with Chrome-for-Testing JSON API: Fall back\n    return DEFAULT_CHROMEDRIVER_VERSION\n\n\ndef get_latest_stable_chromedriver_version():\n    return get_latest_chromedriver_version(channel=\"Stable\")\n\n\ndef get_latest_beta_chromedriver_version():\n    return get_latest_chromedriver_version(channel=\"Beta\")\n\n\ndef get_latest_dev_chromedriver_version():\n    return get_latest_chromedriver_version(channel=\"Dev\")\n\n\ndef get_latest_canary_chromedriver_version():\n    return get_latest_chromedriver_version(channel=\"Canary\")\n\n\ndef log_d(message):\n    \"\"\"If setting sb_config.settings.HIDE_DRIVER_DOWNLOADS to True,\n    output from driver downloads are logged instead of printed.\"\"\"\n    if getattr(sb_config.settings, \"HIDE_DRIVER_DOWNLOADS\", None):\n        logging.debug(message)\n    else:\n        print(message)\n\n\ndef main(override=None, intel_for_uc=None, force_uc=None):\n    if override:\n        found_proxy = None\n        if getattr(sb_config, \"proxy_driver\", None):\n            if \" --proxy=\" in \" \".join(sys.argv):\n                for arg in sys.argv:\n                    if arg.startswith(\"--proxy=\"):\n                        found_proxy = arg\n                        break\n        if override == \"chromedriver\":\n            sys.argv = [\"seleniumbase\", \"get\", \"chromedriver\"]\n        elif override.startswith(\"chromedriver \"):\n            extra = override.split(\"chromedriver \")[1]\n            sys.argv = [\"seleniumbase\", \"get\", \"chromedriver\", extra]\n        elif override == \"edgedriver\":\n            sys.argv = [\"seleniumbase\", \"get\", \"edgedriver\"]\n        elif override.startswith(\"edgedriver \"):\n            extra = override.split(\"edgedriver \")[1]\n            sys.argv = [\"seleniumbase\", \"get\", \"edgedriver\", extra]\n        elif override == \"geckodriver\":\n            sys.argv = [\"seleniumbase\", \"get\", \"geckodriver\"]\n        elif override.startswith(\"geckodriver \"):\n            extra = override.split(\"geckodriver \")[1]\n            sys.argv = [\"seleniumbase\", \"get\", \"geckodriver\", extra]\n        elif override == \"iedriver\":\n            sys.argv = [\"seleniumbase\", \"get\", \"iedriver\"]\n        elif override.startswith(\"iedriver \"):\n            extra = override.split(\"iedriver \")[1]\n            sys.argv = [\"seleniumbase\", \"get\", \"iedriver\", extra]\n        elif override == \"chromium\":\n            sys.argv = [\"seleniumbase\", \"get\", \"chromium\"]\n        elif override.startswith(\"chromium \"):\n            extra = override.split(\"chromium \")[1]\n            sys.argv = [\"seleniumbase\", \"get\", \"chromium\", extra]\n        elif override == \"cft\":\n            sys.argv = [\"seleniumbase\", \"get\", \"cft\"]\n        elif override.startswith(\"cft \"):\n            extra = override.split(\"cft \")[1]\n            sys.argv = [\"seleniumbase\", \"get\", \"cft\", extra]\n        elif override == \"chs\":\n            sys.argv = [\"seleniumbase\", \"get\", \"chs\"]\n        elif override.startswith(\"chs \"):\n            extra = override.split(\"chs \")[1]\n            sys.argv = [\"seleniumbase\", \"get\", \"chs\", extra]\n        if found_proxy:\n            sys.argv.append(found_proxy)\n\n    num_args = len(sys.argv)\n    if (\n        \"sbase\" in sys.argv[0].lower()\n        or (\"seleniumbase\" in sys.argv[0].lower())\n    ):\n        if num_args < 3 or num_args > 5:\n            invalid_run_command()\n    else:\n        invalid_run_command()\n    name = sys.argv[2].lower()\n    if force_uc:\n        name = \"uc_driver\"\n\n    file_name = None\n    download_url = None\n    headless_ie_url = None\n    headless_ie_exists = False\n    headless_ie_file_name = None\n    downloads_folder = DRIVER_DIR\n    if (\n        hasattr(sb_config, \"settings\")\n        and getattr(sb_config.settings, \"NEW_DRIVER_DIR\", None)\n        and os.path.exists(sb_config.settings.NEW_DRIVER_DIR)\n    ):\n        downloads_folder = sb_config.settings.NEW_DRIVER_DIR\n    elif override == \"cft\" or name == \"cft\":\n        downloads_folder = DRIVER_DIR_CFT\n    elif override == \"chs\" or name == \"chs\":\n        downloads_folder = DRIVER_DIR_CHS\n    elif override == \"chromium\":\n        downloads_folder = DRIVER_DIR_CHROMIUM\n    expected_contents = None\n    platform_code = None\n    copy_to_path = False\n    latest_version = \"\"\n    use_version = \"\"\n    new_file = \"\"\n    f_name = \"\"\n    c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    c4 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTWHITE_EX\n    c5 = colorama.Fore.RED + colorama.Back.LIGHTWHITE_EX\n    c6 = colorama.Fore.LIGHTYELLOW_EX + colorama.Back.CYAN\n    cr = colorama.Style.RESET_ALL\n    if IS_LINUX:\n        c1 = \"\"\n        c2 = \"\"\n        c3 = \"\"\n        c4 = \"\"\n        c5 = \"\"\n        c6 = \"\"\n        cr = \"\"\n\n    if name == \"chromedriver\" or name == \"uc_driver\":\n        if name == \"uc_driver\" and IS_ARM_MAC:\n            intel_for_uc = True  # uc_driver is generated from chromedriver\n        last = \"https://chromedriver.storage.googleapis.com/LATEST_RELEASE\"\n        use_version = DEFAULT_CHROMEDRIVER_VERSION  # Until get correct VER\n\n        if (\n            not override\n            and (\n                num_args == 3\n                or (num_args == 4 and \"-p\" in sys.argv[3].lower())\n            )\n        ):\n            major_chrome_version = None\n            try:\n                from seleniumbase.core import detect_b_ver\n\n                br_app = \"google-chrome\"\n                major_chrome_version = (\n                    detect_b_ver.get_browser_version_from_os(br_app)\n                ).split(\".\")[0]\n                if int(major_chrome_version) < 72:\n                    major_chrome_version = None\n            except Exception:\n                major_chrome_version = None\n            if major_chrome_version and major_chrome_version.isnumeric():\n                num_args += 1\n                sys.argv.insert(3, major_chrome_version)\n\n        get_latest = False\n        get_v_latest = False\n        get_previous = False\n        get_beta = False\n        get_canary = False\n        if num_args == 4 or num_args == 5:\n            if \"-p\" not in sys.argv[3].lower():\n                use_version = sys.argv[3]\n                uv_low = use_version.lower()\n                if uv_low == \"latest\" or uv_low == \"stable\":\n                    uv_low = \"latest\"  # If \"stable\", rename\n                    get_latest = True\n                elif uv_low == \"latest-1\" or uv_low == \"previous\":\n                    uv_low = \"latest-1\"  # If \"previous\", rename\n                    get_previous = True\n                elif uv_low == \"beta\":\n                    get_beta = True\n                elif uv_low == \"dev\":\n                    use_version = get_latest_dev_chromedriver_version()\n                    sys.argv[3] = use_version\n                elif uv_low == \"canary\":\n                    get_canary = True\n                elif uv_low.isdigit() and int(uv_low) > 69:\n                    get_v_latest = True\n            else:\n                copy_to_path = True\n        if num_args == 5:\n            if \"-p\" in sys.argv[4].lower():\n                copy_to_path = True\n            else:\n                invalid_run_command()\n        if IS_MAC:\n            if IS_ARM_MAC and not intel_for_uc:\n                use_version = use_version.lower()\n                if (\n                    use_version == \"latest\"\n                    or use_version == \"stable\"\n                    or use_version == \"latest-1\"\n                    or use_version == \"previous\"\n                    or use_version == \"beta\"\n                    or use_version == \"canary\"\n                ):\n                    use_version = get_latest_stable_chromedriver_version()\n                if use_version == \"latest-1\" or use_version == \"previous\":\n                    use_version = str(int(use_version.split(\".\")[0]) - 1)\n                elif use_version == \"beta\":\n                    use_version = str(int(use_version.split(\".\")[0]) + 1)\n                elif use_version == \"canary\":\n                    use_version = str(int(use_version.split(\".\")[0]) + 2)\n            if (\n                IS_ARM_MAC\n                and not intel_for_uc\n                and int(use_version.split(\".\")[0]) > 105\n            ):\n                file_name = \"chromedriver_mac_arm64.zip\"\n            else:\n                file_name = \"chromedriver_mac64.zip\"\n        elif IS_LINUX:\n            file_name = \"chromedriver_linux64.zip\"\n        elif IS_WINDOWS:\n            file_name = \"chromedriver_win32.zip\"  # Works for win32 / win_x64\n            if not get_latest and not get_v_latest and num_args < 4:\n                get_latest = True\n        else:\n            raise Exception(\n                \"Cannot determine which version of chromedriver to download!\"\n            )\n        found_chromedriver = False\n        cft = False\n        if get_latest or get_previous or get_beta or get_canary:\n            use_version = get_latest_stable_chromedriver_version()\n            found_chromedriver = True\n            if get_previous and int(use_version.split(\".\")[0]) >= 115:\n                get_v_latest = True\n                use_version = str(int(use_version.split(\".\")[0]) - 1)\n            elif get_beta and int(use_version.split(\".\")[0]) >= 115:\n                get_v_latest = True\n                use_version = get_latest_beta_chromedriver_version()\n                use_version = use_version.split(\".\")[0]\n            elif get_canary and int(use_version.split(\".\")[0]) >= 115:\n                get_v_latest = True\n                use_version = get_latest_canary_chromedriver_version()\n                use_version = use_version.split(\".\")[0]\n        force_cft = False\n        if (\n            use_version.split(\".\")[0].isnumeric()\n            and int(use_version.split(\".\")[0]) >= 115\n        ):\n            force_cft = True\n        if (get_v_latest or force_cft):\n            if get_v_latest:\n                if not force_cft:\n                    url_req = requests_get(last)\n                    if url_req.ok:\n                        latest_version = url_req.text\n                else:\n                    latest_version = get_latest_stable_chromedriver_version()\n                force_cft = False\n            if not force_cft and int(use_version) < 115:\n                last = last + \"_\" + use_version\n                url_request = requests_get(last)\n                if url_request.ok:\n                    found_chromedriver = True\n                    use_version = url_request.text\n                    if use_version == latest_version:\n                        get_latest = True\n            else:\n                url_request = None\n                cft = True\n                if force_cft:\n                    url_request = get_cft_known_good_versions()\n                    if (\n                        url_request.ok\n                        and '\"version\":\"%s\"' % use_version in url_request.text\n                    ):\n                        fver = use_version\n                        found_chromedriver = True\n                else:\n                    url_request = get_cft_latest_versions_per_milestone()\n                if not force_cft and url_request.ok:\n                    try:\n                        fver = get_cft_latest_version_from_milestone(\n                            use_version\n                        )\n                    except KeyError:\n                        use_version = str(int(use_version) - 1)\n                        fver = get_cft_latest_version_from_milestone(\n                            use_version\n                        )\n                    found_chromedriver = True\n                    use_version = str(fver)\n                    if use_version == latest_version:\n                        get_latest = True\n        download_url = (\n            \"https://chromedriver.storage.googleapis.com/\"\n            \"%s/%s\" % (use_version, file_name)\n        )\n        plat_arch = \"\"\n        if cft:\n            if IS_MAC:\n                if (\n                    IS_ARM_MAC\n                    and not intel_for_uc\n                ):\n                    platform_code = \"mac-arm64\"\n                    file_name = \"chromedriver-mac-arm64.zip\"\n                else:\n                    platform_code = \"mac-x64\"\n                    file_name = \"chromedriver-mac-x64.zip\"\n            elif IS_LINUX:\n                platform_code = \"linux64\"\n                file_name = \"chromedriver-linux64.zip\"\n            elif IS_WINDOWS:\n                if \"64\" in ARCH:\n                    platform_code = \"win64\"\n                    file_name = \"chromedriver-win64.zip\"\n                else:\n                    platform_code = \"win32\"\n                    file_name = \"chromedriver-win32.zip\"\n            plat_arch = file_name.split(\".zip\")[0]\n            download_url = (\n                \"https://storage.googleapis.com/chrome-for-testing-public/\"\n                \"%s/%s/%s\" % (use_version, platform_code, file_name)\n            )\n        url_request = None\n        if not found_chromedriver:\n            url_req = requests_get(last)\n            if url_req.ok:\n                latest_version = url_req.text\n                if use_version == latest_version:\n                    get_latest = True\n            url_request = requests_get(download_url)\n        if found_chromedriver or url_request.ok:\n            p_version = use_version\n            p_version = c3 + use_version + cr\n            latest_stable = get_latest_stable_chromedriver_version()\n            latest_beta = get_latest_beta_chromedriver_version()\n            latest_dev = get_latest_dev_chromedriver_version()\n            latest_canary = get_latest_canary_chromedriver_version()\n            vint = True\n            int_use_ver = None\n            int_latest_ver = None\n            try:\n                int_use_ver = int(use_version.split(\".\")[0])\n                int_latest_ver = int(latest_stable.split(\".\")[0])\n            except Exception:\n                vint = False\n            on_cft = False\n            if int_latest_ver > 115:\n                on_cft = True\n            if cft and on_cft and use_version == latest_stable:\n                p_version = p_version + \" \" + c2 + \"(Latest Stable)\" + cr + \" \"\n            elif cft and on_cft and use_version == latest_beta:\n                p_version = p_version + \" \" + c2 + \"(Latest Beta)\" + cr + \" \"\n            elif cft and on_cft and use_version == latest_dev:\n                p_version = p_version + \" \" + c2 + \"(Latest Dev)\" + cr + \" \"\n            elif cft and on_cft and use_version == latest_canary:\n                p_version = p_version + \" \" + c2 + \"(Latest Canary)\" + cr + \" \"\n            elif not vint:\n                pass\n            elif vint and cft and on_cft and int_use_ver == int_latest_ver:\n                p_version = p_version + \" \" + c2 + \"(Stable)\" + cr\n            elif vint and cft and on_cft and int_use_ver == int_latest_ver + 1:\n                p_version = p_version + \" \" + c2 + \"(Beta)\" + cr\n            elif vint and cft and on_cft and int_use_ver == int_latest_ver + 2:\n                p_version = p_version + \" \" + c2 + \"(Dev / Canary)\" + cr\n            elif vint and cft and on_cft and int_use_ver == int_latest_ver - 1:\n                p_version = p_version + \" \" + c6 + \"(Previous Version)\" + cr\n            elif cft and not on_cft:\n                pass\n            else:\n                not_latest = c5 + \"(\" + c4 + \"Legacy Version\" + c5 + \")\" + cr\n                p_version = p_version + \" \" + not_latest\n            msg = c2 + \"chromedriver to download\" + cr\n            log_d(\"\\n*** %s = %s\" % (msg, p_version))\n        else:\n            raise Exception(\"Could not find chromedriver to download!\\n\")\n        if not get_latest:\n            pass\n    elif name == \"chrome\" or name == \"cft\":\n        set_version = None\n        found_version = None\n        use_version = None\n        major_version = None\n        if num_args >= 4:\n            set_version = sys.argv[3]\n        if (\n            set_version\n            and set_version.split(\".\")[0].isnumeric()\n            and int(set_version.split(\".\")[0]) >= 113\n        ):\n            major_version = set_version.split(\".\")[0]\n        elif (\n            not set_version\n            or set_version.lower() == \"latest\"\n            or set_version.lower() == \"stable\"\n        ):\n            found_version = get_latest_stable_chromedriver_version()\n        elif (\n            set_version and (\n                set_version.lower() == \"latest-1\"\n                or set_version.lower() == \"previous\"\n            )\n        ):\n            found_version = get_latest_stable_chromedriver_version()\n            major_version = str(int(found_version.split(\".\")[0]) - 1)\n            found_version = None\n        elif (set_version and set_version.lower() == \"beta\"):\n            found_version = get_latest_beta_chromedriver_version()\n        elif (set_version and set_version.lower() == \"dev\"):\n            found_version = get_latest_dev_chromedriver_version()\n        elif (set_version and set_version.lower() == \"canary\"):\n            found_version = get_latest_canary_chromedriver_version()\n        if found_version and found_version.split(\".\")[0].isnumeric():\n            major_version = found_version.split(\".\")[0]\n            use_version = found_version\n        if not use_version:\n            use_version = get_cft_latest_version_from_milestone(major_version)\n        msg = c2 + \"Chrome for Testing to download\" + cr\n        p_version = c3 + use_version + cr\n        log_d(\"\\n*** %s = %s\" % (msg, p_version))\n        if IS_MAC:\n            if IS_ARM_MAC:\n                platform_code = \"mac-arm64\"\n                file_name = \"chrome-mac-arm64.zip\"\n            else:\n                platform_code = \"mac-x64\"\n                file_name = \"chrome-mac-x64.zip\"\n        elif IS_LINUX:\n            platform_code = \"linux64\"\n            file_name = \"chrome-linux64.zip\"\n        elif IS_WINDOWS:\n            if \"64\" in ARCH:\n                platform_code = \"win64\"\n                file_name = \"chrome-win64.zip\"\n            else:\n                platform_code = \"win32\"\n                file_name = \"chrome-win32.zip\"\n        plat_arch = file_name.split(\".zip\")[0]\n        download_url = (\n            \"https://storage.googleapis.com/chrome-for-testing-public/\"\n            \"%s/%s/%s\" % (use_version, platform_code, file_name)\n        )\n    elif name == \"chromium\":\n        if IS_MAC:\n            if IS_ARM_MAC:\n                platform_code = \"Mac_Arm\"\n            else:\n                platform_code = \"Mac\"\n            file_name = \"chrome-mac.zip\"\n        elif IS_LINUX:\n            platform_code = \"Linux_x64\"\n            file_name = \"chrome-linux.zip\"\n        elif IS_WINDOWS:\n            if \"64\" in ARCH:\n                platform_code = \"Win_x64\"\n            else:\n                platform_code = \"Win\"\n            file_name = \"chrome-win.zip\"\n        revision = get_chromium_latest_revision(platform_code)\n        msg = c2 + \"Chromium revision to download\" + cr\n        p_version = c3 + revision + cr\n        log_d(\"\\n*** %s = %s\" % (msg, p_version))\n        download_url = (\n            \"https://storage.googleapis.com/chromium-browser-snapshots/\"\n            \"%s/%s/%s\" % (platform_code, revision, file_name)\n        )\n        downloads_folder = DRIVER_DIR_CHROMIUM\n    elif name == \"chrome-headless-shell\" or name == \"chs\":\n        set_version = None\n        found_version = None\n        use_version = None\n        major_version = None\n        if num_args >= 4:\n            set_version = sys.argv[3]\n        if (\n            set_version\n            and set_version.split(\".\")[0].isnumeric()\n            and int(set_version.split(\".\")[0]) >= 113\n        ):\n            major_version = set_version.split(\".\")[0]\n        elif (\n            not set_version\n            or set_version.lower() == \"latest\"\n            or set_version.lower() == \"stable\"\n        ):\n            found_version = get_latest_stable_chromedriver_version()\n        elif (\n            set_version and (\n                set_version.lower() == \"latest-1\"\n                or set_version.lower() == \"previous\"\n            )\n        ):\n            found_version = get_latest_stable_chromedriver_version()\n            major_version = str(int(found_version.split(\".\")[0]) - 1)\n            found_version = None\n        elif (set_version and set_version.lower() == \"beta\"):\n            found_version = get_latest_beta_chromedriver_version()\n        elif (set_version and set_version.lower() == \"dev\"):\n            found_version = get_latest_dev_chromedriver_version()\n        elif (set_version and set_version.lower() == \"canary\"):\n            found_version = get_latest_canary_chromedriver_version()\n        if found_version and found_version.split(\".\")[0].isnumeric():\n            major_version = found_version.split(\".\")[0]\n            use_version = found_version\n        if not use_version:\n            use_version = get_cft_latest_version_from_milestone(major_version)\n        msg = c2 + \"Chrome-Headless-Shell to download\" + cr\n        p_version = c3 + use_version + cr\n        log_d(\"\\n*** %s = %s\" % (msg, p_version))\n        if IS_MAC:\n            if IS_ARM_MAC:\n                platform_code = \"mac-arm64\"\n                file_name = \"chrome-headless-shell-mac-arm64.zip\"\n            else:\n                platform_code = \"mac-x64\"\n                file_name = \"chrome-headless-shell-mac-x64.zip\"\n        elif IS_LINUX:\n            platform_code = \"linux64\"\n            file_name = \"chrome-headless-shell-linux64.zip\"\n        elif IS_WINDOWS:\n            if \"64\" in ARCH:\n                platform_code = \"win64\"\n                file_name = \"chrome-headless-shell-win64.zip\"\n            else:\n                platform_code = \"win32\"\n                file_name = \"chrome-headless-shell-win32.zip\"\n        plat_arch = file_name.split(\".zip\")[0]\n        download_url = (\n            \"https://storage.googleapis.com/chrome-for-testing-public/\"\n            \"%s/%s/%s\" % (use_version, platform_code, file_name)\n        )\n    elif name == \"geckodriver\" or name == \"firefoxdriver\":\n        use_version = DEFAULT_GECKODRIVER_VERSION\n        found_geckodriver = False\n        if num_args == 4 or num_args == 5:\n            if \"-p\" not in sys.argv[3].lower():\n                use_version = sys.argv[3]\n                if use_version.lower() == \"latest\":\n                    last = (\n                        \"https://api.github.com/repos/\"\n                        \"mozilla/geckodriver/releases/latest\"\n                    )\n                    url_request = requests_get(last)\n                    if url_request.ok:\n                        found_geckodriver = True\n                        use_version = url_request.json()[\"tag_name\"]\n                    else:\n                        use_version = DEFAULT_GECKODRIVER_VERSION\n            else:\n                copy_to_path = True\n        if num_args == 5:\n            if \"-p\" in sys.argv[4].lower():\n                copy_to_path = True\n            else:\n                invalid_run_command()\n        if IS_MAC:\n            if IS_ARM_MAC:\n                file_name = \"geckodriver-%s-macos-aarch64.tar.gz\" % use_version\n            else:\n                file_name = \"geckodriver-%s-macos.tar.gz\" % use_version\n        elif IS_LINUX:\n            if \"64\" in ARCH:\n                if \"aarch64\" in platform.processor():\n                    file_name = (\n                        \"geckodriver-%s-linux-aarch64.tar.gz\" % use_version\n                    )\n                else:\n                    file_name = \"geckodriver-%s-linux64.tar.gz\" % use_version\n            else:\n                file_name = \"geckodriver-%s-linux32.tar.gz\" % use_version\n        elif IS_WINDOWS:\n            file_name = \"geckodriver-%s-win64.zip\" % use_version\n        else:\n            raise Exception(\n                \"Cannot determine which version of geckodriver to download!\"\n            )\n        download_url = (\n            \"https://github.com/mozilla/geckodriver/\"\n            \"releases/download/\"\n            \"%s/%s\" % (use_version, file_name)\n        )\n        url_request = None\n        if not found_geckodriver:\n            url_request = requests_get(download_url)\n        if found_geckodriver or url_request.ok:\n            msg = c2 + \"geckodriver to download\" + cr\n            p_version = c3 + use_version + cr\n            log_d(\"\\n*** %s = %s\" % (msg, p_version))\n        else:\n            raise Exception(\n                \"\\nCould not find the specified geckodriver \"\n                \"version to download!\\n\"\n            )\n    elif name == \"edgedriver\" or name == \"msedgedriver\":\n        name = \"edgedriver\"\n        last = (\n            \"https://msedgewebdriverstorage.blob.core.windows.net\"\n            \"/edgewebdriver/LATEST_STABLE\"\n        )\n\n        if (\n            not override\n            and (\n                num_args == 3\n                or (num_args == 4 and \"-p\" in sys.argv[3].lower())\n            )\n        ):\n            use_version = \"latest\"\n            major_edge_version = None\n            try:\n                from seleniumbase.core import detect_b_ver\n\n                br_app = \"edge\"\n                major_edge_version = (\n                    detect_b_ver.get_browser_version_from_os(br_app)\n                ).split(\".\")[0]\n                if int(major_edge_version) < 80:\n                    major_edge_version = None\n            except Exception:\n                major_edge_version = None\n            if major_edge_version and major_edge_version.isnumeric():\n                num_args += 1\n                sys.argv.insert(3, major_edge_version)\n                use_version = major_edge_version\n\n        get_latest = False\n        if num_args == 3:\n            get_latest = True\n        if num_args == 4 and \"-p\" in sys.argv[3].lower():\n            get_latest = True\n        if num_args == 4 or num_args == 5:\n            if \"-p\" not in sys.argv[3].lower():\n                use_version = sys.argv[3]\n                if use_version.lower() == \"latest\":\n                    use_version = DEFAULT_EDGEDRIVER_VERSION\n                    get_latest = True\n            else:\n                copy_to_path = True\n        if num_args == 5:\n            if \"-p\" in sys.argv[4].lower():\n                copy_to_path = True\n            else:\n                invalid_run_command()\n        if get_latest:\n            url_request = requests_get_with_retry(last)\n            if url_request.ok:\n                use_version = url_request.text.split(\"\\r\")[0].split(\"\\n\")[0]\n                use_version = use_version.split(\".\")[0]\n            else:\n                use_version = DEFAULT_EDGEDRIVER_VERSION\n        suffix = None\n        if IS_WINDOWS and \"64\" in ARCH:\n            file_name = \"edgedriver_win64.zip\"\n            suffix = \"WINDOWS\"\n        elif IS_WINDOWS:\n            file_name = \"edgedriver_win32.zip\"\n            suffix = \"WINDOWS\"\n        elif IS_MAC:\n            if IS_ARM_MAC and int(use_version.split(\".\")[0]) > 104:\n                file_name = \"edgedriver_mac64_m1.zip\"\n            else:\n                file_name = \"edgedriver_mac64.zip\"\n            suffix = \"MACOS\"\n        elif IS_LINUX:\n            file_name = \"edgedriver_linux64.zip\"\n            suffix = \"LINUX\"\n        else:\n            raise Exception(\n                \"Cannot determine which version of EdgeDriver to download!\"\n            )\n        if use_version.isdigit():\n            edgedriver_st = \"https://msedgedriver.microsoft.com/LATEST_RELEASE\"\n            use_version = \"%s_%s_%s\" % (edgedriver_st, use_version, suffix)\n            url_request = requests_get_with_retry(use_version)\n            if url_request.ok:\n                use_version = url_request.text.split(\"\\r\")[0].split(\"\\n\")[0]\n                if (\n                    int(use_version.split(\".\")[0]) == 115\n                    and use_version.startswith(\"115.0\")\n                    and use_version != \"115.0.1901.183\"\n                ):\n                    use_version = \"115.0.1901.183\"\n        download_url = \"https://msedgedriver.microsoft.com/%s/%s\" % (\n            use_version,\n            file_name,\n        )\n        if not get_latest and not use_version == DEFAULT_EDGEDRIVER_VERSION:\n            url_request = requests_get_with_retry(download_url)\n            if not url_request.ok:\n                raise Exception(\n                    \"Could not find version [%s] of EdgeDriver!\" % use_version\n                )\n        msg = c2 + \"edgedriver to download\" + cr\n        p_version = c3 + use_version + cr\n        log_d(\"\\n*** %s = %s\" % (msg, p_version))\n    elif name == \"iedriver\":\n        full_version = \"4.14.0\"\n        use_version = full_version\n        if IS_WINDOWS and \"64\" in ARCH:\n            file_name = \"IEDriverServer_x64_%s.zip\" % full_version\n        elif IS_WINDOWS:\n            file_name = \"IEDriverServer_Win32_%s.zip\" % full_version\n        else:\n            raise Exception(\n                \"Sorry! IEDriver is only for \"\n                \"Windows-based systems!\"\n            )\n        download_url = (\n            \"https://github.com/SeleniumHQ/selenium/\"\n            \"releases/download/selenium-\"\n            \"%s/%s\" % (full_version, file_name)\n        )\n        headless_ie_version = \"v1.4\"\n        headless_ie_file_name = \"headless-selenium-for-win-v1-4.zip\"\n        headless_ie_url = (\n            \"https://github.com/kybu/headless-selenium-for-win/\"\n            \"releases/download/\"\n            \"%s/%s\" % (headless_ie_version, headless_ie_file_name)\n        )\n        url_request = requests_get_with_retry(headless_ie_url)\n        if url_request.ok:\n            headless_ie_exists = True\n            msg = c2 + \"HeadlessIEDriver to download\" + cr\n            p_version = c3 + headless_ie_version + cr\n            log_d(\"\\n*** %s = %s\" % (msg, p_version))\n    else:\n        invalid_run_command()\n\n    if file_name is None or download_url is None:\n        invalid_run_command()\n\n    file_path = os.path.join(downloads_folder, file_name)\n    if not os.path.exists(downloads_folder):\n        os.makedirs(downloads_folder)\n\n    driver_name = None  # The name of the driver executable\n    driver_contents = []  # The contents of the driver zip file\n\n    if headless_ie_exists:\n        headless_ie_file_path = os.path.join(\n            downloads_folder, headless_ie_file_name\n        )\n        log_d(\n            \"\\nDownloading %s from:\\n%s ...\"\n            % (headless_ie_file_name, headless_ie_url)\n        )\n        remote_file = requests_get_with_retry(headless_ie_url)\n        with open(headless_ie_file_path, \"wb\") as file:\n            file.write(remote_file.content)\n        log_d(\"%sDownload Complete!%s\\n\" % (c1, cr))\n        zip_file_path = headless_ie_file_path\n        zip_ref = zipfile.ZipFile(zip_file_path, \"r\")\n        contents = zip_ref.namelist()\n        h_ie_fn = headless_ie_file_name.split(\".zip\")[0]\n        expected_contents = [\n            \"%s/\" % h_ie_fn,\n            \"%s/ruby_example/\" % h_ie_fn,\n            \"%s/ruby_example/Gemfile\" % h_ie_fn,\n            \"%s/ruby_example/Gemfile.lock\" % h_ie_fn,\n            \"%s/ruby_example/ruby_example.rb\" % h_ie_fn,\n            \"%s/desktop_utils.exe\" % h_ie_fn,\n            \"%s/headless_ie_selenium.exe\" % h_ie_fn,\n            \"%s/README.md\" % h_ie_fn,\n        ]\n        if len(contents) > 8:\n            raise Exception(\"Unexpected content in HeadlessIEDriver Zip file!\")\n        for content in contents:\n            if content not in expected_contents:\n                raise Exception(\n                    \"Expected file [%s] missing from [%s]\"\n                    % (content, expected_contents)\n                )\n        # Zip file is valid. Proceed.\n        driver_path = None\n        driver_file = None\n        filename = None\n        for f_name in contents:\n            # Remove existing version if exists\n            str_name = str(f_name)\n            new_file = os.path.join(downloads_folder, str_name)\n            if str_name == \"%s/headless_ie_selenium.exe\" % h_ie_fn:\n                driver_file = str_name\n                driver_path = new_file\n                filename = \"headless_ie_selenium.exe\"\n                if os.path.exists(new_file):\n                    os.remove(new_file)\n        if not driver_file or not driver_path or not filename:\n            raise Exception(\"headless_ie_selenium.exe missing from Zip file!\")\n        log_d(\"Extracting %s from %s ...\" % (filename, headless_ie_file_name))\n        zip_ref.extractall(downloads_folder)\n        zip_ref.close()\n        os.remove(zip_file_path)\n        shutil.copy2(driver_path, os.path.join(downloads_folder, filename))\n        log_d(\"%sUnzip Complete!%s\\n\" % (c2, cr))\n        to_remove = [\n            \"%s/%s/ruby_example/Gemfile\" % (downloads_folder, h_ie_fn),\n            \"%s/%s/ruby_example/Gemfile.lock\" % (downloads_folder, h_ie_fn),\n            \"%s/%s/ruby_example/ruby_example.rb\" % (downloads_folder, h_ie_fn),\n            \"%s/%s/desktop_utils.exe\" % (downloads_folder, h_ie_fn),\n            \"%s/%s/headless_ie_selenium.exe\" % (downloads_folder, h_ie_fn),\n            \"%s/%s/README.md\" % (downloads_folder, h_ie_fn),\n        ]\n        for file_to_remove in to_remove:\n            if os.path.exists(file_to_remove):\n                os.remove(file_to_remove)\n        if os.path.exists(\"%s/%s/ruby_example/\" % (downloads_folder, h_ie_fn)):\n            # Only works if the directory is empty\n            os.rmdir(\"%s/%s/ruby_example/\" % (downloads_folder, h_ie_fn))\n        if os.path.exists(os.path.join(downloads_folder, h_ie_fn)):\n            # Only works if the directory is empty\n            os.rmdir(os.path.join(downloads_folder, h_ie_fn))\n        driver_path = os.path.join(downloads_folder, filename)\n        log_d(\n            \"The file [%s] was saved to:\\n%s%s%s\\n\"\n            % (filename, c3, driver_path, cr)\n        )\n        log_d(\"Making [%s %s] executable ...\" % (driver_file, use_version))\n        make_executable(driver_path)\n        log_d(\n            \"%s[%s %s] is now ready for use!%s\"\n            % (c1, driver_file, use_version, cr)\n        )\n\n    log_d(\"\\nDownloading %s from:\\n%s ...\" % (file_name, download_url))\n    remote_file = requests_get_with_retry(download_url)\n    with open(file_path, \"wb\") as file:\n        file.write(remote_file.content)\n\n    if file_name.endswith(\".zip\"):\n        zip_file_path = file_path\n        zip_ref = zipfile.ZipFile(zip_file_path, \"r\")\n        contents = zip_ref.namelist()\n        log_d(\"%sDownload Complete!%s\\n\" % (c1, cr))\n        if (\n            len(contents) >= 1\n            and name in [\"chromedriver\", \"uc_driver\", \"geckodriver\"]\n        ):\n            for f_name in contents:\n                if (\n                    (name == \"chromedriver\" or name == \"uc_driver\")\n                    and (\n                        f_name.split(\"/\")[-1] == \"chromedriver\"\n                        or f_name.split(\"/\")[-1] == \"chromedriver.exe\"\n                    )\n                ):\n                    driver_name = f_name.split(\"/\")[-1]\n                    driver_contents = [driver_name]\n                # Remove existing version if exists\n                new_file = os.path.join(downloads_folder, str(f_name))\n                if name == \"uc_driver\":\n                    if new_file.endswith(\"drivers/chromedriver\"):\n                        new_file = new_file.replace(\n                            \"drivers/chromedriver\", \"drivers/uc_driver\"\n                        )\n                    elif new_file.endswith(\"drivers/chromedriver.exe\"):\n                        new_file = new_file.replace(\n                            \"drivers/chromedriver.exe\", \"drivers/uc_driver.exe\"\n                        )\n                    elif \"drivers/%s/chromedriver\" % plat_arch in new_file:\n                        new_file = new_file.replace(\n                            \"drivers/%s/chromedriver\" % plat_arch,\n                            \"drivers/%s/uc_driver\" % plat_arch\n                        )\n                    elif \"drivers/%s/chromedriver.exe\" % plat_arch in new_file:\n                        new_file = new_file.replace(\n                            \"drivers/%s/chromedriver.exe\" % plat_arch,\n                            \"drivers/%s/uc_driver.exe\" % plat_arch\n                        )\n                if \"Driver\" in new_file or \"driver\" in new_file:\n                    if os.path.exists(new_file):\n                        os.remove(new_file)  # Technically the old file now\n            if driver_contents:\n                contents = driver_contents\n            log_d(\"Extracting %s from %s ...\" % (contents, file_name))\n            if name == \"uc_driver\":\n                f_name = \"uc_driver\"\n                new_file = os.path.join(downloads_folder, f_name)\n                if os.path.exists(new_file):\n                    os.remove(new_file)\n                zipinfos = zip_ref.infolist()\n                for zipinfo in zipinfos:\n                    if zipinfo.filename.split(\"/\")[-1] == \"chromedriver\":\n                        zipinfo.filename = \"uc_driver\"\n                        zip_ref.extract(zipinfo, downloads_folder)\n                    elif zipinfo.filename.split(\"/\")[-1] == \"chromedriver.exe\":\n                        zipinfo.filename = \"uc_driver.exe\"\n                        zip_ref.extract(zipinfo, downloads_folder)\n                contents = zip_ref.namelist()\n                if driver_contents:\n                    contents = driver_contents\n            elif name == \"chromedriver\" or name == \"uc_driver\":\n                zipinfos = zip_ref.infolist()\n                for zipinfo in zipinfos:\n                    if zipinfo.filename.split(\"/\")[-1] == \"chromedriver\":\n                        zipinfo.filename = \"chromedriver\"\n                    elif zipinfo.filename.split(\"/\")[-1] == (\n                        \"chromedriver.exe\"\n                    ):\n                        zipinfo.filename = \"chromedriver.exe\"\n                    if (\n                        zipinfo.filename.split(\"/\")[-1] == \"chromedriver\"\n                        or zipinfo.filename.split(\"/\")[-1] == (\n                            \"chromedriver.exe\"\n                        )\n                    ):\n                        zip_ref.extract(zipinfo, downloads_folder)\n                contents = zip_ref.namelist()\n                if driver_contents:\n                    contents = driver_contents\n            else:\n                zip_ref.extractall(downloads_folder)\n            zip_ref.close()\n            os.remove(zip_file_path)\n            log_d(\"%sUnzip Complete!%s\\n\" % (c2, cr))\n            for f_name in contents:\n                if name == \"uc_driver\":\n                    if IS_WINDOWS:\n                        f_name = \"uc_driver.exe\"\n                    else:\n                        f_name = \"uc_driver\"\n                new_file = os.path.join(downloads_folder, str(f_name))\n                pr_file = c3 + new_file + cr\n                d_folder = os.sep.join(pr_file.split(os.sep)[:-1]) + os.sep\n                d_file = pr_file.split(os.sep)[-1]\n                d_ff = c3 + d_folder + cr + \"\\n\" + c3 + d_file + cr\n                log_d(\"The file [%s] was saved to:\\n%s\\n\" % (f_name, d_ff))\n                log_d(\"Making [%s %s] executable ...\" % (f_name, use_version))\n                make_executable(new_file)\n                log_d(\n                    \"%s[%s %s] is now ready for use!%s\" %\n                    (c1, f_name, use_version, cr)\n                )\n                if copy_to_path and os.path.exists(LOCAL_PATH):\n                    path_file = LOCAL_PATH + f_name\n                    shutil.copy2(new_file, path_file)\n                    make_executable(path_file)\n                    log_d(\"Also copied to: %s%s%s\" % (c3, path_file, cr))\n            log_d(\"\")\n        elif (\n            name == \"edgedriver\"\n            or name == \"msedgedriver\"\n            or name == \"iedriver\"\n        ):\n            if IS_MAC or IS_LINUX:\n                # Mac / Linux\n                expected_contents = [\n                    \"Driver_Notes/\",\n                    \"Driver_Notes/EULA\",\n                    \"Driver_Notes/LICENSE\",\n                    \"Driver_Notes/credits.html\",\n                    \"msedgedriver\",\n                    \"libc++.dylib\",\n                ]\n            else:\n                # Windows\n                expected_contents = [\n                    \"Driver_Notes/\",\n                    \"Driver_Notes/credits.html\",\n                    \"Driver_Notes/EULA\",\n                    \"Driver_Notes/LICENSE\",\n                    \"msedgedriver.exe\",\n                ]\n            if name == \"iedriver\":\n                expected_contents = [\"IEDriverServer.exe\"]\n            if len(contents) > 5:\n                raise Exception(\"Unexpected content in EdgeDriver Zip file!\")\n            for content in contents:\n                if content not in expected_contents:\n                    raise Exception(\n                        \"Expected file [%s] missing from [%s]\"\n                        % (content, expected_contents)\n                    )\n            # Zip file is valid. Proceed.\n            driver_path = None\n            driver_file = None\n            for f_name in contents:\n                # Remove existing version if exists\n                str_name = str(f_name)\n                new_file = os.path.join(downloads_folder, str_name)\n                if (\n                    ((IS_MAC or IS_LINUX) and str_name == \"msedgedriver\")\n                    or (\n                        str_name == \"msedgedriver.exe\"\n                        or str_name == \"IEDriverServer.exe\"\n                    )\n                ):\n                    driver_file = str_name\n                    driver_path = new_file\n                    if os.path.exists(new_file):\n                        os.remove(new_file)\n            if not driver_file or not driver_path:\n                if str_name == \"IEDriverServer.exe\":\n                    raise Exception(\"IEDriverServer missing from Zip file!\")\n                raise Exception(\"msedgedriver missing from Zip file!\")\n            log_d(\"Extracting %s from %s ...\" % (contents, file_name))\n            zip_ref.extractall(downloads_folder)\n            zip_ref.close()\n            os.remove(zip_file_path)\n            log_d(\"%sUnzip Complete!%s\\n\" % (c2, cr))\n            to_remove = [\n                \"%s/Driver_Notes/credits.html\" % downloads_folder,\n                \"%s/Driver_Notes/EULA\" % downloads_folder,\n                \"%s/Driver_Notes/LICENSE\" % downloads_folder,\n            ]\n            for file_to_remove in to_remove:\n                if os.path.exists(file_to_remove):\n                    os.remove(file_to_remove)\n            if os.path.exists(os.path.join(downloads_folder, \"Driver_Notes/\")):\n                # Only works if the directory is empty\n                os.rmdir(os.path.join(downloads_folder, \"Driver_Notes/\"))\n            driver_base = os.sep.join(driver_path.split(os.sep)[:-1])\n            driver_file = driver_path.split(os.sep)[-1]\n            pr_driver_base = c3 + driver_base + cr\n            pr_sep = c3 + os.sep + cr\n            pr_driver_file = c3 + driver_file + cr\n            log_d(\n                \"The file [%s] was saved to:\\n%s%s\\n%s\\n\"\n                % (driver_file, pr_driver_base, pr_sep, pr_driver_file)\n            )\n            log_d(\"Making [%s %s] executable ...\" % (driver_file, use_version))\n            make_executable(driver_path)\n            log_d(\n                \"%s[%s %s] is now ready for use!%s\"\n                % (c1, driver_file, use_version, cr)\n            )\n            if copy_to_path and os.path.exists(LOCAL_PATH):\n                path_file = LOCAL_PATH + f_name\n                shutil.copy2(new_file, path_file)\n                make_executable(path_file)\n                log_d(\"Also copied to: %s%s%s\" % (c3, path_file, cr))\n            log_d(\"\")\n        elif name == \"chrome\" or name == \"cft\":\n            # Zip file is valid. Proceed.\n            driver_path = None\n            driver_file = None\n            base_path = os.sep.join(zip_file_path.split(os.sep)[:-1])\n            folder_name = contents[0].split(\"/\")[0]\n            folder_path = os.path.join(base_path, folder_name)\n            if IS_MAC or IS_LINUX:\n                if (\n                    \"chrome-\" in folder_path\n                    and \"drivers\" in folder_path\n                    and os.path.exists(folder_path)\n                ):\n                    shutil.rmtree(folder_path)\n                subprocess.run(\n                    [\"unzip\", zip_file_path, \"-d\", downloads_folder]\n                )\n            elif IS_WINDOWS:\n                subprocess.run(\n                    [\n                        \"powershell\",\n                        \"Expand-Archive\",\n                        \"-Path\",\n                        zip_file_path,\n                        \"-DestinationPath\",\n                        downloads_folder,\n                        \"-Force\",\n                    ]\n                )\n            else:\n                zip_ref.extractall(downloads_folder)\n                zip_ref.close()\n            with suppress(Exception):\n                os.remove(zip_file_path)\n            log_d(\"%sUnzip Complete!%s\\n\" % (c2, cr))\n            pr_base_path = c3 + base_path + cr\n            pr_sep = c3 + os.sep + cr\n            pr_folder_name = c3 + folder_name + cr\n            log_d(\n                \"Chrome for Testing was saved inside:\\n%s%s\\n%s\\n\"\n                % (pr_base_path, pr_sep, pr_folder_name)\n            )\n        elif name == \"chromium\":\n            # Zip file is valid. Proceed.\n            driver_path = None\n            driver_file = None\n            base_path = os.sep.join(zip_file_path.split(os.sep)[:-1])\n            folder_name = contents[0].split(\"/\")[0]\n            folder_path = os.path.join(base_path, folder_name)\n            if IS_MAC or IS_LINUX:\n                if (\n                    \"chromium\" in folder_path\n                    and \"drivers\" in folder_path\n                    and os.path.exists(folder_path)\n                ):\n                    shutil.rmtree(folder_path)\n                subprocess.run(\n                    [\"unzip\", zip_file_path, \"-d\", downloads_folder]\n                )\n            elif IS_WINDOWS:\n                subprocess.run(\n                    [\n                        \"powershell\",\n                        \"Expand-Archive\",\n                        \"-Path\",\n                        zip_file_path,\n                        \"-DestinationPath\",\n                        downloads_folder,\n                        \"-Force\",\n                    ]\n                )\n            else:\n                zip_ref.extractall(downloads_folder)\n                zip_ref.close()\n            with suppress(Exception):\n                os.remove(zip_file_path)\n            log_d(\"%sUnzip Complete!%s\\n\" % (c2, cr))\n            pr_base_path = c3 + base_path + cr\n            pr_sep = c3 + os.sep + cr\n            pr_folder_name = c3 + folder_name + cr\n            log_d(\n                \"Chromium was saved inside:\\n%s%s\\n%s\\n\"\n                % (pr_base_path, pr_sep, pr_folder_name)\n            )\n        elif name == \"chrome-headless-shell\" or name == \"chs\":\n            # Zip file is valid. Proceed.\n            driver_path = None\n            driver_file = None\n            base_path = os.sep.join(zip_file_path.split(os.sep)[:-1])\n            folder_name = contents[0].split(\"/\")[0]\n            folder_path = os.path.join(base_path, folder_name)\n            if IS_MAC or IS_LINUX:\n                if (\n                    \"chrome-headless-shell-\" in folder_path\n                    and \"drivers\" in folder_path\n                    and os.path.exists(folder_path)\n                ):\n                    shutil.rmtree(folder_path)\n                subprocess.run(\n                    [\"unzip\", zip_file_path, \"-d\", downloads_folder]\n                )\n            elif IS_WINDOWS:\n                subprocess.run(\n                    [\n                        \"powershell\",\n                        \"Expand-Archive\",\n                        \"-Path\",\n                        zip_file_path,\n                        \"-DestinationPath\",\n                        downloads_folder,\n                        \"-Force\",\n                    ]\n                )\n            else:\n                zip_ref.extractall(downloads_folder)\n                zip_ref.close()\n            with suppress(Exception):\n                os.remove(zip_file_path)\n            log_d(\"%sUnzip Complete!%s\\n\" % (c2, cr))\n            pr_base_path = c3 + base_path + cr\n            pr_sep = c3 + os.sep + cr\n            pr_folder_name = c3 + folder_name + cr\n            log_d(\n                \"Chrome-Headless-Shell was saved inside:\\n%s%s\\n%s\\n\"\n                % (pr_base_path, pr_sep, pr_folder_name)\n            )\n        elif len(contents) == 0:\n            raise Exception(\"Zip file %s is empty!\" % zip_file_path)\n        else:\n            raise Exception(\"Expecting only one file in %s!\" % zip_file_path)\n    elif file_name.endswith(\".tar.gz\"):\n        tar_file_path = file_path\n        tar = tarfile.open(file_path)\n        contents = tar.getnames()\n        if len(contents) == 1:\n            log_d(\"%sDownload Complete!%s\\n\" % (c1, cr))\n            for f_name in contents:\n                # Remove existing version if exists\n                new_file = os.path.join(downloads_folder, str(f_name))\n                if \"Driver\" in new_file or \"driver\" in new_file:\n                    if os.path.exists(new_file):\n                        os.remove(new_file)  # Technically the old file now\n            log_d(\"Extracting %s from %s ...\" % (contents, file_name))\n            if sys.version_info < (3, 12):\n                tar.extractall(downloads_folder)\n            else:\n                tar.extractall(downloads_folder, filter=\"fully_trusted\")\n            tar.close()\n            os.remove(tar_file_path)\n            log_d(\"%sUnzip Complete!%s\\n\" % (c2, cr))\n            for f_name in contents:\n                new_file = os.path.join(downloads_folder, str(f_name))\n                pr_file = c3 + new_file + cr\n                log_d(\"The file [%s] was saved to:\\n%s\\n\" % (f_name, pr_file))\n                log_d(\"Making [%s %s] executable ...\" % (f_name, use_version))\n                make_executable(new_file)\n                log_d(\n                    \"%s[%s %s] is now ready for use!%s\"\n                    % (c1, f_name, use_version, cr)\n                )\n                if copy_to_path and os.path.exists(LOCAL_PATH):\n                    path_file = LOCAL_PATH + f_name\n                    shutil.copy2(new_file, path_file)\n                    make_executable(path_file)\n                    log_d(\"Also copied to: %s%s%s\" % (c3, path_file, cr))\n            log_d(\"\")\n        elif len(contents) == 0:\n            raise Exception(\"Tar file %s is empty!\" % tar_file_path)\n        else:\n            raise Exception(\"Expecting only one file in %s!\" % tar_file_path)\n    else:\n        # Not a .zip file or a .tar.gz file. Just a direct download.\n        if \"Driver\" in file_name or \"driver\" in file_name:\n            log_d(\"%sDownload Complete!%s\\n\" % (c1, cr))\n            log_d(\"Making [%s] executable ...\" % file_name)\n            make_executable(file_path)\n            log_d(\"%s[%s] is now ready for use!%s\" % (c1, file_name, cr))\n            log_d(\"Location of [%s]:\\n%s\\n\" % (file_name, file_path))\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_mkchart.py",
    "content": "\"\"\"\nCreates a new SeleniumBase chart presentation with boilerplate code.\n\nUsage:\n    seleniumbase mkchart [FILE.py] [LANG]\n    or     sbase mkchart [FILE.py] [LANG]\n\nExample:\n    sbase mkchart new_chart.py --en\n\nLanguage Options:\n    --en / --English    |    --zh / --Chinese\n    --nl / --Dutch      |    --fr / --French\n    --it / --Italian    |    --ja / --Japanese\n    --ko / --Korean     |    --pt / --Portuguese\n    --ru / --Russian    |    --es / --Spanish\n\nOutput:\n    Creates a new SeleniumBase chart presentation.\n    If the file already exists, an error is raised.\n    By default, the slides are written in English,\n    and use a \"sky\" theme with \"slide\" transition.\n    The chart can be used as a basic boilerplate.\n\"\"\"\nimport colorama\nimport os\nimport sys\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** mkchart **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase mkchart [FILE.py] [LANG]\\n\"\n    exp += \"     OR     sbase mkchart [FILE.py] [LANG]\\n\"\n    exp += \"  Example:\\n\"\n    exp += \"     sbase mkchart new_chart.py --en\\n\"\n    exp += \"  Language Options:\\n\"\n    exp += \"     --en / --English    |    --zh / --Chinese\\n\"\n    exp += \"     --nl / --Dutch      |    --fr / --French\\n\"\n    exp += \"     --it / --Italian    |    --ja / --Japanese\\n\"\n    exp += \"     --ko / --Korean     |    --pt / --Portuguese\\n\"\n    exp += \"     --ru / --Russian    |    --es / --Spanish\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Creates a new SeleniumBase chart presentation.\\n\"\n    exp += \"     If the file already exists, an error is raised.\\n\"\n    exp += \"     By default, the slides are written in English,\\n\"\n    exp += '     and use a \"sky\" theme with \"slide\" transition.\\n'\n    exp += \"     The chart can be used as a basic boilerplate.\\n\"\n    if not msg:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n    elif msg == \"help\":\n        print(\"\\n%s\" % exp)\n        sys.exit()\n    else:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\\n%s\\n\" % (exp, msg))\n\n\ndef main():\n    c1 = \"\"\n    c5 = \"\"\n    c7 = \"\"\n    cr = \"\"\n    if \"linux\" not in sys.platform:\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n        cr = colorama.Style.RESET_ALL\n\n    help_me = False\n    error_msg = None\n    invalid_cmd = None\n    language = \"English\"\n\n    command_args = sys.argv[2:]\n    file_name = command_args[0]\n    if file_name == \"-h\" or file_name == \"--help\":\n        invalid_run_command(\"help\")\n    elif not file_name.endswith(\".py\"):\n        error_msg = 'File name must end with \".py\"!'\n    elif \"*\" in file_name or len(str(file_name)) < 4:\n        error_msg = \"Invalid file name!\"\n    elif file_name.startswith(\"-\"):\n        error_msg = 'File name cannot start with \"-\"!'\n    elif \"/\" in str(file_name) or \"\\\\\" in str(file_name):\n        error_msg = \"File must be created in the current directory!\"\n    elif os.path.exists(os.getcwd() + \"/\" + file_name):\n        error_msg = 'File \"%s\" already exists in this directory!' % file_name\n    if error_msg:\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        invalid_run_command(error_msg)\n\n    if len(command_args) >= 2:\n        options = command_args[1:]\n        for option in options:\n            option = option.lower()\n            if option == \"-h\" or option == \"--help\":\n                help_me = True\n            elif option == \"--en\" or option == \"--english\":\n                language = \"English\"\n            elif option == \"--zh\" or option == \"--chinese\":\n                language = \"Chinese\"\n            elif option == \"--nl\" or option == \"--dutch\":\n                language = \"Dutch\"\n            elif option == \"--fr\" or option == \"--french\":\n                language = \"French\"\n            elif option == \"--it\" or option == \"--italian\":\n                language = \"Italian\"\n            elif option == \"--ja\" or option == \"--japanese\":\n                language = \"Japanese\"\n            elif option == \"--ko\" or option == \"--korean\":\n                language = \"Korean\"\n            elif option == \"--pt\" or option == \"--portuguese\":\n                language = \"Portuguese\"\n            elif option == \"--ru\" or option == \"--russian\":\n                language = \"Russian\"\n            elif option == \"--es\" or option == \"--spanish\":\n                language = \"Spanish\"\n            else:\n                invalid_cmd = \"\\n===> INVALID OPTION: >> %s <<\\n\" % option\n                invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n                invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n                invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n                invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n                help_me = True\n                break\n    if help_me:\n        invalid_run_command(invalid_cmd)\n\n    dir_name = os.getcwd()\n    file_path = \"%s/%s\" % (dir_name, file_name)\n    html_name = file_name.replace(\".py\", \".html\")\n    class_name = \"MyTestClass\"\n    item = \"Item\"\n    select_option = \"Select option\"\n    chart_options = '\"pie\", \"bar\", \"column\", \"line\", \"area\"'\n\n    if language == \"Chinese\":\n        class_name = \"我的测试类\"\n        item = \"目的\"\n        select_option = \"选择选项\"\n        chart_options = '\"饼图\", \"条形图\", \"柱形图\", \"折线图\", \"面积图\"'\n    elif language == \"Dutch\":\n        class_name = \"MijnTestklasse\"\n        item = \"Voorwerp\"\n        select_option = \"Optie selecteren\"\n        chart_options = '\"cirkel\", \"staaf\", \"kolom\", \"lijn\", \"vlak\"'\n    elif language == \"French\":\n        class_name = \"MaClasseDeTest\"\n        item = \"Objet\"\n        select_option = \"Sélectionner option\"\n        chart_options = '\"secteurs\" \"barres\" \"colonnes\" \"linéaire\" \"aires\"'\n    elif language == \"Italian\":\n        class_name = \"MiaClasseDiTest\"\n        item = \"Oggetto\"\n        select_option = \"Selezionare opzione\"\n        chart_options = '\"torta\", \"barre\", \"colonne\", \"linee\", \"area\"'\n    elif language == \"Japanese\":\n        class_name = \"私のテストクラス\"\n        item = \"物体\"\n        select_option = \"でオプションを選択\"\n        chart_options = '\"円\", \"棒\", \"縦棒\", \"折れ線\", \"面\"'\n    elif language == \"Korean\":\n        class_name = \"테스트_클래스\"\n        item = \"물체\"\n        select_option = \"옵션 선택\"\n        chart_options = '\"원형\", \"막대\", \"열\", \"선\", \"영역\"'\n    elif language == \"Portuguese\":\n        class_name = \"MinhaClasseDeTeste\"\n        item = \"Objeto\"\n        select_option = \"Selecionar opção\"\n        chart_options = '\"pizza\", \"barras\", \"colunas\", \"linhas\", \"área\"'\n    elif language == \"Russian\":\n        class_name = \"МойТестовыйКласс\"\n        item = \"Вещь\"\n        select_option = \"Выбрать опцию\"\n        chart_options = '\"круговую\" \"бар\" \"столбчатую\" \"линейную\" \"области\"'\n    elif language == \"Spanish\":\n        class_name = \"MiClaseDePrueba\"\n        item = \"Objeto\"\n        select_option = \"Seleccionar opción\"\n        chart_options = '\"circular\", \"barras\", \"columnas\", \"líneas\", \"área\"'\n\n    import_line = \"from seleniumbase import BaseCase\"\n    main_line = \"BaseCase.main(__name__, __file__)\"\n    parent_class = \"BaseCase\"\n    if language != \"English\":\n        from seleniumbase.translate.master_dict import MD_F\n\n        import_line = MD_F.get_import_line(language)\n        parent_class = MD_F.get_lang_parent_class(language)\n    class_line = \"class %s(%s):\" % (class_name, parent_class)\n    settings = 'theme=\"sky\", transition=\"slide\"'\n    chart_settings = 'title=\"Chart 1\"'\n    add_slide = '\"<p>Chart Demo</p>\" + self.extract_chart()'\n    data = []\n    data.append(\"%s\" % import_line)\n    data.append(\"%s\" % main_line)\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"%s\" % class_line)\n    data.append(\"    def test_chart_presentation(self):\")\n    data.append(\"        self.create_presentation(%s)\" % settings)\n    data.append(\"\")\n    data.append(\"        # %s => %s\" % (select_option, chart_options))\n    data.append(\"        self.create_pie_chart(%s)\" % chart_settings)\n    data.append('        self.add_data_point(\"%s A\", 36)' % item)\n    data.append('        self.add_data_point(\"%s B\", 33)' % item)\n    data.append('        self.add_data_point(\"%s C\", 27)' % item)\n    data.append('        self.add_data_point(\"%s D\", 21)' % item)\n    data.append('        self.add_data_point(\"%s E\", 18)' % item)\n    data.append('        self.add_data_point(\"%s F\", 15)' % item)\n    data.append(\"        self.add_slide(%s)\" % add_slide)\n    data.append(\"\")\n    data.append('        self.begin_presentation(filename=\"%s\")' % html_name)\n    data.append(\"\")\n\n    new_data = []\n    if language == \"English\":\n        new_data = data\n    else:\n        from seleniumbase.translate.master_dict import MD\n        from seleniumbase.translate.master_dict import MD_L_Codes\n\n        md = MD.md\n        lang_codes = MD_L_Codes.lang\n        nl_code = lang_codes[language]\n        dl_code = lang_codes[\"English\"]\n        for line in data:\n            found_swap = False\n            replace_count = line.count(\"self.\")  # Total possible replacements\n            for key in md.keys():\n                original = \"self.\" + md[key][dl_code] + \"(\"\n                if original in line:\n                    replacement = \"self.\" + md[key][nl_code] + \"(\"\n                    new_line = line.replace(original, replacement)\n                    found_swap = True\n                    replace_count -= 1\n                    if replace_count == 0:\n                        break  # Done making replacements\n                    else:\n                        # There might be another method to replace in the line.\n                        # Example: self.assert_true(\"Name\" in self.get_title())\n                        line = new_line\n                        continue\n            if main_line in line:\n                new_main = \"%s.main(__name__, __file__)\" % parent_class\n                new_line = line.replace(main_line, new_main)\n                found_swap = True\n            if found_swap:\n                if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                    new_line = new_line[0 : -len(\"  # noqa\")]\n                new_data.append(new_line)\n                continue\n            new_data.append(line)\n    data = new_data\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n    if \" \" not in file_name:\n        os.system(\"sbase print %s -n\" % file_name)\n    elif '\"' not in file_name:\n        os.system('sbase print \"%s\" -n' % file_name)\n    else:\n        os.system(\"sbase print '%s' -n\" % file_name)\n    success = (\n        \"\\n\" + c1 + '* Chart Presentation: \"' + file_name + '\" was created! *'\n        \"\" + cr + \"\\n\"\n    )\n    print(success)\n\n\nif __name__ == \"__main__\":\n    invalid_run_command()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_mkdir.py",
    "content": "\"\"\"\nCreates a new folder for running SeleniumBase scripts.\n\nUsage:\n    seleniumbase mkdir [DIRECTORY] [OPTIONS]\n    OR     sbase mkdir [DIRECTORY] [OPTIONS]\n\nExample:\n    sbase mkdir ui_tests\n\nOptions:\n    -b / --basic  (Only config files. No tests added.)\n    --gha  (Include GitHub Actions YML with defaults.)\n\nOutput:\n    Creates a new folder for running SBase scripts.\n    The new folder contains default config files,\n    sample tests for helping new users get started,\n    and Python boilerplates for setting up customized\n    test frameworks.\n\"\"\"\nimport colorama\nimport os\nimport sys\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** mkdir **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase mkdir [DIRECTORY] [OPTIONS]\\n\"\n    exp += \"     OR     sbase mkdir [DIRECTORY] [OPTIONS]\\n\"\n    exp += \"  Example:\\n\"\n    exp += \"     sbase mkdir ui_tests\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     -b / --basic  (Only config files. No tests added.)\\n\"\n    exp += \"     --gha  (Include GitHub Actions YML with defaults.)\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Creates a new folder for running SBase scripts.\\n\"\n    exp += \"     The new folder contains default config files,\\n\"\n    exp += \"     sample tests for helping new users get started,\\n\"\n    exp += \"     and Python boilerplates for setting up customized\\n\"\n    exp += \"     test frameworks.\\n\"\n    if not msg:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n    elif msg == \"help\":\n        print(\"\\n%s\" % exp)\n        sys.exit()\n    else:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\\n%s\\n\" % (exp, msg))\n\n\ndef main():\n    c1 = \"\"\n    c5 = \"\"\n    c7 = \"\"\n    cr = \"\"\n    if \"linux\" not in sys.platform:\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n        cr = colorama.Style.RESET_ALL\n\n    gha = False\n    basic = False\n    help_me = False\n    error_msg = None\n    invalid_cmd = None\n\n    command_args = sys.argv[2:]\n    dir_name = command_args[0]\n    if dir_name == \"-h\" or dir_name == \"--help\":\n        invalid_run_command(\"help\")\n    elif len(str(dir_name)) < 2:\n        error_msg = \"Directory name length must be at least 2 characters long!\"\n    elif \"/\" in str(dir_name) or \"\\\\\" in str(dir_name):\n        error_msg = 'Directory name must not include slashes (\"/\", \"\\\\\")!'\n    elif dir_name.startswith(\"-\"):\n        error_msg = 'Directory name cannot start with \"-\"!'\n    elif os.path.exists(os.getcwd() + \"/\" + dir_name):\n        error_msg = (\n            'Directory \"%s\" already exists in this directory!' % dir_name\n        )\n    if error_msg:\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        invalid_run_command(error_msg)\n\n    if len(command_args) >= 2:\n        options = command_args[1:]\n        for option in options:\n            option = option.lower()\n            if option == \"-h\" or option == \"--help\":\n                help_me = True\n            elif option == \"-b\" or option == \"--basic\":\n                basic = True\n            elif option == \"--gha\":\n                gha = True\n            else:\n                invalid_cmd = \"\\n===> INVALID OPTION: >> %s <<\\n\" % option\n                invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n                invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n                invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n                invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n                help_me = True\n                break\n    if help_me:\n        invalid_run_command(invalid_cmd)\n\n    os.mkdir(dir_name)\n\n    data = []\n    seleniumbase_req = \"seleniumbase\"\n    try:\n        from seleniumbase import __version__\n\n        seleniumbase_req = \"seleniumbase>=%s\" % str(__version__)\n    except Exception:\n        pass\n    data.append(seleniumbase_req)\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name, \"requirements.txt\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"[pytest]\")\n    data.append(\"addopts = --capture=tee-sys -p no:cacheprovider\")\n    data.append(\"norecursedirs = .* build dist recordings temp assets\")\n    data.append(\"filterwarnings =\")\n    data.append(\"    ignore::pytest.PytestWarning\")\n    data.append(\"    ignore:.*U.*mode is deprecated:DeprecationWarning\")\n    data.append(\"junit_family = legacy\")\n    data.append(\"python_files = test_*.py *_test.py *_tests.py *_suite.py\")\n    data.append(\"python_classes = Test* *Test* *Test *Tests *Suite\")\n    data.append(\"python_functions = test_*\")\n    data.append(\"markers =\")\n    data.append(\"    marker1: custom marker\")\n    data.append(\"    marker2: custom marker\")\n    data.append(\"    marker3: custom marker\")\n    data.append(\"    marker_test_suite: custom marker\")\n    data.append(\"    expected_failure: custom marker\")\n    data.append(\"    local: custom marker\")\n    data.append(\"    remote: custom marker\")\n    data.append(\"    offline: custom marker\")\n    data.append(\"    develop: custom marker\")\n    data.append(\"    qa: custom marker\")\n    data.append(\"    ci: custom marker\")\n    data.append(\"    e2e: custom marker\")\n    data.append(\"    ready: custom marker\")\n    data.append(\"    smoke: custom marker\")\n    data.append(\"    deploy: custom marker\")\n    data.append(\"    active: custom marker\")\n    data.append(\"    master: custom marker\")\n    data.append(\"    release: custom marker\")\n    data.append(\"    staging: custom marker\")\n    data.append(\"    production: custom marker\")\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name, \"pytest.ini\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"[flake8]\")\n    data.append(\"exclude=recordings,temp\")\n    data.append(\"ignore=W503\")\n    data.append(\"\")\n    data.append(\"[nosetests]\")\n    data.append(\"nocapture=1\")\n    data.append(\"logging-level=INFO\")\n    data.append(\"\")\n    data.append(\"[behave]\")\n    data.append(\"show_skipped=false\")\n    data.append(\"show_timings=false\")\n    file_path = \"%s/%s\" % (dir_name, \"setup.cfg\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name, \"__init__.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"*.py[cod]\")\n    data.append(\"*.egg\")\n    data.append(\"*.egg-info\")\n    data.append(\"dist\")\n    data.append(\"build\")\n    data.append(\".eggs\")\n    data.append(\"eggs\")\n    data.append(\"parts\")\n    data.append(\"bin\")\n    data.append(\"var\")\n    data.append(\"sdist\")\n    data.append(\"develop-eggs\")\n    data.append(\".installed.cfg\")\n    data.append(\"lib\")\n    data.append(\"lib64\")\n    data.append(\"__pycache__\")\n    data.append(\".env\")\n    data.append(\".venv\")\n    data.append(\"env/\")\n    data.append(\"venv/\")\n    data.append(\"ENV/\")\n    data.append(\"VENV/\")\n    data.append(\"env.bak/\")\n    data.append(\"venv.bak/\")\n    data.append(\".sbase\")\n    data.append(\".sbase*\")\n    data.append(\"seleniumbase_env\")\n    data.append(\"seleniumbase_venv\")\n    data.append(\"sbase_env\")\n    data.append(\"sbase_venv\")\n    data.append(\"pyvenv.cfg\")\n    data.append(\".Python\")\n    data.append(\"include\")\n    data.append(\"pip-delete-this-directory.txt\")\n    data.append(\"pip-selfcheck.json\")\n    data.append(\"ipython.1.gz\")\n    data.append(\"nosetests.1\")\n    data.append(\".noseids\")\n    data.append(\"pip-log.txt\")\n    data.append(\".swp\")\n    data.append(\".coverage\")\n    data.append(\".tox\")\n    data.append(\"coverage.xml\")\n    data.append(\"nosetests.xml\")\n    data.append(\".cache/*\")\n    data.append(\".pytest_cache/*\")\n    data.append(\".pytest_config\")\n    data.append(\"junit\")\n    data.append(\"test-results.xml\")\n    data.append(\".idea\")\n    data.append(\".project\")\n    data.append(\".pydevproject\")\n    data.append(\".vscode\")\n    data.append(\"chromedriver\")\n    data.append(\"geckodriver\")\n    data.append(\"msedgedriver\")\n    data.append(\"operadriver\")\n    data.append(\"uc_driver\")\n    data.append(\"MicrosoftWebDriver.exe\")\n    data.append(\"headless_ie_selenium.exe\")\n    data.append(\"IEDriverServer.exe\")\n    data.append(\"chromedriver.exe\")\n    data.append(\"geckodriver.exe\")\n    data.append(\"msedgedriver.exe\")\n    data.append(\"operadriver.exe\")\n    data.append(\"uc_driver.exe\")\n    data.append(\"chrome-mac.zip\")\n    data.append(\"chrome-linux.zip\")\n    data.append(\"chrome-win.zip\")\n    data.append(\"chrome-mac\")\n    data.append(\"chrome-linux\")\n    data.append(\"chrome-win\")\n    data.append(\"chrome-mac-arm64.zip\")\n    data.append(\"chrome-mac-x64.zip\")\n    data.append(\"chrome-linux64.zip\")\n    data.append(\"chrome-win64.zip\")\n    data.append(\"chrome-win32.zip\")\n    data.append(\"chrome-mac-arm64\")\n    data.append(\"chrome-mac-x64\")\n    data.append(\"chrome-linux64\")\n    data.append(\"chrome-win64\")\n    data.append(\"chrome-win32\")\n    data.append(\"chrome-headless-shell-mac-arm64.zip\")\n    data.append(\"chrome-headless-shell-mac-x64.zip\")\n    data.append(\"chrome-headless-shell-linux64.zip\")\n    data.append(\"chrome-headless-shell-win64.zip\")\n    data.append(\"chrome-headless-shell-win32.zip\")\n    data.append(\"chrome-headless-shell-mac-arm64\")\n    data.append(\"chrome-headless-shell-mac-x64\")\n    data.append(\"chrome-headless-shell-linux64\")\n    data.append(\"chrome-headless-shell-win64\")\n    data.append(\"chrome-headless-shell-win32\")\n    data.append(\"libc++.dylib\")\n    data.append(\"logs\")\n    data.append(\"latest_logs\")\n    data.append(\"log_archives\")\n    data.append(\"archived_logs\")\n    data.append(\"geckodriver.log\")\n    data.append(\"ghostdriver.log\")\n    data.append(\"pytestdebug.log\")\n    data.append(\"reports/*.xml\")\n    data.append(\"latest_report\")\n    data.append(\"report_archives\")\n    data.append(\"archived_reports\")\n    data.append(\"html_report.html\")\n    data.append(\"last_report.html\")\n    data.append(\"report.html\")\n    data.append(\"report.xml\")\n    data.append(\"dashboard.html\")\n    data.append(\"dashboard.json\")\n    data.append(\"dash_pie.json\")\n    data.append(\"dashboard.lock\")\n    data.append(\"allure_report\")\n    data.append(\"allure-report\")\n    data.append(\"allure_results\")\n    data.append(\"allure-results\")\n    data.append(\"saved_charts\")\n    data.append(\"saved_presentations\")\n    data.append(\"tours_exported\")\n    data.append(\"images_exported\")\n    data.append(\"saved_cookies\")\n    data.append(\"recordings\")\n    data.append(\"visual_baseline\")\n    data.append(\".DS_Store\")\n    data.append(\"selenium-server-standalone.jar\")\n    data.append(\"proxy.zip\")\n    data.append(\"proxy.lock\")\n    data.append(\"verbose_hub_server.dat\")\n    data.append(\"verbose_node_server.dat\")\n    data.append(\"ip_of_grid_hub.dat\")\n    data.append(\"downloaded_files\")\n    data.append(\"archived_files\")\n    data.append(\"assets\")\n    data.append(\"temp\")\n    data.append(\"temp_*/\")\n    data.append(\"node_modules\")\n    file_path = \"%s/%s\" % (dir_name, \".gitignore\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    if gha:\n        dir_name_b = dir_name + \"/\" + \".github\"\n        os.mkdir(dir_name_b)\n        dir_name_c = dir_name_b + \"/\" + \"workflows\"\n        os.mkdir(dir_name_c)\n\n        data = []\n        data.append(\"name: CI build\")\n        data.append(\"on:\")\n        data.append(\"  push:\")\n        data.append(\"    branches:\")\n        data.append(\"  pull_request:\")\n        data.append(\"    branches:\")\n        data.append(\"  workflow_dispatch:\")\n        data.append(\"    branches:\")\n        data.append(\"jobs:\")\n        data.append(\"  build:\")\n        data.append(\"    env:\")\n        data.append('      PY_COLORS: \"1\"')\n        data.append(\"    strategy:\")\n        data.append(\"      fail-fast: false\")\n        data.append(\"      max-parallel: 15\")\n        data.append(\"      matrix:\")\n        data.append(\"        os: [ubuntu-latest]\")\n        data.append('        python-version: [\"3.x\"]')\n        data.append(\"    runs-on: ${{ matrix.os }}\")\n        data.append(\"    steps:\")\n        data.append(\"    - uses: actions/checkout@v6\")\n        data.append(\"    - name: Set up Python ${{ matrix.python-version }}\")\n        data.append(\"      uses: actions/setup-python@v6\")\n        data.append(\"      with:\")\n        data.append(\"        python-version: ${{ matrix.python-version }}\")\n        data.append(\"    - name: Install dependencies\")\n        data.append(\"      run: |\")\n        data.append(\"        python -m pip install --upgrade pip\")\n        data.append(\"        pip install -r requirements.txt\")\n        data.append(\"    - name: Install Chrome\")\n        data.append(\"      if: matrix.os == 'ubuntu-latest'\")\n        data.append(\"      run: sudo apt install google-chrome-stable\")\n        data.append(\"    - name: Download chromedriver\")\n        data.append(\"      run: sbase get chromedriver\")\n        data.append(\"    - name: Run pytest\")\n        data.append(\"      run: pytest -v -s --rs --crumbs --reruns=1\")\n        data.append(\"    - name: Upload artifacts\")\n        data.append(\"      if: ${{ always() }}\")\n        data.append(\"      uses: actions/upload-artifact@v6\")\n        data.append(\"      with:\")\n        data.append(\"        name: seleniumbase-artifacts\")\n        data.append(\"        path: ./latest_logs/\")\n        data.append(\"        if-no-files-found: ignore\")\n        data.append(\"\")\n        file_path = \"%s/%s\" % (dir_name_c, \"python-package.yml\")\n        file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        file.writelines(\"\\r\\n\".join(data))\n        file.close()\n\n    if basic:\n        data = []\n        data.append(\"  %s/\" % dir_name)\n        if gha:\n            data.append(\"  ├── .github\")\n            data.append(\"  │   └── workflows/\")\n            data.append(\"  │      └── python-package.yml\")\n        data.append(\"  ├── __init__.py\")\n        data.append(\"  ├── pytest.ini\")\n        data.append(\"  ├── requirements.txt\")\n        data.append(\"  └── setup.cfg\")\n        file_path = \"%s/%s\" % (dir_name, \"outline.rst\")\n        file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        file.writelines(\"\\r\\n\".join(data))\n        file.close()\n        os.system(\"sbase print %s -n\" % file_path)\n        os.remove(file_path)\n        success = (\n            \"\\n\" + c1 + '* Directory \"' + dir_name + '\" was created '\n            \"with config files! *\" + cr + \"\\n\"\n        )\n        print(success)\n        return\n\n    data = []\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"BaseCase.main(__name__, __file__)\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class MyTestClass(BaseCase):\")\n    data.append(\"    def test_swag_labs(self):\")\n    data.append('        self.open(\"https://www.saucedemo.com\")')\n    data.append('        self.type(\"#user-name\", \"standard_user\")')\n    data.append('        self.type(\"#password\", \"secret_sauce\\\\n\")')\n    data.append('        self.assert_element(\"div.inventory_list\")')\n    data.append('        self.assert_text(\"Products\", \"span.title\")')\n    data.append(\"        self.click('button[name*=\\\"backpack\\\"]')\")\n    data.append('        self.click(\"#shopping_cart_container a\")')\n    data.append('        self.assert_text(\"Your Cart\", \"span.title\")')\n    data.append('        self.assert_text(\"Backpack\", \"div.cart_item\")')\n    data.append('        self.click(\"button#checkout\")')\n    data.append('        self.type(\"#first-name\", \"SeleniumBase\")')\n    data.append('        self.type(\"#last-name\", \"Automation\")')\n    data.append('        self.type(\"#postal-code\", \"77123\")')\n    data.append('        self.click(\"input#continue\")')\n    data.append('        self.assert_text(\"Checkout: Overview\")')\n    data.append('        self.assert_text(\"Backpack\", \"div.cart_item\")')\n    data.append('        self.click(\"button#finish\")')\n    data.append(\n        '        self.assert_exact_text(\"Thank you for your order!\", \"h2\")'\n    )\n    data.append(\"        self.assert_element('img[alt=\\\"Pony Express\\\"]')\")\n    data.append('        self.js_click(\"a#logout_sidebar_link\")')\n    data.append('        self.assert_element(\"div#login_button_container\")')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name, \"my_first_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"BaseCase.main(__name__, __file__)\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class DemoSiteTests(BaseCase):\")\n    data.append(\"    def test_demo_site(self):\")\n    data.append('        self.open(\"https://seleniumbase.io/demo_page.html\")')\n    data.append('        self.assert_title(\"Web Testing Page\")')\n    data.append('        self.assert_element(\"tbody#tbodyId\")')\n    data.append('        self.assert_text(\"Demo Page\", \"h1\")')\n    data.append('        self.type(\"#myTextInput\", \"This is Automated\")')\n    data.append('        self.type(\"textarea.area1\", \"Testing Time!\\\\n\")')\n    data.append('        self.type(\\'[name=\"preText2\"]\\', \"Typing Text!\")')\n    data.append(\n        '        self.assert_text(\"This is Automated\", \"#myTextInput\")'\n    )\n    data.append(\n        '        self.assert_text(\"Testing Time!\\\\n\", \"textarea.area1\")'\n    )\n    data.append(\n        '        self.assert_text(\"Typing Text!\", \\'[name=\"preText2\"]\\')'\n    )\n    data.append('        self.assert_text(\"Automation Practice\", \"h3\")')\n    data.append(\n        '        self.hover_and_js_click(\"#myDropdown\", \"#dropOption2\")'\n    )\n    data.append('        self.assert_text(\"Link Two Selected\", \"h3\")')\n    data.append('        self.assert_text(\"This Text is Green\", \"#pText\")')\n    data.append('        self.click(\"#myButton\")')\n    data.append('        self.assert_text(\"This Text is Purple\", \"#pText\")')\n    data.append(\"        self.assert_element('svg[name=\\\"svgName\\\"]')\")\n    data.append(\"        self.assert_element('progress[value=\\\"50\\\"]')\")\n    data.append('        self.press_right_arrow(\"#myslider\", times=5)')\n    data.append(\"        self.assert_element('progress[value=\\\"100\\\"]')\")\n    data.append(\"        self.assert_element('meter[value=\\\"0.25\\\"]')\")\n    data.append(\n        '        self.select_option_by_text(\"#mySelect\", \"Set to 75%\")'\n    )\n    data.append(\"        self.assert_element('meter[value=\\\"0.75\\\"]')\")\n    data.append('        self.assert_false(self.is_element_visible(\"img\"))')\n    data.append('        self.switch_to_frame(\"#myFrame1\")')\n    data.append('        self.assert_true(self.is_element_visible(\"img\"))')\n    data.append(\"        self.switch_to_default_content()\")\n    data.append(\n        '        self.assert_false(self.is_text_visible(\"iFrame Text\"))'\n    )\n    data.append('        self.switch_to_frame(\"#myFrame2\")')\n    data.append(\n        '        self.assert_true(self.is_text_visible(\"iFrame Text\"))'\n    )\n    data.append(\"        self.switch_to_default_content()\")\n    data.append('        self.assert_false(self.is_selected(\"#radioButton2\"))')\n    data.append('        self.click(\"#radioButton2\")')\n    data.append('        self.assert_true(self.is_selected(\"#radioButton2\"))')\n    data.append('        self.assert_false(self.is_selected(\"#checkBox1\"))')\n    data.append('        self.click(\"#checkBox1\")')\n    data.append('        self.assert_true(self.is_selected(\"#checkBox1\"))')\n    data.append('        self.assert_false(self.is_selected(\"#checkBox2\"))')\n    data.append('        self.assert_false(self.is_selected(\"#checkBox3\"))')\n    data.append('        self.assert_false(self.is_selected(\"#checkBox4\"))')\n    data.append('        self.click_visible_elements(\"input.checkBoxClassB\")')\n    data.append('        self.assert_true(self.is_selected(\"#checkBox2\"))')\n    data.append('        self.assert_true(self.is_selected(\"#checkBox3\"))')\n    data.append('        self.assert_true(self.is_selected(\"#checkBox4\"))')\n    data.append('        self.assert_false(self.is_element_visible(\".fBox\"))')\n    data.append('        self.switch_to_frame(\"#myFrame3\")')\n    data.append('        self.assert_true(self.is_element_visible(\".fBox\"))')\n    data.append('        self.assert_false(self.is_selected(\".fBox\"))')\n    data.append('        self.click(\".fBox\")')\n    data.append('        self.assert_true(self.is_selected(\".fBox\"))')\n    data.append(\"        self.switch_to_default_content()\")\n    data.append(\n        '        self.assert_element_not_visible(\"div#drop2 img#logo\")'\n    )\n    data.append('        self.drag_and_drop(\"img#logo\", \"div#drop2\")')\n    data.append('        self.assert_element(\"div#drop2 img#logo\")')\n    data.append('        self.assert_link_text(\"seleniumbase.com\")')\n    data.append('        self.assert_link_text(\"SeleniumBase on GitHub\")')\n    data.append('        self.assert_link_text(\"seleniumbase.io\")')\n    data.append('        self.click_link(\"SeleniumBase Demo Page\")')\n    data.append('        self.assert_exact_text(\"Demo Page\", \"h1\")')\n    data.append('        self.highlight(\"h2\")')\n    data.append('        if self.headed:')\n    data.append(\"            self.activate_demo_mode()\")\n    data.append('        self.type(\"input\", \"Have a Nice Day!\")')\n    data.append('        self.assert_text(\"SeleniumBase\", \"h2\")')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name, \"test_demo_site.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"from parameterized import parameterized\")\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"BaseCase.main(__name__, __file__)\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class SearchTests(BaseCase):\")\n    data.append(\"    @parameterized.expand(\")\n    data.append(\"        [\")\n    data.append(\n        \"            \"\n        '[\"SeleniumBase Commander\", \"Commander\", \"GUI / Commander\"],'\n    )\n    data.append(\n        '            [\"SeleniumBase Recorder\", \"Recorder\", \"Recorder Mode\"],'\n    )\n    data.append(\n        '            [\"SeleniumBase Syntax\", \"Syntax\", \"Syntax Formats\"],'\n    )\n    data.append(\"        ]\")\n    data.append(\"    )\")\n    data.append(\n        \"    def test_parameterized_search(\"\n        \"self, search_term, keyword, title_text):\"\n    )\n    data.append(\n        '        self.open(\"https://seleniumbase.io/help_docs/how_it_works/\")'\n    )\n    data.append(\n        '        self.type(\\'[aria-label=\"Search\"]\\', search_term)'\n    )\n    data.append('        self.click(\\'mark:contains(\"%s\")\\' % keyword)')\n    data.append('        self.assert_title_contains(title_text)')\n    data.append('        self.save_screenshot_to_logs()')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name, \"parameterized_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    dir_name_2 = dir_name + \"/\" + \"boilerplates\"\n    os.mkdir(dir_name_2)\n\n    data = []\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_2, \"__init__.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"BaseCase.main(__name__, __file__)\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class BaseTestCase(BaseCase):\")\n    data.append(\"    def setUp(self):\")\n    data.append(\"        super().setUp()\")\n    data.append(\"        # <<< Run custom code AFTER the super() line >>>\")\n    data.append(\"\")\n    data.append(\"    def tearDown(self):\")\n    data.append(\"        self.save_teardown_screenshot()\")\n    data.append(\"        if self.has_exception():\")\n    data.append(\"            # <<< Run custom code if the test failed. >>>\")\n    data.append(\"            pass\")\n    data.append(\"        else:\")\n    data.append(\"            # <<< Run custom code if the test passed. >>>\")\n    data.append(\"            pass\")\n    data.append(\"        # (Wrap unreliable code in a try/except block.)\")\n    data.append(\"        # <<< Run custom code BEFORE the super() line >>>\")\n    data.append(\"        super().tearDown()\")\n    data.append(\"\")\n    data.append(\"    def login(self):\")\n    data.append(\"        # <<< Placeholder. Add your code here. >>>\")\n    data.append(\"        pass\")\n    data.append(\"\")\n    data.append(\"    def example_method(self):\")\n    data.append(\"        # <<< Placeholder. Add your code here. >>>\")\n    data.append(\"        pass\")\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_2, \"base_test_case.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"class Page(object):\")\n    data.append('    html = \"html\"')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_2, \"page_objects.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"from .base_test_case import BaseTestCase\")\n    data.append(\"from .page_objects import Page\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class MyTestClass(BaseTestCase):\")\n    data.append(\"    def test_boilerplate(self):\")\n    data.append(\"        self.login()\")\n    data.append(\"        self.example_method()\")\n    data.append(\"        self.assert_element(Page.html)\")\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_2, \"boilerplate_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"BaseCase.main(__name__, __file__)\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class DataPage:\")\n    data.append(\"    def go_to_data_url(self, sb):\")\n    data.append('        sb.open(\"data:text/html,<p>Hello!</p><input />\")')\n    data.append(\"\")\n    data.append(\"    def add_input_text(self, sb, text):\")\n    data.append('        sb.type(\"input\", text)')\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class ObjTests(BaseCase):\")\n    data.append(\"    def test_data_url_page(self):\")\n    data.append(\"        DataPage().go_to_data_url(self)\")\n    data.append('        self.assert_text(\"Hello!\", \"p\")')\n    data.append('        DataPage().add_input_text(self, \"Goodbye!\")')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_2, \"classic_obj_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"class DataPage:\")\n    data.append(\"    def go_to_data_url(self, sb):\")\n    data.append('        sb.open(\"data:text/html,<p>Hello!</p><input />\")')\n    data.append(\"\")\n    data.append(\"    def add_input_text(self, sb, text):\")\n    data.append('        sb.type(\"input\", text)')\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class ObjTests:\")\n    data.append(\"    def test_data_url_page(self, sb):\")\n    data.append(\"        DataPage().go_to_data_url(sb)\")\n    data.append('        sb.assert_text(\"Hello!\", \"p\")')\n    data.append('        DataPage().add_input_text(sb, \"Goodbye!\")')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_2, \"sb_fixture_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    dir_name_3 = dir_name_2 + \"/\" + \"samples\"\n    os.mkdir(dir_name_3)\n\n    data = []\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_3, \"__init__.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"from .google_objects import HomePage, ResultsPage\")\n    data.append('BaseCase.main(__name__, __file__, \"--uc\")')\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class GoogleTests(BaseCase):\")\n    data.append(\"    def test_google_dot_com(self):\")\n    data.append(\"        if self.headless:\")\n    data.append('            self.skip(\"Skipping test in headless mode.\")')\n    data.append(\"        if not self.undetectable:\")\n    data.append(\"            self.get_new_driver(undetectable=True)\")\n    data.append('        self.open(\"https://google.com/ncr\")')\n    data.append('        self.assert_title_contains(\"Google\")')\n    data.append(\"        self.save_screenshot_to_logs()\")\n    data.append('        self.type(HomePage.search_box, \"github.com\")')\n    data.append(\"        self.assert_element(HomePage.search_button)\")\n    data.append(\"        self.assert_element(HomePage.feeling_lucky_button)\")\n    data.append(\"        self.click(HomePage.search_button)\")\n    data.append(\n        '        self.assert_text(\"github.com\", ResultsPage.search_results)'\n    )\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_3, \"google_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"class HomePage(object):\")\n    data.append(\"    dialog_box = '[role=\\\"dialog\\\"] div'\")\n    data.append(\"    search_box = '[title=\\\"Search\\\"]'\")\n    data.append(\"    search_button = 'input[value=\\\"Google Search\\\"]'\")\n    data.append(\n        '    feeling_lucky_button = \"\"\"input[value=\"I\\'m Feeling Lucky\"]\"\"\"'\n    )\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class ResultsPage(object):\")\n    data.append('    search_results = \"div#center_col\"')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_3, \"google_objects.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append('\"\"\" Classic Page Object Model with BaseCase inheritance \"\"\"')\n    data.append(\"\")\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"BaseCase.main(__name__, __file__)\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class LoginPage:\")\n    data.append(\"    def login_to_swag_labs(self, sb, username):\")\n    data.append('        sb.open(\"https://www.saucedemo.com/\")')\n    data.append('        sb.type(\"#user-name\", username)')\n    data.append('        sb.type(\"#password\", \"secret_sauce\")')\n    data.append(\"        sb.click('input[type=\\\"submit\\\"]')\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class MyTests(BaseCase):\")\n    data.append(\"    def test_swag_labs_login(self):\")\n    data.append(\n        '        LoginPage().login_to_swag_labs(self, \"standard_user\")'\n    )\n    data.append('        self.assert_element(\"#inventory_container\")')\n    data.append(\n        \"        self.assert_element('div:contains(\\\"Sauce Labs Backpack\\\")')\"\n    )\n    data.append('        self.js_click(\"a#logout_sidebar_link\")')\n    data.append('        self.assert_element(\"div#login_button_container\")')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_3, \"swag_labs_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append('\"\"\" Classic Page Object Model with the \"sb\" fixture \"\"\"')\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class LoginPage:\")\n    data.append(\"    def login_to_swag_labs(self, sb, username):\")\n    data.append('        sb.open(\"https://www.saucedemo.com/\")')\n    data.append('        sb.type(\"#user-name\", username)')\n    data.append('        sb.type(\"#password\", \"secret_sauce\")')\n    data.append(\"        sb.click('input[type=\\\"submit\\\"]')\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class MyTests:\")\n    data.append(\"    def test_swag_labs_login(self, sb):\")\n    data.append('        LoginPage().login_to_swag_labs(sb, \"standard_user\")')\n    data.append('        sb.assert_element(\"div.inventory_list\")')\n    data.append(\n        \"        sb.assert_element('div:contains(\\\"Sauce Labs Backpack\\\")')\"\n    )\n    data.append('        sb.js_click(\"a#logout_sidebar_link\")')\n    data.append('        sb.assert_element(\"div#login_button_container\")')\n    data.append(\"\")\n    file_path = \"%s/%s\" % (dir_name_3, \"sb_swag_test.py\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(\"  %s/\" % dir_name)\n    if gha:\n        data.append(\"  ├── .github\")\n        data.append(\"  │   └── workflows/\")\n        data.append(\"  │      └── python-package.yml\")\n    data.append(\"  ├── __init__.py\")\n    data.append(\"  ├── my_first_test.py\")\n    data.append(\"  ├── parameterized_test.py\")\n    data.append(\"  ├── pytest.ini\")\n    data.append(\"  ├── requirements.txt\")\n    data.append(\"  ├── setup.cfg\")\n    data.append(\"  ├── test_demo_site.py\")\n    data.append(\"  └── boilerplates/\")\n    data.append(\"      ├── __init__.py\")\n    data.append(\"      ├── base_test_case.py\")\n    data.append(\"      ├── boilerplate_test.py\")\n    data.append(\"      ├── classic_obj_test.py\")\n    data.append(\"      ├── page_objects.py\")\n    data.append(\"      ├── sb_fixture_test.py\")\n    data.append(\"      └── samples/\")\n    data.append(\"          ├── __init__.py\")\n    data.append(\"          ├── google_objects.py\")\n    data.append(\"          ├── google_test.py\")\n    data.append(\"          ├── sb_swag_test.py\")\n    data.append(\"          └── swag_labs_test.py\")\n    file_path = \"%s/%s\" % (dir_name, \"outline.rst\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n    if \" \" not in file_path:\n        os.system(\"sbase print %s -n\" % file_path)\n    elif '\"' not in file_path:\n        os.system('sbase print \"%s\" -n' % file_path)\n    else:\n        os.system(\"sbase print '%s' -n\" % file_path)\n    os.remove(file_path)\n\n    success = (\n        \"\\n\" + c1 + '* Directory \"' + dir_name + '\" was created '\n        \"with config files and sample tests! *\" + cr + \"\\n\"\n    )\n    print(success)\n\n\nif __name__ == \"__main__\":\n    invalid_run_command()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_mkfile.py",
    "content": "\"\"\"\nCreates a new SeleniumBase test file with boilerplate code.\n\nUsage:\n    seleniumbase mkfile [FILE.py] [OPTIONS]\n    or     sbase mkfile [FILE.py] [OPTIONS]\n\nExample:\n    sbase mkfile new_test.py\n\nOptions:\n    --uc  (UC Mode boilerplate using SB context manager)\n    -b / --basic  (Basic boilerplate / single-line test)\n    -r / --rec  (Adds Pdb+ breakpoint for Recorder Mode)\n    --url=URL  (Makes the test start on a specific page)\n\nLanguage Options:\n    --en / --English    |    --zh / --Chinese\n    --nl / --Dutch      |    --fr / --French\n    --it / --Italian    |    --ja / --Japanese\n    --ko / --Korean     |    --pt / --Portuguese\n    --ru / --Russian    |    --es / --Spanish\n\nSyntax Formats:\n    --bc / --basecase       (BaseCase class inheritance)\n    --pf / --pytest-fixture          (sb pytest fixture)\n    --cf / --class-fixture   (class + sb pytest fixture)\n    --cm / --context-manager        (SB context manager)\n    --dc / --driver-context      (DriverContext manager)\n    --dm / --driver-manager             (Driver manager)\n\nOutput:\n    Creates a new SBase test file with boilerplate code.\n    If the file already exists, an error is raised.\n    By default, uses English with BaseCase inheritance,\n    and creates a boilerplate with common SeleniumBase\n    methods: \"open\", \"type\", \"click\", \"assert_element\",\n    and \"assert_text\". If using the basic boilerplate\n    option, only the \"open\" method is included. Only the\n    BaseCase format supports Languages or Recorder Mode.\n    UC Mode automatically uses English with SB() format.\n\"\"\"\nimport colorama\nimport os\nimport sys\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** mkfile **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase mkfile [FILE.py] [OPTIONS]\\n\"\n    exp += \"     OR     sbase mkfile [FILE.py] [OPTIONS]\\n\"\n    exp += \"  Example:\\n\"\n    exp += \"     sbase mkfile new_test.py\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     --uc  (UC Mode boilerplate using SB context manager)\\n\"\n    exp += \"     -b / --basic  (Basic boilerplate / single-line test)\\n\"\n    exp += \"     -r / --rec  (Adds Pdb+ breakpoint for Recorder Mode)\\n\"\n    exp += \"     --url=URL  (Makes the test start on a specific page)\\n\"\n    exp += \"  Language Options:\\n\"\n    exp += \"     --en / --English    |    --zh / --Chinese\\n\"\n    exp += \"     --nl / --Dutch      |    --fr / --French\\n\"\n    exp += \"     --it / --Italian    |    --ja / --Japanese\\n\"\n    exp += \"     --ko / --Korean     |    --pt / --Portuguese\\n\"\n    exp += \"     --ru / --Russian    |    --es / --Spanish\\n\"\n    exp += \"  Syntax Formats:\\n\"\n    exp += \"     --bc / --basecase       (BaseCase class inheritance)\\n\"\n    exp += \"     --pf / --pytest-fixture          (sb pytest fixture)\\n\"\n    exp += \"     --cf / --class-fixture   (class + sb pytest fixture)\\n\"\n    exp += \"     --cm / --context-manager        (SB context manager)\\n\"\n    exp += \"     --dc / --driver-context      (DriverContext manager)\\n\"\n    exp += \"     --dm / --driver-manager             (Driver manager)\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Creates a new SBase test file with boilerplate code.\\n\"\n    exp += \"     If the file already exists, an error is raised.\\n\"\n    exp += \"     By default, uses English with BaseCase inheritance,\\n\"\n    exp += \"     and creates a boilerplate with common SeleniumBase\\n\"\n    exp += '     methods: \"open\", \"type\", \"click\", \"assert_element\",\\n'\n    exp += '     and \"assert_text\". If using the basic boilerplate\\n'\n    exp += '     option, only the \"open\" method is included. Only the\\n'\n    exp += \"     BaseCase format supports Languages or Recorder Mode.\\n\"\n    exp += \"     UC Mode automatically uses English with SB() format.\\n\"\n    if not msg:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n    elif msg == \"help\":\n        print(\"\\n%s\" % exp)\n        sys.exit()\n    else:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\\n%s\\n\" % (exp, msg))\n\n\ndef main():\n    c1 = \"\"\n    c5 = \"\"\n    c7 = \"\"\n    cr = \"\"\n    if \"linux\" not in sys.platform:\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n        cr = colorama.Style.RESET_ALL\n\n    basic = False\n    use_uc = False\n    help_me = False\n    recorder = False\n    error_msg = None\n    start_page = None\n    invalid_cmd = None\n    syntax = \"BaseCase\"\n    language = \"English\"\n\n    command_args = sys.argv[2:]\n    file_name = command_args[0]\n    if file_name == \"-h\" or file_name == \"--help\":\n        invalid_run_command(\"help\")\n    elif not file_name.endswith(\".py\"):\n        error_msg = 'File name must end with \".py\"!'\n    elif \"*\" in file_name or len(str(file_name)) < 4:\n        error_msg = \"Invalid file name!\"\n    elif file_name.startswith(\"-\"):\n        error_msg = 'File name cannot start with \"-\"!'\n    elif \"/\" in str(file_name) or \"\\\\\" in str(file_name):\n        error_msg = \"File must be created in the current directory!\"\n    elif os.path.exists(os.getcwd() + \"/\" + file_name):\n        error_msg = 'File \"%s\" already exists in this directory!' % file_name\n    if error_msg:\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        invalid_run_command(error_msg)\n\n    if len(command_args) >= 2:\n        options = command_args[1:]\n        for option in options:\n            option = option.lower()\n            if option == \"-h\" or option == \"--help\":\n                help_me = True\n            elif option.startswith(\"--url=\") and len(option) > 6:\n                from seleniumbase.fixtures import page_utils\n                start_page = option.split(\"--url=\")[1]\n                if not page_utils.is_valid_url(start_page):\n                    if page_utils.is_valid_url(\"https://\" + start_page):\n                        start_page = \"https://\" + start_page\n                    else:\n                        raise Exception(\"Invalid URL: %s\" % start_page)\n                basic = True\n            elif option == \"-b\" or option == \"--basic\":\n                basic = True\n            elif option == \"-r\" or option == \"--rec\":\n                recorder = True\n            elif option == \"--record\" or option == \"--recorder\":\n                recorder = True\n            elif use_uc:\n                # UC must use English & ContextManager formats\n                continue\n            elif option == \"--en\" or option == \"--english\":\n                language = \"English\"\n            elif option == \"--zh\" or option == \"--chinese\":\n                language = \"Chinese\"\n            elif option == \"--nl\" or option == \"--dutch\":\n                language = \"Dutch\"\n            elif option == \"--fr\" or option == \"--french\":\n                language = \"French\"\n            elif option == \"--it\" or option == \"--italian\":\n                language = \"Italian\"\n            elif option == \"--ja\" or option == \"--japanese\":\n                language = \"Japanese\"\n            elif option == \"--ko\" or option == \"--korean\":\n                language = \"Korean\"\n            elif option == \"--pt\" or option == \"--portuguese\":\n                language = \"Portuguese\"\n            elif option == \"--ru\" or option == \"--russian\":\n                language = \"Russian\"\n            elif option == \"--es\" or option == \"--spanish\":\n                language = \"Spanish\"\n            elif option == \"--bc\" or option == \"--basecase\":\n                syntax = \"BaseCase\"\n            elif option == \"--pf\" or option == \"--pytest-fixture\":\n                syntax = \"PytestFixture\"\n            elif option == \"--cf\" or option == \"--class-fixture\":\n                syntax = \"ClassFixture\"\n            elif option == \"--cm\" or option == \"--context-manager\":\n                syntax = \"ContextManager\"\n            elif option == \"--dc\" or option == \"--driver-context\":\n                syntax = \"DriverContext\"\n            elif option == \"--dm\" or option == \"--driver-manager\":\n                syntax = \"DriverManager\"\n            elif option == \"--uc\":\n                basic = True\n                language = \"English\"\n                syntax = \"ContextManager\"\n                use_uc = True\n            else:\n                invalid_cmd = \"\\n===> INVALID OPTION: >> %s <<\\n\" % option\n                invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n                invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n                invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n                invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n                help_me = True\n                break\n    if help_me:\n        invalid_run_command(invalid_cmd)\n\n    dir_name = os.getcwd()\n    file_path = \"%s/%s\" % (dir_name, file_name)\n\n    body = \"body\"\n    para1 = \"html body > p\"\n    para2 = \"p\"\n    hello = \"Hello\"\n    goodbye = \"Goodbye\"\n    class_name = \"MyTestClass\"\n    if language == \"Chinese\":\n        hello = \"你好\"\n        goodbye = \"再见\"\n        class_name = \"我的测试类\"\n    elif language == \"Dutch\":\n        hello = \"Hallo\"\n        goodbye = \"Dag\"\n        class_name = \"MijnTestklasse\"\n    elif language == \"French\":\n        hello = \"Bonjour\"\n        goodbye = \"Au revoir\"\n        class_name = \"MaClasseDeTest\"\n    elif language == \"Italian\":\n        hello = \"Ciao\"\n        goodbye = \"Addio\"\n        class_name = \"MiaClasseDiTest\"\n    elif language == \"Japanese\":\n        hello = \"こんにちは\"\n        goodbye = \"さようなら\"\n        class_name = \"私のテストクラス\"\n    elif language == \"Korean\":\n        hello = \"여보세요\"\n        goodbye = \"안녕\"\n        class_name = \"테스트_클래스\"\n    elif language == \"Portuguese\":\n        hello = \"Olá\"\n        goodbye = \"Tchau\"\n        class_name = \"MinhaClasseDeTeste\"\n    elif language == \"Russian\":\n        hello = \"Привет\"\n        goodbye = \"До свидания\"\n        class_name = \"МойТестовыйКласс\"\n    elif language == \"Spanish\":\n        hello = \"Hola\"\n        goodbye = \"Adiós\"\n        class_name = \"MiClaseDePrueba\"\n    url = \"\"\n    if start_page:\n        url = start_page\n    elif basic:\n        url = \"about:blank\"\n    elif language not in [\"English\", \"Dutch\", \"French\", \"Italian\"]:\n        url = \"data:text/html,<meta charset='utf-8'><p>%s<br><input>\" % hello\n    else:\n        url = \"data:text/html,<p>%s<br><input>\" % hello\n\n    import_line = \"from seleniumbase import BaseCase\"\n    main_line = \"BaseCase.main(__name__, __file__)\"\n    parent_class = \"BaseCase\"\n    if language != \"English\":\n        from seleniumbase.translate.master_dict import MD_F\n\n        import_line = MD_F.get_import_line(language)\n        parent_class = MD_F.get_lang_parent_class(language)\n    class_line = \"class %s(%s):\" % (class_name, parent_class)\n\n    data = []\n    data.append(\"%s\" % import_line)\n    if not recorder:\n        data.append(main_line)\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"%s\" % class_line)\n    data.append(\"    def test_base(self):\")\n    if not recorder:\n        data.append('        self.open(\"%s\")' % url)\n    else:\n        data.append(\"        if self.recorder_ext and not self.xvfb:\")\n        data.append(\"            import pdb; pdb.set_trace()\")\n    if not basic and not recorder:\n        data.append(\n            '        self.type(\"input\", \"%s\")' \"  # selector, text\" % goodbye\n        )\n        data.append('        self.click(\"%s\")  # selector' % para1)\n        data.append('        self.assert_element(\"%s\")  # selector' % body)\n        data.append(\n            '        self.assert_text(\"%s\", \"%s\")'\n            \"  # text, selector\" % (hello, para2)\n        )\n    data.append(\"\")\n\n    new_data = []\n    if language == \"English\" and syntax == \"BaseCase\":\n        new_data = data\n    elif language == \"English\" and syntax == \"PytestFixture\":\n        data = []\n        data.append(\"def test_base(sb):\")\n        data.append('    sb.open(\"data:text/html,<p>Hello<br><input>\")')\n        if not basic:\n            data.append('    sb.type(\"input\", \"Goodbye\")  # selector, text')\n            data.append('    sb.click(\"html body > p\")  # selector')\n            data.append('    sb.assert_element(\"body\")  # selector')\n            data.append('    sb.assert_text(\"Hello\", \"p\")  # text, selector')\n        data.append(\"\")\n        new_data = data\n    elif language == \"English\" and syntax == \"ClassFixture\":\n        data = []\n        data.append(\"class %s:\" % class_name)\n        data.append(\"    def test_base(self, sb):\")\n        data.append('        sb.open(\"data:text/html,<p>Hello<br><input>\")')\n        if not basic:\n            data.append(\n                '        sb.type(\"input\", \"Goodbye\")  # selector, text'\n            )\n            data.append('        sb.click(\"html body > p\")  # selector')\n            data.append('        sb.assert_element(\"body\")  # selector')\n            data.append(\n                '        sb.assert_text(\"Hello\", \"p\")  # text, selector'\n            )\n        data.append(\"\")\n        new_data = data\n    elif language == \"English\" and syntax == \"ContextManager\":\n        data = []\n        data.append(\"from seleniumbase import SB\")\n        data.append(\"\")\n        if use_uc:\n            data.append('with SB(uc=True) as sb:')\n        else:\n            data.append('with SB(browser=\"chrome\") as sb:')\n        if use_uc:\n            data.append('    url = \"%s\"' % url)\n            data.append(\"    sb.uc_open_with_reconnect(url, 4)\")\n            data.append(\"    sb.uc_gui_click_captcha()\")\n        else:\n            data.append('    sb.open(\"%s\")' % url)\n        if not basic:\n            data.append('    sb.type(\"input\", \"Goodbye\")  # selector, text')\n            data.append('    sb.click(\"html body > p\")  # selector')\n            data.append('    sb.assert_element(\"input\")  # selector')\n            data.append('    sb.assert_text(\"Hello\", \"p\")  # text, selector')\n            data.append('    sb.highlight(\"p\")  # selector')\n            data.append(\"    sb.sleep(0.5)  # seconds\")\n        data.append(\"\")\n        new_data = data\n    elif language == \"English\" and syntax == \"DriverContext\":\n        data = []\n        data.append(\"from seleniumbase import DriverContext\")\n        data.append(\"\")\n        data.append('with DriverContext(browser=\"chrome\") as driver:')\n        data.append('    driver.get(\"%s\")' % url)\n        if not basic:\n            data.append('    driver.type(\"input\", \"Goodbye\")  # sel, text')\n            data.append('    driver.click(\"html body > p\")  # selector')\n            data.append('    driver.assert_element(\"input\")  # selector')\n            data.append('    driver.assert_text(\"Hello\", \"p\")  # text, sel')\n            data.append('    driver.highlight(\"p\")  # selector')\n            data.append(\"    driver.sleep(0.5)  # seconds\")\n        data.append(\"\")\n        new_data = data\n    elif language == \"English\" and syntax == \"DriverManager\":\n        data = []\n        data.append(\"from seleniumbase import Driver\")\n        data.append(\"\")\n        data.append('driver = Driver(browser=\"chrome\")')\n        data.append(\"try:\")\n        data.append('    driver.get(\"%s\")' % url)\n        if not basic:\n            data.append('    driver.type(\"input\", \"Goodbye\")  # sel, text')\n            data.append('    driver.click(\"html body > p\")  # selector')\n            data.append('    driver.assert_element(\"input\")  # selector')\n            data.append('    driver.assert_text(\"Hello\", \"p\")  # text, sel')\n            data.append('    driver.highlight(\"p\")  # selector')\n            data.append(\"    driver.sleep(0.5)  # seconds\")\n        data.append(\"finally:\")\n        data.append(\"    driver.quit()\")\n        data.append(\"\")\n        new_data = data\n    else:\n        from seleniumbase.translate.master_dict import MD\n        from seleniumbase.translate.master_dict import MD_L_Codes\n\n        md = MD.md\n        lang_codes = MD_L_Codes.lang\n        nl_code = lang_codes[language]\n        dl_code = lang_codes[\"English\"]\n        for line in data:\n            found_swap = False\n            replace_count = line.count(\"self.\")  # Total possible replacements\n            for key in md.keys():\n                original = \"self.\" + md[key][dl_code] + \"(\"\n                if original in line:\n                    replacement = \"self.\" + md[key][nl_code] + \"(\"\n                    new_line = line.replace(original, replacement)\n                    found_swap = True\n                    replace_count -= 1\n                    if replace_count == 0:\n                        break  # Done making replacements\n                    else:\n                        # There might be another method to replace in the line.\n                        # Example: self.assert_true(\"Name\" in self.get_title())\n                        line = new_line\n                        continue\n            if main_line in line:\n                new_main = \"%s.main(__name__, __file__)\" % parent_class\n                new_line = line.replace(main_line, new_main)\n                found_swap = True\n            if found_swap:\n                if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                    new_line = new_line[0 : -len(\"  # noqa\")]\n                new_data.append(new_line)\n                continue\n            new_data.append(line)\n    data = new_data\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n    if \" \" not in file_name:\n        os.system(\"sbase print %s -n\" % file_name)\n    elif '\"' not in file_name:\n        os.system('sbase print \"%s\" -n' % file_name)\n    else:\n        os.system(\"sbase print '%s' -n\" % file_name)\n    success = (\n        \"\\n\" + c1 + '* Test file: \"' + file_name + '\" was created! *'\n        \"\" + cr + \"\\n\"\n    )\n    print(success)\n\n\nif __name__ == \"__main__\":\n    invalid_run_command()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_mkpres.py",
    "content": "\"\"\"\nCreates a new SeleniumBase presentation with boilerplate code.\n\nUsage:\n    seleniumbase mkpres [FILE.py] [LANG]\n    or     sbase mkpres [FILE.py] [LANG]\n\nExample:\n    sbase mkpres new_presentation.py --en\n\nLanguage Options:\n    --en / --English    |    --zh / --Chinese\n    --nl / --Dutch      |    --fr / --French\n    --it / --Italian    |    --ja / --Japanese\n    --ko / --Korean     |    --pt / --Portuguese\n    --ru / --Russian    |    --es / --Spanish\n\nOutput:\n    Creates a new presentation with 3 example slides.\n    If the file already exists, an error is raised.\n    By default, the slides are written in English,\n    and use \"serif\" theme with \"slide\" transition.\n    The slides can be used as a basic boilerplate.\n\"\"\"\nimport colorama\nimport os\nimport sys\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** mkpres **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase mkpres [FILE.py] [LANG]\\n\"\n    exp += \"     OR     sbase mkpres [FILE.py] [LANG]\\n\"\n    exp += \"  Example:\\n\"\n    exp += \"     sbase mkpres new_presentation.py --en\\n\"\n    exp += \"  Language Options:\\n\"\n    exp += \"     --en / --English    |    --zh / --Chinese\\n\"\n    exp += \"     --nl / --Dutch      |    --fr / --French\\n\"\n    exp += \"     --it / --Italian    |    --ja / --Japanese\\n\"\n    exp += \"     --ko / --Korean     |    --pt / --Portuguese\\n\"\n    exp += \"     --ru / --Russian    |    --es / --Spanish\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Creates a new presentation with 3 example slides.\\n\"\n    exp += \"     If the file already exists, an error is raised.\\n\"\n    exp += \"     By default, the slides are written in English,\\n\"\n    exp += '     and use \"serif\" theme with \"slide\" transition.\\n'\n    exp += \"     The slides can be used as a basic boilerplate.\\n\"\n    if not msg:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n    elif msg == \"help\":\n        print(\"\\n%s\" % exp)\n        sys.exit()\n    else:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\\n%s\\n\" % (exp, msg))\n\n\ndef main():\n    c1 = \"\"\n    c5 = \"\"\n    c7 = \"\"\n    cr = \"\"\n    if \"linux\" not in sys.platform:\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n        cr = colorama.Style.RESET_ALL\n\n    help_me = False\n    error_msg = None\n    invalid_cmd = None\n    language = \"English\"\n\n    command_args = sys.argv[2:]\n    file_name = command_args[0]\n    if file_name == \"-h\" or file_name == \"--help\":\n        invalid_run_command(\"help\")\n    elif not file_name.endswith(\".py\"):\n        error_msg = 'File name must end with \".py\"!'\n    elif \"*\" in file_name or len(str(file_name)) < 4:\n        error_msg = \"Invalid file name!\"\n    elif file_name.startswith(\"-\"):\n        error_msg = 'File name cannot start with \"-\"!'\n    elif \"/\" in str(file_name) or \"\\\\\" in str(file_name):\n        error_msg = \"File must be created in the current directory!\"\n    elif os.path.exists(os.getcwd() + \"/\" + file_name):\n        error_msg = 'File \"%s\" already exists in this directory!' % file_name\n    if error_msg:\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        invalid_run_command(error_msg)\n\n    if len(command_args) >= 2:\n        options = command_args[1:]\n        for option in options:\n            option = option.lower()\n            if option == \"-h\" or option == \"--help\":\n                help_me = True\n            elif option == \"--en\" or option == \"--english\":\n                language = \"English\"\n            elif option == \"--zh\" or option == \"--chinese\":\n                language = \"Chinese\"\n            elif option == \"--nl\" or option == \"--dutch\":\n                language = \"Dutch\"\n            elif option == \"--fr\" or option == \"--french\":\n                language = \"French\"\n            elif option == \"--it\" or option == \"--italian\":\n                language = \"Italian\"\n            elif option == \"--ja\" or option == \"--japanese\":\n                language = \"Japanese\"\n            elif option == \"--ko\" or option == \"--korean\":\n                language = \"Korean\"\n            elif option == \"--pt\" or option == \"--portuguese\":\n                language = \"Portuguese\"\n            elif option == \"--ru\" or option == \"--russian\":\n                language = \"Russian\"\n            elif option == \"--es\" or option == \"--spanish\":\n                language = \"Spanish\"\n            else:\n                invalid_cmd = \"\\n===> INVALID OPTION: >> %s <<\\n\" % option\n                invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n                invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n                invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n                invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n                help_me = True\n                break\n    if help_me:\n        invalid_run_command(invalid_cmd)\n\n    dir_name = os.getcwd()\n    file_path = \"%s/%s\" % (dir_name, file_name)\n    html_name = file_name.replace(\".py\", \".html\")\n\n    hello = \"Hello\"\n    press_right_arrow = \"Press Right Arrow\"\n    add_text = \"Add Text\"\n    goodbye = \"Goodbye\"\n    class_name = \"MyTestClass\"\n\n    if language == \"Chinese\":\n        hello = \"你好\"\n        press_right_arrow = \"按向右箭头\"\n        add_text = \"添加文本\"\n        goodbye = \"再见\"\n        class_name = \"我的测试类\"\n    elif language == \"Dutch\":\n        hello = \"Hallo\"\n        press_right_arrow = \"Druk op pijl rechts\"\n        add_text = \"Tekst Toevoegen\"\n        goodbye = \"Dag\"\n        class_name = \"MijnTestklasse\"\n    elif language == \"French\":\n        hello = \"Bonjour\"\n        press_right_arrow = \"Appuyer sur flèche droite\"\n        add_text = \"Ajouter Texte\"\n        goodbye = \"Au revoir\"\n        class_name = \"MaClasseDeTest\"\n    elif language == \"Italian\":\n        hello = \"Ciao\"\n        press_right_arrow = \"Premere la freccia destra\"\n        add_text = \"Aggiungi Testo\"\n        goodbye = \"Addio\"\n        class_name = \"MiaClasseDiTest\"\n    elif language == \"Japanese\":\n        hello = \"こんにちは\"\n        press_right_arrow = \"右矢印を押します\"\n        add_text = \"テキストを追加\"\n        goodbye = \"さようなら\"\n        class_name = \"私のテストクラス\"\n    elif language == \"Korean\":\n        hello = \"여보세요\"\n        press_right_arrow = \"오른쪽 화살표를 누르십시오\"\n        add_text = \"텍스트를 추가\"\n        goodbye = \"안녕\"\n        class_name = \"테스트_클래스\"\n    elif language == \"Portuguese\":\n        hello = \"Olá\"\n        press_right_arrow = \"Pressione a seta direita\"\n        add_text = \"Adicionar Texto\"\n        goodbye = \"Tchau\"\n        class_name = \"MinhaClasseDeTeste\"\n    elif language == \"Russian\":\n        hello = \"Привет\"\n        press_right_arrow = \"Нажмите стрелку вправо\"\n        add_text = \"Добавить Текст\"\n        goodbye = \"До свидания\"\n        class_name = \"МойТестовыйКласс\"\n    elif language == \"Spanish\":\n        hello = \"Hola\"\n        press_right_arrow = \"Presione la flecha derecha\"\n        add_text = \"Agregar Texto\"\n        goodbye = \"Adiós\"\n        class_name = \"MiClaseDePrueba\"\n\n    import_line = \"from seleniumbase import BaseCase\"\n    main_line = \"BaseCase.main(__name__, __file__)\"\n    parent_class = \"BaseCase\"\n    if language != \"English\":\n        from seleniumbase.translate.master_dict import MD_F\n\n        import_line = MD_F.get_import_line(language)\n        parent_class = MD_F.get_lang_parent_class(language)\n    class_line = \"class %s(%s):\" % (class_name, parent_class)\n    settings = 'theme=\"serif\", transition=\"slide\"'\n    img_src_1 = 'src=\"https://seleniumbase.github.io/cdn/gif/chart_pres.gif\"'\n    img_src_2 = 'src=\"https://seleniumbase.github.io/cdn/img/sb_logo_10.png\"'\n    hello_page = (\n        \"\\n            '<h1>%s</h1><hr /><br />'\"\n        \"\\n            '<p>%s</p>'\"\n        \"\" % (hello, press_right_arrow)\n    )\n    add_text_page = (\n        \"\\n            '<h2><b>*</b>  %s  <b>*</b></h2>'\"\n        \"\\n            '<img %s>'\"\n        \"\" % (add_text, img_src_1)\n    )\n    goodbye_page = (\n        \"\\n            '<h2>%s</h2>'\"\n        \"\\n            '<img %s>'\"\n        \"\" % (goodbye, img_src_2)\n    )\n\n    data = []\n    data.append(\"%s\" % import_line)\n    data.append(\"%s\" % main_line)\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"%s\" % class_line)\n    data.append(\"    def test_presentation(self):\")\n    data.append(\"        self.create_presentation(%s)\" % settings)\n    data.append(\"        self.add_slide(%s)\" % hello_page)\n    data.append(\"        self.add_slide(%s)\" % add_text_page)\n    data.append(\"        self.add_slide(%s)\" % goodbye_page)\n    data.append('        self.begin_presentation(filename=\"%s\")' % html_name)\n    data.append(\"\")\n\n    new_data = []\n    if language == \"English\":\n        new_data = data\n    else:\n        from seleniumbase.translate.master_dict import MD\n        from seleniumbase.translate.master_dict import MD_L_Codes\n\n        md = MD.md\n        lang_codes = MD_L_Codes.lang\n        nl_code = lang_codes[language]\n        dl_code = lang_codes[\"English\"]\n        for line in data:\n            found_swap = False\n            replace_count = line.count(\"self.\")  # Total possible replacements\n            for key in md.keys():\n                original = \"self.\" + md[key][dl_code] + \"(\"\n                if original in line:\n                    replacement = \"self.\" + md[key][nl_code] + \"(\"\n                    new_line = line.replace(original, replacement)\n                    found_swap = True\n                    replace_count -= 1\n                    if replace_count == 0:\n                        break  # Done making replacements\n                    else:\n                        # There might be another method to replace in the line.\n                        # Example: self.assert_true(\"Name\" in self.get_title())\n                        line = new_line\n                        continue\n            if main_line in line:\n                new_main = \"%s.main(__name__, __file__)\" % parent_class\n                new_line = line.replace(main_line, new_main)\n                found_swap = True\n            if found_swap:\n                if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                    new_line = new_line[0 : -len(\"  # noqa\")]\n                new_data.append(new_line)\n                continue\n            new_data.append(line)\n    data = new_data\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n    if \" \" not in file_name:\n        os.system(\"sbase print %s -n\" % file_name)\n    elif '\"' not in file_name:\n        os.system('sbase print \"%s\" -n' % file_name)\n    else:\n        os.system(\"sbase print '%s' -n\" % file_name)\n    success = (\n        \"\\n\" + c1 + '* Presentation: \"' + file_name + '\" was created! *'\n        \"\" + cr + \"\\n\"\n    )\n    print(success)\n\n\nif __name__ == \"__main__\":\n    invalid_run_command()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_mkrec.py",
    "content": "\"\"\"\n** mkrec / record / codegen **\n\nCreates a new SeleniumBase test file using the Recorder.\n\nUsage:\n      seleniumbase mkrec [FILE.py] [OPTIONS]\n             sbase mkrec [FILE.py] [OPTIONS]\n     seleniumbase record [FILE.py] [OPTIONS]\n            sbase record [FILE.py] [OPTIONS]\n    seleniumbase codegen [FILE.py] [OPTIONS]\n           sbase codegen [FILE.py] [OPTIONS]\n\nExamples:\n    sbase mkrec new_test.py\n    sbase mkrec new_test.py --url=seleniumbase.io\n    sbase codegen new_test.py\n    sbase codegen new_test.py --url=wikipedia.org\n\nOptions:\n    --url=URL  (Sets the initial start page URL.)\n    --edge  (Use Edge browser instead of Chrome.)\n    --gui / --headed  (Use headed mode on Linux.)\n    --uc / --undetected  (Use undetectable mode.)\n    --overwrite  (Overwrite file when it exists.)\n    --behave  (Also output Behave/Gherkin files.)\n\nOutput:\n    Creates a new SeleniumBase test using the Recorder.\n    If the filename already exists, an error is raised.\n\"\"\"\nimport colorama\nimport shutil\nimport os\nimport sys\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** mkrec / record / codegen **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase mkrec [FILE.py]\\n\"\n    exp += \"     OR:    sbase mkrec [FILE.py]\\n\"\n    exp += \"  Examples:\\n\"\n    exp += \"     sbase mkrec new_test.py\\n\"\n    exp += \"     sbase mkrec new_test.py --url=wikipedia.org\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     --url=URL  (Sets the initial start page URL.)\\n\"\n    exp += \"     --edge  (Use Edge browser instead of Chrome.)\\n\"\n    exp += \"     --gui / --headed  (Use headed mode on Linux.)\\n\"\n    exp += \"     --uc / --undetected  (Use undetectable mode.)\\n\"\n    exp += \"     --overwrite  (Overwrite file when it exists.)\\n\"\n    exp += \"     --behave  (Also output Behave/Gherkin files.)\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Creates a new SeleniumBase test using the Recorder.\\n\"\n    exp += \"     If the filename already exists, an error is raised.\\n\"\n    if not msg:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n    elif msg == \"help\":\n        print(\"\\n%s\" % exp)\n        sys.exit()\n    else:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\\n%s\\n\" % (exp, msg))\n\n\ndef set_colors(use_colors):\n    c0 = \"\"\n    c1 = \"\"\n    c2 = \"\"\n    c5 = \"\"\n    c7 = \"\"\n    cr = \"\"\n    if use_colors:\n        c0 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX\n        c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n        cr = colorama.Style.RESET_ALL\n    return c0, c1, c2, c5, c7, cr\n\n\ndef main():\n    help_me = False\n    error_msg = None\n    invalid_cmd = None\n    use_edge = False\n    use_opera = False\n    use_brave = False\n    use_comet = False\n    use_atlas = False\n    use_chromium = False\n    use_uc = False\n    esc_end = False\n    start_page = None\n    next_is_url = False\n    use_colors = True\n    force_gui = False\n    rec_behave = False\n\n    sys_executable = sys.executable\n    if \" \" in sys_executable:\n        sys_executable = \"python\"\n\n    if \"linux\" in sys.platform:\n        use_colors = False\n    c0, c1, c2, c5, c7, cr = set_colors(use_colors)\n\n    command_args = sys.argv[2:]\n    file_name = command_args[0]\n    if file_name == \"-h\" or file_name == \"--help\":\n        invalid_run_command(\"help\")\n    elif not file_name.endswith(\".py\"):\n        error_msg = 'File name must end with \".py\"!'\n    elif \"*\" in file_name or len(str(file_name)) < 4:\n        error_msg = \"Invalid file name!\"\n    elif file_name.startswith(\"-\"):\n        error_msg = 'File name cannot start with \"-\"!'\n    elif \"/\" in str(file_name) or \"\\\\\" in str(file_name):\n        error_msg = \"File must be created in the current directory!\"\n    elif file_name == \"abc.py\":\n        error_msg = '\"abc.py\" is a reserved Python module! Use another name!'\n    if error_msg:\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        invalid_run_command(error_msg)\n\n    dir_name = os.getcwd()\n    file_path = os.path.join(dir_name, file_name)\n\n    if (\n        \"--overwrite\" in \" \".join(command_args).lower()\n        and os.path.exists(file_path)\n    ):\n        os.remove(file_path)\n    if os.path.exists(file_path):\n        error_msg = 'File \"%s\" already exists in this directory!' % file_name\n        error_msg = c5 + \"ERROR: \" + error_msg + cr\n        invalid_run_command(error_msg)\n\n    if len(command_args) >= 2:\n        options = command_args[1:]\n        for option in options:\n            if option.lower() == \"-h\" or option.lower() == \"--help\":\n                help_me = True\n            elif option.lower() == \"--edge\":\n                use_edge = True\n            elif option.lower() == \"--opera\":\n                use_opera = True\n            elif option.lower() == \"--brave\":\n                use_brave = True\n            elif option.lower() == \"--comet\":\n                use_comet = True\n            elif option.lower() == \"--atlas\":\n                use_atlas = True\n            elif option.lower() == \"--use-chromium\":\n                use_chromium = True\n            elif option.lower() == \"--ee\":\n                esc_end = True\n            elif option.lower() in (\"--gui\", \"--headed\"):\n                if \"linux\" in sys.platform:\n                    force_gui = True\n            elif option.lower() in (\n                \"--uc\", \"--cdp\", \"--undetected\", \"--undetectable\"\n            ):\n                use_uc = True\n            elif option.lower() in (\"--rec-behave\", \"--behave\", \"--gherkin\"):\n                rec_behave = True\n            elif option.lower().startswith(\"--url=\"):\n                start_page = option[len(\"--url=\"):]\n            elif option.lower() == \"--url\":\n                next_is_url = True\n            elif next_is_url:\n                start_page = option\n                next_is_url = False\n            elif option.lower() == \"--overwrite\":\n                pass  # Already handled if file existed\n            else:\n                invalid_cmd = \"\\n===> INVALID OPTION: >> %s <<\\n\" % option\n                invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n                invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n                invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n                invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n                help_me = True\n                break\n    if help_me:\n        invalid_run_command(invalid_cmd)\n\n    data = []\n    data.append(\"from seleniumbase import BaseCase\")\n    data.append(\"\")\n    data.append(\"\")\n    data.append(\"class RecorderTest(BaseCase):\")\n    data.append(\"    def test_recording(self):\")\n    if use_uc:\n        data.append(\"        if self.undetectable:\")\n        if (\n            start_page\n            and (\n                start_page.startswith(\"http:\")\n                or start_page.startswith(\"https:\")\n                or start_page.startswith(\"file:\")\n            )\n        ):\n            used_sp = start_page\n            if '\"' not in start_page:\n                used_sp = '\"%s\"' % start_page\n            elif \"'\" not in start_page:\n                used_sp = \"'%s'\" % start_page\n            data.append(\n                \"            self.activate_cdp_mode(\\n\"\n                \"                %s,\\n\"\n                \"                recorder=True,\\n\"\n                \"            )\" % used_sp\n            )\n        else:\n            data.append(\"            self.disconnect()\")\n    data.append(\"        if self.recorder_ext:\")\n    data.append(\"            # When done recording actions,\")\n    data.append('            # type \"c\", and press [Enter].')\n    data.append(\"            import pdb; pdb.set_trace()\")\n    data.append(\"\")\n\n    if esc_end:\n        msg = \">>> Use [SHIFT + ESC] in the browser to end recording!\"\n        d2 = []\n        d2.append(\"from seleniumbase import BaseCase\")\n        d2.append(\"\")\n        d2.append(\"\")\n        d2.append(\"class RecorderTest(BaseCase):\")\n        d2.append(\"    def test_recording(self):\")\n        d2.append(\"        if self.recorder_ext:\")\n        d2.append(\"            print(\")\n        d2.append('                \"\\\\n\\\\n%s\\\\n\"' % msg)\n        d2.append(\"            )\")\n        d2.append('            script = self._get_rec_shift_esc_script()')\n        d2.append('            esc = \"return document.sb_esc_end;\"')\n        d2.append(\"            start_time = self.time()\")\n        d2.append(\"            last_handles_num = self._get_num_handles()\")\n        d2.append(\"            for i in range(1200):\")\n        d2.append(\"                try:\")\n        d2.append(\"                    self.execute_script(script)\")\n        d2.append(\"                    handles_num = self._get_num_handles()\")\n        d2.append(\"                    if handles_num < 1:\")\n        d2.append(\"                        return\")\n        d2.append(\"                    elif handles_num != last_handles_num:\")\n        d2.append(\"                        self.switch_to_window(-1)\")\n        d2.append(\"                        last_handles_num = handles_num\")\n        d2.append('                    if self.execute_script(esc) == \"yes\":')\n        d2.append(\"                        return\")\n        d2.append(\"                    elif self.time() - start_time > 600:\")\n        d2.append(\"                        return\")\n        d2.append(\"                    self.sleep(0.5)\")\n        d2.append(\"                except Exception:\")\n        d2.append(\"                    return\")\n        d2.append(\"\")\n        data = d2\n\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n    success = (\n        \"\\n\" + c0 + \"* RECORDING initialized:\" + cr + \" \"\n        \"\" + c1 + file_name + \"\" + cr + \"\\n\"\n    )\n    print(success)\n    run_cmd = None\n    if (\n        not start_page\n        or (\n            use_uc\n            and (\n                start_page.startswith(\"http:\")\n                or start_page.startswith(\"https:\")\n                or start_page.startswith(\"file:\")\n            )\n            and not esc_end\n        )\n    ):\n        run_cmd = \"%s -m pytest %s --rec -q -s\" % (sys_executable, file_name)\n    else:\n        run_cmd = \"%s -m pytest %s --rec -q -s --url=%s\" % (\n            sys_executable, file_name, start_page\n        )\n        if '\"' not in start_page:\n            run_cmd = '%s -m pytest %s --rec -q -s --url=\"%s\"' % (\n                sys_executable, file_name, start_page\n            )\n        elif \"'\" not in start_page:\n            run_cmd = \"%s -m pytest %s --rec -q -s --url='%s'\" % (\n                sys_executable, file_name, start_page\n            )\n    if use_edge:\n        run_cmd += \" --edge\"\n    elif use_opera:\n        run_cmd += \" --opera\"\n    elif use_brave:\n        run_cmd += \" --brave\"\n    elif use_comet:\n        run_cmd += \" --comet\"\n    elif use_atlas:\n        run_cmd += \" --atlas\"\n    elif use_chromium:\n        run_cmd += \" --use-chromium\"\n    if force_gui:\n        run_cmd += \" --gui\"\n    if use_uc:\n        run_cmd += \" --uc\"\n    if rec_behave:\n        run_cmd += \" --rec-behave\"\n    print(run_cmd)\n    os.system(run_cmd)\n    if os.path.exists(file_path):\n        os.remove(file_path)\n    recorded_filename = file_name[:-3] + \"_rec.py\"\n    recordings_dir = os.path.join(dir_name, \"recordings\")\n    recorded_file = os.path.join(recordings_dir, recorded_filename)\n    prefix = \"%s -m \" % sys_executable\n    if \" \" not in recorded_file:\n        os.system(\"%sseleniumbase print %s -n\" % (prefix, recorded_file))\n    elif '\"' not in recorded_file:\n        os.system('%sseleniumbase print \"%s\" -n' % (prefix, recorded_file))\n    else:\n        os.system(\"%sseleniumbase print '%s' -n\" % (prefix, recorded_file))\n    shutil.copy(recorded_file, file_path)\n    success = (\n        \"\\n\" + c2 + \"***\" + cr + \" RECORDING COPIED to: \"\n        \"\" + c1 + file_name + cr + \"\\n\"\n    )\n    print(success)\n    if rec_behave:\n        recorded_filename = file_name[:-3] + \"_rec.feature\"\n        recordings_dir = os.path.join(dir_name, \"recordings\")\n        features_dir = os.path.join(recordings_dir, \"features\")\n        recorded_file = os.path.join(features_dir, recorded_filename)\n        if \" \" not in recorded_file:\n            os.system(\"%sseleniumbase print %s -n\" % (prefix, recorded_file))\n        elif '\"' not in recorded_file:\n            os.system('%sseleniumbase print \"%s\" -n' % (prefix, recorded_file))\n        else:\n            os.system(\"%sseleniumbase print '%s' -n\" % (prefix, recorded_file))\n        success = (\n            \"\\n\" + c2 + \"***\" + cr + \" BEHAVE RECORDING at: \"\n            \"\" + c1 + os.path.relpath(recorded_file) + cr + \"\\n\"\n        )\n        print(success)\n\n\nif __name__ == \"__main__\":\n    invalid_run_command()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_objectify.py",
    "content": "\"\"\"\nConverts a SeleniumBase Python file into one that uses the Page Object Pattern.\n\nUsage:\n    seleniumbase objectify [SELENIUMBASE_PYTHON_FILE].py\nOutput:\n    A modified version of the file where the selectors\n    have been replaced with variable names defined in\n    \"page_objects.py\", supporting the Page Object Pattern.\n\"\"\"\nimport os\nimport re\nimport sys\n\nPAGE_OBJECTS_FILE = \"page_objects.py\"  # Don't change this. It's hard-coded.\np_o_import = \"from .page_objects import \"\nif not os.path.exists(\"__init__.py\"):\n    p_o_import = \"from page_objects import \"\n\n\ndef invalid_run_command(shell_command):\n    if shell_command == \"objectify\":\n        invalid_objectify_run_command()\n    elif shell_command == \"inject-objects\":\n        invalid_inject_objects_run_command()\n    elif shell_command == \"extract-objects\":\n        invalid_extract_objects_run_command()\n    elif shell_command == \"revert-objects\":\n        invalid_revert_objects_run_command()\n    else:\n        invalid_objectify_run_command()\n\n\ndef invalid_objectify_run_command():\n    exp = \"  ** objectify **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase objectify [SELENIUMBASE_PYTHON_FILE]\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     -c, --comments  (Add object selectors to the comments.)\\n\"\n    exp += \"                     (Default: No added comments.)\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Converts a SeleniumBase Python file into one that uses\\n\"\n    exp += \"     the Page Object Pattern by converting page selectors\\n\"\n    exp += '     into objects stored in a \"page_objects.py\" file that is\\n'\n    exp += \"     autogenerated and stored in the same folder as tests.\\n\"\n    exp += '     (seleniumbase \"objectify\" has the same outcome as\\n'\n    exp += '     combining \"extract-objects\" with \"inject-objects\")\\n'\n    raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n\n\ndef invalid_inject_objects_run_command():\n    exp = \"  ** inject-objects **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase inject-objects [SELENIUMBASE_PYTHON_FILE]\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     -c, --comments  (Add object selectors to the comments.)\\n\"\n    exp += \"                     (Default: No added comments.)\\n\"\n    exp += \"  Output:\\n\"\n    exp += '     Takes the page objects found in the \"page_objects.py\"\\n'\n    exp += \"     file and uses those to replace matching selectors in\\n\"\n    exp += \"     the selected seleniumbase Python file.\\n\"\n    raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n\n\ndef invalid_extract_objects_run_command():\n    exp = \"  ** extract-objects **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase extract-objects [SELENIUMBASE_PYTHON_FILE]\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Creates page objects based on selectors found in a\\n\"\n    exp += \"     seleniumbase Python file and saves those objects to the\\n\"\n    exp += '     \"page_objects.py\" file in the same folder as the tests.\\n'\n    raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n\n\ndef invalid_revert_objects_run_command():\n    exp = \"  ** revert-objects **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase revert-objects [SELENIUMBASE_PYTHON_FILE]\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     -c, --comments  (Keep existing comments for the lines.)\\n\"\n    exp += \"                     (Default: No comments are kept.)\\n\"\n    exp += \"  Output:\\n\"\n    exp += '     Reverts the changes made by \"seleniumbase objectify\" or\\n'\n    exp += '     \"seleniumbase inject-objects\" when run against a\\n'\n    exp += \"     seleniumbase Python file. Objects will get replaced by\\n\"\n    exp += '     selectors stored in the \"page_objects.py\" file.\\n'\n    raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n\n\ndef remove_extra_slashes(selector):\n    if selector.count('\\\\\"') > 0:\n        if selector.count('\\\\\"') == selector.count('\"'):\n            selector = selector.replace('\\\\\"', '\"')\n        elif selector.count('\\\\\"') == selector[1:-1].count('\"') and (\n            \"'\" not in selector[1:-1]\n        ):\n            selector = \"'\" + selector[1:-1].replace('\\\\\"', '\"') + \"'\"\n        else:\n            pass\n    if selector.count(\"\\\\'\") > 0:\n        if selector.count(\"\\\\'\") == selector.count(\"'\"):\n            selector = selector.replace(\"\\\\'\", \"'\")\n        elif selector.count(\"\\\\'\") == selector[1:-1].count(\"'\") and (\n            '\"' not in selector[1:-1]\n        ):\n            selector = '\"' + selector[1:-1].replace(\"\\\\'\", \"'\") + '\"'\n        else:\n            pass\n    return selector\n\n\ndef create_objects_file(selector_list_dict=None):\n    data = []\n    if selector_list_dict:\n        data.append(\"# PAGE OBJECTS FILE >>> (autogenerated)\")\n        data.append(\"\")\n        for key in selector_list_dict.keys():\n            if key == \"None\":\n                if len(selector_list_dict[\"None\"]) > 0:\n                    for pair in selector_list_dict[\"None\"]:\n                        data.append(\"%s = %s\" % (pair[0], pair[1]))\n                    data.append(\"\")\n                else:\n                    pass\n            else:\n                data.append(\"\")\n                data.append(\"class %s(object):\" % key)\n                for pair in selector_list_dict[key]:\n                    data.append(\"    %s = %s\" % (pair[0], pair[1]))\n                data.append(\"\")\n    else:\n        data.append(\"\")\n        data.append(\"class Page(object):\")\n        data.append('    html = \"html\"')\n        data.append(\"\")\n    file_path = PAGE_OBJECTS_FILE\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n    if not selector_list_dict:\n        print('\\n>>> [\"%s\"] was created!' % file_path)\n    else:\n        print('\\n>>> [\"%s\"] was updated!' % file_path)\n\n\ndef scan_objects_file():\n    if not os.path.exists(PAGE_OBJECTS_FILE):\n        create_objects_file()\n\n    page_selectors = {}\n    with open(PAGE_OBJECTS_FILE, mode=\"r\", encoding=\"utf-8\") as f:\n        all_code = f.read()\n\n    var_names = []\n    selectors = []\n    current_class = \"None\"\n    selector_list_dict = {}  # Key = class name / Values are name-value tuples\n    selector_list_dict[current_class] = []\n    selector_list_dict[\"Page\"] = []\n    code_lines = all_code.split(\"\\n\")\n    for line in code_lines:\n        line = line.rstrip()\n        # Handle: class CLASSNAME(object):  OR  class CLASSNAME():\n        data = re.match(r\"\"\"^(\\s*)class\\s+([\\S]+)\\((object|)\\):\\s*$\"\"\", line)\n        if data:\n            whitespace = data.group(1)\n            name = \"%s\" % data.group(2)\n            current_class = name\n            selector_list_dict[current_class] = []\n            page_selectors[\"class %s\" % name] = \".\"\n\n        # Handle: SELECTOR_NAME = \"SELECTOR\"\n        data = re.match(r\"\"\"^(\\s*)(\\S+)\\s*=\\s*([\\S\\s]+)\\s*$\"\"\", line)\n        if data:\n            whitespace = data.group(1)\n            name = data.group(2)\n            selector = data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors[name] = selector\n            var_names.append(name)\n            selectors.append(selector)\n            if whitespace == \"\":\n                current_class = \"None\"\n            if (len(selector) > 2 and selector[0] == \"'\") and (\n                selector[-1] == \"'\" and '\"' not in selector[1:-1]\n            ):\n                selector = '\"' + selector[1:-1] + '\"'\n            if \"\\\\[\" in selector or \"\\\\]\" in selector or \"\\\\.\" in selector:\n                if selector[0] != \"r\":\n                    selector = \"r\" + selector\n            selector_list_dict[current_class].append((name, selector))\n\n        if \"class Page\" not in page_selectors.keys():\n            page_selectors[\"class Page\"] = \".\"\n\n    if len(selector_list_dict[\"Page\"]) == 0:\n        selector_list_dict[\"Page\"].append((\"html\", '\"html\"'))\n    return var_names, selectors, selector_list_dict\n\n\ndef optimize_selector(selector):\n    if (len(selector) > 2 and selector[0] == \"'\") and (\n        selector[-1] == \"'\" and '\"' not in selector[1:-1]\n    ):\n        selector = '\"' + selector[1:-1] + '\"'\n    if \"\\\\[\" in selector or \"\\\\]\" in selector or \"\\\\.\" in selector:\n        if selector[0] != \"r\":\n            selector = \"r\" + selector\n    return selector\n\n\ndef get_next_var_name(existing_names):\n    base_name = \"css_\"\n    for i in range(1, 99999):\n        new_name = \"%s%s\" % (base_name, str(i))\n        if new_name not in existing_names:\n            return new_name\n    raise Exception(\"Out of range! (Selector name generation)\")\n\n\ndef process_test_file(\n    code_lines, selector_dict=None, object_dict=None, add_comments=False\n):\n\n    seleniumbase_lines = []\n    page_selectors = []\n    changed = []  # The classes of page_objects.py to add to the test import\n\n    for line in code_lines:\n        line = line.rstrip()\n\n        # Keep lines with \"%s\" in them as they were\n        if r\"%s\" in line:\n            seleniumbase_lines.append(line)\n            continue\n\n        # Handle self.drag_and_drop(SELECTOR, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.drag_and_drop\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.drag_and_drop\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector1 = \"%s\" % data.group(2)\n            selector1 = remove_extra_slashes(selector1)\n            page_selectors.append(selector1)\n            selector2 = \"%s\" % data.group(3)\n            selector2 = remove_extra_slashes(selector2)\n            page_selectors.append(selector2)\n            comments = data.group(4)\n            command = \"\"\"%sself.drag_and_drop(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector1,\n                selector2,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s, %s\" % (selector1, selector2)\n                selector1 = optimize_selector(selector1)\n                selector2 = optimize_selector(selector2)\n                if selector1 in selector_dict.keys() and (\n                    selector2 in selector_dict.keys()\n                ):\n                    selector_object1 = selector_dict[selector1]\n                    selector_object2 = selector_dict[selector2]\n                    changed.append(selector_object1.split(\".\")[0])\n                    changed.append(selector_object2.split(\".\")[0])\n                    command = \"\"\"%sself.drag_and_drop(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object1,\n                        selector_object2,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name1 = selector1\n                object_name2 = selector2\n                if object_name1 in object_dict.keys() and (\n                    object_name2 in object_dict.keys()\n                ):\n                    selector_object1 = object_dict[object_name1]\n                    selector_object2 = object_dict[object_name2]\n                    changed.append(object_name1.split(\".\")[0])\n                    changed.append(object_name2.split(\".\")[0])\n                    command = \"\"\"%sself.drag_and_drop(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object1,\n                        selector_object2,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.hover_and_click(SELECTOR, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.hover_and_click\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.hover_and_click\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector1 = \"%s\" % data.group(2)\n            selector1 = remove_extra_slashes(selector1)\n            page_selectors.append(selector1)\n            selector2 = \"%s\" % data.group(3)\n            selector2 = remove_extra_slashes(selector2)\n            page_selectors.append(selector2)\n            comments = data.group(4)\n            command = \"\"\"%sself.hover_and_click(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector1,\n                selector2,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s, %s\" % (selector1, selector2)\n                selector1 = optimize_selector(selector1)\n                selector2 = optimize_selector(selector2)\n                if selector1 in selector_dict.keys() and (\n                    selector2 in selector_dict.keys()\n                ):\n                    selector_object1 = selector_dict[selector1]\n                    selector_object2 = selector_dict[selector2]\n                    changed.append(selector_object1.split(\".\")[0])\n                    changed.append(selector_object2.split(\".\")[0])\n                    command = \"\"\"%sself.hover_and_click(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object1,\n                        selector_object2,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name1 = selector1\n                object_name2 = selector2\n                if object_name1 in object_dict.keys() and (\n                    object_name2 in object_dict.keys()\n                ):\n                    selector_object1 = object_dict[object_name1]\n                    selector_object2 = object_dict[object_name2]\n                    changed.append(object_name1.split(\".\")[0])\n                    changed.append(object_name2.split(\".\")[0])\n                    command = \"\"\"%sself.hover_and_click(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object1,\n                        selector_object2,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.*_click(SELECTOR, OTHER_ARGS)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_click\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_click\"\"\"\n                r\"\"\"\\(([\\S]+),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            action = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            other_args = data.group(4)\n            page_selectors.append(selector)\n            comments = data.group(5)\n            command = \"\"\"%sself.%s_click(%s,%s)%s\"\"\" % (\n                whitespace,\n                action,\n                selector,\n                other_args,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.%s_click(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.%s_click(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.*_click(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_click\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_click\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            action = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.%s_click(%s)%s\"\"\" % (\n                whitespace,\n                action,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.%s_click(%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.%s_click(%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.click(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.click\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.click\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.click(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.click(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.click(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.js_click_*(SELECTOR)  * = all/if_present/if_visible\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.js_click_(\\S*)\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.js_click_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            by_type = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.js_click_%s(%s)%s\"\"\" % (\n                whitespace,\n                by_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.js_click_%s(%s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.js_click_%s(%s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.click(SELECTOR, OTHER_ARGS)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.click\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.click\"\"\"\n                r\"\"\"\\(([\\S]+),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            other_args = data.group(3)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.click(%s,%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                other_args,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.click(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.click(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.click_if_visible(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.click_if_visible\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.click_if_visible\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.click_if_visible(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.click_if_visible(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.click_if_visible(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.hover(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.hover\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.hover\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.hover(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.hover(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.hover(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.*_element(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_element\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_element\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            action = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.%s_element(%s)%s\"\"\" % (\n                whitespace,\n                action,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.%s_element(%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.%s_element(%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.*_elements(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_elements\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_elements\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            action = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.%s_elements(%s)%s\"\"\" % (\n                whitespace,\n                action,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.%s_elements(%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.%s_elements(%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.set_text_content(SELECTOR, OTHER_ARGS)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_text_content\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_text_content\"\"\"\n                r\"\"\"\\(([\\S]+),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            other_args = data.group(3)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.set_text_content(%s,%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                other_args,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.set_text_content(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.set_text_content(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.highlight(SELECTOR, OTHER_ARGS)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.highlight\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.highlight\"\"\"\n                r\"\"\"\\(([\\S]+),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            other_args = data.group(3)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.highlight(%s,%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                other_args,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.highlight(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.highlight(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.highlight(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.highlight\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.highlight\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.highlight(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.highlight(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.highlight(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.check_if_unchecked(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.check_if_unchecked\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.check_if_unchecked\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.check_if_unchecked(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.check_if_unchecked(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.check_if_unchecked(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.uncheck_if_checked(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.uncheck_if_checked\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.uncheck_if_checked\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.uncheck_if_checked(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.uncheck_if_checked(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.uncheck_if_checked(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.select_if_unselected(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.select_if_unselected\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.select_if_unselected\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.select_if_unselected(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.select_if_unselected(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.select_if_unselected(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.unselect_if_selected(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.unselect_if_selected\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.unselect_if_selected\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.unselect_if_selected(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.unselect_if_selected(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.unselect_if_selected(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.switch_to_frame(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.switch_to_frame\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.switch_to_frame\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(3)\n            command = \"\"\"%sself.switch_to_frame(%s)%s\"\"\" % (\n                whitespace,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.switch_to_frame(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.switch_to_frame(%s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle with self.frame_switch(SELECTOR):\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.frame_switch\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.frame_switch\"\"\"\n                r\"\"\"\\(([\\S]+)\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            if_type = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%s%s self.frame_switch(%s):%s\"\"\" % (\n                whitespace,\n                if_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%s%s self.frame_switch(%s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%s%s self.frame_switch(%s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.assert_element_*(SELECTOR)  *= present/not_visible/absent\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert_element_(\\S*)\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert_element_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            by_type = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.assert_element_%s(%s)%s\"\"\" % (\n                whitespace,\n                by_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.assert_element_%s(%s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.assert_element_%s(%s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.wait_for_element_*(SELECTOR)  * = present/visible\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_element_(\\S*)\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_element_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            by_type = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.wait_for_element_%s(%s)%s\"\"\" % (\n                whitespace,\n                by_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.wait_for_element_%s(%s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.wait_for_element_%s(%s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.update_text(SELECTOR, TEXT)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.update_text\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.update_text\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(3)\n            comments = data.group(4)\n            command = \"\"\"%sself.update_text(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.update_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.update_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.type(SELECTOR, TEXT)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.type\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.type\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(3)\n            comments = data.group(4)\n            command = \"\"\"%sself.type(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.type(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.type(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.input(SELECTOR, TEXT)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.input\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.input\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(3)\n            comments = data.group(4)\n            command = \"\"\"%sself.input(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.input(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.input(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.write(SELECTOR, TEXT)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.write\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.write\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(3)\n            comments = data.group(4)\n            command = \"\"\"%sself.write(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.write(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.write(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.add_text(SELECTOR, TEXT)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.add_text\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.add_text\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(3)\n            comments = data.group(4)\n            command = \"\"\"%sself.add_text(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.add_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.add_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.send_keys(SELECTOR, TEXT)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.send_keys\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.send_keys\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(3)\n            comments = data.group(4)\n            command = \"\"\"%sself.send_keys(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.send_keys(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.send_keys(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.set_value(SELECTOR, TEXT)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_value\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_value\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(3)\n            comments = data.group(4)\n            command = \"\"\"%sself.set_value(%s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.set_value(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.set_value(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.press_*_arrow(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.press_(\\S*)_arrow\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.press_(\\S*)_arrow\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            arrow = \"%s\" % data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.press_%s_arrow(%s)%s\"\"\" % (\n                whitespace,\n                arrow,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.press_%s_arrow(%s)%s\"\"\" % (\n                        whitespace,\n                        arrow,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.press_%s_arrow(%s)%s\"\"\" % (\n                        whitespace,\n                        arrow,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.press_*_arrow(SELECTOR, TIMES)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.press_(\\S*)_arrow\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.press_(\\S*)_arrow\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            arrow = \"%s\" % data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            times = data.group(4)\n            comments = data.group(5)\n            command = \"\"\"%sself.press_%s_arrow(%s, %s)%s\"\"\" % (\n                whitespace,\n                arrow,\n                selector,\n                times,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.press_%s_arrow(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        arrow,\n                        selector_object,\n                        times,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.press_%s_arrow(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        arrow,\n                        selector_object,\n                        times,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.assert_text(TEXT, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?(r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            text = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.assert_text(%s, %s)%s\"\"\" % (\n                whitespace,\n                text,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.assert_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.assert_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.assert_exact_text(TEXT, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert_exact_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?(r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert_exact_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            text = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.assert_exact_text(%s, %s)%s\"\"\" % (\n                whitespace,\n                text,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.assert_exact_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.assert_exact_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.find_text(TEXT, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.find_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?(r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.find_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            text = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.find_text(%s, %s)%s\"\"\" % (\n                whitespace,\n                text,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.find_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.find_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle if/elif self.is_text_*(TEXT, SELECTOR):  * = present/visible\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.is_text_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?(r?['\"][\\S\\s]+['\"])\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.is_text_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?([\\S]+)\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            if_type = data.group(2)\n            by_type = data.group(3)\n            text = data.group(4)\n            selector = \"%s\" % data.group(5)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(6)\n            command = \"\"\"%s%s self.is_text_%s(%s, %s):%s\"\"\" % (\n                whitespace,\n                if_type,\n                by_type,\n                text,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%s%s self.is_text_%s(%s, %s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        by_type,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%s%s self.is_text_%s(%s, %s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        by_type,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.wait_for_text(TEXT, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?(r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_text\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            text = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.wait_for_text(%s, %s)%s\"\"\" % (\n                whitespace,\n                text,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.wait_for_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.wait_for_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.wait_for_text_visible(TEXT, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_text_visible\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?(r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_text_visible\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            text = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.wait_for_text(%s, %s)%s\"\"\" % (\n                whitespace,\n                text,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.wait_for_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.wait_for_text(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.wait_for_text_not_visible(TEXT, SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_text_not_visible\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?(r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.wait_for_text_not_visible\"\"\"\n                r\"\"\"\\(([\\S\\s]+),\\s?([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            text = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.wait_for_text_not_visible(%s, %s)%s\"\"\" % (\n                whitespace,\n                text,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"%sself.wait_for_text_not_visible(%s, %s)%s\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"%sself.wait_for_text_not_visible(%s, %s)%s\" % (\n                        whitespace,\n                        text,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle if/elif self.is_element_*(SELECTOR):  * = present/visible\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.is_element_(\\S*)\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.is_element_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S]+)\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            if_type = data.group(2)\n            by_type = data.group(3)\n            selector = \"%s\" % data.group(4)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(5)\n            command = \"\"\"%s%s self.is_element_%s(%s):%s\"\"\" % (\n                whitespace,\n                if_type,\n                by_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%s%s self.is_element_%s(%s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%s%s self.is_element_%s(%s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        by_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle if/elif self.is_selected(SELECTOR):\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.is_selected\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\sself\\.is_selected\"\"\"\n                r\"\"\"\\(([\\S]+)\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            if_type = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%s%s self.is_selected(%s):%s\"\"\" % (\n                whitespace,\n                if_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%s%s self.is_selected(%s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%s%s self.is_selected(%s):%s\"\"\" % (\n                        whitespace,\n                        if_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.assert*(self.is_selected(SELECTOR))\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert(\\S*)\\(self\\.is_selected\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert(\\S*)\\(self\\.is_selected\"\"\"\n                r\"\"\"\\(([\\S]+)\\)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            a_type = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sself.assert%s(self.is_selected(%s))%s\"\"\" % (\n                whitespace,\n                a_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.assert%s(self.is_selected(%s))%s\"\"\" % (\n                        whitespace,\n                        a_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.assert%s(self.is_selected(%s))%s\"\"\" % (\n                        whitespace,\n                        a_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.assert*(self.is_element_*(SELECTOR))\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert(\\S*)\\(self\\.is_element_(\\S*)\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.assert(\\S*)\\(self\\.is_element_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S]+)\\)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            a_type = data.group(2)\n            v_type = data.group(3)\n            selector = \"%s\" % data.group(4)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(5)\n            command = \"\"\"%sself.assert%s(self.is_element_%s(%s))%s\"\"\" % (\n                whitespace,\n                a_type,\n                v_type,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"%sself.assert%s(self.is_element_%s(%s))%s\" % (\n                        whitespace,\n                        a_type,\n                        v_type,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"%sself.assert%s(self.is_element_%s(%s))%s\" % (\n                        whitespace,\n                        a_type,\n                        v_type,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.set_attribute(SELECTOR, ATTRIBUTE, VALUE)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_attribute\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\"\"\"\n                r\"\"\",\\s?([\\S\\s]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_attribute\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            attribute = data.group(3)\n            value = data.group(4)\n            comments = data.group(5)\n            command = \"\"\"%sself.set_attribute(%s, %s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                attribute,\n                value,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.set_attribute(%s, %s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        attribute,\n                        value,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.set_attribute(%s, %s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        attribute,\n                        value,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.set_attributes(SELECTOR, ATTRIBUTE, VALUE)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_attributes\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\"\"\"\n                r\"\"\"\\s?([\\S\\s]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.set_attributes\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            attribute = data.group(3)\n            value = data.group(4)\n            comments = data.group(5)\n            command = \"\"\"%sself.set_attributes(%s, %s, %s)%s\"\"\" % (\n                whitespace,\n                selector,\n                attribute,\n                value,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.set_attributes(%s, %s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        attribute,\n                        value,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.set_attributes(%s, %s, %s)%s\"\"\" % (\n                        whitespace,\n                        selector_object,\n                        attribute,\n                        value,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle VAR = self.get_attribute(SELECTOR, ATTRIBUTE)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\s?=\\s?self\\.get_attribute\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?(['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\s?=\\s?self\\.get_attribute\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?(['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            var_name = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            attribute = data.group(4)\n            comments = data.group(5)\n            command = \"\"\"%s%s = self.get_attribute(%s, %s)%s\"\"\" % (\n                whitespace,\n                var_name,\n                selector,\n                attribute,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%s%s = self.get_attribute(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        var_name,\n                        selector_object,\n                        attribute,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%s%s = self.get_attribute(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        var_name,\n                        selector_object,\n                        attribute,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle VAR = self.get_text(SELECTOR)\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\s?=\\s?self\\.get_text\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)(\\S*)\\s?=\\s?self\\.get_text\"\"\"\n                r\"\"\"\\(([\\S]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            var_name = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%s%s = self.get_text(%s)%s\"\"\" % (\n                whitespace,\n                var_name,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%s%s = self.get_text(%s)%s\"\"\" % (\n                        whitespace,\n                        var_name,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%s%s = self.get_text(%s)%s\"\"\" % (\n                        whitespace,\n                        var_name,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle if VAR [in|not in] self.get_text(SELECTOR):\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)if\\s(\\S*\\s?\\S*)\\sin\\s?self\\.get_text\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"])\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)if\\s(\\S*\\s?\\S*)\\sin\\s?self\\.get_text\"\"\"\n                r\"\"\"\\(([\\S]+)\\):([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            var_prefix = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            comments = data.group(4)\n            command = \"\"\"%sif %s in self.get_text(%s):%s\"\"\" % (\n                whitespace,\n                var_prefix,\n                selector,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sif %s in self.get_text(%s):%s\"\"\" % (\n                        whitespace,\n                        var_prefix,\n                        selector_object,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sif %s in self.get_text(%s):%s\"\"\" % (\n                        whitespace,\n                        var_prefix,\n                        selector_object,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.*_element(SELECTOR, OTHER_ARGS)  * = remove/hide\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_element\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_element\"\"\"\n                r\"\"\"\\(([\\S]+),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            action = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            other_args = data.group(4)\n            page_selectors.append(selector)\n            comments = data.group(5)\n            command = \"\"\"%sself.%s_element(%s,%s)%s\"\"\" % (\n                whitespace,\n                action,\n                selector,\n                other_args,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.%s_element(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.%s_element(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.*_elements(SELECTOR, OTHER_ARGS)  * = remove/hide\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_elements\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.(\\S*)_elements\"\"\"\n                r\"\"\"\\(([\\S]+),([\\S\\s]*)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            action = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            other_args = data.group(4)\n            page_selectors.append(selector)\n            comments = data.group(5)\n            command = \"\"\"%sself.%s_elements(%s,%s)%s\"\"\" % (\n                whitespace,\n                action,\n                selector,\n                other_args,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.%s_elements(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.%s_elements(%s,%s)%s\"\"\" % (\n                        whitespace,\n                        action,\n                        selector_object,\n                        other_args,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.select_option_by_*(SELECTOR, TEXT)  * = index/value/text\n        if not object_dict:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.select_option_by_(\\S*)\"\"\"\n                r\"\"\"\\((r?['\"][\\S\\s]+['\"]),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        else:\n            data = re.match(\n                r\"\"\"^(\\s*)self\\.select_option_by_(\\S*)\"\"\"\n                r\"\"\"\\(([\\S]+),\\s?([\\S\\s]+)\\)([\\S\\s]*)$\"\"\",\n                line,\n            )\n        if data:\n            whitespace = data.group(1)\n            by_type = data.group(2)\n            selector = \"%s\" % data.group(3)\n            selector = remove_extra_slashes(selector)\n            page_selectors.append(selector)\n            text = data.group(4)\n            comments = data.group(5)\n            command = \"\"\"%sself.select_option_by_%s(%s, %s)%s\"\"\" % (\n                whitespace,\n                by_type,\n                selector,\n                text,\n                comments,\n            )\n            if selector_dict:\n                if add_comments:\n                    comments = \"  # %s\" % selector\n                selector = optimize_selector(selector)\n                if selector in selector_dict.keys():\n                    selector_object = selector_dict[selector]\n                    changed.append(selector_object.split(\".\")[0])\n                    command = \"\"\"%sself.select_option_by_%s(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            if object_dict:\n                if not add_comments:\n                    comments = \"\"\n                object_name = selector\n                if object_name in object_dict.keys():\n                    selector_object = object_dict[object_name]\n                    changed.append(object_name.split(\".\")[0])\n                    command = \"\"\"%sself.select_option_by_%s(%s, %s)%s\"\"\" % (\n                        whitespace,\n                        by_type,\n                        selector_object,\n                        text,\n                        comments,\n                    )\n            seleniumbase_lines.append(command)\n            continue\n\n        # seleniumbase_lines.append(\"### \" + line)  # untouched lines (Debug)\n        seleniumbase_lines.append(line)\n        continue\n\n    return seleniumbase_lines, page_selectors, changed\n\n\ndef extract_objects():\n    main(shell_command=\"extract-objects\")\n\n\ndef inject_objects():\n    main(shell_command=\"inject-objects\")\n\n\ndef objectify():\n    main(shell_command=\"objectify\")\n\n\ndef revert_objects():\n    main(shell_command=\"revert-objects\")\n\n\ndef main(shell_command):\n    expected_arg = \"[A SeleniumBase Python file]\"\n    num_args = len(sys.argv)\n    command_args = sys.argv[2:]\n\n    add_comments = False\n    if shell_command == \"objectify\" or (\n        shell_command == \"inject-objects\"\n        or (shell_command == \"revert-objects\")\n    ):\n        if len(command_args) >= 2:\n            options = command_args[1:]\n            for option in options:\n                if option == \"-c\" or option == \"--comments\":\n                    add_comments = True\n                else:\n                    invalid_run_command(shell_command)\n\n    if (\n        sys.argv[0].split(\"/\")[-1] == \"seleniumbase\"\n        or (sys.argv[0].split(\"\\\\\")[-1] == \"seleniumbase\")\n        or (sys.argv[0].split(\"/\")[-1] == \"sbase\")\n        or (sys.argv[0].split(\"\\\\\")[-1] == \"sbase\")\n    ):\n        if num_args < 3:\n            invalid_run_command(shell_command)\n        elif num_args > 3:\n            if shell_command == \"extract-objects\":\n                invalid_run_command(shell_command)\n            else:\n                pass\n        else:\n            pass\n    else:\n        invalid_run_command(shell_command)\n\n    seleniumbase_file = command_args[0]\n    if not seleniumbase_file.endswith(\".py\"):\n        raise Exception(\n            \"\\n\\n`%s` is not a Python file!\\n\\n\"\n            \"Expecting: %s\\n\" % (seleniumbase_file, expected_arg)\n        )\n\n    with open(seleniumbase_file, mode=\"r\", encoding=\"utf-8\") as f:\n        all_code = f.read()\n    if \"def test_\" not in all_code:\n        raise Exception(\n            \"\\n\\n`%s` is not a valid SeleniumBase unittest file!\\n\"\n            \"\\nExpecting: %s\\n\" % (seleniumbase_file, expected_arg)\n        )\n    code_lines = all_code.split(\"\\n\")\n    seleniumbase_lines, page_selectors, changed = process_test_file(code_lines)\n    var_names, existing_selectors, selector_list_dict = scan_objects_file()\n    new_page_selectors = []\n\n    for selector in page_selectors:\n        selector = optimize_selector(selector)\n        if selector not in existing_selectors:\n            new_page_selectors.append(selector)\n            var_name = get_next_var_name(var_names)\n            var_names.append(var_name)\n            selector_list_dict[\"Page\"].append((var_name, selector))\n            existing_selectors.append(selector)\n\n    # print(new_page_selectors)  # (For debugging)\n    # print(selector_list_dict)  # (For debugging)\n\n    if shell_command == \"extract-objects\" or shell_command == \"objectify\":\n        create_objects_file(selector_list_dict)\n\n    if shell_command == \"extract-objects\":\n        print(\"\")\n        return\n\n    selector_dict = {}  # Key: selector, Value: object\n    object_dict = {}  # Key: object, Value: selector\n    for key in selector_list_dict.keys():\n        for pair in selector_list_dict[key]:\n            selector_dict[pair[1]] = \"%s.%s\" % (str(key), str(pair[0]))\n            object_name = \"%s.%s\" % (str(key), str(pair[0]))\n            object_dict[object_name] = pair[1]\n\n    good_sel_dict = {}\n    aa, bb, cc = scan_objects_file()\n    for s_key in selector_dict.keys():\n        if s_key in bb:\n            good_sel_dict[s_key] = selector_dict[s_key]\n\n    if shell_command == \"inject-objects\" or shell_command == \"objectify\":\n        seleniumbase_lines, page_selectors, changed = process_test_file(\n            code_lines, selector_dict=good_sel_dict, add_comments=add_comments\n        )\n        added_classes = []\n        for item in changed:\n            if item not in added_classes:\n                added_classes.append(item)\n        for line in seleniumbase_lines:\n            if p_o_import in line:\n                token = line.split(p_o_import)[1].strip()\n                if token in added_classes:\n                    # Don't import page_objects classes if already imported\n                    added_classes.remove(token)\n        if added_classes:\n            sb_lines = []\n            fit_in = False\n            for line in seleniumbase_lines:\n                if line.startswith(\"from\") and \"import\" in line and not fit_in:\n                    fit_in = True\n                    for add_me in added_classes:\n                        import_line = \"%s%s\" % (p_o_import, add_me)\n                        sb_lines.append(import_line)\n                sb_lines.append(line)\n            seleniumbase_lines = sb_lines\n\n    if shell_command == \"revert-objects\":\n        seleniumbase_lines, page_selectors, changed = process_test_file(\n            code_lines, object_dict=object_dict, add_comments=add_comments\n        )\n        removed_classes = []\n        for item in changed:\n            if item not in removed_classes:\n                removed_classes.append(item)\n        if removed_classes:\n            sb_lines = []\n            for line in seleniumbase_lines:\n                if p_o_import in line:\n                    token = line.split(p_o_import)[1].strip()\n                    if token in removed_classes:\n                        continue\n                sb_lines.append(line)\n            seleniumbase_lines = sb_lines\n\n    seleniumbase_code = \"\"\n    for line in seleniumbase_lines:\n        seleniumbase_code += line\n        seleniumbase_code += \"\\n\"\n    seleniumbase_code = seleniumbase_code[:-1]\n    # print (seleniumbase_code)  # (For debugging)\n\n    # Create SeleniumBase test file\n    base_file_name = seleniumbase_file.split(\".py\")[0]\n    converted_file_name = base_file_name + \".py\"  # Change end to make a copy\n    out_file = open(converted_file_name, mode=\"w+\", encoding=\"utf-8\")\n    out_file.writelines(seleniumbase_code)\n    out_file.close()\n    print('\\n>>> [\"%s\"] was updated!\\n' % converted_file_name)\n\n\nif __name__ == \"__main__\":\n    invalid_objectify_run_command()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_print.py",
    "content": "\"\"\"\nPrints the code/text of any file with syntax-highlighting\n\nUsage:\n        seleniumbase print [FILE] [OPTIONS]\n        OR:    sbase print [FILE] [OPTIONS]\nOptions:\n        -n   (Add line Numbers to the rows)\nOutput:\n        Prints the code/text of any file\n        with syntax-highlighting.\n\"\"\"\nimport colorama\nimport os\nimport sys\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** print **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"     seleniumbase print [FILE] [OPTIONS]\\n\"\n    exp += \"     OR:    sbase print [FILE] [OPTIONS]\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"     -n   (Add line Numbers to the rows)\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"     Prints the code/text of any file\\n\"\n    exp += \"     with syntax-highlighting.\\n\"\n    if not msg:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n    else:\n        raise Exception(\"INVALID RUN COMMAND!\\n%s\\n\\n%s\" % (msg, exp))\n\n\ndef sc_ranges():\n    # Get the ranges of special double-width characters.\n    special_char_ranges = [\n        {\"from\": ord(\"\\u4e00\"), \"to\": ord(\"\\u9FFF\")},\n        {\"from\": ord(\"\\u3040\"), \"to\": ord(\"\\u30ff\")},\n        {\"from\": ord(\"\\uac00\"), \"to\": ord(\"\\ud7a3\")},\n        {\"from\": ord(\"\\uff01\"), \"to\": ord(\"\\uff60\")},\n    ]\n    return special_char_ranges\n\n\ndef is_char_wide(char):\n    # Returns True if the special character is Chinese, Japanese, or Korean.\n    sc = any(\n        [range[\"from\"] <= ord(char) <= range[\"to\"] for range in sc_ranges()]\n    )\n    return sc\n\n\ndef get_width(line):\n    # Return the true width of the line. Not the same as line length.\n    # Chinese/Japanese/Korean characters take up two spaces of width.\n    line_length = len(line)\n    for char in line:\n        if is_char_wide(char):\n            line_length += 1\n    return line_length\n\n\ndef main():\n    c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n    c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n    cr = colorama.Style.RESET_ALL\n    line_numbers = False\n    word_wrap = True  # Always use word wrap now\n    help_me = False\n    invalid_cmd = None\n    is_python_file = False\n    code_lang = None\n\n    command_args = sys.argv[2:]\n    file_to_print = command_args[0]\n    if file_to_print.lower().endswith(\".py\"):\n        is_python_file = True\n        code_lang = \"python\"\n    elif file_to_print.lower().endswith(\".js\"):\n        code_lang = \"javascript\"\n    elif file_to_print.lower().endswith(\".md\"):\n        code_lang = \"markdown\"\n    elif file_to_print.lower().endswith(\".html\"):\n        code_lang = \"html\"\n    elif file_to_print.lower().endswith(\".css\"):\n        code_lang = \"css\"\n    elif file_to_print.lower().endswith(\".go\"):\n        code_lang = \"go\"\n    elif file_to_print.lower().endswith(\".java\"):\n        code_lang = \"java\"\n    elif file_to_print.lower().endswith(\".feature\"):\n        code_lang = \"gherkin\"\n    elif file_to_print.lower().endswith(\".txt\"):\n        code_lang = \"javascript\"\n    elif file_to_print.lower().endswith(\".yml\"):\n        code_lang = \"javascript\"\n    elif file_to_print.lower().endswith(\".in\"):\n        code_lang = \"javascript\"\n    elif \".\" not in file_to_print:\n        code_lang = \"markdown\"\n    else:\n        code_lang = file_to_print.split(\".\")[-1].lower()\n\n    if len(command_args) >= 2:\n        options = command_args[1:]\n        for option in options:\n            option = option.lower()\n            if option == \"-n\":\n                line_numbers = True\n            elif option == \"-w\":\n                word_wrap = True\n            else:\n                invalid_cmd = \"\\n===> INVALID OPTION: >> %s <<\\n\" % option\n                invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n                invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n                invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n                invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n                help_me = True\n                break\n\n    if help_me:\n        invalid_run_command(invalid_cmd)\n\n    all_code = None\n    with open(\n        file_to_print, mode=\"r+\", encoding=\"utf-8\", errors=\"ignore\"\n    ) as f:\n        all_code = f.read()\n    all_code = all_code.replace(\"\\t\", \"    \")\n    code_lines = all_code.split(\"\\n\")\n\n    console_width = None  # width of console output when running script\n    used_width = None  # code_width and few spaces on right for padding\n    magic_syntax = None  # the syntax generated by rich.syntax.Syntax()\n    try:\n        console_width = os.get_terminal_size().columns\n        if console_width:\n            console_width = int(console_width)\n    except Exception:\n        console_width = None\n\n    from seleniumbase.console_scripts import rich_helper\n\n    the_code = \"\\n\".join(code_lines)\n    code_width = 1\n    w = 0  # line number whitespace\n    if line_numbers:\n        w = 4\n        num_lines = len(code_lines)\n        if num_lines >= 10:\n            w = 5\n        if num_lines >= 100:\n            w = 6\n        if num_lines >= 1000:\n            w = 7\n        if num_lines >= 10000:\n            w = 8\n\n    if is_python_file:\n        new_sb_lines = []\n        for line in code_lines:\n            if line.endswith(\"  # noqa\") and line.count(\"  # noqa\") == 1:\n                line = line.replace(\"  # noqa\", \"\")\n            line_length2 = len(line)  # Normal Python string length used\n            line_length = get_width(line)  # Special characters count 2X\n            if line_length > code_width:\n                code_width = line_length\n\n            if console_width:\n                # If line is larger than console_width, try to optimize it.\n                # Smart Python word wrap to be used with valid indentation.\n                if line_length + w > console_width:  # 5 is line number ws\n                    if line.strip().startswith(\"#\"):\n                        new_sb_lines.append(line)\n                        continue\n                    elif (\n                        line.count(\"  # \") == 1\n                        and get_width(line.split(\"  # \")[0]) + w\n                        <= console_width\n                    ):\n                        # Line is short enough once comment is removed\n                        line = line.split(\"  # \")[0]\n                        new_sb_lines.append(line)\n                        continue\n                    elif (\n                        line.count(\" # \") == 1\n                        and get_width(line.split(\" # \")[0]) + w\n                        <= console_width\n                    ):\n                        # L-Length good if removing bad flake8 comment\n                        line = line.split(\"  # \")[0]\n                        new_sb_lines.append(line)\n                        continue\n                    if line.startswith(\"from\") and \" import \" in line:\n                        line1 = line.split(\" import \")[0] + \" \\\\\"\n                        line2 = \"    import \" + line.split(\" import \")[1]\n                        new_sb_lines.append(line1)\n                        new_sb_lines.append(line2)\n                        continue\n                    if (\n                        line.count(\"(\") >= 1\n                        and line.count(\"(\") == line.count(\")\")\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        first_paren = line.find(\"(\")\n                        line1 = line[:first_paren + 1]\n                        line2 = new_ws + line[first_paren + 1:]\n                        if (\"):\") not in line2:\n                            new_sb_lines.append(line1)\n                            if get_width(line2) + w > console_width:\n                                if line2.count('\", \"') == 1:\n                                    line2a = line2.split('\", \"')[0] + '\",'\n                                    line2b = (\n                                        new_ws\n                                        + '\"'\n                                        + (line2.split('\", \"')[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"', '\") == 1:\n                                    line2a = line2.split(\"', '\")[0] + \"',\"\n                                    line2b = (\n                                        new_ws\n                                        + \"'\"\n                                        + (line2.split(\"', '\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"://\") == 1 and (\n                                    line2.count('\")') == 1\n                                ):\n                                    line2a = line2.split(\"://\")[0] + '://\"'\n                                    line2b = (\n                                        new_ws\n                                        + '\"'\n                                        + (line2.split(\"://\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    if get_width(line2b) + w > (\n                                        console_width\n                                    ):\n                                        if line2b.count(\"/\") > 0:\n                                            slash_one = line2b.find(\"/\")\n                                            slash_one_p1 = slash_one + 1\n                                            line2b1 = (\n                                                line2b[: slash_one + 1]\n                                                + '\"'\n                                            )\n                                            line2b2 = (\n                                                new_ws\n                                                + '\"'\n                                                + (line2b[slash_one_p1:])\n                                            )\n                                            new_sb_lines.append(line2b1)\n                                            if line2b2.count(\")  # \") == 1:\n                                                line2b2 = (\n                                                    line2b2.split(\")  # \")[\n                                                        0\n                                                    ]\n                                                    + \")\"\n                                                )\n                                            new_sb_lines.append(line2b2)\n                                            continue\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"://\") == 1 and (\n                                    line2.count(\"')\") == 1\n                                ):\n                                    line2a = line2.split(\"://\")[0] + \"://'\"\n                                    line2b = (\n                                        new_ws\n                                        + \"'\"\n                                        + (line2.split(\"://\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    if get_width(line2b) + w > (\n                                        console_width\n                                    ):\n                                        if line2b.count(\"/\") > 0:\n                                            slash_one = line2b.find(\"/\")\n                                            slash_one_p1 = slash_one + 1\n                                            line2b1 = (\n                                                line2b[: slash_one + 1]\n                                                + \"'\"\n                                            )\n                                            line2b2 = (\n                                                new_ws\n                                                + \"'\"\n                                                + (line2b[slash_one_p1:])\n                                            )\n                                            new_sb_lines.append(line2b1)\n                                            if line2b2.count(\")  # \") == 1:\n                                                line2b2 = (\n                                                    line2b2.split(\")  # \")[\n                                                        0\n                                                    ]\n                                                    + \")\"\n                                                )\n                                            new_sb_lines.append(line2b2)\n                                            continue\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\", \") == 1:\n                                    line2a = line2.split(\", \")[0] + \",\"\n                                    line2b = new_ws + line2.split(\", \")[1]\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count('=\"') == 1 and (\n                                    line2.lstrip().startswith(\"'\")\n                                ):\n                                    line2a = line2.split('=\"')[0] + \"='\"\n                                    line2b = (\n                                        new_ws\n                                        + \"'\\\"\"\n                                        + (line2.split('=\"')[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"='\") == 1 and (\n                                    line2.lstrip().startswith('\"')\n                                ):\n                                    line2a = line2.split(\"='\")[0] + '=\"'\n                                    line2b = (\n                                        new_ws\n                                        + \"\\\"'\"\n                                        + (line2.split(\"='\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                            new_sb_lines.append(line2)\n                        elif get_width(line2) + 4 + w <= console_width:\n                            line2 = \"    \" + line2\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                        else:\n                            new_sb_lines.append(line)\n                        continue\n                    if line.count('(\"') == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split('(\"')[0] + \"(\"\n                        line2 = new_ws + '\"' + line.split('(\"')[1]\n                        if (\"):\") not in line2:\n                            new_sb_lines.append(line1)\n                            if get_width(line2) + w > console_width:\n                                if line2.count('\" in self.') == 1:\n                                    line2a = (\n                                        line2.split('\" in self.')[0]\n                                        + '\" in'\n                                    )\n                                    line2b = (\n                                        new_ws\n                                        + \"self.\"\n                                        + (line2.split('\" in self.')[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                            new_sb_lines.append(line2)\n                        elif get_width(line2) + 4 + w <= console_width:\n                            line2 = \"    \" + line2\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                        else:\n                            new_sb_lines.append(line)\n                        continue\n                    if line.count(\"('\") == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"('\")[0] + \"(\"\n                        line2 = new_ws + \"'\" + line.split(\"('\")[1]\n                        if (\"):\") not in line2:\n                            new_sb_lines.append(line1)\n                            if get_width(line2) + w > console_width:\n                                if line2.count(\"' in self.\") == 1:\n                                    line2a = (\n                                        line2.split(\"' in self.\")[0]\n                                        + \"' in\"\n                                    )\n                                    line2b = (\n                                        new_ws\n                                        + \"self.\"\n                                        + (line2.split(\"' in self.\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                            new_sb_lines.append(line2)\n                        elif get_width(line2) + 4 + w <= console_width:\n                            line2 = \"    \" + line2\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                        else:\n                            new_sb_lines.append(line)\n                        continue\n                    if line.count('= \"') == 1 and line.count(\"://\") == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"://\")[0] + '://\" \\\\'\n                        line2 = new_ws + '\"' + line.split(\"://\")[1]\n                        new_sb_lines.append(line1)\n                        if get_width(line2) + w > console_width:\n                            if line2.count(\"/\") > 0:\n                                slash_one = line2.find(\"/\")\n                                slash_one_p1 = slash_one + 1\n                                line2a = line2[: slash_one + 1] + '\" \\\\'\n                                line2b = (\n                                    new_ws + '\"' + line2[slash_one_p1:]\n                                )\n                                new_sb_lines.append(line2a)\n                                new_sb_lines.append(line2b)\n                                continue\n                        new_sb_lines.append(line2)\n                        continue\n                    if line.count(\"= '\") == 1 and line.count(\"://\") == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"://\")[0] + \"://' \\\\\"\n                        line2 = new_ws + \"'\" + line.split(\"://\")[1]\n                        new_sb_lines.append(line1)\n                        if get_width(line2) + w > console_width:\n                            if line2.count(\"/\") > 0:\n                                slash_one = line2.find(\"/\")\n                                slash_one_p1 = slash_one + 1\n                                line2a = line2[: slash_one + 1] + \"' \\\\\"\n                                line2b = (\n                                    new_ws + \"'\" + line2[slash_one_p1:]\n                                )\n                                new_sb_lines.append(line2a)\n                                new_sb_lines.append(line2b)\n                                continue\n                        new_sb_lines.append(line2)\n                        continue\n                    if line.count(\"(self.\") == 1 and (\"):\") not in line:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"(self.\")[0] + \"(\"\n                        line2 = new_ws + \"self.\" + line.split(\"(self.\")[1]\n                        if get_width(line1) + w <= console_width:\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" == \") == 1 and not (\n                        line.endswith(\":\") or (\":  #\") in line\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\" == \")[0] + \" == (\"\n                        line2 = new_ws + line.split(\" == \")[1] + \")\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" == \") == 1 and line.endswith(\":\"):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"        \"\n                        line1 = line.split(\" == \")[0] + \" == (\"\n                        line2 = new_ws + line.split(\" == \")[1][:-1] + \"):\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if (\n                        line.count(\" == \") == 1\n                        and (line.count(\":  #\") == 1)\n                        and (line.find(\" == \") < line.find(\":  #\"))\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"        \"\n                        comments = \"  #\" + line.split(\":  #\")[1]\n                        line0 = line.split(\":  #\")[0] + \":\"\n                        line1 = line0.split(\" == \")[0] + \" == (\"\n                        line2 = new_ws + line0.split(\" == \")[1][:-1] + \"):\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            if (\n                                get_width(line2 + comments) + w\n                                <= console_width\n                            ):\n                                new_sb_lines.append(line2 + comments)\n                            else:\n                                new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" % \") == 1 and (\"):\") not in line:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\" % \")[0] + \" \\\\\"\n                        line2 = new_ws + \"% \" + line.split(\" % \")[1]\n                        if get_width(line1) + w <= console_width:\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" = \") == 1 and (\"  # \") not in line:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\" = \")[0] + \" = (\"\n                        line2 = new_ws + line.split(\" = \")[1] + \")\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                        elif get_width(line1) + w <= console_width:\n                            if line2.count(\" % \") == 1 and not (\n                                line2.endswith(\":\")\n                            ):\n                                whitespace = line_length2 - len(\n                                    line2.lstrip()\n                                )\n                                line2a = line2.split(\" % \")[0] + \" \\\\\"\n                                line2b = (\n                                    new_ws + \"% \" + line2.split(\" % \")[1]\n                                )\n                                if get_width(line2a) + w <= console_width:\n                                    if (\n                                        get_width(line2b) + w\n                                        <= console_width\n                                    ):\n                                        new_sb_lines.append(line1)\n                                        new_sb_lines.append(line2a)\n                                        new_sb_lines.append(line2b)\n                                        continue\n                    if (\n                        line.count(\" = \") == 1\n                        and (line.count(\"  # \") == 1)\n                        and (line.find(\" = \") < line.find(\"  # \"))\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"        \"\n                        comments = \"  # \" + line.split(\"  # \")[1]\n                        line0 = line.split(\"  # \")[0]\n                        line1 = line0.split(\" = \")[0] + \" = (\"\n                        line2 = new_ws + line0.split(\" = \")[1] + \")\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            if (\n                                get_width(line2 + comments) + w\n                                <= console_width\n                            ):\n                                new_sb_lines.append(line2 + comments)\n                            else:\n                                new_sb_lines.append(line2)\n                            continue\n                new_sb_lines.append(line)\n\n        if new_sb_lines:\n            code_lines = new_sb_lines\n            the_code = \"\\n\".join(code_lines)\n\n    if code_lang != \"python\":\n        for line in code_lines:\n            line_length = get_width(line)\n            if line_length > code_width:\n                code_width = line_length\n\n    extra_r_spaces = 2\n    if console_width and (code_width + extra_r_spaces < console_width):\n        used_width = code_width + extra_r_spaces\n\n    the_code = rich_helper.fix_emoji_spacing(the_code)\n    the_theme = \"monokai\"\n    if file_to_print.split(os.sep)[-1].startswith(\".\"):\n        the_theme = \"tango\"\n\n    magic_syntax = rich_helper.process_syntax(\n        the_code,\n        code_lang,\n        theme=the_theme,\n        line_numbers=line_numbers,\n        code_width=used_width,\n        word_wrap=word_wrap,\n    )\n    # ----------------------------------------\n    dash_length = 62  # May change\n    if used_width and used_width + w < console_width:\n        dash_length = used_width + w\n    elif console_width:\n        dash_length = console_width\n    dashes = \"-\" * dash_length\n    print(dashes)\n    print_success = False\n    if code_lang == \"markdown\" and not line_numbers:\n        all_code = rich_helper.fix_emoji_spacing(all_code)\n        if \"<b>*\" not in all_code and \"*<b>\" not in all_code:\n            if \"</b>*\" not in all_code and \"*</b>\" not in all_code:\n                all_code = all_code.replace(\"<b>\", \"**\")\n                all_code = all_code.replace(\"</b>\", \"**\")\n        if \"<code>`\" not in all_code and \"`<code>\" not in all_code:\n            if \"</code>`\" not in all_code and \"`</code>\" not in all_code:\n                all_code = all_code.replace('<code translate=\"no\">', \"``\")\n                all_code = all_code.replace(\"<code>\", \"``\")\n                all_code = all_code.replace(\"</code>\", \"``\")\n        # Display ALL <h> tags as an <h1> because the font size is fixed\n        all_code = all_code.replace(\"\\n<h1>\", \"\\n# \").replace(\"</h1>\", \"\")\n        all_code = all_code.replace(\"\\n<h2>\", \"\\n# \").replace(\"</h2>\", \"\")\n        all_code = all_code.replace(\"\\n<h3>\", \"\\n# \").replace(\"</h3>\", \"\")\n        all_code = all_code.replace(\"\\n<h4>\", \"\\n# \").replace(\"</h4>\", \"\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"summary\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"details\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"span\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"div\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"img\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"li\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"ul\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"a\")\n        all_code = rich_helper.get_code_without_tag(all_code, \"p\")\n        all_code = all_code.replace(\"<br />\", \"\\n\")\n        print_success = rich_helper.display_markdown(all_code)\n        if all_code.endswith(\"\\n\"):\n            print()  # Because \"rich\" skips the last line if new-line\n    elif magic_syntax:\n        print_success = rich_helper.display_code(magic_syntax)\n    if not magic_syntax or not print_success:\n        for line in code_lines:\n            print(line)\n    print(dashes)\n    # ----------------------------------------\n\n\nif __name__ == \"__main__\":\n    invalid_run_command()\n"
  },
  {
    "path": "seleniumbase/console_scripts/sb_recorder.py",
    "content": "\"\"\"\n** recorder **\n\nLaunches the SeleniumBase Recorder Desktop App.\n\nUsage:\n    seleniumbase recorder [OPTIONS]\n           sbase recorder [OPTIONS]\n\nOptions:\n    --uc / --undetected  (Use undetectable mode.)\n    --cdp  (Same as \"--uc\" and \"--undetectable\".)\n    --behave  (Also output Behave/Gherkin files.)\n\nOutput:\n    Launches the SeleniumBase Recorder Desktop App.\n\"\"\"\nimport colorama\nimport os\nimport subprocess\nimport sys\nimport tkinter as tk\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.fixtures import page_utils\nfrom seleniumbase.fixtures import shared_utils\nfrom tkinter import messagebox\n\nsb_config.rec_subprocess_p = None\nsb_config.rec_subprocess_used = False\nsys_executable = sys.executable\nif \" \" in sys_executable:\n    sys_executable = \"python\"\n\n\ndef set_colors(use_colors):\n    c0 = \"\"\n    c1 = \"\"\n    c2 = \"\"\n    c3 = \"\"\n    c4 = \"\"\n    cr = \"\"\n    if use_colors:\n        c0 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n        c2 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n        c3 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX\n        c4 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n        cr = colorama.Style.RESET_ALL\n    return c0, c1, c2, c3, c4, cr\n\n\ndef send_window_to_front(window):\n    window.lift()\n    window.attributes(\"-topmost\", True)\n    window.after_idle(window.attributes, \"-topmost\", False)\n\n\ndef show_already_recording_warning():\n    messagebox.showwarning(\n        \"SeleniumBase Recorder: Already Running!\",\n        \"Please finalize the active recording from the terminal\\n\"\n        'where you opened the Recorder: Type \"c\" and hit Enter.',\n    )\n\n\ndef file_name_error(file_name):\n    error_msg = None\n    if not file_name.endswith(\".py\"):\n        error_msg = 'File name must end with \".py\"!'\n    elif \"*\" in file_name or len(str(file_name)) < 4:\n        error_msg = \"Invalid file name!\"\n    elif file_name.startswith(\"-\"):\n        error_msg = 'File name cannot start with \"-\"!'\n    elif \"/\" in str(file_name) or \"\\\\\" in str(file_name):\n        error_msg = \"File must be created in the current directory!\"\n    return error_msg\n\n\ndef do_recording(file_name, url, overwrite_enabled, use_chrome, window):\n    poll = None\n    if sb_config.rec_subprocess_used:\n        poll = sb_config.rec_subprocess_p.poll()\n    if not sb_config.rec_subprocess_used or poll is not None:\n        pass\n    else:\n        show_already_recording_warning()\n        send_window_to_front(window)\n        poll = sb_config.rec_subprocess_p.poll()\n        if poll is None:\n            return\n\n    file_name = file_name.strip()\n    error_msg = file_name_error(file_name)\n    if error_msg:\n        messagebox.showwarning(\n            \"Invalid filename\", \"Invalid filename: %s\" % error_msg\n        )\n        return\n\n    url = url.strip()\n    if not page_utils.is_valid_url(url):\n        if page_utils.is_valid_url(\"https://\" + url):\n            url = \"https://\" + url\n    if not page_utils.is_valid_url(url):\n        messagebox.showwarning(\n            \"Invalid URL\", \"Enter a valid URL! (Eg. seleniumbase.io)\"\n        )\n    else:\n        if os.path.exists(os.getcwd() + \"/\" + file_name):\n            if not overwrite_enabled:\n                msgbox = tk.messagebox.askquestion(\n                    \"Overwrite?\",\n                    'Are you sure you want to overwrite \"%s\"?' % file_name,\n                    icon=\"warning\",\n                )\n                if msgbox == \"yes\":\n                    os.remove(file_name)\n                else:\n                    tk.messagebox.showinfo(\"Cancelled\", \"Recording Cancelled!\")\n                    return\n            else:\n                os.remove(file_name)\n        add_on = \"\"\n        command_args = sys.argv[2:]\n        if (\n            \"--rec-behave\" in command_args\n            or \"--behave\" in command_args\n            or \"--gherkin\" in command_args\n        ):\n            add_on = \" --rec-behave\"\n        command = (\n            \"%s -m seleniumbase mkrec %s --url=%s --gui\"\n            % (sys_executable, file_name, url)\n        )\n        if '\"' not in url:\n            command = (\n                '%s -m seleniumbase mkrec %s --url=\"%s\" --gui'\n                % (sys_executable, file_name, url)\n            )\n        elif \"'\" not in url:\n            command = (\n                \"%s -m seleniumbase mkrec %s --url='%s' --gui\"\n                % (sys_executable, file_name, url)\n            )\n        if not use_chrome:\n            command += \" --edge\"\n        elif \"--opera\" in command_args:\n            command += \" --opera\"\n        elif \"--brave\" in command_args:\n            command += \" --brave\"\n        elif \"--comet\" in command_args:\n            command += \" --comet\"\n        elif \"--atlas\" in command_args:\n            command += \" --atlas\"\n        elif \"--use-chromium\" in command_args:\n            command += \" --use-chromium\"\n        if (\n            \"--uc\" in command_args\n            or \"--cdp\" in command_args\n            or \"--undetected\" in command_args\n            or \"--undetectable\" in command_args\n        ):\n            command += \" --uc\"\n        if \"--ee\" in command_args:\n            command += \" --ee\"\n        command += add_on\n        poll = None\n        if sb_config.rec_subprocess_used:\n            poll = sb_config.rec_subprocess_p.poll()\n        if not sb_config.rec_subprocess_used or poll is not None:\n            sb_config.rec_subprocess_p = subprocess.Popen(command, shell=True)\n            sb_config.rec_subprocess_used = True\n        else:\n            show_already_recording_warning()\n        send_window_to_front(window)\n\n\ndef do_playback(file_name, use_chrome, window, demo_mode=False):\n    file_name = file_name.strip()\n    error_msg = file_name_error(file_name)\n    if error_msg:\n        messagebox.showwarning(\n            \"Invalid filename\", \"Invalid filename: %s\" % error_msg\n        )\n        return\n    if not os.path.exists(os.getcwd() + \"/\" + file_name):\n        messagebox.showwarning(\n            \"File does not exist\",\n            'File \"%s\" does not exist in the current directory!' % file_name,\n        )\n        return\n    command = \"%s -m pytest %s -q -s\" % (sys_executable, file_name)\n    if shared_utils.is_linux():\n        command += \" --gui\"\n    if not use_chrome:\n        command += \" --edge\"\n    if demo_mode:\n        command += \" --demo\"\n    command_args = sys.argv[2:]\n    if (\n        \"--uc\" in command_args\n        or \"--cdp\" in command_args\n        or \"--undetected\" in command_args\n        or \"--undetectable\" in command_args\n    ):\n        command += \" --uc\"\n    poll = None\n    if sb_config.rec_subprocess_used:\n        poll = sb_config.rec_subprocess_p.poll()\n    if not sb_config.rec_subprocess_used or poll is not None:\n        print(command)\n        subprocess.Popen(command, shell=True)\n    else:\n        messagebox.showwarning(\n            \"SeleniumBase Recorder: Already Running!\",\n            \"Please finalize the active recording from the terminal\\n\"\n            'where you opened the Recorder: Type \"c\" and hit Enter.',\n        )\n    send_window_to_front(window)\n\n\ndef create_tkinter_gui():\n    default_file_name = \"new_recording.py\"\n    window = tk.Tk()\n    window.title(\"SeleniumBase Recorder App\")\n    window.geometry(\"344x388\")\n    frame = tk.Frame(window)\n    frame.pack()\n\n    tk.Label(window, text=\"\").pack()\n    fname = tk.StringVar(value=default_file_name)\n    tk.Label(window, text=\"Enter filename to save recording as:\").pack()\n    entry = tk.Entry(window, textvariable=fname)\n    entry.pack()\n    cbx = tk.IntVar()\n    chk = tk.Checkbutton(window, text=\"Overwrite existing files\", variable=cbx)\n    chk.pack()\n    chk.select()\n    use_stealth = False\n    command_args = sys.argv[2:]\n    if (\n        \"--uc\" in command_args\n        or \"--cdp\" in command_args\n        or \"--undetected\" in command_args\n        or \"--undetectable\" in command_args\n    ):\n        use_stealth = True\n    browser_display = \"Use Chrome over Edge\"\n    if \"--opera\" in command_args:\n        browser_display = \"Use Opera over Edge\"\n    elif \"--brave\" in command_args:\n        browser_display = \"Use Brave over Edge\"\n    elif \"--comet\" in command_args:\n        browser_display = \"Use Comet over Edge\"\n    elif \"--atlas\" in command_args:\n        browser_display = \"Use Atlas over Edge\"\n    cbb = tk.IntVar()\n    if not use_stealth:\n        chkb = tk.Checkbutton(window, text=browser_display, variable=cbb)\n        chkb.pack()\n        if \"--edge\" not in command_args:\n            chkb.select()\n    else:\n        chkb = tk.Checkbutton(\n            window, text=\"Stealthy Chrome Mode\", variable=cbb\n        )\n        chkb.pack()\n        chkb.select()\n        chkb.config(state=tk.DISABLED)\n    tk.Label(window, text=\"\").pack()\n    url = tk.StringVar()\n    tk.Label(window, text=\"Enter the URL to start recording on:\").pack()\n    entry = tk.Entry(window, textvariable=url)\n    entry.pack()\n    entry.focus()\n    entry.bind(\n        \"<Return>\",\n        (\n            lambda _: do_recording(\n                fname.get(), url.get(), cbx.get(), cbb.get(), window\n            )\n        ),\n    )\n    tk.Button(\n        window,\n        text=\"Record\",\n        fg=\"red\",\n        command=lambda: do_recording(\n            fname.get(), url.get(), cbx.get(), cbb.get(), window\n        ),\n    ).pack()\n    tk.Label(window, text=\"\").pack()\n    tk.Label(window, text=\"Playback recording (Normal Mode):\").pack()\n    tk.Button(\n        window,\n        text=\"Playback\",\n        fg=\"green\",\n        command=lambda: do_playback(fname.get(), cbb.get(), window),\n    ).pack()\n    tk.Label(window, text=\"\").pack()\n    tk.Label(window, text=\"Playback recording (Demo Mode):\").pack()\n    try:\n        tk.Button(\n            window,\n            text=\"Playback (Demo Mode)\",\n            fg=\"teal\",\n            command=lambda: do_playback(\n                fname.get(), cbb.get(), window, demo_mode=True\n            ),\n        ).pack()\n    except Exception:\n        tk.Button(\n            window,\n            text=\"Playback (Demo Mode)\",\n            fg=\"blue\",\n            command=lambda: do_playback(\n                fname.get(), cbb.get(), window, demo_mode=True\n            ),\n        ).pack()\n\n    # Bring form window to front\n    send_window_to_front(window)\n    # Use decoy to set correct focus on main window\n    decoy = tk.Tk()\n    decoy.geometry(\"1x1\")\n    decoy.iconify()\n    decoy.update()\n    decoy.deiconify()\n    decoy.destroy()\n    # Start tkinter\n    window.mainloop()\n    end_program()\n\n\ndef recorder_still_running():\n    poll = None\n    if sb_config.rec_subprocess_used:\n        try:\n            poll = sb_config.rec_subprocess_p.poll()\n        except Exception:\n            return False\n    else:\n        return False\n    if poll is not None:\n        return False\n    return True\n\n\ndef show_still_running_warning():\n    \"\"\"Give the user a chance to end the recording safely via the\n    pytest pdb Debug Mode so that processes such as chromedriver\n    and Python don't remain open and hanging in the background.\"\"\"\n    messagebox.showwarning(\n        \"SeleniumBase Recorder: Still Running!\",\n        \"Please finalize the active recording from the terminal\\n\"\n        'where you opened the Recorder: Type \"c\" and hit Enter.\\n'\n        \"(Then you can safely close this alert.)\",\n    )\n\n\ndef end_program():\n    if recorder_still_running():\n        show_still_running_warning()\n\n\ndef main():\n    use_colors = True\n    if shared_utils.is_linux():\n        use_colors = False\n    c0, c1, c2, c3, c4, cr = set_colors(use_colors)\n    message = \"\"\n    message += c2\n    message += \"*\"\n    message += c4\n    message += \" Starting the \"\n    message += c0\n    message += \"Selenium\"\n    message += c1\n    message += \"Base\"\n    message += c2\n    message += \" \"\n    message += c3\n    message += \"Recorder\"\n    message += c4\n    message += \" Desktop App\"\n    message += c2\n    message += \"...\"\n    message += cr\n    print(message)\n    create_tkinter_gui()\n\n\nif __name__ == \"__main__\":\n    print('To open the Recorder Desktop App: \"seleniumbase recorder\"')\n"
  },
  {
    "path": "seleniumbase/core/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/core/application_manager.py",
    "content": "import time\n\n\nclass ApplicationManager:\n    \"\"\"Generating application strings for the Testcase Database.\"\"\"\n\n    @classmethod\n    def generate_application_string(cls, test):\n        \"\"\"Generate a string based on some of the given information\n        that's pulled from the test object: app_env, start_time.\"\"\"\n\n        app_env = \"test\"\n        if hasattr(test, \"env\"):\n            app_env = test.env\n        elif hasattr(test, \"environment\"):\n            app_env = test.environment\n\n        start_time = int(time.time() * 1000)\n\n        return \"%s.%s\" % (app_env, start_time)\n"
  },
  {
    "path": "seleniumbase/core/browser_launcher.py",
    "content": "import fasteners\nimport json\nimport logging\nimport os\nimport platform\nimport re\nimport shutil\nimport subprocess\nimport sys\nimport time\nimport types\nimport urllib3\nimport warnings\nfrom contextlib import suppress\nfrom filelock import FileLock\nfrom selenium import webdriver\nfrom selenium.common.exceptions import ElementClickInterceptedException\nfrom selenium.common.exceptions import InvalidSessionIdException\nfrom selenium.common.exceptions import SessionNotCreatedException\nfrom selenium.webdriver.chrome.service import Service as ChromeService\nfrom selenium.webdriver.common.options import ArgOptions\nfrom selenium.webdriver.common.service import utils as service_utils\nfrom selenium.webdriver.edge.service import Service as EdgeService\nfrom selenium.webdriver.firefox.service import Service as FirefoxService\nfrom selenium.webdriver.safari.service import Service as SafariService\nfrom seleniumbase import config as sb_config\nfrom seleniumbase import decorators\nfrom seleniumbase import drivers  # webdriver storage folder for SeleniumBase\nfrom seleniumbase.drivers import cft_drivers  # chrome-for-testing\nfrom seleniumbase.drivers import chs_drivers  # chrome-headless-shell\nfrom seleniumbase.drivers import opera_drivers  # still uses chromedriver\nfrom seleniumbase.drivers import brave_drivers  # still uses chromedriver\nfrom seleniumbase.drivers import comet_drivers  # still uses chromedriver\nfrom seleniumbase.drivers import atlas_drivers  # still uses chromedriver\nfrom seleniumbase.drivers import chromium_drivers  # still uses chromedriver\nfrom seleniumbase import extensions  # browser extensions storage folder\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import detect_b_ver\nfrom seleniumbase.core import download_helper\nfrom seleniumbase.core import proxy_helper\nfrom seleniumbase.core import sb_driver\nfrom seleniumbase.core import sb_cdp\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import js_utils\nfrom seleniumbase.fixtures import page_actions\nfrom seleniumbase.fixtures import shared_utils\n\nurllib3.disable_warnings()\nDRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))\nDRIVER_DIR_CFT = os.path.dirname(os.path.realpath(cft_drivers.__file__))\nDRIVER_DIR_CHS = os.path.dirname(os.path.realpath(chs_drivers.__file__))\nDRIVER_DIR_OPERA = os.path.dirname(os.path.realpath(opera_drivers.__file__))\nDRIVER_DIR_BRAVE = os.path.dirname(os.path.realpath(brave_drivers.__file__))\nDRIVER_DIR_COMET = os.path.dirname(os.path.realpath(comet_drivers.__file__))\nDRIVER_DIR_ATLAS = os.path.dirname(os.path.realpath(atlas_drivers.__file__))\nDRIVER_DIR_CHROMIUM = os.path.dirname(\n    os.path.realpath(chromium_drivers.__file__)\n)\n# Make sure that the SeleniumBase DRIVER_DIR is at the top of the System PATH\n# (Changes to the System PATH with os.environ only last during the test run)\nif not os.environ[\"PATH\"].startswith(DRIVER_DIR):\n    # Remove existing SeleniumBase DRIVER_DIR from System PATH if present\n    os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(DRIVER_DIR, \"\")\n    # If two path separators are next to each other, replace with just one\n    os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(\n        os.pathsep + os.pathsep, os.pathsep\n    )\n    # Put the SeleniumBase DRIVER_DIR at the beginning of the System PATH\n    os.environ[\"PATH\"] = DRIVER_DIR + os.pathsep + os.environ[\"PATH\"]\nEXTENSIONS_DIR = os.path.dirname(os.path.realpath(extensions.__file__))\nDISABLE_CSP_ZIP_PATH = os.path.join(EXTENSIONS_DIR, \"disable_csp.zip\")\nAD_BLOCK_ZIP_PATH = os.path.join(EXTENSIONS_DIR, \"ad_block.zip\")\nRECORDER_ZIP_PATH = os.path.join(EXTENSIONS_DIR, \"recorder.zip\")\nSBASE_EXT_ZIP_PATH = os.path.join(EXTENSIONS_DIR, \"sbase_ext.zip\")\nDOWNLOADS_FOLDER = download_helper.get_downloads_folder()\nPROXY_ZIP_PATH = proxy_helper.PROXY_ZIP_PATH\nPROXY_ZIP_LOCK = proxy_helper.PROXY_ZIP_LOCK\nPROXY_DIR_PATH = proxy_helper.PROXY_DIR_PATH\nPROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK\nLOCAL_CHROMEDRIVER = None\nLOCAL_GECKODRIVER = None\nLOCAL_EDGEDRIVER = None\nLOCAL_IEDRIVER = None\nLOCAL_HEADLESS_IEDRIVER = None\nLOCAL_UC_DRIVER = None\nARCH = platform.architecture()[0]\nIS_ARM_MAC = shared_utils.is_arm_mac()\nIS_MAC = shared_utils.is_mac()\nIS_LINUX = shared_utils.is_linux()\nIS_WINDOWS = shared_utils.is_windows()\nif IS_MAC or IS_LINUX:\n    LOCAL_CHROMEDRIVER = DRIVER_DIR + \"/chromedriver\"\n    LOCAL_GECKODRIVER = DRIVER_DIR + \"/geckodriver\"\n    LOCAL_EDGEDRIVER = DRIVER_DIR + \"/msedgedriver\"\n    LOCAL_UC_DRIVER = DRIVER_DIR + \"/uc_driver\"\nelif IS_WINDOWS:\n    LOCAL_EDGEDRIVER = DRIVER_DIR + \"/msedgedriver.exe\"\n    LOCAL_IEDRIVER = DRIVER_DIR + \"/IEDriverServer.exe\"\n    LOCAL_HEADLESS_IEDRIVER = DRIVER_DIR + \"/headless_ie_selenium.exe\"\n    LOCAL_CHROMEDRIVER = DRIVER_DIR + \"/chromedriver.exe\"\n    LOCAL_GECKODRIVER = DRIVER_DIR + \"/geckodriver.exe\"\n    LOCAL_UC_DRIVER = DRIVER_DIR + \"/uc_driver.exe\"\nelse:\n    # Cannot determine system\n    pass  # SeleniumBase will use web drivers from the System PATH by default\n\n\ndef log_d(message):\n    \"\"\"If setting sb_config.settings.HIDE_DRIVER_DOWNLOADS to True,\n    output from driver downloads are logged instead of printed.\"\"\"\n    if getattr(settings, \"HIDE_DRIVER_DOWNLOADS\", None):\n        logging.debug(message)\n    else:\n        print(message)\n\n\ndef override_driver_dir(driver_dir):\n    if (\n        driver_dir\n        and isinstance(driver_dir, str)\n        and os.path.exists(driver_dir)\n    ):\n        driver_dir = os.path.realpath(driver_dir)\n        sb_config.settings.NEW_DRIVER_DIR = driver_dir\n        if (\n            not os.environ[\"PATH\"].startswith(driver_dir)\n            and len(driver_dir) >= 4\n        ):\n            os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(driver_dir, \"\")\n            os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(\n                os.pathsep + os.pathsep, os.pathsep\n            )\n            os.environ[\"PATH\"] = driver_dir + os.pathsep + os.environ[\"PATH\"]\n    elif (\n        not driver_dir\n        or not isinstance(driver_dir, str)\n        or not os.path.exists(driver_dir)\n    ):\n        bad_dir = \"\"\n        if driver_dir and isinstance(driver_dir, str):\n            bad_dir = os.path.realpath(driver_dir)\n        log_d(\n            \"\\n* Warning: Cannot set driver_dir to nonexistent directory:\\n%s\"\n            \"\\n* Will use the default folder instead:\\n%s\"\n            % (bad_dir, DRIVER_DIR)\n        )\n\n\ndef make_driver_executable_if_not(driver_path):\n    # Verify driver has executable permissions. If not, add them.\n    permissions = oct(os.stat(driver_path)[0])[-3:]\n    if \"4\" in permissions or \"6\" in permissions:\n        # We want at least a '5' or '7' to make sure it's executable\n        shared_utils.make_executable(driver_path)\n\n\ndef extend_driver(\n    driver, proxy_auth=False, use_uc=True, recorder_ext=False\n):\n    # Extend the driver with new methods\n    driver.default_find_element = driver.find_element\n    driver.default_find_elements = driver.find_elements\n    driver.default_add_cookie = driver.add_cookie\n    driver.default_get_cookie = driver.get_cookie\n    driver.default_delete_cookie = driver.delete_cookie\n    driver.default_back = driver.back\n    driver.default_forward = driver.forward\n    driver.default_refresh = driver.refresh\n    DM = sb_driver.DriverMethods(driver)\n    driver.find_element = DM.find_element\n    driver.find_elements = DM.find_elements\n    driver.add_cookie = DM.add_cookie\n    driver.get_cookie = DM.get_cookie\n    driver.delete_cookie = DM.delete_cookie\n    driver.back = DM.back\n    driver.forward = DM.forward\n    driver.refresh = DM.refresh\n    driver.locator = DM.locator\n    page = types.SimpleNamespace()\n    page.open = DM.open_url\n    page.click = DM.click\n    page.click_link = DM.click_link\n    page.click_if_visible = DM.click_if_visible\n    page.click_active_element = DM.click_active_element\n    page.send_keys = DM.send_keys\n    page.press_keys = DM.press_keys\n    page.type = DM.update_text\n    page.submit = DM.submit\n    page.assert_element = DM.assert_element_visible\n    page.assert_element_present = DM.assert_element_present\n    page.assert_element_not_visible = DM.assert_element_not_visible\n    page.assert_text = DM.assert_text\n    page.assert_exact_text = DM.assert_exact_text\n    page.assert_non_empty_text = DM.assert_non_empty_text\n    page.assert_text_not_visible = DM.assert_text_not_visible\n    page.wait_for_element = DM.wait_for_element\n    page.wait_for_text = DM.wait_for_text\n    page.wait_for_exact_text = DM.wait_for_exact_text\n    page.wait_for_non_empty_text = DM.wait_for_non_empty_text\n    page.wait_for_text_not_visible = DM.wait_for_text_not_visible\n    page.wait_for_and_accept_alert = DM.wait_for_and_accept_alert\n    page.wait_for_and_dismiss_alert = DM.wait_for_and_dismiss_alert\n    page.is_element_present = DM.is_element_present\n    page.is_element_visible = DM.is_element_visible\n    page.is_text_visible = DM.is_text_visible\n    page.is_exact_text_visible = DM.is_exact_text_visible\n    page.is_attribute_present = DM.is_attribute_present\n    page.is_non_empty_text_visible = DM.is_non_empty_text_visible\n    page.get_text = DM.get_text\n    page.find_element = DM.find_element\n    page.find_elements = DM.find_elements\n    page.locator = DM.locator\n    page.get_current_url = DM.get_current_url\n    page.get_page_source = DM.get_page_source\n    page.get_title = DM.get_title\n    page.get_page_title = DM.get_title\n    page.switch_to_default_window = DM.switch_to_default_window\n    page.switch_to_newest_window = DM.switch_to_newest_window\n    page.open_new_window = DM.open_new_window\n    page.open_new_tab = DM.open_new_tab\n    page.switch_to_window = DM.switch_to_window\n    page.switch_to_tab = DM.switch_to_tab\n    page.switch_to_frame = DM.switch_to_frame\n    driver.page = page\n    js = types.SimpleNamespace()\n    js.js_click = DM.js_click\n    js.get_active_element_css = DM.get_active_element_css\n    js.get_locale_code = DM.get_locale_code\n    js.get_origin = DM.get_origin\n    js.get_user_agent = DM.get_user_agent\n    js.highlight = DM.highlight\n    driver.js = js\n    driver.open = DM.open_url\n    driver.click = DM.click\n    driver.click_link = DM.click_link\n    driver.click_if_visible = DM.click_if_visible\n    driver.click_active_element = DM.click_active_element\n    driver.send_keys = DM.send_keys\n    driver.press_keys = DM.press_keys\n    driver.type = DM.update_text\n    driver.submit = DM.submit\n    driver.assert_element = DM.assert_element_visible\n    driver.assert_element_present = DM.assert_element_present\n    driver.assert_element_not_visible = DM.assert_element_not_visible\n    driver.assert_text = DM.assert_text\n    driver.assert_exact_text = DM.assert_exact_text\n    driver.assert_non_empty_text = DM.assert_non_empty_text\n    driver.assert_text_not_visible = DM.assert_text_not_visible\n    driver.wait_for_element = DM.wait_for_element\n    driver.wait_for_element_visible = DM.wait_for_element_visible\n    driver.wait_for_element_present = DM.wait_for_element_present\n    driver.wait_for_element_absent = DM.wait_for_element_absent\n    driver.wait_for_element_not_visible = DM.wait_for_element_not_visible\n    driver.wait_for_selector = DM.wait_for_selector\n    driver.wait_for_text = DM.wait_for_text\n    driver.wait_for_exact_text = DM.wait_for_exact_text\n    driver.wait_for_non_empty_text = DM.wait_for_non_empty_text\n    driver.wait_for_text_not_visible = DM.wait_for_text_not_visible\n    driver.wait_for_and_accept_alert = DM.wait_for_and_accept_alert\n    driver.wait_for_and_dismiss_alert = DM.wait_for_and_dismiss_alert\n    driver.is_element_present = DM.is_element_present\n    driver.is_element_visible = DM.is_element_visible\n    driver.is_text_visible = DM.is_text_visible\n    driver.is_exact_text_visible = DM.is_exact_text_visible\n    driver.is_attribute_present = DM.is_attribute_present\n    driver.is_non_empty_text_visible = DM.is_non_empty_text_visible\n    driver.is_valid_url = DM.is_valid_url\n    driver.is_alert_present = DM.is_alert_present\n    driver.is_online = DM.is_online\n    driver.is_connected = DM.is_connected\n    driver.is_uc_mode_active = DM.is_uc_mode_active\n    driver.is_cdp_mode_active = DM.is_cdp_mode_active\n    driver.js_click = DM.js_click\n    driver.get_text = DM.get_text\n    driver.get_active_element_css = DM.get_active_element_css\n    driver.get_locale_code = DM.get_locale_code\n    driver.get_screen_rect = DM.get_screen_rect\n    driver.get_origin = DM.get_origin\n    driver.get_user_agent = DM.get_user_agent\n    driver.get_cookie_string = DM.get_cookie_string\n    driver.highlight = DM.highlight\n    driver.highlight_click = DM.highlight_click\n    driver.highlight_if_visible = DM.highlight_if_visible\n    driver.sleep = time.sleep\n    driver.get_attribute = DM.get_attribute\n    driver.get_parent = DM.get_parent\n    driver.get_current_url = DM.get_current_url\n    driver.get_page_source = DM.get_page_source\n    driver.get_title = DM.get_title\n    driver.get_page_title = DM.get_title\n    driver.switch_to_default_window = DM.switch_to_default_window\n    driver.switch_to_newest_window = DM.switch_to_newest_window\n    driver.open_new_window = DM.open_new_window\n    driver.open_new_tab = DM.open_new_tab\n    driver.switch_to_window = DM.switch_to_window\n    driver.switch_to_tab = DM.switch_to_tab\n    driver.switch_to_frame = DM.switch_to_frame\n    driver.reset_window_size = DM.reset_window_size\n    if recorder_ext:\n        from seleniumbase.js_code.recorder_js import recorder_js\n        recorder_code = (\n            \"\"\"window.onload = function() { %s };\"\"\" % recorder_js\n        )\n        driver.execute_cdp_cmd(\n            \"Page.addScriptToEvaluateOnNewDocument\", {\"source\": recorder_code}\n        )\n    if hasattr(driver, \"proxy\"):\n        driver.set_wire_proxy = DM.set_wire_proxy\n    completed_loads = []\n    for ext_dir in sb_config._ext_dirs:\n        if ext_dir not in completed_loads:\n            completed_loads.append(ext_dir)\n            if not use_uc and os.path.exists(os.path.realpath(ext_dir)):\n                with suppress(Exception):\n                    driver.webextension.install(os.path.realpath(ext_dir))\n    driver._is_using_auth = False\n    if proxy_auth:\n        driver._is_using_auth = True\n        if not use_uc and os.path.exists(proxy_helper.PROXY_DIR_PATH):\n            with suppress(Exception):\n                driver.webextension.install(proxy_helper.PROXY_DIR_PATH)\n        # Proxy needs a moment to load in Manifest V3\n        if use_uc:\n            time.sleep(0.14)\n        else:\n            time.sleep(0.28)\n    return driver\n\n\n@decorators.rate_limited(4)\ndef requests_get(url, proxy_string=None):\n    import requests\n\n    protocol = \"http\"\n    proxies = None\n    if proxy_string:\n        if proxy_string.endswith(\":443\"):\n            protocol = \"https\"\n        elif \"socks4\" in proxy_string:\n            protocol = \"socks4\"\n        elif \"socks5\" in proxy_string:\n            protocol = \"socks5\"\n        proxies = {protocol: proxy_string}\n    response = None\n    try:\n        response = requests.get(url, proxies=proxies, timeout=1.25)\n    except Exception:\n        # Prevent SSLCertVerificationError / CERTIFICATE_VERIFY_FAILED\n        url = url.replace(\"https://\", \"http://\")\n        time.sleep(0.04)\n        response = requests.get(url, proxies=proxies, timeout=2.75)\n    return response\n\n\ndef get_latest_chromedriver_version():\n    from seleniumbase.console_scripts import sb_install\n    return sb_install.get_latest_stable_chromedriver_version()\n\n\ndef chromedriver_on_path():\n    paths = os.environ[\"PATH\"].split(os.pathsep)\n    for path in paths:\n        if (\n            not IS_WINDOWS\n            and os.path.exists(os.path.join(path, \"chromedriver\"))\n        ):\n            return os.path.join(path, \"chromedriver\")\n        elif (\n            IS_WINDOWS\n            and os.path.exists(os.path.join(path, \"chromedriver.exe\"))\n        ):\n            return os.path.join(path, \"chromedriver.exe\")\n    return None\n\n\ndef get_uc_driver_version(full=False, local_uc_driver=None):\n    if not local_uc_driver:\n        local_uc_driver = LOCAL_UC_DRIVER\n    uc_driver_version = None\n    if os.path.exists(local_uc_driver):\n        with suppress(Exception):\n            output = subprocess.check_output(\n                '\"%s\" --version' % local_uc_driver, shell=True\n            )\n            if IS_WINDOWS:\n                output = output.decode(\"latin1\")\n            else:\n                output = output.decode(\"utf-8\")\n            full_version = output.split(\" \")[1]\n            output = output.split(\" \")[1].split(\".\")[0]\n            if int(output) >= 72:\n                if full:\n                    uc_driver_version = full_version\n                else:\n                    uc_driver_version = output\n    return uc_driver_version\n\n\ndef find_chromedriver_version_to_use(use_version, driver_version):\n    # Note: https://chromedriver.chromium.org/downloads stops at 114.\n    # Future drivers are part of the Chrome-for-Testing collection.\n    if (\n        driver_version\n        and str(driver_version).split(\".\")[0].isdigit()\n        and int(str(driver_version).split(\".\")[0]) >= 72\n    ):\n        use_version = str(driver_version)\n    elif driver_version and not str(driver_version).split(\".\")[0].isdigit():\n        from seleniumbase.console_scripts import sb_install\n        driver_version = driver_version.lower()\n        if driver_version == \"stable\" or driver_version == \"latest\":\n            use_version = sb_install.get_latest_stable_chromedriver_version()\n        elif driver_version == \"beta\":\n            use_version = sb_install.get_latest_beta_chromedriver_version()\n        elif driver_version == \"dev\":\n            use_version = sb_install.get_latest_dev_chromedriver_version()\n        elif driver_version == \"canary\":\n            use_version = sb_install.get_latest_canary_chromedriver_version()\n        elif driver_version == \"previous\" or driver_version == \"latest-1\":\n            use_version = sb_install.get_latest_stable_chromedriver_version()\n            use_version = str(int(use_version.split(\".\")[0]) - 1)\n        elif driver_version == \"mlatest\":\n            if use_version.split(\".\")[0].isdigit():\n                major = use_version.split(\".\")[0]\n                if int(major) >= 115:\n                    use_version = (\n                        sb_install.get_cft_latest_version_from_milestone(major)\n                    )\n    return use_version\n\n\ndef find_edgedriver_version_to_use(use_version, driver_version):\n    if (\n        driver_version\n        and str(driver_version).split(\".\")[0].isdigit()\n        and int(str(driver_version).split(\".\")[0]) >= 80\n    ):\n        use_version = str(driver_version)\n    return use_version\n\n\ndef has_captcha(text):\n    if (\n        \"<title>403 Forbidden</title>\" in text\n        or \"Permission Denied</title>\" in text\n        or 'id=\"challenge-error-text\"' in text\n        or \"/challenge-platform/h/b/\" in text\n        or \"<title>Just a moment...\" in text\n        or 'action=\"/?__cf_chl_f_tk' in text\n        or 'id=\"challenge-widget-' in text\n        or 'src=\"chromedriver.js\"' in text\n        or \"com/recaptcha/api.js\" in text\n        or 'class=\"g-recaptcha\"' in text\n        or 'content=\"Pixelscan\"' in text\n        or 'id=\"challenge-form\"' in text\n        or \"window._cf_chl_opt\" in text\n        or \"/turnstile/\" in text\n    ):\n        return True\n    return False\n\n\ndef __is_cdp_swap_needed(driver):\n    \"\"\"If the driver is disconnected, use a CDP method when available.\"\"\"\n    return shared_utils.is_cdp_swap_needed(driver)\n\n\ndef uc_execute_cdp_cmd(driver, *args, **kwargs):\n    if not driver.is_connected():\n        driver.connect()\n    return driver.default_execute_cdp_cmd(*args, **kwargs)\n\n\ndef updated_get(driver, url):\n    if url and \":\" not in url and \".\" in url:\n        url = \"https:\" + url\n    driver.default_get(url)\n\n\ndef uc_special_open_if_cf(\n    driver,\n    url,\n    proxy_string=None,\n    mobile_emulator=None,\n    device_width=None,\n    device_height=None,\n    device_pixel_ratio=None,\n):\n    if url and \":\" not in url and \".\" in url:\n        url = \"https:\" + url\n    if url.startswith(\"http:\") or url.startswith(\"https:\"):\n        special = False\n        with suppress(Exception):\n            req_get = requests_get(url, proxy_string)\n            status_str = str(req_get.status_code)\n            if (\n                status_str.startswith(\"3\")\n                or status_str.startswith(\"4\")\n                or status_str.startswith(\"5\")\n                or has_captcha(req_get.text)\n            ):\n                special = True\n                if status_str == \"403\" or status_str == \"429\":\n                    time.sleep(0.06)  # Forbidden / Blocked! (Wait first!)\n        if special and not hasattr(driver, \"cdp_base\"):\n            time.sleep(0.05)\n            with driver:\n                driver.execute_script('window.open(\"%s\",\"_blank\");' % url)\n                driver.close()\n                if mobile_emulator:\n                    page_actions.switch_to_window(\n                        driver, driver.window_handles[-1], 2\n                    )\n                    uc_metrics = {}\n                    if (\n                        isinstance(device_width, int)\n                        and isinstance(device_height, int)\n                        and isinstance(device_pixel_ratio, (int, float))\n                    ):\n                        uc_metrics[\"width\"] = device_width\n                        uc_metrics[\"height\"] = device_height\n                        uc_metrics[\"pixelRatio\"] = device_pixel_ratio\n                    else:\n                        uc_metrics[\"width\"] = constants.Mobile.WIDTH\n                        uc_metrics[\"height\"] = constants.Mobile.HEIGHT\n                        uc_metrics[\"pixelRatio\"] = constants.Mobile.RATIO\n                    set_device_metrics_override = dict(\n                        {\n                            \"width\": uc_metrics[\"width\"],\n                            \"height\": uc_metrics[\"height\"],\n                            \"deviceScaleFactor\": uc_metrics[\"pixelRatio\"],\n                            \"mobile\": True\n                        }\n                    )\n                    with suppress(Exception):\n                        driver.execute_cdp_cmd(\n                            'Emulation.setDeviceMetricsOverride',\n                            set_device_metrics_override\n                        )\n            if not mobile_emulator:\n                page_actions.switch_to_window(\n                    driver, driver.window_handles[-1], 2\n                )\n        else:\n            driver.default_get(url)  # The original one\n    else:\n        driver.default_get(url)  # The original one\n    return None\n\n\ndef uc_open(driver, url):\n    url = shared_utils.fix_url_as_needed(url)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.get(url)\n        time.sleep(0.3)\n        return\n    if (url.startswith(\"http:\") or url.startswith(\"https:\")):\n        with driver:\n            script = 'window.location.href = \"%s\";' % url\n            js_utils.call_me_later(driver, script, 5)\n    else:\n        driver.default_get(url)  # The original one\n    return None\n\n\ndef uc_open_with_tab(driver, url):\n    url = shared_utils.fix_url_as_needed(url)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.get(url)\n        time.sleep(0.3)\n        return\n    if (url.startswith(\"http:\") or url.startswith(\"https:\")):\n        if not hasattr(driver, \"cdp_base\"):\n            with driver:\n                driver.execute_script('window.open(\"%s\",\"_blank\");' % url)\n                driver.close()\n        else:\n            driver.cdp.open(url)\n        page_actions.switch_to_window(driver, driver.window_handles[-1], 2)\n    else:\n        driver.default_get(url)  # The original one\n    return None\n\n\ndef uc_open_with_reconnect(driver, url, reconnect_time=None):\n    \"\"\"Open a url, disconnect chromedriver, wait, and reconnect.\"\"\"\n    if (\n        hasattr(sb_config, \"_cdp_browser\")\n        and sb_config._cdp_browser in [\"comet\", \"opera\", \"atlas\"]\n    ):\n        if not __is_cdp_swap_needed(driver):\n            if not driver.current_url.startswith(\n                (\"about\", \"data\", \"chrome\")\n            ):\n                driver.get(\"about:blank\")\n            uc_activate_cdp_mode(driver, url)\n        else:\n            driver.cdp.open(url)\n        return\n    url = shared_utils.fix_url_as_needed(url)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.get(url)\n        time.sleep(0.3)\n        return\n    if not reconnect_time:\n        reconnect_time = constants.UC.RECONNECT_TIME\n    if (url.startswith(\"http:\") or url.startswith(\"https:\")):\n        script = 'window.open(\"%s\",\"_blank\");' % url\n        if not hasattr(driver, \"cdp_base\"):\n            driver.execute_script(script)\n            time.sleep(0.05)\n            driver.close()\n        else:\n            driver.cdp.open(url)\n        if reconnect_time == \"disconnect\":\n            driver.disconnect()\n            time.sleep(0.008)\n        else:\n            driver.reconnect(reconnect_time)\n            time.sleep(0.004)\n            try:\n                page_actions.switch_to_window(\n                    driver, driver.window_handles[-1], 2\n                )\n            except InvalidSessionIdException:\n                time.sleep(0.05)\n                page_actions.switch_to_window(\n                    driver, driver.window_handles[-1], 2\n                )\n    else:\n        driver.default_get(url)  # The original one\n    return None\n\n\ndef uc_open_with_cdp_mode(driver, url=None, **kwargs):\n    \"\"\"Activate CDP Mode with the URL and kwargs.\"\"\"\n    import asyncio\n    from seleniumbase.undetected.cdp_driver import cdp_util\n\n    current_url = None\n    try:\n        current_url = driver.current_url\n    except Exception:\n        driver.connect()\n        current_url = driver.current_url\n    url_protocol = current_url.split(\":\")[0]\n    driver.disconnect()\n\n    cdp_details = driver._get_cdp_details()\n    cdp_host = cdp_details[1].split(\"://\")[1].split(\":\")[0]\n    cdp_port = int(cdp_details[1].split(\"://\")[1].split(\":\")[1].split(\"/\")[0])\n\n    url = shared_utils.fix_url_as_needed(url)\n    url_protocol = url.split(\":\")[0]\n    safe_url = True\n    if url_protocol not in [\"about\", \"data\", \"chrome\"]:\n        safe_url = False\n\n    if (\n        getattr(driver, \"_is_using_cdp\", None)\n        and getattr(driver, \"cdp\", None)\n        and hasattr(driver.cdp, \"loop\")\n    ):\n        # CDP Mode was already initialized\n        driver.cdp.open(url, **kwargs)\n        if not safe_url:\n            time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)\n            if IS_WINDOWS:\n                time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)\n        else:\n            time.sleep(0.012)\n        return\n\n    headless = False\n    headed = None\n    xvfb = None\n    xvfb_metrics = None\n    binary_location = None\n    if hasattr(sb_config, \"headless\"):\n        headless = sb_config.headless\n    if hasattr(sb_config, \"headed\"):\n        headed = sb_config.headed\n    if hasattr(sb_config, \"xvfb\"):\n        xvfb = sb_config.xvfb\n    if hasattr(sb_config, \"xvfb_metrics\"):\n        xvfb_metrics = sb_config.xvfb_metrics\n    if hasattr(sb_config, \"binary_location\"):\n        binary_location = sb_config.binary_location\n\n    loop = asyncio.new_event_loop()\n    asyncio.set_event_loop(loop)\n    driver.cdp_base = loop.run_until_complete(\n        cdp_util.start(\n            host=cdp_host,\n            port=cdp_port,\n            headless=headless,\n            headed=headed,\n            xvfb=xvfb,\n            xvfb_metrics=xvfb_metrics,\n            browser_executable_path=binary_location,\n            mobile=getattr(sb_config, \"_cdp_mobile_mode\", None),\n        )\n    )\n    loop.run_until_complete(driver.cdp_base.wait(0))\n\n    gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n\n    if (\n        \"chrome-extension://\" in str(driver.cdp_base.main_tab)\n        and len(driver.cdp_base.tabs) >= 2\n    ):\n        with suppress(Exception):\n            loop.run_until_complete(driver.cdp_base.main_tab.close())\n\n        for tab in driver.cdp_base.tabs[-1::-1]:\n            if \"chrome-extension://\" not in str(tab):\n                with gui_lock:\n                    with suppress(Exception):\n                        shared_utils.make_writable(\n                            constants.MultiBrowser.PYAUTOGUILOCK\n                        )\n                    loop.run_until_complete(tab.activate())\n                break\n\n        page_tab = None\n        if \"chrome-extension://\" not in str(driver.cdp_base.tabs[-1]):\n            page_tab = driver.cdp_base.tabs[-1]\n        else:\n            for tab in driver.cdp_base.tabs:\n                if \"chrome-extension://\" not in str(tab):\n                    page_tab = tab\n                    break\n        if page_tab:\n            loop.run_until_complete(page_tab.aopen())\n            with gui_lock:\n                with suppress(Exception):\n                    shared_utils.make_writable(\n                        constants.MultiBrowser.PYAUTOGUILOCK\n                    )\n                loop.run_until_complete(page_tab.activate())\n\n    loop.run_until_complete(driver.cdp_base.update_targets())\n    page = loop.run_until_complete(\n        driver.cdp_base.get(url, **kwargs)\n    )\n    with gui_lock:\n        with suppress(Exception):\n            shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK)\n        loop.run_until_complete(page.activate())\n    loop.run_until_complete(page.wait())\n    if not safe_url:\n        time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)\n        if IS_WINDOWS:\n            time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)\n    else:\n        time.sleep(0.012)\n    cdp = types.SimpleNamespace()\n    CDPM = sb_cdp.CDPMethods(loop, page, driver)\n    cdp.get = CDPM.get\n    cdp.open = CDPM.open\n    cdp.reload = CDPM.reload\n    cdp.refresh = CDPM.refresh\n    cdp.add_handler = CDPM.add_handler\n    cdp.get_event_loop = CDPM.get_event_loop\n    cdp.get_rd_host = CDPM.get_rd_host\n    cdp.get_rd_port = CDPM.get_rd_port\n    cdp.get_rd_url = CDPM.get_rd_url\n    cdp.get_endpoint_url = CDPM.get_endpoint_url\n    cdp.get_websocket_url = CDPM.get_websocket_url\n    cdp.get_port = CDPM.get_port\n    cdp.find_element = CDPM.find_element\n    cdp.find = CDPM.find_element\n    cdp.locator = CDPM.find_element\n    cdp.find_element_by_text = CDPM.find_element_by_text\n    cdp.find_all = CDPM.find_all\n    cdp.find_elements_by_text = CDPM.find_elements_by_text\n    cdp.select = CDPM.select\n    cdp.select_all = CDPM.select_all\n    cdp.find_elements = CDPM.find_elements\n    cdp.find_visible_elements = CDPM.find_visible_elements\n    cdp.click_nth_element = CDPM.click_nth_element\n    cdp.click_nth_visible_element = CDPM.click_nth_visible_element\n    cdp.click_link = CDPM.click_link\n    cdp.go_back = CDPM.go_back\n    cdp.go_forward = CDPM.go_forward\n    cdp.get_navigation_history = CDPM.get_navigation_history\n    cdp.tile_windows = CDPM.tile_windows\n    cdp.grant_permissions = CDPM.grant_permissions\n    cdp.grant_all_permissions = CDPM.grant_all_permissions\n    cdp.reset_permissions = CDPM.reset_permissions\n    cdp.get_all_cookies = CDPM.get_all_cookies\n    cdp.set_all_cookies = CDPM.set_all_cookies\n    cdp.save_cookies = CDPM.save_cookies\n    cdp.load_cookies = CDPM.load_cookies\n    cdp.clear_cookies = CDPM.clear_cookies\n    cdp.sleep = CDPM.sleep\n    cdp.bring_active_window_to_front = CDPM.bring_active_window_to_front\n    cdp.bring_to_front = CDPM.bring_active_window_to_front\n    cdp.get_active_element = CDPM.get_active_element\n    cdp.get_active_element_css = CDPM.get_active_element_css\n    cdp.click = CDPM.click\n    cdp.click_active_element = CDPM.click_active_element\n    cdp.click_if_visible = CDPM.click_if_visible\n    cdp.click_visible_elements = CDPM.click_visible_elements\n    cdp.click_with_offset = CDPM.click_with_offset\n    cdp.mouse_click = CDPM.mouse_click\n    cdp.get_parent = CDPM.get_parent\n    cdp.remove_element = CDPM.remove_element\n    cdp.remove_from_dom = CDPM.remove_from_dom\n    cdp.remove_elements = CDPM.remove_elements\n    cdp.send_keys = CDPM.send_keys\n    cdp.press_keys = CDPM.press_keys\n    cdp.type = CDPM.type\n    cdp.clear_input = CDPM.clear_input\n    cdp.clear = CDPM.clear_input\n    cdp.set_value = CDPM.set_value\n    cdp.submit = CDPM.submit\n    cdp.evaluate = CDPM.evaluate\n    cdp.execute_script = CDPM.execute_script\n    cdp.js_dumps = CDPM.js_dumps\n    cdp.maximize = CDPM.maximize\n    cdp.minimize = CDPM.minimize\n    cdp.medimize = CDPM.medimize\n    cdp.set_window_rect = CDPM.set_window_rect\n    cdp.reset_window_size = CDPM.reset_window_size\n    cdp.activate_messenger = CDPM.activate_messenger\n    cdp.set_messenger_theme = CDPM.set_messenger_theme\n    cdp.post_message = CDPM.post_message\n    cdp.set_locale = CDPM.set_locale\n    cdp.set_local_storage_item = CDPM.set_local_storage_item\n    cdp.set_session_storage_item = CDPM.set_session_storage_item\n    cdp.set_attributes = CDPM.set_attributes\n    cdp.is_attribute_present = CDPM.is_attribute_present\n    cdp.is_online = CDPM.is_online\n    cdp.solve_captcha = CDPM.solve_captcha\n    cdp.click_captcha = CDPM.click_captcha\n    cdp.gui_press_key = CDPM.gui_press_key\n    cdp.gui_press_keys = CDPM.gui_press_keys\n    cdp.gui_write = CDPM.gui_write\n    cdp.gui_click_x_y = CDPM.gui_click_x_y\n    cdp.gui_click_element = CDPM.gui_click_element\n    cdp.gui_click_with_offset = CDPM.gui_click_with_offset\n    cdp.gui_click_captcha = CDPM.gui_click_captcha\n    cdp.gui_drag_drop_points = CDPM.gui_drag_drop_points\n    cdp.gui_drag_and_drop = CDPM.gui_drag_and_drop\n    cdp.gui_click_and_hold = CDPM.gui_click_and_hold\n    cdp.gui_hover_x_y = CDPM.gui_hover_x_y\n    cdp.gui_hover_element = CDPM.gui_hover_element\n    cdp.gui_hover_and_click = CDPM.gui_hover_and_click\n    cdp.hover_element = CDPM.hover_element\n    cdp.hover_and_click = CDPM.hover_and_click\n    cdp.internalize_links = CDPM.internalize_links\n    cdp.open_new_window = CDPM.open_new_window\n    cdp.switch_to_window = CDPM.switch_to_window\n    cdp.switch_to_newest_window = CDPM.switch_to_newest_window\n    cdp.open_new_tab = CDPM.open_new_tab\n    cdp.switch_to_tab = CDPM.switch_to_tab\n    cdp.switch_to_newest_tab = CDPM.switch_to_newest_tab\n    cdp.close_active_tab = CDPM.close_active_tab\n    cdp.get_active_tab = CDPM.get_active_tab\n    cdp.get_tabs = CDPM.get_tabs\n    cdp.get_window = CDPM.get_window\n    cdp.get_element_attributes = CDPM.get_element_attributes\n    cdp.get_element_attribute = CDPM.get_element_attribute\n    cdp.get_attribute = CDPM.get_attribute\n    cdp.get_element_html = CDPM.get_element_html\n    cdp.get_element_rect = CDPM.get_element_rect\n    cdp.get_element_size = CDPM.get_element_size\n    cdp.get_element_position = CDPM.get_element_position\n    cdp.get_gui_element_rect = CDPM.get_gui_element_rect\n    cdp.get_gui_element_center = CDPM.get_gui_element_center\n    cdp.get_html = CDPM.get_html\n    cdp.get_page_source = CDPM.get_page_source\n    cdp.get_user_agent = CDPM.get_user_agent\n    cdp.get_cookie_string = CDPM.get_cookie_string\n    cdp.get_locale_code = CDPM.get_locale_code\n    cdp.get_local_storage_item = CDPM.get_local_storage_item\n    cdp.get_session_storage_item = CDPM.get_session_storage_item\n    cdp.get_text = CDPM.get_text\n    cdp.get_title = CDPM.get_title\n    cdp.get_page_title = CDPM.get_title\n    cdp.get_current_url = CDPM.get_current_url\n    cdp.get_origin = CDPM.get_origin\n    cdp.get_nested_element = CDPM.get_nested_element\n    cdp.get_document = CDPM.get_document\n    cdp.get_flattened_document = CDPM.get_flattened_document\n    cdp.get_screen_rect = CDPM.get_screen_rect\n    cdp.get_window_rect = CDPM.get_window_rect\n    cdp.get_window_size = CDPM.get_window_size\n    cdp.get_mfa_code = CDPM.get_mfa_code\n    cdp.nested_click = CDPM.nested_click\n    cdp.select_option_by_text = CDPM.select_option_by_text\n    cdp.select_option_by_index = CDPM.select_option_by_index\n    cdp.select_option_by_value = CDPM.select_option_by_value\n    cdp.enter_mfa_code = CDPM.enter_mfa_code\n    cdp.flash = CDPM.flash\n    cdp.highlight = CDPM.highlight\n    cdp.focus = CDPM.focus\n    cdp.highlight_overlay = CDPM.highlight_overlay\n    cdp.get_window_position = CDPM.get_window_position\n    cdp.check_if_unchecked = CDPM.check_if_unchecked\n    cdp.uncheck_if_checked = CDPM.uncheck_if_checked\n    cdp.select_if_unselected = CDPM.select_if_unselected\n    cdp.unselect_if_selected = CDPM.unselect_if_selected\n    cdp.is_checked = CDPM.is_checked\n    cdp.is_selected = CDPM.is_selected\n    cdp.is_element_present = CDPM.is_element_present\n    cdp.is_element_visible = CDPM.is_element_visible\n    cdp.is_text_visible = CDPM.is_text_visible\n    cdp.is_exact_text_visible = CDPM.is_exact_text_visible\n    cdp.wait_for_text = CDPM.wait_for_text\n    cdp.wait_for_text_not_visible = CDPM.wait_for_text_not_visible\n    cdp.wait_for_element_visible = CDPM.wait_for_element_visible\n    cdp.wait_for_element = CDPM.wait_for_element\n    cdp.wait_for_element_not_visible = CDPM.wait_for_element_not_visible\n    cdp.wait_for_element_absent = CDPM.wait_for_element_absent\n    cdp.wait_for_any_of_elements_visible = (\n        CDPM.wait_for_any_of_elements_visible\n    )\n    cdp.wait_for_any_of_elements_present = (\n        CDPM.wait_for_any_of_elements_present\n    )\n    cdp.assert_any_of_elements_visible = CDPM.assert_any_of_elements_visible\n    cdp.assert_any_of_elements_present = CDPM.assert_any_of_elements_present\n    cdp.assert_element = CDPM.assert_element\n    cdp.assert_element_visible = CDPM.assert_element_visible\n    cdp.assert_element_present = CDPM.assert_element_present\n    cdp.assert_element_absent = CDPM.assert_element_absent\n    cdp.assert_element_not_visible = CDPM.assert_element_not_visible\n    cdp.assert_element_attribute = CDPM.assert_element_attribute\n    cdp.assert_title = CDPM.assert_title\n    cdp.assert_title_contains = CDPM.assert_title_contains\n    cdp.assert_url = CDPM.assert_url\n    cdp.assert_url_contains = CDPM.assert_url_contains\n    cdp.assert_text = CDPM.assert_text\n    cdp.assert_exact_text = CDPM.assert_exact_text\n    cdp.assert_text_not_visible = CDPM.assert_text_not_visible\n    cdp.assert_true = CDPM.assert_true\n    cdp.assert_false = CDPM.assert_false\n    cdp.assert_equal = CDPM.assert_equal\n    cdp.assert_not_equal = CDPM.assert_not_equal\n    cdp.assert_in = CDPM.assert_in\n    cdp.assert_not_in = CDPM.assert_not_in\n    cdp.scroll_into_view = CDPM.scroll_into_view\n    cdp.scroll_to_y = CDPM.scroll_to_y\n    cdp.scroll_by_y = CDPM.scroll_by_y\n    cdp.scroll_to_top = CDPM.scroll_to_top\n    cdp.scroll_to_bottom = CDPM.scroll_to_bottom\n    cdp.scroll_up = CDPM.scroll_up\n    cdp.scroll_down = CDPM.scroll_down\n    cdp.save_page_source = CDPM.save_page_source\n    cdp.save_as_html = CDPM.save_as_html\n    cdp.save_screenshot = CDPM.save_screenshot\n    cdp.print_to_pdf = CDPM.print_to_pdf\n    cdp.save_as_pdf = CDPM.save_as_pdf\n    cdp.page = page  # async world\n    cdp.driver = driver.cdp_base  # async world\n    cdp.tab = cdp.page  # shortcut (original)\n    cdp.browser = driver.cdp_base  # shortcut (original)\n    cdp.util = cdp_util  # shortcut (original)\n    core_items = types.SimpleNamespace()\n    core_items.browser = cdp.browser\n    core_items.tab = cdp.tab\n    core_items.util = cdp.util\n    cdp._swap_driver = CDPM._swap_driver\n    cdp.core = core_items\n    cdp.loop = cdp.get_event_loop()\n    driver.cdp = cdp\n    driver.solve_captcha = CDPM.solve_captcha\n    driver.click_captcha = CDPM.click_captcha\n    driver.find_element_by_text = CDPM.find_element_by_text\n    driver._is_using_cdp = True\n    if (\n        getattr(sb_config, \"_cdp_proxy\", None)\n        and \"@\" in sb_config._cdp_proxy\n    ):\n        time.sleep(0.077)\n        loop.run_until_complete(page.wait(0.25))\n        time.sleep(0.022)\n\n\ndef uc_activate_cdp_mode(driver, url=None, **kwargs):\n    uc_open_with_cdp_mode(driver, url=url, **kwargs)\n\n\ndef uc_open_with_disconnect(driver, url, timeout=None):\n    \"\"\"Open a url and disconnect chromedriver.\n    Then waits for the duration of the timeout.\n    Note: You can't perform Selenium actions again\n    until after you've called driver.connect().\"\"\"\n    url = shared_utils.fix_url_as_needed(url)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.get(url)\n        time.sleep(0.3)\n        return\n    if not driver.is_connected():\n        driver.connect()\n    if (url.startswith(\"http:\") or url.startswith(\"https:\")):\n        script = 'window.open(\"%s\",\"_blank\");' % url\n        driver.execute_script(script)\n        time.sleep(0.05)\n        driver.close()\n        driver.disconnect()\n        min_timeout = 0.008\n        if timeout and not str(timeout).replace(\".\", \"\", 1).isdigit():\n            timeout = min_timeout\n        if not timeout or timeout < min_timeout:\n            timeout = min_timeout\n        time.sleep(timeout)\n    else:\n        driver.default_get(url)  # The original one\n    return None\n\n\ndef uc_click(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n    reconnect_time=None,\n):\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.click(selector)\n        return\n    with suppress(Exception):\n        rct = float(by)  # Add shortcut: driver.uc_click(selector, RCT)\n        if not reconnect_time:\n            reconnect_time = rct\n        by = \"css selector\"\n    element = driver.wait_for_selector(selector, by=by, timeout=timeout)\n    tag_name = element.tag_name\n    if not tag_name == \"span\" and not tag_name == \"input\":  # Must be \"visible\"\n        element = driver.wait_for_element(selector, by=by, timeout=timeout)\n    try:\n        element.uc_click(\n            driver,\n            selector,\n            by=by,\n            reconnect_time=reconnect_time,\n            tag_name=tag_name,\n        )\n    except ElementClickInterceptedException:\n        time.sleep(0.16)\n        driver.js_click(selector, by=by, timeout=timeout)\n        if not reconnect_time:\n            driver.reconnect(0.1)\n        else:\n            driver.reconnect(reconnect_time)\n\n\ndef verify_pyautogui_has_a_headed_browser(driver):\n    \"\"\"PyAutoGUI requires a headed browser so that it can\n    focus on the correct element when performing actions.\"\"\"\n    if getattr(driver, \"_is_hidden\", None):\n        raise Exception(\n            \"PyAutoGUI can't be used in headless mode!\"\n        )\n\n\ndef __install_pyautogui_if_missing():\n    try:\n        import pyautogui\n        with suppress(Exception):\n            use_pyautogui_ver = constants.PyAutoGUI.VER\n            u_pv = shared_utils.make_version_tuple(use_pyautogui_ver)\n            pv = shared_utils.make_version_tuple(pyautogui.__version__)\n            if pv < u_pv:\n                del pyautogui  # To get newer ver\n                shared_utils.pip_install(\"pyautogui\", version=\"Latest\")\n                import pyautogui\n    except Exception:\n        print(\"\\nPyAutoGUI required! Installing now...\")\n        shared_utils.pip_install(\"pyautogui\", version=\"Latest\")\n        try:\n            import pyautogui\n        except Exception:\n            if (\n                IS_LINUX\n                and hasattr(sb_config, \"xvfb\")\n                and hasattr(sb_config, \"headed\")\n                and hasattr(sb_config, \"headless\")\n                and hasattr(sb_config, \"headless2\")\n                and (not sb_config.headed or sb_config.xvfb)\n                and not (sb_config.headless or sb_config.headless2)\n            ):\n                from sbvirtualdisplay import Display\n                xvfb_width = 1366\n                xvfb_height = 768\n                if (\n                    getattr(sb_config, \"_xvfb_width\", None)\n                    and isinstance(sb_config._xvfb_width, int)\n                    and getattr(sb_config, \"_xvfb_height\", None)\n                    and isinstance(sb_config._xvfb_height, int)\n                ):\n                    xvfb_width = sb_config._xvfb_width\n                    xvfb_height = sb_config._xvfb_height\n                    if xvfb_width < 1024:\n                        xvfb_width = 1024\n                    sb_config._xvfb_width = xvfb_width\n                    if xvfb_height < 768:\n                        xvfb_height = 768\n                    sb_config._xvfb_height = xvfb_height\n                with suppress(Exception):\n                    _xvfb_display = Display(\n                        visible=True,\n                        size=(xvfb_width, xvfb_height),\n                        backend=\"xvfb\",\n                        use_xauth=True,\n                    )\n                    if \"--debug-display\" in sys.argv:\n                        print(\n                            \"Starting VDisplay from browser_launcher: (%s, %s)\"\n                            % (xvfb_width, xvfb_height)\n                        )\n                    _xvfb_display.start()\n                    sb_config._virtual_display = _xvfb_display\n                    sb_config.headless_active = True\n                    if (\n                        getattr(sb_config, \"reuse_session\", None)\n                        and hasattr(sb_config, \"_vd_list\")\n                        and isinstance(sb_config._vd_list, list)\n                    ):\n                        sb_config._vd_list.append(_xvfb_display)\n\n\ndef install_pyautogui_if_missing(driver):\n    verify_pyautogui_has_a_headed_browser(driver)\n    pip_find_lock = fasteners.InterProcessLock(\n        constants.PipInstall.FINDLOCK\n    )\n    try:\n        with pip_find_lock:\n            pass\n    except Exception:\n        # Since missing permissions, skip the locks\n        __install_pyautogui_if_missing()\n        return\n    with pip_find_lock:  # Prevent issues with multiple processes\n        with suppress(Exception):\n            shared_utils.make_writable(constants.PipInstall.FINDLOCK)\n        __install_pyautogui_if_missing()\n\n\ndef get_configured_pyautogui(pyautogui_copy):\n    if (\n        IS_LINUX\n        and hasattr(pyautogui_copy, \"_pyautogui_x11\")\n        and \"DISPLAY\" in os.environ.keys()\n    ):\n        if (\n            getattr(sb_config, \"_pyautogui_x11_display\", None)\n            and hasattr(pyautogui_copy._pyautogui_x11, \"_display\")\n            and (\n                sb_config._pyautogui_x11_display\n                == pyautogui_copy._pyautogui_x11._display\n            )\n        ):\n            pass\n        else:\n            import Xlib.display\n            pyautogui_copy._pyautogui_x11._display = (\n                Xlib.display.Display(os.environ['DISPLAY'])\n            )\n            sb_config._pyautogui_x11_display = (\n                pyautogui_copy._pyautogui_x11._display\n            )\n    return pyautogui_copy\n\n\ndef uc_gui_press_key(driver, key):\n    install_pyautogui_if_missing(driver)\n    import pyautogui\n    pyautogui = get_configured_pyautogui(pyautogui)\n    gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n    with gui_lock:\n        pyautogui.press(key)\n\n\ndef uc_gui_press_keys(driver, keys):\n    install_pyautogui_if_missing(driver)\n    import pyautogui\n    pyautogui = get_configured_pyautogui(pyautogui)\n    gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n    with gui_lock:\n        for key in keys:\n            pyautogui.press(key)\n\n\ndef uc_gui_write(driver, text):\n    install_pyautogui_if_missing(driver)\n    import pyautogui\n    pyautogui = get_configured_pyautogui(pyautogui)\n    gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n    with gui_lock:\n        pyautogui.write(text)\n\n\ndef get_gui_element_position(driver, selector):\n    if __is_cdp_swap_needed(driver):\n        element_rect = driver.cdp.get_gui_element_rect(selector)\n        return (element_rect[\"x\"], element_rect[\"y\"])\n    element = driver.wait_for_element_present(selector, timeout=3)\n    element_rect = element.rect\n    window_rect = driver.get_window_rect()\n    window_bottom_y = window_rect[\"y\"] + window_rect[\"height\"]\n    viewport_height = driver.execute_script(\"return window.innerHeight;\")\n    viewport_x = window_rect[\"x\"] + element_rect[\"x\"]\n    viewport_y = window_bottom_y - viewport_height + element_rect[\"y\"]\n    y_scroll_offset = driver.execute_script(\"return window.pageYOffset;\")\n    viewport_y = viewport_y - y_scroll_offset\n    return (viewport_x, viewport_y)\n\n\ndef _uc_gui_click_x_y(driver, x, y, timeframe=0.25, uc_lock=False):\n    install_pyautogui_if_missing(driver)\n    import pyautogui\n    pyautogui = get_configured_pyautogui(pyautogui)\n    screen_width, screen_height = pyautogui.size()\n    if x < 0 or y < 0 or x > screen_width or y > screen_height:\n        raise Exception(\n            \"PyAutoGUI cannot click on point (%s, %s)\"\n            \" outside screen. (Width: %s, Height: %s)\"\n            % (x, y, screen_width, screen_height)\n        )\n    if uc_lock:\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:  # Prevent issues with multiple processes\n            pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)\n            if timeframe >= 0.25:\n                time.sleep(0.056)  # Wait if moving at human-speed\n            if \"--debug\" in sys.argv:\n                print(\" <DEBUG> pyautogui.click(%s, %s)\" % (x, y))\n            pyautogui.click(x=x, y=y)\n    else:\n        # Called from a method where the gui_lock is already active\n        pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)\n        if timeframe >= 0.25:\n            time.sleep(0.056)  # Wait if moving at human-speed\n        if \"--debug\" in sys.argv:\n            print(\" <DEBUG> pyautogui.click(%s, %s)\" % (x, y))\n        pyautogui.click(x=x, y=y)\n\n\ndef uc_gui_click_x_y(driver, x, y, timeframe=0.25):\n    gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n    with gui_lock:  # Prevent issues with multiple processes\n        install_pyautogui_if_missing(driver)\n        import pyautogui\n        pyautogui = get_configured_pyautogui(pyautogui)\n        connected = True\n        width_ratio = 1.0\n        if IS_WINDOWS:\n            connected = driver.is_connected()\n            if (\n                not connected\n                and not getattr(sb_config, \"_saved_width_ratio\", None)\n                and not __is_cdp_swap_needed(driver)\n            ):\n                driver.reconnect(0.1)\n        if IS_WINDOWS and not __is_cdp_swap_needed(driver):\n            window_rect = driver.get_window_rect()\n            width = window_rect[\"width\"]\n            height = window_rect[\"height\"]\n            win_x = window_rect[\"x\"]\n            win_y = window_rect[\"y\"]\n            scr_width = pyautogui.size().width\n            driver.maximize_window()\n            win_width = driver.get_window_size()[\"width\"]\n            width_ratio = round(float(scr_width) / float(win_width), 2) + 0.01\n            if width_ratio < 0.45 or width_ratio > 2.55:\n                width_ratio = 1.01\n            sb_config._saved_width_ratio = width_ratio\n            driver.minimize_window()\n            driver.set_window_rect(win_x, win_y, width, height)\n        elif IS_WINDOWS and __is_cdp_swap_needed(driver):\n            window_rect = driver.cdp.get_window_rect()\n            width = window_rect[\"width\"]\n            height = window_rect[\"height\"]\n            win_x = window_rect[\"x\"]\n            win_y = window_rect[\"y\"]\n            scr_width = pyautogui.size().width\n            driver.cdp.maximize()\n            win_width = driver.cdp.get_window_size()[\"width\"]\n            width_ratio = round(float(scr_width) / float(win_width), 2) + 0.01\n            if width_ratio < 0.45 or width_ratio > 2.55:\n                width_ratio = 1.01\n            sb_config._saved_width_ratio = width_ratio\n            driver.cdp.minimize()\n            driver.cdp.set_window_rect(win_x, win_y, width, height)\n        if IS_WINDOWS:\n            x = x * (width_ratio + 0.03)\n            y = y * (width_ratio - 0.03)\n            _uc_gui_click_x_y(driver, x, y, timeframe=timeframe, uc_lock=False)\n            return\n        with suppress(Exception):\n            page_actions.switch_to_window(\n                driver, driver.current_window_handle, 2, uc_lock=False\n            )\n        _uc_gui_click_x_y(driver, x, y, timeframe=timeframe, uc_lock=False)\n\n\ndef _on_a_cf_turnstile_page(driver):\n    source = driver.get_page_source()\n    if (\n        (\n            'data-callback=\"onCaptchaSuccess\"' in source\n            and 'title=\"reCAPTCHA\"' not in source\n            and 'id=\"recaptcha-token\"' not in source\n        )\n        or \"/challenge-platform/h/b/\" in source\n        or 'id=\"challenge-widget-' in source\n        or \"challenges.cloudf\" in source\n        or \"cf-turnstile-\" in source\n    ):\n        return True\n    return False\n\n\ndef _on_a_g_recaptcha_page(driver):\n    source = driver.get_page_source()\n    if (\n        'id=\"recaptcha-token\"' in source\n        or 'title=\"reCAPTCHA\"' in source\n    ):\n        return True\n    return False\n\n\ndef _uc_gui_click_captcha(\n    driver,\n    frame=\"iframe\",\n    retry=False,\n    blind=False,\n    ctype=None,\n):\n    cdp_mode_on_at_start = __is_cdp_swap_needed(driver)\n    if cdp_mode_on_at_start:\n        return driver.cdp.gui_click_captcha()\n    _on_a_captcha_page = None\n    if ctype == \"cf_t\":\n        if not _on_a_cf_turnstile_page(driver):\n            return False\n        else:\n            _on_a_captcha_page = _on_a_cf_turnstile_page\n    elif ctype == \"g_rc\":\n        if not _on_a_g_recaptcha_page(driver):\n            return False\n        else:\n            _on_a_captcha_page = _on_a_g_recaptcha_page\n    else:\n        if _on_a_g_recaptcha_page(driver):\n            ctype = \"g_rc\"\n            _on_a_captcha_page = _on_a_g_recaptcha_page\n        elif _on_a_cf_turnstile_page(driver):\n            ctype = \"cf_t\"\n            _on_a_captcha_page = _on_a_cf_turnstile_page\n        else:\n            return False\n    install_pyautogui_if_missing(driver)\n    import pyautogui\n    pyautogui = get_configured_pyautogui(pyautogui)\n    i_x = None\n    i_y = None\n    x = None\n    y = None\n    visible_iframe = True\n    gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n    with gui_lock:  # Prevent issues with multiple processes\n        needs_switch = False\n        width_ratio = 1.0\n        is_in_frame = js_utils.is_in_frame(driver)\n        if is_in_frame and driver.is_element_present(\"#challenge-stage\"):\n            driver.switch_to.parent_frame()\n            needs_switch = True\n            is_in_frame = js_utils.is_in_frame(driver)\n        if not is_in_frame:\n            # Make sure the window is on top\n            if __is_cdp_swap_needed(driver):\n                driver.cdp.bring_active_window_to_front()\n            else:\n                page_actions.switch_to_window(\n                    driver, driver.current_window_handle, 2, uc_lock=False\n                )\n        if IS_WINDOWS and not __is_cdp_swap_needed(driver):\n            window_rect = driver.get_window_rect()\n            width = window_rect[\"width\"]\n            height = window_rect[\"height\"]\n            win_x = window_rect[\"x\"]\n            win_y = window_rect[\"y\"]\n            scr_width = pyautogui.size().width\n            driver.maximize_window()\n            win_width = driver.get_window_size()[\"width\"]\n            width_ratio = round(float(scr_width) / float(win_width), 2) + 0.01\n            if width_ratio < 0.45 or width_ratio > 2.55:\n                width_ratio = 1.01\n            sb_config._saved_width_ratio = width_ratio\n            driver.minimize_window()\n            driver.set_window_rect(win_x, win_y, width, height)\n        elif IS_WINDOWS and __is_cdp_swap_needed(driver):\n            window_rect = driver.cdp.get_window_rect()\n            width = window_rect[\"width\"]\n            height = window_rect[\"height\"]\n            win_x = window_rect[\"x\"]\n            win_y = window_rect[\"y\"]\n            scr_width = pyautogui.size().width\n            driver.cdp.maximize()\n            win_width = driver.cdp.get_window_size()[\"width\"]\n            width_ratio = round(float(scr_width) / float(win_width), 2) + 0.01\n            if width_ratio < 0.45 or width_ratio > 2.55:\n                width_ratio = 1.01\n            sb_config._saved_width_ratio = width_ratio\n            driver.cdp.minimize()\n            driver.cdp.set_window_rect(win_x, win_y, width, height)\n        if ctype == \"cf_t\":\n            if (\n                driver.is_element_present(\".cf-turnstile-wrapper iframe\")\n                or driver.is_element_present(\n                    '[data-callback=\"onCaptchaSuccess\"] iframe'\n                )\n            ):\n                pass\n            else:\n                visible_iframe = False\n                if (\n                    frame != \"iframe\"\n                    and driver.is_element_present(\n                        \"%s .cf-turnstile-wrapper\" % frame\n                    )\n                ):\n                    frame = \"%s .cf-turnstile-wrapper\" % frame\n                elif (\n                    frame != \"iframe\"\n                    and driver.is_element_present(\n                        '%s [name*=\"cf-turnstile\"]' % frame\n                    )\n                    and driver.is_element_present(\"%s div\" % frame)\n                ):\n                    frame = \"%s div\" % frame\n                elif driver.is_element_present(\"#challenge-form div > div\"):\n                    frame = \"#challenge-form div > div\"\n                elif (\n                    driver.is_element_present(\n                        '[style=\"display: grid;\"] div div'\n                    )\n                ):\n                    frame = '[style=\"display: grid;\"] div div'\n                elif (\n                    driver.is_element_present('[name*=\"cf-turnstile-\"]')\n                    and driver.is_element_present(\n                        \".spacer + div div:not([class])\"\n                    )\n                ):\n                    frame = '.spacer + div div:not([class])'\n                elif (\n                    driver.is_element_present(\".spacer div:not([class])\")\n                ):\n                    frame = \".spacer div:not([class])\"\n                elif (\n                    driver.is_element_present(\n                        '[data-testid*=\"challenge-\"] div'\n                    )\n                ):\n                    frame = '[data-testid*=\"challenge-\"] div'\n                elif driver.is_element_present(\n                    \"div#turnstile-widget div:not([class])\"\n                ):\n                    frame = \"div#turnstile-widget div:not([class])\"\n                elif driver.is_element_present(\n                    \"ngx-turnstile div:not([class])\"\n                ):\n                    frame = \"ngx-turnstile div:not([class])\"\n                elif driver.is_element_present(\n                    'form div:not([class]):has(input[name*=\"cf-turn\"])'\n                ):\n                    frame = 'form div:not([class]):has(input[name*=\"cf-turn\"])'\n                elif (\n                    driver.is_element_present('[src*=\"/turnstile/\"]')\n                    and driver.is_element_present(\"form div:not(:has(*))\")\n                ):\n                    frame = \"form div:not(:has(*))\"\n                elif (\n                    driver.is_element_present('[src*=\"/turnstile/\"]')\n                    and driver.is_element_present(\n                        \"body > div#check > div:not([class])\"\n                    )\n                ):\n                    frame = \"body > div#check > div:not([class])\"\n                elif driver.is_element_present(\".cf-turnstile-wrapper\"):\n                    frame = \".cf-turnstile-wrapper\"\n                elif driver.is_element_present('[class=\"cf-turnstile\"]'):\n                    frame = '[class=\"cf-turnstile\"]'\n                elif driver.is_element_present(\n                    '[id*=\"turnstile\"] div:not([class])'\n                ):\n                    frame = '[id*=\"turnstile\"] div:not([class])'\n                elif driver.is_element_present(\n                    '[class*=\"turnstile\"] div:not([class])'\n                ):\n                    frame = '[class*=\"turnstile\"] div:not([class])'\n                elif driver.is_element_present(\n                    '[data-callback=\"onCaptchaSuccess\"]'\n                ):\n                    frame = '[data-callback=\"onCaptchaSuccess\"]'\n                elif driver.is_element_present(\n                    \"div:not([class]):not([id]):not([aria-label]) > \"\n                    \"div:not([class]):not([id]):not([aria-label])\"\n                ):\n                    frame = (\n                        \"div:not([class]):not([id]):not([aria-label]) > \"\n                        \"div:not([class]):not([id]):not([aria-label])\"\n                    )\n                else:\n                    return False\n            if (\n                driver.is_element_present(\"form\")\n                and (\n                    driver.is_element_present('form[class*=\"center\"]')\n                    or driver.is_element_present('form[class*=\"right\"]')\n                    or driver.is_element_present('form div[class*=\"center\"]')\n                    or driver.is_element_present('form div[class*=\"right\"]')\n                )\n            ):\n                script = (\n                    \"\"\"var $elements = document.querySelectorAll(\n                    'form[class], form div[class]');\n                    var index = 0, length = $elements.length;\n                    for(; index < length; index++){\n                    the_class = $elements[index].getAttribute('class');\n                    new_class = the_class.replaceAll('center', 'left');\n                    new_class = new_class.replaceAll('right', 'left');\n                    $elements[index].setAttribute('class', new_class);}\"\"\"\n                )\n                if __is_cdp_swap_needed(driver):\n                    driver.cdp.evaluate(script)\n                else:\n                    driver.execute_script(script)\n            elif (\n                driver.is_element_present(\"form\")\n                and (\n                    driver.is_element_present('form div[style*=\"center\"]')\n                    or driver.is_element_present('form div[style*=\"right\"]')\n                )\n            ):\n                script = (\n                    \"\"\"var $elements = document.querySelectorAll(\n                    'form[style], form div[style]');\n                    var index = 0, length = $elements.length;\n                    for(; index < length; index++){\n                    the_style = $elements[index].getAttribute('style');\n                    new_style = the_style.replaceAll('center', 'left');\n                    new_style = new_style.replaceAll('right', 'left');\n                    $elements[index].setAttribute('style', new_style);}\"\"\"\n                )\n                if __is_cdp_swap_needed(driver):\n                    driver.cdp.evaluate(script)\n                else:\n                    driver.execute_script(script)\n            elif (\n                driver.is_element_present(\n                    'form [id*=\"turnstile\"] div:not([class])'\n                )\n                or driver.is_element_present(\n                    'form [class*=\"turnstile\"] div:not([class])'\n                )\n            ):\n                script = (\n                    \"\"\"var $elements = document.querySelectorAll(\n                    'form [id*=\"turnstile\"]');\n                    var index = 0, length = $elements.length;\n                    for(; index < length; index++){\n                    $elements[index].setAttribute('align', 'left');}\n                    var $elements = document.querySelectorAll(\n                    'form [class*=\"turnstile\"]');\n                    var index = 0, length = $elements.length;\n                    for(; index < length; index++){\n                    $elements[index].setAttribute('align', 'left');}\"\"\"\n                )\n                if __is_cdp_swap_needed(driver):\n                    driver.cdp.evaluate(script)\n                else:\n                    driver.execute_script(script)\n            elif (\n                driver.is_element_present(\n                    '[style*=\"text-align: center;\"] div:not([class])'\n                )\n            ):\n                script = (\n                    \"\"\"var $elements = document.querySelectorAll(\n                    '[style*=\"text-align: center;\"]');\n                    var index = 0, length = $elements.length;\n                    for(; index < length; index++){\n                    the_style = $elements[index].getAttribute('style');\n                    new_style = the_style.replaceAll('center', 'left');\n                    $elements[index].setAttribute('style', new_style);}\"\"\"\n                )\n                if __is_cdp_swap_needed(driver):\n                    driver.cdp.evaluate(script)\n                else:\n                    driver.execute_script(script)\n        if not is_in_frame or needs_switch:\n            # Currently not in frame (or nested frame outside CF one)\n            try:\n                i_x, i_y = get_gui_element_position(driver, frame)\n                if visible_iframe:\n                    driver.switch_to_frame(frame)\n            except Exception:\n                if visible_iframe:\n                    if driver.is_element_present(\"iframe\"):\n                        i_x, i_y = get_gui_element_position(driver, \"iframe\")\n                        if driver.is_connected():\n                            driver.switch_to_frame(\"iframe\")\n                    else:\n                        return False\n            if not i_x or not i_y:\n                return False\n        try:\n            if ctype == \"g_rc\" and not driver.is_connected():\n                x = (i_x + 29) * width_ratio\n                y = (i_y + 35) * width_ratio\n            elif visible_iframe:\n                selector = \"span\"\n                if ctype == \"g_rc\":\n                    selector = \"span.recaptcha-checkbox\"\n                    if not driver.is_connected():\n                        selector = \"iframe\"\n                element = driver.wait_for_element_present(\n                    selector, timeout=2.5\n                )\n                x = i_x + element.rect[\"x\"] + (element.rect[\"width\"] / 2.0)\n                x += 0.5\n                y = i_y + element.rect[\"y\"] + (element.rect[\"height\"] / 2.0)\n                y += 0.5\n            else:\n                x = (i_x + 28) * width_ratio\n                if not IS_WINDOWS:\n                    y = (i_y + 32) * width_ratio\n                else:\n                    y = (i_y + 22) * width_ratio\n            if driver.is_connected():\n                driver.switch_to.default_content()\n        except Exception:\n            if driver.is_connected():\n                try:\n                    driver.switch_to.default_content()\n                except Exception:\n                    return False\n        if x and y:\n            sb_config._saved_cf_x_y = (x, y)\n            if not __is_cdp_swap_needed(driver):\n                if driver.is_element_present(\".footer .clearfix .ray-id\"):\n                    driver.uc_open_with_disconnect(\n                        driver.get_current_url(), 3.8\n                    )\n                else:\n                    driver.disconnect()\n            with suppress(Exception):\n                _uc_gui_click_x_y(driver, x, y, timeframe=0.32)\n                if __is_cdp_swap_needed(driver):\n                    time.sleep(float(constants.UC.RECONNECT_TIME) / 2.0)\n                    return True\n    reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.6\n    if IS_LINUX:\n        reconnect_time = constants.UC.RECONNECT_TIME + 0.2\n    if not x or not y:\n        reconnect_time = 1  # Make it quick (it already failed)\n    driver.reconnect(reconnect_time)\n    caught = False\n    if (\n        driver.is_element_present(\".footer .clearfix .ray-id\")\n        and not driver.is_element_visible(\"#challenge-success-text\")\n    ):\n        blind = True\n        caught = True\n    if blind:\n        retry = True\n    if retry and x and y and (caught or _on_a_captcha_page(driver)):\n        with gui_lock:  # Prevent issues with multiple processes\n            # Make sure the window is on top\n            if __is_cdp_swap_needed(driver):\n                driver.cdp.bring_active_window_to_front()\n            else:\n                page_actions.switch_to_window(\n                    driver, driver.current_window_handle, 2, uc_lock=False\n                )\n            if driver.is_element_present(\"iframe\"):\n                try:\n                    driver.switch_to_frame(frame)\n                except Exception:\n                    try:\n                        driver.switch_to_frame(\"iframe\")\n                    except Exception:\n                        return False\n                checkbox_success = None\n                if ctype == \"cf_t\":\n                    checkbox_success = \"#success-icon\"\n                elif ctype == \"g_rc\":\n                    checkbox_success = \"span.recaptcha-checkbox-checked\"\n                else:\n                    return False  # If line is reached, ctype wasn't set\n                if driver.is_element_visible(\"#success-icon\"):\n                    driver.switch_to.parent_frame(checkbox_success)\n                    return True\n            if blind:\n                driver.uc_open_with_disconnect(driver.get_current_url(), 3.8)\n                if __is_cdp_swap_needed(driver) and _on_a_captcha_page(driver):\n                    _uc_gui_click_x_y(driver, x, y, timeframe=0.32)\n                else:\n                    time.sleep(0.1)\n            else:\n                driver.uc_open_with_reconnect(driver.get_current_url(), 3.8)\n                if _on_a_captcha_page(driver):\n                    driver.disconnect()\n                    _uc_gui_click_x_y(driver, x, y, timeframe=0.32)\n        if not cdp_mode_on_at_start:\n            driver.reconnect(reconnect_time)\n    return True\n\n\ndef uc_gui_click_captcha(driver, frame=\"iframe\", retry=False, blind=False):\n    _uc_gui_click_captcha(\n        driver,\n        frame=frame,\n        retry=retry,\n        blind=blind,\n        ctype=None,\n    )\n\n\ndef uc_gui_click_rc(driver, frame=\"iframe\", retry=False, blind=False):\n    _uc_gui_click_captcha(\n        driver,\n        frame=frame,\n        retry=retry,\n        blind=blind,\n        ctype=\"g_rc\",\n    )\n\n\ndef uc_gui_click_cf(driver, frame=\"iframe\", retry=False, blind=False):\n    _uc_gui_click_captcha(\n        driver,\n        frame=frame,\n        retry=retry,\n        blind=blind,\n        ctype=\"cf_t\",\n    )\n\n\ndef _uc_gui_handle_captcha_(driver, frame=\"iframe\", ctype=None):\n    if ctype == \"cf_t\":\n        if not _on_a_cf_turnstile_page(driver):\n            return\n    elif ctype == \"g_rc\":\n        if not _on_a_g_recaptcha_page(driver):\n            return\n    else:\n        if _on_a_g_recaptcha_page(driver):\n            ctype = \"g_rc\"\n        elif _on_a_cf_turnstile_page(driver):\n            ctype = \"cf_t\"\n        else:\n            return\n    if not driver.is_connected() and not __is_cdp_swap_needed(driver):\n        driver.connect()\n        time.sleep(2)\n    install_pyautogui_if_missing(driver)\n    import pyautogui\n    pyautogui = get_configured_pyautogui(pyautogui)\n    visible_iframe = True\n    gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n    with gui_lock:  # Prevent issues with multiple processes\n        needs_switch = False\n        if not __is_cdp_swap_needed(driver):\n            is_in_frame = js_utils.is_in_frame(driver)\n        else:\n            is_in_frame = False\n        selector = \"#challenge-stage\"\n        if ctype == \"g_rc\":\n            selector = \"#recaptcha-token\"\n        if is_in_frame and driver.is_element_present(selector):\n            driver.switch_to.parent_frame()\n            needs_switch = True\n            is_in_frame = js_utils.is_in_frame(driver)\n        if not is_in_frame and not __is_cdp_swap_needed(driver):\n            # Make sure the window is on top\n            page_actions.switch_to_window(\n                driver, driver.current_window_handle, 2, uc_lock=False\n            )\n        if IS_WINDOWS and hasattr(pyautogui, \"getActiveWindowTitle\"):\n            py_a_g_title = pyautogui.getActiveWindowTitle() or \"\"\n            window_title = driver.get_title()\n            if not py_a_g_title.startswith(window_title):\n                window_rect = driver.get_window_rect()\n                width = window_rect[\"width\"]\n                height = window_rect[\"height\"]\n                win_x = window_rect[\"x\"]\n                win_y = window_rect[\"y\"]\n                driver.minimize_window()\n                driver.set_window_rect(win_x, win_y, width, height)\n                time.sleep(0.33)\n        tab_up_first = False\n        if ctype == \"cf_t\":\n            if (\n                driver.is_element_present(\".cf-turnstile-wrapper iframe\")\n                or driver.is_element_present(\n                    '[data-callback=\"onCaptchaSuccess\"] iframe'\n                )\n            ):\n                pass\n            else:\n                visible_iframe = False\n                if driver.is_element_present(\".cf-turnstile-wrapper\"):\n                    frame = \".cf-turnstile-wrapper\"\n                elif driver.is_element_present(\n                    '[data-callback=\"onCaptchaSuccess\"]'\n                ):\n                    frame = '[data-callback=\"onCaptchaSuccess\"]'\n                elif (\n                    driver.is_element_present('[name*=\"cf-turnstile-\"]')\n                    and driver.is_element_present(\".spacer div:not([class])\")\n                ):\n                    frame = \".spacer div:not([class])\"\n                elif (\n                    driver.is_element_present('script[src*=\"challenges.c\"]')\n                    and driver.is_element_present(\n                        '[data-testid*=\"challenge-\"] div'\n                    )\n                ):\n                    frame = '[data-testid*=\"challenge-\"] div'\n                elif driver.is_element_present(\n                    'form div:not([class]):has(input[name*=\"cf-turn\"])'\n                ):\n                    frame = 'form div:not([class]):has(input[name*=\"cf-turn\"])'\n                    tab_up_first = True\n                elif (\n                    driver.is_element_present('[src*=\"/turnstile/\"]')\n                    and driver.is_element_present(\"form div:not(:has(*))\")\n                ):\n                    frame = \"form div:not(:has(*))\"\n                    tab_up_first = True\n                elif (\n                    driver.is_element_present('[src*=\"/turnstile/\"]')\n                    and driver.is_element_present(\n                        \"body > div#check > div:not([class])\"\n                    )\n                ):\n                    frame = \"body > div#check > div:not([class])\"\n                elif driver.is_element_present(\n                    \"div#turnstile-widget div:not([class])\"\n                ):\n                    frame = \"div#turnstile-widget div:not([class])\"\n                elif driver.is_element_present(\".cf-turnstile-wrapper\"):\n                    frame = \".cf-turnstile-wrapper\"\n                elif driver.is_element_present('[class=\"cf-turnstile\"]'):\n                    frame = '[class=\"cf-turnstile\"]'\n                elif driver.is_element_present(\n                    \"div:not([class]) > div:not([class])\"\n                ):\n                    frame = \"div:not([class]) > div:not([class])\"\n                else:\n                    return\n        else:\n            if (\n                driver.is_element_present('iframe[title=\"reCAPTCHA\"]')\n                and frame == \"iframe\"\n            ):\n                frame = 'iframe[title=\"reCAPTCHA\"]'\n        if not __is_cdp_swap_needed(driver):\n            if not is_in_frame or needs_switch:\n                # Currently not in frame (or nested frame outside CF one)\n                try:\n                    if visible_iframe or ctype == \"g_rc\":\n                        driver.switch_to_frame(frame)\n                except Exception:\n                    if visible_iframe or ctype == \"g_rc\":\n                        if driver.is_element_present(\"iframe\"):\n                            driver.switch_to_frame(\"iframe\")\n                        else:\n                            return\n        try:\n            selector = \"div.cf-turnstile\"\n            if ctype == \"g_rc\":\n                selector = \"span#recaptcha-anchor\"\n            found_checkbox = False\n            if tab_up_first:\n                for i in range(10):\n                    pyautogui.hotkey(\"shift\", \"tab\")\n                    time.sleep(0.027)\n                    if ctype == \"g_rc\":\n                        if js_utils.get_active_element_css(driver) == \"body\":\n                            break\n            tab_count = 0\n            for i in range(34):\n                pyautogui.press(\"\\t\")\n                tab_count += 1\n                time.sleep(0.027)\n                active_element_css = js_utils.get_active_element_css(driver)\n                if (\n                    active_element_css.startswith(selector)\n                    or active_element_css.endswith(\" div\")\n                    or (ctype == \"g_rc\" and \"frame[name\" in active_element_css)\n                ):\n                    found_checkbox = True\n                    sb_config._saved_cf_tab_count = tab_count\n                    break\n                time.sleep(0.02)\n            if not found_checkbox:\n                return\n        except Exception:\n            try:\n                driver.switch_to.default_content()\n            except Exception:\n                return\n        if (\n            (\n                driver.is_element_present(\".footer .clearfix .ray-id\")\n                or driver.is_element_present(\"script[data-cf-beacon]\")\n            )\n            and getattr(sb_config, \"_saved_cf_tab_count\", None)\n            and not __is_cdp_swap_needed(driver)\n        ):\n            driver.uc_open_with_disconnect(driver.current_url, 3.8)\n            with suppress(Exception):\n                if \"--debug\" in sys.argv:\n                    if sb_config._saved_cf_tab_count == 1:\n                        print(' <DEBUG> pyautogui.press(\"\\\\t\")')\n                    else:\n                        print(\n                            ' <DEBUG> pyautogui.press(\"\\\\t\") * %s'\n                            % sb_config._saved_cf_tab_count\n                        )\n                for i in range(sb_config._saved_cf_tab_count):\n                    pyautogui.press(\"\\t\")\n                    time.sleep(0.027)\n                if \"--debug\" in sys.argv:\n                    print(' <DEBUG> pyautogui.press(\" \")')\n                pyautogui.press(\" \")\n        else:\n            driver.disconnect()\n            pyautogui.press(\" \")\n    reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.6\n    if IS_LINUX:\n        reconnect_time = constants.UC.RECONNECT_TIME + 0.2\n    driver.reconnect(reconnect_time)\n\n\ndef _uc_gui_handle_captcha(driver, frame=\"iframe\", ctype=None):\n    _uc_gui_handle_captcha_(driver, frame=frame, ctype=ctype)\n    if (\n        driver.is_element_present(\".footer .clearfix .ray-id\")\n        and not driver.is_element_visible(\"#challenge-success-text\")\n    ):\n        driver.uc_open_with_reconnect(driver.current_url, 3.8)\n        _uc_gui_handle_captcha_(driver, frame=frame, ctype=ctype)\n\n\ndef uc_gui_handle_captcha(driver, frame=\"iframe\"):\n    _uc_gui_handle_captcha(driver, frame=frame, ctype=None)\n\n\ndef uc_gui_handle_cf(driver, frame=\"iframe\"):\n    _uc_gui_handle_captcha(driver, frame=frame, ctype=\"cf_t\")\n\n\ndef uc_gui_handle_rc(driver, frame=\"iframe\"):\n    _uc_gui_handle_captcha(driver, frame=frame, ctype=\"g_rc\")\n\n\ndef uc_switch_to_frame(driver, frame=\"iframe\", reconnect_time=None):\n    from selenium.webdriver.remote.webelement import WebElement\n    if isinstance(frame, WebElement):\n        if not reconnect_time:\n            driver.reconnect(0.1)\n        else:\n            driver.reconnect(reconnect_time)\n        driver.switch_to.frame(frame)\n    else:\n        iframe = driver.locator(frame)\n        if not reconnect_time:\n            driver.reconnect(0.1)\n        else:\n            driver.reconnect(reconnect_time)\n        driver.switch_to.frame(iframe)\n\n\ndef edgedriver_on_path():\n    return os.path.exists(LOCAL_EDGEDRIVER)\n\n\ndef geckodriver_on_path():\n    paths = os.environ[\"PATH\"].split(os.pathsep)\n    for path in paths:\n        if (not IS_WINDOWS) and os.path.exists(path + \"/geckodriver\"):\n            return True\n        elif IS_WINDOWS and os.path.exists(path + \"/geckodriver.exe\"):\n            return True\n    return False\n\n\ndef iedriver_on_path():\n    paths = os.environ[\"PATH\"].split(os.pathsep)\n    for path in paths:\n        if os.path.exists(path + \"/IEDriverServer.exe\"):\n            return True\n    return False\n\n\ndef headless_iedriver_on_path():\n    return os.path.exists(LOCAL_HEADLESS_IEDRIVER)\n\n\ndef get_valid_binary_names_for_browser(browser):\n    if browser == constants.Browser.GOOGLE_CHROME:\n        if IS_LINUX:\n            return constants.ValidBinaries.valid_chrome_binaries_on_linux\n        elif IS_MAC:\n            return constants.ValidBinaries.valid_chrome_binaries_on_macos\n        elif IS_WINDOWS:\n            return constants.ValidBinaries.valid_chrome_binaries_on_windows\n        else:\n            raise Exception(\"Could not determine OS, or unsupported!\")\n    elif browser == constants.Browser.EDGE:\n        if IS_LINUX:\n            return constants.ValidBinaries.valid_edge_binaries_on_linux\n        elif IS_MAC:\n            return constants.ValidBinaries.valid_edge_binaries_on_macos\n        elif IS_WINDOWS:\n            return constants.ValidBinaries.valid_edge_binaries_on_windows\n        else:\n            raise Exception(\"Could not determine OS, or unsupported!\")\n    else:\n        raise Exception(\"Invalid combination for OS browser binaries!\")\n\n\ndef _special_binary_exists(location, name):\n    filename = str(location).split(\"/\")[-1].split(\"\\\\\")[-1]\n    return (\n        location\n        and str(name).lower() in filename.lower()\n        and os.path.exists(location)\n    )\n\n\ndef _repair_chromedriver(chrome_options, headless_options, mcv=None):\n    if mcv:\n        subprocess.check_call(\n            \"sbase get chromedriver %s\" % mcv, shell=True\n        )\n        return\n    driver = None\n    subprocess.check_call(\n        \"sbase get chromedriver 72.0.3626.69\", shell=True\n    )\n    try:\n        service = ChromeService(executable_path=LOCAL_CHROMEDRIVER)\n        driver = webdriver.Chrome(\n            service=service,\n            options=headless_options,\n        )\n    except Exception:\n        subprocess.check_call(\n            \"sbase get chromedriver latest-1\", shell=True\n        )\n        return\n    chrome_version = None\n    if \"version\" in driver.capabilities:\n        chrome_version = driver.capabilities[\"version\"]\n    else:\n        chrome_version = driver.capabilities[\"browserVersion\"]\n    major_chrome_ver = chrome_version.split(\".\")[0]\n    chrome_dict = driver.capabilities[\"chrome\"]\n    driver.quit()\n    chromedriver_ver = chrome_dict[\"chromedriverVersion\"]\n    chromedriver_ver = chromedriver_ver.split(\" \")[0]\n    major_chromedriver_ver = chromedriver_ver.split(\".\")[0]\n    if (\n        major_chromedriver_ver != major_chrome_ver\n        and int(major_chrome_ver) >= 73\n    ):\n        subprocess.check_call(\n            \"sbase get chromedriver %s\" % major_chrome_ver, shell=True\n        )\n    return\n\n\ndef _repair_edgedriver(edge_version):\n    log_d(\n        \"\\nWarning: msedgedriver version doesn't match Edge version!\"\n        \"\\nAttempting to install a matching version of msedgedriver:\"\n    )\n    subprocess.check_call(\n        \"sbase get edgedriver %s\" % edge_version, shell=True\n    )\n    return\n\n\ndef _mark_driver_repaired():\n    abs_path = os.path.abspath(\".\")\n    driver_repaired_lock = constants.MultiBrowser.DRIVER_REPAIRED\n    file_path = os.path.join(abs_path, driver_repaired_lock)\n    if not os.path.exists(DOWNLOADS_FOLDER):\n        os.makedirs(DOWNLOADS_FOLDER)\n    out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    out_file.writelines(\"\")\n    out_file.close()\n\n\ndef _was_driver_repaired():\n    abs_path = os.path.abspath(\".\")\n    driver_repaired_lock = constants.MultiBrowser.DRIVER_REPAIRED\n    file_path = os.path.join(abs_path, driver_repaired_lock)\n    return os.path.exists(file_path)\n\n\ndef _set_proxy_filenames():\n    DOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER\n    for num in range(1000):\n        PROXY_ZIP_PATH = os.path.join(DOWNLOADS_DIR, \"proxy_%s.zip\" % num)\n        PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, \"proxy_ext_dir_%s\" % num)\n        if os.path.exists(PROXY_ZIP_PATH) or os.path.exists(PROXY_DIR_PATH):\n            continue\n        proxy_helper.PROXY_ZIP_PATH = PROXY_ZIP_PATH\n        proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH\n        return\n    # Exceeded upper bound. Use Defaults:\n    PROXY_ZIP_PATH = os.path.join(DOWNLOADS_DIR, \"proxy.zip\")\n    PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, \"proxy_ext_dir\")\n    proxy_helper.PROXY_ZIP_PATH = PROXY_ZIP_PATH\n    proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH\n\n\ndef _add_chrome_proxy_extension(\n    chrome_options,\n    proxy_string,\n    proxy_user,\n    proxy_pass,\n    proxy_scheme,\n    proxy_bypass_list=None,\n    zip_it=True,\n    multi_proxy=False,\n):\n    \"\"\"Implementation of https://stackoverflow.com/a/35293284/7058266\n    for https://stackoverflow.com/q/12848327/7058266\n    (Run Selenium on a proxy server that requires authentication.)\"\"\"\n    zip_it = False\n    args = \" \".join(sys.argv)\n    bypass_list = proxy_bypass_list\n    if (\n        not (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\")\n        and not multi_proxy\n    ):\n        # Single-threaded\n        if zip_it:\n            proxy_zip_lock = fasteners.InterProcessLock(PROXY_ZIP_LOCK)\n            with proxy_zip_lock:\n                proxy_helper.create_proxy_ext(\n                    proxy_string,\n                    proxy_user,\n                    proxy_pass,\n                    proxy_scheme,\n                    bypass_list,\n                )\n                proxy_zip = proxy_helper.PROXY_ZIP_PATH\n                chrome_options.add_extension(proxy_zip)\n        else:\n            proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)\n            with proxy_dir_lock:\n                proxy_helper.create_proxy_ext(\n                    proxy_string,\n                    proxy_user,\n                    proxy_pass,\n                    proxy_scheme,\n                    bypass_list,\n                    zip_it=False,\n                )\n                proxy_dir_path = proxy_helper.PROXY_DIR_PATH\n                chrome_options = add_chrome_ext_dir(\n                    chrome_options, proxy_dir_path\n                )\n    else:\n        # Multi-threaded\n        if zip_it:\n            proxy_zip_lock = fasteners.InterProcessLock(PROXY_ZIP_LOCK)\n            with proxy_zip_lock:\n                with suppress(Exception):\n                    shared_utils.make_writable(PROXY_ZIP_LOCK)\n                if multi_proxy:\n                    _set_proxy_filenames()\n                if not os.path.exists(proxy_helper.PROXY_ZIP_PATH):\n                    proxy_helper.create_proxy_ext(\n                        proxy_string,\n                        proxy_user,\n                        proxy_pass,\n                        proxy_scheme,\n                        bypass_list,\n                    )\n                proxy_zip = proxy_helper.PROXY_ZIP_PATH\n                chrome_options.add_extension(proxy_zip)\n        else:\n            proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)\n            with proxy_dir_lock:\n                with suppress(Exception):\n                    shared_utils.make_writable(PROXY_DIR_LOCK)\n                if multi_proxy:\n                    _set_proxy_filenames()\n                if not os.path.exists(proxy_helper.PROXY_DIR_PATH):\n                    proxy_helper.create_proxy_ext(\n                        proxy_string,\n                        proxy_user,\n                        proxy_pass,\n                        proxy_scheme,\n                        bypass_list,\n                        zip_it=False,\n                    )\n                chrome_options = add_chrome_ext_dir(\n                    chrome_options, proxy_helper.PROXY_DIR_PATH\n                )\n    return chrome_options\n\n\ndef is_using_uc(undetectable, browser_name):\n    if undetectable and browser_name == constants.Browser.GOOGLE_CHROME:\n        return True\n    return False\n\n\ndef _unzip_to_new_folder(zip_file, folder):\n    proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)\n    with proxy_dir_lock:\n        with suppress(Exception):\n            shared_utils.make_writable(PROXY_DIR_LOCK)\n        if not os.path.exists(folder):\n            import zipfile\n            zip_ref = zipfile.ZipFile(zip_file, \"r\")\n            os.makedirs(folder)\n            zip_ref.extractall(folder)\n            zip_ref.close()\n\n\ndef add_chrome_ext_dir(chrome_options, dir_path):\n    option_exists = False\n    for arg in chrome_options.arguments:\n        if arg.startswith(\"--load-extension=\"):\n            option_exists = True\n            chrome_options.arguments.remove(arg)\n            chrome_options.add_argument(\n                \"%s,%s\" % (arg, os.path.realpath(dir_path))\n            )\n    if not option_exists:\n        chrome_options.add_argument(\n            \"--load-extension=%s\" % os.path.realpath(dir_path)\n        )\n    return chrome_options\n\n\ndef _add_chrome_disable_csp_extension(chrome_options):\n    \"\"\"Disable Chrome's Content-Security-Policy with a browser extension.\n    See https://github.com/PhilGrayson/chrome-csp-disable for details.\"\"\"\n    chrome_options.add_extension(DISABLE_CSP_ZIP_PATH)\n    return chrome_options\n\n\ndef _add_chrome_ad_block_extension(chrome_options):\n    \"\"\"Block Ads on Chromium Browsers with a browser extension.\n    See https://github.com/slingamn/simpleblock for details.\"\"\"\n    chrome_options.add_extension(AD_BLOCK_ZIP_PATH)\n    return chrome_options\n\n\ndef _add_chrome_recorder_extension(chrome_options):\n    \"\"\"The SeleniumBase Recorder Chrome/Edge extension.\n    https://seleniumbase.io/help_docs/recorder_mode/\"\"\"\n    chrome_options.add_extension(RECORDER_ZIP_PATH)\n    return chrome_options\n\n\ndef _set_chrome_options(\n    browser_name,\n    downloads_path,\n    headless,\n    locale_code,\n    proxy_string,\n    proxy_auth,\n    proxy_user,\n    proxy_pass,\n    proxy_scheme,\n    proxy_bypass_list,\n    proxy_pac_url,\n    multi_proxy,\n    user_agent,\n    recorder_ext,\n    disable_cookies,\n    disable_js,\n    disable_csp,\n    enable_ws,\n    enable_sync,\n    use_auto_ext,\n    undetectable,\n    uc_cdp_events,\n    uc_subprocess,\n    log_cdp_events,\n    no_sandbox,\n    disable_gpu,\n    headless1,\n    headless2,\n    incognito,\n    guest_mode,\n    dark_mode,\n    devtools,\n    remote_debug,\n    enable_3d_apis,\n    swiftshader,\n    ad_block_on,\n    host_resolver_rules,\n    block_images,\n    do_not_track,\n    chromium_arg,\n    user_data_dir,\n    extension_zip,\n    extension_dir,\n    disable_features,\n    binary_location,\n    driver_version,\n    page_load_strategy,\n    use_wire,\n    external_pdf,\n    servername,\n    mobile_emulator,\n    device_width,\n    device_height,\n    device_pixel_ratio,\n):\n    chrome_options = webdriver.ChromeOptions()\n    if is_using_uc(undetectable, browser_name):\n        from seleniumbase import undetected\n        chrome_options = undetected.ChromeOptions()\n    elif browser_name == constants.Browser.EDGE:\n        chrome_options = webdriver.edge.options.Options()\n    prefs = {}\n    prefs[\"download.default_directory\"] = downloads_path\n    prefs[\"download.directory_upgrade\"] = True\n    prefs[\"download.prompt_for_download\"] = False\n    prefs[\"download_bubble.partial_view_enabled\"] = False\n    prefs[\"credentials_enable_service\"] = False\n    prefs[\"autofill.credit_card_enabled\"] = False\n    prefs[\"local_discovery.notifications_enabled\"] = False\n    prefs[\"safebrowsing.enabled\"] = False  # Prevent PW \"data breach\" pop-ups\n    prefs[\"safebrowsing.disable_download_protection\"] = True\n    prefs[\"omnibox-max-zero-suggest-matches\"] = 0\n    prefs[\"omnibox-use-existing-autocomplete-client\"] = 0\n    prefs[\"omnibox-trending-zero-prefix-suggestions-on-ntp\"] = 0\n    prefs[\"omnibox-local-history-zero-suggest-beyond-ntp\"] = 0\n    prefs[\"omnibox-on-focus-suggestions-contextual-web\"] = 0\n    prefs[\"omnibox-on-focus-suggestions-srp\"] = 0\n    prefs[\"omnibox-zero-suggest-prefetching\"] = 0\n    prefs[\"omnibox-zero-suggest-prefetching-on-srp\"] = 0\n    prefs[\"omnibox-zero-suggest-prefetching-on-web\"] = 0\n    prefs[\"omnibox-zero-suggest-in-memory-caching\"] = 0\n    prefs[\"content_settings.exceptions.automatic_downloads.*.setting\"] = 1\n    prefs[\"default_content_setting_values.notifications\"] = 0\n    prefs[\"default_content_settings.popups\"] = 0\n    prefs[\"managed_default_content_settings.popups\"] = 0\n    prefs[\"profile.password_manager_enabled\"] = False\n    prefs[\"profile.password_manager_leak_detection\"] = False\n    prefs[\"profile.default_content_setting_values.notifications\"] = 2\n    prefs[\"profile.default_content_settings.popups\"] = 0\n    prefs[\"profile.managed_default_content_settings.popups\"] = 0\n    prefs[\"profile.default_content_setting_values.automatic_downloads\"] = 1\n    if locale_code:\n        prefs[\"intl.accept_languages\"] = locale_code\n        sb_config._cdp_locale = locale_code\n    else:\n        sb_config._cdp_locale = None\n    if block_images:\n        prefs[\"profile.managed_default_content_settings.images\"] = 2\n    if disable_cookies:\n        prefs[\"profile.default_content_setting_values.cookies\"] = 2\n    if disable_js:\n        prefs[\"profile.managed_default_content_settings.javascript\"] = 2\n    if do_not_track:\n        prefs[\"enable_do_not_track\"] = True\n    if external_pdf:\n        prefs[\"plugins.always_open_pdf_externally\"] = True\n        pdf_settings = {\n            \"recentDestinations\": [\n                {\"id\": \"Save as PDF\", \"origin\": \"local\", \"account\": \"\"}\n            ],\n            \"selectedDestinationId\": \"Save as PDF\",\n            \"version\": 2,\n        }\n        app_state = \"printing.print_preview_sticky_settings.appState\"\n        prefs[app_state] = json.dumps(pdf_settings)\n    if proxy_string or proxy_pac_url:\n        # Implementation of https://stackoverflow.com/q/65705775/7058266\n        prefs[\"webrtc.ip_handling_policy\"] = \"disable_non_proxied_udp\"\n        prefs[\"webrtc.multiple_routes_enabled\"] = False\n        prefs[\"webrtc.nonproxied_udp_enabled\"] = False\n    chrome_options.add_experimental_option(\"prefs\", prefs)\n    if enable_sync:\n        chrome_options.add_experimental_option(\n            \"excludeSwitches\",\n            [\"enable-automation\", \"enable-logging\", \"disable-sync\"],\n        )\n        chrome_options.add_argument(\"--enable-sync\")\n    else:\n        chrome_options.add_experimental_option(\n            \"excludeSwitches\",\n            [\"enable-automation\", \"enable-logging\", \"enable-blink-features\"],\n        )\n    if log_cdp_events:\n        chrome_options.set_capability(\n            \"goog:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"}\n        )\n    if host_resolver_rules:\n        chrome_options.add_argument(\n            \"--host-resolver-rules=%s\" % host_resolver_rules\n        )\n    if mobile_emulator and not is_using_uc(undetectable, browser_name):\n        emulator_settings = {}\n        device_metrics = {}\n        if (\n            isinstance(device_width, int)\n            and isinstance(device_height, int)\n            and isinstance(device_pixel_ratio, (int, float))\n        ):\n            device_metrics[\"width\"] = device_width\n            device_metrics[\"height\"] = device_height\n            device_metrics[\"pixelRatio\"] = device_pixel_ratio\n        else:\n            device_metrics[\"width\"] = constants.Mobile.WIDTH\n            device_metrics[\"height\"] = constants.Mobile.HEIGHT\n            device_metrics[\"pixelRatio\"] = constants.Mobile.RATIO\n        emulator_settings[\"deviceMetrics\"] = device_metrics\n        if user_agent:\n            emulator_settings[\"userAgent\"] = user_agent\n        chrome_options.add_experimental_option(\n            \"mobileEmulation\", emulator_settings\n        )\n    # Handle Window Position\n    if (headless or headless2) and IS_WINDOWS:\n        # https://stackoverflow.com/a/78999088/7058266\n        chrome_options.add_argument(\"--window-position=-2400,-2400\")\n    else:\n        if (\n            hasattr(settings, \"WINDOW_START_X\")\n            and isinstance(settings.WINDOW_START_X, int)\n            and hasattr(settings, \"WINDOW_START_Y\")\n            and isinstance(settings.WINDOW_START_Y, int)\n        ):\n            chrome_options.add_argument(\n                \"--window-position=%s,%s\" % (\n                    settings.WINDOW_START_X, settings.WINDOW_START_Y\n                )\n            )\n    # Handle Window Size\n    if headless or headless2:\n        if (\n            hasattr(settings, \"HEADLESS_START_WIDTH\")\n            and isinstance(settings.HEADLESS_START_WIDTH, int)\n            and hasattr(settings, \"HEADLESS_START_HEIGHT\")\n            and isinstance(settings.HEADLESS_START_HEIGHT, int)\n        ):\n            chrome_options.add_argument(\n                \"--window-size=%s,%s\" % (\n                    settings.HEADLESS_START_WIDTH,\n                    settings.HEADLESS_START_HEIGHT,\n                )\n            )\n    else:\n        if (\n            hasattr(settings, \"CHROME_START_WIDTH\")\n            and isinstance(settings.CHROME_START_WIDTH, int)\n            and hasattr(settings, \"CHROME_START_HEIGHT\")\n            and isinstance(settings.CHROME_START_HEIGHT, int)\n        ):\n            chrome_options.add_argument(\n                \"--window-size=%s,%s\" % (\n                    settings.CHROME_START_WIDTH,\n                    settings.CHROME_START_HEIGHT,\n                )\n            )\n    if (\n        not proxy_auth\n        and not disable_csp\n        and not ad_block_on\n        and not recorder_ext\n        and (not extension_zip and not extension_dir)\n    ):\n        if (\n            binary_location\n            and isinstance(binary_location, str)\n            and (\n                binary_location.lower().endswith(\"comet\")\n                or binary_location.lower().endswith(\"comet.exe\")\n                or binary_location.lower().endswith(\"atlas\")\n                or binary_location.lower().endswith(\"atlas.exe\")\n            )\n        ):\n            # AI browsers don't like Incognito / Guest Mode\n            incognito = False\n            guest_mode = False\n        if incognito:\n            # Use Chrome's Incognito Mode\n            # Incognito Mode prevents Chrome extensions from loading,\n            # so if using extensions or a feature that uses extensions,\n            # then Chrome's Incognito mode will be disabled instead.\n            chrome_options.add_argument(\"--incognito\")\n        elif guest_mode:\n            # Use Chrome's Guest Mode\n            # Guest mode prevents Chrome extensions from loading,\n            # so if using extensions or a feature that uses extensions,\n            # then Chrome's Guest Mode will be disabled instead.\n            chrome_options.add_argument(\"--guest\")\n        else:\n            pass\n    if dark_mode:\n        chrome_options.add_argument(\"--enable-features=WebContentsForceDark\")\n    if user_data_dir and not is_using_uc(undetectable, browser_name):\n        abs_path = os.path.abspath(user_data_dir)\n        chrome_options.add_argument(\"--user-data-dir=%s\" % abs_path)\n    if extension_zip:\n        # Can be a comma-separated list of .ZIP or .CRX files\n        extension_zip_list = extension_zip.split(\",\")\n        for extension_zip_item in extension_zip_list:\n            abs_path = os.path.realpath(extension_zip_item)\n            if os.path.exists(abs_path):\n                try:\n                    abs_path_dir = os.path.join(\n                        DOWNLOADS_FOLDER, abs_path.split(\".\")[0]\n                    )\n                    _unzip_to_new_folder(abs_path, abs_path_dir)\n                    chrome_options = add_chrome_ext_dir(\n                        chrome_options, abs_path_dir\n                    )\n                    sb_config._ext_dirs.append(abs_path_dir)\n                except Exception:\n                    with suppress(Exception):\n                        chrome_options.add_extension(abs_path)\n    if extension_dir:\n        # load-extension input can be a comma-separated list\n        abs_path = (\n            \",\".join(os.path.realpath(p) for p in extension_dir.split(\",\"))\n        )\n        chrome_options = add_chrome_ext_dir(chrome_options, abs_path)\n        for p in extension_dir.split(\",\"):\n            sb_config._ext_dirs.append(os.path.realpath(p))\n    if (\n        page_load_strategy\n        and page_load_strategy.lower() in [\"eager\", \"none\"]\n    ):\n        # Only change it if not \"normal\", which is the default.\n        chrome_options.page_load_strategy = page_load_strategy.lower()\n    elif (\n        not page_load_strategy\n        and getattr(settings, \"PAGE_LOAD_STRATEGY\", None)\n        and settings.PAGE_LOAD_STRATEGY.lower() in [\"eager\", \"none\"]\n    ):\n        # Only change it if not \"normal\", which is the default.\n        chrome_options.page_load_strategy = settings.PAGE_LOAD_STRATEGY.lower()\n    if headless2:\n        if servername and servername != \"localhost\":\n            # The Grid Node will need Chrome 109 or newer\n            chrome_options.add_argument(\"--headless=new\")\n        else:\n            pass  # Processed After Version Check\n    elif headless:\n        if not undetectable:\n            if headless1:\n                chrome_options.add_argument(\"--headless=old\")\n            else:\n                chrome_options.add_argument(\"--headless\")\n        if undetectable and servername and servername != \"localhost\":\n            # The Grid Node will need Chrome 109 or newer\n            chrome_options.add_argument(\"--headless=new\")\n        else:\n            pass  # Processed After Version Check\n    if (settings.DISABLE_CSP_ON_CHROME or disable_csp) and not headless:\n        # Headless Chrome does not support extensions, which are required\n        # for disabling the Content Security Policy on Chrome.\n        disable_csp_zip = DISABLE_CSP_ZIP_PATH\n        disable_csp_dir = os.path.join(DOWNLOADS_FOLDER, \"disable_csp\")\n        _unzip_to_new_folder(disable_csp_zip, disable_csp_dir)\n        chrome_options = add_chrome_ext_dir(\n            chrome_options, disable_csp_dir\n        )\n        sb_config._ext_dirs.append(disable_csp_dir)\n    if ad_block_on and not headless:\n        # Headless Chrome does not support extensions.\n        ad_block_zip = AD_BLOCK_ZIP_PATH\n        ad_block_dir = os.path.join(DOWNLOADS_FOLDER, \"ad_block\")\n        _unzip_to_new_folder(ad_block_zip, ad_block_dir)\n        chrome_options = add_chrome_ext_dir(chrome_options, ad_block_dir)\n        sb_config._ext_dirs.append(ad_block_dir)\n    if recorder_ext and not headless:\n        recorder_zip = RECORDER_ZIP_PATH\n        recorder_dir = os.path.join(DOWNLOADS_FOLDER, \"recorder\")\n        _unzip_to_new_folder(recorder_zip, recorder_dir)\n        chrome_options = add_chrome_ext_dir(chrome_options, recorder_dir)\n        sb_config._ext_dirs.append(recorder_dir)\n    if chromium_arg and \"sbase\" in chromium_arg:\n        sbase_ext_zip = SBASE_EXT_ZIP_PATH\n        sbase_ext_dir = os.path.join(DOWNLOADS_FOLDER, \"sbase_ext\")\n        _unzip_to_new_folder(sbase_ext_zip, sbase_ext_dir)\n        chrome_options = add_chrome_ext_dir(chrome_options, sbase_ext_dir)\n        sb_config._ext_dirs.append(sbase_ext_dir)\n    if proxy_string:\n        if proxy_auth:\n            zip_it = True\n            if is_using_uc(undetectable, browser_name):\n                zip_it = False  # undetected-chromedriver needs a folder ext\n            chrome_options = _add_chrome_proxy_extension(\n                chrome_options,\n                proxy_string,\n                proxy_user,\n                proxy_pass,\n                proxy_scheme,\n                proxy_bypass_list,\n                zip_it,\n                multi_proxy,\n            )\n        chrome_options.add_argument(\"--proxy-server=%s\" % proxy_string)\n        if proxy_bypass_list:\n            chrome_options.add_argument(\n                \"--proxy-bypass-list=%s\" % proxy_bypass_list\n            )\n    elif proxy_pac_url:\n        if proxy_auth:\n            zip_it = True  # undetected-chromedriver needs a folder ext\n            if is_using_uc(undetectable, browser_name):\n                zip_it = False\n            chrome_options = _add_chrome_proxy_extension(\n                chrome_options,\n                None,\n                proxy_user,\n                proxy_pass,\n                proxy_scheme,\n                proxy_bypass_list,\n                zip_it,\n                multi_proxy,\n            )\n        chrome_options.add_argument(\"--proxy-pac-url=%s\" % proxy_pac_url)\n    if (\n        not is_using_uc(undetectable, browser_name)\n        or not enable_ws\n        or proxy_string\n    ):\n        chrome_options.add_argument(\"--ignore-certificate-errors\")\n        chrome_options.add_argument(\"--ignore-ssl-errors=yes\")\n    if not enable_ws:\n        chrome_options.add_argument(\"--disable-web-security\")\n    if (\n        IS_LINUX\n        or (IS_MAC and not is_using_uc(undetectable, browser_name))\n    ):\n        chrome_options.add_argument(\"--no-sandbox\")\n    if remote_debug:\n        # To access the Debugger, go to: chrome://inspect/#devices\n        # while a Chromium driver is running.\n        # Info: https://chromedevtools.github.io/devtools-protocol/\n        args = \" \".join(sys.argv)\n        debug_port = 9222\n        if (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n            debug_port = service_utils.free_port()\n        chrome_options.add_argument(\"--remote-debugging-port=%s\" % debug_port)\n    if swiftshader:\n        chrome_options.add_argument(\"--use-gl=angle\")\n        chrome_options.add_argument(\"--use-angle=swiftshader-webgl\")\n    elif (\n        not is_using_uc(undetectable, browser_name)\n        and not enable_3d_apis\n    ):\n        chrome_options.add_argument(\"--disable-gpu\")\n    if (\n        (not IS_LINUX and is_using_uc(undetectable, browser_name))\n        or (\n            IS_MAC\n            and binary_location\n            and \"chrome-headless-shell\" in binary_location\n        )\n    ):\n        chrome_options.add_argument(\"--disable-dev-shm-usage\")\n        chrome_options.add_argument(\"--disable-application-cache\")\n    if IS_LINUX:\n        chrome_options.add_argument(\"--disable-dev-shm-usage\")\n        if is_using_uc(undetectable, browser_name):\n            chrome_options.add_argument(\"--disable-application-cache\")\n            chrome_options.add_argument(\"--disable-setuid-sandbox\")\n        if not binary_location:\n            if os.path.exists(\"/bin/google-chrome\"):\n                binary_location = \"/bin/google-chrome\"\n            elif os.path.exists(\"/usr/bin/google-chrome-stable\"):\n                binary_location = \"/usr/bin/google-chrome-stable\"\n            elif os.path.exists(\"/usr/bin/google-chrome\"):\n                binary_location = \"/usr/bin/google-chrome\"\n            elif os.path.exists(\"/usr/bin/google-chrome-stable\"):\n                binary_location = \"/usr/bin/google-chrome-stable\"\n            else:\n                br_app = \"google-chrome\"\n                binary_loc = detect_b_ver.get_binary_location(br_app, True)\n                if os.path.exists(binary_loc):\n                    binary_location = binary_loc\n    extra_disabled_features = []\n    if chromium_arg:\n        # Can be a comma-separated list of Chromium args or a list\n        chromium_arg_list = None\n        if isinstance(chromium_arg, (list, tuple)):\n            chromium_arg_list = chromium_arg\n        else:\n            chromium_arg_list = chromium_arg.split(\",\")\n        for chromium_arg_item in chromium_arg_list:\n            chromium_arg_item = chromium_arg_item.strip()\n            if not chromium_arg_item.startswith(\"--\"):\n                if chromium_arg_item.startswith(\"-\"):\n                    chromium_arg_item = \"-\" + chromium_arg_item\n                else:\n                    chromium_arg_item = \"--\" + chromium_arg_item\n            if \"remote-debugging-port=\" in chromium_arg_item:\n                with suppress(Exception):\n                    # Extra processing for UC Mode\n                    chrome_options._remote_debugging_port = int(\n                        chromium_arg_item.split(\"remote-debugging-port=\")[1]\n                    )\n            if \"set-binary\" in chromium_arg_item and not binary_location:\n                br_app = \"google-chrome\"\n                binary_loc = detect_b_ver.get_binary_location(\n                    br_app, is_using_uc(undetectable, browser_name)\n                )\n                if os.path.exists(binary_loc):\n                    binary_location = binary_loc\n            elif \"disable-features=\" in chromium_arg_item:\n                d_f = chromium_arg_item.split(\"disable-features=\")[-1]\n                extra_disabled_features.append(d_f)\n            elif len(chromium_arg_item) >= 3:\n                chrome_options.add_argument(chromium_arg_item)\n    if disable_features:\n        extra_disabled_features.extend(disable_features.split(\",\"))\n    if devtools and not headless:\n        chrome_options.add_argument(\"--auto-open-devtools-for-tabs\")\n    if user_agent:\n        chrome_options.add_argument(\"--user-agent=%s\" % user_agent)\n    chrome_options.add_argument(\"--safebrowsing-disable-download-protection\")\n    chrome_options.add_argument(\"--disable-search-engine-choice-screen\")\n    chrome_options.add_argument(\"--disable-browser-side-navigation\")\n    chrome_options.add_argument(\"--disable-save-password-bubble\")\n    chrome_options.add_argument(\"--disable-single-click-autofill\")\n    chrome_options.add_argument(\"--allow-file-access-from-files\")\n    chrome_options.add_argument(\"--disable-prompt-on-repost\")\n    chrome_options.add_argument(\"--dns-prefetch-disable\")\n    chrome_options.add_argument(\"--disable-translate\")\n    if binary_location:\n        chrome_options.binary_location = binary_location\n    if not enable_3d_apis and not is_using_uc(undetectable, browser_name):\n        chrome_options.add_argument(\"--disable-3d-apis\")\n    if headless or headless2 or is_using_uc(undetectable, browser_name):\n        chrome_options.add_argument(\"--disable-renderer-backgrounding\")\n    chrome_options.add_argument(\"--disable-backgrounding-occluded-windows\")\n    chrome_options.add_argument(\"--disable-client-side-phishing-detection\")\n    chrome_options.add_argument(\"--disable-device-discovery-notifications\")\n    chrome_options.add_argument(\"--disable-oopr-debug-crash-dump\")\n    chrome_options.add_argument(\"--disable-top-sites\")\n    chrome_options.add_argument(\"--ash-no-nudges\")\n    chrome_options.add_argument(\"--no-crash-upload\")\n    chrome_options.add_argument(\"--deny-permission-prompts\")\n    chrome_options.add_argument(\n        '--simulate-outdated-no-au=\"Tue, 31 Dec 2099 23:59:59 GMT\"'\n    )\n    chrome_options.add_argument(\"--disable-ipc-flooding-protection\")\n    chrome_options.add_argument(\"--disable-password-generation\")\n    chrome_options.add_argument(\"--disable-domain-reliability\")\n    chrome_options.add_argument(\"--disable-breakpad\")\n    included_disabled_features = []\n    included_disabled_features.append(\"OptimizationHints\")\n    included_disabled_features.append(\"OptimizationHintsFetching\")\n    included_disabled_features.append(\"Translate\")\n    included_disabled_features.append(\"ComponentUpdater\")\n    included_disabled_features.append(\"OptimizationTargetPrediction\")\n    included_disabled_features.append(\"OptimizationGuideModelDownloading\")\n    included_disabled_features.append(\"DownloadBubble\")\n    included_disabled_features.append(\"DownloadBubbleV2\")\n    included_disabled_features.append(\"InsecureDownloadWarnings\")\n    included_disabled_features.append(\"InterestFeedContentSuggestions\")\n    included_disabled_features.append(\"PrivacySandboxSettings4\")\n    included_disabled_features.append(\"SidePanelPinning\")\n    included_disabled_features.append(\"UserAgentClientHint\")\n    included_disabled_features.append(\"DisableLoadExtensionCommandLineSwitch\")\n    included_disabled_features.append(\"Bluetooth\")\n    included_disabled_features.append(\"WebBluetooth\")\n    included_disabled_features.append(\"UnifiedWebBluetooth\")\n    included_disabled_features.append(\"WebAuthentication\")\n    included_disabled_features.append(\"PasskeyAuth\")\n    for item in extra_disabled_features:\n        if item not in included_disabled_features:\n            included_disabled_features.append(item)\n    d_f_string = \",\".join(included_disabled_features)\n    chrome_options.add_argument(\"--disable-features=%s\" % d_f_string)\n    chrome_options.add_argument(\"--enable-unsafe-extension-debugging\")\n    if proxy_string:\n        chrome_options.add_argument(\"--test-type\")\n    if proxy_auth or sb_config._ext_dirs:\n        if not is_using_uc(undetectable, browser_name):\n            chrome_options.add_argument(\"--remote-debugging-pipe\")\n            chrome_options.enable_webextensions = True\n            chrome_options.enable_bidi = True\n    if (\n        is_using_uc(undetectable, browser_name)\n        and (\n            not headless\n            or IS_LINUX  # switches to Xvfb (non-headless)\n        )\n    ):\n        chrome_options.add_argument(\"--no-pings\")\n        chrome_options.add_argument(\"--homepage=chrome://version/\")\n        chrome_options.add_argument(\"--animation-duration-scale=0\")\n        chrome_options.add_argument(\"--wm-window-animations-disabled\")\n        chrome_options.add_argument(\"--enable-privacy-sandbox-ads-apis\")\n        chrome_options.add_argument(\"--disable-background-timer-throttling\")\n        # Prevent new tabs opened by Selenium from being blocked:\n        chrome_options.add_argument(\"--disable-popup-blocking\")\n        # Skip remaining options that trigger anti-bot services\n        return chrome_options\n    if not proxy_string:\n        chrome_options.add_argument(\"--test-type\")\n    chrome_options.add_argument(\"--log-level=3\")\n    chrome_options.add_argument(\"--no-first-run\")\n    chrome_options.add_argument(\"--allow-insecure-localhost\")\n    chrome_options.add_argument(\"--allow-running-insecure-content\")\n    chrome_options.add_argument(\"--disable-infobars\")\n    chrome_options.add_argument(\"--disable-notifications\")\n    chrome_options.add_argument(\n        \"--disable-autofill-keyboard-accessory-view[8]\"\n    )\n    chrome_options.add_argument(\"--homepage=about:blank\")\n    chrome_options.add_argument(\"--dom-automation\")\n    chrome_options.add_argument(\"--disable-hang-monitor\")\n    return chrome_options\n\n\ndef _set_firefox_options(\n    downloads_path,\n    headless,\n    locale_code,\n    proxy_string,\n    proxy_bypass_list,\n    proxy_pac_url,\n    user_agent,\n    disable_cookies,\n    disable_js,\n    disable_csp,\n    firefox_arg,\n    firefox_pref,\n    external_pdf,\n):\n    blank_p = \"about:blank\"\n    options = webdriver.FirefoxOptions()\n    options.accept_untrusted_certs = True\n    options.set_preference(\"reader.parse-on-load.enabled\", False)\n    options.set_preference(\"browser.startup.homepage\", blank_p)\n    options.set_preference(\"startup.homepage_welcome_url\", blank_p)\n    options.set_preference(\"startup.homepage_welcome_url.additional\", blank_p)\n    options.set_preference(\"browser.newtab.url\", blank_p)\n    options.set_preference(\"trailhead.firstrun.branches\", \"nofirstrun-empty\")\n    options.set_preference(\"browser.aboutwelcome.enabled\", False)\n    options.set_preference(\"app.update.auto\", False)\n    options.set_preference(\"app.update.enabled\", False)\n    options.set_preference(\"browser.formfill.enable\", False)\n    options.set_preference(\"browser.privatebrowsing.autostart\", True)\n    options.set_preference(\"dom.webnotifications.enabled\", False)\n    options.set_preference(\"dom.disable_beforeunload\", True)\n    options.set_preference(\"browser.contentblocking.database.enabled\", True)\n    options.set_preference(\"extensions.systemAddon.update.enabled\", False)\n    options.set_preference(\"extensions.update.autoUpdateDefault\", False)\n    options.set_preference(\"extensions.update.enabled\", False)\n    options.set_preference(\"datareporting.healthreport.service.enabled\", False)\n    options.set_preference(\"datareporting.healthreport.uploadEnabled\", False)\n    options.set_preference(\"datareporting.policy.dataSubmissionEnabled\", False)\n    options.set_preference(\"browser.search.update\", False)\n    options.set_preference(\"privacy.trackingprotection.enabled\", False)\n    options.set_preference(\"toolkit.telemetry.enabled\", False)\n    options.set_preference(\"toolkit.telemetry.unified\", False)\n    options.set_preference(\"toolkit.telemetry.archive.enabled\", False)\n    if proxy_string:\n        socks_proxy = False\n        socks_ver = 0\n        chunks = proxy_string.split(\":\")\n        if len(chunks) == 3 and (\n            chunks[0] == \"socks4\" or chunks[0] == \"socks5\"\n        ):\n            socks_proxy = True\n            socks_ver = int(chunks[0][5])\n            if chunks[1].startswith(\"//\") and len(chunks[1]) > 2:\n                chunks[1] = chunks[1][2:]\n            proxy_server = chunks[1]\n            proxy_port = chunks[2]\n        else:\n            proxy_server = proxy_string.split(\":\")[0]\n            proxy_port = proxy_string.split(\":\")[1]\n        options.set_preference(\"network.proxy.type\", 1)\n        if socks_proxy:\n            options.set_preference(\"network.proxy.socks\", proxy_server)\n            options.set_preference(\"network.proxy.socks_port\", int(proxy_port))\n            options.set_preference(\"network.proxy.socks_version\", socks_ver)\n        else:\n            options.set_preference(\"network.proxy.http\", proxy_server)\n            options.set_preference(\"network.proxy.http_port\", int(proxy_port))\n            options.set_preference(\"network.proxy.ssl\", proxy_server)\n            options.set_preference(\"network.proxy.ssl_port\", int(proxy_port))\n        if proxy_bypass_list:\n            options.set_preference(\"no_proxies_on\", proxy_bypass_list)\n    elif proxy_pac_url:\n        options.set_preference(\"network.proxy.type\", 2)\n        options.set_preference(\"network.proxy.autoconfig_url\", proxy_pac_url)\n    if user_agent:\n        options.set_preference(\"general.useragent.override\", user_agent)\n    options.set_preference(\n        \"security.mixed_content.block_active_content\", False\n    )\n    options.set_preference(\"security.warn_submit_insecure\", False)\n    if disable_cookies:\n        options.set_preference(\"network.cookie.cookieBehavior\", 2)\n    if disable_js:\n        options.set_preference(\"javascript.enabled\", False)\n    if settings.DISABLE_CSP_ON_FIREFOX or disable_csp:\n        options.set_preference(\"security.csp.enable\", False)\n    options.set_preference(\n        \"browser.download.manager.showAlertOnComplete\", False\n    )\n    if headless and not IS_LINUX:\n        options.add_argument(\"--headless\")\n    elif headless and IS_LINUX:\n        # This assumes Xvfb is running, which prevents many Linux issues.\n        # If not, we'll fix this later during the error-handling process.\n        # To override this feature: ``pytest --firefox-arg=\"-headless\"``.\n        pass\n    if locale_code:\n        options.set_preference(\"intl.accept_languages\", locale_code)\n    options.set_preference(\"browser.shell.checkDefaultBrowser\", False)\n    options.set_preference(\"browser.startup.page\", 0)\n    options.set_preference(\"browser.download.panel.shown\", False)\n    options.set_preference(\"browser.download.animateNotifications\", False)\n    options.set_preference(\"browser.download.dir\", downloads_path)\n    options.set_preference(\"browser.download.folderList\", 2)\n    options.set_preference(\"browser.helperApps.alwaysAsk.force\", False)\n    options.set_preference(\"browser.download.manager.showWhenStarting\", False)\n    options.set_preference(\n        \"browser.helperApps.neverAsk.saveToDisk\",\n        (\n            \"application/pdf,application/zip,application/octet-stream,\"\n            \"text/csv,text/xml,application/xml,text/plain,application/json,\"\n            \"text/octet-stream,application/x-gzip,application/x-tar,\"\n            \"application/java-archive,text/x-java-source,java,\"\n            \"application/javascript,video/jpeg,audio/x-aac,image/svg+xml,\"\n            \"application/x-font-woff,application/x-7z-compressed,\"\n            \"application/mp4,video/mp4,audio/mp4,video/x-msvideo,\"\n            \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n        ),\n    )\n    if external_pdf:\n        options.set_preference(\"pdfjs.disabled\", True)\n    else:\n        options.set_preference(\"pdfjs.disabled\", False)\n    if firefox_arg:\n        # Can be a comma-separated list of Firefox args\n        firefox_arg_list = firefox_arg.split(\",\")\n        for firefox_arg_item in firefox_arg_list:\n            firefox_arg_item = firefox_arg_item.strip()\n            if not firefox_arg_item.startswith(\"-\"):\n                if firefox_arg_item.count(os.sep) == 0:\n                    firefox_arg_item = \"--\" + firefox_arg_item\n            if len(firefox_arg_item) >= 3:\n                options.add_argument(firefox_arg_item)\n    if firefox_pref:\n        # Can be a comma-separated list of Firefox preference:value pairs\n        firefox_pref_list = firefox_pref.split(\",\")\n        for firefox_pref_item in firefox_pref_list:\n            f_pref = None\n            f_pref_value = None\n            needs_conversion = False\n            if firefox_pref_item.count(\":\") == 0:\n                f_pref = firefox_pref_item\n                f_pref_value = True\n            elif firefox_pref_item.count(\":\") == 1:\n                f_pref = firefox_pref_item.split(\":\")[0]\n                f_pref_value = firefox_pref_item.split(\":\")[1]\n                needs_conversion = True\n            elif firefox_pref_item.count(\"://\") == 1:\n                f_pref = firefox_pref_item.split(\":\")[0]\n                f_pref_value = \":\".join(firefox_pref_item.split(\":\")[1:])\n            else:  # More than one \":\" in the set without a URL.\n                raise Exception(\n                    'Incorrect formatting for Firefox \"pref:value\" set!'\n                )\n            if needs_conversion:\n                if f_pref_value.lower() == \"true\" or len(f_pref_value) == 0:\n                    f_pref_value = True\n                elif f_pref_value.lower() == \"false\":\n                    f_pref_value = False\n                elif f_pref_value.isdigit():\n                    f_pref_value = int(f_pref_value)\n                elif f_pref_value.replace(\".\", \"\", 1).isdigit():\n                    f_pref_value = float(f_pref_value)\n                else:\n                    pass  # keep as string\n            if len(f_pref) >= 1:\n                options.set_preference(f_pref, f_pref_value)\n    return options\n\n\ndef get_driver(\n    browser_name=None,\n    headless=False,\n    locale_code=None,\n    use_grid=False,\n    protocol=\"http\",\n    servername=\"localhost\",\n    port=4444,\n    proxy_string=None,\n    proxy_bypass_list=None,\n    proxy_pac_url=None,\n    multi_proxy=None,\n    user_agent=None,\n    cap_file=None,\n    cap_string=None,\n    recorder_ext=False,\n    disable_cookies=False,\n    disable_js=False,\n    disable_csp=False,\n    enable_ws=False,\n    enable_sync=False,\n    use_auto_ext=False,\n    undetectable=False,\n    uc_cdp_events=False,\n    uc_subprocess=False,\n    log_cdp_events=False,\n    no_sandbox=False,\n    disable_gpu=False,\n    headless1=False,\n    headless2=False,\n    incognito=False,\n    guest_mode=False,\n    dark_mode=False,\n    devtools=False,\n    remote_debug=False,\n    enable_3d_apis=False,\n    swiftshader=False,\n    ad_block_on=False,\n    host_resolver_rules=None,\n    block_images=False,\n    do_not_track=False,\n    chromium_arg=None,\n    firefox_arg=None,\n    firefox_pref=None,\n    user_data_dir=None,\n    extension_zip=None,\n    extension_dir=None,\n    disable_features=None,\n    binary_location=None,\n    driver_version=None,\n    page_load_strategy=None,\n    use_wire=False,\n    external_pdf=False,\n    test_id=None,\n    mobile_emulator=False,\n    device_width=None,\n    device_height=None,\n    device_pixel_ratio=None,\n    browser=None,  # A duplicate of browser_name to avoid confusion\n):\n    sb_config._ext_dirs = []\n    driver_dir = DRIVER_DIR\n    if binary_location == \"_chromium_\":\n        driver_dir = DRIVER_DIR_CHROMIUM\n    elif binary_location == \"cft\":\n        driver_dir = DRIVER_DIR_CFT\n    elif binary_location == \"chs\":\n        driver_dir = DRIVER_DIR_CHS\n    elif _special_binary_exists(binary_location, \"opera\"):\n        driver_dir = DRIVER_DIR_OPERA\n        sb_config._cdp_browser = \"opera\"\n    elif _special_binary_exists(binary_location, \"brave\"):\n        driver_dir = DRIVER_DIR_BRAVE\n        sb_config._cdp_browser = \"brave\"\n    elif _special_binary_exists(binary_location, \"comet\"):\n        driver_dir = DRIVER_DIR_COMET\n        sb_config._cdp_browser = \"comet\"\n    elif _special_binary_exists(binary_location, \"atlas\"):\n        driver_dir = DRIVER_DIR_ATLAS\n        sb_config._cdp_browser = \"atlas\"\n    if (\n        hasattr(sb_config, \"settings\")\n        and getattr(sb_config.settings, \"NEW_DRIVER_DIR\", None)\n        and os.path.exists(sb_config.settings.NEW_DRIVER_DIR)\n    ):\n        driver_dir = sb_config.settings.NEW_DRIVER_DIR\n    if not browser_name:\n        if browser:\n            browser_name = browser\n        else:\n            browser_name = \"chrome\"  # The default if not specified\n    if browser_name in constants.ChromiumSubs.chromium_subs:\n        browser_name = \"chrome\"\n    browser_name = browser_name.lower()\n    if is_using_uc(undetectable, browser_name):\n        if ad_block_on:\n            sb_config.ad_block_on = True\n        else:\n            sb_config.ad_block_on = False\n        if disable_csp:\n            sb_config.disable_csp = True\n        else:\n            sb_config.disable_csp = False\n        if mobile_emulator:\n            # For stealthy mobile mode, see the CDP Mode examples\n            # to learn how to properly configure it.\n            user_agent = None  # Undo the override\n            mobile_emulator = False  # Instead, set from CDP Mode\n            sb_config._cdp_mobile_mode = True\n        else:\n            sb_config._cdp_mobile_mode = False\n    if headless2 and browser_name == constants.Browser.FIREFOX:\n        headless2 = False  # Only for Chromium\n        headless = True\n    if binary_location and isinstance(binary_location, str):\n        binary_location = binary_location.strip()\n    if (\n        is_using_uc(undetectable, browser_name)\n        and binary_location\n        and isinstance(binary_location, str)\n        and binary_location.lower() == \"chs\"\n    ):\n        raise Exception(\"UC Mode can't be used with Chrome-Headless-Shell!\")\n    if (\n        binary_location\n        and isinstance(binary_location, str)\n        and (\n            browser_name == constants.Browser.GOOGLE_CHROME\n            or browser_name == constants.Browser.EDGE\n        )\n    ):\n        if (\n            binary_location.lower() == \"_chromium_\"\n            and browser_name == constants.Browser.GOOGLE_CHROME\n        ):\n            binary_folder = None\n            if IS_MAC:\n                binary_folder = \"chrome-mac\"\n            elif IS_LINUX:\n                binary_folder = \"chrome-linux\"\n            elif IS_WINDOWS:\n                binary_folder = \"chrome-win\"\n            if binary_folder:\n                binary_location = os.path.join(driver_dir, binary_folder)\n                if not os.path.exists(binary_location):\n                    from seleniumbase.console_scripts import sb_install\n                    args = \" \".join(sys.argv)\n                    if not (\n                        \"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"\n                    ):\n                        # (Not multithreaded)\n                        sys_args = sys.argv  # Save a copy of current sys args\n                        log_d(\"\\nWarning: Chromium binary not found...\")\n                        try:\n                            sb_install.main(override=\"chromium\")\n                        except Exception as e:\n                            log_d(\"\\nWarning: Chrome download failed: %s\" % e)\n                        sys.argv = sys_args  # Put back the original sys args\n                    else:\n                        chrome_fixing_lock = fasteners.InterProcessLock(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                        with chrome_fixing_lock:\n                            with suppress(Exception):\n                                shared_utils.make_writable(\n                                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                                )\n                            if not os.path.exists(binary_location):\n                                sys_args = sys.argv  # Save a copy of sys args\n                                log_d(\n                                    \"\\nWarning: Chromium binary not found...\"\n                                )\n                                sb_install.main(override=\"chromium\")\n                                sys.argv = sys_args  # Put back original args\n            else:\n                binary_location = None\n        if (\n            binary_location.lower() == \"cft\"\n            and browser_name == constants.Browser.GOOGLE_CHROME\n        ):\n            binary_folder = None\n            if IS_MAC:\n                if IS_ARM_MAC:\n                    binary_folder = \"chrome-mac-arm64\"\n                else:\n                    binary_folder = \"chrome-mac-x64\"\n            elif IS_LINUX:\n                binary_folder = \"chrome-linux64\"\n            elif IS_WINDOWS:\n                if \"64\" in ARCH:\n                    binary_folder = \"chrome-win64\"\n                else:\n                    binary_folder = \"chrome-win32\"\n            if binary_folder:\n                binary_location = os.path.join(driver_dir, binary_folder)\n                if not os.path.exists(binary_location):\n                    from seleniumbase.console_scripts import sb_install\n                    args = \" \".join(sys.argv)\n                    if not (\n                        \"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"\n                    ):\n                        # (Not multithreaded)\n                        sys_args = sys.argv  # Save a copy of current sys args\n                        log_d(\n                            \"\\nWarning: Chrome for Testing binary not found...\"\n                        )\n                        try:\n                            sb_install.main(override=\"cft\")\n                        except Exception as e:\n                            log_d(\"\\nWarning: Chrome download failed: %s\" % e)\n                        sys.argv = sys_args  # Put back the original sys args\n                    else:\n                        chrome_fixing_lock = fasteners.InterProcessLock(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                        with chrome_fixing_lock:\n                            with suppress(Exception):\n                                shared_utils.make_writable(\n                                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                                )\n                            if not os.path.exists(binary_location):\n                                sys_args = sys.argv  # Save a copy of sys args\n                                log_d(\n                                    \"\\nWarning: \"\n                                    \"Chrome for Testing binary not found...\"\n                                )\n                                sb_install.main(override=\"cft\")\n                                sys.argv = sys_args  # Put back original args\n            else:\n                binary_location = None\n        if (\n            binary_location.lower() == \"chs\"\n            and browser_name == constants.Browser.GOOGLE_CHROME\n        ):\n            binary_folder = None\n            if IS_MAC:\n                if IS_ARM_MAC:\n                    binary_folder = \"chrome-headless-shell-mac-arm64\"\n                else:\n                    binary_folder = \"chrome-headless-shell-mac-x64\"\n            elif IS_LINUX:\n                binary_folder = \"chrome-headless-shell-linux64\"\n            elif IS_WINDOWS:\n                if \"64\" in ARCH:\n                    binary_folder = \"chrome-headless-shell-win64\"\n                else:\n                    binary_folder = \"chrome-headless-shell-win32\"\n            if binary_folder:\n                binary_location = os.path.join(driver_dir, binary_folder)\n                if not os.path.exists(binary_location):\n                    from seleniumbase.console_scripts import sb_install\n                    args = \" \".join(sys.argv)\n                    if not (\n                        \"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"\n                    ):\n                        # (Not multithreaded)\n                        sys_args = sys.argv  # Save a copy of current sys args\n                        log_d(\n                            \"\\nWarning: \"\n                            \"Chrome-Headless-Shell binary not found...\"\n                        )\n                        try:\n                            sb_install.main(override=\"chs\")\n                        except Exception as e:\n                            log_d(\n                                \"\\nWarning: \"\n                                \"Chrome-Headless-Shell download failed: %s\" % e\n                            )\n                        sys.argv = sys_args  # Put back the original sys args\n                    else:\n                        chrome_fixing_lock = fasteners.InterProcessLock(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                        with chrome_fixing_lock:\n                            with suppress(Exception):\n                                shared_utils.make_writable(\n                                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                                )\n                            if not os.path.exists(binary_location):\n                                sys_args = sys.argv  # Save a copy of sys args\n                                log_d(\n                                    \"\\nWarning: \"\n                                    \"Chrome-Headless-Shell binary not found...\"\n                                )\n                                sb_install.main(override=\"chs\")\n                                sys.argv = sys_args  # Put back original args\n            else:\n                binary_location = None\n        if not os.path.exists(binary_location):\n            log_d(\n                \"\\nWarning: The Chromium binary specified (%s) was NOT found!\"\n                \"\\n(Will use default settings...)\\n\" % binary_location\n            )\n            binary_location = None\n        elif binary_location.endswith(\"/\") or binary_location.endswith(\"\\\\\"):\n            log_d(\n                \"\\nWarning: The Chromium binary path must be a full path \"\n                \"that includes the browser filename at the end of it!\"\n                \"\\n(Will use default settings...)\\n\" % binary_location\n            )\n            # Example of a valid binary location path - MacOS:\n            # \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\"\n            binary_location = None\n        else:\n            binary_name = binary_location.split(\"/\")[-1].split(\"\\\\\")[-1]\n            valid_names = get_valid_binary_names_for_browser(browser_name)\n            if binary_name == \"Google Chrome for Testing.app\":\n                binary_name = \"Google Chrome for Testing\"\n                binary_location += \"/Contents/MacOS/Google Chrome for Testing\"\n            elif binary_name in [\"chrome-mac-arm64\", \"chrome-mac-x64\"]:\n                binary_name = \"Google Chrome for Testing\"\n                binary_location += \"/Google Chrome for Testing.app\"\n                binary_location += \"/Contents/MacOS/Google Chrome for Testing\"\n            elif binary_name == \"Chromium.app\":\n                binary_name = \"Chromium\"\n                binary_location += \"/Contents/MacOS/Chromium\"\n            elif binary_name in [\"chrome-mac\"]:\n                binary_name = \"Chromium\"\n                binary_location += \"/Chromium.app\"\n                binary_location += \"/Contents/MacOS/Chromium\"\n            elif binary_name == \"chrome-linux64\":\n                binary_name = \"chrome\"\n                binary_location += \"/chrome\"\n            elif binary_name == \"chrome-linux\":\n                binary_name = \"chrome\"\n                binary_location += \"/chrome\"\n            elif binary_name in [\"chrome-win32\", \"chrome-win64\"]:\n                binary_name = \"chrome.exe\"\n                binary_location += \"\\\\chrome.exe\"\n            elif binary_name in [\"chrome-win\"]:\n                binary_name = \"chrome.exe\"\n                binary_location += \"\\\\chrome.exe\"\n            elif binary_name in [\n                \"chrome-headless-shell-mac-arm64\",\n                \"chrome-headless-shell-mac-x64\",\n            ]:\n                binary_name = \"chrome-headless-shell\"\n                binary_location += \"/chrome-headless-shell\"\n            elif binary_name == \"chrome-headless-shell-linux64\":\n                binary_name = \"chrome-headless-shell\"\n                binary_location += \"/chrome-headless-shell\"\n            elif binary_name in [\n                \"chrome-headless-shell-win32\",\n                \"chrome-headless-shell-win64\",\n            ]:\n                binary_name = \"chrome-headless-shell.exe\"\n                binary_location += \"\\\\chrome-headless-shell.exe\"\n            if binary_name not in valid_names:\n                log_d(\n                    \"\\nWarning: The Chromium binary specified is NOT valid!\"\n                    '\\nExpecting \"%s\" to be found in %s for the browser / OS!'\n                    \"\\n(Will use default settings...)\\n\"\n                    \"\" % (binary_name, valid_names)\n                )\n                binary_location = None\n            elif binary_location.lower() == \"chs\":\n                headless = True\n    if (uc_cdp_events or uc_subprocess) and not undetectable:\n        undetectable = True\n    if mobile_emulator and not user_agent:\n        # Use a Pixel user agent by default if not specified\n        user_agent = constants.Mobile.AGENT\n    if page_load_strategy and page_load_strategy.lower() == \"none\":\n        settings.PAGE_LOAD_STRATEGY = \"none\"\n    proxy_auth = False\n    proxy_user = None\n    proxy_pass = None\n    proxy_scheme = \"http\"\n    if proxy_string:\n        # (The line below is for the Chrome 142 proxy auth fix)\n        sb_config._cdp_proxy = proxy_string\n        username_and_password = None\n        if \"@\" in proxy_string:\n            # Format => username:password@hostname:port\n            try:\n                username_and_password = proxy_string.split(\"@\")[0]\n                proxy_string = proxy_string.split(\"@\")[1]\n                proxy_user = username_and_password.split(\":\")[0]\n                proxy_pass = username_and_password.split(\":\")[1]\n            except Exception:\n                raise Exception(\n                    \"The format for using a proxy server with authentication \"\n                    'is: \"username:password@hostname:port\". If using a proxy '\n                    'server without auth, the format is: \"hostname:port\".'\n                )\n            if browser_name != constants.Browser.GOOGLE_CHROME and (\n                browser_name != constants.Browser.EDGE\n            ):\n                raise Exception(\n                    \"Chrome or Edge is required when using a proxy server \"\n                    \"that has authentication! (If using a proxy server \"\n                    \"without auth, Chrome, Edge, or Firefox may be used.)\"\n                )\n        proxy_string, proxy_scheme = proxy_helper.validate_proxy_string(\n            proxy_string, keep_scheme=True\n        )\n        if proxy_user and not proxy_pass:\n            proxy_pass = \"\"\n        if proxy_string and proxy_user:\n            proxy_auth = True\n    elif proxy_pac_url:\n        username_and_password = None\n        if \"@\" in proxy_pac_url:\n            # Format => username:password@PAC_URL.pac\n            try:\n                username_and_password = proxy_pac_url.split(\"@\")[0]\n                proxy_pac_url = proxy_pac_url.split(\"@\")[1]\n                proxy_user = username_and_password.split(\":\")[0]\n                proxy_pass = username_and_password.split(\":\")[1]\n            except Exception:\n                raise Exception(\n                    \"The format for using a PAC URL with authentication \"\n                    'is: \"username:password@PAC_URL.pac\". If using a PAC '\n                    'URL without auth, the format is: \"PAC_URL.pac\".'\n                )\n            if browser_name != constants.Browser.GOOGLE_CHROME and (\n                browser_name != constants.Browser.EDGE\n            ):\n                raise Exception(\n                    \"Chrome or Edge is required when using a PAC URL \"\n                    \"that has authentication! (If using a PAC URL \"\n                    \"without auth, Chrome, Edge, or Firefox may be used.)\"\n                )\n        if not proxy_pac_url.lower().endswith(\".pac\"):\n            raise Exception('The proxy PAC URL must end with \".pac\"!')\n        if proxy_user and not proxy_pass:\n            proxy_pass = \"\"\n        if proxy_pac_url and proxy_user:\n            proxy_auth = True\n    if (\n        is_using_uc(undetectable, browser_name)\n        and not IS_LINUX\n        and headless\n    ):\n        headless = False\n        headless2 = True\n    if (\n        headless\n        and (\n            proxy_auth\n            or disable_cookies\n            or disable_js\n            or ad_block_on\n            or disable_csp\n            or recorder_ext\n            or extension_zip\n            or extension_dir\n        )\n        and (\n            browser_name == constants.Browser.GOOGLE_CHROME\n            or browser_name == constants.Browser.EDGE\n        )\n    ):\n        # Headless Chrome/Edge doesn't support extensions, which are\n        # required when using a proxy server that has authentication,\n        # or when using other SeleniumBase extensions (eg: Recorder).\n        # Instead, base_case.py will use the SBVirtualDisplay when not\n        # using Chrome's built-in headless mode. See link for details:\n        # https://bugs.chromium.org/p/chromium/issues/detail?id=706008\n        headless = False\n        if not IS_LINUX:\n            # Use the new headless mode on Chrome if not using Linux:\n            # bugs.chromium.org/p/chromium/issues/detail?id=706008#c36\n            # Although Linux is technically supported, there are a lot\n            # of old versions of Chrome on Linux server machines, and\n            # this mode requires a recent version of Chrome to work.\n            # Specify \"--headless2\" as a pytest arg to use on Linux.\n            headless2 = True\n    if (\n        browser_name == constants.Browser.GOOGLE_CHROME\n        and user_data_dir\n        and len(user_data_dir) < 3\n    ):\n        raise Exception(\n            \"Name length of Chrome's User Data Directory must be >= 3.\"\n        )\n    if use_grid:\n        return get_remote_driver(\n            browser_name,\n            headless,\n            locale_code,\n            protocol,\n            servername,\n            port,\n            proxy_string,\n            proxy_auth,\n            proxy_user,\n            proxy_pass,\n            proxy_scheme,\n            proxy_bypass_list,\n            proxy_pac_url,\n            multi_proxy,\n            user_agent,\n            cap_file,\n            cap_string,\n            recorder_ext,\n            disable_cookies,\n            disable_js,\n            disable_csp,\n            enable_ws,\n            enable_sync,\n            use_auto_ext,\n            undetectable,\n            uc_cdp_events,\n            uc_subprocess,\n            log_cdp_events,\n            no_sandbox,\n            disable_gpu,\n            headless1,\n            headless2,\n            incognito,\n            guest_mode,\n            dark_mode,\n            devtools,\n            remote_debug,\n            enable_3d_apis,\n            swiftshader,\n            ad_block_on,\n            host_resolver_rules,\n            block_images,\n            do_not_track,\n            chromium_arg,\n            firefox_arg,\n            firefox_pref,\n            user_data_dir,\n            extension_zip,\n            extension_dir,\n            disable_features,\n            binary_location,\n            driver_version,\n            page_load_strategy,\n            use_wire,\n            external_pdf,\n            test_id,\n            mobile_emulator,\n            device_width,\n            device_height,\n            device_pixel_ratio,\n        )\n    else:\n        return get_local_driver(\n            browser_name,\n            headless,\n            locale_code,\n            servername,\n            proxy_string,\n            proxy_auth,\n            proxy_user,\n            proxy_pass,\n            proxy_scheme,\n            proxy_bypass_list,\n            proxy_pac_url,\n            multi_proxy,\n            user_agent,\n            recorder_ext,\n            disable_cookies,\n            disable_js,\n            disable_csp,\n            enable_ws,\n            enable_sync,\n            use_auto_ext,\n            undetectable,\n            uc_cdp_events,\n            uc_subprocess,\n            log_cdp_events,\n            no_sandbox,\n            disable_gpu,\n            headless1,\n            headless2,\n            incognito,\n            guest_mode,\n            dark_mode,\n            devtools,\n            remote_debug,\n            enable_3d_apis,\n            swiftshader,\n            ad_block_on,\n            host_resolver_rules,\n            block_images,\n            do_not_track,\n            chromium_arg,\n            firefox_arg,\n            firefox_pref,\n            user_data_dir,\n            extension_zip,\n            extension_dir,\n            disable_features,\n            binary_location,\n            driver_version,\n            page_load_strategy,\n            use_wire,\n            external_pdf,\n            mobile_emulator,\n            device_width,\n            device_height,\n            device_pixel_ratio,\n        )\n\n\ndef get_remote_driver(\n    browser_name,\n    headless,\n    locale_code,\n    protocol,\n    servername,\n    port,\n    proxy_string,\n    proxy_auth,\n    proxy_user,\n    proxy_pass,\n    proxy_scheme,\n    proxy_bypass_list,\n    proxy_pac_url,\n    multi_proxy,\n    user_agent,\n    cap_file,\n    cap_string,\n    recorder_ext,\n    disable_cookies,\n    disable_js,\n    disable_csp,\n    enable_ws,\n    enable_sync,\n    use_auto_ext,\n    undetectable,\n    uc_cdp_events,\n    uc_subprocess,\n    log_cdp_events,\n    no_sandbox,\n    disable_gpu,\n    headless1,\n    headless2,\n    incognito,\n    guest_mode,\n    dark_mode,\n    devtools,\n    remote_debug,\n    enable_3d_apis,\n    swiftshader,\n    ad_block_on,\n    host_resolver_rules,\n    block_images,\n    do_not_track,\n    chromium_arg,\n    firefox_arg,\n    firefox_pref,\n    user_data_dir,\n    extension_zip,\n    extension_dir,\n    disable_features,\n    binary_location,\n    driver_version,\n    page_load_strategy,\n    use_wire,\n    external_pdf,\n    test_id,\n    mobile_emulator,\n    device_width,\n    device_height,\n    device_pixel_ratio,\n):\n    if use_wire:\n        pip_find_lock = fasteners.InterProcessLock(\n            constants.PipInstall.FINDLOCK\n        )\n        with pip_find_lock:  # Prevent issues with multiple processes\n            with suppress(Exception):\n                shared_utils.make_writable(constants.PipInstall.FINDLOCK)\n            try:\n                from seleniumwire import webdriver\n                import blinker\n                with suppress(Exception):\n                    use_blinker_ver = constants.SeleniumWire.BLINKER_VER\n                    if blinker.__version__ != use_blinker_ver:\n                        shared_utils.pip_install(\n                            \"blinker\", version=use_blinker_ver\n                        )\n                del blinker\n            except Exception:\n                shared_utils.pip_install(\n                    \"blinker\", version=constants.SeleniumWire.BLINKER_VER\n                )\n                shared_utils.pip_install(\n                    \"selenium-wire\", version=constants.SeleniumWire.VER\n                )\n                from seleniumwire import webdriver\n            warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n    else:\n        from selenium import webdriver\n\n    # Construct the address for connecting to a Selenium Grid\n    if servername.startswith(\"https://\"):\n        protocol = \"https\"\n        servername = servername.split(\"https://\")[1]\n    elif \"://\" in servername:\n        servername = servername.split(\"://\")[1]\n    server_with_port = \"\"\n    if \":\" not in servername:\n        col_port = \":\" + str(port)\n        first_slash = servername.find(\"/\")\n        if first_slash != -1:\n            server_with_port = (\n                servername[:first_slash] + col_port + servername[first_slash:]\n            )\n        else:\n            server_with_port = servername + col_port\n    else:\n        server_with_port = servername\n    address = \"%s://%s\" % (protocol, server_with_port)\n    if not address.endswith(\"/wd/hub\"):\n        if address.endswith(\"/\"):\n            address += \"wd/hub\"\n        else:\n            address += \"/wd/hub\"\n    downloads_path = DOWNLOADS_FOLDER\n    desired_caps = {}\n    extra_caps = {}\n    if cap_file:\n        from seleniumbase.core import capabilities_parser\n        desired_caps = capabilities_parser.get_desired_capabilities(cap_file)\n    if cap_string:\n        try:\n            extra_caps = json.loads(str(cap_string))\n        except Exception as e:\n            p1 = \"Invalid input format for --cap-string:\\n  %s\" % e\n            p2 = \"The --cap-string input was: %s\" % cap_string\n            p3 = (\n                \"Enclose cap-string in SINGLE quotes; \"\n                \"keys and values in DOUBLE quotes.\"\n            )\n            p4 = (\n                \"\"\"Here's an example of correct cap-string usage:\\n  \"\"\"\n                \"\"\"--cap-string='{\"browserName\":\"chrome\",\"name\":\"test1\"}'\"\"\"\n            )\n            raise Exception(\"%s\\n%s\\n%s\\n%s\" % (p1, p2, p3, p4))\n        for cap_key in extra_caps.keys():\n            desired_caps[cap_key] = extra_caps[cap_key]\n    if cap_file or cap_string:\n        if \"name\" in desired_caps.keys():\n            if desired_caps[\"name\"] == \"*\":\n                desired_caps[\"name\"] = test_id\n    if browser_name == constants.Browser.GOOGLE_CHROME:\n        chrome_options = _set_chrome_options(\n            browser_name,\n            downloads_path,\n            headless,\n            locale_code,\n            proxy_string,\n            proxy_auth,\n            proxy_user,\n            proxy_pass,\n            proxy_scheme,\n            proxy_bypass_list,\n            proxy_pac_url,\n            multi_proxy,\n            user_agent,\n            recorder_ext,\n            disable_cookies,\n            disable_js,\n            disable_csp,\n            enable_ws,\n            enable_sync,\n            use_auto_ext,\n            undetectable,\n            uc_cdp_events,\n            uc_subprocess,\n            log_cdp_events,\n            no_sandbox,\n            disable_gpu,\n            headless1,\n            headless2,\n            incognito,\n            guest_mode,\n            dark_mode,\n            devtools,\n            remote_debug,\n            enable_3d_apis,\n            swiftshader,\n            ad_block_on,\n            host_resolver_rules,\n            block_images,\n            do_not_track,\n            chromium_arg,\n            user_data_dir,\n            extension_zip,\n            extension_dir,\n            disable_features,\n            binary_location,\n            driver_version,\n            page_load_strategy,\n            use_wire,\n            external_pdf,\n            servername,\n            mobile_emulator,\n            device_width,\n            device_height,\n            device_pixel_ratio,\n        )\n        capabilities = webdriver.ChromeOptions().to_capabilities()\n        # Set custom desired capabilities\n        selenoid = False\n        selenoid_options = None\n        screen_resolution = None\n        browser_version = None\n        platform_name = None\n        extension_capabilities = {}\n        for key in desired_caps.keys():\n            capabilities[key] = desired_caps[key]\n            if key == \"selenoid:options\":\n                selenoid = True\n                selenoid_options = desired_caps[key]\n            elif key == \"screenResolution\":\n                screen_resolution = desired_caps[key]\n            elif key == \"version\" or key == \"browserVersion\":\n                browser_version = desired_caps[key]\n            elif key == \"platform\" or key == \"platformName\":\n                platform_name = desired_caps[key]\n            elif re.match(\"[a-zA-Z0-9]*:[a-zA-Z0-9]*\", key):\n                extension_capabilities[key] = desired_caps[key]\n        cap_str = str(desired_caps).lower()\n        if \"browserstack\" in cap_str or \"bstack\" in cap_str:\n            chrome_options.set_capability(\"bstack:options\", desired_caps)\n        chrome_options.set_capability(\"cloud:options\", capabilities)\n        if selenoid:\n            snops = selenoid_options\n            chrome_options.set_capability(\"selenoid:options\", snops)\n        if screen_resolution:\n            scres = screen_resolution\n            chrome_options.set_capability(\"screenResolution\", scres)\n        if browser_version:\n            br_vers = browser_version\n            chrome_options.set_capability(\"browserVersion\", br_vers)\n        if platform_name:\n            plat_name = platform_name\n            chrome_options.set_capability(\"platformName\", plat_name)\n        if extension_capabilities:\n            for key in extension_capabilities:\n                ext_caps = extension_capabilities\n                chrome_options.set_capability(key, ext_caps[key])\n        driver = webdriver.Remote(\n            command_executor=address,\n            options=chrome_options,\n        )\n        return extend_driver(driver)\n    elif browser_name == constants.Browser.FIREFOX:\n        firefox_options = _set_firefox_options(\n            downloads_path,\n            headless,\n            locale_code,\n            proxy_string,\n            proxy_bypass_list,\n            proxy_pac_url,\n            user_agent,\n            disable_cookies,\n            disable_js,\n            disable_csp,\n            firefox_arg,\n            firefox_pref,\n            external_pdf,\n        )\n        capabilities = webdriver.FirefoxOptions().to_capabilities()\n        capabilities[\"marionette\"] = True\n        if IS_LINUX and headless:\n            capabilities[\"moz:firefoxOptions\"] = {\"args\": [\"-headless\"]}\n        # Set custom desired capabilities\n        selenoid = False\n        selenoid_options = None\n        screen_resolution = None\n        browser_version = None\n        platform_name = None\n        extension_capabilities = {}\n        for key in desired_caps.keys():\n            capabilities[key] = desired_caps[key]\n            if key == \"selenoid:options\":\n                selenoid = True\n                selenoid_options = desired_caps[key]\n            elif key == \"screenResolution\":\n                screen_resolution = desired_caps[key]\n            elif key == \"version\" or key == \"browserVersion\":\n                browser_version = desired_caps[key]\n            elif key == \"platform\" or key == \"platformName\":\n                platform_name = desired_caps[key]\n            elif re.match(\"[a-zA-Z0-9]*:[a-zA-Z0-9]*\", key):\n                extension_capabilities[key] = desired_caps[key]\n        cap_str = str(desired_caps).lower()\n        if \"browserstack\" in cap_str or \"bstack\" in cap_str:\n            firefox_options.set_capability(\"bstack:options\", desired_caps)\n        firefox_options.set_capability(\"cloud:options\", capabilities)\n        if selenoid:\n            snops = selenoid_options\n            firefox_options.set_capability(\"selenoid:options\", snops)\n        if screen_resolution:\n            scres = screen_resolution\n            firefox_options.set_capability(\"screenResolution\", scres)\n        if browser_version:\n            br_vers = browser_version\n            firefox_options.set_capability(\"browserVersion\", br_vers)\n        if platform_name:\n            plat_name = platform_name\n            firefox_options.set_capability(\"platformName\", plat_name)\n        if extension_capabilities:\n            for key in extension_capabilities:\n                ext_caps = extension_capabilities\n                firefox_options.set_capability(key, ext_caps[key])\n        driver = webdriver.Remote(\n            command_executor=address,\n            options=firefox_options,\n        )\n        return extend_driver(driver)\n    elif browser_name == constants.Browser.INTERNET_EXPLORER:\n        capabilities = webdriver.DesiredCapabilities.INTERNETEXPLORER\n        remote_options = ArgOptions()\n        remote_options.set_capability(\"cloud:options\", desired_caps)\n        driver = webdriver.Remote(\n            command_executor=address,\n            options=remote_options,\n        )\n        return extend_driver(driver)\n    elif browser_name == constants.Browser.EDGE:\n        edge_options = _set_chrome_options(\n            browser_name,\n            downloads_path,\n            headless,\n            locale_code,\n            proxy_string,\n            proxy_auth,\n            proxy_user,\n            proxy_pass,\n            proxy_scheme,\n            proxy_bypass_list,\n            proxy_pac_url,\n            multi_proxy,\n            user_agent,\n            recorder_ext,\n            disable_cookies,\n            disable_js,\n            disable_csp,\n            enable_ws,\n            enable_sync,\n            use_auto_ext,\n            undetectable,\n            uc_cdp_events,\n            uc_subprocess,\n            log_cdp_events,\n            no_sandbox,\n            disable_gpu,\n            headless1,\n            headless2,\n            incognito,\n            guest_mode,\n            dark_mode,\n            devtools,\n            remote_debug,\n            enable_3d_apis,\n            swiftshader,\n            ad_block_on,\n            host_resolver_rules,\n            block_images,\n            do_not_track,\n            chromium_arg,\n            user_data_dir,\n            extension_zip,\n            extension_dir,\n            disable_features,\n            binary_location,\n            driver_version,\n            page_load_strategy,\n            use_wire,\n            external_pdf,\n            servername,\n            mobile_emulator,\n            device_width,\n            device_height,\n            device_pixel_ratio,\n        )\n        capabilities = webdriver.EdgeOptions().to_capabilities()\n        # Set custom desired capabilities\n        selenoid = False\n        selenoid_options = None\n        screen_resolution = None\n        browser_version = None\n        platform_name = None\n        extension_capabilities = {}\n        for key in desired_caps.keys():\n            capabilities[key] = desired_caps[key]\n            if key == \"selenoid:options\":\n                selenoid = True\n                selenoid_options = desired_caps[key]\n            elif key == \"screenResolution\":\n                screen_resolution = desired_caps[key]\n            elif key == \"version\" or key == \"browserVersion\":\n                browser_version = desired_caps[key]\n            elif key == \"platform\" or key == \"platformName\":\n                platform_name = desired_caps[key]\n            elif re.match(\"[a-zA-Z0-9]*:[a-zA-Z0-9]*\", key):\n                extension_capabilities[key] = desired_caps[key]\n        edge_options.set_capability(\"cloud:options\", capabilities)\n        if selenoid:\n            snops = selenoid_options\n            edge_options.set_capability(\"selenoid:options\", snops)\n        if screen_resolution:\n            scres = screen_resolution\n            edge_options.set_capability(\"screenResolution\", scres)\n        if browser_version:\n            br_vers = browser_version\n            edge_options.set_capability(\"browserVersion\", br_vers)\n        if platform_name:\n            plat_name = platform_name\n            edge_options.set_capability(\"platformName\", plat_name)\n        if extension_capabilities:\n            for key in extension_capabilities:\n                ext_caps = extension_capabilities\n                edge_options.set_capability(key, ext_caps[key])\n        driver = webdriver.Remote(\n            command_executor=address,\n            options=edge_options,\n        )\n        return extend_driver(driver)\n    elif browser_name == constants.Browser.SAFARI:\n        capabilities = webdriver.DesiredCapabilities.SAFARI\n        remote_options = ArgOptions()\n        remote_options.set_capability(\"cloud:options\", desired_caps)\n        driver = webdriver.Remote(\n            command_executor=address,\n            options=remote_options,\n        )\n        return extend_driver(driver)\n    elif browser_name == constants.Browser.REMOTE:\n        remote_options = ArgOptions()\n        for cap_name, cap_value in desired_caps.items():\n            remote_options.set_capability(cap_name, cap_value)\n        cap_str = str(desired_caps).lower()\n        if \"browserstack\" in cap_str or \"bstack\" in cap_str:\n            remote_options.set_capability(\"bstack:options\", desired_caps)\n        driver = webdriver.Remote(\n            command_executor=address,\n            options=remote_options,\n        )\n        return extend_driver(driver)\n\n\ndef get_local_driver(\n    browser_name,\n    headless,\n    locale_code,\n    servername,\n    proxy_string,\n    proxy_auth,\n    proxy_user,\n    proxy_pass,\n    proxy_scheme,\n    proxy_bypass_list,\n    proxy_pac_url,\n    multi_proxy,\n    user_agent,\n    recorder_ext,\n    disable_cookies,\n    disable_js,\n    disable_csp,\n    enable_ws,\n    enable_sync,\n    use_auto_ext,\n    undetectable,\n    uc_cdp_events,\n    uc_subprocess,\n    log_cdp_events,\n    no_sandbox,\n    disable_gpu,\n    headless1,\n    headless2,\n    incognito,\n    guest_mode,\n    dark_mode,\n    devtools,\n    remote_debug,\n    enable_3d_apis,\n    swiftshader,\n    ad_block_on,\n    host_resolver_rules,\n    block_images,\n    do_not_track,\n    chromium_arg,\n    firefox_arg,\n    firefox_pref,\n    user_data_dir,\n    extension_zip,\n    extension_dir,\n    disable_features,\n    binary_location,\n    driver_version,\n    page_load_strategy,\n    use_wire,\n    external_pdf,\n    mobile_emulator,\n    device_width,\n    device_height,\n    device_pixel_ratio,\n):\n    \"\"\"Spins up a new web browser and returns the driver.\n    Can also be used to spin up additional browsers for the same test.\"\"\"\n    downloads_path = DOWNLOADS_FOLDER\n    driver_dir = DRIVER_DIR\n    special_chrome = False\n    if binary_location:\n        if (\n            binary_location == \"_chromium_\"\n            or \"chromium_drivers\" in binary_location\n        ):\n            special_chrome = True\n            driver_dir = DRIVER_DIR_CHROMIUM\n        elif binary_location == \"cft\" or \"cft_drivers\" in binary_location:\n            special_chrome = True\n            driver_dir = DRIVER_DIR_CFT\n        elif binary_location == \"chs\" or \"chs_drivers\" in binary_location:\n            special_chrome = True\n            driver_dir = DRIVER_DIR_CHS\n        elif _special_binary_exists(binary_location, \"opera\"):\n            special_chrome = True\n            driver_dir = DRIVER_DIR_OPERA\n        elif _special_binary_exists(binary_location, \"brave\"):\n            special_chrome = True\n            driver_dir = DRIVER_DIR_BRAVE\n        elif _special_binary_exists(binary_location, \"comet\"):\n            special_chrome = True\n            driver_dir = DRIVER_DIR_COMET\n        elif _special_binary_exists(binary_location, \"atlas\"):\n            special_chrome = True\n            driver_dir = DRIVER_DIR_ATLAS\n    if (\n        hasattr(sb_config, \"settings\")\n        and getattr(sb_config.settings, \"NEW_DRIVER_DIR\", None)\n        and os.path.exists(sb_config.settings.NEW_DRIVER_DIR)\n    ):\n        driver_dir = sb_config.settings.NEW_DRIVER_DIR\n    elif special_chrome:\n        override_driver_dir(driver_dir)\n    if IS_MAC or IS_LINUX:\n        local_chromedriver = driver_dir + \"/chromedriver\"\n        local_geckodriver = driver_dir + \"/geckodriver\"\n        local_edgedriver = driver_dir + \"/msedgedriver\"\n        local_uc_driver = driver_dir + \"/uc_driver\"\n    elif IS_WINDOWS:\n        local_edgedriver = driver_dir + \"/msedgedriver.exe\"\n        local_iedriver = driver_dir + \"/IEDriverServer.exe\"\n        local_headless_iedriver = driver_dir + \"/headless_ie_selenium.exe\"\n        local_chromedriver = driver_dir + \"/chromedriver.exe\"\n        local_geckodriver = driver_dir + \"/geckodriver.exe\"\n        local_uc_driver = driver_dir + \"/uc_driver.exe\"\n    b_path = binary_location\n    use_uc = is_using_uc(undetectable, browser_name)\n    if use_wire:\n        pip_find_lock = fasteners.InterProcessLock(\n            constants.PipInstall.FINDLOCK\n        )\n        with pip_find_lock:  # Prevent issues with multiple processes\n            with suppress(Exception):\n                shared_utils.make_writable(constants.PipInstall.FINDLOCK)\n            try:\n                from seleniumwire import webdriver\n                import blinker\n                with suppress(Exception):\n                    use_blinker_ver = constants.SeleniumWire.BLINKER_VER\n                    if blinker.__version__ != use_blinker_ver:\n                        shared_utils.pip_install(\n                            \"blinker\", version=use_blinker_ver\n                        )\n                del blinker\n            except Exception:\n                shared_utils.pip_install(\n                    \"blinker\", version=constants.SeleniumWire.BLINKER_VER\n                )\n                shared_utils.pip_install(\n                    \"selenium-wire\", version=constants.SeleniumWire.VER\n                )\n                from seleniumwire import webdriver\n            warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n    else:\n        from selenium import webdriver\n\n    if browser_name == constants.Browser.FIREFOX:\n        firefox_options = _set_firefox_options(\n            downloads_path,\n            headless,\n            locale_code,\n            proxy_string,\n            proxy_bypass_list,\n            proxy_pac_url,\n            user_agent,\n            disable_cookies,\n            disable_js,\n            disable_csp,\n            firefox_arg,\n            firefox_pref,\n            external_pdf,\n        )\n        if local_geckodriver and os.path.exists(local_geckodriver):\n            try:\n                make_driver_executable_if_not(local_geckodriver)\n            except Exception as e:\n                logging.debug(\n                    \"\\nWarning: Could not make geckodriver\"\n                    \" executable: %s\" % e\n                )\n        elif not geckodriver_on_path():\n            from seleniumbase.console_scripts import sb_install\n            args = \" \".join(sys.argv)\n            if not (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n                # (Not multithreaded)\n                sys_args = sys.argv  # Save a copy of current sys args\n                log_d(\"\\nWarning: geckodriver not found. Getting it now:\")\n                try:\n                    sb_install.main(override=\"geckodriver\")\n                except Exception as e:\n                    log_d(\"\\nWarning: Could not install geckodriver: %s\" % e)\n                sys.argv = sys_args  # Put back the original sys args\n            else:\n                geckodriver_fixing_lock = fasteners.InterProcessLock(\n                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                )\n                with geckodriver_fixing_lock:\n                    with suppress(Exception):\n                        shared_utils.make_writable(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                    if not geckodriver_on_path():\n                        sys_args = sys.argv  # Save a copy of sys args\n                        log_d(\n                            \"\\nWarning: geckodriver not found. \"\n                            \"Getting it now:\"\n                        )\n                        sb_install.main(override=\"geckodriver\")\n                        sys.argv = sys_args  # Put back original sys args\n        # Launch Firefox\n        if os.path.exists(local_geckodriver):\n            service = FirefoxService(\n                executable_path=local_geckodriver,\n                log_output=os.devnull,\n            )\n            try:\n                driver = webdriver.Firefox(\n                    service=service,\n                    options=firefox_options,\n                )\n                return extend_driver(driver)\n            except BaseException as e:\n                if (\n                    \"geckodriver unexpectedly exited\" in str(e)\n                    or \"Process unexpectedly closed\" in str(e)\n                    or \"Failed to read marionette port\" in str(e)\n                    or \"A connection attempt failed\" in str(e)\n                    or \"Expected browser binary\" in str(e)\n                    or hasattr(e, \"msg\") and (\n                        \"geckodriver unexpectedly exited\" in e.msg\n                        or \"Process unexpectedly closed\" in e.msg\n                        or \"Failed to read marionette port\" in e.msg\n                        or \"A connection attempt failed\" in e.msg\n                        or \"Expected browser binary\" in e.msg\n                    )\n                ):\n                    time.sleep(0.1)\n                    if (\n                        IS_LINUX\n                        and headless\n                        and (\n                            \"unexpected\" in str(e)\n                            or (\n                                hasattr(e, \"msg\") and \"unexpected\" in e.msg\n                            )\n                        )\n                    ):\n                        firefox_options.add_argument(\"-headless\")\n                    driver = webdriver.Firefox(\n                        service=service,\n                        options=firefox_options,\n                    )\n                    return extend_driver(driver)\n                else:\n                    raise  # Not an obvious fix.\n        else:\n            service = FirefoxService(log_output=os.devnull)\n            try:\n                driver = webdriver.Firefox(\n                    service=service,\n                    options=firefox_options,\n                )\n                return extend_driver(driver)\n            except BaseException as e:\n                if (\n                    \"geckodriver unexpectedly exited\" in str(e)\n                    or \"Process unexpectedly closed\" in str(e)\n                    or \"Failed to read marionette port\" in str(e)\n                    or \"A connection attempt failed\" in str(e)\n                    or \"Expected browser binary\" in str(e)\n                    or hasattr(e, \"msg\") and (\n                        \"geckodriver unexpectedly exited\" in e.msg\n                        or \"Process unexpectedly closed\" in e.msg\n                        or \"Failed to read marionette port\" in e.msg\n                        or \"A connection attempt failed\" in e.msg\n                        or \"Expected browser binary\" in e.msg\n                    )\n                ):\n                    time.sleep(0.1)\n                    if (\n                        IS_LINUX\n                        and headless\n                        and (\n                            \"unexpected\" in str(e)\n                            or (\n                                hasattr(e, \"msg\") and \"unexpected\" in e.msg\n                            )\n                        )\n                    ):\n                        firefox_options.add_argument(\"-headless\")\n                    driver = webdriver.Firefox(\n                        service=service,\n                        options=firefox_options,\n                    )\n                    return extend_driver(driver)\n                else:\n                    raise  # Not an obvious fix.\n    elif browser_name == constants.Browser.INTERNET_EXPLORER:\n        if not IS_WINDOWS:\n            raise Exception(\n                \"IE Browser is for Windows-based systems only!\"\n            )\n        from selenium.webdriver.ie.options import Options\n        from selenium.webdriver.ie.service import Service\n        ie_options = Options()\n        ie_options.add_argument(\"--guest\")\n        ie_options.attach_to_edge_chrome = True\n        ie_options.ignore_protected_mode_settings = True\n        ie_options.ignore_zoom_level = True\n        ie_options.require_window_focus = False\n        ie_options.native_events = True\n        ie_options.full_page_screenshot = True\n        ie_options.persistent_hover = True\n        if local_iedriver and os.path.exists(local_iedriver):\n            try:\n                make_driver_executable_if_not(local_iedriver)\n            except Exception as e:\n                logging.debug(\n                    \"\\nWarning: Could not make IEDriver executable: %s\" % e\n                )\n        elif not iedriver_on_path():\n            from seleniumbase.console_scripts import sb_install\n            args = \" \".join(sys.argv)\n            if not (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n                # (Not multithreaded)\n                sys_args = sys.argv  # Save a copy of current sys args\n                log_d(\"\\nWarning: IEDriver not found. Getting it now:\")\n                sb_install.main(override=\"iedriver\")\n                sys.argv = sys_args  # Put back the original sys args\n        if local_headless_iedriver and os.path.exists(local_headless_iedriver):\n            try:\n                make_driver_executable_if_not(local_headless_iedriver)\n            except Exception as e:\n                logging.debug(\n                    \"\\nWarning: Could not make HeadlessIEDriver executable: %s\"\n                    % e\n                )\n        elif not headless_iedriver_on_path():\n            from seleniumbase.console_scripts import sb_install\n            args = \" \".join(sys.argv)\n            if not (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n                # (Not multithreaded)\n                sys_args = sys.argv  # Save a copy of current sys args\n                log_d(\"\\nWarning: HeadlessIEDriver not found. Getting it now:\")\n                sb_install.main(override=\"iedriver\")\n                sys.argv = sys_args  # Put back the original sys args\n        d_b_c = \"--disable-build-check\"\n        logger = logging.getLogger(\"selenium\")\n        logger.setLevel(\"INFO\")\n        if not headless:\n            warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n            service = Service(service_args=[d_b_c], log_output=os.devnull)\n            driver = webdriver.Ie(service=service, options=ie_options)\n            return extend_driver(driver)\n        else:\n            warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n            service = Service(\n                executable_path=local_iedriver,\n                service_args=[d_b_c],\n                log_output=os.devnull,\n            )\n            driver = webdriver.Ie(service=service, options=ie_options)\n            return extend_driver(driver)\n    elif browser_name == constants.Browser.EDGE:\n        prefs = {\n            \"download.default_directory\": downloads_path,\n            \"download.directory_upgrade\": True,\n            \"download.prompt_for_download\": False,\n            \"credentials_enable_service\": False,\n            \"autofill.credit_card_enabled\": False,\n            \"local_discovery.notifications_enabled\": False,\n            \"safebrowsing.disable_download_protection\": True,\n            \"safebrowsing.enabled\": False,  # Prevent PW \"data breach\" pop-ups\n            \"omnibox-max-zero-suggest-matches\": 0,\n            \"omnibox-use-existing-autocomplete-client\": 0,\n            \"omnibox-trending-zero-prefix-suggestions-on-ntp\": 0,\n            \"omnibox-local-history-zero-suggest-beyond-ntp\": 0,\n            \"omnibox-on-focus-suggestions-contextual-web\": 0,\n            \"omnibox-on-focus-suggestions-srp\": 0,\n            \"omnibox-zero-suggest-prefetching\": 0,\n            \"omnibox-zero-suggest-prefetching-on-srp\": 0,\n            \"omnibox-zero-suggest-prefetching-on-web\": 0,\n            \"omnibox-zero-suggest-in-memory-caching\": 0,\n            \"content_settings.exceptions.automatic_downloads.*.setting\": 1,\n            \"default_content_setting_values.notifications\": 0,\n            \"default_content_settings.popups\": 0,\n            \"managed_default_content_settings.popups\": 0,\n            \"profile.password_manager_enabled\": False,\n            \"profile.password_manager_leak_detection\": False,\n            \"profile.default_content_setting_values.notifications\": 2,\n            \"profile.default_content_settings.popups\": 0,\n            \"profile.managed_default_content_settings.popups\": 0,\n            \"profile.default_content_setting_values.automatic_downloads\": 1,\n        }\n        use_version = \"latest\"\n        major_edge_version = None\n        saved_mev = None\n        use_br_version_for_edge = False\n        use_exact_version_for_edge = False\n        try:\n            if binary_location:\n                try:\n                    major_edge_version = (\n                        detect_b_ver.get_browser_version_from_binary(\n                            binary_location\n                        )\n                    )\n                    saved_mev = major_edge_version\n                    major_edge_version = saved_mev.split(\".\")[0]\n                    if len(major_edge_version) < 2:\n                        major_edge_version = None\n                except Exception:\n                    major_edge_version = None\n            if not major_edge_version:\n                br_app = \"edge\"\n                major_edge_version = (\n                    detect_b_ver.get_browser_version_from_os(br_app)\n                )\n                saved_mev = major_edge_version\n                major_edge_version = major_edge_version.split(\".\")[0]\n            if int(major_edge_version) < 80:\n                major_edge_version = None\n            elif int(major_edge_version) >= 115:\n                if (\n                    driver_version == \"browser\"\n                    and saved_mev\n                    and len(saved_mev.split(\".\")) == 4\n                ):\n                    driver_version = saved_mev\n                    use_br_version_for_edge = True\n        except Exception:\n            major_edge_version = None\n        if driver_version and \".\" in driver_version:\n            use_exact_version_for_edge = True\n        if use_br_version_for_edge:\n            major_edge_version = saved_mev\n        if major_edge_version:\n            use_version = major_edge_version\n        edge_driver_version = None\n        edgedriver_upgrade_needed = False\n        if os.path.exists(local_edgedriver):\n            with suppress(Exception):\n                output = subprocess.check_output(\n                    '\"%s\" --version' % local_edgedriver, shell=True\n                )\n                if IS_WINDOWS:\n                    output = output.decode(\"latin1\")\n                else:\n                    output = output.decode(\"utf-8\")\n                if output.split(\" \")[0] == \"MSEdgeDriver\":\n                    # MSEdgeDriver VERSION\n                    output = output.split(\" \")[1]\n                    if use_exact_version_for_edge:\n                        edge_driver_version = output.split(\" \")[0]\n                    output = output.split(\".\")[0]\n                elif output.split(\" \")[0] == \"Microsoft\":\n                    output = output.split(\" \")[3]\n                    if use_exact_version_for_edge:\n                        edge_driver_version = output.split(\" \")[0]\n                    output = output.split(\".\")[0]\n                else:\n                    output = 0\n                if int(output) >= 2:\n                    if not use_exact_version_for_edge:\n                        edge_driver_version = output\n                    if driver_version == \"keep\":\n                        driver_version = edge_driver_version\n        use_version = find_edgedriver_version_to_use(\n            use_version, driver_version\n        )\n        local_edgedriver_exists = False\n        if local_edgedriver and os.path.exists(local_edgedriver):\n            local_edgedriver_exists = True\n            if (\n                use_version != \"latest\"\n                and edge_driver_version\n                and use_version != edge_driver_version\n            ):\n                edgedriver_upgrade_needed = True\n            else:\n                try:\n                    make_driver_executable_if_not(local_edgedriver)\n                except Exception as e:\n                    logging.debug(\n                        \"\\nWarning: Could not make edgedriver\"\n                        \" executable: %s\" % e\n                    )\n        if not local_edgedriver_exists or edgedriver_upgrade_needed:\n            from seleniumbase.console_scripts import sb_install\n            args = \" \".join(sys.argv)\n            if not (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n                # (Not multithreaded)\n                msg = \"Microsoft Edge Driver not found.\"\n                if edgedriver_upgrade_needed:\n                    msg = \"Microsoft Edge Driver update needed.\"\n                sys_args = sys.argv  # Save a copy of current sys args\n                log_d(\"\\n%s Getting it now:\" % msg)\n                sb_install.main(override=\"edgedriver %s\" % use_version)\n                sys.argv = sys_args  # Put back the original sys args\n            else:\n                edgedriver_fixing_lock = fasteners.InterProcessLock(\n                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                )\n                with edgedriver_fixing_lock:\n                    with suppress(Exception):\n                        shared_utils.make_writable(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                    msg = \"Microsoft Edge Driver not found.\"\n                    if edgedriver_upgrade_needed:\n                        msg = \"Microsoft Edge Driver update needed.\"\n                    sys_args = sys.argv  # Save a copy of current sys args\n                    log_d(\"\\n%s Getting it now:\" % msg)\n                    sb_install.main(override=\"edgedriver %s\" % use_version)\n                    sys.argv = sys_args  # Put back the original sys args\n\n        # For Microsoft Edge (Chromium) version 80 or higher\n        Edge = webdriver.Edge\n        EdgeOptions = webdriver.EdgeOptions\n        if local_edgedriver and os.path.exists(local_edgedriver):\n            try:\n                make_driver_executable_if_not(local_edgedriver)\n            except Exception as e:\n                logging.debug(\n                    \"\\nWarning: Could not make edgedriver\"\n                    \" executable: %s\" % e\n                )\n        edge_options = EdgeOptions()\n        edge_options.use_chromium = True\n        if locale_code:\n            prefs[\"intl.accept_languages\"] = locale_code\n        if block_images:\n            prefs[\"profile.managed_default_content_settings.images\"] = 2\n        if disable_cookies:\n            prefs[\"profile.default_content_setting_values.cookies\"] = 2\n        if disable_js:\n            prefs[\"profile.managed_default_content_settings.javascript\"] = 2\n        if do_not_track:\n            prefs[\"enable_do_not_track\"] = True\n        if external_pdf:\n            prefs[\"plugins.always_open_pdf_externally\"] = True\n        pdce = \"user_experience_metrics.personalization_data_consent_enabled\"\n        prefs[pdce] = True  # Remove \"Personalize your web experience\" prompt\n        edge_options.add_experimental_option(\"prefs\", prefs)\n        edge_options.add_argument(\n            \"--disable-blink-features=AutomationControlled\"\n        )\n        edge_options.add_experimental_option(\n            \"excludeSwitches\", [\"enable-automation\", \"enable-logging\"]\n        )\n        if log_cdp_events:\n            edge_options.set_capability(\n                \"ms:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"}\n            )\n        if host_resolver_rules:\n            edge_options.add_argument(\n                \"--host-resolver-rules=%s\" % host_resolver_rules\n            )\n        if not enable_sync:\n            edge_options.add_argument(\"--disable-sync\")\n        if (\n            not recorder_ext and not disable_csp and not proxy_auth\n        ):\n            edge_options.add_argument(\"--guest\")\n        if dark_mode:\n            edge_options.add_argument(\"--enable-features=WebContentsForceDark\")\n        if headless1:\n            # developer.chrome.com/blog/removing-headless-old-from-chrome\n            with suppress(Exception):\n                if int(str(use_version).split(\".\")[0]) >= 132:\n                    headless1 = False\n                    headless2 = True\n        if headless2:\n            try:\n                if use_version == \"latest\" or int(use_version) >= 109:\n                    edge_options.add_argument(\"--headless=new\")\n                else:\n                    edge_options.add_argument(\"--headless=chrome\")\n            except Exception:\n                edge_options.add_argument(\"--headless=new\")\n        elif headless and undetectable:\n            # (For later: UC Mode doesn't support Edge now)\n            with suppress(Exception):\n                if int(use_version) >= 109:\n                    edge_options.add_argument(\"--headless=new\")\n                elif (\n                    int(use_version) >= 96\n                    and int(use_version) <= 108\n                ):\n                    edge_options.add_argument(\"--headless=chrome\")\n                else:\n                    pass  # Will need Xvfb on Linux\n        elif headless:\n            if (\n                \"--headless\" not in edge_options.arguments\n                and \"--headless=old\" not in edge_options.arguments\n            ):\n                if headless1:\n                    edge_options.add_argument(\"--headless=old\")\n                else:\n                    edge_options.add_argument(\"--headless\")\n        if mobile_emulator and not use_uc:\n            emulator_settings = {}\n            device_metrics = {}\n            if (\n                isinstance(device_width, int)\n                and isinstance(device_height, int)\n                and isinstance(device_pixel_ratio, (int, float))\n            ):\n                device_metrics[\"width\"] = device_width\n                device_metrics[\"height\"] = device_height\n                device_metrics[\"pixelRatio\"] = device_pixel_ratio\n            else:\n                device_metrics[\"width\"] = constants.Mobile.WIDTH\n                device_metrics[\"height\"] = constants.Mobile.HEIGHT\n                device_metrics[\"pixelRatio\"] = constants.Mobile.RATIO\n            emulator_settings[\"deviceMetrics\"] = device_metrics\n            if user_agent:\n                emulator_settings[\"userAgent\"] = user_agent\n            edge_options.add_experimental_option(\n                \"mobileEmulation\", emulator_settings\n            )\n        # Handle Window Position\n        if (headless or headless2) and IS_WINDOWS:\n            # https://stackoverflow.com/a/78999088/7058266\n            edge_options.add_argument(\"--window-position=-2400,-2400\")\n        else:\n            if (\n                hasattr(settings, \"WINDOW_START_X\")\n                and isinstance(settings.WINDOW_START_X, int)\n                and hasattr(settings, \"WINDOW_START_Y\")\n                and isinstance(settings.WINDOW_START_Y, int)\n            ):\n                edge_options.add_argument(\n                    \"--window-position=%s,%s\" % (\n                        settings.WINDOW_START_X, settings.WINDOW_START_Y\n                    )\n                )\n        # Handle Window Size\n        if headless or headless2:\n            if (\n                hasattr(settings, \"HEADLESS_START_WIDTH\")\n                and isinstance(settings.HEADLESS_START_WIDTH, int)\n                and hasattr(settings, \"HEADLESS_START_HEIGHT\")\n                and isinstance(settings.HEADLESS_START_HEIGHT, int)\n            ):\n                edge_options.add_argument(\n                    \"--window-size=%s,%s\" % (\n                        settings.HEADLESS_START_WIDTH,\n                        settings.HEADLESS_START_HEIGHT,\n                    )\n                )\n        else:\n            if (\n                hasattr(settings, \"CHROME_START_WIDTH\")\n                and isinstance(settings.CHROME_START_WIDTH, int)\n                and hasattr(settings, \"CHROME_START_HEIGHT\")\n                and isinstance(settings.CHROME_START_HEIGHT, int)\n            ):\n                edge_options.add_argument(\n                    \"--window-size=%s,%s\" % (\n                        settings.CHROME_START_WIDTH,\n                        settings.CHROME_START_HEIGHT,\n                    )\n                )\n        if user_data_dir and not use_uc:\n            abs_path = os.path.abspath(user_data_dir)\n            edge_options.add_argument(\"--user-data-dir=%s\" % abs_path)\n        if extension_zip:\n            # Can be a comma-separated list of .ZIP or .CRX files\n            extension_zip_list = extension_zip.split(\",\")\n            for extension_zip_item in extension_zip_list:\n                abs_path = os.path.realpath(extension_zip_item)\n                edge_options.add_extension(abs_path)\n        if extension_dir:\n            # load-extension input can be a comma-separated list\n            abs_path = (\n                \",\".join(os.path.realpath(p) for p in extension_dir.split(\",\"))\n            )\n            edge_options = add_chrome_ext_dir(edge_options, abs_path)\n        edge_options.add_argument(\"--disable-infobars\")\n        edge_options.add_argument(\"--disable-notifications\")\n        edge_options.add_argument(\"--disable-save-password-bubble\")\n        edge_options.add_argument(\"--disable-single-click-autofill\")\n        edge_options.add_argument(\n            \"--disable-autofill-keyboard-accessory-view[8]\"\n        )\n        edge_options.add_argument(\"--safebrowsing-disable-download-protection\")\n        edge_options.add_argument(\"--disable-search-engine-choice-screen\")\n        edge_options.add_argument(\"--disable-browser-side-navigation\")\n        edge_options.add_argument(\"--disable-translate\")\n        if not enable_ws:\n            edge_options.add_argument(\"--disable-web-security\")\n        edge_options.add_argument(\"--homepage=about:blank\")\n        edge_options.add_argument(\"--dns-prefetch-disable\")\n        edge_options.add_argument(\"--dom-automation\")\n        edge_options.add_argument(\"--disable-hang-monitor\")\n        edge_options.add_argument(\"--disable-prompt-on-repost\")\n        if not enable_3d_apis:\n            edge_options.add_argument(\"--disable-3d-apis\")\n        if headless or headless2 or use_uc:\n            edge_options.add_argument(\"--disable-renderer-backgrounding\")\n        edge_options.add_argument(\"--disable-backgrounding-occluded-windows\")\n        edge_options.add_argument(\"--disable-client-side-phishing-detection\")\n        edge_options.add_argument(\"--disable-oopr-debug-crash-dump\")\n        edge_options.add_argument(\"--disable-top-sites\")\n        edge_options.add_argument(\"--ash-no-nudges\")\n        edge_options.add_argument(\"--no-crash-upload\")\n        edge_options.add_argument(\"--deny-permission-prompts\")\n        if (\n            page_load_strategy\n            and page_load_strategy.lower() in [\"eager\", \"none\"]\n        ):\n            # Only change it if not \"normal\", which is the default.\n            edge_options.page_load_strategy = page_load_strategy.lower()\n        elif (\n            not page_load_strategy\n            and getattr(settings, \"PAGE_LOAD_STRATEGY\", None)\n            and settings.PAGE_LOAD_STRATEGY.lower() in [\"eager\", \"none\"]\n        ):\n            # Only change it if not \"normal\", which is the default.\n            edge_options.page_load_strategy = (\n                settings.PAGE_LOAD_STRATEGY.lower()\n            )\n        if (settings.DISABLE_CSP_ON_CHROME or disable_csp) and not headless:\n            # Headless Edge doesn't support extensions, which are required\n            # for disabling the Content Security Policy on Edge\n            edge_options = _add_chrome_disable_csp_extension(edge_options)\n        if ad_block_on and not headless:\n            edge_options = _add_chrome_ad_block_extension(edge_options)\n        if recorder_ext and not headless:\n            edge_options = _add_chrome_recorder_extension(edge_options)\n        if proxy_string:\n            if proxy_auth:\n                edge_options = _add_chrome_proxy_extension(\n                    edge_options,\n                    proxy_string,\n                    proxy_user,\n                    proxy_pass,\n                    proxy_scheme,\n                    proxy_bypass_list,\n                    zip_it=True,\n                    multi_proxy=multi_proxy,\n                )\n            edge_options.add_argument(\"--proxy-server=%s\" % proxy_string)\n            if proxy_bypass_list:\n                edge_options.add_argument(\n                    \"--proxy-bypass-list=%s\" % proxy_bypass_list\n                )\n        elif proxy_pac_url:\n            if proxy_auth:\n                edge_options = _add_chrome_proxy_extension(\n                    edge_options,\n                    None,\n                    proxy_user,\n                    proxy_pass,\n                    proxy_scheme,\n                    proxy_bypass_list,\n                    zip_it=True,\n                    multi_proxy=multi_proxy,\n                )\n            edge_options.add_argument(\"--proxy-pac-url=%s\" % proxy_pac_url)\n        edge_options.add_argument(\"--test-type\")\n        edge_options.add_argument(\"--log-level=3\")\n        edge_options.add_argument(\"--no-first-run\")\n        edge_options.add_argument(\"--ignore-certificate-errors\")\n        edge_options.add_argument(\"--ignore-ssl-errors=yes\")\n        if devtools and not headless:\n            edge_options.add_argument(\"--auto-open-devtools-for-tabs\")\n        edge_options.add_argument(\"--allow-file-access-from-files\")\n        edge_options.add_argument(\"--allow-insecure-localhost\")\n        edge_options.add_argument(\"--allow-running-insecure-content\")\n        if user_agent:\n            edge_options.add_argument(\"--user-agent=%s\" % user_agent)\n        if IS_LINUX or (IS_MAC and not use_uc):\n            edge_options.add_argument(\"--no-sandbox\")\n        if remote_debug:\n            # To access the Debugger, go to: edge://inspect/#devices\n            # while a Chromium driver is running.\n            # Info: https://chromedevtools.github.io/devtools-protocol/\n            args = \" \".join(sys.argv)\n            free_port = 9222\n            if (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n                free_port = service_utils.free_port()\n            edge_options.add_argument(\"--remote-debugging-port=%s\" % free_port)\n        if swiftshader:\n            edge_options.add_argument(\"--use-gl=angle\")\n            edge_options.add_argument(\"--use-angle=swiftshader-webgl\")\n        elif not use_uc and not enable_3d_apis:\n            edge_options.add_argument(\"--disable-gpu\")\n        if IS_LINUX:\n            edge_options.add_argument(\"--disable-dev-shm-usage\")\n        extra_disabled_features = []\n        set_binary = False\n        if chromium_arg:\n            # Can be a comma-separated list of Chromium args\n            chromium_arg_list = None\n            if isinstance(chromium_arg, (list, tuple)):\n                chromium_arg_list = chromium_arg\n            else:\n                chromium_arg_list = chromium_arg.split(\",\")\n            for chromium_arg_item in chromium_arg_list:\n                chromium_arg_item = chromium_arg_item.strip()\n                if not chromium_arg_item.startswith(\"--\"):\n                    if chromium_arg_item.startswith(\"-\"):\n                        chromium_arg_item = \"-\" + chromium_arg_item\n                    else:\n                        chromium_arg_item = \"--\" + chromium_arg_item\n                if \"set-binary\" in chromium_arg_item:\n                    set_binary = True\n                elif \"disable-features=\" in chromium_arg_item:\n                    d_f = chromium_arg_item.split(\"disable-features=\")[-1]\n                    extra_disabled_features.append(d_f)\n                elif len(chromium_arg_item) >= 3:\n                    edge_options.add_argument(chromium_arg_item)\n        if disable_features:\n            extra_disabled_features.extend(disable_features.split(\",\"))\n        edge_options.add_argument(\n            '--simulate-outdated-no-au=\"Tue, 31 Dec 2099 23:59:59 GMT\"'\n        )\n        edge_options.add_argument(\"--disable-ipc-flooding-protection\")\n        edge_options.add_argument(\"--disable-password-generation\")\n        edge_options.add_argument(\"--disable-domain-reliability\")\n        edge_options.add_argument(\"--disable-breakpad\")\n        included_disabled_features = []\n        included_disabled_features.append(\"OptimizationHints\")\n        included_disabled_features.append(\"OptimizationHintsFetching\")\n        included_disabled_features.append(\"Translate\")\n        included_disabled_features.append(\"ComponentUpdater\")\n        included_disabled_features.append(\"OptimizationTargetPrediction\")\n        included_disabled_features.append(\"OptimizationGuideModelDownloading\")\n        included_disabled_features.append(\"InsecureDownloadWarnings\")\n        included_disabled_features.append(\"InterestFeedContentSuggestions\")\n        included_disabled_features.append(\"PrivacySandboxSettings4\")\n        included_disabled_features.append(\"SidePanelPinning\")\n        included_disabled_features.append(\"UserAgentClientHint\")\n        included_disabled_features.append(\n            \"DisableLoadExtensionCommandLineSwitch\"\n        )\n        included_disabled_features.append(\"Bluetooth\")\n        included_disabled_features.append(\"WebBluetooth\")\n        included_disabled_features.append(\"UnifiedWebBluetooth\")\n        included_disabled_features.append(\"WebAuthentication\")\n        included_disabled_features.append(\"PasskeyAuth\")\n        for item in extra_disabled_features:\n            if item not in included_disabled_features:\n                included_disabled_features.append(item)\n        d_f_string = \",\".join(included_disabled_features)\n        edge_options.add_argument(\"--disable-features=%s\" % d_f_string)\n        if (set_binary or IS_LINUX) and not binary_location:\n            br_app = \"edge\"\n            binary_loc = detect_b_ver.get_binary_location(br_app)\n            if os.path.exists(binary_loc):\n                binary_location = binary_loc\n        if binary_location:\n            edge_options.binary_location = binary_location\n        service = EdgeService(\n            executable_path=local_edgedriver,\n            log_output=os.devnull,\n            service_args=[\"--disable-build-check\"],\n        )\n        try:\n            driver = Edge(service=service, options=edge_options)\n        except Exception as e:\n            if not hasattr(e, \"msg\"):\n                raise\n            auto_upgrade_edgedriver = False\n            edge_version = None\n            if (\n                \"This version of MSEdgeDriver only supports\" in e.msg\n                or \"This version of Microsoft Edge WebDriver\" in e.msg\n            ):\n                if \"Current browser version is \" in e.msg:\n                    auto_upgrade_edgedriver = True\n                    edge_version = e.msg.split(\n                        \"Current browser version is \"\n                    )[1].split(\" \")[0]\n                elif \"only supports MSEdge version \" in e.msg:\n                    auto_upgrade_edgedriver = True\n                    edge_version = e.msg.split(\n                        \"only supports MSEdge version \"\n                    )[1].split(\" \")[0]\n            elif \"DevToolsActivePort file doesn't exist\" in e.msg:\n                # https://stackoverflow.com/a/56638103/7058266\n                args = \" \".join(sys.argv)\n                free_port = 9222\n                if (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n                    free_port = service_utils.free_port()\n                edge_options.add_argument(\n                    \"--remote-debugging-port=%s\" % free_port\n                )\n                driver = Edge(service=service, options=edge_options)\n                return extend_driver(driver)\n            if not auto_upgrade_edgedriver:\n                raise  # Not an obvious fix.\n            else:\n                pass  # Try upgrading EdgeDriver to match Edge.\n            args = \" \".join(sys.argv)\n            if \"-n\" in sys.argv or \" -n=\" in args or args == \"-c\":\n                edgedriver_fixing_lock = fasteners.InterProcessLock(\n                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                )\n                with edgedriver_fixing_lock:\n                    with suppress(Exception):\n                        shared_utils.make_writable(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                    with suppress(Exception):\n                        if not _was_driver_repaired():\n                            _repair_edgedriver(edge_version)\n                            _mark_driver_repaired()\n            else:\n                with suppress(Exception):\n                    if not _was_driver_repaired():\n                        _repair_edgedriver(edge_version)\n                    _mark_driver_repaired()\n            driver = Edge(service=service, options=edge_options)\n        return extend_driver(driver)\n    elif browser_name == constants.Browser.SAFARI:\n        args = \" \".join(sys.argv)\n        if (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n            # Skip if multithreaded\n            raise Exception(\"Can't run Safari tests in multithreaded mode!\")\n        warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n        from selenium.webdriver.safari.options import Options as SafariOptions\n        service = SafariService(quiet=False)\n        options = SafariOptions()\n        if (\n            page_load_strategy\n            and page_load_strategy.lower() in [\"eager\", \"none\"]\n        ):\n            # Only change it if not \"normal\", which is the default.\n            options.page_load_strategy = page_load_strategy.lower()\n        elif (\n            not page_load_strategy\n            and getattr(settings, \"PAGE_LOAD_STRATEGY\", None)\n            and settings.PAGE_LOAD_STRATEGY.lower() in [\"eager\", \"none\"]\n        ):\n            # Only change it if not \"normal\", which is the default.\n            options.page_load_strategy = settings.PAGE_LOAD_STRATEGY.lower()\n        driver = webdriver.safari.webdriver.WebDriver(\n            service=service, options=options\n        )\n        return extend_driver(driver)\n    elif browser_name == constants.Browser.GOOGLE_CHROME:\n        set_chromium = None\n        if _special_binary_exists(binary_location, \"opera\"):\n            set_chromium = \"opera\"\n            local_chromedriver = DRIVER_DIR_OPERA + \"/chromedriver\"\n            if IS_WINDOWS:\n                local_chromedriver = DRIVER_DIR_OPERA + \"/chromedriver.exe\"\n        if _special_binary_exists(binary_location, \"brave\"):\n            set_chromium = \"brave\"\n            local_chromedriver = DRIVER_DIR_BRAVE + \"/chromedriver\"\n            if IS_WINDOWS:\n                local_chromedriver = DRIVER_DIR_BRAVE + \"/chromedriver.exe\"\n        if _special_binary_exists(binary_location, \"comet\"):\n            set_chromium = \"comet\"\n            local_chromedriver = DRIVER_DIR_COMET + \"/chromedriver\"\n            if IS_WINDOWS:\n                local_chromedriver = DRIVER_DIR_COMET + \"/chromedriver.exe\"\n        if _special_binary_exists(binary_location, \"atlas\"):\n            set_chromium = \"atlas\"\n            local_chromedriver = DRIVER_DIR_ATLAS + \"/chromedriver\"\n            if IS_WINDOWS:\n                local_chromedriver = DRIVER_DIR_ATLAS + \"/chromedriver.exe\"\n        try:\n            chrome_options = _set_chrome_options(\n                browser_name,\n                downloads_path,\n                headless,\n                locale_code,\n                proxy_string,\n                proxy_auth,\n                proxy_user,\n                proxy_pass,\n                proxy_scheme,\n                proxy_bypass_list,\n                proxy_pac_url,\n                multi_proxy,\n                user_agent,\n                recorder_ext,\n                disable_cookies,\n                disable_js,\n                disable_csp,\n                enable_ws,\n                enable_sync,\n                use_auto_ext,\n                undetectable,\n                uc_cdp_events,\n                uc_subprocess,\n                log_cdp_events,\n                no_sandbox,\n                disable_gpu,\n                headless1,\n                headless2,\n                incognito,\n                guest_mode,\n                dark_mode,\n                devtools,\n                remote_debug,\n                enable_3d_apis,\n                swiftshader,\n                ad_block_on,\n                host_resolver_rules,\n                block_images,\n                do_not_track,\n                chromium_arg,\n                user_data_dir,\n                extension_zip,\n                extension_dir,\n                disable_features,\n                binary_location,\n                driver_version,\n                page_load_strategy,\n                use_wire,\n                external_pdf,\n                servername,\n                mobile_emulator,\n                device_width,\n                device_height,\n                device_pixel_ratio,\n            )\n            if binary_location and \"chromium_drivers\" in binary_location:\n                chrome_options.add_argument(\"--use-mock-keychain\")\n            use_version = \"latest\"\n            major_chrome_version = None\n            saved_mcv = None\n            full_ch_version = None\n            full_ch_driver_version = None\n            use_br_version_for_uc = False\n            try:\n                if chrome_options.binary_location:\n                    try:\n                        major_chrome_version = (\n                            detect_b_ver.get_browser_version_from_binary(\n                                chrome_options.binary_location,\n                            )\n                        )\n                        saved_mcv = major_chrome_version\n                        major_chrome_version = saved_mcv.split(\".\")[0]\n                        if len(major_chrome_version) < 2:\n                            major_chrome_version = None\n                    except Exception:\n                        major_chrome_version = None\n                if not major_chrome_version:\n                    br_app = \"google-chrome\"\n                    full_ch_version = (\n                        detect_b_ver.get_browser_version_from_os(br_app)\n                    )\n                    saved_mcv = full_ch_version\n                    major_chrome_version = full_ch_version.split(\".\")[0]\n                if int(major_chrome_version) < 67:\n                    major_chrome_version = None\n                elif (\n                    int(major_chrome_version) >= 67\n                    and int(major_chrome_version) <= 72\n                ):\n                    # chromedrivers 2.41 - 2.46 could be swapped with 72\n                    major_chrome_version = \"72\"\n                elif int(major_chrome_version) >= 115:\n                    if (\n                        driver_version == \"browser\"\n                        and saved_mcv\n                        and len(saved_mcv.split(\".\")) == 4\n                    ):\n                        driver_version = saved_mcv\n                        if use_uc:\n                            use_br_version_for_uc = True\n                    if (\n                        (headless or headless2)\n                        and IS_WINDOWS\n                        and major_chrome_version\n                        and int(major_chrome_version) >= 117\n                        and not use_uc\n                        and not (remote_debug or devtools or use_wire)\n                        and not (proxy_string or multi_proxy or proxy_pac_url)\n                        and (not chromium_arg or \"debug\" not in chromium_arg)\n                        and (not servername or servername == \"localhost\")\n                    ):\n                        # Hide the \"DevTools listening on ...\" message.\n                        # https://bugs.chromium.org\n                        # /p/chromedriver/issues/detail?id=4403#c35\n                        # (Only when the remote debugging port is not needed.)\n                        chrome_options.add_argument(\"--remote-debugging-pipe\")\n            except Exception:\n                major_chrome_version = None\n            if major_chrome_version:\n                use_version = major_chrome_version\n            if (\n                set_chromium == \"opera\"\n                and use_version.isnumeric()\n                and int(use_version) < 130\n            ):\n                use_version = \"130\"  # Special case\n            ch_driver_version = None\n            path_chromedriver = chromedriver_on_path()\n            if os.path.exists(local_chromedriver):\n                with suppress(Exception):\n                    output = subprocess.check_output(\n                        '\"%s\" --version' % local_chromedriver, shell=True\n                    )\n                    if IS_WINDOWS:\n                        output = output.decode(\"latin1\")\n                    else:\n                        output = output.decode(\"utf-8\")\n                    full_ch_driver_version = output.split(\" \")[1]\n                    output = full_ch_driver_version.split(\".\")[0]\n                    if int(output) >= 2:\n                        ch_driver_version = output\n                        if driver_version == \"keep\":\n                            driver_version = ch_driver_version\n            elif path_chromedriver and not set_chromium:\n                try:\n                    make_driver_executable_if_not(path_chromedriver)\n                except Exception as e:\n                    logging.debug(\n                        \"\\nWarning: Could not make chromedriver\"\n                        \" executable: %s\" % e\n                    )\n                with suppress(Exception):\n                    output = subprocess.check_output(\n                        '\"%s\" --version' % path_chromedriver, shell=True\n                    )\n                    if IS_WINDOWS:\n                        output = output.decode(\"latin1\")\n                    else:\n                        output = output.decode(\"utf-8\")\n                    full_ch_driver_version = output.split(\" \")[1]\n                    output = full_ch_driver_version.split(\".\")[0]\n                    if int(output) >= 2:\n                        ch_driver_version = output\n                        if driver_version == \"keep\":\n                            use_version = ch_driver_version\n            disable_build_check = True\n            uc_driver_version = None\n            if use_uc:\n                if use_br_version_for_uc or driver_version == \"mlatest\":\n                    uc_driver_version = get_uc_driver_version(\n                        full=True, local_uc_driver=local_uc_driver\n                    )\n                    full_ch_driver_version = uc_driver_version\n                else:\n                    uc_driver_version = get_uc_driver_version(\n                        local_uc_driver=local_uc_driver\n                    )\n                if multi_proxy:\n                    sb_config.multi_proxy = True\n                if uc_driver_version and driver_version == \"keep\":\n                    driver_version = uc_driver_version\n            use_version = find_chromedriver_version_to_use(\n                use_version, driver_version\n            )\n            if headless1:\n                # developer.chrome.com/blog/removing-headless-old-from-chrome\n                with suppress(Exception):\n                    if int(str(use_version).split(\".\")[0]) >= 132:\n                        headless1 = False\n                        headless2 = True\n            if headless2:\n                try:\n                    if (\n                        use_version == \"latest\"\n                        or int(str(use_version).split(\".\")[0]) >= 109\n                    ):\n                        chrome_options.add_argument(\"--headless=new\")\n                    else:\n                        chrome_options.add_argument(\"--headless=chrome\")\n                except Exception:\n                    chrome_options.add_argument(\"--headless=new\")\n            elif headless and undetectable:\n                try:\n                    int_use_version = int(str(use_version).split(\".\")[0])\n                    if int_use_version >= 109:\n                        chrome_options.add_argument(\"--headless=new\")\n                    elif (\n                        int_use_version >= 96\n                        and int_use_version <= 108\n                    ):\n                        chrome_options.add_argument(\"--headless=chrome\")\n                    else:\n                        pass  # Will need Xvfb on Linux\n                except Exception:\n                    pass  # Will need Xvfb on Linux\n            elif headless:\n                if (\n                    \"--headless\" not in chrome_options.arguments\n                    and \"--headless=old\" not in chrome_options.arguments\n                ):\n                    if headless1:\n                        chrome_options.add_argument(\"--headless=old\")\n                    else:\n                        chrome_options.add_argument(\"--headless\")\n            if local_chromedriver and os.path.exists(local_chromedriver):\n                try:\n                    make_driver_executable_if_not(local_chromedriver)\n                except Exception as e:\n                    logging.debug(\n                        \"\\nWarning: Could not make chromedriver\"\n                        \" executable: %s\" % e\n                    )\n            make_uc_driver_from_chromedriver = False\n            local_ch_exists = (\n                local_chromedriver and os.path.exists(local_chromedriver)\n            )\n            \"\"\"If no local_chromedriver, but path_chromedriver, and the\n            browser version nearly matches the driver version, then use\n            the path_chromedriver instead of downloading a new driver.\n            Eg. 116.0.* for both is close, but not 116.0.* and 116.1.*\"\"\"\n            browser_driver_close_match = False\n            if (\n                path_chromedriver\n                and full_ch_version\n                and full_ch_driver_version\n            ):\n                full_ch_v_p = full_ch_version.split(\".\")[0:2]\n                full_ch_driver_v_p = full_ch_driver_version.split(\".\")[0:2]\n                if (\n                    full_ch_v_p == full_ch_driver_v_p\n                    or driver_version == \"keep\"\n                ):\n                    browser_driver_close_match = True\n            one_off_chromium = False\n            if (\n                hasattr(sb_config, \"binary_location\")\n                and sb_config.binary_location == \"_chromium_\"\n            ):\n                with suppress(Exception):\n                    one_off_chromium_ver = int(use_version.split(\".\")[0]) - 1\n                    if one_off_chromium_ver == int(ch_driver_version):\n                        one_off_chromium = True\n            # If not ARM MAC and need to use uc_driver (and it's missing),\n            # and already have chromedriver with the correct version,\n            # then copy chromedriver to uc_driver (and it'll get patched).\n            if (\n                not IS_ARM_MAC\n                and use_uc\n                and (\n                    (\n                        (local_ch_exists or path_chromedriver)\n                        and use_version == ch_driver_version\n                        and (\n                            not os.path.exists(local_uc_driver)\n                            or uc_driver_version != use_version\n                        )\n                    )\n                    or (\n                        local_ch_exists\n                        and use_version == \"latest\"\n                        and not os.path.exists(local_uc_driver)\n                    )\n                )\n            ):\n                make_uc_driver_from_chromedriver = True\n            elif (\n                (use_uc and not os.path.exists(local_uc_driver))\n                or (not use_uc and not path_chromedriver)\n                or (\n                    not use_uc\n                    and use_version != \"latest\"  # Browser version detected\n                    and (ch_driver_version or not local_ch_exists)\n                    and (\n                        (\n                            use_version.split(\".\")[0] != ch_driver_version\n                            and not one_off_chromium\n                        )\n                        or (\n                            not local_ch_exists\n                            and use_version.isnumeric()\n                            and int(use_version) >= 115\n                            and not browser_driver_close_match\n                            and not one_off_chromium\n                        )\n                    )\n                )\n                or (\n                    use_uc\n                    and use_version != \"latest\"  # Browser version detected\n                    and uc_driver_version != use_version\n                    and not one_off_chromium\n                )\n                or (\n                    full_ch_driver_version  # Also used for the uc_driver\n                    and driver_version\n                    and len(str(driver_version).split(\".\")) == 4\n                    and full_ch_driver_version != driver_version\n                    and not one_off_chromium\n                )\n            ):\n                # chromedriver download needed in the seleniumbase/drivers dir\n                from seleniumbase.console_scripts import sb_install\n                args = \" \".join(sys.argv)\n                if not (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\"):\n                    # (Not multithreaded)\n                    sys_args = sys.argv  # Save a copy of current sys args\n                    msg = \"chromedriver update needed. Getting it now:\"\n                    if not path_chromedriver:\n                        msg = \"chromedriver not found. Getting it now:\"\n                    if use_uc and not os.path.exists(local_uc_driver):\n                        msg = \"uc_driver not found. Getting it now:\"\n                    if use_uc and os.path.exists(local_uc_driver):\n                        msg = \"uc_driver update needed. Getting it now:\"\n                    log_d(\"\\nWarning: %s\" % msg)\n                    force_uc = False\n                    intel_for_uc = False\n                    if use_uc:\n                        force_uc = True\n                    if IS_ARM_MAC and use_uc:\n                        intel_for_uc = True  # Use Intel's driver for UC Mode\n                    try:\n                        sb_install.main(\n                            override=\"chromedriver %s\" % use_version,\n                            intel_for_uc=intel_for_uc,\n                            force_uc=force_uc,\n                        )\n                    except Exception:\n                        d_latest = get_latest_chromedriver_version()\n                        if (\n                            d_latest\n                            and use_version != \"latest\"\n                            and int(use_version) > int(d_latest.split(\".\")[0])\n                        ):\n                            disable_build_check = True\n                            d_latest_major = d_latest.split(\".\")[0]\n                            if (\n                                not path_chromedriver\n                                or (\n                                    ch_driver_version\n                                    and (\n                                        int(ch_driver_version)\n                                        < int(d_latest_major)\n                                    )\n                                )\n                            ):\n                                sb_install.main(override=\"chromedriver latest\")\n                    sys.argv = sys_args  # Put back the original sys args\n                else:\n                    # (Multithreaded)\n                    chromedriver_fixing_lock = fasteners.InterProcessLock(\n                        constants.MultiBrowser.DRIVER_FIXING_LOCK\n                    )\n                    with chromedriver_fixing_lock:\n                        with suppress(Exception):\n                            shared_utils.make_writable(\n                                constants.MultiBrowser.DRIVER_FIXING_LOCK\n                            )\n                        msg = \"chromedriver update needed. Getting it now:\"\n                        if not path_chromedriver:\n                            msg = \"chromedriver not found. Getting it now:\"\n                        if use_uc and not os.path.exists(local_uc_driver):\n                            msg = \"uc_driver not found. Getting it now:\"\n                        if use_uc and os.path.exists(local_uc_driver):\n                            msg = \"uc_driver update needed. Getting it now:\"\n                        force_uc = False\n                        intel_for_uc = False\n                        if use_uc:\n                            force_uc = True\n                        if IS_ARM_MAC and use_uc:\n                            intel_for_uc = True  # Use Intel driver for UC Mode\n                        if os.path.exists(local_chromedriver):\n                            with suppress(Exception):\n                                output = subprocess.check_output(\n                                    '\"%s\" --version' % local_chromedriver,\n                                    shell=True,\n                                )\n                                if IS_WINDOWS:\n                                    output = output.decode(\"latin1\")\n                                else:\n                                    output = output.decode(\"utf-8\")\n                                full_ch_driver_version = output.split(\" \")[1]\n                                output = full_ch_driver_version.split(\".\")[0]\n                                if int(output) >= 2:\n                                    ch_driver_version = output\n                        if (\n                            (\n                                not use_uc\n                                and not os.path.exists(local_chromedriver)\n                            )\n                            or (use_uc and not os.path.exists(local_uc_driver))\n                            or (\n                                not use_uc\n                                and (\n                                    use_version.split(\".\")[0]\n                                    != ch_driver_version\n                                )\n                            )\n                            or (\n                                use_uc\n                                and (\n                                    use_version.split(\".\")[0]\n                                    != get_uc_driver_version(\n                                        local_uc_driver=local_uc_driver\n                                    )\n                                )\n                            )\n                        ):\n                            log_d(\"\\nWarning: %s\" % msg)\n                            sys_args = sys.argv  # Save a copy of sys args\n                            try:\n                                sb_install.main(\n                                    override=\"chromedriver %s\" % use_version,\n                                    intel_for_uc=intel_for_uc,\n                                    force_uc=force_uc,\n                                )\n                            except Exception:\n                                d_latest = get_latest_chromedriver_version()\n                                if (\n                                    d_latest\n                                    and use_version != \"latest\"\n                                    and (\n                                        int(use_version)\n                                        > int(d_latest.split(\".\")[0])\n                                    )\n                                ):\n                                    disable_build_check = True\n                                    d_latest_major = d_latest.split(\".\")[0]\n                                    if (\n                                        not path_chromedriver\n                                        or (\n                                            ch_driver_version\n                                            and (\n                                                int(ch_driver_version)\n                                                < int(d_latest_major)\n                                            )\n                                        )\n                                    ):\n                                        sb_install.main(\n                                            override=\"chromedriver latest\"\n                                        )\n                            finally:\n                                sys.argv = sys_args  # Put back original args\n            service_args = []\n            if disable_build_check:\n                service_args = [\"--disable-build-check\"]\n            if use_uc:\n                uc_lock = fasteners.InterProcessLock(\n                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                )\n                with uc_lock:  # Avoid multithreaded issues\n                    with suppress(Exception):\n                        shared_utils.make_writable(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                    if make_uc_driver_from_chromedriver:\n                        if os.path.exists(local_chromedriver):\n                            with suppress(Exception):\n                                make_driver_executable_if_not(\n                                    local_chromedriver\n                                )\n                            shutil.copy2(local_chromedriver, local_uc_driver)\n                        elif os.path.exists(path_chromedriver):\n                            with suppress(Exception):\n                                make_driver_executable_if_not(\n                                    path_chromedriver\n                                )\n                            shutil.copy2(path_chromedriver, local_uc_driver)\n                        try:\n                            make_driver_executable_if_not(local_uc_driver)\n                        except Exception as e:\n                            logging.debug(\n                                \"\\nWarning: Could not make uc_driver\"\n                                \" executable: %s\" % e\n                            )\n            if not headless or not IS_LINUX or use_uc:\n                uc_activated = False\n                try:\n                    if os.path.exists(local_chromedriver) or use_uc:\n                        if headless and not IS_LINUX:\n                            undetectable = False  # No support for headless\n                            use_uc = is_using_uc(undetectable, browser_name)\n                        if use_uc:\n                            from seleniumbase import undetected\n                            from urllib.error import URLError\n                            if IS_LINUX:\n                                if \"--headless\" in (\n                                    chrome_options.arguments\n                                ):\n                                    chrome_options.arguments.remove(\n                                        \"--headless\"\n                                    )\n                                if \"--headless=old\" in (\n                                    chrome_options.arguments\n                                ):\n                                    chrome_options.arguments.remove(\n                                        \"--headless=old\"\n                                    )\n                            uc_chrome_version = None\n                            if (\n                                use_version.isnumeric()\n                                and int(use_version) >= 72\n                            ):\n                                uc_chrome_version = int(use_version)\n                            elif (\n                                str(use_version).split(\".\")[0].isnumeric()\n                                and int(str(use_version).split(\".\")[0]) >= 72\n                            ):\n                                uc_chrome_version = (\n                                    int(str(use_version).split(\".\")[0])\n                                )\n                            cdp_events = uc_cdp_events\n                            cert = \"unable to get local issuer certificate\"\n                            mac_certificate_error = False\n                            if (\n                                use_version.isnumeric()\n                                and int(use_version) <= 74\n                            ):\n                                chrome_options.add_experimental_option(\n                                    \"w3c\", True\n                                )\n                            if (\n                                (not user_agent or \"Headless\" in user_agent)\n                                and uc_chrome_version\n                                and uc_chrome_version >= 117\n                                and (headless or headless2)\n                                and hasattr(sb_config, \"uc_agent_cache\")\n                            ):\n                                user_agent = sb_config.uc_agent_cache\n                                chrome_options.add_argument(\n                                    \"--user-agent=%s\" % user_agent\n                                )\n                            with suppress(Exception):\n                                if (\n                                    (\n                                        not user_agent\n                                        or \"Headless\" in user_agent\n                                    )\n                                    and uc_chrome_version\n                                    and uc_chrome_version >= 117\n                                    and (headless or headless2)\n                                    and chromium_arg != \"decoy\"\n                                ):\n                                    from seleniumbase.console_scripts import (\n                                        sb_install\n                                    )\n                                    sb_config.uc_user_agent_cache = True\n                                    headless_options = _set_chrome_options(\n                                        browser_name,\n                                        downloads_path,\n                                        True,  # headless\n                                        locale_code,\n                                        None,  # proxy_string\n                                        None,  # proxy_auth\n                                        None,  # proxy_user\n                                        None,  # proxy_pass\n                                        None,  # proxy_scheme\n                                        None,  # proxy_bypass_list\n                                        None,  # proxy_pac_url\n                                        None,  # multi_proxy\n                                        None,  # user_agent\n                                        None,  # recorder_ext\n                                        disable_cookies,\n                                        disable_js,\n                                        disable_csp,\n                                        enable_ws,\n                                        enable_sync,\n                                        use_auto_ext,\n                                        False,  # undetectable\n                                        False,  # uc_cdp_events\n                                        False,  # uc_subprocess\n                                        False,  # log_cdp_events\n                                        no_sandbox,\n                                        disable_gpu,\n                                        False,  # headless1\n                                        False,  # headless2\n                                        incognito,\n                                        guest_mode,\n                                        dark_mode,\n                                        None,  # devtools\n                                        remote_debug,\n                                        enable_3d_apis,\n                                        swiftshader,\n                                        None,  # ad_block_on\n                                        None,  # host_resolver_rules\n                                        block_images,\n                                        do_not_track,\n                                        None,  # chromium_arg\n                                        None,  # user_data_dir\n                                        None,  # extension_zip\n                                        None,  # extension_dir\n                                        None,  # disable_features\n                                        binary_location,\n                                        driver_version,\n                                        page_load_strategy,\n                                        use_wire,\n                                        external_pdf,\n                                        servername,\n                                        mobile_emulator,\n                                        device_width,\n                                        device_height,\n                                        device_pixel_ratio,\n                                    )\n                                    if (\n                                        not path_chromedriver\n                                        or (\n                                            ch_driver_version\n                                            and use_version\n                                            and (\n                                                int(ch_driver_version)\n                                                < int(str(\n                                                    use_version).split(\".\")[0]\n                                                )\n                                            )\n                                        )\n                                    ):\n                                        sb_install.main(\n                                            override=\"chromedriver %s\"\n                                            % use_version,\n                                            intel_for_uc=False,\n                                            force_uc=False,\n                                        )\n                                    d_b_c = \"--disable-build-check\"\n                                    if os.path.exists(local_chromedriver):\n                                        service = ChromeService(\n                                            executable_path=local_chromedriver,\n                                            log_output=os.devnull,\n                                            service_args=[d_b_c],\n                                        )\n                                        driver = webdriver.Chrome(\n                                            service=service,\n                                            options=headless_options,\n                                        )\n                                    else:\n                                        service = ChromeService(\n                                            log_output=os.devnull,\n                                            service_args=[d_b_c],\n                                        )\n                                        driver = webdriver.Chrome(\n                                            service=service,\n                                            options=headless_options,\n                                        )\n                                    with suppress(Exception):\n                                        user_agent = driver.execute_script(\n                                            \"return navigator.userAgent;\"\n                                        )\n                                        if (\n                                            major_chrome_version\n                                            and full_ch_version\n                                            and full_ch_version.count(\".\") == 3\n                                            and full_ch_version in user_agent\n                                        ):\n                                            mcv = major_chrome_version\n                                            user_agent = user_agent.replace(\n                                                \"Chrome/%s\" % full_ch_version,\n                                                \"Chrome/%s.0.0.0\" % mcv\n                                            )\n                                        user_agent = user_agent.replace(\n                                            \"Headless\", \"\"\n                                        )\n                                        chrome_options.add_argument(\n                                            \"--user-agent=%s\" % user_agent\n                                        )\n                                        sb_config.uc_agent_cache = user_agent\n                                    driver.quit()\n                            uc_path = None\n                            if os.path.exists(local_uc_driver):\n                                uc_path = local_uc_driver\n                                uc_path = os.path.realpath(uc_path)\n                            try:\n                                driver = undetected.Chrome(\n                                    options=chrome_options,\n                                    user_data_dir=user_data_dir,\n                                    driver_executable_path=uc_path,\n                                    browser_executable_path=b_path,\n                                    enable_cdp_events=cdp_events,\n                                    headless=False,  # Xvfb needed!\n                                    version_main=uc_chrome_version,\n                                    use_subprocess=True,  # Always!\n                                )\n                                uc_activated = True\n                            except URLError as e:\n                                if (\n                                    IS_MAC\n                                    and hasattr(e, \"args\")\n                                    and isinstance(e.args, (list, tuple))\n                                    and len(e.args) > 0\n                                    and cert in e.args[0]\n                                ):\n                                    mac_certificate_error = True\n                                else:\n                                    raise\n                            except SessionNotCreatedException:\n                                time.sleep(0.2)\n                                driver = undetected.Chrome(\n                                    options=chrome_options,\n                                    user_data_dir=user_data_dir,\n                                    driver_executable_path=uc_path,\n                                    browser_executable_path=b_path,\n                                    enable_cdp_events=cdp_events,\n                                    headless=False,  # Xvfb needed!\n                                    version_main=uc_chrome_version,\n                                    use_subprocess=True,  # Always!\n                                )\n                                uc_activated = True\n                            if mac_certificate_error:\n                                cf_lock_path = (\n                                    constants.MultiBrowser.CERT_FIXING_LOCK\n                                )\n                                cf_lock = fasteners.InterProcessLock(\n                                    constants.MultiBrowser.CERT_FIXING_LOCK\n                                )\n                                if not os.path.exists(cf_lock_path):\n                                    # Avoid multithreaded issues\n                                    with cf_lock:\n                                        with suppress(Exception):\n                                            shared_utils.make_writable(\n                                                cf_lock_path\n                                            )\n                                        # Install Python Certificates (MAC)\n                                        os.system(\n                                            r\"bash /Applications/Python*/\"\n                                            r\"Install\\ \"\n                                            r\"Certificates.command\"\n                                        )\n                                driver = undetected.Chrome(\n                                    options=chrome_options,\n                                    user_data_dir=user_data_dir,\n                                    driver_executable_path=uc_path,\n                                    browser_executable_path=b_path,\n                                    enable_cdp_events=cdp_events,\n                                    headless=False,  # Xvfb needed!\n                                    version_main=uc_chrome_version,\n                                    use_subprocess=True,  # Always!\n                                )\n                                uc_activated = True\n                        else:\n                            if (\n                                use_version.isnumeric()\n                                and int(use_version) <= 74\n                            ):\n                                chrome_options.add_experimental_option(\n                                    \"w3c\", True\n                                )\n                            service = ChromeService(\n                                executable_path=local_chromedriver,\n                                log_output=os.devnull,\n                                service_args=service_args,\n                            )\n                            driver = webdriver.Chrome(\n                                service=service,\n                                options=chrome_options,\n                            )\n                    else:\n                        service = ChromeService(\n                            log_output=os.devnull,\n                            service_args=service_args,\n                        )\n                        driver = webdriver.Chrome(\n                            service=service,\n                            options=chrome_options,\n                        )\n                except Exception as e:\n                    if not hasattr(e, \"msg\"):\n                        raise\n                    auto_upgrade_chromedriver = False\n                    if \"This version of ChromeDriver only supports\" in e.msg:\n                        auto_upgrade_chromedriver = True\n                    elif \"Chrome version must be between\" in e.msg:\n                        auto_upgrade_chromedriver = True\n                    elif \"Missing or invalid capabilities\" in e.msg:\n                        chrome_options.add_experimental_option(\"w3c\", True)\n                        service = ChromeService(\n                            log_output=os.devnull,\n                            service_args=service_args,\n                        )\n                        with warnings.catch_warnings():\n                            warnings.simplefilter(\n                                \"ignore\", category=DeprecationWarning\n                            )\n                            driver = webdriver.Chrome(\n                                service=service, options=chrome_options\n                            )\n                            return extend_driver(\n                                driver, proxy_auth, use_uc, recorder_ext\n                            )\n                    if not auto_upgrade_chromedriver:\n                        raise  # Not an obvious fix.\n                    else:\n                        pass  # Try upgrading ChromeDriver to match Chrome.\n                    mcv = None  # Major Chrome Version\n                    if \"Current browser version is \" in e.msg:\n                        line = e.msg.split(\"Current browser version is \")[1]\n                        browser_version = line.split(\" \")[0]\n                        major_chrome_version = browser_version.split(\".\")[0]\n                        if (\n                            major_chrome_version.isnumeric()\n                            and int(major_chrome_version) >= 86\n                        ):\n                            mcv = major_chrome_version\n                            mcv = find_chromedriver_version_to_use(\n                                mcv, driver_version\n                            )\n                    headless_options = _set_chrome_options(\n                        browser_name,\n                        downloads_path,\n                        True,  # headless\n                        locale_code,\n                        None,  # proxy_string\n                        None,  # proxy_auth\n                        None,  # proxy_user\n                        None,  # proxy_pass\n                        None,  # proxy_scheme\n                        None,  # proxy_bypass_list\n                        None,  # proxy_pac_url\n                        None,  # multi_proxy\n                        None,  # user_agent\n                        None,  # recorder_ext\n                        disable_cookies,\n                        disable_js,\n                        disable_csp,\n                        enable_ws,\n                        enable_sync,\n                        use_auto_ext,\n                        False,  # undetectable\n                        False,  # uc_cdp_events\n                        False,  # uc_subprocess\n                        False,  # log_cdp_events\n                        no_sandbox,\n                        disable_gpu,\n                        False,  # headless1\n                        False,  # headless2\n                        incognito,\n                        guest_mode,\n                        dark_mode,\n                        None,  # devtools\n                        remote_debug,\n                        enable_3d_apis,\n                        swiftshader,\n                        None,  # ad_block_on\n                        None,  # host_resolver_rules\n                        block_images,\n                        do_not_track,\n                        None,  # chromium_arg\n                        None,  # user_data_dir\n                        None,  # extension_zip\n                        None,  # extension_dir\n                        None,  # disable_features\n                        binary_location,\n                        driver_version,\n                        page_load_strategy,\n                        use_wire,\n                        external_pdf,\n                        servername,\n                        mobile_emulator,\n                        device_width,\n                        device_height,\n                        device_pixel_ratio,\n                    )\n                    args = \" \".join(sys.argv)\n                    if \"-n\" in sys.argv or \" -n=\" in args or args == \"-c\":\n                        chromedriver_fixing_lock = fasteners.InterProcessLock(\n                            constants.MultiBrowser.DRIVER_FIXING_LOCK\n                        )\n                        with chromedriver_fixing_lock:\n                            with suppress(Exception):\n                                shared_utils.make_writable(\n                                    constants.MultiBrowser.DRIVER_FIXING_LOCK\n                                )\n                            if not _was_driver_repaired():\n                                _repair_chromedriver(\n                                    chrome_options, headless_options, mcv\n                                )\n                                _mark_driver_repaired()\n                    else:\n                        if not _was_driver_repaired():\n                            _repair_chromedriver(\n                                chrome_options, headless_options, mcv\n                            )\n                        _mark_driver_repaired()\n                    if os.path.exists(local_chromedriver):\n                        service = ChromeService(\n                            executable_path=local_chromedriver,\n                            log_output=os.devnull,\n                            service_args=[\"--disable-build-check\"],\n                        )\n                        driver = webdriver.Chrome(\n                            service=service,\n                            options=chrome_options,\n                        )\n                    else:\n                        service = ChromeService(\n                            log_output=os.devnull,\n                            service_args=[\"--disable-build-check\"],\n                        )\n                        driver = webdriver.Chrome(\n                            service=service,\n                            options=chrome_options,\n                        )\n                driver.default_get = driver.get  # Save copy of original\n                driver.cdp = None  # Set a placeholder\n                driver._is_using_uc = False\n                driver._is_using_cdp = False\n                driver._is_connected = True\n                if uc_activated:\n                    driver.get = lambda url: uc_special_open_if_cf(\n                        driver,\n                        url,\n                        proxy_string,\n                        mobile_emulator,\n                        device_width,\n                        device_height,\n                        device_pixel_ratio,\n                    )\n                    driver.uc_open = lambda url: uc_open(driver, url)\n                    driver.uc_open_with_tab = (\n                        lambda url: uc_open_with_tab(driver, url)\n                    )\n                    driver.uc_open_with_reconnect = (\n                        lambda *args, **kwargs: uc_open_with_reconnect(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_open_with_disconnect = (\n                        lambda *args, **kwargs: uc_open_with_disconnect(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_click = lambda *args, **kwargs: uc_click(\n                        driver, *args, **kwargs\n                    )\n                    driver.uc_activate_cdp_mode = (\n                        lambda *args, **kwargs: uc_activate_cdp_mode(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.activate_cdp_mode = driver.uc_activate_cdp_mode\n                    driver.uc_open_with_cdp_mode = (\n                        lambda *args, **kwargs: uc_open_with_cdp_mode(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_press_key = (\n                        lambda *args, **kwargs: uc_gui_press_key(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_press_keys = (\n                        lambda *args, **kwargs: uc_gui_press_keys(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_write = (\n                        lambda *args, **kwargs: uc_gui_write(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_click_x_y = (\n                        lambda *args, **kwargs: uc_gui_click_x_y(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_click_captcha = (\n                        lambda *args, **kwargs: uc_gui_click_captcha(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_click_cf = (\n                        lambda *args, **kwargs: uc_gui_click_cf(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_click_rc = (\n                        lambda *args, **kwargs: uc_gui_click_rc(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_handle_captcha = (\n                        lambda *args, **kwargs: uc_gui_handle_captcha(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_handle_cf = (\n                        lambda *args, **kwargs: uc_gui_handle_cf(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_gui_handle_rc = (\n                        lambda *args, **kwargs: uc_gui_handle_rc(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.uc_switch_to_frame = (\n                        lambda *args, **kwargs: uc_switch_to_frame(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver.default_execute_cdp_cmd = driver.execute_cdp_cmd\n                    driver.execute_cdp_cmd = (\n                        lambda *args, **kwargs: uc_execute_cdp_cmd(\n                            driver, *args, **kwargs\n                        )\n                    )\n                    driver._is_hidden = (headless or headless2)\n                    driver._is_using_uc = True\n                    with suppress(Exception):\n                        if int(uc_driver_version) >= 133:\n                            for window_handle in driver.window_handles:\n                                driver.switch_to.window(window_handle)\n                                if driver.current_url.startswith(\n                                    \"chrome-extension://\"\n                                ):\n                                    driver.close()\n                                    time.sleep(0.003)\n                            driver.switch_to.window(driver.window_handles[0])\n                            time.sleep(0.003)\n                            # seleniumbase/SeleniumBase/discussions/4190\n                            if getattr(sb_config, \"skip_133_patch\", None):\n                                # To skip the connect() patch for Chrome 133+:\n                                # from seleniumbase import config as sb_config\n                                # sb_config.skip_133_patch = True\n                                # (Do the above before launching the browser.)\n                                pass\n                            else:\n                                # This fixes an issue on Chrome 133+\n                                # (Some people might not need it though.)\n                                driver.connect()\n                                time.sleep(0.003)\n                    if mobile_emulator:\n                        uc_metrics = {}\n                        if (\n                            isinstance(device_width, int)\n                            and isinstance(device_height, int)\n                            and isinstance(device_pixel_ratio, (int, float))\n                        ):\n                            uc_metrics[\"width\"] = device_width\n                            uc_metrics[\"height\"] = device_height\n                            uc_metrics[\"pixelRatio\"] = device_pixel_ratio\n                        else:\n                            uc_metrics[\"width\"] = constants.Mobile.WIDTH\n                            uc_metrics[\"height\"] = constants.Mobile.HEIGHT\n                            uc_metrics[\"pixelRatio\"] = constants.Mobile.RATIO\n                        set_device_metrics_override = dict(\n                            {\n                                \"width\": uc_metrics[\"width\"],\n                                \"height\": uc_metrics[\"height\"],\n                                \"deviceScaleFactor\": uc_metrics[\"pixelRatio\"],\n                                \"mobile\": True\n                            }\n                        )\n                        with suppress(Exception):\n                            driver.execute_cdp_cmd(\n                                'Emulation.setDeviceMetricsOverride',\n                                set_device_metrics_override\n                            )\n                else:\n                    driver.get = lambda url: updated_get(driver, url)\n                return extend_driver(\n                    driver, proxy_auth, use_uc, recorder_ext\n                )\n            else:  # Running headless on Linux (and not using --uc)\n                try:\n                    driver = webdriver.Chrome(options=chrome_options)\n                    return extend_driver(\n                        driver, proxy_auth, use_uc, recorder_ext\n                    )\n                except Exception as e:\n                    if not hasattr(e, \"msg\"):\n                        raise\n                    auto_upgrade_chromedriver = False\n                    if \"This version of ChromeDriver only supports\" in e.msg:\n                        auto_upgrade_chromedriver = True\n                    elif \"Chrome version must be between\" in e.msg:\n                        auto_upgrade_chromedriver = True\n                    elif \"Missing or invalid capabilities\" in e.msg:\n                        chrome_options.add_experimental_option(\"w3c\", True)\n                        service = ChromeService(\n                            log_output=os.devnull,\n                            service_args=[\"--disable-build-check\"],\n                        )\n                        with warnings.catch_warnings():\n                            warnings.simplefilter(\n                                \"ignore\", category=DeprecationWarning\n                            )\n                            driver = webdriver.Chrome(\n                                service=service, options=chrome_options\n                            )\n                            return extend_driver(\n                                driver, proxy_auth, use_uc, recorder_ext\n                            )\n                    mcv = None  # Major Chrome Version\n                    if \"Current browser version is \" in e.msg:\n                        line = e.msg.split(\"Current browser version is \")[1]\n                        browser_version = line.split(\" \")[0]\n                        major_chrome_version = browser_version.split(\".\")[0]\n                        if (\n                            major_chrome_version.isnumeric()\n                            and int(major_chrome_version) >= 86\n                        ):\n                            mcv = major_chrome_version\n                    if auto_upgrade_chromedriver:\n                        args = \" \".join(sys.argv)\n                        if \"-n\" in sys.argv or \" -n=\" in args or args == \"-c\":\n                            chromedr_fixing_lock = fasteners.InterProcessLock(\n                                constants.MultiBrowser.DRIVER_FIXING_LOCK\n                            )\n                            D_F_L = constants.MultiBrowser.DRIVER_FIXING_LOCK\n                            with chromedr_fixing_lock:\n                                with suppress(Exception):\n                                    shared_utils.make_writable(D_F_L)\n                                if not _was_driver_repaired():\n                                    with suppress(Exception):\n                                        _repair_chromedriver(\n                                            chrome_options, chrome_options, mcv\n                                        )\n                                        _mark_driver_repaired()\n                        else:\n                            if not _was_driver_repaired():\n                                with suppress(Exception):\n                                    _repair_chromedriver(\n                                        chrome_options, chrome_options, mcv\n                                    )\n                            _mark_driver_repaired()\n                        with suppress(Exception):\n                            service = ChromeService(\n                                log_output=os.devnull,\n                                service_args=[\"--disable-build-check\"],\n                            )\n                            driver = webdriver.Chrome(\n                                service=service,\n                                options=chrome_options,\n                            )\n                            return extend_driver(\n                                driver, proxy_auth, use_uc, recorder_ext\n                            )\n                    # Use the virtual display on Linux during headless errors\n                    logging.debug(\n                        \"\\nWarning: Chrome failed to launch in\"\n                        \" headless mode. Attempting to use the\"\n                        \" SeleniumBase virtual display on Linux...\"\n                    )\n                    if \"--headless\" in chrome_options.arguments:\n                        chrome_options.arguments.remove(\"--headless\")\n                    if \"--headless=old\" in chrome_options.arguments:\n                        chrome_options.arguments.remove(\"--headless=old\")\n                    service = ChromeService(\n                        log_output=os.devnull,\n                        service_args=[\"--disable-build-check\"]\n                    )\n                    driver = webdriver.Chrome(\n                        service=service, options=chrome_options\n                    )\n                    return extend_driver(\n                        driver, proxy_auth, use_uc, recorder_ext\n                    )\n        except Exception as original_exception:\n            if use_uc:\n                raise\n            # Try again if Chrome didn't launch\n            with suppress(Exception):\n                service = ChromeService(service_args=[\"--disable-build-check\"])\n                driver = webdriver.Chrome(\n                    service=service, options=chrome_options\n                )\n                return extend_driver(\n                    driver, proxy_auth, use_uc, recorder_ext\n                )\n            if user_data_dir:\n                print(\"\\nUnable to set user_data_dir while starting Chrome!\\n\")\n                raise\n            elif mobile_emulator:\n                print(\"\\nFailed to start Chrome's mobile device emulator!\\n\")\n                raise\n            elif extension_zip or extension_dir:\n                print(\"\\nUnable to load extension while starting Chrome!\\n\")\n                raise\n            elif headless or headless2 or IS_LINUX or proxy_string or use_wire:\n                raise\n            # Try running without any options (bare bones Chrome launch)\n            if local_chromedriver and os.path.exists(local_chromedriver):\n                try:\n                    make_driver_executable_if_not(local_chromedriver)\n                except Exception as e:\n                    logging.debug(\n                        \"\\nWarning: Could not make chromedriver\"\n                        \" executable: %s\" % e\n                    )\n            service = ChromeService(\n                log_output=os.devnull,\n                service_args=[\"--disable-build-check\"]\n            )\n            try:\n                driver = webdriver.Chrome(service=service)\n                return extend_driver(\n                    driver, proxy_auth, use_uc, recorder_ext\n                )\n            except Exception:\n                raise original_exception\n    else:\n        raise Exception(\n            \"%s is not a valid browser option for this system!\" % browser_name\n        )\n"
  },
  {
    "path": "seleniumbase/core/capabilities_parser.py",
    "content": "import re\nimport ast\nimport json\nimport yaml  # Requires pyyaml\n\n\ndef _analyze_ast(contents):\n    try:\n        return ast.literal_eval(contents)\n    except SyntaxError:\n        pass\n    try:\n        # Remove all comments\n        contents = re.sub(re.compile(r\"/\\*.*?\\*/\", re.DOTALL), \"\", contents)\n        contents = re.sub(re.compile(r\"#.*?\\n\"), \"\", contents)\n\n        # Remove anything before dict declaration like: \"caps = { ...\"\n        match = re.match(r\"^([^{]+)\", contents)\n        if match:\n            contents = contents.replace(match.group(1), \"\")\n\n        return ast.literal_eval(contents)\n    except SyntaxError:\n        pass\n\n    return False\n\n\ndef _analyze_manual(contents):\n    capabilities = {}\n\n    code_lines = contents.split(\"\\n\")\n    for line in code_lines:\n        if \"desired_cap = {\" in line:\n            line = line.split(\"desired_cap = {\")[1]\n\n        # 'KEY' : 'VALUE'\n        data = re.match(r\"^\\s*'([\\S\\s]+)'\\s*:\\s*'([\\S\\s]+)'\\s*[,}]?\\s*$\", line)\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # \"KEY\" : \"VALUE\"\n        data = re.match(r'^\\s*\"([\\S\\s]+)\"\\s*:\\s*\"([\\S\\s]+)\"\\s*[,}]?\\s*$', line)\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # 'KEY' : \"VALUE\"\n        data = re.match(\n            r\"\"\"^\\s*'([\\S\\s]+)'\\s*:\\s*\"([\\S\\s]+)\"\\s*[,}]?\\s*$\"\"\", line\n        )\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # \"KEY\" : 'VALUE'\n        data = re.match(\n            r\"\"\"^\\s*\"([\\S\\s]+)\"\\s*:\\s*'([\\S\\s]+)'\\s*[,}]?\\s*$\"\"\", line\n        )\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # \"KEY\" : True\n        data = re.match(r\"\"\"^\\s*\"([\\S\\s]+)\"\\s*:\\s*True\\s*[,}]?\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = True\n            capabilities[key] = value\n            continue\n\n        # 'KEY' : True\n        data = re.match(r\"\"\"^\\s*'([\\S\\s]+)'\\s*:\\s*True\\s*[,}]?\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = True\n            capabilities[key] = value\n            continue\n\n        # \"KEY\" : False\n        data = re.match(r\"\"\"^\\s*\"([\\S\\s]+)\"\\s*:\\s*False\\s*[,}]?\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = False\n            capabilities[key] = value\n            continue\n\n        # 'KEY' : False\n        data = re.match(r\"\"\"^\\s*'([\\S\\s]+)'\\s*:\\s*False\\s*[,}]?\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = False\n            capabilities[key] = value\n            continue\n\n        # caps['KEY'] = 'VALUE'\n        data = re.match(r\"^\\s*caps\\['([\\S\\s]+)'\\]\\s*=\\s*'([\\S\\s]+)'\\s*$\", line)\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # caps[\"KEY\"] = \"VALUE\"\n        data = re.match(r'^\\s*caps\\[\"([\\S\\s]+)\"\\]\\s*=\\s*\"([\\S\\s]+)\"\\s*$', line)\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # caps['KEY'] = \"VALUE\"\n        data = re.match(\n            r\"\"\"^\\s*caps\\['([\\S\\s]+)'\\]\\s*=\\s*\"([\\S\\s]+)\"\\s*$\"\"\", line\n        )\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # caps[\"KEY\"] = 'VALUE'\n        data = re.match(\n            r\"\"\"^\\s*caps\\[\"([\\S\\s]+)\"\\]\\s*=\\s*'([\\S\\s]+)'\\s*$\"\"\", line\n        )\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            capabilities[key] = value\n            continue\n\n        # caps[\"KEY\"] = True\n        data = re.match(r\"\"\"^\\s*caps\\[\"([\\S\\s]+)\"\\]\\s*=\\s*True\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = True\n            capabilities[key] = value\n            continue\n\n        # caps['KEY'] = True\n        data = re.match(r\"\"\"^\\s*caps\\['([\\S\\s]+)'\\]\\s*=\\s*True\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = True\n            capabilities[key] = value\n            continue\n\n        # caps[\"KEY\"] = False\n        data = re.match(r\"\"\"^\\s*caps\\[\"([\\S\\s]+)\"\\]\\s*=\\s*False\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = False\n            capabilities[key] = value\n            continue\n\n        # caps['KEY'] = False\n        data = re.match(r\"\"\"^\\s*caps\\['([\\S\\s]+)'\\]\\s*=\\s*False\\s*$\"\"\", line)\n        if data:\n            key = data.group(1)\n            value = False\n            capabilities[key] = value\n            continue\n\n    return capabilities\n\n\ndef _read_file(file):\n    f = open(file, \"r\")\n    data = f.read()\n    f.close()\n\n    return data\n\n\ndef _parse_py_file(cap_file):\n    all_code = _read_file(cap_file)\n    capabilities = _analyze_ast(all_code)\n    if not capabilities:\n        capabilities = _analyze_manual(all_code)\n    return capabilities\n\n\ndef _parse_json_file(cap_file):\n    all_code = _read_file(cap_file)\n    return json.loads(all_code)\n\n\ndef _parse_yaml_file(cap_file):\n    all_code = _read_file(cap_file)\n    return yaml.safe_load(all_code)\n\n\ndef get_desired_capabilities(cap_file):\n    if cap_file.endswith(\".py\"):\n        capabilities = _parse_py_file(cap_file)\n    elif cap_file.endswith(\".json\"):\n        capabilities = _parse_json_file(cap_file)\n    elif (cap_file.endswith(\".yml\") or cap_file.endswith(\".yaml\")):\n        capabilities = _parse_yaml_file(cap_file)\n    else:\n        raise Exception(\n            '\\n\\n`%s` must end in \".py\", \".json\", \".yml\", or \".yaml\"!\\n'\n            % cap_file\n        )\n    if len(capabilities.keys()) == 0:\n        raise Exception(\"Unable to parse desired capabilities file!\")\n    return capabilities\n"
  },
  {
    "path": "seleniumbase/core/colored_traceback.py",
    "content": "import sys\n\n\ndef add_hook(always=False, style=\"default\", debug=False):\n    import os\n\n    if os.environ.get(\"NO_COLOR\", \"\"):\n        return  # https://no-color.org\n    isatty = getattr(sys.stderr, \"isatty\", lambda: False)\n    if always or isatty():\n        colorizer = Colorizer(style, debug)\n        sys.excepthook = colorizer.colorize_traceback\n\n\nclass Colorizer(object):\n    def __init__(self, style, debug=False):\n        self.style = style\n        self.debug = debug\n\n    def colorize_traceback(self, type, value, tb):\n        import traceback\n        import pygments.lexers\n\n        tb_text = \"\".join(traceback.format_exception(type, value, tb))\n        lexer_name = \"py3tb\"\n        lexer = pygments.lexers.get_lexer_by_name(lexer_name)\n        tb_colored = pygments.highlight(tb_text, lexer, self.formatter)\n        self.stream.write(tb_colored)\n\n    @property\n    def formatter(self):\n        from pygments.formatters import get_formatter_by_name\n        import pygments.util\n\n        colors = _get_term_color_support()\n        if self.debug:\n            sys.stderr.write(\"Detected support for %s colors\\n\" % colors)\n        if colors == 256:\n            fmt_options = {\"style\": self.style}\n        elif self.style in (\"light\", \"dark\"):\n            fmt_options = {\"bg\": self.style}\n        else:\n            fmt_options = {\"bg\": \"dark\"}\n        fmt_alias = \"terminal256\" if colors == 256 else \"terminal\"\n        try:\n            return get_formatter_by_name(fmt_alias, **fmt_options)\n        except pygments.util.ClassNotFound as ex:\n            if self.debug:\n                sys.stderr.write(str(ex) + \"\\n\")\n            return get_formatter_by_name(fmt_alias)\n\n    @property\n    def stream(self):\n        try:\n            import colorama\n        except ImportError:\n            return sys.stderr\n        return colorama.AnsiToWin32(sys.stderr)\n\n\ndef _get_term_color_support():\n    try:\n        import curses\n    except ImportError:\n        return 16\n    curses.setupterm()\n    return curses.tigetnum(\"colors\")\n"
  },
  {
    "path": "seleniumbase/core/create_db_tables.sql",
    "content": "# Creates test_db tables for using SeleniumBase with MySQL\n\n# test_run_data table\n# -----------------------------------\nCREATE TABLE `test_run_data` (\n  `guid` varchar(64) NOT NULL DEFAULT '',\n  `test_address` varchar(255) DEFAULT NULL,\n  `env` varchar(64) DEFAULT NULL,\n  `start_time` varchar(64) DEFAULT NULL,\n  `execution_guid` varchar(64) DEFAULT NULL,\n  `runtime` int(11),\n  `state` varchar(64) DEFAULT NULL,\n  `browser` varchar(64) DEFAULT NULL,\n  `message` text,\n  `stack_trace` text,\n  `retry_count` int(11) DEFAULT '0',\n  `exception_map_guid` varchar(64) DEFAULT NULL,\n  `log_url` text,\n  PRIMARY KEY (`guid`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n# test_execution table\n# -----------------------------------\nCREATE TABLE `test_execution` (\n  `guid` varchar(64) NOT NULL DEFAULT '',\n  `total_execution_time` int(11),\n  `username` varchar(255) DEFAULT NULL,\n  `execution_start` bigint(20) DEFAULT '0',\n  PRIMARY KEY (`guid`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n"
  },
  {
    "path": "seleniumbase/core/detect_b_ver.py",
    "content": "\"\"\"Detect the browser version before launching tests.\r\nEg. detect_b_ver.get_browser_version_from_os(\"google-chrome\")\"\"\"\r\nimport datetime\r\nimport os\r\nimport platform\r\nimport re\r\nimport subprocess\r\nimport sys\r\n\r\n\r\nclass File(object):\r\n    def __init__(self, stream):\r\n        self.content = stream.content\r\n        self.__stream = stream\r\n        self.__temp_name = \"driver\"\r\n\r\n    @property\r\n    def filename(self):\r\n        try:\r\n            filename = re.findall(\r\n                \"filename=(.+)\", self.__stream.headers[\"content-disposition\"]\r\n            )[0]\r\n        except KeyError:\r\n            filename = \"%s.zip\" % self.__temp_name\r\n        except IndexError:\r\n            filename = \"%s.exe\" % self.__temp_name\r\n\r\n        if '\"' in filename:\r\n            filename = filename.replace('\"', \"\")\r\n\r\n        return filename\r\n\r\n\r\nclass OSType(object):\r\n    LINUX = \"linux\"\r\n    MAC = \"mac\"\r\n    WIN = \"win\"\r\n\r\n\r\nclass ChromeType(object):\r\n    GOOGLE = \"google-chrome\"\r\n    MSEDGE = \"edge\"\r\n    OPERA = \"opera\"\r\n    BRAVE = \"brave\"\r\n    COMET = \"comet\"\r\n    ATLAS = \"atlas\"\r\n\r\n\r\nPATTERN = {\r\n    ChromeType.GOOGLE: r\"\\d+\\.\\d+\\.\\d+\",\r\n    ChromeType.MSEDGE: r\"\\d+\\.\\d+\\.\\d+\",\r\n}\r\n\r\n\r\ndef os_name():\r\n    if \"linux\" in sys.platform:\r\n        return OSType.LINUX\r\n    elif \"darwin\" in sys.platform:\r\n        return OSType.MAC\r\n    elif \"win32\" in sys.platform:\r\n        return OSType.WIN\r\n    else:\r\n        raise Exception(\"Could not determine the OS type!\")\r\n\r\n\r\ndef os_architecture():\r\n    if platform.machine().endswith(\"64\"):\r\n        return 64\r\n    else:\r\n        return 32\r\n\r\n\r\ndef os_type():\r\n    return \"%s%s\" % (os_name(), os_architecture())\r\n\r\n\r\ndef is_arch(os_sys_type):\r\n    if '_m1' in os_sys_type:\r\n        return True\r\n    return platform.processor() != 'i386'\r\n\r\n\r\ndef is_mac_os(os_sys_type):\r\n    return OSType.MAC in os_sys_type\r\n\r\n\r\ndef get_date_diff(date1, date2, date_format):\r\n    a = datetime.datetime.strptime(date1, date_format)\r\n    b = datetime.datetime.strptime(\r\n        str(date2.strftime(date_format)), date_format)\r\n    return (b - a).days\r\n\r\n\r\ndef linux_browser_apps_to_cmd(*apps):\r\n    \"\"\"Create 'browser --version' command from browser app names.\"\"\"\r\n    ignore_errors_cmd_part = \" 2>/dev/null\" if os.getenv(\r\n        \"WDM_LOG_LEVEL\") == \"0\" else \"\"\r\n    return \" || \".join(\r\n        \"%s --version%s\" % (i, ignore_errors_cmd_part) for i in apps\r\n    )\r\n\r\n\r\ndef chrome_on_linux_path(chromium_ok=False, browser_type=None):\r\n    if browser_type and browser_type != ChromeType.GOOGLE:\r\n        return \"\"\r\n    if os_name() != OSType.LINUX:\r\n        return \"\"\r\n    paths = [\r\n        \"/bin/google-chrome\",\r\n        \"/bin/google-chrome-stable\",\r\n        \"/usr/bin/google-chrome\",\r\n        \"/usr/bin/google-chrome-stable\"\r\n    ]\r\n    for path in paths:\r\n        try:\r\n            if (\r\n                os.path.exists(path)\r\n                and os.access(path, os.R_OK)\r\n                and os.access(path, os.X_OK)\r\n            ):\r\n                return path\r\n        except Exception:\r\n            pass\r\n    paths = os.environ[\"PATH\"].split(os.pathsep)\r\n    binaries = []\r\n    binaries.append(\"google-chrome\")\r\n    binaries.append(\"google-chrome-stable\")\r\n    binaries.append(\"google-chrome-beta\")\r\n    binaries.append(\"google-chrome-dev\")\r\n    binaries.append(\"google-chrome-unstable\")\r\n    binaries.append(\"chrome\")\r\n    binaries.append(\"chromium\")\r\n    binaries.append(\"chromium-browser\")\r\n    for binary in binaries:\r\n        for path in paths:\r\n            full_path = os.path.join(path, binary)\r\n            try:\r\n                if (\r\n                    os.path.exists(full_path)\r\n                    and os.access(full_path, os.R_OK)\r\n                    and os.access(full_path, os.X_OK)\r\n                ):\r\n                    return full_path\r\n            except Exception:\r\n                pass\r\n    if chromium_ok:\r\n        paths = [\r\n            \"/bin/chromium\",\r\n            \"/bin/chromium-browser\",\r\n            \"/usr/bin/chromium\",\r\n            \"/usr/bin/chromium-browser\"\r\n        ]\r\n        for path in paths:\r\n            try:\r\n                if (\r\n                    os.path.exists(path)\r\n                    and os.access(path, os.R_OK)\r\n                    and os.access(path, os.X_OK)\r\n                ):\r\n                    return path\r\n            except Exception:\r\n                pass\r\n    return \"/usr/bin/google-chrome\"\r\n\r\n\r\ndef edge_on_linux_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.MSEDGE:\r\n        return \"\"\r\n    if os_name() != OSType.LINUX:\r\n        return \"\"\r\n    paths = os.environ[\"PATH\"].split(os.pathsep)\r\n    binaries = []\r\n    binaries.append(\"microsoft-edge\")\r\n    binaries.append(\"microsoft-edge-stable\")\r\n    binaries.append(\"microsoft-edge-beta\")\r\n    binaries.append(\"microsoft-edge-dev\")\r\n    for binary in binaries:\r\n        for path in paths:\r\n            full_path = os.path.join(path, binary)\r\n            if os.path.exists(full_path) and os.access(full_path, os.X_OK):\r\n                return full_path\r\n    return \"/usr/bin/microsoft-edge\"\r\n\r\n\r\ndef opera_on_linux_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.OPERA:\r\n        return \"\"\r\n    if os_name() != OSType.LINUX:\r\n        return \"\"\r\n    paths = os.environ[\"PATH\"].split(os.pathsep)\r\n    binaries = []\r\n    binaries.append(\"opera\")\r\n    binaries.append(\"opera-stable\")\r\n    for binary in binaries:\r\n        for path in paths:\r\n            full_path = os.path.join(path, binary)\r\n            if os.path.exists(full_path) and os.access(full_path, os.X_OK):\r\n                return full_path\r\n    return \"/usr/bin/opera-stable\"\r\n\r\n\r\ndef brave_on_linux_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.BRAVE:\r\n        return \"\"\r\n    if os_name() != OSType.LINUX:\r\n        return \"\"\r\n    paths = os.environ[\"PATH\"].split(os.pathsep)\r\n    binaries = []\r\n    binaries.append(\"brave-browser\")\r\n    binaries.append(\"brave\")\r\n    binaries.append(\"brave-browser-stable\")\r\n    for binary in binaries:\r\n        for path in paths:\r\n            full_path = os.path.join(path, binary)\r\n            if os.path.exists(full_path) and os.access(full_path, os.X_OK):\r\n                return full_path\r\n    return \"/usr/bin/brave-browser\"\r\n\r\n\r\ndef comet_on_linux_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.COMET:\r\n        return \"\"\r\n    if os_name() != OSType.LINUX:\r\n        return \"\"\r\n    return \"\"  # Comet Browser isn't supported on Linux yet\r\n\r\n\r\ndef atlas_on_linux_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.ATLAS:\r\n        return \"\"\r\n    if os_name() != OSType.LINUX:\r\n        return \"\"\r\n    return \"\"  # Atlas Browser isn't supported on Linux yet\r\n\r\n\r\ndef chrome_on_windows_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.GOOGLE:\r\n        return \"\"\r\n    if os_name() != OSType.WIN:\r\n        return \"\"\r\n    candidates = []\r\n    for item in map(\r\n        os.environ.get,\r\n        (\r\n            \"PROGRAMFILES\",\r\n            \"PROGRAMFILES(X86)\",\r\n            \"LOCALAPPDATA\",\r\n            \"PROGRAMW6432\",\r\n        ),\r\n    ):\r\n        for subitem in (\r\n            \"Google/Chrome/Application\",\r\n            \"Google/Chrome Beta/Application\",\r\n            \"Google/Chrome Canary/Application\",\r\n        ):\r\n            try:\r\n                candidates.append(os.sep.join((item, subitem, \"chrome.exe\")))\r\n            except TypeError:\r\n                pass\r\n    for candidate in candidates:\r\n        if os.path.exists(candidate) and os.access(candidate, os.X_OK):\r\n            return os.path.normpath(candidate)\r\n    return \"\"\r\n\r\n\r\ndef edge_on_windows_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.MSEDGE:\r\n        return \"\"\r\n    if os_name() != OSType.WIN:\r\n        return \"\"\r\n    candidates = []\r\n    for item in map(\r\n        os.environ.get,\r\n        (\r\n            \"PROGRAMFILES\",\r\n            \"PROGRAMFILES(X86)\",\r\n            \"LOCALAPPDATA\",\r\n            \"PROGRAMW6432\",\r\n        ),\r\n    ):\r\n        for subitem in (\r\n            \"Microsoft/Edge/Application\",\r\n            \"Microsoft/Edge Beta/Application\",\r\n            \"Microsoft/Edge Canary/Application\",\r\n        ):\r\n            try:\r\n                candidates.append(os.sep.join((item, subitem, \"msedge.exe\")))\r\n            except TypeError:\r\n                pass\r\n    for candidate in candidates:\r\n        if os.path.exists(candidate) and os.access(candidate, os.X_OK):\r\n            return os.path.normpath(candidate)\r\n    return \"\"\r\n\r\n\r\ndef opera_on_windows_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.OPERA:\r\n        return \"\"\r\n    if os_name() != OSType.WIN:\r\n        return \"\"\r\n    candidates = []\r\n    for item in map(\r\n        os.environ.get,\r\n        (\r\n            \"PROGRAMFILES\",\r\n            \"PROGRAMFILES(X86)\",\r\n            \"LOCALAPPDATA\",\r\n            \"PROGRAMW6432\",\r\n        ),\r\n    ):\r\n        for subitem in (\r\n            \"Programs/Opera\",\r\n            \"Opera\",\r\n            \"Opera/Application\",\r\n        ):\r\n            try:\r\n                candidates.append(os.sep.join((item, subitem, \"opera.exe\")))\r\n            except TypeError:\r\n                pass\r\n    for candidate in candidates:\r\n        if os.path.exists(candidate) and os.access(candidate, os.X_OK):\r\n            return os.path.normpath(candidate)\r\n    return \"\"\r\n\r\n\r\ndef brave_on_windows_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.BRAVE:\r\n        return \"\"\r\n    if os_name() != OSType.WIN:\r\n        return \"\"\r\n    candidates = []\r\n    for item in map(\r\n        os.environ.get,\r\n        (\r\n            \"PROGRAMFILES\",\r\n            \"PROGRAMFILES(X86)\",\r\n            \"LOCALAPPDATA\",\r\n            \"PROGRAMW6432\",\r\n        ),\r\n    ):\r\n        for subitem in (\r\n            \"BraveSoftware/Brave-Browser/Application\",\r\n        ):\r\n            try:\r\n                candidates.append(os.sep.join((item, subitem, \"brave.exe\")))\r\n            except TypeError:\r\n                pass\r\n    for candidate in candidates:\r\n        if os.path.exists(candidate) and os.access(candidate, os.X_OK):\r\n            return os.path.normpath(candidate)\r\n    return \"\"\r\n\r\n\r\ndef comet_on_windows_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.COMET:\r\n        return \"\"\r\n    if os_name() != OSType.WIN:\r\n        return \"\"\r\n    candidates = []\r\n    for item in map(\r\n        os.environ.get,\r\n        (\r\n            \"LOCALAPPDATA\",\r\n            \"PROGRAMFILES\",\r\n            \"PROGRAMFILES(X86)\",\r\n            \"PROGRAMW6432\",\r\n        ),\r\n    ):\r\n        for subitem in (\r\n            \"Perplexity/Comet/Application\",\r\n            \"Comet/Application\",\r\n            \"Programs/Comet\",\r\n        ):\r\n            try:\r\n                candidates.append(os.sep.join((item, subitem, \"comet.exe\")))\r\n            except TypeError:\r\n                pass\r\n    for candidate in candidates:\r\n        if os.path.exists(candidate) and os.access(candidate, os.X_OK):\r\n            return os.path.normpath(candidate)\r\n    return \"\"\r\n\r\n\r\ndef atlas_on_windows_path(browser_type=None):\r\n    if browser_type and browser_type != ChromeType.ATLAS:\r\n        return \"\"\r\n    if os_name() != OSType.WIN:\r\n        return \"\"\r\n    candidates = []\r\n    for item in map(\r\n        os.environ.get,\r\n        (\r\n            \"LOCALAPPDATA\",\r\n            \"PROGRAMFILES\",\r\n            \"PROGRAMFILES(X86)\",\r\n            \"PROGRAMW6432\",\r\n        ),\r\n    ):\r\n        for subitem in (\r\n            \"OpenAI/Atlas/Application\",\r\n            \"Atlas/Application\",\r\n            \"Programs/Atlas\",\r\n        ):\r\n            try:\r\n                candidates.append(os.sep.join((item, subitem, \"atlas.exe\")))\r\n            except TypeError:\r\n                pass\r\n    for candidate in candidates:\r\n        if os.path.exists(candidate) and os.access(candidate, os.X_OK):\r\n            return os.path.normpath(candidate)\r\n    return \"\"\r\n\r\n\r\ndef windows_browser_apps_to_cmd(*apps):\r\n    \"\"\"Create analogue of browser --version command for windows.\"\"\"\r\n    powershell = determine_powershell()\r\n    first_hit_template = \"$tmp = {expression}; if ($tmp) {{echo $tmp; Exit;}};\"\r\n    script = \"$ErrorActionPreference='silentlycontinue'; \" + \" \".join(\r\n        first_hit_template.format(expression=e) for e in apps\r\n    )\r\n    return '%s -NoProfile \"%s\"' % (powershell, script)\r\n\r\n\r\ndef get_binary_location(browser_type, chromium_ok=False):\r\n    \"\"\"Return the full path of the browser binary.\"\"\"\r\n    if browser_type.lower() == \"chrome\":\r\n        browser_type = \"google-chrome\"\r\n    elif browser_type.lower() == \"msedge\":\r\n        browser_type = \"edge\"\r\n    else:\r\n        browser_type = browser_type.lower()\r\n    cmd_mapping = {\r\n        ChromeType.GOOGLE: {\r\n            OSType.LINUX: chrome_on_linux_path(chromium_ok, browser_type),\r\n            OSType.MAC: r\"/Applications/Google Chrome.app\"\r\n                        r\"/Contents/MacOS/Google Chrome\",\r\n            OSType.WIN: chrome_on_windows_path(browser_type),\r\n        },\r\n        ChromeType.MSEDGE: {\r\n            OSType.LINUX: edge_on_linux_path(browser_type),\r\n            OSType.MAC: r\"/Applications/Microsoft Edge.app\"\r\n                        r\"/Contents/MacOS/Microsoft Edge\",\r\n            OSType.WIN: edge_on_windows_path(browser_type),\r\n        },\r\n        ChromeType.OPERA: {\r\n            OSType.LINUX: opera_on_linux_path(browser_type),\r\n            OSType.MAC: r\"/Applications/Opera.app\"\r\n                        r\"/Contents/MacOS/Opera\",\r\n            OSType.WIN: opera_on_windows_path(browser_type),\r\n        },\r\n        ChromeType.BRAVE: {\r\n            OSType.LINUX: brave_on_linux_path(browser_type),\r\n            OSType.MAC: r\"/Applications/Brave Browser.app\"\r\n                        r\"/Contents/MacOS/Brave Browser\",\r\n            OSType.WIN: brave_on_windows_path(browser_type),\r\n        },\r\n        ChromeType.COMET: {\r\n            OSType.LINUX: comet_on_linux_path(browser_type),\r\n            OSType.MAC: r\"/Applications/Comet.app\"\r\n                        r\"/Contents/MacOS/Comet\",\r\n            OSType.WIN: comet_on_windows_path(browser_type),\r\n        },\r\n        ChromeType.ATLAS: {\r\n            OSType.LINUX: atlas_on_linux_path(browser_type),\r\n            OSType.MAC: r\"/Applications/ChatGPT Atlas.app\"\r\n                        r\"/Contents/MacOS/ChatGPT Atlas\",\r\n            OSType.WIN: atlas_on_windows_path(browser_type),\r\n        },\r\n    }\r\n    return cmd_mapping[browser_type][os_name()]\r\n\r\n\r\ndef get_browser_version_from_binary(binary_location):\r\n    try:\r\n        if not os.path.exists(binary_location):\r\n            return None\r\n        path = binary_location\r\n        pattern = r\"\\d+\\.\\d+\\.\\d+\"\r\n        quad_pattern = r\"\\d+\\.\\d+\\.\\d+\\.\\d+\"\r\n        if os_name() == OSType.WIN:\r\n            path = path.replace(r\"\\ \", r\" \").replace(\"\\\\\", \"\\\\\\\\\")\r\n            cmd_mapping = (\r\n                '''powershell -command \"&{(Get-Item -Path '%s')'''\r\n                '''.VersionInfo.FileVersion}\"''' % path\r\n            )\r\n            quad_version = read_version_from_cmd(cmd_mapping, quad_pattern)\r\n            if quad_version and len(str(quad_version)) >= 9:  # Eg. 122.0.0.0\r\n                return quad_version\r\n            return read_version_from_cmd(cmd_mapping, pattern)\r\n        if binary_location.count(r\"\\ \") != binary_location.count(\" \"):\r\n            binary_location = binary_location.replace(\" \", r\"\\ \")\r\n        cmd_mapping = binary_location + \" --version\"\r\n        quad_version = read_version_from_cmd(cmd_mapping, quad_pattern)\r\n        if quad_version and len(str(quad_version)) >= 9:\r\n            return quad_version\r\n        return read_version_from_cmd(cmd_mapping, pattern)\r\n    except Exception:\r\n        return None\r\n\r\n\r\ndef get_browser_version_from_os(browser_type):\r\n    \"\"\"Return installed browser version.\"\"\"\r\n    cmd_mapping = {\r\n        ChromeType.GOOGLE: {\r\n            OSType.LINUX: linux_browser_apps_to_cmd(\r\n                \"google-chrome\",\r\n                \"google-chrome-stable\",\r\n                \"chrome\",\r\n                \"chromium\",\r\n                \"chromium-browser\",\r\n                \"google-chrome-beta\",\r\n                \"google-chrome-dev\",\r\n                \"google-chrome-unstable\",\r\n            ),\r\n            OSType.MAC: r\"/Applications/Google\\ Chrome.app\"\r\n                        r\"/Contents/MacOS/Google\\ Chrome --version\",\r\n            OSType.WIN: windows_browser_apps_to_cmd(\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES\\Google\\Chrome'\r\n                r'\\Application\\chrome.exe\").VersionInfo.FileVersion',\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES (x86)\\Google\\Chrome'\r\n                r'\\Application\\chrome.exe\").VersionInfo.FileVersion',\r\n                r'(Get-Item -Path \"$env:LOCALAPPDATA\\Google\\Chrome'\r\n                r'\\Application\\chrome.exe\").VersionInfo.FileVersion',\r\n                r'(Get-ItemProperty -Path Registry::\"HKCU\\SOFTWARE'\r\n                r'\\Google\\Chrome\\BLBeacon\").version',\r\n                r'(Get-ItemProperty -Path Registry::\"HKLM\\SOFTWARE'\r\n                r'\\Wow6432Node\\Microsoft\\Windows'\r\n                r'\\CurrentVersion\\Uninstall\\Google Chrome\").version',\r\n            ),\r\n        },\r\n        ChromeType.MSEDGE: {\r\n            OSType.LINUX: linux_browser_apps_to_cmd(\r\n                \"microsoft-edge\",\r\n                \"microsoft-edge-stable\",\r\n                \"microsoft-edge-beta\",\r\n                \"microsoft-edge-dev\",\r\n            ),\r\n            OSType.MAC: r\"/Applications/Microsoft\\ Edge.app\"\r\n                        r\"/Contents/MacOS/Microsoft\\ Edge --version\",\r\n            OSType.WIN: windows_browser_apps_to_cmd(\r\n                # stable edge\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES\\Microsoft\\Edge'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES (x86)\\Microsoft'\r\n                r'\\Edge\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-ItemProperty -Path Registry::\"HKCU\\SOFTWARE'\r\n                r'\\Microsoft\\Edge\\BLBeacon\").version',\r\n                r'(Get-ItemProperty -Path Registry::\"HKLM\\SOFTWARE'\r\n                r'\\Microsoft\\EdgeUpdate\\Clients'\r\n                r'\\{56EB18F8-8008-4CBD-B6D2-8C97FE7E9062}\").pv',\r\n                # beta edge\r\n                r'(Get-Item -Path \"$env:LOCALAPPDATA\\Microsoft\\Edge Beta'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES\\Microsoft\\Edge Beta'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES (x86)\\Microsoft\\Edge Beta'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-ItemProperty -Path Registry::\"HKCU\\SOFTWARE\\Microsoft'\r\n                r'\\Edge Beta\\BLBeacon\").version',\r\n                # dev edge\r\n                r'(Get-Item -Path \"$env:LOCALAPPDATA\\Microsoft\\Edge Dev'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES\\Microsoft\\Edge Dev'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-Item -Path \"$env:PROGRAMFILES (x86)\\Microsoft\\Edge Dev'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-ItemProperty -Path Registry::\"HKCU\\SOFTWARE\\Microsoft'\r\n                r'\\Edge Dev\\BLBeacon\").version',\r\n                # canary edge\r\n                r'(Get-Item -Path \"$env:LOCALAPPDATA\\Microsoft\\Edge SxS'\r\n                r'\\Application\\msedge.exe\").VersionInfo.FileVersion',\r\n                r'(Get-ItemProperty -Path Registry::\"HKCU\\SOFTWARE'\r\n                r'\\Microsoft\\Edge SxS\\BLBeacon\").version',\r\n                # highest edge\r\n                r\"(Get-Item (Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\"\r\n                r\"\\Windows\\CurrentVersion\\App Paths\\msedge.exe').\"\r\n                r\"'(Default)').VersionInfo.ProductVersion\",\r\n                r\"[System.Diagnostics.FileVersionInfo]::GetVersionInfo((\"\r\n                r\"Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\"\r\n                r\"\\CurrentVersion\\App Paths\\msedge.exe').\"\r\n                r\"'(Default)').ProductVersion\",\r\n                r\"Get-AppxPackage -Name *MicrosoftEdge.* | Foreach Version\",\r\n                r'(Get-ItemProperty -Path Registry::\"HKLM\\SOFTWARE\\Wow6432Node'\r\n                r'\\Microsoft\\Windows\\CurrentVersion\\Uninstall'\r\n                r'\\Microsoft Edge\").version',\r\n            ),\r\n        },\r\n    }\r\n    try:\r\n        cmd_mapping = cmd_mapping[browser_type][os_name()]\r\n        pattern = PATTERN[browser_type]\r\n        quad_pattern = r\"\\d+\\.\\d+\\.\\d+\\.\\d+\"\r\n        quad_version = read_version_from_cmd(cmd_mapping, quad_pattern)\r\n        if quad_version and len(str(quad_version)) >= 9:  # Eg. 115.0.0.0\r\n            return quad_version\r\n        version = read_version_from_cmd(cmd_mapping, pattern)\r\n        return version\r\n    except Exception:\r\n        raise Exception(\r\n            \"Can not find browser %s installed in your system!\" % browser_type\r\n        )\r\n\r\n\r\ndef format_version(browser_type, version):\r\n    if not version or version == 'latest':\r\n        return 'latest'\r\n    try:\r\n        pattern = PATTERN[browser_type]\r\n        result = re.search(pattern, version)\r\n        return result.group(0) if result else version\r\n    except Exception:\r\n        return \"latest\"\r\n\r\n\r\ndef get_browser_version(browser_type, metadata):\r\n    pattern = PATTERN[browser_type]\r\n    version_from_os = metadata['version']\r\n    result = re.search(pattern, version_from_os)\r\n    version = result.group(0) if version_from_os else None\r\n    return version\r\n\r\n\r\ndef read_version_from_cmd(cmd, pattern):\r\n    with subprocess.Popen(\r\n            cmd,\r\n            stdout=subprocess.PIPE,\r\n            stderr=subprocess.DEVNULL,\r\n            stdin=subprocess.DEVNULL,\r\n            shell=True,\r\n    ) as stream:\r\n        stdout = stream.communicate()[0].decode()\r\n        version = re.search(pattern, stdout)\r\n        version = version.group(0) if version else None\r\n    return version\r\n\r\n\r\ndef determine_powershell():\r\n    \"\"\"Returns \"True\" if runs in Powershell and \"False\" if another console.\"\"\"\r\n    cmd = \"(dir 2>&1 *`|echo CMD);&<# rem #>echo powershell\"\r\n    with subprocess.Popen(\r\n            cmd,\r\n            stdout=subprocess.PIPE,\r\n            stderr=subprocess.DEVNULL,\r\n            stdin=subprocess.DEVNULL,\r\n            shell=True,\r\n    ) as stream:\r\n        stdout = stream.communicate()[0].decode()\r\n    return \"\" if stdout == \"powershell\" else \"powershell\"\r\n"
  },
  {
    "path": "seleniumbase/core/download_helper.py",
    "content": "import os\nimport shutil\nimport time\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\n\n# The \"downloads_folder\" is a folder for saving downloaded files.\n# Works for downloads initiated by Chromium and Firefox WebDriver clicks.\n# Browser type doesn't matter if using self.download_file(file_url)\n#                             or self.save_file_as(file_url, new_file_name)\n# The \"downloads_folder\" is cleaned out at the start of each pytest run,\n#     but there is an option to save existing files in \"archived_files\".\nDOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER\nabs_path = os.path.abspath(\".\")\ndownloads_path = os.path.join(abs_path, DOWNLOADS_DIR)\n\n\ndef get_downloads_folder():\n    return downloads_path\n\n\ndef reset_downloads_folder():\n    \"\"\"Clears the downloads folder.\n    If settings.ARCHIVE_EXISTING_DOWNLOADS is set to True, archives it.\"\"\"\n    downloads_dir = constants.Files.DOWNLOADS_FOLDER\n    archive_dir = constants.Files.ARCHIVED_DOWNLOADS_FOLDER\n    if downloads_dir.endswith(\"/\"):\n        downloads_dir = downloads_dir[:-1]\n    if downloads_dir.startswith(\"/\"):\n        downloads_dir = downloads_dir[1:]\n    if archive_dir.endswith(\"/\"):\n        archive_dir = archive_dir[:-1]\n    if archive_dir.startswith(\"/\"):\n        archive_dir = archive_dir[1:]\n    if len(downloads_dir) < 10 or len(archive_dir) < 10:\n        return  # Prevent accidental deletions if constants are renamed\n    archived_downloads_folder = os.path.join(os.getcwd(), archive_dir) + os.sep\n    if os.path.exists(downloads_path) and not os.listdir(downloads_path) == []:\n        reset_downloads_folder_assistant(archived_downloads_folder)\n    if os.path.exists(downloads_path) and os.listdir(downloads_path) == []:\n        try:\n            os.rmdir(downloads_path)\n        except OSError:\n            pass\n    if (\n        os.path.exists(archived_downloads_folder)\n        and os.listdir(archived_downloads_folder) == []\n    ):\n        try:\n            os.rmdir(archived_downloads_folder)\n        except OSError:\n            pass\n\n\ndef reset_downloads_folder_assistant(archived_downloads_folder):\n    if not os.path.exists(archived_downloads_folder):\n        try:\n            os.makedirs(archived_downloads_folder, exist_ok=True)\n        except Exception:\n            pass  # Should only be reachable during multi-threaded test runs\n    new_archived_downloads_sub_folder = \"%s/downloads_%s\" % (\n        archived_downloads_folder,\n        int(time.time()),\n    )\n    if os.path.exists(downloads_path):\n        if not os.listdir(downloads_path) == []:\n            try:\n                shutil.move(downloads_path, new_archived_downloads_sub_folder)\n                os.makedirs(downloads_path, exist_ok=True)\n            except Exception:\n                pass\n    if not settings.ARCHIVE_EXISTING_DOWNLOADS:\n        try:\n            shutil.rmtree(new_archived_downloads_sub_folder)\n        except OSError:\n            pass\n"
  },
  {
    "path": "seleniumbase/core/encoded_images.py",
    "content": "\"\"\" Instructions for generating encoded images:\n    > import base64\n    > with open(\"YOUR_FILE.png\", \"rb\") as image_file:\n    >     encoded_string = base64.b64encode(image_file.read())\n\"\"\"\n\n\ndef get_dash_pie_png1():\n    DASH_PIE_PNG_1 = (\n        \"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAA\"\n        \"AAXNSR0IArs4c6QAABqxJREFUeAHtnOtPFFcUwO/sLo9GUKIkUtNWDK+a2qZGm2LShDFS\"\n        \"oeWhqUatxrSoMSS06R/QD0DSP0MTP1Cb9pMCpUCx0EIaWiBChBYLUWwMEndBEBamuztze\"\n        \"8+yd5jdndl57Mzu7IZN8N69j3PP+c05587s3hWh7dc2ASMEGCOTjM7xN37RAnMZhJvlZL\"\n        \"TVBeYGi58VRvUxeIDBzK/QfqOsvSWq36IGy+EAEAKjgujPqtmgCCdyYgiW1aAsgUOAsCH\"\n        \"vUAUitVszHOkkAMU7W28cvDMgbTaj7jJDCJWxBQWztE1fifUNh9GYYbFDYK/+UxcMPTO9\"\n        \"yaFfG/kZgcamfuIt/aSXlR9hcStAQqiZQOq/9vdpU3SIGw7kFAIGLnn8ChlwnCjkm57UD\"\n        \"5Ci+nQ2xAUn5C3NOtdMzHACKV5AhnJOKLfEfWUspwSAHtZjRnAcN5KwdXuOlWDMiCo54C\"\n        \"RhG8pDuuBYCWbTKKvwkE3NACDNcKwHI3fNzW3TC0gTnHQAQzHrAaQJTuj+hcq3sBQslL0\"\n        \"lGgBtvVOuqcKB7Vp5usk9iWETVFrLNh8TDgmnFiKJDUpLt3/INq92Jx0TTujhMd2wiPao\"\n        \"5R9FOM+72r/hXS5OlJSmFezkm5VMk4XzeOYBu15a9PXLhrqhQH7ekNLktGiPEV6ycBjGG\"\n        \"aTp3fVqpfvzs4denKvqRlkZs5bDwJavILuAkvdEwQGvIfeT5G/zxaPMPO8bRdXzXza4No\"\n        \"6W3hUyMjy0L21KBe+JgoOQg5UzWkCuwkW28pS78cIsV7a/T25MKrfJeU8UHIZBigkKjPd\"\n        \"n5ZZ76moqlxrqu/ndu4bNBZKkuAIjiPdE2hIG5/HMVEvkAKX363teq35+5Vzx0qkPf8TZ\"\n        \"mU+VxulqTyIb0PPaw/oWqb5hcBiGqZB2qtV5lJG/XlJS86zpM447UtpJ8tGy2hw792Mm+\"\n        \"C2JqGIYHGkiFkdoqAhMRrHneGWt+/qFaV/R6wMapthzSERoiXA2d6n4dPa/klu+ePpk+d\"\n        \"Llms7A7l3j8UlLzmzpI4UIR2mX0qsiz2Rlr+/dX+u+cr5g9cSxAcHpnNMuI8lJBxQlX/N\"\n        \"QfSVwaJM5JY9cBSuHD7Oery6tce8UdQqZmeqPIjZgI807Ihy1LdwoMp8j55DnZFWt5+rZ\"\n        \"0cDePSqPIragw1JbRTi0warStyPvA/flT46+uPhxt39n7qRV65gpN2FwQGmy9Wd79xVWu\"\n        \"69/mrNa8f4An+FaMNMYs2UlFA5VHh5FVt47wi42XVzg3j7QJ340YoOoojpC6ZK+SXTd58\"\n        \"p511P1EdpxbH4g5+5gDmK8+YnWIdZ6SfGcSIW8O/exy5cq16YP8rbQh+qXVM8BJZzIvzA\"\n        \"mDE904K4qcuTLVq+kwpnFI6MdQk/OGvJW2YpKSJmkwBHQ6tB3gTb+EfNvhR2hUJ0SCodB\"\n        \"/NxQoH3sF+bPM3YLIQpEWiYEDskryxNoZPQnvq+QYzbOSBWwc12EgzFqteIRYkGYHP8e3\"\n        \"8l+ibyVdgZBdSN7Qiuti3Bog1mlA60N3xbauBn8hDVLZqLliHAOlLzVMjc71RyvAg4UmB\"\n        \"sReqe68GBNvLKSPT/szoLAMXwD70S+tSl8H7bmAg5tvJlsw4yuf7OsXWQieg4IM5p3/hN\"\n        \"W7rXhW2geLZwwqpQt5pED31I9wuAgJJBOh+bQcuL18R/wt2t/4UepDSVEBE7CS+GILkQb\"\n        \"tYQWyStPx/AfU718X5mP4Qrp3FQvpSEFtkR4TuzQcmI/N43uj3fg3jwvJrf8UWhTFw8xJ\"\n        \"cxrwBJZ8+S8J4BW+27zt5gnaD4tQijyMsqdVY7yHJgkTcwu5J28x/dM/YZGzkcKTJv3JB\"\n        \"HLHeKWhUMT84Twe08v/rmIQ1z6giFXODIR04suG1bQCd8bk5sezTsXFZhqJeQapZ8hKcI\"\n        \"BI+F3A6lmrF59I3co6fyYH0tCkpIOTre6mn0x4UCSArdLNyhBexSSsNTWmGFFBwYPNEec\"\n        \"QKB9KVkSMDdLO1SjQhMcAJA2gDSCAZtjhhUMoC8tpOlYO5dK27aczprhwGS1BCa3gJ3a5\"\n        \"O6CY+mnOayoEDjcEzx5mUo5SEcoUTuh1A2HTk6ZHGQQDNipK6woGCghBxGyrdI2u9VBv3\"\n        \"hypWHPoSCCYabxx110TiJKvflFTqe44YDQ4CFDcpbODs9i4C1Kz0pyAGK1mQJHukCychF\"\n        \"AQYJD9qMHqX566qbDgcUT6UlmekokOEvgSBexApQVXiLVmdYth0MXoiX9fUHwSKuWeyWy\"\n        \"FSfjf12i+m6X2wT0E/gfn/18pZirCc8AAAAASUVORK5CYII=\"\n    )\n    return DASH_PIE_PNG_1\n\n\ndef get_dash_pie_png2():\n    DASH_PIE_PNG_2 = (\n        \"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAA\"\n        \"AAXNSR0IArs4c6QAACQ9JREFUeAHlnH9sFEUUx9/uXXtXKHCWlhatWEoFKddGkWDVCoeQ\"\n        \"AELxD0QgoIjxZ4yJJmIIicFGJcZo4l8kBhJjAlhA/hABIeHHIS3yo0SsRyUCpWApx6/Yw\"\n        \"PW6pXe37ndxjuuy1929vd9O0s7d3Mzse5+8ebMzO285SmJaufnUx7hcx1VabeSyeXbOPX\"\n        \"yoeBBtPl844WMjbc3U5cw01tMWQG7c5Kb2CKKr01MkN2lv1dPybh2H1Ax/SPc7rxGDlWh\"\n        \"QCYGzcusfrhtdltUMSNc1Ivy9NHiXrOBHZcflXPlvQ12g/VDF5TJluddTTpf/KJeLu/Yt\"\n        \"kkEBVhiUI1j/+YIqt7Kd2e9xhcOgXLk4yNVxalAYyFRHJ01xXNKUNRocZUMGi4EqneCn4\"\n        \"lF+eejF05riBuf1da0HGJSHus7QuyMO6gISqbheOJFtAOr89nlkv+WULWrMpOvu4XGyJN\"\n        \"Nw4FPgYFsPFFGsUJiyG+r6pGHlLWPfjeTMmqhlETFLWvd65TQjfSjrmoIDaznXXOiCg13\"\n        \"/wHrDlqIUxgwc1lekJT0++7rbDKCY4MC3dHj5A7CWOv8uiuZgmcB68w1zJct5ODbLUV5D\"\n        \"tqSNq6hy2jUqLQlNi8Vh88pOtb4DTGvLkAOHtxTRl7nr4wZG67pGfy9xttHIJWsIckJey\"\n        \"G20D0NwGBhL0yX6a0K96WFkVFij9QFo2vrX6OzFtpgA6YYTCWbr2E1G5dRVX9RVy3ilJ1\"\n        \"d9HRMgXXCSAeaOyonCQxQLIE04yQNj3CKMtjAKaEA42QSGgTQCaEA4mK497kGUKB/DBL6\"\n        \"bh+5+TOAnAIJe0G+gy0SFgxs83Mfg5i5pKTlsZHXGv7eGoB/0jKafKhwsCXDnixs8PQvG\"\n        \"aJ2nczmmeWGIh7AehPtQk1UVDtZKWBLE685X7cLpUKblf+6Bw4bTp7MONQatViEdlEikD\"\n        \"KPnbSdsr2D/SXmdfnBgXjCzUflXaMF8f23fsqcbA4WORmWjbPo+0PDqBwf0QPHdeedk/W\"\n        \"1DemdwS6ucvS88sZtsOWcTDiVx94ADih7NesJwIq1mivNWuDOe+hy5pdys4NuTrT2Txv4\"\n        \"Yysm5Hv4xSz5Es54wHArxLt/VQVRdfkVVZZ6CZfbagueDb9ScFcY9tFe1UgYXqllPGE54\"\n        \"hlp0eUAVrTl9NbbZI2f4l0/ZHSwYdmTAyoZ/TNG4kuSE9eAhAB4KMLFlOLivwWOTpdNbW\"\n        \"Llmnjfs9ixxWVWFUDdxp2jP7dBsoKdC6tjI0jmmNxA4gAcKZDh4rgRqU50+uZLefxYxUG\"\n        \"gbkzsn9OYkQXh87A7JH3XpbZuO9UZW3bEe8IB8MhyYEuBEOmIjwvNcqML2TMHc4Gs1p2+\"\n        \"PedBtpG061VUOLR6z1M2rg6l6tLojNiK81dZXw9eV1QhLntoRKBh20kjbdKlrL/cQeIAL\"\n        \"rzVLGRXaQn12W1FoLvdyVUng2UfcIYulXX8fKXY6kqD20R7CrA0u4dlKvwL6avIUKLFUO\"\n        \"1zBd2p9QvWYHaHcXO2lSOrZUKTf4fVO4fqQ3FvLyvc6bc8WzQ0un9QcKB6usRRJPZ1Iv2\"\n        \"O9V53ElFjzQrXBxZWC4A3stuzylObclJ7fpnlK2LBS01v2RyXiLP7V6vzAlHHuYI7Vq1Y\"\n        \"vXcqSCocpjaWIZeJ9LvGtp71C1ei94a2R1I8qJqKcJ21Y9bvqf18slt5HLdOLqXfyKHdo\"\n        \"hyefuO5CtXqpKkuJ5SiVlbZGXL1LJvpOjw+mhTxMvpRaDoQIkdV7kL/w+2bx8EyK6VgDU\"\n        \"yX+eUrhHONuNG+ho/k+sXtm/FUz32NK4Pg4a+NasSnYRhenmlchcT0kFU6Qs7ZvE8+d2E\"\n        \"/H5qfbEFJDnBQ4IcrpauL+bt5GzWUC1zNfTZB0LLM898o7XLdvsKv9gt/wfo4ehU5xPSe\"\n        \"/4nYLzXR2coACBXrapLLOb9/PoJI8J413+usTZjk9XO6RtVyjcEa84EqlsmauzePcLg47\"\n        \"b9hXbaafcNsQWdq3896d79PGmkwGA4XiZjlByvEd4zubN4tHSwSxZ06YVoZ9wMHvyhevy\"\n        \"TEW8h1p6QiqL6sk+qRhZEyqdHHcvs+4/Ue/E90ugXoeiamTNGiEE6gIG0BsBcS5Yzl8yJ\"\n        \"0/wr+6pa1YKhr40QwasdTL5Z38hmv0tYpt01lZJuc4CV8hhQrgBDz0kC0HZ3SHjuimlvO\"\n        \"Ao50kv9Kxj7+0Z4W4xSGBqdVukRk1hDYngQM7sxxe6OkZWpJfEY5yN46s4H/ybZXWQrc5\"\n        \"oSwz1NaWElM4XAs4sNphOFqzlk+07P2Cczd9S/trusXujPUrTHFlLpx3kuRapLEUcrPfw\"\n        \"nBQoGY9t3mbZxPXvvkDvmHGBerMCt/ClGc5HDGibhCWxIYUfusHB9RA745jJnLznXs+FH\"\n        \"+w/0LHF7KOsjGHI0akDXPETMd+cECt0tldfzOvmJ5o+IcaxKaZAgkVrHI25vJywe4k6B1\"\n        \"pNdC1HxwUMN+Dm6H/Q4KeWCGoRfjdAwdAEILjdPnp1zXvZTUf6Ac9oa+aoqpw2PAqkcwN\"\n        \"ZpeNKZoTjtRVFQ4qwMwQL4mwQHSUTQn6XJYC1bQi+TS3tHH09sTPhS4EduFRaaYnvWCgZ\"\n        \"1TLYRAQI4lQwD+/XsWKMjqPNm2rKaUJB42yxUHDAVeMKqfK6lu6Yj41hxUjisM8iJVESC\"\n        \"BOXmbSEMNQguVjZtILBnrrhoPKDBAOcmeKD2I+JpYoYUNwGCCcdIeTpuoGemxx+h5Jlm9\"\n        \"DpNlWa1aCXmrJMBzWCWYxhB15BY8cP8nK0yWHf8F9Gm5HYg28jxkOIOC8bqtn8Gr4IZyl\"\n        \"SwcrgrVgSSD7F6yXTLxvxxQcGdB/jhqHDBGjhQi4VDhrQMGeDKwFa6VY30IQafmm4TBAO\"\n        \"H3JXviBCLhkWRKzFOziYbtFXl2bsJa4w4nsEL4IMVvMknCuN96gmJVgzxdQZEvB9qa0H6\"\n        \"XcdoiUzejnuFiO8qKY8pkl4cAzA4V6iC9AwpFWPcNPnooj3rqEtsxKsBmO3UszfgX9RUs\"\n        \"JgRN5sUhQKGfv7UI4Af60kvJ9Xagvb4LH2UrU5Eg4HOVFWUQKe8mZ8nfld/byMpQnykKU\"\n        \"12Tf/wU491oPtPXjKQAAAABJRU5ErkJggg==\"\n    )\n    return DASH_PIE_PNG_2\n\n\ndef get_dash_pie_png3():\n    DASH_PIE_PNG_3 = (\n        \"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAA\"\n        \"AAXNSR0IArs4c6QAACThJREFUeAHlnH9sE+cZx587O3FIUuIxnARKISQRtImTdgy1gbIQ\"\n        \"IAKmAv2DsaWiW9UJbaumSZ20SRPSpk3b+GPqH/zVaVKkaVLpAqhSoRSxKgRXJClh6caPJ\"\n        \"KvWNE1oSAxetSS1k3Ni+3bf13nds3P2nX//2CvZ53vv/fE8Hz/v8/64e0+w/OqPMmUotF\"\n        \"RuYjU9JgePRqudFWZpjmZY8hsPJ4xmSzqdOekSdAoAkNVkpQq5gqbu2ljq/hGdTBGXrTY\"\n        \"bWYNZ6Zh9E3FY6QaVFjgbyiqooawmBOS+i2hY+Xy37DJT+73Gv0eoHzx947Bv/Hr9dE3k\"\n        \"RedQLU3frWXR4+c6FFBBWBzUiGecJj2zkdmSPk8pHA5FeLCeJgdKQ0B2W6eotfF+wsJW2\"\n        \"8cIHxZe6CYOi4NqbFxPDVVTrOml0ppSBmd/5ZPEoWya+Zheq3w/KSCxSIZgLYMavXiESo\"\n        \"btrOntb7JSqiwpaTjwKXCwIz02CkHZkqiVxN83BEGdDlnTTE8HcUt67+HtWIx1ryUFB9b\"\n        \"iuVtPcLCdj3ZSa8JQdOXUTcCtydk0RtyS9u8lSgZQQnDgW3aUPsms5fD8ZYrmYHU1ikwQ\"\n        \"v+FElsB8U7U9aEn3z5ykY3sq6IP52wk5bHFF6ToRANPo3kn952z0WnEn/bJGu+fRKSbtl\"\n        \"2FJ646fYnJCXsgdb4gLDgdj6rtP/278DbVaE/Ut8YqZWHoA2tN5gkbvjbE/NF5AhuGowZ\"\n        \"zf8mZi0urkSkGr0qxhx8nTCQEyBCcTYIJapQsPUSKAdOFkDozmn57SyHgBxYRTSGA45Xg\"\n        \"AxYSD7nrIUUrp8jFc4C+PgS9/pvEXAEEv6BcrRIWDAd7INRsb3MUqIKXXMsOGifzEq6eY\"\n        \"ftAzWtCEgykBRr4Y4OV6dx1NMb14dPPSI0NsPgj3oRU04WCuNK5MCXJ1gKelSCJxev5nB\"\n        \"RzenH538Hqv32yWEqk0n/JsPnKRJodL2fpTpNxhcGBeWHbYWP6Ajh2d37X00rO9vrXW3s\"\n        \"hMhXQeq3mFwcHqHSj+5MgnTH/LI9524cUmu/dbz1whS9Fo2qGkbwwYU/Ro1hOCo7aaVvs\"\n        \"XocJEWrIWbxAO+l952rywfcuFQFHRf0IXC+RHNOtRwbGS+2EpNdc+0FRZJH9Nya41z/t/\"\n        \"0DIqbd3UrZkojyO1rCcEJ9RDdUzHVNFctNRi+ea69vmXW6/411TciJk47otZaleKnLCeG\"\n        \"eUmAO6S8MDgYFwzNWSjF/fd4fG6x1UViwfll5rqpcPb3pVLiid1MxhJkD02TDrrvi7GAT\"\n        \"wQGBzcVwK13XY3izT6ZZJ9ay11xc8Ffrhdkr6+5ZLij4J33owWkGPp1ilLrOAAHggMDkw\"\n        \"JkWpHHI/cohCot3xjzSH/iZaPFusec8STN5fSRjYtEb3U3MMyat6s7YjjEd5sWWoRD9e0\"\n        \"SMd3XvKtqbgVT95cSVtSO8R4gIsCJ3YvFa/QJloqsdgCh4TvNVX79j7uCJhM48bLyLLTU\"\n        \"QQt2TzEem1wCfVWxhUwllIkX7Wp2drm//Eut9RcdylQXKw/Fck+G1L7HdFoF24MycpUZt\"\n        \"Frt+y1HfK/vH3QV/VVnalI9umo/Y55pTrpiTGvCuzyv9AgSU7fFdPloQ1Fc1/Y01NT6kp\"\n        \"NW7PSEpH5o2r5oPj95nJf61aHv8js1EqXK3EZhcOVxlTEtO0rbfKPnnVKTZu7Q0sj2W9V\"\n        \"XER2zFizCqt1+cRk8j5l2ldF3qc3OgKXhspJ8KzVSpetuKxYTqSyytJIm/f4NvdHT/hzQ\"\n        \"h4uX1YtB0IEyOx8X5y4fVbuP0ACFys3jlmFc1P4fPAcDZS7Zc+B3MARLkVW4LgFc+/rcp\"\n        \"9/jO7tDhcnt84yCscvmMffkj/5sIduHs21JqT1t2QEToCKZvqEzwbfosEaSVg4qiVILsa\"\n        \"JnwkTVNNA9NuudWmRb1hYuPUL4YLzDF1vl2ihPi2VpLDQf/61nfEAl7RZzoJQfON1oVf6\"\n        \"WJ5oS6HsGS1KxHO76+0ueuNqc0oqDpBp/KLofPendKYln8EARsosx09F7pvi1OBZeaBak\"\n        \"heeSwnpLBQyc7WDGr7tovOK0bARabJ+Z0YQrv5e6Bn4i+xoU/zK41nQKSVV4sl47LHA3g\"\n        \"oEZjmTnhlqrJynO2NVSlTsWzMs1/KXV1h1609Cr3tEHtunjs/X358qT8LXN87TsLKXAmE\"\n        \"ZziztqPRQvwNw9IPiVyavifeGLwT+sXWRpKf0c+RHCmnMTqu3u2jSFbSc0ETPSNNS/Io0\"\n        \"IHx+4+fiO+7zylxoUZBq8kNtfSnVXThPHYKj12u5ZVP3HwRH35+pp8Uje/LWr3DFI4/Sp\"\n        \"3YqV1wLXAwPITiI0LKeRdEy9KYwfvZnYlf7BE0VhG/hyvMjHHGJsmorK9uS1Pu2wrrySM\"\n        \"fsEKf+9rb8YZ1E0nd4QYV4jHTEXMcwywG12aoRmltVRc90/Ze65L4DCph6nrgQj/A11SV\"\n        \"2prfaaqBrGBxEcN+DwdD/Q4CemCFo7fBbAQdAsAXH3jZPH5x6taD5QD+mp6KvVtCEw5sX\"\n        \"zA1mV4ghmhNW66oJBwlgZmVNymOAdzrYFkF1pnz/zTbQKhvVHt07GnMnX1Q4AIAtgChgW\"\n        \"ikIBRZCMAoGusaEgwQA1LDHRf86fRKneR/QbW9Q5k/YQawXdOGggEJx0HDA9Rtrabi8P2\"\n        \"ywFw2SIThw0CgQBaOCfGtikPfaic64wACYIThIqAaUTz6I+5idygKWUYuBvggm8+5Dv2a\"\n        \"/DHzNLXnJW+6iuq0Bmrr4PDld8+xhHwNZs5IEwxB39yusU3nHdZMgfzzBsOXwQmFBvBez\"\n        \"Tnfk7EARzR/y6XXXXC+to5DM+3PwvG7Fgwa28xbP0n1NebdEtgOsBVMCjHwxT9SaFhiVM\"\n        \"Sk4qARPXWJTO7YGYI8WdsDh0bFMB0DBmgxG9ZgrJfoWArXcScNBYQCEpy/ZCz+UrY/YAZ\"\n        \"cpS+KWghuTWKxK1lpSDkddIDazYc8WtyQ815tqUNxKsOYLKLAULNRhPQo+MVUhJZYTKYz\"\n        \"akvAAOAeFdNhfgIBHWo00P9YVL791iS+jcCtZrdwUAJRk/AoTJspXWuCo61KDQjw2oCBg\"\n        \"OwE+egH3kfj7umAhCOmwEi050g4nslK+I4W/5CzyeuQ5f3kZ4tNlIZF18vP/AXYR+dvV3\"\n        \"FCCAAAAAElFTkSuQmCC\"\n    )\n    return DASH_PIE_PNG_3\n\n\ndef get_report_favicon():\n    REPORT_FAVICON = (\n        \"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAA\"\n        \"AAXNSR0IArs4c6QAAAM9JREFUKBVjnNS8goEUwESKYpBaFiD+z/hTJz2EoM4rM9cw/mcH\"\n        \"aYCA2Nl3YUwEvTBFkZGREchnBEIwAGv4z/6f4T8DWAIk+P8/RA6ZBCkAA4QNi1IUkVVgY\"\n        \"f9nBwpCNTiLcGNRgSp0FcxF2CC3RhVVAcOjkNtAEYg4hA3kIjRAPYWmCeRdFIDQ8BBsHo\"\n        \"okmIMmDtXw5s0bTKVoIhCrQBogLHaPUGQVP7avgnA5PMOAjJ87VkO4ZCUNiFa4GRAu3K9\"\n        \"o4lA/LJ+xF6KOIEmykwBQHy74EMZM3QAAAABJRU5ErkJggg==\"\n    )\n    return REPORT_FAVICON\n\n\ndef get_side_by_side_png():\n    SIDE_BY_SIDE_PNG = (\n        \"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAA\"\n        \"AAXNSR0IArs4c6QAAAIVJREFUKBVjvPLvPwMpgIkUxSC1LBANOo0oGq/Ug7hYBUm2gWQN\"\n        \"jBBPa/1lRHbTNWZQSGAVhPqBaRWyeoYrkSAuozaKIMMNEJdkJ5GsAeqkf2Eotl8D8/5fR\"\n        \"RGEcGB+aEGJb0g8MGEVxGIIXiFy/eC8CuJsmOH1WkAWVkFoxMEUEqZJdhIAo3Aj/iHmzl\"\n        \"MAAAAASUVORK5CYII=\"\n    )\n    return SIDE_BY_SIDE_PNG\n\n\ndef get_no_screenshot_png():\n    NO_SCREENSHOT = (\n        \"iVBORw0KGgoAAAANSUhEUgAAAIwAAAA8CAAAAACRYQ2XAAAF10lEQVRo3u3WeVATVxwH8\"\n        \"AiGIDEkEEjEoCCpXGVsFI3EE5DBitd4Y6nngK1a79pRUYeq1FIto9JWO9ra4mCZoiCKR4\"\n        \"tarMihYAEJJIpXNgmQmJBAIHe+/QORadC2M870mNn9a/f3e2/eZ/f33ttHwX/oopAYEkN\"\n        \"iSAyJITEkhsSQGBJDYkgMiSExJIbEkBgSQ2JIDIn59zA6RbP5H8Los7bkWQHAdDyt+SXt\"\n        \"avfEDA1OOiZ/nbFUn33R1TdqNZidMYrRlKDbAKCfwqzr2+PaCCpPGDqQmtDwGpjGIKGmb\"\n        \"/RO4jd2J4wyikJZ1AagPZ59r0+HZwnuaU81rdenUFJeo1SSYJG2b7TIfYPDGTN24BCP47\"\n        \"0Ys/LRk97XqOWKVABwk/OmDECHrInoBGwarV2jtALWZkJh7G5pkBNqBwCLqsOhJuSdAGB\"\n        \"rJQiVHZAEj9NqCaIdAGCUP5TpAIchx+O9FosTRhiUzhop7cHc/2AYx1d0qqfElaxxLQCg\"\n        \"O/SpCiidNciHt6gazQsTs8cKCDzYyucMXVpqA6wXZvtxBOkEUBO140sBxz+xDlCnh3K5E\"\n        \"ZntkISMOjyW4zf9hgOoT/HncGN/MJu38Vy8hNXOmGE171PWGrsx8gS3Obs3D2MesnVnm8\"\n        \"JoS660WgDYgV9DGMtS51Mny+WRnjxufOvjONr01JXM4GtAHtdvzbZJ/ZN0KGOxeYt3jKc\"\n        \"s6LCl0d/enRpFOwhJmPugWamzXSfI8GDSgPlp64d6f2fNm08dk/bYGRPYJB3BKkB7PLse\"\n        \"B11W6IGr/nxxd9aSNYjiEbZ83+VmoCOp/14zNHNpZxRCSqJEa9lJ3aiD9RA1yfRkxOCLg\"\n        \"HKm22mUe3seM6NmOL9BPT6wDqiKmN0hDaPuMUAxmXkV21w3G4BCL4ECl+mbnVeTUhh4H1\"\n        \"+7x7QY4tli/QT2bQDGpe65z9Om0r0iH9d+nhMvQcofLgVQtqeSGM2rBBRRgTWwOWTh/NY\"\n        \"c6mqz3YYztFWOcla0Bmifyi1XTxiwX2EyN9TbJSEhUsC62qNQNX5IFQBTIq0QRfSN9r4Y\"\n        \"KXQLXfa1TWWLiYDRrQAcabSs3lmvFWd/OJISIalkxeq6I0TkWy1ALY/9bkpyynIeW7zPR\"\n        \"ZicnJwyq9/0zgpmkgnomMG55cjyceUvTi8zQRIs0gC2TR4FDcOi1ACwg3LklRhU83kXEt\"\n        \"gNTbxoDQD77ucYx0+ZjwDALp5Ey7nBmGHowYxUAVW+jIkx0dExcQubUl2Gx0RHR8fG7TR\"\n        \"VMJeZgY4ZnFJYSlIFTCp3u1EaLNJ2Y2p4E9sAYDsl69UYWwZ1ushHrOSHEwCMK3rKtIly\"\n        \"FABg30Q9WsUWPQPw21f3iEiBChAHhorb9Xrt3WpLhstHOr1eryxvQnkP5paxUQV1xX4er\"\n        \"/r+C8yDEAEBwLyYdv7VGLROdXXn1pvmuZ0CIA0PqO3OnugX3wwAzQnuZ5RCn2tA10pmvi\"\n        \"JSoAI08Yx8ALWjEw1FA2I1AI7xs3ox5XWj1poA0wJWyYMeTH77NM8CADWDwwgU0Tf2mcB\"\n        \"jAqUAUOJP4dzDOa/Q7xt/eYe65fl++zTaZW5OcXH2HGqMzHGAFpdf8rGnSCaPFKgAfEuP\"\n        \"Olt3fppbJnTz3NaV3s0cHFL3AuNbphzne+ThoxOBo568+DJncZIe+WNj8UzqJzYUMybmq\"\n        \"Z0w44PvA4Bll/sQMYwZXGbQIMYSRY/59jQ6neVFZ8y9B2g3env7McZchzxKqALQmcH1Cv\"\n        \"TxXqcFGhPofgEDwwuBCt9kM2CY41+G/ADGG8M9A/IgjZisBWxbvQrQtc+HFcT1TFYDD0V\"\n        \"uAeV/xHRdKujeppW5eTrAcvPA+vSCtt61pD63d8WqzMs6AOj8+Wjm6UdA58UiIwBYbny+\"\n        \"9fDVTgBoydm562Q9AFVumR2wluSp4LhzaPWarCoH9IVXzICj6rQMMF0/sGF/kR6A4252r\"\n        \"uqvzjNWp2eH+U/+kbYXd3b7S9IWy98YgDx2/k8wvwNEPGrBGochUwAAAABJRU5ErkJggg\"\n        \"==\"\n    )\n    return NO_SCREENSHOT\n"
  },
  {
    "path": "seleniumbase/core/jqc_helper.py",
    "content": "\"\"\"This module contains methods for opening jquery-confirm boxes.\nThese helper methods SHOULD NOT be called directly from tests.\"\"\"\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import js_utils\n\n\nform_code = \"\"\"'<form align=\"center\" action=\"\" class=\"jqc_form\">' +\n    '<div class=\"form-group\">' +\n    '<input style=\"font-size:20px; background-color: #f8fdfd; ' +\n    ' width: 84%%; border: 1px solid blue; ' +\n    ' box-shadow:inset 0 0 2px 2px #f4fafa;\"' +\n    ' type=\"text\" class=\"jqc_input\" />' +\n    '</div>' +\n    '</form>'\"\"\"\n\n\ndef jquery_confirm_button_dialog(driver, message, buttons, options=None):\n    js_utils.activate_jquery_confirm(driver)\n    # These defaults will be overwritten later if set\n    theme = constants.JqueryConfirm.DEFAULT_THEME\n    border_color = constants.JqueryConfirm.DEFAULT_COLOR\n    width = constants.JqueryConfirm.DEFAULT_WIDTH\n    if options:\n        for option in options:\n            if option[0].lower() == \"theme\":\n                theme = option[1]\n            elif option[0].lower() == \"color\":\n                border_color = option[1]\n            elif option[0].lower() == \"width\":\n                width = option[1]\n            else:\n                raise Exception('Unknown option: \"%s\"' % option[0])\n    if not message:\n        message = \"\"\n    key_row = \"\"\n    if len(buttons) == 1:  # There's only one button as an option\n        key_row = \"keys: ['enter', 'y', '1'],\"  # Shortcut: \"Enter\",\"Y\",\"1\"\n    b_html = \"\"\"button_%s: {\n        btnClass: 'btn-%s',\n        text: '<b>%s</b>',\n        %s\n        action: function(){\n            jqc_status = '%s';\n            $jqc_status = jqc_status;\n            jconfirm.lastButtonText = jqc_status;\n        }\n    },\"\"\"\n    all_buttons = \"\"\n    btn_count = 0\n    for button in buttons:\n        btn_count += 1\n        text = button[0]\n        text = js_utils.escape_quotes_if_needed(text)\n        if len(buttons) > 1 and text.lower() == \"yes\":\n            key_row = \"keys: ['y'],\"\n            if btn_count < 10:\n                key_row = \"keys: ['y', '%s'],\" % btn_count\n        elif len(buttons) > 1 and text.lower() == \"no\":\n            key_row = \"keys: ['n'],\"\n            if btn_count < 10:\n                key_row = \"keys: ['n', '%s'],\" % btn_count\n        elif len(buttons) > 1:\n            if btn_count < 10:\n                key_row = \"keys: ['%s'],\" % btn_count\n        color = button[1]\n        if not color:\n            color = \"blue\"\n        new_button = b_html % (btn_count, color, text, key_row, text)\n        all_buttons += new_button\n\n    content = '<div></div><font color=\"#0066ee\">%s</font>' % (message)\n    content = js_utils.escape_quotes_if_needed(content)\n    overlay_opacity = \"0.32\"\n    if theme.lower() == \"supervan\":\n        overlay_opacity = \"0.56\"\n    if theme.lower() == \"bootstrap\":\n        overlay_opacity = \"0.64\"\n    if theme.lower() == \"modern\":\n        overlay_opacity = \"0.5\"\n    if theme.lower() == \"material\":\n        overlay_opacity = \"0.4\"\n    jqcd = \"\"\"jconfirm({\n            boxWidth: '%s',\n            useBootstrap: false,\n            containerFluid: true,\n            bgOpacity: %s,\n            type: '%s',\n            theme: '%s',\n            animationBounce: 1,\n            typeAnimated: true,\n            animation: 'scale',\n            draggable: true,\n            dragWindowGap: 1,\n            container: 'body',\n            title: '%s',\n            content: '<div></div>',\n            buttons: {\n                %s\n            }\n        });\"\"\" % (\n        width,\n        overlay_opacity,\n        border_color,\n        theme,\n        content,\n        all_buttons,\n    )\n    driver.execute_script(jqcd)\n\n\ndef jquery_confirm_text_dialog(driver, message, button=None, options=None):\n    js_utils.activate_jquery_confirm(driver)\n    # These defaults will be overwritten later if set\n    theme = constants.JqueryConfirm.DEFAULT_THEME\n    border_color = constants.JqueryConfirm.DEFAULT_COLOR\n    width = constants.JqueryConfirm.DEFAULT_WIDTH\n\n    if not message:\n        message = \"\"\n    if button:\n        if not isinstance(button, (list, tuple)) or len(button) != 2:\n            raise Exception('\"button\" should be a (text, color) tuple!')\n    else:\n        button = (\"Submit\", \"blue\")\n    if options:\n        for option in options:\n            if option[0].lower() == \"theme\":\n                theme = option[1]\n            elif option[0].lower() == \"color\":\n                border_color = option[1]\n            elif option[0].lower() == \"width\":\n                width = option[1]\n            else:\n                raise Exception('Unknown option: \"%s\"' % option[0])\n    btn_text = button[0]\n    btn_color = button[1]\n    if not btn_color:\n        btn_color = \"blue\"\n    content = '<div></div><font color=\"#0066ee\">%s</font>' % (message)\n    content = js_utils.escape_quotes_if_needed(content)\n    overlay_opacity = \"0.32\"\n    if theme.lower() == \"supervan\":\n        overlay_opacity = \"0.56\"\n    if theme.lower() == \"bootstrap\":\n        overlay_opacity = \"0.64\"\n    if theme.lower() == \"modern\":\n        overlay_opacity = \"0.5\"\n    if theme.lower() == \"material\":\n        overlay_opacity = \"0.4\"\n    jqcd = \"\"\"jconfirm({\n            boxWidth: '%s',\n            useBootstrap: false,\n            containerFluid: true,\n            bgOpacity: %s,\n            type: '%s',\n            theme: '%s',\n            animationBounce: 1,\n            typeAnimated: true,\n            animation: 'scale',\n            draggable: true,\n            dragWindowGap: 1,\n            container: 'body',\n            title: '%s',\n            content: '<div></div>' +\n            %s,\n            buttons: {\n                formSubmit: {\n                btnClass: 'btn-%s',\n                text: '%s',\n                action: function () {\n                    jqc_input = this.$content.find('.jqc_input').val();\n                    $jqc_input = this.$content.find('.jqc_input').val();\n                    jconfirm.lastInputText = jqc_input;\n                    $jqc_status = '%s';  // There is only one button\n                },\n            },\n            },\n            onContentReady: function () {\n            var jc = this;\n            this.$content.find('form.jqc_form').on('submit', function (e) {\n                // User submits the form by pressing \"Enter\" in the field\n                e.preventDefault();\n                jc.$$formSubmit.trigger('click');  // Click the button\n            });\n            }\n        });\"\"\" % (\n        width,\n        overlay_opacity,\n        border_color,\n        theme,\n        content,\n        form_code,\n        btn_color,\n        btn_text,\n        btn_text,\n    )\n    driver.execute_script(jqcd)\n\n\ndef jquery_confirm_full_dialog(driver, message, buttons, options=None):\n    js_utils.activate_jquery_confirm(driver)\n    # These defaults will be overwritten later if set\n    theme = constants.JqueryConfirm.DEFAULT_THEME\n    border_color = constants.JqueryConfirm.DEFAULT_COLOR\n    width = constants.JqueryConfirm.DEFAULT_WIDTH\n\n    if not message:\n        message = \"\"\n    btn_count = 0\n    b_html = \"\"\"button_%s: {\n            btnClass: 'btn-%s',\n            text: '%s',\n            action: function(){\n            jqc_input = this.$content.find('.jqc_input').val();\n            $jqc_input = this.$content.find('.jqc_input').val();\n            jconfirm.lastInputText = jqc_input;\n            $jqc_status = '%s';\n            }\n        },\"\"\"\n    b1_html = \"\"\"formSubmit: {\n            btnClass: 'btn-%s',\n            text: '%s',\n            action: function(){\n            jqc_input = this.$content.find('.jqc_input').val();\n            $jqc_input = this.$content.find('.jqc_input').val();\n            jconfirm.lastInputText = jqc_input;\n            jqc_status = '%s';\n            $jqc_status = jqc_status;\n            jconfirm.lastButtonText = jqc_status;\n            }\n        },\"\"\"\n    one_button_trigger = \"\"\n    if len(buttons) == 1:\n        # If there's only one button, allow form submit with \"Enter/Return\"\n        one_button_trigger = \"jc.$$formSubmit.trigger('click');\"\n    all_buttons = \"\"\n    for button in buttons:\n        text = button[0]\n        text = js_utils.escape_quotes_if_needed(text)\n        color = button[1]\n        if not color:\n            color = \"blue\"\n        btn_count += 1\n        if len(buttons) == 1:\n            new_button = b1_html % (color, text, text)\n        else:\n            new_button = b_html % (btn_count, color, text, text)\n        all_buttons += new_button\n    if options:\n        for option in options:\n            if option[0].lower() == \"theme\":\n                theme = option[1]\n            elif option[0].lower() == \"color\":\n                border_color = option[1]\n            elif option[0].lower() == \"width\":\n                width = option[1]\n            else:\n                raise Exception('Unknown option: \"%s\"' % option[0])\n\n    content = '<div></div><font color=\"#0066ee\">%s</font>' % (message)\n    content = js_utils.escape_quotes_if_needed(content)\n    overlay_opacity = \"0.32\"\n    if theme.lower() == \"supervan\":\n        overlay_opacity = \"0.56\"\n    if theme.lower() == \"bootstrap\":\n        overlay_opacity = \"0.64\"\n    if theme.lower() == \"modern\":\n        overlay_opacity = \"0.5\"\n    if theme.lower() == \"material\":\n        overlay_opacity = \"0.4\"\n    jqcd = \"\"\"jconfirm({\n            boxWidth: '%s',\n            useBootstrap: false,\n            containerFluid: true,\n            bgOpacity: %s,\n            type: '%s',\n            theme: '%s',\n            animationBounce: 1,\n            typeAnimated: true,\n            animation: 'scale',\n            draggable: true,\n            dragWindowGap: 1,\n            container: 'body',\n            title: '%s',\n            content: '<div></div>' +\n            %s,\n            buttons: {\n                %s\n            },\n            onContentReady: function () {\n            var jc = this;\n            this.$content.find('form.jqc_form').on('submit', function (e) {\n                // User submits the form by pressing \"Enter\" in the field\n                e.preventDefault();\n                %s\n            });\n            }\n        });\"\"\" % (\n        width,\n        overlay_opacity,\n        border_color,\n        theme,\n        content,\n        form_code,\n        all_buttons,\n        one_button_trigger,\n    )\n    driver.execute_script(jqcd)\n"
  },
  {
    "path": "seleniumbase/core/log_helper.py",
    "content": "import os\nimport shutil\nimport sys\nimport time\nfrom contextlib import suppress\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import shared_utils\n\npython3_11_or_newer = False\nif sys.version_info >= (3, 11):\n    python3_11_or_newer = True\npy311_patch2 = constants.PatchPy311.PATCH\n\n\ndef __is_cdp_swap_needed(driver):\n    \"\"\"If the driver is disconnected, use a CDP method when available.\"\"\"\n    return shared_utils.is_cdp_swap_needed(driver)\n\n\ndef log_screenshot(test_logpath, driver, screenshot=None, get=False):\n    screenshot_name = settings.SCREENSHOT_NAME\n    screenshot_path = os.path.join(test_logpath, screenshot_name)\n    screenshot_skipped = constants.Warnings.SCREENSHOT_SKIPPED\n    screenshot_warning = constants.Warnings.SCREENSHOT_UNDEFINED\n    if (\n        getattr(sb_config, \"no_screenshot\", None)\n        or screenshot == screenshot_skipped\n    ):\n        if get:\n            return screenshot\n        return\n    try:\n        if not screenshot:\n            element = driver.find_element(\"tag name\", \"body\")\n            screenshot = element.screenshot_as_base64\n        if screenshot != screenshot_warning:\n            with open(screenshot_path, mode=\"wb\") as file:\n                file.write(screenshot)\n            with suppress(Exception):\n                shared_utils.make_writable(screenshot_path)\n        else:\n            print(\"WARNING: %s\" % screenshot_warning)\n        if get:\n            return screenshot\n    except Exception:\n        try:\n            driver.get_screenshot_as_file(screenshot_path)\n        except Exception:\n            print(\"WARNING: %s\" % screenshot_warning)\n\n\ndef get_master_time():\n    \"\"\"Returns (timestamp, the_date, the_time)\"\"\"\n    import datetime\n\n    timestamp = str(int(time.time())) + \"  (Unix Timestamp)\"\n    now = datetime.datetime.now()\n    utc_offset = -time.timezone / 3600.0\n    utc_offset += time.daylight\n    utc_str = \"UTC+0\"\n    if utc_offset > 0:\n        if utc_offset < 10:\n            utc_str = \"UTC+0%s\" % utc_offset\n        else:\n            utc_str = \"UTC+%s\" % utc_offset\n    elif utc_offset < 0:\n        if utc_offset > -10:\n            utc_str = \"UTC-0%s\" % abs(utc_offset)\n        else:\n            utc_str = \"UTC-%s\" % abs(utc_offset)\n    utc_str = utc_str.replace(\".5\", \".3\").replace(\".\", \":\") + \"0\"\n    time_zone = \"\"\n    try:\n        time_zone = \"(\" + time.tzname[time.daylight] + \", \" + utc_str + \")\"\n    except Exception:\n        time_zone = \"(\" + utc_str + \")\"\n    # Use [Day-of-Week, Month Day, Year] format when time zone < GMT/UTC-3\n    the_date = now.strftime(\"%A, %B %d, %Y\").replace(\" 0\", \" \")\n    if utc_offset >= -3:\n        # Use [Day-of-Week, Day Month Year] format when time zone >= GMT/UTC-3\n        the_date = now.strftime(\"%A, %d %B %Y\").replace(\" 0\", \" \")\n    the_time = now.strftime(\"%I:%M:%S %p  \") + time_zone\n    if the_time.startswith(\"0\"):\n        the_time = the_time[1:]\n    return timestamp, the_date, the_time\n\n\ndef get_browser_version(driver):\n    if (\n        python3_11_or_newer\n        and py311_patch2\n        and hasattr(sb_config, \"_browser_version\")\n    ):\n        return sb_config._browser_version\n    driver_capabilities = driver.capabilities\n    if \"version\" in driver_capabilities:\n        browser_version = driver_capabilities[\"version\"]\n    else:\n        browser_version = driver_capabilities[\"browserVersion\"]\n    return browser_version\n\n\ndef get_driver_name_and_version(driver, browser):\n    if hasattr(sb_config, \"_driver_name_version\"):\n        return sb_config._driver_name_version\n    if driver.capabilities[\"browserName\"].lower() == \"chrome\":\n        cap_dict = driver.capabilities[\"chrome\"]\n        return (\"chromedriver\", cap_dict[\"chromedriverVersion\"].split(\" \")[0])\n    elif driver.capabilities[\"browserName\"].lower() == \"msedge\":\n        cap_dict = driver.capabilities[\"msedge\"]\n        return (\"msedgedriver\", cap_dict[\"msedgedriverVersion\"].split(\" \")[0])\n    elif driver.capabilities[\"browserName\"].lower() == \"firefox\":\n        return (\"geckodriver\", driver.capabilities[\"moz:geckodriverVersion\"])\n    elif browser == \"safari\":\n        return (\"safaridriver\", get_browser_version(driver))\n    elif browser == \"ie\":\n        return (\"iedriver\", get_browser_version(driver))\n    else:\n        return None\n\n\ndef log_test_failure_data(test, test_logpath, driver, browser, url=None):\n    import traceback\n\n    browser_displayed = browser\n    driver_displayed = None\n    browser_version = None\n    driver_version = None\n    driver_name = None\n    duration = None\n    exc_message = None\n    try:\n        browser_version = get_browser_version(driver)\n    except Exception:\n        pass\n    try:\n        driver_name, driver_version = get_driver_name_and_version(\n            driver, browser\n        )\n    except Exception:\n        pass\n    try:\n        duration = \"%.2fs\" % (time.time() - (sb_config.start_time_ms / 1000.0))\n    except Exception:\n        duration = \"(Unknown Duration)\"\n    if browser_version:\n        headless = \"\"\n        if test.headless and browser in [\"chrome\", \"edge\", \"firefox\"]:\n            headless = \" / headless\"\n        if test.headless2 and browser in [\"chrome\", \"edge\"]:\n            headless = \" / headless2\"\n        if browser and len(browser) > 1:\n            # Capitalize the first letter\n            browser = \"%s%s\" % (browser[0].upper(), browser[1:])\n        browser_displayed = \"%s %s%s\" % (browser, browser_version, headless)\n        if driver_name and driver_version:\n            driver_displayed = \"%s %s\" % (driver_name, driver_version)\n    else:\n        browser_displayed = browser\n        driver_displayed = \"(Unknown Driver)\"\n    if not driver_displayed:\n        driver_displayed = \"(Unknown Driver)\"\n    basic_info_name = settings.BASIC_INFO_NAME\n    basic_file_path = os.path.join(test_logpath, basic_info_name)\n    if url:\n        last_page = url\n    else:\n        last_page = get_last_page(driver)\n    sb_config._fail_page = last_page\n    timestamp, the_date, the_time = get_master_time()\n    test_id = get_test_id(test)  # pytest runnable display_id (with the \"::\")\n    data_to_save = []\n    data_to_save.append(\"%s\" % test_id)\n    data_to_save.append(\n        \"--------------------------------------------------------------------\"\n    )\n    data_to_save.append(\"Last Page: %s\" % last_page)\n    data_to_save.append(\" Duration: %s\" % duration)\n    data_to_save.append(\"  Browser: %s\" % browser_displayed)\n    data_to_save.append(\"   Driver: %s\" % driver_displayed)\n    data_to_save.append(\"Timestamp: %s\" % timestamp)\n    data_to_save.append(\"     Date: %s\" % the_date)\n    data_to_save.append(\"     Time: %s\" % the_time)\n    data_to_save.append(\n        \"--------------------------------------------------------------------\"\n    )\n    if hasattr(test, \"_outcome\") and getattr(test._outcome, \"errors\", None):\n        try:\n            exc_message = test._outcome.errors[-1][1][1]\n            traceback_address = test._outcome.errors[-1][1][2]\n            traceback_list = traceback.format_list(\n                traceback.extract_tb(traceback_address)[1:]\n            )\n            updated_list = []\n            counter = 0\n            for traceback_item in traceback_list:\n                if \"self._callTestMethod(testMethod)\" in traceback_item:\n                    counter = 1\n                    updated_list.append(traceback_item)  # In case not cleared\n                    continue\n                elif (\n                    \", in _callTestMethod\" in traceback_item.strip()\n                    and \"method()\" in traceback_item.strip()\n                    and counter == 1\n                ):\n                    counter = 0\n                    updated_list = []\n                    continue\n                else:\n                    counter = 0\n                    updated_list.append(traceback_item)\n            traceback_list = updated_list\n            traceback_message = \"\".join(traceback_list).strip()\n        except Exception:\n            exc_message = \"(Unknown Exception)\"\n            traceback_message = \"(Unknown Traceback)\"\n        traceback_message = str(traceback_message).strip()\n        data_to_save.append(\"Traceback:\\n  %s\" % traceback_message)\n        data_to_save.append(\"Exception: %s\" % exc_message)\n    else:\n        traceback_message = None\n        if getattr(test, \"is_behave\", None):\n            if sb_config.behave_scenario.status.name == \"failed\":\n                if (\n                    hasattr(sb_config, \"behave_step\")\n                    and getattr(sb_config.behave_step, \"error_message\", None)\n                ):\n                    traceback_message = sb_config.behave_step.error_message\n        else:\n            format_exception = traceback.format_exception(\n                sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]\n            )\n            if format_exception:\n                updated_list = []\n                for line in format_exception:\n                    if \"sb_manager.py\" in line and \"yield sb\" in line:\n                        continue\n                    updated_list.append(line)\n                format_exception = updated_list\n            traceback_message = \"\".join(format_exception)\n        if (\n            not traceback_message\n            or len(str(traceback_message)) < 30\n            or traceback_message.endswith(\"StopIteration\\n\")\n        ):\n            good_stack = []\n            the_stacks = []\n            if hasattr(sys, \"last_traceback\"):\n                the_stacks = traceback.format_list(\n                    traceback.extract_tb(sys.last_traceback)\n                )\n            elif hasattr(sb_config, \"_excinfo_tb\"):\n                the_stacks = traceback.format_list(\n                    traceback.extract_tb(sb_config._excinfo_tb)\n                )\n            else:\n                message = None\n                if getattr(test, \"is_behave\", None):\n                    message = \"Behave step was not implemented or skipped!\"\n                else:\n                    message = \"Traceback not found!\"\n                the_stacks = [message]\n            for stack in the_stacks:\n                if \"/site-packages/pluggy/\" not in stack:\n                    if \"/site-packages/_pytest/\" not in stack:\n                        good_stack.append(stack)\n            traceback_message = str(\"\".join(good_stack)).strip()\n            data_to_save.append(\"Traceback:\\n  %s\" % traceback_message)\n            if hasattr(sys, \"last_value\"):\n                last_value = sys.last_value\n                if last_value:\n                    data_to_save.append(\"Exception: %s\" % last_value)\n            elif hasattr(sb_config, \"_excinfo_value\"):\n                data_to_save.append(\"Exception: %s\" % sb_config._excinfo_value)\n        else:\n            data_to_save.append(\"Traceback:\\n  %s\" % traceback_message)\n    if getattr(test, \"is_nosetest\", None):\n        # Also save the data for the report\n        sb_config._report_test_id = test_id\n        sb_config._report_fail_page = last_page\n        sb_config._report_duration = duration\n        sb_config._report_browser = browser_displayed\n        sb_config._report_driver = driver_displayed\n        sb_config._report_timestamp = timestamp\n        sb_config._report_date = the_date\n        sb_config._report_time = the_time\n        sb_config._report_traceback = traceback_message\n        sb_config._report_exception = exc_message\n    if not os.path.exists(test_logpath):\n        with suppress(Exception):\n            os.makedirs(test_logpath)\n    with suppress(Exception):\n        log_file = open(basic_file_path, mode=\"w+\", encoding=\"utf-8\")\n        log_file.writelines(\"\\r\\n\".join(data_to_save))\n        log_file.close()\n        shared_utils.make_writable(basic_file_path)\n\n\ndef log_skipped_test_data(test, test_logpath, driver, browser, reason):\n    browser_displayed = browser\n    driver_displayed = None\n    browser_version = None\n    driver_version = None\n    driver_name = None\n    with suppress(Exception):\n        browser_version = get_browser_version(driver)\n    with suppress(Exception):\n        driver_name, driver_version = get_driver_name_and_version(\n            driver, browser\n        )\n    if browser_version:\n        headless = \"\"\n        if test.headless and browser in [\"chrome\", \"edge\", \"firefox\"]:\n            headless = \" / headless\"\n        if test.headless2 and browser in [\"chrome\", \"edge\"]:\n            headless = \" / headless2\"\n        if browser and len(browser) > 1:\n            # Capitalize the first letter\n            browser = \"%s%s\" % (browser[0].upper(), browser[1:])\n        browser_displayed = \"%s %s%s\" % (browser, browser_version, headless)\n        if driver_name and driver_version:\n            driver_displayed = \"%s %s\" % (driver_name, driver_version)\n    else:\n        browser_displayed = browser\n        driver_displayed = \"(Unknown Driver)\"\n    if not driver_displayed:\n        driver_displayed = \"(Unknown Driver)\"\n    timestamp, the_date, the_time = get_master_time()\n    test_id = get_test_id(test)  # pytest runnable display_id (with the \"::\")\n    data_to_save = []\n    data_to_save.append(\"%s\" % test_id)\n    data_to_save.append(\n        \"--------------------------------------------------------------------\"\n    )\n    data_to_save.append(\"       Outcome: SKIPPED\")\n    data_to_save.append(\"       Browser: %s\" % browser_displayed)\n    data_to_save.append(\"        Driver: %s\" % driver_displayed)\n    data_to_save.append(\"     Timestamp: %s\" % timestamp)\n    data_to_save.append(\"          Date: %s\" % the_date)\n    data_to_save.append(\"          Time: %s\" % the_time)\n    data_to_save.append(\n        \"--------------------------------------------------------------------\"\n    )\n    data_to_save.append(\" * Skip Reason: %s\" % reason)\n    data_to_save.append(\"\")\n    file_path = os.path.join(test_logpath, \"skip_reason.txt\")\n    with suppress(Exception):\n        log_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        log_file.writelines(\"\\r\\n\".join(data_to_save))\n        log_file.close()\n        shared_utils.make_writable(file_path)\n\n\ndef log_page_source(test_logpath, driver, source=None):\n    html_file_name = settings.PAGE_SOURCE_NAME\n    if source:\n        page_source = source\n    else:\n        try:\n            page_source = None\n            if __is_cdp_swap_needed(driver):\n                page_source = driver.cdp.get_page_source()\n            else:\n                page_source = driver.page_source\n            page_source = get_html_source_with_base_href(driver, page_source)\n        except Exception:\n            source = constants.Warnings.PAGE_SOURCE_UNDEFINED\n            page_source = constants.Warnings.PAGE_SOURCE_UNDEFINED\n    if source == constants.Warnings.PAGE_SOURCE_UNDEFINED:\n        page_source = (\n            \"<h3>Warning: \"\n            + source\n            + (\n                \"</h3>\\n<h4>The browser window was either unreachable, \"\n                \"unresponsive, or closed prematurely!</h4>\"\n            )\n        )\n    if not os.path.exists(test_logpath):\n        with suppress(Exception):\n            os.makedirs(test_logpath)\n    html_file_path = os.path.join(test_logpath, html_file_name)\n    with suppress(Exception):\n        html_file = open(html_file_path, mode=\"w+\", encoding=\"utf-8\")\n        html_file.write(page_source)\n        html_file.close()\n        shared_utils.make_writable(html_file_path)\n\n\ndef get_test_id(test):\n    if getattr(test, \"is_behave\", None):\n        file_name = sb_config.behave_scenario.filename\n        line_num = sb_config.behave_line_num\n        scenario_name = sb_config.behave_scenario.name\n        if \" -- @\" in scenario_name:\n            scenario_name = scenario_name.split(\" -- @\")[0]\n        test_id = \"%s:%s => %s\" % (file_name, line_num, scenario_name)\n        return test_id\n    elif getattr(test, \"is_context_manager\", None):\n        filename = test.__class__.__module__.split(\".\")[-1] + \".py\"\n        classname = test.__class__.__name__\n        methodname = test._testMethodName\n        context_id = None\n        if filename == \"base_case.py\" or methodname == \"runTest\":\n            import traceback\n\n            stack_base = traceback.format_stack()[0].split(\", in \")[0]\n            test_base = stack_base.split(\", in \")[0].split(os.sep)[-1]\n            if getattr(test, \"cm_filename\", None):\n                filename = test.cm_filename\n            else:\n                filename = test_base.split('\"')[0]\n            classname = \"SB\"\n            methodname = \".py:\" + test_base.split(\", line \")[-1]\n            context_id = filename.split(\".\")[0] + methodname + \":\" + classname\n            return context_id\n    test_id = None\n    try:\n        test_id = get_test_name(test)\n    except Exception:\n        test_id = \"%s.%s.%s\" % (\n            test.__class__.__module__,\n            test.__class__.__name__,\n            test._testMethodName,\n        )\n        if test._sb_test_identifier and len(str(test._sb_test_identifier)) > 6:\n            test_id = test._sb_test_identifier\n    return test_id\n\n\ndef get_test_name(test):\n    if \"PYTEST_CURRENT_TEST\" in os.environ:\n        full_name = os.environ[\"PYTEST_CURRENT_TEST\"]\n        if \"] \" in full_name:\n            test_name = full_name.split(\"] \")[0] + \"]\"\n        else:\n            test_name = full_name.split(\" \")[0]\n    elif test.is_pytest:\n        test_name = \"%s.py::%s::%s\" % (\n            test.__class__.__module__.split(\".\")[-1],\n            test.__class__.__name__,\n            test._testMethodName,\n        )\n    else:\n        test_name = \"%s.py:%s.%s\" % (\n            test.__class__.__module__.split(\".\")[-1],\n            test.__class__.__name__,\n            test._testMethodName,\n        )\n    if test._sb_test_identifier and len(str(test._sb_test_identifier)) > 6:\n        test_name = test._sb_test_identifier\n    return test_name\n\n\ndef get_last_page(driver):\n    try:\n        last_page = None\n        if __is_cdp_swap_needed(driver):\n            last_page = driver.cdp.get_current_url()\n        else:\n            last_page = driver.current_url\n    except Exception:\n        last_page = \"[WARNING! Browser Not Open!]\"\n    if len(last_page) < 5:\n        last_page = \"[WARNING! Browser Not Open!]\"\n    return last_page\n\n\ndef get_base_url(full_url):\n    protocol = full_url.split(\"://\")[0]\n    simple_url = full_url.split(\"://\")[1]\n    base_url = simple_url.split(\"/\")[0]\n    full_base_url = \"%s://%s\" % (protocol, base_url)\n    return full_base_url\n\n\ndef get_base_href_html(full_url):\n    \"\"\"The base href line tells the html what the base page really is.\n    This is important when trying to open the page outside it's home.\"\"\"\n    base_url = get_base_url(full_url)\n    return '<base href=\"%s\">' % base_url\n\n\ndef get_html_source_with_base_href(driver, page_source):\n    \"\"\"Combines the domain base href with the html source.\n    Also adds on the meta charset, which may get dropped.\n    This is needed for the page html to render correctly.\"\"\"\n    last_page = get_last_page(driver)\n    meta_charset = '<meta charset=\"utf-8\">'\n    if \"://\" in last_page:\n        base_href_html = get_base_href_html(last_page)\n        if ' charset=\"' not in page_source:\n            return \"%s\\n%s\\n%s\" % (base_href_html, meta_charset, page_source)\n        else:\n            return \"%s\\n%s\" % (base_href_html, page_source)\n    return \"\"\n\n\ndef copytree(src, dst, symlinks=False, ignore=None):\n    if not os.path.exists(dst):\n        os.makedirs(dst)\n    for item in os.listdir(src):\n        s = os.path.join(src, item)\n        d = os.path.join(dst, item)\n        if os.path.isdir(s):\n            copytree(s, d, symlinks, ignore)\n        else:\n            if not os.path.exists(d) or (\n                os.stat(s).st_mtime - os.stat(d).st_mtime > 1\n            ):\n                shutil.copy2(s, d)\n\n\ndef archive_logs_if_set(log_path, archive_logs=False):\n    \"\"\"Handle Logging\"\"\"\n    arg_join = \" \".join(sys.argv)\n    if (\"-n\" in sys.argv) or (\"-n=\" in arg_join) or (arg_join == \"-c\"):\n        return  # Skip if multithreaded\n    if log_path.endswith(\"/\"):\n        log_path = log_path[:-1]\n    if not os.path.exists(log_path):\n        try:\n            os.makedirs(log_path)\n        except Exception:\n            pass  # Only reachable during multi-threaded runs\n    else:\n        if settings.ARCHIVE_EXISTING_LOGS or archive_logs:\n            if len(os.listdir(log_path)) > 0:\n                saved_folder = \"%s/../%s/\" % (log_path, constants.Logs.SAVED)\n                archived_folder = os.path.realpath(saved_folder) + \"/\"\n                log_path = os.path.realpath(log_path) + \"/\"\n                if not os.path.exists(archived_folder):\n                    try:\n                        os.makedirs(archived_folder)\n                    except Exception:\n                        pass  # Only reachable during multi-threaded runs\n                time_id = str(int(time.time()))\n                archived_logs = \"%slogs_%s\" % (archived_folder, time_id)\n                copytree(log_path, archived_logs)\n\n\ndef log_folder_setup(log_path, archive_logs=False):\n    \"\"\"Clean up logs to prepare for another run\"\"\"\n    if log_path.endswith(\"/\"):\n        log_path = log_path[:-1]\n    if log_path.startswith(\"/\"):\n        log_path = log_path[1:]\n    if constants.Logs.SAVED.endswith(\"/\"):\n        constants.Logs.SAVED = constants.Logs.SAVED[:-1]\n    if constants.Logs.SAVED.startswith(\"/\"):\n        constants.Logs.SAVED = constants.Logs.SAVED[1:]\n    if len(log_path) < 10 or len(constants.Logs.SAVED) < 10:\n        return  # Prevent accidental deletions if constants are renamed\n    if not os.path.exists(log_path):\n        try:\n            os.makedirs(log_path)\n        except Exception:\n            pass  # Only reachable during multi-threaded runs\n    else:\n        saved_folder = \"%s/../%s/\" % (log_path, constants.Logs.SAVED)\n        archived_folder = os.path.realpath(saved_folder) + \"/\"\n        if not os.path.exists(archived_folder):\n            try:\n                os.makedirs(archived_folder)\n            except Exception:\n                pass  # Only reachable during multi-threaded runs\n        archived_logs = \"%slogs_%s\" % (archived_folder, int(time.time()))\n        if len(os.listdir(log_path)) > 0:\n            try:\n                shutil.move(log_path, archived_logs)\n                os.makedirs(log_path)\n            except Exception:\n                pass  # A file was probably open at the time\n            if not settings.ARCHIVE_EXISTING_LOGS and not archive_logs:\n                shutil.rmtree(archived_logs)\n            else:\n                a_join = \" \".join(sys.argv)\n                if (\"-n\" in sys.argv) or (\"-n=\" in a_join) or (a_join == \"-c\"):\n                    # Logs are saved/archived now if tests are multithreaded\n                    pass\n                else:\n                    shutil.rmtree(archived_logs)  # (Archive test run later)\n\n\ndef clear_empty_logs():\n    latest_logs_dir = os.path.join(os.getcwd(), constants.Logs.LATEST) + os.sep\n    archived_folder = os.path.join(os.getcwd(), constants.Logs.SAVED) + os.sep\n    if os.path.exists(latest_logs_dir) and not os.listdir(latest_logs_dir):\n        try:\n            os.rmdir(latest_logs_dir)\n        except OSError:\n            pass\n    if os.path.exists(archived_folder) and not os.listdir(archived_folder):\n        try:\n            os.rmdir(archived_folder)\n        except OSError:\n            pass\n"
  },
  {
    "path": "seleniumbase/core/mysql.py",
    "content": "\"\"\"Wrapper for MySQL DB functions\"\"\"\n\n\nclass DatabaseManager:\n    \"\"\"This class wraps MySQL database methods for easy use.\"\"\"\n\n    def __init__(self, database_env=\"test\", conf_creds=None):\n        \"\"\"Create a connection to the MySQL DB.\"\"\"\n        import fasteners\n        import time\n        from seleniumbase import config as sb_config\n        from seleniumbase.config import settings\n        from seleniumbase.core import settings_parser\n        from seleniumbase.fixtures import constants\n        from seleniumbase.fixtures import shared_utils\n\n        pip_find_lock = fasteners.InterProcessLock(\n            constants.PipInstall.FINDLOCK\n        )\n        with pip_find_lock:\n            try:\n                import cryptography  # noqa: F401\n                import pymysql\n            except Exception:\n                shared_utils.pip_install(\"PyMySQL[rsa]\", version=\"1.1.1\")\n                import pymysql\n        db_server = settings.DB_HOST\n        db_port = settings.DB_PORT\n        db_user = settings.DB_USERNAME\n        db_pass = settings.DB_PASSWORD\n        db_schema = settings.DB_SCHEMA\n        if getattr(sb_config, \"settings_file\", None):\n            override = settings_parser.set_settings(sb_config.settings_file)\n            if \"DB_HOST\" in override.keys():\n                db_server = override[\"DB_HOST\"]\n            if \"DB_PORT\" in override.keys():\n                db_port = override[\"DB_PORT\"]\n            if \"DB_USERNAME\" in override.keys():\n                db_user = override[\"DB_USERNAME\"]\n            if \"DB_PASSWORD\" in override.keys():\n                db_pass = override[\"DB_PASSWORD\"]\n            if \"DB_SCHEMA\" in override.keys():\n                db_schema = override[\"DB_SCHEMA\"]\n        retry_count = 3\n        backoff = 1.2  # Time to wait (in seconds) between retries.\n        count = 0\n        while count < retry_count:\n            try:\n                self.conn = pymysql.connect(\n                    host=db_server,\n                    port=db_port,\n                    user=db_user,\n                    password=db_pass,\n                    database=db_schema,\n                )\n                self.conn.autocommit(True)\n                self.cursor = self.conn.cursor()\n                return\n            except Exception:\n                time.sleep(backoff)\n                count = count + 1\n                if retry_count == 3:\n                    print(\"Unable to connect to Database after 3 retries.\")\n                    raise\n\n    def query_fetch_all(self, query, values):\n        \"\"\"Execute db query, get all the values, and close the connection.\"\"\"\n        self.cursor.execute(query, values)\n        retval = self.cursor.fetchall()\n        self.__close_db()\n        return retval\n\n    def query_fetch_one(self, query, values):\n        \"\"\"Execute db query, get the first value, and close the connection.\"\"\"\n        self.cursor.execute(query, values)\n        retval = self.cursor.fetchone()\n        self.__close_db()\n        return retval\n\n    def execute_query(self, query, values):\n        \"\"\"Execute db query, close the connection, and return the results.\"\"\"\n        retval = self.cursor.execute(query, values)\n        self.__close_db()\n        return retval\n\n    def __close_db(self):\n        self.cursor.close()\n        self.conn.close()\n"
  },
  {
    "path": "seleniumbase/core/proxy_helper.py",
    "content": "import os\nimport re\nimport warnings\nimport zipfile\nfrom contextlib import suppress\nfrom seleniumbase.config import proxy_list\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import page_utils\nfrom seleniumbase.fixtures import shared_utils\n\nDOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER\nPROXY_ZIP_PATH = os.path.join(DOWNLOADS_DIR, \"proxy.zip\")\nPROXY_ZIP_LOCK = os.path.join(DOWNLOADS_DIR, \"proxy.lock\")\nPROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, \"proxy_ext_dir\")\nPROXY_DIR_LOCK = os.path.join(DOWNLOADS_DIR, \"proxy_dir.lock\")\n\n\ndef create_proxy_ext(\n    proxy_string,\n    proxy_user,\n    proxy_pass,\n    proxy_scheme=\"http\",\n    bypass_list=None,\n    zip_it=True,\n):\n    \"\"\"Implementation of https://stackoverflow.com/a/35293284 for\n    https://stackoverflow.com/questions/12848327/\n    (Run Selenium on a proxy server that requires authentication.)\n    Solution involves creating & adding a Chromium extension at runtime.\n    CHROMIUM-ONLY! *** Only Chrome and Edge browsers are supported. ***\n    \"\"\"\n    background_js = None\n    if not bypass_list:\n        bypass_list = \"\"\n    if proxy_string:\n        proxy_protocol = \"\"\n        if proxy_string.count(\"://\") == 1:\n            proxy_protocol = proxy_string.split(\"://\")[0] + \"://\"\n            proxy_string = proxy_string.split(\"://\")[1]\n        proxy_host = proxy_protocol + proxy_string.split(\":\")[0]\n        proxy_port = proxy_string.split(\":\")[1]\n        background_js = (\n            \"\"\"var config = {\\n\"\"\"\n            \"\"\"    mode: \"fixed_servers\",\\n\"\"\"\n            \"\"\"    rules: {\\n\"\"\"\n            \"\"\"      singleProxy: {\\n\"\"\"\n            \"\"\"        scheme: \"%s\",\\n\"\"\"\n            \"\"\"        host: \"%s\",\\n\"\"\"\n            \"\"\"        port: parseInt(\"%s\")\\n\"\"\"\n            \"\"\"      },\\n\"\"\"\n            \"\"\"    bypassList: [\"%s\"]\\n\"\"\"\n            \"\"\"    }\\n\"\"\"\n            \"\"\"  };\\n\"\"\"\n            \"\"\"chrome.proxy.settings.set(\"\"\"\n            \"\"\"{value: config, scope: \"regular\"}, function() {\"\"\"\n            \"\"\"});\\n\"\"\"\n            \"\"\"function callbackFn(details) {\\n\"\"\"\n            \"\"\"    return {\\n\"\"\"\n            \"\"\"        authCredentials: {\\n\"\"\"\n            \"\"\"            username: \"%s\",\\n\"\"\"\n            \"\"\"            password: \"%s\"\\n\"\"\"\n            \"\"\"        }\\n\"\"\"\n            \"\"\"    };\\n\"\"\"\n            \"\"\"}\\n\"\"\"\n            \"\"\"chrome.webRequest.onAuthRequired.addListener(\\n\"\"\"\n            \"\"\"        callbackFn,\\n\"\"\"\n            \"\"\"        {urls: [\"<all_urls>\"]},\\n\"\"\"\n            \"\"\"        ['blocking']\\n\"\"\"\n            \"\"\");\"\"\" % (\n                proxy_scheme,\n                proxy_host,\n                proxy_port,\n                bypass_list,\n                proxy_user,\n                proxy_pass,\n            )\n        )\n    else:\n        background_js = (\n            \"\"\"var config = {\\n\"\"\"\n            \"\"\"    mode: \"fixed_servers\",\\n\"\"\"\n            \"\"\"    rules: {\\n\"\"\"\n            \"\"\"    },\\n\"\"\"\n            \"\"\"    bypassList: [\"%s\"]\\n\"\"\"\n            \"\"\"  };\\n\"\"\"\n            \"\"\"chrome.proxy.settings.set(\"\"\"\n            \"\"\"{value: config, scope: \"regular\"}, function() {\"\"\"\n            \"\"\"});\\n\"\"\"\n            \"\"\"function callbackFn(details) {\\n\"\"\"\n            \"\"\"    return {\\n\"\"\"\n            \"\"\"        authCredentials: {\\n\"\"\"\n            \"\"\"            username: \"%s\",\\n\"\"\"\n            \"\"\"            password: \"%s\"\\n\"\"\"\n            \"\"\"        }\\n\"\"\"\n            \"\"\"    };\\n\"\"\"\n            \"\"\"}\\n\"\"\"\n            \"\"\"chrome.webRequest.onAuthRequired.addListener(\\n\"\"\"\n            \"\"\"        callbackFn,\\n\"\"\"\n            \"\"\"        {urls: [\"<all_urls>\"]},\\n\"\"\"\n            \"\"\"        ['blocking']\\n\"\"\"\n            \"\"\");\"\"\" % (bypass_list, proxy_user, proxy_pass)\n        )\n    manifest_json = (\n        \"\"\"{\\n\"\"\"\n        \"\"\"\"version\": \"1.0.0\",\\n\"\"\"\n        \"\"\"\"manifest_version\": 3,\\n\"\"\"\n        \"\"\"\"name\": \"Chrome Proxy\",\\n\"\"\"\n        \"\"\"\"permissions\": [\\n\"\"\"\n        \"\"\"    \"proxy\",\\n\"\"\"\n        \"\"\"    \"tabs\",\\n\"\"\"\n        \"\"\"    \"unlimitedStorage\",\\n\"\"\"\n        \"\"\"    \"storage\",\\n\"\"\"\n        \"\"\"    \"webRequest\",\\n\"\"\"\n        \"\"\"    \"webRequestAuthProvider\"\\n\"\"\"\n        \"\"\"],\\n\"\"\"\n        \"\"\"\"host_permissions\": [\\n\"\"\"\n        \"\"\"    \"<all_urls>\"\\n\"\"\"\n        \"\"\"],\\n\"\"\"\n        \"\"\"\"background\": {\\n\"\"\"\n        \"\"\"    \"service_worker\": \"background.js\"\\n\"\"\"\n        \"\"\"},\\n\"\"\"\n        \"\"\"\"minimum_chrome_version\":\"88.0.0\"\\n\"\"\"\n        \"\"\"}\"\"\"\n    )\n    abs_path = os.path.abspath(\".\")\n    downloads_path = os.path.join(abs_path, DOWNLOADS_DIR)\n    if not os.path.exists(downloads_path):\n        os.mkdir(downloads_path)\n    if zip_it:\n        zf = zipfile.ZipFile(PROXY_ZIP_PATH, mode=\"w\")\n        zf.writestr(\"background.js\", background_js)\n        zf.writestr(\"manifest.json\", manifest_json)\n        zf.close()\n        with suppress(Exception):\n            shared_utils.make_writable(PROXY_ZIP_PATH)\n    else:\n        proxy_ext_dir = PROXY_DIR_PATH\n        if not os.path.exists(proxy_ext_dir):\n            os.mkdir(proxy_ext_dir)\n        with suppress(Exception):\n            shared_utils.make_writable(proxy_ext_dir)\n        manifest_file = os.path.join(proxy_ext_dir, \"manifest.json\")\n        with open(manifest_file, mode=\"w\") as f:\n            f.write(manifest_json)\n        with suppress(Exception):\n            shared_utils.make_writable(manifest_json)\n        proxy_host = proxy_string.split(\":\")[0]\n        proxy_port = proxy_string.split(\":\")[1]\n        background_file = os.path.join(proxy_ext_dir, \"background.js\")\n        with open(background_file, mode=\"w\") as f:\n            f.write(background_js)\n        with suppress(Exception):\n            shared_utils.make_writable(background_js)\n\n\ndef remove_proxy_zip_if_present():\n    \"\"\"Remove Chromium extension zip file used for proxy server authentication.\n    Used in the implementation of https://stackoverflow.com/a/35293284\n    for https://stackoverflow.com/questions/12848327/\n    \"\"\"\n    if os.path.exists(PROXY_ZIP_PATH):\n        with suppress(Exception):\n            os.remove(PROXY_ZIP_PATH)\n    if os.path.exists(PROXY_ZIP_LOCK):\n        with suppress(Exception):\n            os.remove(PROXY_ZIP_LOCK)\n\n\ndef validate_proxy_string(proxy_string, keep_scheme=False):\n    if proxy_string in proxy_list.PROXY_LIST.keys():\n        proxy_string = proxy_list.PROXY_LIST[proxy_string]\n        if not proxy_string:\n            return None\n    proxy_scheme = \"http\"\n    if proxy_string.startswith(\"https://\"):\n        proxy_scheme = \"https\"\n    elif proxy_string.startswith(\"socks4://\"):\n        proxy_scheme = \"socks4\"\n    elif proxy_string.startswith(\"socks5://\"):\n        proxy_scheme = \"socks5\"\n    valid = False\n    val_ip = re.match(\n        r\"^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d+$\", proxy_string\n    )\n    if not val_ip:\n        if proxy_string.startswith(\"http://\"):\n            proxy_string = proxy_string.split(\"http://\")[1]\n        elif proxy_string.startswith(\"https://\"):\n            proxy_string = proxy_string.split(\"https://\")[1]\n        elif \"://\" in proxy_string:\n            if not proxy_string.startswith(\"socks4://\") and not (\n                proxy_string.startswith(\"socks5://\")\n            ):\n                proxy_string = proxy_string.split(\"://\")[1]\n        chunks = proxy_string.split(\":\")\n        if len(chunks) == 2:\n            if re.match(r\"^\\d+$\", chunks[1]):\n                if page_utils.is_valid_url(\"http://\" + proxy_string):\n                    valid = True\n        elif len(chunks) == 3:\n            if re.match(r\"^\\d+$\", chunks[2]):\n                if page_utils.is_valid_url(\"http:\" + \":\".join(chunks[1:])):\n                    if chunks[0] == \"http\":\n                        valid = True\n                    elif chunks[0] == \"https\":\n                        valid = True\n                    elif chunks[0] == \"socks4\":\n                        valid = True\n                    elif chunks[0] == \"socks5\":\n                        valid = True\n    else:\n        proxy_string = val_ip.group()\n        valid = True\n    if not valid:\n        __display_proxy_warning(proxy_string)\n        proxy_string = None\n    if keep_scheme:\n        return (proxy_string, proxy_scheme)\n    return proxy_string\n\n\ndef __display_proxy_warning(proxy_string):\n    message = (\n        '\\nWARNING: Proxy String [\"%s\"] is NOT in the expected '\n        '\"ip_address:port\" or \"server:port\" format, '\n        \"(OR the key does not exist in \"\n        \"seleniumbase.config.proxy_list.PROXY_LIST).\" % proxy_string\n    )\n    if settings.RAISE_INVALID_PROXY_STRING_EXCEPTION:\n        raise Exception(message)\n    else:\n        message += \" *** DEFAULTING to NOT USING a Proxy Server! ***\"\n        warnings.simplefilter(\"always\", Warning)  # See Warnings\n        warnings.warn(message, category=Warning, stacklevel=2)\n        warnings.simplefilter(\"default\", Warning)  # Set Default\n"
  },
  {
    "path": "seleniumbase/core/recorder_helper.py",
    "content": "\"\"\"Generating SeleniumBase Python code from the Recorder\"\"\"\n\n\ndef generate_sbase_code(srt_actions):\n    sb_actions = []\n    for action in srt_actions:\n        if action[0] == \"begin\" or action[0] == \"_url_\":\n            if \"%\" in action[2]:\n                try:\n                    from urllib.parse import unquote\n\n                    action[2] = unquote(action[2], errors=\"strict\")\n                except Exception:\n                    pass\n            if '\"' not in action[2]:\n                sb_actions.append('self.open(\"%s\")' % action[2])\n            elif \"'\" not in action[2]:\n                sb_actions.append(\"self.open('%s')\" % action[2])\n            else:\n                sb_actions.append(\n                    'self.open(\"%s\")' % action[2].replace('\"', '\\\\\"')\n                )\n        elif action[0] == \"f_url\":\n            if \"%\" in action[2]:\n                try:\n                    from urllib.parse import unquote\n\n                    action[2] = unquote(action[2], errors=\"strict\")\n                except Exception:\n                    pass\n            if '\"' not in action[2]:\n                sb_actions.append('self.open_if_not_url(\"%s\")' % action[2])\n            elif \"'\" not in action[2]:\n                sb_actions.append(\"self.open_if_not_url('%s')\" % action[2])\n            else:\n                sb_actions.append(\n                    'self.open_if_not_url(\"%s\")'\n                    % action[2].replace('\"', '\\\\\"')\n                )\n        elif action[0] == \"click\":\n            method = \"click\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"dbclk\":\n            method = \"double_click\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"js_cl\":\n            method = \"js_click\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"js_ca\":\n            method = \"js_click_all\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"jq_cl\":\n            method = \"jquery_click\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"jq_ca\":\n            method = \"jquery_click_all\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"r_clk\":\n            method = \"context_click\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"canva\":\n            method = \"click_with_offset\"\n            selector = action[1][0]\n            p_x = action[1][1]\n            p_y = action[1][2]\n            if '\"' not in selector:\n                sb_actions.append(\n                    'self.%s(\"%s\", %s, %s)' % (method, selector, p_x, p_y)\n                )\n            else:\n                sb_actions.append(\n                    \"self.%s('%s', %s, %s)\" % (method, selector, p_x, p_y)\n                )\n        elif (\n            action[0] == \"input\"\n            or action[0] == \"js_ty\"\n            or action[0] == \"jq_ty\"\n            or action[0] == \"pkeys\"\n        ):\n            method = \"type\"\n            if action[0] == \"js_ty\":\n                method = \"js_type\"\n            elif action[0] == \"jq_ty\":\n                method = \"jquery_type\"\n            elif action[0] == \"pkeys\":\n                method = \"press_keys\"\n            text = action[2].replace(\"\\n\", \"\\\\n\")\n            if '\"' not in action[1] and '\"' not in text:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], text)\n                )\n            elif '\"' not in action[1] and '\"' in text:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')' % (method, action[1], text)\n                )\n            elif '\"' in action[1] and '\"' not in text:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")' % (method, action[1], text)\n                )\n            elif '\"' in action[1] and '\"' in text:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], text)\n                )\n        elif action[0] == \"hover\":\n            method = \"hover\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"e_mfa\":\n            method = \"enter_mfa_code\"\n            text = action[2].replace(\"\\n\", \"\\\\n\")\n            if '\"' not in action[1] and '\"' not in text:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], text)\n                )\n            elif '\"' not in action[1] and '\"' in text:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')' % (method, action[1], text)\n                )\n            elif '\"' in action[1] and '\"' not in text:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")' % (method, action[1], text)\n                )\n            elif '\"' in action[1] and '\"' in text:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], text)\n                )\n        elif action[0] == \"h_clk\":\n            method = \"hover_and_click\"\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], action[2])\n                )\n        elif action[0] == \"ddrop\":\n            method = \"drag_and_drop\"\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], action[2])\n                )\n        elif action[0] == \"s_opt\":\n            method = \"select_option_by_text\"\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], action[2])\n                )\n        elif action[0] == \"set_v\":\n            method = \"set_value\"\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], action[2])\n                )\n        elif action[0] == \"cho_f\":\n            method = \"choose_file\"\n            action[2] = action[2].replace(\"\\\\\", \"\\\\\\\\\")\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], action[2])\n                )\n        elif action[0] == \"sw_fr\":\n            method = \"switch_to_frame\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"sw_dc\":\n            sb_actions.append(\"self.switch_to_default_content()\")\n        elif action[0] == \"sw_pf\":\n            sb_actions.append(\"self.switch_to_parent_frame()\")\n        elif action[0] == \"s_c_f\":\n            method = \"set_content_to_frame\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"s_c_d\":\n            method = \"set_content_to_default\"\n            nested = action[1]\n            if nested:\n                method = \"set_content_to_parent\"\n                sb_actions.append(\"self.%s()\" % method)\n            else:\n                sb_actions.append(\"self.%s()\" % method)\n        elif action[0] == \"sleep\":\n            method = \"sleep\"\n            sb_actions.append(\"self.%s(%s)\" % (method, action[1]))\n        elif action[0] == \"wf_el\":\n            method = \"wait_for_element\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"as_el\":\n            method = \"assert_element\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"as_ep\":\n            method = \"assert_element_present\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"asenv\":\n            method = \"assert_element_not_visible\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"s_at_\" or action[0] == \"s_ats\":\n            method = \"set_attribute\"\n            if action[0] == \"s_ats\":\n                method = \"set_attributes\"\n            if '\"' not in action[1][0]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\", \"%s\")'\n                    % (method, action[1][0], action[1][1], action[1][2])\n                )\n            elif \"'\" not in action[1][0]:\n                sb_actions.append(\n                    \"self.%s('%s', \\\"%s\\\", \\\"%s\\\")\"\n                    % (method, action[1][0], action[1][1], action[1][2])\n                )\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\", \"%s\", \"%s\")'\n                    % (method, action[1][0], action[1][1], action[1][2])\n                )\n        elif action[0] == \"acc_a\":\n            sb_actions.append(\"self.accept_alert()\")\n        elif action[0] == \"dis_a\":\n            sb_actions.append(\"self.dismiss_alert()\")\n        elif action[0] == \"hi_li\":\n            method = \"highlight\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"as_lt\":\n            method = \"assert_link_text\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"as_ti\":\n            method = \"assert_title\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"as_tc\":\n            method = \"assert_title_contains\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"a_url\":\n            method = \"assert_url\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"a_u_c\":\n            method = \"assert_url_contains\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"as_df\":\n            method = \"assert_downloaded_file\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"do_fi\":\n            method = \"download_file\"\n            file_url = action[1][0]\n            dest = action[1][1]\n            if not dest:\n                sb_actions.append('self.%s(\"%s\")' % (method, file_url))\n            else:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, file_url, dest)\n                )\n        elif action[0] == \"as_at\":\n            method = \"assert_attribute\"\n            if ('\"' not in action[1][0]) and action[1][2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\", \"%s\")'\n                    % (method, action[1][0], action[1][1], action[1][2])\n                )\n            elif ('\"' not in action[1][0]) and not action[1][2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")'\n                    % (method, action[1][0], action[1][1])\n                )\n            elif ('\"' in action[1][0]) and action[1][2]:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\", \"%s\")'\n                    % (method, action[1][0], action[1][1], action[1][2])\n                )\n            else:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")'\n                    % (method, action[1][0], action[1][1])\n                )\n        elif (\n            action[0] == \"as_te\"\n            or action[0] == \"as_et\"\n            or action[0] == \"astnv\"\n            or action[0] == \"aetnv\"\n            or action[0] == \"da_te\"\n            or action[0] == \"da_et\"\n        ):\n            import unicodedata\n\n            text_list = False\n            try:\n                action[1][0] = unicodedata.normalize(\"NFKC\", action[1][0])\n                action[1][0] = action[1][0].replace(\"\\n\", \"\\\\n\")\n                action[1][0] = action[1][0].replace(\"\\u00B6\", \"\")\n            except Exception:\n                text_list = True\n\n            method = \"assert_text\"\n            if action[0] == \"as_et\":\n                method = \"assert_exact_text\"\n            elif action[0] == \"astnv\":\n                method = \"assert_text_not_visible\"\n            elif action[0] == \"aetnv\":\n                method = \"assert_exact_text_not_visible\"\n            elif action[0] == \"da_te\":\n                method = \"deferred_assert_text\"\n            elif action[0] == \"da_et\":\n                method = \"deferred_assert_exact_text\"\n            if action[1][1] != \"html\":\n                if text_list and '\"' not in action[1][1]:\n                    sb_actions.append(\n                        'self.%s(%s, \"%s\")'\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif text_list and \"'\" not in action[1][1]:\n                    sb_actions.append(\n                        \"self.%s(%s, '%s')\"\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif '\"' not in action[1][0] and '\"' not in action[1][1]:\n                    sb_actions.append(\n                        'self.%s(\"%s\", \"%s\")'\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif '\"' not in action[1][0] and '\"' in action[1][1]:\n                    sb_actions.append(\n                        'self.%s(\"%s\", \\'%s\\')'\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif '\"' in action[1] and '\"' not in action[1][1]:\n                    sb_actions.append(\n                        'self.%s(\\'%s\\', \"%s\")'\n                        % (method, action[1][0], action[1][1])\n                    )\n                elif '\"' in action[1] and '\"' in action[1][1]:\n                    sb_actions.append(\n                        \"self.%s('%s', '%s')\"\n                        % (method, action[1][0], action[1][1])\n                    )\n            else:\n                if text_list:\n                    sb_actions.append(\n                        'self.%s(%s)' % (method, action[1][0])\n                    )\n                elif '\"' not in action[1][0]:\n                    sb_actions.append(\n                        'self.%s(\"%s\")' % (method, action[1][0])\n                    )\n                else:\n                    sb_actions.append(\n                        \"self.%s('%s')\" % (method, action[1][0])\n                    )\n        elif action[0] == \"asnet\":\n            method = \"assert_non_empty_text\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"da_el\":\n            method = \"deferred_assert_element\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"da_ep\":\n            method = \"deferred_assert_element_present\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"danet\":\n            method = \"deferred_assert_non_empty_text\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            elif \"'\" not in action[1]:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n            else:\n                sb_actions.append(\n                    'self.%s(\"\"\"%s\"\"\")' % (method, action[1])\n                )\n        elif action[0] == \"s_scr\":\n            method = \"save_screenshot\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n        elif action[0] == \"ss_tf\":\n            method = \"save_screenshot\"\n            action[2] = action[1][1]\n            action[1] = action[1][0]\n            if '\"' not in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \"%s\")' % (method, action[1], action[2])\n                )\n            elif '\"' not in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    'self.%s(\"%s\", \\'%s\\')'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' not in action[2]:\n                sb_actions.append(\n                    'self.%s(\\'%s\\', \"%s\")'\n                    % (method, action[1], action[2])\n                )\n            elif '\"' in action[1] and '\"' in action[2]:\n                sb_actions.append(\n                    \"self.%s('%s', '%s')\" % (method, action[1], action[2])\n                )\n        elif action[0] == \"ss_tl\":\n            method = \"save_screenshot_to_logs\"\n            sb_actions.append(\"self.%s()\" % method)\n        elif action[0] == \"pdftl\":\n            method = \"save_as_pdf_to_logs\"\n            sb_actions.append(\"self.%s()\" % method)\n        elif action[0] == \"spstl\":\n            method = \"save_page_source_to_logs\"\n            sb_actions.append(\"self.%s()\" % method)\n        elif action[0] == \"sh_fc\":\n            method = \"show_file_choosers\"\n            sb_actions.append(\"self.%s()\" % method)\n        elif action[0] == \"pr_da\":\n            sb_actions.append(\"self.process_deferred_asserts()\")\n        elif action[0] == \"a_d_m\":\n            sb_actions.append(\"self.activate_demo_mode()\")\n        elif action[0] == \"d_d_m\":\n            sb_actions.append(\"self.deactivate_demo_mode()\")\n        elif action[0] == \"c_l_s\":\n            sb_actions.append(\"self.clear_local_storage()\")\n        elif action[0] == \"c_s_s\":\n            sb_actions.append(\"self.clear_session_storage()\")\n        elif action[0] == \"d_a_c\":\n            sb_actions.append(\"self.delete_all_cookies()\")\n        elif action[0] == \"go_bk\":\n            sb_actions.append(\"self.go_back()\")\n        elif action[0] == \"go_fw\":\n            sb_actions.append(\"self.go_forward()\")\n        elif action[0] == \"c_box\":\n            method = \"check_if_unchecked\"\n            if action[2] == \"no\":\n                method = \"uncheck_if_checked\"\n            if '\"' not in action[1]:\n                sb_actions.append('self.%s(\"%s\")' % (method, action[1]))\n            else:\n                sb_actions.append(\"self.%s('%s')\" % (method, action[1]))\n    return sb_actions\n"
  },
  {
    "path": "seleniumbase/core/report_helper.py",
    "content": "import os\nimport shutil\nimport sys\nimport time\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.core.style_sheet import get_report_style\n\nLATEST_REPORT_DIR = settings.LATEST_REPORT_DIR\nARCHIVE_DIR = settings.REPORT_ARCHIVE_DIR\nHTML_REPORT = settings.HTML_REPORT\nRESULTS_TABLE = settings.RESULTS_TABLE\n\n\ndef get_timestamp():\n    return str(int(time.time() * 1000))\n\n\ndef process_successes(test, test_count, duration):\n    return '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"' % (\n        test_count,\n        \"Passed!\",\n        \"*\",\n        \"*\",\n        \"*\",\n        test.browser,\n        get_timestamp()[:-3],\n        duration,\n        test.id(),\n        \"*\",\n    )\n\n\ndef save_test_failure_data(test, name, folder=None):\n    \"\"\"\n    Saves failure data to the current directory, or to a subfolder if provided.\n    If {name} does not end in \".txt\", it will get added to it.\n    If the folder provided doesn't exist, it will get created.\n    \"\"\"\n    if not name.endswith(\".txt\"):\n        name = name + \".txt\"\n    if folder:\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, folder)\n        if not os.path.exists(file_path):\n            os.makedirs(file_path)\n        failure_data_file_path = os.path.join(file_path, name)\n    else:\n        failure_data_file_path = name\n    failure_data_file = open(\n        failure_data_file_path, mode=\"w+\", encoding=\"utf-8\"\n    )\n    data_to_save = []\n    if not hasattr(sb_config, \"_report_test_id\"):\n        exc_message = \"(Unknown Exception)\"\n        traceback_message = \"\"\n        if hasattr(sb_config, \"_report_traceback\"):\n            traceback_message = str(sb_config._report_traceback)\n        if hasattr(sb_config, \"_report_exception\"):\n            if type(sb_config._report_exception) is tuple:\n                exc_message = str(sb_config._report_exception[1].message)\n            else:\n                exc_message = str(sb_config._report_exception)\n        data_to_save.append(test.id())\n        data_to_save.append(\n            \"----------------------------------------------------------------\"\n        )\n        data_to_save.append(\"Last Page: %s\" % test._last_page_url)\n        data_to_save.append(\"  Browser: %s\" % test.browser)\n        data_to_save.append(\"Timestamp: %s\" % get_timestamp()[:-3])\n        data_to_save.append(\n            \"----------------------------------------------------------------\"\n        )\n        data_to_save.append(\"Traceback: %s\" % traceback_message)\n        data_to_save.append(\"Exception: %s\" % exc_message)\n        failure_data_file.writelines(\"\\r\\n\".join(data_to_save))\n        failure_data_file.close()\n        return\n    data_to_save.append(sb_config._report_test_id)\n    data_to_save.append(\n        \"--------------------------------------------------------------------\"\n    )\n    data_to_save.append(\"Last Page: %s\" % sb_config._report_fail_page)\n    data_to_save.append(\" Duration: %s\" % sb_config._report_duration)\n    data_to_save.append(\"  Browser: %s\" % sb_config._report_browser)\n    data_to_save.append(\"   Driver: %s\" % sb_config._report_driver)\n    data_to_save.append(\"Timestamp: %s\" % sb_config._report_timestamp)\n    data_to_save.append(\"     Date: %s\" % sb_config._report_date)\n    data_to_save.append(\"     Time: %s\" % sb_config._report_time)\n    data_to_save.append(\n        \"--------------------------------------------------------------------\"\n    )\n    data_to_save.append(\"Traceback: %s\" % sb_config._report_traceback)\n    data_to_save.append(\"Exception: %s\" % sb_config._report_exception)\n    failure_data_file.writelines(\"\\r\\n\".join(data_to_save))\n    failure_data_file.close()\n\n\ndef process_failures(test, test_count, duration):\n    bad_page_image = \"failure_%s.png\" % test_count\n    bad_page_data = \"failure_%s.txt\" % test_count\n    screenshot_path = os.path.join(LATEST_REPORT_DIR, bad_page_image)\n    if getattr(test, \"_last_page_screenshot\", None):\n        with open(screenshot_path, mode=\"wb\") as file:\n            file.write(test._last_page_screenshot)\n    save_test_failure_data(test, bad_page_data, folder=LATEST_REPORT_DIR)\n    exc_message = None\n    if hasattr(test, \"_outcome\") and getattr(test._outcome, \"errors\", None):\n        try:\n            exc_message = test._outcome.errors[-1][1][1]\n        except Exception:\n            exc_message = \"(Unknown Exception)\"\n    else:\n        try:\n            exc_message = sys.last_value\n        except Exception:\n            exc_message = \"(Unknown Exception)\"\n    if not hasattr(test, \"_last_page_url\"):\n        test._last_page_url = \"about:blank\"\n    return '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"' % (\n        test_count,\n        \"FAILED!\",\n        bad_page_data,\n        bad_page_image,\n        test._last_page_url,\n        test.browser,\n        get_timestamp()[:-3],\n        duration,\n        test.id(),\n        exc_message,\n    )\n\n\ndef clear_out_old_report_logs(archive_past_runs=True, get_log_folder=False):\n    abs_path = os.path.abspath(\".\")\n    file_path = os.path.join(abs_path, LATEST_REPORT_DIR)\n    if not os.path.exists(file_path):\n        try:\n            os.makedirs(file_path)\n        except Exception:\n            pass  # Should only be reachable during multi-threaded runs\n\n    if archive_past_runs:\n        archive_timestamp = int(time.time())\n        archive_dir_root = os.path.join(file_path, \"..\", ARCHIVE_DIR)\n        if not os.path.exists(archive_dir_root):\n            os.makedirs(archive_dir_root)\n        archive_dir = os.path.join(\n            archive_dir_root, \"report_%s\" % archive_timestamp\n        )\n        shutil.move(file_path, archive_dir)\n        os.makedirs(file_path)\n        if get_log_folder:\n            return archive_dir\n    else:\n        # Just delete bad pages to make room for the latest run.\n        filelist = [\n            f\n            for f in os.listdir(os.path.join(\".\", LATEST_REPORT_DIR))\n            if f.startswith(\"failure_\")\n            or (f == HTML_REPORT)\n            or (f.startswith(\"automation_failure\"))\n            or (f == RESULTS_TABLE)\n        ]\n        for f in filelist:\n            os.remove(os.path.join(file_path, f))\n\n\ndef add_bad_page_log_file(page_results_list):\n    abs_path = os.path.abspath(\".\")\n    file_path = os.path.join(abs_path, LATEST_REPORT_DIR)\n    log_file = os.path.join(file_path, RESULTS_TABLE)\n    f = open(log_file, mode=\"w\")\n    h_p1 = '\"Num\",\"Result\",\"Stacktrace\",\"Screenshot\",'\n    h_p2 = '\"URL\",\"Browser\",\"Epoch Time\",\"Duration\",'\n    h_p3 = '\"Test Case Address\",\"Additional Info\"\\n'\n    page_header = h_p1 + h_p2 + h_p3\n    f.write(page_header)\n    for line in page_results_list:\n        f.write(\"%s\\n\" % line)\n    f.close()\n\n\ndef archive_new_report_logs():\n    log_string = clear_out_old_report_logs(get_log_folder=True)\n    log_folder = log_string.split(\"/\")[-1]\n    abs_path = os.path.abspath(\".\")\n    file_path = os.path.join(abs_path, ARCHIVE_DIR)\n    report_log_path = os.path.join(file_path, log_folder)\n    return report_log_path\n\n\ndef add_results_page(html):\n    abs_path = os.path.abspath(\".\")\n    file_path = os.path.join(abs_path, LATEST_REPORT_DIR)\n    results_file_name = HTML_REPORT\n    results_file = os.path.join(file_path, results_file_name)\n    f = open(results_file, mode=\"w\")\n    f.write(html)\n    f.close()\n    return results_file\n\n\ndef build_report(\n    report_log_path,\n    page_results_list,\n    successes,\n    failures,\n    browser_type,\n    show_report,\n):\n\n    web_log_path = \"file://%s\" % report_log_path\n    successes_count = len(successes)\n    failures_count = len(failures)\n    total_test_count = successes_count + failures_count\n\n    tf_color = \"#11BB11\"\n    if failures_count > 0:\n        tf_color = \"#EE3A3A\"\n\n    summary_table = \"\"\"<div><table><thead><tr>\n        <th>TESTING SUMMARY</th>\n        <th>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>\n        </tr></thead><tbody>\n        <tr style=\"color:#00BB00\"><td>TESTS PASSING: <td>%s</tr>\n        <tr style=\"color:%s\"     ><td>TESTS FAILING: <td>%s</tr>\n        <tr style=\"color:#4D4DDD\"><td>TOTAL TESTS: <td>%s</tr>\n        </tbody></table>\"\"\" % (\n        successes_count,\n        tf_color,\n        failures_count,\n        total_test_count,\n    )\n\n    summary_table = (\n        \"\"\"<h1 id=\"ContextHeader\" class=\"sectionHeader\" title=\"\">\n        %s</h1>\"\"\"\n        % summary_table\n    )\n\n    log_link_shown = os.path.join(\n        \"..\", \"%s%s\" % (ARCHIVE_DIR, web_log_path.split(ARCHIVE_DIR)[1])\n    )\n    csv_link = os.path.join(web_log_path, RESULTS_TABLE)\n    csv_link_shown = \"%s\" % RESULTS_TABLE\n    log_table = \"\"\"<p><p><p><p><h2><table><tbody>\n        <tr><td>LOG FILES LINK:&nbsp;&nbsp;<td><a href=\"%s\">%s</a></tr>\n        <tr><td>RESULTS TABLE:&nbsp;&nbsp;<td><a href=\"%s\">%s</a></tr>\n        </tbody></table></h2><p><p><p><p>\"\"\" % (\n        web_log_path,\n        log_link_shown,\n        csv_link,\n        csv_link_shown,\n    )\n\n    failure_table = \"<h2><table><tbody></div>\"\n    any_screenshots = False\n    for line in page_results_list:\n        line = line.split(\",\")\n        if line[1] == '\"FAILED!\"':\n            if not any_screenshots:\n                any_screenshots = True\n                failure_table += \"\"\"<thead><tr>\n                    <th>STACKTRACE&nbsp;&nbsp;</th>\n                    <th>SCREENSHOT&nbsp;&nbsp;</th>\n                    <th>LOCATION OF FAILURE</th>\n                    </tr></thead>\"\"\"\n            display_url = line[4]\n            actual_url = line[4]\n            if len(display_url) < 7:\n                display_url = sb_config._report_fail_page\n                actual_url = sb_config._report_fail_page\n            if len(display_url) > 60:\n                display_url = display_url[0:58] + \"...\"\n            line = (\n                '<a href=\"%s\">%s</a>'\n                % (\"file://\" + report_log_path + \"/\" + line[2], line[2])\n                + \"\"\"\n                &nbsp;&nbsp;\n                \"\"\"\n                + '<td><a href=\"%s\">%s</a>'\n                % (\"file://\" + report_log_path + \"/\" + line[3], line[3])\n                + \"\"\"\n                &nbsp;&nbsp;\n                \"\"\"\n                + '<td><a href=\"%s\">%s</a>' % (actual_url, display_url)\n            )\n            line = line.replace('\"', \"\")\n            failure_table += \"<tr><td>%s</tr>\\n\" % line\n    failure_table += \"</tbody></table></h2>\"\n\n    failing_list = \"\"\n    if failures:\n        failing_list = \"<h2><table><tbody>\"\n        failing_list += \"\"\"<thead><tr><th>LIST OF FAILING TESTS\n                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\n                        </th></tr></thead>\"\"\"\n        for failure in failures:\n            failing_list += '<tr style=\"color:#EE3A3A\"><td>%s</tr>\\n' % failure\n        failing_list += \"</tbody></table></h2>\"\n\n    passing_list = \"\"\n    if successes:\n        passing_list = \"<h2><table><tbody>\"\n        passing_list += \"\"\"<thead><tr><th>LIST OF PASSING TESTS\n                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\n                        </th></tr></thead>\"\"\"\n        for success in successes:\n            passing_list += '<tr style=\"color:#00BB00\"><td>%s</tr>\\n' % success\n        passing_list += \"</tbody></table></h2>\"\n\n    table_view = \"%s%s%s%s%s\" % (\n        summary_table,\n        log_table,\n        failure_table,\n        failing_list,\n        passing_list,\n    )\n    report_html = \"<html><head>%s</head><body>%s</body></html>\" % (\n        get_report_style(),\n        table_view,\n    )\n    results_file = add_results_page(report_html)\n    archived_results_file = report_log_path + \"/\" + HTML_REPORT\n    shutil.copyfile(results_file, archived_results_file)\n    print(\"\\n* The latest html report page is located at:\\n\" + results_file)\n    print(\n        \"\\n* Files saved for this report are located at:\\n\" + report_log_path\n    )\n    print(\"\")\n    if show_report:\n        from seleniumbase import get_driver\n\n        driver = get_driver(browser_type, headless=False)\n        driver.get(\"file://%s\" % archived_results_file)\n        print(\"\\n*** Close the html report window to continue. ***\")\n        while len(driver.window_handles):\n            time.sleep(0.1)\n        driver.quit()\n"
  },
  {
    "path": "seleniumbase/core/s3_manager.py",
    "content": "\"\"\"Methods for uploading/managing files on Amazon S3.\"\"\"\n\nalready_uploaded_files = []\n\n\nclass S3LoggingBucket(object):\n    \"\"\"A class for uploading log files from tests to Amazon S3.\n    Those files can then be shared easily.\"\"\"\n    from seleniumbase.config import settings\n\n    def __init__(\n        self,\n        log_bucket=settings.S3_LOG_BUCKET,\n        bucket_url=settings.S3_BUCKET_URL,\n        selenium_access_key=settings.S3_SELENIUM_ACCESS_KEY,\n        selenium_secret_key=settings.S3_SELENIUM_SECRET_KEY,\n    ):\n        import fasteners\n        from seleniumbase.fixtures import constants\n        from seleniumbase.fixtures import shared_utils\n\n        pip_find_lock = fasteners.InterProcessLock(\n            constants.PipInstall.FINDLOCK\n        )\n        with pip_find_lock:\n            try:\n                import boto3\n            except Exception:\n                shared_utils.pip_install(\"boto3\")\n                import boto3\n        self.conn = boto3.Session(\n            aws_access_key_id=selenium_access_key,\n            aws_secret_access_key=selenium_secret_key,\n        )\n        self.bucket = log_bucket\n        self.bucket_url = bucket_url\n\n    def get_key(self, file_name):\n        \"\"\"Create a new S3 connection instance with the given name.\"\"\"\n        return self.conn.resource(\"s3\").Object(self.bucket, file_name)\n\n    def get_bucket(self):\n        \"\"\"Return the bucket being used.\"\"\"\n        return self.bucket\n\n    def upload_file(self, file_name, file_path):\n        \"\"\"Upload a given file from the file_path to the bucket\n        with the new name/path file_name.\"\"\"\n        upload_key = self.get_key(file_name)\n        content_type = \"text/plain\"\n        if file_name.endswith(\".html\"):\n            content_type = \"text/html\"\n        elif file_name.endswith(\".jpg\"):\n            content_type = \"image/jpeg\"\n        elif file_name.endswith(\".png\"):\n            content_type = \"image/png\"\n        upload_key.Bucket().upload_file(\n            file_path,\n            file_name,\n            ExtraArgs={\"ACL\": \"public-read\", \"ContentType\": content_type},\n        )\n\n    def upload_index_file(\n        self, test_address, timestamp, data_path, save_data_to_logs\n    ):\n        \"\"\"Create an index.html file with links to all the log files\n        that were just uploaded.\"\"\"\n        import os\n\n        global already_uploaded_files\n        already_uploaded_files = list(set(already_uploaded_files))\n        already_uploaded_files.sort()\n        file_name = \"%s/%s/index.html\" % (test_address, timestamp)\n        index = self.get_key(file_name)\n        index_str = []\n        for completed_file in already_uploaded_files:\n            index_str.append(\n                \"<a href='\" + self.bucket_url + \"\"\n                \"%s'>%s</a>\" % (completed_file, completed_file)\n            )\n        index_page = str(\"<br>\".join(index_str))\n        save_data_to_logs(index_page, \"index.html\")\n        file_path = os.path.join(data_path, \"index.html\")\n        index.Bucket().upload_file(\n            file_path,\n            file_name,\n            ExtraArgs={\"ACL\": \"public-read\", \"ContentType\": \"text/html\"},\n        )\n        return \"%s%s\" % (self.bucket_url, file_name)\n\n    def save_uploaded_file_names(self, files):\n        \"\"\"Keep a record of all file names that have been uploaded.\n        Upload log files related to each test after its execution.\n        Once done, use already_uploaded_files to create an index file.\"\"\"\n        global already_uploaded_files  # noqa\n        already_uploaded_files.extend(files)\n"
  },
  {
    "path": "seleniumbase/core/sb_cdp.py",
    "content": "\"\"\"Add CDP methods to extend the driver\"\"\"\nimport asyncio\nimport fasteners\nimport mycdp\nimport os\nimport random\nimport re\nimport sys\nimport time\nfrom contextlib import suppress\nfrom filelock import FileLock\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import js_utils\nfrom seleniumbase.fixtures import page_utils\nfrom seleniumbase.fixtures import shared_utils\nfrom seleniumbase.undetected.cdp_driver import cdp_util\nfrom seleniumbase.undetected.cdp_driver import tab as cdp_tab\n\n\nclass CDPMethods():\n    def __init__(self, loop, page, driver):\n        self.loop = loop\n        self.page = page\n        self.driver = driver\n\n    def _swap_driver(self, driver):\n        self.driver = driver\n        self.page = driver.cdp.page\n        self.loop = driver.cdp.loop\n\n    def __slow_mode_pause_if_set(self):\n        if (\n            (hasattr(sb_config, \"demo_mode\") and sb_config.demo_mode)\n            or \"--demo\" in sys.argv\n        ):\n            time.sleep(0.48)\n        elif (\n            (hasattr(sb_config, \"slow_mode\") and sb_config.slow_mode)\n            or \"--slow\" in sys.argv\n        ):\n            time.sleep(0.24)\n\n    def __add_light_pause(self):\n        time.sleep(0.007)\n\n    def __convert_to_css_if_xpath(self, selector):\n        if page_utils.is_xpath_selector(selector):\n            with suppress(Exception):\n                css = js_utils.convert_to_css_selector(selector, \"xpath\")\n                if css:\n                    return css\n        return selector\n\n    def __add_sync_methods(self, element):\n        if not element:\n            return element\n        element.clear_input = lambda: self.__clear_input(element)\n        element.click = lambda: self.__click(element)\n        element.flash = lambda *args, **kwargs: self.__flash(\n            element, *args, **kwargs\n        )\n        element.focus = lambda: self.__focus(element)\n        element.gui_click = (\n            lambda *args, **kwargs: self.__gui_click(element, *args, **kwargs)\n        )\n        element.highlight_overlay = lambda: self.__highlight_overlay(element)\n        element.mouse_click = lambda: self.__mouse_click(element)\n        element.click_with_offset = (\n            lambda *args, **kwargs: self.__mouse_click_with_offset_async(\n                element, *args, **kwargs\n            )\n        )\n        element.mouse_drag = (\n            lambda destination: self.__mouse_drag(element, destination)\n        )\n        element.mouse_move = lambda: self.__mouse_move(element)\n        element.press_keys = lambda text: self.__press_keys(element, text)\n        element.query_selector = (\n            lambda selector: self.__query_selector(element, selector)\n        )\n        element.querySelector = element.query_selector\n        element.query_selector_all = (\n            lambda selector: self.__query_selector_all(element, selector)\n        )\n        element.querySelectorAll = element.query_selector_all\n        element.remove_from_dom = lambda: self.__remove_from_dom(element)\n        element.save_screenshot = (\n            lambda *args, **kwargs: self.__save_screenshot(\n                element, *args, **kwargs)\n        )\n        element.save_to_dom = lambda: self.__save_to_dom(element)\n        element.scroll_into_view = lambda: self.__scroll_into_view(element)\n        element.select_option = lambda: self.__select_option(element)\n        element.send_file = (\n            lambda *file_paths: self.__send_file(element, *file_paths)\n        )\n        element.send_keys = lambda text: self.__send_keys(element, text)\n        element.set_text = lambda value: self.__set_text(element, value)\n        element.set_value = lambda value: self.__set_value(element, value)\n        element.type = lambda text: self.__type(element, text)\n        element.get_position = lambda: self.__get_position(element)\n        element.get_html = lambda: self.__get_html(element)\n        element.get_js_attributes = lambda: self.__get_js_attributes(element)\n        element.get_attribute = (\n            lambda attribute: self.__get_attribute(element, attribute)\n        )\n        # element.get_parent() should come last\n        element.get_parent = lambda: self.__get_parent(element)\n        return element\n\n    def get(self, url, **kwargs):\n        url = shared_utils.fix_url_as_needed(url)\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        load_timeout = 60.0\n        wait_timeout = 30.0\n        if hasattr(sb_config, \"_cdp_proxy\") and sb_config._cdp_proxy:\n            load_timeout = 90.0\n            wait_timeout = 45.0\n        try:\n            task = self.page.get(url, **kwargs)\n            self.loop.run_until_complete(\n                asyncio.wait_for(task, timeout=load_timeout)\n            )\n        except asyncio.TimeoutError:\n            print(\"Timeout loading %s\" % url)\n        url_protocol = url.split(\":\")[0]\n        safe_url = True\n        if url_protocol not in [\"about\", \"data\", \"chrome\"]:\n            safe_url = False\n        if not safe_url:\n            time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)\n            if shared_utils.is_windows():\n                time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)\n        else:\n            time.sleep(0.012)\n        self.__slow_mode_pause_if_set()\n        try:\n            self.loop.run_until_complete(\n                asyncio.wait_for(self.page.wait(), timeout=wait_timeout)\n            )\n        except asyncio.TimeoutError:\n            pass\n        except Exception:\n            pass\n\n    def open(self, url, **kwargs):\n        self.get(url, **kwargs)\n\n    def reload(self, ignore_cache=True, script_to_evaluate_on_load=None):\n        self.loop.run_until_complete(\n            self.page.reload(\n                ignore_cache=ignore_cache,\n                script_to_evaluate_on_load=script_to_evaluate_on_load,\n            )\n        )\n\n    def refresh(self, *args, **kwargs):\n        self.reload(*args, **kwargs)\n\n    def get_event_loop(self):\n        return self.loop\n\n    def get_rd_host(self):\n        \"\"\"Returns the remote-debugging host (likely 127.0.0.1)\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return driver.config.host\n\n    def get_rd_port(self):\n        \"\"\"Returns the remote-debugging port (commonly 9222)\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return driver.config.port\n\n    def get_rd_url(self):\n        \"\"\"Returns the remote-debugging URL, which is used for\n        allowing the Playwright integration to launch stealthy,\n        and also applies nest-asyncio for nested event loops so\n        that SeleniumBase methods can be called from Playwright\n        without encountering event loop error messages such as:\n        Cannot run the event loop while another loop is running.\n        Also sets an environment variable to hide this warning:\n        Deprecation: \"url.parse() behavior is not standardized\".\n        (github.com/microsoft/playwright-python/issues/3016)\"\"\"\n        import nest_asyncio\n        nest_asyncio.apply()\n        os.environ[\"NODE_NO_WARNINGS\"] = \"1\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        host = driver.config.host\n        port = driver.config.port\n        return f\"http://{host}:{port}\"\n\n    def get_endpoint_url(self):\n        \"\"\"Same as get_rd_url(), which returns the remote-debugging URL.\"\"\"\n        return self.get_rd_url()\n\n    def get_port(self):\n        \"\"\"Same as get_rd_port(), which returns the remote-debugging port.\"\"\"\n        return self.get_rd_port()\n\n    def get_websocket_url(self):\n        \"\"\"Returns the websocket URL of the active tab.\n        The websocket URL starts with `ws://`.\"\"\"\n        return self.get_active_tab().websocket_url\n\n    def add_handler(self, event, handler):\n        self.page.add_handler(event, handler)\n\n    def find_element(self, selector, best_match=False, timeout=None):\n        \"\"\"Similar to select(), but also finds elements by text content.\n        When using text-based searches, if best_match=False, then will\n        find the first element with the text. If best_match=True, then\n        if multiple elements have that text, then will use the element\n        with the closest text-length to the text being searched for.\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__add_light_pause()\n        selector = self.__convert_to_css_if_xpath(selector)\n        early_failure = False\n        if (\":contains(\") in selector:\n            selector, _ = page_utils.recalculate_selector(\n                selector, by=\"css selector\", xp_ok=True\n            )\n        failure = False\n        try:\n            if early_failure:\n                raise Exception(\"Failed!\")\n            if (\n                \"contains(\" not in selector\n                and not page_utils.is_xpath_selector(selector)\n                and not re.findall(r\"\\.\\s\", selector)\n                and not re.findall(r\"\\.$\", selector)\n                and not re.findall(r\"#\\s\", selector)\n                and not re.findall(r\"#$\", selector)\n                and (\n                    selector in [\"html\", \"body\"]\n                    or \"[\" in selector\n                    or re.findall(r\"\\.\\S\", selector)\n                    or re.findall(r\"#\\S\", selector)\n                    or \" > \" in selector\n                )\n            ):\n                element = self.loop.run_until_complete(\n                    self.page.select(selector, timeout=timeout)\n                )\n            else:\n                element = self.loop.run_until_complete(\n                    self.page.find(\n                        selector, best_match=best_match, timeout=timeout\n                    )\n                )\n        except Exception:\n            failure = True\n            plural = \"s\"\n            if timeout == 1:\n                plural = \"\"\n            message = \"\\n Element {%s} was not found after %s second%s!\" % (\n                selector,\n                timeout,\n                plural,\n            )\n        if failure:\n            raise Exception(message)\n        element = self.__add_sync_methods(element)\n        self.__slow_mode_pause_if_set()\n        return element\n\n    def find_element_by_text(self, text, tag_name=None, timeout=None):\n        \"\"\"Returns an element by matching text.\n        Optionally, provide a tag_name to narrow down the search to an\n        element with the given tag. (Eg: a, button, div, script, span)\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if tag_name:\n            try:\n                return self.find_element(\n                    '%s:contains(\"%s\")' % (tag_name, text), timeout=timeout\n                )\n            except Exception:\n                pass  # The exception will be raised later\n        else:\n            self.__add_light_pause()\n            self.assert_text(text, timeout=timeout)\n            elements = self.loop.run_until_complete(\n                self.page.find_elements_by_text(text=text)\n            )\n            for element in elements:\n                if element:\n                    element = self.__add_sync_methods(element)\n                    return self.__add_sync_methods(element)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        raise Exception(\n            \"Text {%s} with tag {%s} was not found after %s second%s!\"\n            % (text, tag_name, timeout, plural)\n        )\n\n    def find_all(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__add_light_pause()\n        selector = self.__convert_to_css_if_xpath(selector)\n        elements = self.loop.run_until_complete(\n            self.page.find_all(selector, timeout=timeout)\n        )\n        updated_elements = []\n        for element in elements:\n            element = self.__add_sync_methods(element)\n            updated_elements.append(element)\n        return updated_elements\n\n    def find_elements_by_text(self, text, tag_name=None):\n        \"\"\"Returns a list of elements by matching text.\n        Optionally, provide a tag_name to narrow down the search to only\n        elements with the given tag. (Eg: a, button, div, script, span)\"\"\"\n        self.__add_light_pause()\n        elements = self.loop.run_until_complete(\n            self.page.find_elements_by_text(text=text)\n        )\n        updated_elements = []\n        if tag_name:\n            tag_name = tag_name.lower().strip()\n        for element in elements:\n            if element and not tag_name:\n                element = self.__add_sync_methods(element)\n                if element not in updated_elements:\n                    updated_elements.append(element)\n            elif (\n                element\n                and tag_name in element.tag_name.lower()\n                and text.strip() in element.text\n            ):\n                element = self.__add_sync_methods(element)\n                if element not in updated_elements:\n                    updated_elements.append(element)\n            elif (\n                element\n                and element.parent\n                and tag_name in element.parent.tag_name.lower()\n                and text.strip() in element.parent.text\n            ):\n                element = self.__add_sync_methods(element.parent)\n                if element not in updated_elements:\n                    updated_elements.append(element)\n            elif (\n                element\n                and element.parent\n                and element.parent.parent\n                and tag_name in element.parent.parent.tag_name.lower()\n                and text.strip() in element.parent.parent.text\n            ):\n                element = self.__add_sync_methods(element.parent.parent)\n                if element not in updated_elements:\n                    updated_elements.append(element)\n        return updated_elements\n\n    def select(self, selector, timeout=None):\n        \"\"\"Similar to find_element().\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__add_light_pause()\n        selector = self.__convert_to_css_if_xpath(selector)\n        if (\":contains(\" in selector):\n            return self.find_element(selector, timeout=timeout)\n        failure = False\n        try:\n            element = self.loop.run_until_complete(\n                self.page.select(selector, timeout=timeout)\n            )\n        except Exception:\n            failure = True\n            plural = \"s\"\n            if timeout == 1:\n                plural = \"\"\n            msg = \"\\n Element {%s} was not found after %s second%s!\"\n            message = msg % (selector, timeout, plural)\n        if failure:\n            raise Exception(message)\n        element = self.__add_sync_methods(element)\n        self.__slow_mode_pause_if_set()\n        return element\n\n    def select_all(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        self.__add_light_pause()\n        selector = self.__convert_to_css_if_xpath(selector)\n        if not self.is_element_present(selector):\n            self.sleep(1)\n            timeout = timeout - 1\n            if timeout < 1:\n                timeout = 1\n            try:\n                self.select(selector, timeout=timeout)\n            except Exception:\n                return []\n        elements = self.loop.run_until_complete(\n            self.page.select_all(selector, timeout=0.1)\n        )\n        updated_elements = []\n        for element in elements:\n            element = self.__add_sync_methods(element)\n            updated_elements.append(element)\n        return updated_elements\n\n    def find_elements(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        return self.select_all(selector, timeout=timeout)\n\n    def find_visible_elements(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        visible_elements = []\n        elements = self.select_all(selector, timeout=timeout)\n        for element in elements:\n            with suppress(Exception):\n                position = element.get_position()\n                if (position.width != 0 or position.height != 0):\n                    visible_elements.append(element)\n        return visible_elements\n\n    def click_nth_element(self, selector, number):\n        elements = self.select_all(selector)\n        if len(elements) < number:\n            raise Exception(\n                \"Not enough matching {%s} elements to \"\n                \"click number %s!\" % (selector, number)\n            )\n        number = number - 1\n        if number < 0:\n            number = 0\n        element = elements[number]\n        element.scroll_into_view()\n        element.click()\n\n    def click_nth_visible_element(self, selector, number):\n        \"\"\"Finds all matching page elements and clicks the nth visible one.\n        Example: self.click_nth_visible_element('[type=\"checkbox\"]', 5)\n                (Clicks the 5th visible checkbox on the page.)\"\"\"\n        elements = self.find_visible_elements(selector)\n        if len(elements) < number:\n            raise Exception(\n                \"Not enough matching {%s} elements to \"\n                \"click number %s!\" % (selector, number)\n            )\n        number = number - 1\n        if number < 0:\n            number = 0\n        element = elements[number]\n        element.scroll_into_view()\n        element.click()\n\n    def click_link(self, link_text):\n        self.find_elements_by_text(link_text, \"a\")[0].click()\n\n    def go_back(self):\n        self.loop.run_until_complete(self.page.back())\n\n    def go_forward(self):\n        self.loop.run_until_complete(self.page.forward())\n\n    def get_navigation_history(self):\n        return self.loop.run_until_complete(self.page.get_navigation_history())\n\n    def __clear_input(self, element):\n        return (\n            self.loop.run_until_complete(element.clear_input_async())\n        )\n\n    def __click(self, element):\n        result = (\n            self.loop.run_until_complete(element.click_async())\n        )\n        self.loop.run_until_complete(self.page.wait(0.2))\n        return result\n\n    def __flash(self, element, *args, **kwargs):\n        element.scroll_into_view()\n        if len(args) < 3 and \"x_offset\" not in kwargs:\n            x_offset = self.__get_x_scroll_offset()\n            kwargs[\"x_offset\"] = x_offset\n        if len(args) < 3 and \"y_offset\" not in kwargs:\n            y_offset = self.__get_y_scroll_offset()\n            kwargs[\"y_offset\"] = y_offset\n        return (\n            self.loop.run_until_complete(\n                element.flash_async(*args, **kwargs)\n            )\n        )\n\n    def __focus(self, element):\n        return (\n            self.loop.run_until_complete(element.focus_async())\n        )\n\n    def __gui_click(self, element, timeframe=None):\n        element.scroll_into_view()\n        self.__add_light_pause()\n        position = element.get_position()\n        x = position.x\n        y = position.y\n        e_width = position.width\n        e_height = position.height\n        # Relative to window\n        element_rect = {\"height\": e_height, \"width\": e_width, \"x\": x, \"y\": y}\n        window_rect = self.get_window_rect()\n        w_bottom_y = window_rect[\"y\"] + window_rect[\"height\"]\n        viewport_height = window_rect[\"innerHeight\"]\n        x = window_rect[\"x\"] + element_rect[\"x\"]\n        y = w_bottom_y - viewport_height + element_rect[\"y\"]\n        y_scroll_offset = window_rect[\"pageYOffset\"]\n        y = y - y_scroll_offset\n        x = x + window_rect[\"scrollX\"]\n        y = y + window_rect[\"scrollY\"]\n        # Relative to screen\n        element_rect = {\"height\": e_height, \"width\": e_width, \"x\": x, \"y\": y}\n        e_width = element_rect[\"width\"]\n        e_height = element_rect[\"height\"]\n        e_x = element_rect[\"x\"]\n        e_y = element_rect[\"y\"]\n        x, y = ((e_x + e_width / 2.0) + 0.5), ((e_y + e_height / 2.0) + 0.5)\n        if not timeframe or not isinstance(timeframe, (int, float)):\n            timeframe = 0.25\n        if timeframe > 3:\n            timeframe = 3\n        self.gui_click_x_y(x, y, timeframe=timeframe)\n        return self.loop.run_until_complete(self.page.wait(0.2))\n\n    def __highlight_overlay(self, element):\n        return (\n            self.loop.run_until_complete(element.highlight_overlay_async())\n        )\n\n    def __mouse_click(self, element):\n        result = (\n            self.loop.run_until_complete(element.mouse_click_async())\n        )\n        self.loop.run_until_complete(self.page.wait(0.2))\n        return result\n\n    def __mouse_click_with_offset_async(self, element, *args, **kwargs):\n        result = (\n            self.loop.run_until_complete(\n                element.mouse_click_with_offset_async(*args, **kwargs)\n            )\n        )\n        self.loop.run_until_complete(self.page.wait(0.2))\n        return result\n\n    def __mouse_drag(self, element, destination):\n        return (\n            self.loop.run_until_complete(element.mouse_drag_async(destination))\n        )\n\n    def __mouse_move(self, element):\n        return (\n            self.loop.run_until_complete(element.mouse_move_async())\n        )\n\n    def __press_keys(self, element, text):\n        element.scroll_into_view()\n        submit = False\n        if text.endswith(\"\\n\") or text.endswith(\"\\r\"):\n            submit = True\n            text = text[:-1]\n        for key in text:\n            element.send_keys(key)\n            time.sleep(float(0.042 + (random.random() / 110.0)))\n        if submit:\n            element.send_keys(\"\\r\\n\")\n            time.sleep(0.044)\n        self.__slow_mode_pause_if_set()\n        return self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def __query_selector(self, element, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        element2 = self.loop.run_until_complete(\n            element.query_selector_async(selector)\n        )\n        element2 = self.__add_sync_methods(element2)\n        return element2\n\n    def __query_selector_all(self, element, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        elements = self.loop.run_until_complete(\n            element.query_selector_all_async(selector)\n        )\n        updated_elements = []\n        for element in elements:\n            element = self.__add_sync_methods(element)\n            updated_elements.append(element)\n        self.__slow_mode_pause_if_set()\n        return updated_elements\n\n    def __remove_from_dom(self, element):\n        return (\n            self.loop.run_until_complete(element.remove_from_dom_async())\n        )\n\n    def __save_screenshot(self, element, *args, **kwargs):\n        return (\n            self.loop.run_until_complete(\n                element.save_screenshot_async(*args, **kwargs)\n            )\n        )\n\n    def __save_to_dom(self, element):\n        return (\n            self.loop.run_until_complete(element.save_to_dom_async())\n        )\n\n    def __scroll_into_view(self, element):\n        self.loop.run_until_complete(element.scroll_into_view_async())\n        self.__add_light_pause()\n        return None\n\n    def __select_option(self, element):\n        return (\n            self.loop.run_until_complete(element.select_option_async())\n        )\n\n    def __send_file(self, element, *file_paths):\n        return (\n            self.loop.run_until_complete(element.send_file_async(*file_paths))\n        )\n\n    def __send_keys(self, element, text):\n        return (\n            self.loop.run_until_complete(element.send_keys_async(text))\n        )\n\n    def __set_text(self, element, value):\n        return (\n            self.loop.run_until_complete(element.set_text_async(value))\n        )\n\n    def __set_value(self, element, value):\n        return (\n            self.loop.run_until_complete(element.set_value_async(value))\n        )\n\n    def __type(self, element, text):\n        with suppress(Exception):\n            element.clear_input()\n        element.send_keys(text)\n\n    def __get_position(self, element):\n        return (\n            self.loop.run_until_complete(element.get_position_async())\n        )\n\n    def __get_html(self, element):\n        return (\n            self.loop.run_until_complete(element.get_html_async())\n        )\n\n    def __get_js_attributes(self, element):\n        return (\n            self.loop.run_until_complete(element.get_js_attributes_async())\n        )\n\n    def __get_attribute(self, element, attribute):\n        try:\n            return element.get_js_attributes()[attribute]\n        except Exception:\n            if not attribute:\n                raise\n            try:\n                attribute_str = element.get_js_attributes()\n                locate = ' %s=\"' % attribute\n                if locate in attribute_str.outerHTML:\n                    outer_html = attribute_str.outerHTML\n                    attr_start = outer_html.find(locate) + len(locate)\n                    attr_end = outer_html.find('\"', attr_start)\n                    value = outer_html[attr_start:attr_end]\n                    return value\n            except Exception:\n                pass\n        return None\n\n    def __get_parent(self, element):\n        return self.__add_sync_methods(element.parent)\n\n    def __get_x_scroll_offset(self):\n        x_scroll_offset = self.loop.run_until_complete(\n            self.page.evaluate(\"window.pageXOffset\")\n        )\n        return x_scroll_offset or 0\n\n    def __get_y_scroll_offset(self):\n        y_scroll_offset = self.loop.run_until_complete(\n            self.page.evaluate(\"window.pageYOffset\")\n        )\n        return y_scroll_offset or 0\n\n    def tile_windows(self, windows=None, max_columns=0):\n        \"\"\"Tile windows and return the grid of tiled windows.\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(\n            driver.tile_windows(windows, max_columns)\n        )\n\n    def grant_permissions(self, permissions, origin=None):\n        \"\"\"Grant specific permissions to the current window.\n        Applies to all origins if no origin is specified.\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(\n            driver.grant_permissions(permissions, origin)\n        )\n\n    def grant_all_permissions(self):\n        \"\"\"Grant all permissions to the current window for all origins.\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(driver.grant_all_permissions())\n\n    def reset_permissions(self):\n        \"\"\"Reset permissions for all origins on the current window.\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(driver.reset_permissions())\n\n    def get_all_cookies(self, *args, **kwargs):\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(\n            driver.cookies.get_all(*args, **kwargs)\n        )\n\n    def set_all_cookies(self, *args, **kwargs):\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(\n            driver.cookies.set_all(*args, **kwargs)\n        )\n\n    def save_cookies(self, *args, **kwargs):\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(\n            driver.cookies.save(*args, **kwargs)\n        )\n\n    def load_cookies(self, *args, **kwargs):\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(\n            driver.cookies.load(*args, **kwargs)\n        )\n\n    def clear_cookies(self):\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return self.loop.run_until_complete(driver.cookies.clear())\n\n    def sleep(self, seconds):\n        time.sleep(seconds)\n\n    def bring_active_window_to_front(self):\n        self.loop.run_until_complete(self.page.bring_to_front())\n        self.__add_light_pause()\n\n    def get_active_element(self):\n        return self.loop.run_until_complete(\n            self.page.js_dumps(\"document.activeElement\")\n        )\n\n    def get_active_element_css(self):\n        from seleniumbase.js_code import active_css_js\n\n        js_code = active_css_js.get_active_element_css\n        js_code = js_code.replace(\"return getBestSelector\", \"getBestSelector\")\n        return self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def click(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__slow_mode_pause_if_set()\n        element = self.find_element(selector, timeout=timeout)\n        element.scroll_into_view()\n        tag_name = element.tag_name\n        if tag_name:\n            tag_name = tag_name.lower().strip()\n        if (\n            tag_name in [\n                \"a\", \"button\", \"canvas\", \"div\", \"input\", \"li\", \"span\", \"label\"\n            ]\n            and \"contains(\" not in selector\n        ):\n            try:\n                element.mouse_click()  # Simulated click (NOT PyAutoGUI)\n            except Exception:\n                element.click()  # Standard CDP click\n        else:\n            element.click()  # Standard CDP click\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.2))\n\n    def click_active_element(self):\n        self.loop.run_until_complete(\n            self.page.evaluate(\"document.activeElement.click()\")\n        )\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.2))\n\n    def click_if_visible(self, selector, timeout=0):\n        if self.is_element_visible(selector):\n            with suppress(Exception):\n                self.click(selector, timeout=1)\n        elif timeout == 0:\n            return\n        else:\n            with suppress(Exception):\n                self.find_element(selector, timeout=timeout)\n                if self.is_element_visible(selector):\n                    self.click(selector, timeout=1)\n\n    def click_visible_elements(self, selector, limit=0):\n        \"\"\"Finds all matching page elements and clicks visible ones in order.\n        If a click reloads or opens a new page, the clicking will stop.\n        If no matching elements appear, an Exception will be raised.\n        If \"limit\" is set and > 0, will only click that many elements.\n        Also clicks elements that become visible from previous clicks.\n        Works best for actions such as clicking all checkboxes on a page.\n        Example: self.click_visible_elements('input[type=\"checkbox\"]')\"\"\"\n        elements = self.select_all(selector)\n        click_count = 0\n        for element in elements:\n            if limit and limit > 0 and click_count >= limit:\n                return\n            try:\n                width = 0\n                height = 0\n                try:\n                    position = element.get_position()\n                    width = position.width\n                    height = position.height\n                except Exception:\n                    continue\n                if (width != 0 or height != 0):\n                    element.scroll_into_view()\n                    element.click()\n                    click_count += 1\n                    time.sleep(0.044)\n                    self.__slow_mode_pause_if_set()\n                    self.loop.run_until_complete(self.page.wait(0.2))\n            except Exception:\n                break\n\n    def mouse_click(self, selector, timeout=None):\n        \"\"\"(Attempt simulating a mouse click)\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__slow_mode_pause_if_set()\n        element = self.find_element(selector, timeout=timeout)\n        element.scroll_into_view()\n        element.mouse_click()\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.2))\n\n    def nested_click(self, parent_selector, selector):\n        \"\"\"\n        Find parent element and click on child element inside it.\n        (This can be used to click on elements inside an iframe.)\n        \"\"\"\n        element = self.find_element(parent_selector)\n        element.query_selector(selector).mouse_click()\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.2))\n\n    def get_nested_element(self, parent_selector, selector):\n        \"\"\"(Can be used to find an element inside an iframe)\"\"\"\n        element = self.find_element(parent_selector)\n        return element.query_selector(selector)\n\n    def select_option_by_text(self, dropdown_selector, option):\n        element = self.find_element(dropdown_selector)\n        element.scroll_into_view()\n        options = element.query_selector_all(\"option\")\n        for found_option in options:\n            if found_option.text.strip() == option.strip():\n                found_option.select_option()\n                return\n        raise Exception(\n            \"Unable to find text option {%s} in dropdown {%s}!\"\n            % (dropdown_selector, option)\n        )\n\n    def select_option_by_index(self, dropdown_selector, option):\n        element = self.find_element(dropdown_selector)\n        element.scroll_into_view()\n        options = element.query_selector_all(\"option\")\n        count = 0\n        for found_option in options:\n            if count == int(option):\n                found_option.select_option()\n                return\n            count += 1\n        raise Exception(\n            \"Unable to find index option {%s} in dropdown {%s}!\"\n            % (dropdown_selector, option)\n        )\n\n    def select_option_by_value(self, dropdown_selector, option):\n        element = self.find_element(dropdown_selector)\n        element.scroll_into_view()\n        options = element.query_selector_all(\"option\")\n        for found_option in options:\n            if (\n                \"value\" in found_option.attrs\n                and str(found_option.attrs[\"value\"]) == str(option)\n            ):\n                found_option.select_option()\n                return\n        raise Exception(\n            \"Unable to find value option {%s} in dropdown {%s}!\"\n            % (dropdown_selector, option)\n        )\n\n    def flash(\n        self,\n        selector,  # The CSS Selector to flash\n        duration=1,  # (seconds) flash duration\n        color=\"44CC88\",  # RGB hex flash color\n        pause=0,  # (seconds) If 0, the next action starts during flash\n    ):\n        \"\"\"Paint a quickly-vanishing dot over an element.\"\"\"\n        selector = self.__convert_to_css_if_xpath(selector)\n        element = self.find_element(selector)\n        element.scroll_into_view()\n        x_offset = self.__get_x_scroll_offset()\n        y_offset = self.__get_y_scroll_offset()\n        element.flash(duration, color, x_offset, y_offset)\n        if pause and isinstance(pause, (int, float)):\n            time.sleep(pause)\n\n    def highlight(self, selector):\n        \"\"\"Highlight an element with multi-colors.\"\"\"\n        selector = self.__convert_to_css_if_xpath(selector)\n        element = self.find_element(selector)\n        element.scroll_into_view()\n        x_offset = self.__get_x_scroll_offset()\n        y_offset = self.__get_y_scroll_offset()\n        element.flash(0.46, \"44CC88\", x_offset, y_offset)\n        time.sleep(0.15)\n        element.flash(0.42, \"8844CC\", x_offset, y_offset)\n        time.sleep(0.15)\n        element.flash(0.38, \"CC8844\", x_offset, y_offset)\n        time.sleep(0.15)\n        element.flash(0.30, \"44CC88\", x_offset, y_offset)\n        time.sleep(0.30)\n\n    def focus(self, selector):\n        element = self.find_element(selector)\n        element.scroll_into_view()\n        element.focus()\n\n    def highlight_overlay(self, selector):\n        self.find_element(selector).highlight_overlay()\n\n    def get_parent(self, element):\n        if isinstance(element, str):\n            element = self.select(element)\n        return self.__add_sync_methods(element.parent)\n\n    def remove_element(self, selector):\n        self.select(selector).remove_from_dom()\n\n    def remove_from_dom(self, selector):\n        self.select(selector).remove_from_dom()\n\n    def remove_elements(self, selector):\n        \"\"\"Remove all elements on the page that match the selector.\"\"\"\n        css_selector = self.__convert_to_css_if_xpath(selector)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = js_utils.escape_quotes_if_needed(css_selector)\n        js_code = (\n            \"\"\"var $elements = document.querySelectorAll('%s');\n            var index = 0, length = $elements.length;\n            for(; index < length; index++){\n            $elements[index].remove();}\"\"\"\n            % css_selector\n        )\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def send_keys(self, selector, text, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__slow_mode_pause_if_set()\n        element = self.select(selector, timeout=timeout)\n        element.scroll_into_view()\n        if text.endswith(\"\\n\") or text.endswith(\"\\r\"):\n            text = text[:-1] + \"\\r\\n\"\n        elif (\n            element.tag_name == \"textarea\"\n            and \"\\n\" in text\n            and \"\\r\" not in text\n        ):\n            text = text.replace(\"\\n\", \"\\r\")\n        element.send_keys(text)\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def press_keys(self, selector, text, timeout=None):\n        \"\"\"Similar to send_keys(), but presses keys at human speed.\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__slow_mode_pause_if_set()\n        element = self.select(selector, timeout=timeout)\n        element.scroll_into_view()\n        submit = False\n        if text.endswith(\"\\n\") or text.endswith(\"\\r\"):\n            submit = True\n            text = text[:-1]\n        elif (\n            element.tag_name == \"textarea\"\n            and \"\\n\" in text\n            and \"\\r\" not in text\n        ):\n            text = text.replace(\"\\n\", \"\\r\")\n        for key in text:\n            element.send_keys(key)\n            time.sleep(float(0.042 + (random.random() / 110.0)))\n        if submit:\n            element.send_keys(\"\\r\\n\")\n            time.sleep(0.044)\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def type(self, selector, text, timeout=None):\n        \"\"\"Similar to send_keys(), but clears the text field first.\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__slow_mode_pause_if_set()\n        element = self.select(selector, timeout=timeout)\n        element.scroll_into_view()\n        with suppress(Exception):\n            element.clear_input()\n        if text.endswith(\"\\n\") or text.endswith(\"\\r\"):\n            text = text[:-1] + \"\\r\\n\"\n        elif (\n            element.tag_name == \"textarea\"\n            and \"\\n\" in text\n            and \"\\r\" not in text\n        ):\n            text = text.replace(\"\\n\", \"\\r\")\n        element.send_keys(text)\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def clear_input(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__slow_mode_pause_if_set()\n        element = self.select(selector, timeout=timeout)\n        element.scroll_into_view()\n        element.clear_input()\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def set_value(self, selector, text, timeout=None):\n        \"\"\"Similar to send_keys(), but clears the text field first.\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.__slow_mode_pause_if_set()\n        selector = self.__convert_to_css_if_xpath(selector)\n        element = self.select(selector, timeout=timeout)\n        element.scroll_into_view()\n        press_enter = False\n        if text.endswith(\"\\n\"):\n            text = text[:-1]\n            press_enter = True\n        value = js_utils.escape_quotes_if_needed(re.escape(text))\n        css_selector = re.escape(selector)\n        css_selector = js_utils.escape_quotes_if_needed(css_selector)\n        set_value_script = (\n            \"\"\"m_elm = document.querySelector('%s');\"\"\"\n            \"\"\"m_elm.value = '%s';\"\"\" % (css_selector, value)\n        )\n        self.loop.run_until_complete(self.page.evaluate(set_value_script))\n        the_type = self.get_element_attribute(selector, \"type\")\n        if the_type == \"range\":\n            # Some input sliders need a mouse event to trigger listeners.\n            with suppress(Exception):\n                mouse_move_script = (\n                    \"\"\"m_elm = document.querySelector('%s');\"\"\"\n                    \"\"\"m_evt = new Event('mousemove');\"\"\"\n                    \"\"\"m_elm.dispatchEvent(m_evt);\"\"\" % css_selector\n                )\n                self.loop.run_until_complete(\n                    self.page.evaluate(mouse_move_script)\n                )\n        elif press_enter:\n            self.__add_light_pause()\n            self.send_keys(selector, \"\\n\")\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def submit(self, selector):\n        submit_script = (\n            \"\"\"elm = document.querySelector('%s');\n            const event = new KeyboardEvent(\"keydown\", {\n                key: \"Enter\",\n                keyCode: 13,\n                code: \"Enter\",\n                which: 13,\n                bubbles: true\n            });\n            elm.dispatchEvent(event);\"\"\" % selector\n        )\n        self.loop.run_until_complete(self.page.evaluate(submit_script))\n\n    def evaluate(self, expression):\n        \"\"\"Run a JavaScript expression and return the result.\"\"\"\n        expression = expression.strip()\n        exp_list = expression.split(\"\\n\")\n        if exp_list and exp_list[-1].strip().startswith(\"return \"):\n            expression = (\n                \"\\n\".join(exp_list[0:-1]) + \"\\n\"\n                + exp_list[-1].strip()[len(\"return \"):]\n            ).strip()\n        return self.loop.run_until_complete(self.page.evaluate(expression))\n\n    def execute_script(self, expression):\n        return self.evaluate(expression)\n\n    def js_dumps(self, obj_name):\n        \"\"\"Similar to evaluate(), but for dictionary results.\"\"\"\n        if obj_name.startswith(\"return \"):\n            obj_name = obj_name[len(\"return \"):]\n        return self.loop.run_until_complete(self.page.js_dumps(obj_name))\n\n    def maximize(self):\n        try:\n            if self.get_window()[1].window_state.value == \"maximized\":\n                return\n            elif self.get_window()[1].window_state.value == \"minimized\":\n                self.loop.run_until_complete(self.page.maximize())\n                time.sleep(0.044)\n            return self.loop.run_until_complete(self.page.maximize())\n        except Exception:\n            with suppress(Exception):\n                width = self.evaluate(\"screen.availWidth;\")\n                height = self.evaluate(\"screen.availHeight;\")\n                self.__set_window_rect(0, 0, width, height)\n                return\n\n    def minimize(self):\n        if self.get_window()[1].window_state.value != \"minimized\":\n            return self.loop.run_until_complete(self.page.minimize())\n\n    def medimize(self):\n        if self.get_window()[1].window_state.value == \"minimized\":\n            self.loop.run_until_complete(self.page.medimize())\n            time.sleep(0.044)\n        return self.loop.run_until_complete(self.page.medimize())\n\n    def __set_window_rect(self, x, y, width, height, uc_lock=False):\n        if uc_lock:\n            gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n            with gui_lock:\n                self.__make_sure_pyautogui_lock_is_writable()\n                if self.get_window()[1].window_state.value == \"minimized\":\n                    self.loop.run_until_complete(\n                        self.page.set_window_size(\n                            left=x, top=y, width=width, height=height)\n                    )\n                    time.sleep(0.044)\n                return self.loop.run_until_complete(\n                    self.page.set_window_size(\n                        left=x, top=y, width=width, height=height)\n                )\n        else:\n            if self.get_window()[1].window_state.value == \"minimized\":\n                self.loop.run_until_complete(\n                    self.page.set_window_size(\n                        left=x, top=y, width=width, height=height)\n                )\n                time.sleep(0.044)\n            return self.loop.run_until_complete(\n                self.page.set_window_size(\n                    left=x, top=y, width=width, height=height)\n            )\n\n    def set_window_rect(self, x, y, width, height):\n        return self.__set_window_rect(x, y, width, height, uc_lock=True)\n\n    def reset_window_size(self):\n        x = settings.WINDOW_START_X\n        y = settings.WINDOW_START_Y\n        width = settings.CHROME_START_WIDTH\n        height = settings.CHROME_START_HEIGHT\n        self.__set_window_rect(x, y, width, height, uc_lock=True)\n        self.__add_light_pause()\n\n    def open_new_window(self, url=None, switch_to=True):\n        return self.open_new_tab(url=url, switch_to=switch_to)\n\n    def switch_to_window(self, window):\n        self.switch_to_tab(window)\n\n    def switch_to_newest_window(self):\n        self.switch_to_tab(-1)\n\n    def open_new_tab(self, url=None, switch_to=True, **kwargs):\n        driver = self.driver\n        if not isinstance(url, str):\n            url = \"about:blank\"\n        if hasattr(driver, \"cdp_base\"):\n            try:\n                self.loop.run_until_complete(\n                    self.page.get(url, new_tab=True, **kwargs)\n                )\n            except Exception:\n                original_targets = self.loop.run_until_complete(\n                    self.page.send(mycdp.target.get_targets())\n                )\n                tab_url = driver.cdp_base.tabs[0].websocket_url\n                if not self.driver.is_connected():\n                    self.driver.connect()\n                self.driver.open_new_tab()\n                targets = self.loop.run_until_complete(\n                    self.page.send(mycdp.target.get_targets())\n                )\n                new_targets = []\n                for target in targets:\n                    if target not in original_targets:\n                        new_targets.append(target)\n                if new_targets:\n                    found_target = new_targets[0]\n                    t_str = str(new_targets[0])\n                    target_id = (\n                        t_str.split(\"target_id=TargetID('\")[-1].split(\"')\")[0]\n                    )\n                    pre_tab_url = tab_url.split(\"/page/\")[0] + \"/page/\"\n                    new_tab_url = pre_tab_url + target_id\n                    new_tab = cdp_tab.Tab(\n                        new_tab_url, found_target, driver.cdp_base\n                    )\n                    driver.cdp_base.targets.append(new_tab)\n                    driver.cdp_base.tabs.append(new_tab)\n                    self.driver.disconnect()\n                    self.switch_to_newest_tab()\n                    self.open(url)\n                    return\n                elif getattr(sb_config, \"guest_mode\", None):\n                    print(\"  open_new_tab() failed! (Known Guest Mode issue)\")\n            if switch_to:\n                self.switch_to_newest_tab()\n            return\n\n        target_id = self.loop.run_until_complete(\n            self.page.send(mycdp.target.create_target(url))\n        )\n        if not target_id and getattr(sb_config, \"guest_mode\", None):\n            print(\"  open_new_tab() failed! (Known Guest Mode issue)\")\n        found_target = None\n        targets = self.loop.run_until_complete(\n            self.page.send(mycdp.target.get_targets())\n        )\n        if target_id:\n            for target in targets:\n                if str(target_id) in str(target):\n                    found_target = target\n                    break\n        if found_target:\n            tab_url = driver.tabs[0].websocket_url\n            pre_tab_url = tab_url.split(\"/page/\")[0] + \"/page/\"\n            new_tab_url = pre_tab_url + target_id\n            new_tab = cdp_tab.Tab(new_tab_url, found_target, driver)\n            driver.targets.append(new_tab)\n            driver.tabs.append(new_tab)\n        if switch_to:\n            self.switch_to_newest_tab()\n\n    def switch_to_tab(self, tab):\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        if isinstance(tab, int):\n            self.page = driver.tabs[tab]\n        elif isinstance(tab, cdp_util.Tab):\n            self.page = tab\n        else:\n            raise Exception(\"`tab` must be an int or a Tab type!\")\n        self.bring_active_window_to_front()\n\n    def switch_to_newest_tab(self):\n        self.switch_to_tab(-1)\n\n    def close_active_tab(self):\n        \"\"\"Close the active tab.\n        The active tab is the one currenly controlled by CDP.\n        The active tab MIGHT NOT be the currently visible tab!\n        (If a page opens a new tab, the new tab WON'T be active)\n        To switch the active tab, call: sb.switch_to_tab(tab)\"\"\"\n        return self.loop.run_until_complete(self.page.close())\n\n    def get_active_tab(self):\n        \"\"\"Return the active tab.\n        The active tab is the one currenly controlled by CDP.\n        The active tab MIGHT NOT be the currently visible tab!\n        (If a page opens a new tab, the new tab WON'T be active)\n        To switch the active tab, call: sb.switch_to_tab(tab)\"\"\"\n        return self.page\n\n    def get_tabs(self):\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        return driver.tabs\n\n    def get_window(self):\n        return self.loop.run_until_complete(self.page.get_window())\n\n    def get_text(self, selector):\n        return self.find_element(selector).text_all\n\n    def get_title(self):\n        return self.loop.run_until_complete(\n            self.page.evaluate(\"document.title\")\n        )\n\n    def get_current_url(self):\n        return self.loop.run_until_complete(\n            self.page.evaluate(\"window.location.href\")\n        )\n\n    def get_origin(self):\n        return self.loop.run_until_complete(\n            self.page.evaluate(\"window.location.origin\")\n        )\n\n    def get_html(self, include_shadow_dom=True):\n        return self.get_page_source(\n            include_shadow_dom=include_shadow_dom\n        )\n\n    def get_page_source(self, include_shadow_dom=True):\n        if include_shadow_dom:\n            return self.find_element(\"html\").get_html()\n        try:\n            source = self.loop.run_until_complete(\n                self.page.evaluate(\"document.documentElement.outerHTML\")\n            )\n        except Exception:\n            time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)\n            source = self.loop.run_until_complete(\n                self.page.evaluate(\"document.documentElement.outerHTML\")\n            )\n        return source\n\n    def get_user_agent(self):\n        return self.loop.run_until_complete(\n            self.page.evaluate(\"navigator.userAgent\")\n        )\n\n    def get_cookie_string(self):\n        return self.loop.run_until_complete(\n            self.page.evaluate(\"document.cookie\")\n        )\n\n    def get_locale_code(self):\n        return self.loop.run_until_complete(\n            self.page.evaluate(\"navigator.language || navigator.languages[0]\")\n        )\n\n    def get_local_storage_item(self, key):\n        js_code = \"\"\"localStorage.getItem('%s');\"\"\" % key\n        with suppress(Exception):\n            return self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def get_session_storage_item(self, key):\n        js_code = \"\"\"sessionStorage.getItem('%s');\"\"\" % key\n        with suppress(Exception):\n            return self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def get_screen_rect(self):\n        coordinates = self.loop.run_until_complete(\n            self.page.js_dumps(\"window.screen\")\n        )\n        return coordinates\n\n    def get_window_rect(self):\n        coordinates = {}\n        innerWidth = self.loop.run_until_complete(\n            self.page.evaluate(\"window.innerWidth\")\n        )\n        innerHeight = self.loop.run_until_complete(\n            self.page.evaluate(\"window.innerHeight\")\n        )\n        outerWidth = self.loop.run_until_complete(\n            self.page.evaluate(\"window.outerWidth\")\n        )\n        outerHeight = self.loop.run_until_complete(\n            self.page.evaluate(\"window.outerHeight\")\n        )\n        pageXOffset = self.loop.run_until_complete(\n            self.page.evaluate(\"window.pageXOffset\")\n        )\n        pageYOffset = self.loop.run_until_complete(\n            self.page.evaluate(\"window.pageYOffset\")\n        )\n        scrollX = self.loop.run_until_complete(\n            self.page.evaluate(\"window.scrollX\")\n        )\n        scrollY = self.loop.run_until_complete(\n            self.page.evaluate(\"window.scrollY\")\n        )\n        screenLeft = self.loop.run_until_complete(\n            self.page.evaluate(\"window.screenLeft\")\n        )\n        screenTop = self.loop.run_until_complete(\n            self.page.evaluate(\"window.screenTop\")\n        )\n        x = self.loop.run_until_complete(\n            self.page.evaluate(\"window.screenX\")\n        )\n        y = self.loop.run_until_complete(\n            self.page.evaluate(\"window.screenY\")\n        )\n        coordinates[\"innerWidth\"] = innerWidth\n        coordinates[\"innerHeight\"] = innerHeight\n        coordinates[\"outerWidth\"] = outerWidth\n        coordinates[\"outerHeight\"] = outerHeight\n        coordinates[\"width\"] = outerWidth\n        coordinates[\"height\"] = outerHeight\n        coordinates[\"pageXOffset\"] = pageXOffset if pageXOffset else 0\n        coordinates[\"pageYOffset\"] = pageYOffset if pageYOffset else 0\n        coordinates[\"scrollX\"] = scrollX if scrollX else 0\n        coordinates[\"scrollY\"] = scrollY if scrollY else 0\n        coordinates[\"screenLeft\"] = screenLeft if screenLeft else 0\n        coordinates[\"screenTop\"] = screenTop if screenTop else 0\n        coordinates[\"x\"] = x if x else 0\n        coordinates[\"y\"] = y if y else 0\n        return coordinates\n\n    def get_window_size(self):\n        coordinates = {}\n        outerWidth = self.loop.run_until_complete(\n            self.page.evaluate(\"window.outerWidth\")\n        )\n        outerHeight = self.loop.run_until_complete(\n            self.page.evaluate(\"window.outerHeight\")\n        )\n        coordinates[\"width\"] = outerWidth\n        coordinates[\"height\"] = outerHeight\n        return coordinates\n\n    def get_window_position(self):\n        coordinates = {}\n        x = self.loop.run_until_complete(\n            self.page.evaluate(\"window.screenX\")\n        )\n        y = self.loop.run_until_complete(\n            self.page.evaluate(\"window.screenY\")\n        )\n        coordinates[\"x\"] = x if x else 0\n        coordinates[\"y\"] = y if y else 0\n        return coordinates\n\n    def get_element_rect(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        selector = self.__convert_to_css_if_xpath(selector)\n        element = self.select(selector, timeout=timeout)\n        self.__add_light_pause()\n        coordinates = None\n        if \":contains(\" in selector:\n            position = element.get_position()\n            x = position.x\n            y = position.y\n            width = position.width\n            height = position.height\n            coordinates = {\"x\": x, \"y\": y, \"width\": width, \"height\": height}\n        else:\n            coordinates = self.loop.run_until_complete(\n                self.page.js_dumps(\n                    \"\"\"document.querySelector('%s').getBoundingClientRect()\"\"\"\n                    % js_utils.escape_quotes_if_needed(re.escape(selector))\n                )\n            )\n        return coordinates\n\n    def get_element_size(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        element_rect = self.get_element_rect(selector, timeout=timeout)\n        coordinates = {}\n        coordinates[\"width\"] = element_rect[\"width\"]\n        coordinates[\"height\"] = element_rect[\"height\"]\n        return coordinates\n\n    def get_element_position(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        element_rect = self.get_element_rect(selector, timeout=timeout)\n        coordinates = {}\n        coordinates[\"x\"] = element_rect[\"x\"]\n        coordinates[\"y\"] = element_rect[\"y\"]\n        return coordinates\n\n    def get_gui_element_rect(self, selector, timeout=None):\n        \"\"\"(Coordinates are relative to the screen. Not the window.)\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        element_rect = self.get_element_rect(selector, timeout=timeout)\n        e_width = element_rect[\"width\"]\n        e_height = element_rect[\"height\"]\n        window_rect = self.get_window_rect()\n        w_bottom_y = window_rect[\"y\"] + window_rect[\"height\"]\n        viewport_height = window_rect[\"innerHeight\"]\n        x = window_rect[\"x\"] + element_rect[\"x\"]\n        y = w_bottom_y - viewport_height + element_rect[\"y\"]\n        y_scroll_offset = window_rect[\"pageYOffset\"]\n        if (\n            hasattr(sb_config, \"_cdp_browser\")\n            and sb_config._cdp_browser == \"opera\"\n        ):\n            # Handle special case where Opera side panel shifts coordinates\n            x_offset = window_rect[\"outerWidth\"] - window_rect[\"innerWidth\"]\n            if x_offset > 56:\n                x_offset = 56\n            elif x_offset < 22:\n                x_offset = 0\n            x = x + x_offset\n        y = y - y_scroll_offset\n        x = x + window_rect[\"scrollX\"]\n        y = y + window_rect[\"scrollY\"]\n        return ({\"height\": e_height, \"width\": e_width, \"x\": x, \"y\": y})\n\n    def get_gui_element_center(self, selector, timeout=None):\n        \"\"\"(Coordinates are relative to the screen. Not the window.)\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        element_rect = self.get_gui_element_rect(selector, timeout=timeout)\n        e_width = element_rect[\"width\"]\n        e_height = element_rect[\"height\"]\n        e_x = element_rect[\"x\"]\n        e_y = element_rect[\"y\"]\n        return ((e_x + e_width / 2.0) + 0.5, (e_y + e_height / 2.0) + 0.5)\n\n    def get_document(self):\n        return self.loop.run_until_complete(self.page.get_document())\n\n    def get_flattened_document(self):\n        return self.loop.run_until_complete(self.page.get_flattened_document())\n\n    def get_element_attributes(self, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        return self.loop.run_until_complete(\n            self.page.js_dumps(\n                \"\"\"document.querySelector('%s')\"\"\"\n                % js_utils.escape_quotes_if_needed(re.escape(selector))\n            )\n        )\n\n    def get_element_attribute(self, selector, attribute):\n        \"\"\"Find an element and return the value of an attribute.\n        Raises an exception if there's no such element or attribute.\"\"\"\n        attributes = self.get_element_attributes(selector)\n        with suppress(Exception):\n            return attributes[attribute]\n        locate = ' %s=\"' % attribute\n        value = self.get_attribute(selector, attribute)\n        if not value and locate not in attributes:\n            raise KeyError(attribute)\n        return value\n\n    def get_attribute(self, selector, attribute):\n        \"\"\"Find an element and return the value of an attribute.\n        If the element doesn't exist: Raises an exception.\n        If the attribute doesn't exist: Returns None.\"\"\"\n        return self.find_element(selector).get_attribute(attribute)\n\n    def get_element_html(self, selector):\n        \"\"\"Find an element and return the outerHTML.\"\"\"\n        selector = self.__convert_to_css_if_xpath(selector)\n        self.find_element(selector)\n        self.__add_light_pause()\n        return self.loop.run_until_complete(\n            self.page.evaluate(\n                \"\"\"document.querySelector('%s').outerHTML\"\"\"\n                % js_utils.escape_quotes_if_needed(re.escape(selector))\n            )\n        )\n\n    def get_mfa_code(self, totp_key=None):\n        \"\"\"Returns a time-based one-time password based on the Google\n        Authenticator algorithm for multi-factor authentication.\"\"\"\n        return shared_utils.get_mfa_code(totp_key)\n\n    def enter_mfa_code(self, selector, totp_key=None, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        mfa_code = self.get_mfa_code(totp_key)\n        self.type(selector, mfa_code + \"\\n\", timeout=timeout)\n\n    def activate_messenger(self):\n        js_utils.activate_messenger(self)\n        self.__add_light_pause()\n\n    def set_messenger_theme(\n        self, theme=\"default\", location=\"default\", max_messages=\"default\"\n    ):\n        \"\"\"Sets a theme for posting messages.\n        Themes: [\"flat\", \"future\", \"block\", \"air\", \"ice\"]\n        Locations: [\"top_left\", \"top_center\", \"top_right\",\n                    \"bottom_left\", \"bottom_center\", \"bottom_right\"]\n        max_messages: The limit of concurrent messages to display.\"\"\"\n        if not theme:\n            theme = \"default\"  # \"flat\"\n        if not location:\n            location = \"default\"  # \"bottom_right\"\n        if not max_messages:\n            max_messages = \"default\"  # \"8\"\n        else:\n            max_messages = str(max_messages)  # Value must be in string format\n        js_utils.set_messenger_theme(\n            self,\n            theme=theme,\n            location=location,\n            max_messages=max_messages,\n        )\n        self.__add_light_pause()\n\n    def post_message(self, message, duration=None, pause=True, style=\"info\"):\n        \"\"\"Post a message on the screen with Messenger.\n        Arguments:\n            message: The message to display.\n            duration: The time until the message vanishes. (Default: 2.55s)\n            pause: If True, the program waits until the message completes.\n            style: \"info\", \"success\", or \"error\".\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        if style not in [\"info\", \"success\", \"error\"]:\n            style = \"info\"\n        if not duration:\n            duration = settings.DEFAULT_MESSAGE_DURATION\n        if (\n            (\n                driver.config.headless\n                or (hasattr(sb_config, \"xvfb\") and sb_config.xvfb)\n            )\n            and float(duration) > 0.75\n        ):\n            duration = 0.75\n        try:\n            js_utils.post_message(self, message, duration, style=style)\n        except Exception:\n            print(\" * %s message: %s\" % (style.upper(), message))\n        if pause:\n            duration = float(duration) + 0.15\n            time.sleep(float(duration))\n\n    def set_locale(self, locale):\n        \"\"\"(Settings will take effect on the next page load)\"\"\"\n        self.loop.run_until_complete(self.page.set_locale(locale))\n\n    def set_local_storage_item(self, key, value):\n        js_code = \"\"\"localStorage.setItem('%s','%s');\"\"\" % (key, value)\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def set_session_storage_item(self, key, value):\n        js_code = \"\"\"sessionStorage.setItem('%s','%s');\"\"\" % (key, value)\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def set_attributes(self, selector, attribute, value):\n        \"\"\"This method uses JavaScript to set/update a common attribute.\n        All matching selectors from querySelectorAll() are used.\n        Example => (Make all links on a website redirect to Google):\n        self.set_attributes(\"a\", \"href\", \"https://google.com\")\"\"\"\n        attribute = re.escape(attribute)\n        attribute = js_utils.escape_quotes_if_needed(attribute)\n        value = re.escape(value)\n        value = js_utils.escape_quotes_if_needed(value)\n        css_selector = self.__convert_to_css_if_xpath(selector)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = js_utils.escape_quotes_if_needed(css_selector)\n        js_code = (\n            \"\"\"var $elements = document.querySelectorAll('%s');\n            var index = 0, length = $elements.length;\n            for(; index < length; index++){\n            $elements[index].setAttribute('%s','%s');}\"\"\" % (\n                css_selector,\n                attribute,\n                value,\n            )\n        )\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def is_attribute_present(self, selector, attribute, value=None):\n        try:\n            element = self.find_element(selector, timeout=0.1)\n            found_value = element.get_attribute(attribute)\n            if found_value is None:\n                return False\n            if value is not None:\n                if found_value == value:\n                    return True\n                else:\n                    return False\n            else:\n                return True\n        except Exception:\n            return False\n\n    def is_online(self):\n        js_code = \"navigator.onLine;\"\n        return self.loop.run_until_complete(self.page.evaluate(js_code))\n\n    def __make_sure_pyautogui_lock_is_writable(self):\n        with suppress(Exception):\n            shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK)\n\n    def __verify_pyautogui_has_a_headed_browser(self):\n        \"\"\"PyAutoGUI requires a headed browser so that it can\n        focus on the correct element when performing actions.\"\"\"\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        if driver.config.headless:\n            raise Exception(\n                \"PyAutoGUI can't be used in headless mode!\"\n            )\n\n    def __install_pyautogui_if_missing(self):\n        self.__verify_pyautogui_has_a_headed_browser()\n        driver = self.driver\n        if hasattr(driver, \"cdp_base\"):\n            driver = driver.cdp_base\n        pip_find_lock = fasteners.InterProcessLock(\n            constants.PipInstall.FINDLOCK\n        )\n        with pip_find_lock:  # Prevent issues with multiple processes\n            with suppress(Exception):\n                shared_utils.make_writable(constants.PipInstall.FINDLOCK)\n            try:\n                import pyautogui\n                with suppress(Exception):\n                    use_pyautogui_ver = constants.PyAutoGUI.VER\n                    u_pv = shared_utils.make_version_tuple(use_pyautogui_ver)\n                    pv = shared_utils.make_version_tuple(pyautogui.__version__)\n                    if pv < u_pv:\n                        del pyautogui  # To get newer ver\n                        shared_utils.pip_install(\"pyautogui\", version=\"Latest\")\n                        import pyautogui\n            except Exception:\n                print(\"\\nPyAutoGUI required! Installing now...\")\n                shared_utils.pip_install(\"pyautogui\", version=\"Latest\")\n                try:\n                    import pyautogui\n                except Exception:\n                    if (\n                        shared_utils.is_linux()\n                        and (\n                            not sb_config.headed\n                            or (hasattr(sb_config, \"xvfb\") and sb_config.xvfb)\n                        )\n                        and not driver.config.headless\n                        and (\n                            not hasattr(sb_config, \"_virtual_display\")\n                            or not sb_config._virtual_display\n                        )\n                    ):\n                        from sbvirtualdisplay import Display\n                        xvfb_width = 1366\n                        xvfb_height = 768\n                        if (\n                            hasattr(sb_config, \"_xvfb_width\")\n                            and sb_config._xvfb_width\n                            and isinstance(sb_config._xvfb_width, int)\n                            and hasattr(sb_config, \"_xvfb_height\")\n                            and sb_config._xvfb_height\n                            and isinstance(sb_config._xvfb_height, int)\n                        ):\n                            xvfb_width = sb_config._xvfb_width\n                            xvfb_height = sb_config._xvfb_height\n                            if xvfb_width < 1024:\n                                xvfb_width = 1024\n                            sb_config._xvfb_width = xvfb_width\n                            if xvfb_height < 768:\n                                xvfb_height = 768\n                            sb_config._xvfb_height = xvfb_height\n                        with suppress(Exception):\n                            xvfb_display = Display(\n                                visible=True,\n                                size=(xvfb_width, xvfb_height),\n                                backend=\"xvfb\",\n                                use_xauth=True,\n                            )\n                            if \"--debug-display\" in sys.argv:\n                                print(\n                                    \"Starting VDisplay from sb_cdp: (%s, %s)\"\n                                    % (xvfb_width, xvfb_height)\n                                )\n                            xvfb_display.start()\n\n    def __get_configured_pyautogui(self, pyautogui_copy):\n        if (\n            shared_utils.is_linux()\n            and hasattr(pyautogui_copy, \"_pyautogui_x11\")\n            and \"DISPLAY\" in os.environ.keys()\n        ):\n            if (\n                hasattr(sb_config, \"_pyautogui_x11_display\")\n                and sb_config._pyautogui_x11_display\n                and hasattr(pyautogui_copy._pyautogui_x11, \"_display\")\n                and (\n                    sb_config._pyautogui_x11_display\n                    == pyautogui_copy._pyautogui_x11._display\n                )\n            ):\n                pass\n            else:\n                import Xlib.display\n                pyautogui_copy._pyautogui_x11._display = (\n                    Xlib.display.Display(os.environ['DISPLAY'])\n                )\n                sb_config._pyautogui_x11_display = (\n                    pyautogui_copy._pyautogui_x11._display\n                )\n        return pyautogui_copy\n\n    def gui_press_key(self, key):\n        self.__install_pyautogui_if_missing()\n        import pyautogui\n        pyautogui = self.__get_configured_pyautogui(pyautogui)\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:\n            self.__make_sure_pyautogui_lock_is_writable()\n            pyautogui.press(key)\n            time.sleep(0.044)\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def gui_press_keys(self, keys):\n        self.__install_pyautogui_if_missing()\n        import pyautogui\n        pyautogui = self.__get_configured_pyautogui(pyautogui)\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:\n            self.__make_sure_pyautogui_lock_is_writable()\n            for key in keys:\n                pyautogui.press(key)\n                time.sleep(float(0.042 + (random.random() / 110.0)))\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def gui_write(self, text):\n        self.__install_pyautogui_if_missing()\n        import pyautogui\n        pyautogui = self.__get_configured_pyautogui(pyautogui)\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:\n            self.__make_sure_pyautogui_lock_is_writable()\n            pyautogui.write(text)\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.sleep(0.025))\n\n    def __gui_click_x_y(self, x, y, timeframe=0.25, uc_lock=False):\n        self.__install_pyautogui_if_missing()\n        import pyautogui\n        pyautogui = self.__get_configured_pyautogui(pyautogui)\n        screen_width, screen_height = pyautogui.size()\n        if x < 0 or y < 0 or x > screen_width or y > screen_height:\n            raise Exception(\n                \"PyAutoGUI cannot click on point (%s, %s)\"\n                \" outside screen. (Width: %s, Height: %s)\"\n                % (x, y, screen_width, screen_height)\n            )\n        if uc_lock:\n            gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n            with gui_lock:  # Prevent issues with multiple processes\n                self.__make_sure_pyautogui_lock_is_writable()\n                pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)\n                if timeframe >= 0.25:\n                    time.sleep(0.056)  # Wait if moving at human-speed\n                if \"--debug\" in sys.argv:\n                    print(\" <DEBUG> pyautogui.click(%s, %s)\" % (x, y))\n                pyautogui.click(x=x, y=y)\n        else:\n            # Called from a method where the gui_lock is already active\n            pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)\n            if timeframe >= 0.25:\n                time.sleep(0.056)  # Wait if moving at human-speed\n            if \"--debug\" in sys.argv:\n                print(\" <DEBUG> pyautogui.click(%s, %s)\" % (x, y))\n            pyautogui.click(x=x, y=y)\n\n    def gui_click_x_y(self, x, y, timeframe=0.25):\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:  # Prevent issues with multiple processes\n            self.__make_sure_pyautogui_lock_is_writable()\n            self.__install_pyautogui_if_missing()\n            import pyautogui\n            pyautogui = self.__get_configured_pyautogui(pyautogui)\n            width_ratio = 1.0\n            if shared_utils.is_windows():\n                window_rect = self.get_window_rect()\n                width = window_rect[\"width\"]\n                height = window_rect[\"height\"]\n                win_x = window_rect[\"x\"]\n                win_y = window_rect[\"y\"]\n                scr_width = pyautogui.size().width\n                win_width = self.evaluate(\"screen.availWidth;\")\n                width_ratio = round(float(scr_width) / float(win_width), 2)\n                width_ratio += 0.01\n                if width_ratio < 0.45 or width_ratio > 2.55:\n                    width_ratio = 1.01\n                sb_config._saved_width_ratio = width_ratio\n                with suppress(Exception):\n                    self.get_window()  # If this fails, skip the rest\n                    self.minimize()\n                    self.__add_light_pause()\n                    self.__set_window_rect(win_x, win_y, width, height)\n                    self.__add_light_pause()\n                x = x * (width_ratio + 0.03)\n                y = y * (width_ratio - 0.03)\n            self.bring_active_window_to_front()\n            self.__gui_click_x_y(x, y, timeframe=timeframe, uc_lock=False)\n\n    def gui_click_element(self, selector, timeframe=0.25):\n        self.__slow_mode_pause_if_set()\n        x, y = self.get_gui_element_center(selector)\n        self.__add_light_pause()\n        self.gui_click_x_y(x, y, timeframe=timeframe)\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.2))\n\n    def gui_click_with_offset(\n        self, selector, x, y, timeframe=0.25, center=False\n    ):\n        \"\"\"Click an element at an {X,Y}-offset location.\n        {0,0} is the top-left corner of the element.\n        If center==True, {0,0} becomes the center of the element.\n        The timeframe is the time spent moving the mouse.\"\"\"\n        if center:\n            px, py = self.get_gui_element_center(selector)\n            self.gui_click_x_y(px + x, py + y, timeframe=timeframe)\n        else:\n            element_rect = self.get_gui_element_rect(selector)\n            px = element_rect[\"x\"]\n            py = element_rect[\"y\"]\n            self.gui_click_x_y(px + x, py + y, timeframe=timeframe)\n\n    def click_with_offset(self, selector, x, y, center=False):\n        element = self.find_element(selector)\n        element.scroll_into_view()\n        if \"--debug\" in sys.argv:\n            displayed_selector = \"`%s`\" % selector\n            if '\"' not in selector:\n                displayed_selector = '\"%s\"' % selector\n            elif \"'\" not in selector:\n                displayed_selector = \"'%s'\" % selector\n            print(\n                \" <DEBUG> sb.click_with_offset(%s, %s, %s, center=%s)\"\n                % (displayed_selector, x, y, center)\n            )\n        element.click_with_offset(x=x, y=y, center=center)\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.2))\n\n    def _on_a_cf_turnstile_page(self, source=None):\n        if not source or len(source) < 400:\n            time.sleep(0.2)\n            source = self.get_page_source()\n        if (\n            (\n                'data-callback=\"onCaptchaSuccess\"' in source\n                and 'title=\"reCAPTCHA\"' not in source\n                and 'id=\"recaptcha-token\"' not in source\n            )\n            or \"/challenge-platform/h/b/\" in source\n            or 'id=\"challenge-widget-' in source\n            or \"challenges.cloudf\" in source\n            or \"cf-turnstile-\" in source\n        ):\n            return True\n        return False\n\n    def _on_an_incapsula_hcaptcha_page(self, *args, **kwargs):\n        self.loop.run_until_complete(self.page.wait(0.1))\n        if (\n            self.is_element_visible('iframe[src*=\"_Incapsula_Resource?\"]')\n            or self.is_element_visible(\"iframe[data-hcaptcha-widget-id]\")\n        ):\n            return True\n        return False\n\n    def _on_a_datadome_slider_page(self, *args, **kwargs):\n        self.loop.run_until_complete(self.page.wait(0.1))\n        if (\n            self.is_element_visible(\n                'body > iframe[src*=\"/geo.captcha-delivery.com/captcha/\"]'\n            )\n        ):\n            return True\n        return False\n\n    def _on_a_g_recaptcha_page(self, *args, **kwargs):\n        time.sleep(0.4)  # reCAPTCHA may need a moment to appear\n        self.loop.run_until_complete(self.page.wait(0.1))\n        source = self.get_page_source()\n        if (\n            (\n                'id=\"recaptcha-token\"' in source\n                or 'title=\"reCAPTCHA\"' in source\n            )\n            and self.is_element_visible('iframe[title=\"reCAPTCHA\"]')\n        ):\n            try:\n                self.loop.run_until_complete(self.page.wait(0.1))\n            except Exception:\n                time.sleep(0.1)\n            return True\n        elif \"com/recaptcha/api.js\" in source:\n            time.sleep(1.2)  # Maybe still loading\n            try:\n                self.loop.run_until_complete(self.page.wait(0.1))\n            except Exception:\n                time.sleep(0.1)\n            return True\n        return False\n\n    def __gui_click_recaptcha(self, use_cdp=False):\n        selector = None\n        if self.is_element_visible('iframe[title=\"reCAPTCHA\"]'):\n            selector = 'iframe[title=\"reCAPTCHA\"]'\n        else:\n            return False\n        time.sleep(0.25)\n        self.loop.run_until_complete(self.page.wait(0.1))\n        time.sleep(0.25)\n        with suppress(Exception):\n            element_rect = self.get_element_rect(selector, timeout=0.1)\n            e_x = element_rect[\"x\"]\n            e_y = element_rect[\"y\"]\n            window_rect = self.get_window_rect()\n            win_width = window_rect[\"innerWidth\"]\n            win_height = window_rect[\"innerHeight\"]\n            if (\n                e_x > 1040\n                and e_y > 640\n                and abs(win_width - e_x) < 110\n                and abs(win_height - e_y) < 110\n            ):\n                # Probably the invisible reCAPTCHA in the bottom right corner\n                return False\n            gui_element_rect = self.get_gui_element_rect(selector, timeout=1)\n            gui_e_x = gui_element_rect[\"x\"]\n            gui_e_y = gui_element_rect[\"y\"]\n            x_offset = 26\n            y_offset = 35\n            if shared_utils.is_windows():\n                x_offset = 29\n            x = gui_e_x + x_offset\n            y = gui_e_y + y_offset\n            sb_config._saved_cf_x_y = (x, y)\n            time.sleep(0.08)\n            if use_cdp:\n                self.sleep(0.03)\n                gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n                with gui_lock:  # Prevent issues with multiple processes\n                    self.bring_active_window_to_front()\n                    time.sleep(0.056)\n                    self.click_with_offset(selector, x_offset, y_offset)\n                    time.sleep(0.056)\n            else:\n                self.gui_click_x_y(x, y)\n            return True\n        return False\n\n    def __gui_slide_datadome_captcha(self):\n        iframe = 'body > iframe[src*=\"/geo.captcha-delivery.com/captcha/\"]'\n        if not self.is_element_visible(iframe):\n            return False\n        src = self.get_attribute(iframe, \"src\")\n        tab = self.get_active_tab()\n        self.open_new_tab(url=src)\n        time.sleep(0.41)\n        self.loop.run_until_complete(self.page.wait(0.1))\n        time.sleep(0.25)\n        x1, y1 = self.get_gui_element_center(\"div.slider\")\n        x2, y2 = self.get_gui_element_center(\"div.sliderTarget\")\n        self.close_active_tab()\n        self.switch_to_tab(tab)\n        self.gui_drag_drop_points(x1, y1, x2, y2, timeframe=0.55)\n        time.sleep(0.25)\n        self.loop.run_until_complete(self.page.wait(0.2))\n        time.sleep(0.15)\n        return True\n\n    def __cdp_click_incapsula_hcaptcha(self):\n        selector = \"iframe[data-hcaptcha-widget-id]\"\n        if self.is_element_visible('iframe[src*=\"_Incapsula_Resource?\"]'):\n            outer_selector = 'iframe[src*=\"_Incapsula_Resource?\"]'\n            element = self.get_nested_element(outer_selector, selector)\n            if not element:\n                return False\n        elif self.is_element_visible(selector):\n            element = self.select(selector, timeout=0.1)\n            if not element:\n                return False\n        else:\n            return False\n        time.sleep(0.05)\n        self.loop.run_until_complete(self.page.wait(0.1))\n        time.sleep(0.05)\n        x_offset = 30\n        y_offset = 36\n        was_clicked = False\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:  # Prevent issues with multiple processes\n            self.bring_active_window_to_front()\n            time.sleep(0.056)\n            if \"--debug\" in sys.argv:\n                displayed_selector = \"`%s`\" % selector\n                if '\"' not in selector:\n                    displayed_selector = '\"%s\"' % selector\n                elif \"'\" not in selector:\n                    displayed_selector = \"'%s'\" % selector\n                print(\n                    \" <DEBUG> click_with_offset(%s, %s, %s)\"\n                    % (displayed_selector, x_offset, y_offset)\n                )\n            with suppress(Exception):\n                element.click_with_offset(x_offset, y_offset)\n                was_clicked = True\n                time.sleep(0.075)\n        if was_clicked:\n            # Wait a moment for the click to succeed\n            time.sleep(0.75)\n            self.__slow_mode_pause_if_set()\n            self.loop.run_until_complete(self.page.wait(0.1))\n            if \"--debug\" in sys.argv:\n                print(\" <DEBUG> hCaptcha was clicked!\")\n            return True\n        if \"--debug\" in sys.argv:\n            print(\" <DEBUG> hCaptcha was NOT clicked!\")\n        return False\n\n    def solve_captcha(self):\n        self.__click_captcha(use_cdp=True)\n\n    def click_captcha(self):\n        \"\"\"Same as solve_captcha()\"\"\"\n        self.__click_captcha(use_cdp=True)\n\n    def gui_click_captcha(self):\n        \"\"\"Use PyAutoGUI to click the CAPTCHA\"\"\"\n        self.__click_captcha(use_cdp=False)\n\n    def __click_captcha(self, use_cdp=False):\n        \"\"\"Uses PyAutoGUI unless use_cdp == True\"\"\"\n        self.sleep(0.075)\n        self.loop.run_until_complete(self.page.wait(0.1))\n        self.sleep(0.025)\n        source = self.get_page_source()\n        if self._on_a_cf_turnstile_page(source):\n            pass\n        elif self._on_a_g_recaptcha_page(source):\n            result = self.__gui_click_recaptcha(use_cdp)\n            return result\n        elif self._on_an_incapsula_hcaptcha_page():\n            result = self.__cdp_click_incapsula_hcaptcha()\n            return result\n        elif self._on_a_datadome_slider_page():\n            result = self.__gui_slide_datadome_captcha()\n            return result\n        else:\n            return False\n        selector = None\n        if self.is_element_present('[class=\"cf-turnstile\"]'):\n            selector = '[class=\"cf-turnstile\"]'\n        elif self.is_element_present(\"#challenge-form div > div\"):\n            selector = \"#challenge-form div > div\"\n        elif self.is_element_present('[style=\"display: grid;\"] div div'):\n            selector = '[style=\"display: grid;\"] div div'\n        elif self.is_element_present(\"[class*=spacer] + div div\"):\n            selector = '[class*=spacer] + div div'\n        elif self.is_element_present(\".spacer div:not([class])\"):\n            selector = \".spacer div:not([class])\"\n        elif self.is_element_present('[data-testid*=\"challenge-\"] div'):\n            selector = '[data-testid*=\"challenge-\"] div'\n        elif self.is_element_present(\"div#turnstile-widget div:not([class])\"):\n            selector = \"div#turnstile-widget div:not([class])\"\n        elif self.is_element_present(\"ngx-turnstile div:not([class])\"):\n            selector = \"ngx-turnstile div:not([class])\"\n        elif self.is_element_present(\n            'form div:not([class]):has(input[name*=\"cf-turn\"])'\n        ):\n            selector = 'form div:not([class]):has(input[name*=\"cf-turn\"])'\n        elif self.is_element_present(\"form div:not(:has(*))\"):\n            selector = \"form div:not(:has(*))\"\n        elif self.is_element_present(\"body > div#check > div:not([class])\"):\n            selector = \"body > div#check > div:not([class])\"\n        elif self.is_element_present(\".cf-turnstile-wrapper\"):\n            selector = \".cf-turnstile-wrapper\"\n        elif self.is_element_present(\n            '[id*=\"turnstile\"] div:not([class])'\n        ):\n            selector = '[id*=\"turnstile\"] div:not([class])'\n        elif self.is_element_present(\n            '[class*=\"turnstile\"] div:not([class])'\n        ):\n            selector = '[class*=\"turnstile\"] div:not([class])'\n        elif self.is_element_present(\n            \"iframe[data-hcaptcha-widget-id]\"\n        ):\n            selector = \"iframe[data-hcaptcha-widget-id]\"\n        elif self.is_element_present(\n            '[data-callback=\"onCaptchaSuccess\"]'\n        ):\n            selector = '[data-callback=\"onCaptchaSuccess\"]'\n        elif self.is_element_present(\n            \"div:not([class]):not([id]):not([aria-label]) > \"\n            \"div:not([class]):not([id]):not([aria-label])\"\n        ):\n            selector = (\n                \"div:not([class]):not([id]):not([aria-label]) > \"\n                \"div:not([class]):not([id]):not([aria-label])\"\n            )\n        else:\n            return False\n        if not selector:\n            return False\n        if (\n            self.is_element_present(\"form\")\n            and (\n                self.is_element_present('form[class*=\"center\"]')\n                or self.is_element_present('form[class*=\"right\"]')\n                or self.is_element_present('form div[class*=\"center\"]')\n                or self.is_element_present('form div[class*=\"right\"]')\n            )\n        ):\n            script = (\n                \"\"\"var $elements = document.querySelectorAll(\n                'form[class], form div[class]');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                the_class = $elements[index].getAttribute('class');\n                new_class = the_class.replaceAll('center', 'left');\n                new_class = new_class.replaceAll('right', 'left');\n                $elements[index].setAttribute('class', new_class);}\"\"\"\n            )\n            with suppress(Exception):\n                self.loop.run_until_complete(self.page.evaluate(script))\n                self.loop.run_until_complete(self.page.wait(0.1))\n        elif (\n            self.is_element_present(\"form\")\n            and (\n                self.is_element_present('form div[style*=\"center\"]')\n                or self.is_element_present('form div[style*=\"right\"]')\n            )\n        ):\n            script = (\n                \"\"\"var $elements = document.querySelectorAll(\n                'form[style], form div[style]');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                the_style = $elements[index].getAttribute('style');\n                new_style = the_style.replaceAll('center', 'left');\n                new_style = new_style.replaceAll('right', 'left');\n                $elements[index].setAttribute('style', new_style);}\"\"\"\n            )\n            with suppress(Exception):\n                self.loop.run_until_complete(self.page.evaluate(script))\n                self.loop.run_until_complete(self.page.wait(0.1))\n        elif (\n            self.is_element_present(\n                'form [id*=\"turnstile\"] div:not([class])'\n            )\n            or self.is_element_present(\n                'form [class*=\"turnstile\"] div:not([class])'\n            )\n        ):\n            script = (\n                \"\"\"var $elements = document.querySelectorAll(\n                'form [id*=\"turnstile\"]');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                $elements[index].setAttribute('align', 'left');}\n                var $elements = document.querySelectorAll(\n                'form [class*=\"turnstile\"]');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                $elements[index].setAttribute('align', 'left');}\"\"\"\n            )\n            with suppress(Exception):\n                self.loop.run_until_complete(self.page.evaluate(script))\n                self.loop.run_until_complete(self.page.wait(0.1))\n        elif (\n            self.is_element_present(\n                '[style*=\"text-align: center;\"] div:not([class])'\n            )\n        ):\n            script = (\n                \"\"\"var $elements = document.querySelectorAll(\n                '[style*=\"text-align: center;\"]');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                the_style = $elements[index].getAttribute('style');\n                new_style = the_style.replaceAll('center', 'left');\n                $elements[index].setAttribute('style', new_style);}\"\"\"\n            )\n            with suppress(Exception):\n                self.loop.run_until_complete(self.page.evaluate(script))\n                self.loop.run_until_complete(self.page.wait(0.1))\n        with suppress(Exception):\n            time.sleep(0.05)\n            element_rect = self.get_gui_element_rect(selector, timeout=1)\n            e_x = element_rect[\"x\"]\n            e_y = element_rect[\"y\"]\n            x_offset = 28\n            y_offset = 32\n            if shared_utils.is_windows():\n                y_offset = 28\n            x = e_x + x_offset\n            y = e_y + y_offset\n            sb_config._saved_cf_x_y = (x, y)\n            time.sleep(0.05)\n            if hasattr(sb_config, \"_cdp_proxy\") and sb_config._cdp_proxy:\n                time.sleep(0.22)  # CAPTCHA may load slower with proxy\n            if use_cdp:\n                self.sleep(0.03)\n                gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n                with gui_lock:  # Prevent issues with multiple processes\n                    self.bring_active_window_to_front()\n                    time.sleep(0.05)\n                    self.click_with_offset(selector, x_offset, y_offset)\n                    time.sleep(0.05)\n            else:\n                self.gui_click_x_y(x, y)\n            return True\n        return False\n\n    def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False):\n        self.__install_pyautogui_if_missing()\n        import pyautogui\n        pyautogui = self.__get_configured_pyautogui(pyautogui)\n        screen_width, screen_height = pyautogui.size()\n        if x1 < 0 or y1 < 0 or x1 > screen_width or y1 > screen_height:\n            raise Exception(\n                \"PyAutoGUI cannot drag-drop from point (%s, %s)\"\n                \" outside screen. (Width: %s, Height: %s)\"\n                % (x1, y1, screen_width, screen_height)\n            )\n        if x2 < 0 or y2 < 0 or x2 > screen_width or y2 > screen_height:\n            raise Exception(\n                \"PyAutoGUI cannot drag-drop to point (%s, %s)\"\n                \" outside screen. (Width: %s, Height: %s)\"\n                % (x2, y2, screen_width, screen_height)\n            )\n        if uc_lock:\n            gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n            with gui_lock:  # Prevent issues with multiple processes\n                if \"--debug\" in sys.argv:\n                    print(\" <DEBUG> pyautogui.moveTo(%s, %s)\" % (x1, y1))\n                pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)\n                self.__add_light_pause()\n                if \"--debug\" in sys.argv:\n                    print(\" <DEBUG> pyautogui.dragTo(%s, %s)\" % (x2, y2))\n                pyautogui.dragTo(x2, y2, button=\"left\", duration=timeframe)\n        else:\n            # Called from a method where the gui_lock is already active\n            if \"--debug\" in sys.argv:\n                print(\" <DEBUG> pyautogui.moveTo(%s, %s)\" % (x1, y1))\n            pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)\n            self.__add_light_pause()\n            if \"--debug\" in sys.argv:\n                print(\" <DEBUG> pyautogui.dragTo(%s, %s)\" % (x2, y2))\n            pyautogui.dragTo(x2, y2, button=\"left\", duration=timeframe)\n\n    def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35):\n        \"\"\"Use PyAutoGUI to drag-and-drop from one point to another.\n        Can simulate click-and-hold when using the same point twice.\"\"\"\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:  # Prevent issues with multiple processes\n            self.__install_pyautogui_if_missing()\n            import pyautogui\n            pyautogui = self.__get_configured_pyautogui(pyautogui)\n            width_ratio = 1.0\n            if shared_utils.is_windows():\n                window_rect = self.get_window_rect()\n                width = window_rect[\"width\"]\n                height = window_rect[\"height\"]\n                win_x = window_rect[\"x\"]\n                win_y = window_rect[\"y\"]\n                scr_width = pyautogui.size().width\n                win_width = self.evaluate(\"screen.availWidth;\")\n                width_ratio = round(float(scr_width) / float(win_width), 2)\n                width_ratio += 0.01\n                if width_ratio < 0.45 or width_ratio > 2.55:\n                    width_ratio = 1.01\n                sb_config._saved_width_ratio = width_ratio\n                with suppress(Exception):\n                    self.get_window()  # If this fails, skip the rest\n                    self.minimize()\n                    self.__add_light_pause()\n                    self.__set_window_rect(win_x, win_y, width, height)\n                    self.__add_light_pause()\n                x1 = x1 * width_ratio\n                y1 = y1 * (width_ratio - 0.02)\n                x2 = x2 * width_ratio\n                y2 = y2 * (width_ratio - 0.02)\n            self.bring_active_window_to_front()\n            self.__gui_drag_drop(\n                x1, y1, x2, y2, timeframe=timeframe, uc_lock=False\n            )\n        self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.2))\n\n    def gui_drag_and_drop(self, drag_selector, drop_selector, timeframe=0.35):\n        \"\"\"Use PyAutoGUI to drag-and-drop from one selector to another.\n        Can simulate click-and-hold when using the same selector twice.\"\"\"\n        self.__slow_mode_pause_if_set()\n        self.bring_active_window_to_front()\n        x1, y1 = self.get_gui_element_center(drag_selector)\n        self.__add_light_pause()\n        x2, y2 = self.get_gui_element_center(drop_selector)\n        self.__add_light_pause()\n        self.gui_drag_drop_points(x1, y1, x2, y2, timeframe=timeframe)\n\n    def gui_click_and_hold(self, selector, timeframe=0.35):\n        \"\"\"Use PyAutoGUI to click-and-hold a selector.\"\"\"\n        self.__slow_mode_pause_if_set()\n        self.bring_active_window_to_front()\n        x, y = self.get_gui_element_center(selector)\n        self.__add_light_pause()\n        self.gui_drag_drop_points(x, y, x, y, timeframe=timeframe)\n\n    def __gui_hover_x_y(self, x, y, timeframe=0.25, uc_lock=False):\n        self.__install_pyautogui_if_missing()\n        import pyautogui\n        pyautogui = self.__get_configured_pyautogui(pyautogui)\n        screen_width, screen_height = pyautogui.size()\n        if x < 0 or y < 0 or x > screen_width or y > screen_height:\n            raise Exception(\n                \"PyAutoGUI cannot hover on point (%s, %s)\"\n                \" outside screen. (Width: %s, Height: %s)\"\n                % (x, y, screen_width, screen_height)\n            )\n        if uc_lock:\n            gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n            with gui_lock:  # Prevent issues with multiple processes\n                if \"--debug\" in sys.argv:\n                    print(\" <DEBUG> pyautogui.moveTo(%s, %s)\" % (x, y))\n                pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)\n                time.sleep(0.056)\n        else:\n            # Called from a method where the gui_lock is already active\n            if \"--debug\" in sys.argv:\n                print(\" <DEBUG> pyautogui.moveTo(%s, %s)\" % (x, y))\n            pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)\n            time.sleep(0.056)\n\n    def gui_hover_x_y(self, x, y, timeframe=0.25):\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:  # Prevent issues with multiple processes\n            self.__install_pyautogui_if_missing()\n            import pyautogui\n            pyautogui = self.__get_configured_pyautogui(pyautogui)\n            width_ratio = 1.0\n            if (\n                shared_utils.is_windows()\n                and (\n                    not hasattr(sb_config, \"_saved_width_ratio\")\n                    or not sb_config._saved_width_ratio\n                )\n            ):\n                window_rect = self.get_window_rect()\n                width = window_rect[\"width\"]\n                height = window_rect[\"height\"]\n                win_x = window_rect[\"x\"]\n                win_y = window_rect[\"y\"]\n                if (\n                    hasattr(sb_config, \"_saved_width_ratio\")\n                    and sb_config._saved_width_ratio\n                ):\n                    width_ratio = sb_config._saved_width_ratio\n                else:\n                    scr_width = pyautogui.size().width\n                    win_width = self.evaluate(\"screen.availWidth;\")\n                    width_ratio = round(float(scr_width) / float(win_width), 2)\n                    width_ratio += 0.01\n                    if width_ratio < 0.45 or width_ratio > 2.55:\n                        width_ratio = 1.01\n                    sb_config._saved_width_ratio = width_ratio\n                with suppress(Exception):\n                    self.__set_window_rect(win_x, win_y, width, height)\n                self.__add_light_pause()\n                self.bring_active_window_to_front()\n            elif (\n                shared_utils.is_windows()\n                and hasattr(sb_config, \"_saved_width_ratio\")\n                and sb_config._saved_width_ratio\n            ):\n                width_ratio = sb_config._saved_width_ratio\n                self.bring_active_window_to_front()\n            if shared_utils.is_windows():\n                x = x * width_ratio\n                y = y * width_ratio\n                self.__gui_hover_x_y(x, y, timeframe=timeframe, uc_lock=False)\n                return\n            self.bring_active_window_to_front()\n            self.__gui_hover_x_y(x, y, timeframe=timeframe, uc_lock=False)\n\n    def gui_hover_element(self, selector, timeframe=0.25):\n        self.__slow_mode_pause_if_set()\n        element_rect = self.get_gui_element_rect(selector)\n        width = element_rect[\"width\"]\n        height = element_rect[\"height\"]\n        if width > 0 and height > 0:\n            x, y = self.get_gui_element_center(selector)\n            self.bring_active_window_to_front()\n            self.__gui_hover_x_y(x, y, timeframe=timeframe, uc_lock=False)\n            self.__slow_mode_pause_if_set()\n        self.loop.run_until_complete(self.page.wait(0.1))\n\n    def hover_element(self, selector, timeframe=0.25):\n        element = self.select(selector)\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:\n            self.bring_active_window_to_front()\n            self.sleep(0.02)\n            element.mouse_move()\n            self.sleep(timeframe)\n\n    def hover_and_click(self, hover_selector, click_selector):\n        if getattr(sb_config, \"_cdp_mobile_mode\", None):\n            self.select(click_selector).click()\n            return\n        hover_element = self.select(hover_selector)\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:\n            self.bring_active_window_to_front()\n            self.sleep(0.02)\n            hover_element.mouse_move()\n            self.sleep(0.25)\n            try:\n                self.click(click_selector, timeout=0.5)\n            except Exception:\n                self.select(click_selector, timeout=2).click()\n\n    def gui_hover_and_click(self, hover_selector, click_selector):\n        if getattr(sb_config, \"_cdp_mobile_mode\", None):\n            self.select(click_selector).click()\n            return\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:\n            self.__make_sure_pyautogui_lock_is_writable()\n            self.bring_active_window_to_front()\n            self.gui_hover_element(hover_selector)\n            time.sleep(0.15)\n            self.gui_hover_element(click_selector)\n            self.click(click_selector)\n\n    def internalize_links(self):\n        \"\"\"All `target=\"_blank\"` links become `target=\"_self\"`.\n        This prevents those links from opening in a new tab.\"\"\"\n        self.set_attributes('[target=\"_blank\"]', \"target\", \"_self\")\n\n    def is_checked(self, selector):\n        \"\"\"Return True if checkbox (or radio button) is checked.\"\"\"\n        selector = self.__convert_to_css_if_xpath(selector)\n        self.find_element(selector, timeout=settings.SMALL_TIMEOUT)\n        return bool(self.get_element_attribute(selector, \"checked\"))\n\n    def is_selected(self, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        return self.is_checked(selector)\n\n    def check_if_unchecked(self, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        if not self.is_checked(selector):\n            self.click(selector)\n\n    def select_if_unselected(self, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        self.check_if_unchecked(selector)\n\n    def uncheck_if_checked(self, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        if self.is_checked(selector):\n            self.click(selector)\n\n    def unselect_if_selected(self, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        self.uncheck_if_checked(selector)\n\n    def is_element_present(self, selector):\n        try:\n            self.select(selector, timeout=0.01)\n            return True\n        except Exception:\n            return False\n\n    def is_element_visible(self, selector):\n        selector = self.__convert_to_css_if_xpath(selector)\n        element = None\n        if \":contains(\" not in selector:\n            try:\n                element = self.select(selector, timeout=0.01)\n            except Exception:\n                return False\n            if not element:\n                return False\n            try:\n                position = element.get_position()\n                return (position.width != 0 or position.height != 0)\n            except Exception:\n                return False\n        else:\n            with suppress(Exception):\n                tag_name = selector.split(\":contains(\")[0].split(\" \")[-1]\n                text = selector.split(\":contains(\")[1].split(\")\")[0][1:-1]\n                self.loop.run_until_complete(\n                    self.page.select(tag_name, timeout=0.1)\n                )\n                self.loop.run_until_complete(self.page.find(text, timeout=0.1))\n                return True\n            return False\n\n    def is_text_visible(self, text, selector=\"body\"):\n        selector = self.__convert_to_css_if_xpath(selector)\n        text = text.strip()\n        element = None\n        try:\n            element = self.find_element(selector, timeout=0.1)\n        except Exception:\n            return False\n        with suppress(Exception):\n            if text in element.text_all:\n                return True\n        return False\n\n    def is_exact_text_visible(self, text, selector=\"body\"):\n        selector = self.__convert_to_css_if_xpath(selector)\n        text = text.strip()\n        element = None\n        try:\n            element = self.find_element(selector, timeout=0.1)\n        except Exception:\n            return False\n        with suppress(Exception):\n            if text == element.text_all.strip():\n                return True\n        return False\n\n    def wait_for_text(self, text, selector=\"body\", timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        text = text.strip()\n        element = None\n        try:\n            element = self.find_element(selector, timeout=timeout)\n        except Exception:\n            raise Exception(\"Element {%s} not found!\" % selector)\n        for i in range(int(timeout * 10)):\n            with suppress(Exception):\n                element = self.find_element(selector, timeout=0.1)\n            if text in element.text_all:\n                return True\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        raise Exception(\n            \"Text {%s} not found in {%s}! Actual text: {%s}\"\n            % (text, selector, element.text_all)\n        )\n\n    def wait_for_text_not_visible(self, text, selector=\"body\", timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        text = text.strip()\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        for i in range(int(timeout * 10)):\n            if not self.is_text_visible(text, selector):\n                return True\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        raise Exception(\n            \"Text {%s} in {%s} was still visible after %s second%s!\"\n            % (text, selector, timeout, plural)\n        )\n\n    def wait_for_element_visible(self, selector, timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        try:\n            self.select(selector, timeout=timeout)\n        except Exception:\n            raise Exception(\"Element {%s} was not found!\" % selector)\n        for i in range(30):\n            if self.is_element_visible(selector):\n                return self.select(selector)\n            time.sleep(0.1)\n        raise Exception(\"Element {%s} was not visible!\" % selector)\n\n    def wait_for_element(self, selector, **kwargs):\n        \"\"\"Same as wait_for_element_visible()\"\"\"\n        return self.wait_for_element_visible(selector, **kwargs)\n\n    def wait_for_element_not_visible(self, selector, timeout=None):\n        \"\"\"Wait for element to not be visible on page. (May still be in DOM)\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        for i in range(int(timeout * 10)):\n            if not self.is_element_present(selector):\n                return True\n            elif not self.is_element_visible(selector):\n                return True\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        raise Exception(\n            \"Element {%s} was still visible after %s second%s!\"\n            % (selector, timeout, plural)\n        )\n\n    def wait_for_element_absent(self, selector, timeout=None):\n        \"\"\"Wait for element to not be present in the DOM.\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        for i in range(int(timeout * 10)):\n            if not self.is_element_present(selector):\n                return True\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        raise Exception(\n            \"Element {%s} was still present after %s second%s!\"\n            % (selector, timeout, plural)\n        )\n\n    def wait_for_any_of_elements_visible(self, *args, **kwargs):\n        \"\"\"Waits for at least one of the elements to be visible.\n        Returns the first element that is found.\n        The input is a list of elements. (Should be CSS selectors)\n        Optional kwargs include: \"timeout\" (used by all selectors).\n        Raises an exception if no elements are visible by the timeout.\n        Examples:\n            sb.cdp.wait_for_any_of_elements_visible(\"h1\", \"h2\", \"h3\")\n            OR\n            sb.cdp.wait_for_any_of_elements_visible([\"h1\", \"h2\", \"h3\"]) \"\"\"\n        selectors = []\n        timeout = None\n        for kwarg in kwargs:\n            if kwarg == \"timeout\":\n                timeout = kwargs[\"timeout\"]\n            elif kwarg == \"by\":\n                pass  # Autodetected\n            elif kwarg == \"selector\" or kwarg == \"selectors\":\n                selector = kwargs[kwarg]\n                if isinstance(selector, str):\n                    selectors.append(selector)\n                elif isinstance(selector, list):\n                    selectors_list = selector\n                    for selector in selectors_list:\n                        if isinstance(selector, str):\n                            selectors.append(selector)\n            else:\n                raise Exception('Unknown kwarg: \"%s\"!' % kwarg)\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        for arg in args:\n            if isinstance(arg, list):\n                for selector in arg:\n                    if isinstance(selector, str):\n                        selectors.append(selector)\n            elif isinstance(arg, str):\n                selectors.append(arg)\n        if not selectors:\n            raise Exception(\"The selectors list was empty!\")\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        any_present = False\n        for i in range(int(timeout * 10)):\n            for selector in selectors:\n                if self.is_element_visible(selector):\n                    return self.select(selector)\n                if self.is_element_present(selector):\n                    any_present = True\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        if not any_present:\n            # None of the elements exist in the HTML\n            raise Exception(\n                \"None of the elements {%s} were present after %s second%s!\" % (\n                    str(selectors),\n                    timeout,\n                    plural,\n                )\n            )\n        raise Exception(\n            \"None of the elements %s were visible after %s second%s!\" % (\n                str(selectors),\n                timeout,\n                plural,\n            )\n        )\n\n    def wait_for_any_of_elements_present(self, *args, **kwargs):\n        \"\"\"Waits for at least one of the elements to be present.\n        Visibility not required, but element must be in the DOM.\n        Returns the first element that is found.\n        The input is a list of elements. (Should be CSS selectors)\n        Optional kwargs include: \"timeout\" (used by all selectors).\n        Raises an exception if no elements are present by the timeout.\n        Examples:\n            self.wait_for_any_of_elements_present(\"style\", \"script\")\n            OR\n            self.wait_for_any_of_elements_present([\"style\", \"script\"]) \"\"\"\n        selectors = []\n        timeout = None\n        for kwarg in kwargs:\n            if kwarg == \"timeout\":\n                timeout = kwargs[\"timeout\"]\n            elif kwarg == \"by\":\n                pass  # Autodetected\n            elif kwarg == \"selector\" or kwarg == \"selectors\":\n                selector = kwargs[kwarg]\n                if isinstance(selector, str):\n                    selectors.append(selector)\n                elif isinstance(selector, list):\n                    selectors_list = selector\n                    for selector in selectors_list:\n                        if isinstance(selector, str):\n                            selectors.append(selector)\n            else:\n                raise Exception('Unknown kwarg: \"%s\"!' % kwarg)\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        for arg in args:\n            if isinstance(arg, list):\n                for selector in arg:\n                    if isinstance(selector, str):\n                        selectors.append(selector)\n            elif isinstance(arg, str):\n                selectors.append(arg)\n        if not selectors:\n            raise Exception(\"The selectors list was empty!\")\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        for i in range(int(timeout * 10)):\n            for selector in selectors:\n                if self.is_element_present(selector):\n                    return self.select(selector)\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        # None of the elements exist in the HTML\n        raise Exception(\n            \"None of the elements %s were present after %s second%s!\" % (\n                str(selectors),\n                timeout,\n                plural,\n            )\n        )\n\n    def assert_any_of_elements_visible(self, *args, **kwargs):\n        \"\"\"Like wait_for_any_of_elements_visible(), but returns nothing.\"\"\"\n        self.wait_for_any_of_elements_visible(*args, **kwargs)\n        return True\n\n    def assert_any_of_elements_present(self, *args, **kwargs):\n        \"\"\"Like wait_for_any_of_elements_present(), but returns nothing.\"\"\"\n        self.wait_for_any_of_elements_present(*args, **kwargs)\n        return True\n\n    def assert_element(self, selector, timeout=None):\n        \"\"\"Same as assert_element_visible()\"\"\"\n        self.assert_element_visible(selector, timeout=timeout)\n        return True\n\n    def assert_element_visible(self, selector, timeout=None):\n        \"\"\"Same as assert_element()\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        try:\n            self.select(selector, timeout=timeout)\n        except Exception:\n            raise Exception(\"Element {%s} was not found!\" % selector)\n        for i in range(30):\n            if self.is_element_visible(selector):\n                return True\n            time.sleep(0.1)\n        raise Exception(\"Element {%s} was not visible!\" % selector)\n\n    def assert_element_present(self, selector, timeout=None):\n        \"\"\"Assert element is present in the DOM. (Visibility NOT required)\"\"\"\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        try:\n            self.select(selector, timeout=timeout)\n        except Exception:\n            raise Exception(\"Element {%s} was not found!\" % selector)\n        return True\n\n    def assert_element_absent(self, selector, timeout=None):\n        \"\"\"Assert element is not present in the DOM.\"\"\"\n        self.wait_for_element_absent(selector, timeout=timeout)\n        return True\n\n    def assert_element_not_visible(self, selector, timeout=None):\n        \"\"\"Assert element is not visible on page. (May still be in DOM)\"\"\"\n        self.wait_for_element_not_visible(selector, timeout=timeout)\n        return True\n\n    def assert_element_attribute(self, selector, attribute, value=None):\n        attributes = self.get_element_attributes(selector)\n        if attribute not in attributes:\n            raise Exception(\n                \"Attribute {%s} was not found in element {%s}!\"\n                % (attribute, selector)\n            )\n        if value and attributes[attribute] != value:\n            raise Exception(\n                \"Expected value {%s} of attribute {%s} \"\n                \"was not found in element {%s}! \"\n                \"(Actual value was {%s})\"\n                % (value, attribute, selector, attributes[attribute])\n            )\n\n    def assert_title(self, title):\n        expected = title.strip()\n        actual = self.get_title().strip()\n        error = (\n            \"Expected page title [%s] does not match the actual title [%s]!\"\n        )\n        try:\n            if expected != actual:\n                raise Exception(error % (expected, actual))\n        except Exception:\n            time.sleep(2)\n            actual = self.get_title().strip()\n            if expected != actual:\n                raise Exception(error % (expected, actual))\n\n    def assert_title_contains(self, substring):\n        expected = substring.strip()\n        actual = self.get_title().strip()\n        error = (\n            \"Expected title substring [%s] does not appear \"\n            \"in the actual page title [%s]!\"\n        )\n        try:\n            if expected not in actual:\n                raise Exception(error % (expected, actual))\n        except Exception:\n            time.sleep(2)\n            actual = self.get_title().strip()\n            if expected not in actual:\n                raise Exception(error % (expected, actual))\n\n    def assert_url(self, url):\n        expected = url.strip()\n        actual = self.get_current_url().strip()\n        error = \"Expected URL [%s] does not match the actual URL [%s]!\"\n        try:\n            if expected != actual:\n                raise Exception(error % (expected, actual))\n        except Exception:\n            time.sleep(2)\n            actual = self.get_current_url().strip()\n            if expected != actual:\n                raise Exception(error % (expected, actual))\n\n    def assert_url_contains(self, substring):\n        expected = substring.strip()\n        actual = self.get_current_url().strip()\n        error = (\n            \"Expected URL substring [%s] does not appear \"\n            \"in the full URL [%s]!\"\n        )\n        try:\n            if expected not in actual:\n                raise Exception(error % (expected, actual))\n        except Exception:\n            time.sleep(2)\n            actual = self.get_current_url().strip()\n            if expected not in actual:\n                raise Exception(error % (expected, actual))\n\n    def assert_text(self, text, selector=\"body\", timeout=None):\n        \"\"\"Same as wait_for_text()\"\"\"\n        self.wait_for_text(text, selector=selector, timeout=timeout)\n        return True\n\n    def assert_exact_text(self, text, selector=\"body\", timeout=None):\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        text = text.strip()\n        element = None\n        try:\n            element = self.select(selector, timeout=timeout)\n        except Exception:\n            raise Exception(\"Element {%s} not found!\" % selector)\n        for i in range(int(timeout * 10)):\n            with suppress(Exception):\n                element = self.select(selector, timeout=0.1)\n            if (\n                self.is_element_visible(selector)\n                and text.strip() == element.text_all.strip()\n            ):\n                return True\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        raise Exception(\n            \"Expected Text {%s}, is not equal to {%s} in {%s}!\"\n            % (text, element.text_all, selector)\n        )\n\n    def assert_text_not_visible(self, text, selector=\"body\", timeout=None):\n        \"\"\"Raises an exception if the text is still visible after timeout.\"\"\"\n        self.wait_for_text_not_visible(\n            text, selector=selector, timeout=timeout\n        )\n        return True\n\n    def assert_true(self, expression):\n        if not expression:\n            raise AssertionError(\"%s is not true\" % expression)\n\n    def assert_false(self, expression):\n        if expression:\n            raise AssertionError(\"%s is not false\" % expression)\n\n    def assert_equal(self, first, second):\n        if first != second:\n            raise AssertionError(\"%s is not equal to %s\" % (first, second))\n\n    def assert_not_equal(self, first, second):\n        if first == second:\n            raise AssertionError(\"%s is equal to %s\" % (first, second))\n\n    def assert_in(self, first, second):\n        if first not in second:\n            raise AssertionError(\"%s is not in %s\" % (first, second))\n\n    def assert_not_in(self, first, second):\n        if first in second:\n            raise AssertionError(\"%s is in %s\" % (first, second))\n\n    def scroll_into_view(self, selector):\n        self.find_element(selector).scroll_into_view()\n        self.loop.run_until_complete(self.page.wait(0.1))\n\n    def scroll_to_y(self, y):\n        y = int(y)\n        js_code = \"window.scrollTo(0, %s);\" % y\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n            self.loop.run_until_complete(self.page.wait(0.1))\n\n    def scroll_by_y(self, y):\n        y = int(y)\n        js_code = \"window.scrollBy(0, %s);\" % y\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n            self.loop.run_until_complete(self.page.wait(0.1))\n\n    def scroll_to_top(self):\n        js_code = \"window.scrollTo(0, 0);\"\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n            self.loop.run_until_complete(self.page.wait(0.1))\n\n    def scroll_to_bottom(self):\n        js_code = \"window.scrollTo(0, 10000);\"\n        with suppress(Exception):\n            self.loop.run_until_complete(self.page.evaluate(js_code))\n            self.loop.run_until_complete(self.page.wait(0.1))\n\n    def scroll_up(self, amount=25):\n        \"\"\"Scrolls up as a percentage of the page.\"\"\"\n        try:\n            self.loop.run_until_complete(self.page.scroll_up(amount))\n        except Exception:\n            amount = self.get_window_size()[\"height\"] * amount / 100\n            self.execute_script(\"window.scrollBy(0, -%s);\" % amount)\n        self.loop.run_until_complete(self.page.wait(0.1))\n\n    def scroll_down(self, amount=25):\n        \"\"\"Scrolls down as a percentage of the page.\"\"\"\n        try:\n            self.loop.run_until_complete(self.page.scroll_down(amount))\n        except Exception:\n            amount = self.get_window_size()[\"height\"] * amount / 100\n            self.execute_script(\"window.scrollBy(0, %s);\" % amount)\n        self.loop.run_until_complete(self.page.wait(0.1))\n\n    def save_page_source(self, name, folder=None):\n        from seleniumbase.core import log_helper\n        if not name.endswith(\".html\"):\n            name = name + \".html\"\n        if folder:\n            abs_path = os.path.abspath(\".\")\n            file_path = os.path.join(abs_path, folder)\n            if not os.path.exists(file_path):\n                os.makedirs(file_path)\n            html_file_path = os.path.join(file_path, name)\n        else:\n            html_file_path = name\n        page_source = self.get_page_source()\n        last_page = self.get_current_url()\n        meta_charset = '<meta charset=\"utf-8\">'\n        rendered_source = \"\"\n        if \"://\" in last_page:\n            base_href_html = log_helper.get_base_href_html(last_page)\n            if ' charset=\"' not in page_source:\n                rendered_source = \"%s\\n%s\\n%s\" % (\n                    base_href_html, meta_charset, page_source\n                )\n            else:\n                rendered_source = \"%s\\n%s\" % (base_href_html, page_source)\n        else:\n            rendered_source = page_source\n        html_file = open(html_file_path, mode=\"w+\", encoding=\"utf-8\")\n        html_file.write(rendered_source)\n        html_file.close()\n\n    def save_as_html(self, *args, **kwargs):\n        self.save_page_source(*args, **kwargs)\n\n    def save_screenshot(self, name, folder=None, selector=None):\n        filename = name\n        if folder:\n            filename = os.path.join(folder, name)\n        if not selector:\n            self.loop.run_until_complete(\n                self.page.save_screenshot(filename)\n            )\n        else:\n            self.select(selector).save_screenshot(filename)\n\n    def print_to_pdf(self, name, folder=None):\n        filename = name\n        if folder:\n            filename = os.path.join(folder, name)\n        self.loop.run_until_complete(self.page.print_to_pdf(filename))\n\n    def save_as_pdf(self, *args, **kwargs):\n        self.print_to_pdf(*args, **kwargs)\n\n\nclass Chrome(CDPMethods):\n    def __init__(self, url=None, **kwargs):\n        if not url:\n            url = \"about:blank\"\n        driver = cdp_util.start_sync(**kwargs)\n        loop = asyncio.new_event_loop()\n        page = loop.run_until_complete(driver.get(url))\n        wait_timeout = 30.0\n        if hasattr(sb_config, \"_cdp_proxy\") and sb_config._cdp_proxy:\n            wait_timeout = 45.0\n        try:\n            loop.run_until_complete(\n                asyncio.wait_for(page.wait(), timeout=wait_timeout)\n            )\n        except asyncio.TimeoutError:\n            pass\n        except Exception:\n            pass\n        super().__init__(loop, page, driver)\n"
  },
  {
    "path": "seleniumbase/core/sb_driver.py",
    "content": "\"\"\"Add new methods to extend the driver\"\"\"\nfrom contextlib import suppress\nfrom selenium.webdriver.remote.webdriver import WebDriver\nfrom selenium.webdriver.remote.webelement import WebElement\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import js_utils\nfrom seleniumbase.fixtures import page_actions\nfrom seleniumbase.fixtures import page_utils\nfrom seleniumbase.fixtures import shared_utils\n\n\nclass DriverMethods(WebDriver):\n    def __init__(self, driver):\n        self.driver = driver\n        if hasattr(driver, \"session_id\"):\n            self.session_id = driver.session_id\n        if hasattr(driver, \"command_executor\"):\n            self.command_executor = driver.command_executor\n\n    def __is_cdp_swap_needed(self):\n        \"\"\"If the driver is disconnected, use a CDP method when available.\"\"\"\n        return shared_utils.is_cdp_swap_needed(self.driver)\n\n    def find_element(self, by=None, value=None):\n        if not value:\n            value = by\n            by = \"css selector\"\n        elif not by:\n            by = \"css selector\"\n        else:\n            value, by = page_utils.swap_selector_and_by_if_reversed(value, by)\n        return self.driver.default_find_element(by=by, value=value)\n\n    def find_elements(self, by=None, value=None):\n        if not value:\n            value = by\n            by = \"css selector\"\n        elif not by:\n            by = \"css selector\"\n        else:\n            value, by = page_utils.swap_selector_and_by_if_reversed(value, by)\n        return self.driver.default_find_elements(by=by, value=value)\n\n    def add_cookie(self, *args, **kwargs):\n        page_actions._reconnect_if_disconnected(self.driver)\n        self.driver.default_add_cookie(*args, **kwargs)\n\n    def get_cookie(self, *args, **kwargs):\n        page_actions._reconnect_if_disconnected(self.driver)\n        self.driver.default_get_cookie(*args, **kwargs)\n\n    def delete_cookie(self, *args, **kwargs):\n        page_actions._reconnect_if_disconnected(self.driver)\n        self.driver.default_delete_cookie(*args, **kwargs)\n\n    def back(self):\n        if self.__is_cdp_swap_needed():\n            self.driver.cdp.go_back()\n            return\n        self.driver.default_back()\n\n    def forward(self):\n        if self.__is_cdp_swap_needed():\n            self.driver.cdp.go_forward()\n            return\n        self.driver.default_forward()\n\n    def refresh(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            self.driver.cdp.refresh(*args, **kwargs)\n            return\n        self.driver.default_refresh()\n\n    def locator(self, selector, by=None):\n        if not by:\n            by = \"css selector\"\n        else:\n            selector, by = page_utils.swap_selector_and_by_if_reversed(\n                selector, by\n            )\n        with suppress(Exception):\n            return self.driver.default_find_element(by=by, value=selector)\n        raise Exception('No such Element: {%s} (by=\"%s\")!' % (selector, by))\n\n    def get_attribute(self, selector, attribute, by=\"css selector\"):\n        element = self.locator(selector, by=by)\n        return element.get_attribute(attribute)\n\n    def get_parent(self, element):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_parent(element)\n        if isinstance(element, str):\n            element = self.locator(element)\n        return element.find_element(by=\"xpath\", value=\"..\")\n\n    def get_current_url(self):\n        if self.__is_cdp_swap_needed():\n            current_url = self.driver.cdp.get_current_url()\n        else:\n            current_url = self.driver.current_url\n        return current_url\n\n    def get_page_source(self):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_page_source()\n        return self.driver.page_source\n\n    def get_title(self):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_title()\n        return self.driver.title\n\n    def open_url(self, *args, **kwargs):\n        page_actions.open_url(self.driver, *args, **kwargs)\n\n    def click(self, *args, **kwargs):\n        page_actions.click(self.driver, *args, **kwargs)\n\n    def click_link(self, *args, **kwargs):\n        page_actions.click_link(self.driver, *args, **kwargs)\n\n    def click_if_visible(self, *args, **kwargs):\n        page_actions.click_if_visible(self.driver, *args, **kwargs)\n\n    def click_active_element(self, *args, **kwargs):\n        page_actions.click_active_element(self.driver, *args, **kwargs)\n\n    def send_keys(self, *args, **kwargs):\n        page_actions.send_keys(self.driver, *args, **kwargs)\n\n    def press_keys(self, *args, **kwargs):\n        page_actions.press_keys(self.driver, *args, **kwargs)\n\n    def update_text(self, *args, **kwargs):\n        page_actions.update_text(self.driver, *args, **kwargs)\n\n    def submit(self, *args, **kwargs):\n        page_actions.submit(self.driver, *args, **kwargs)\n\n    def assert_element_visible(self, *args, **kwargs):\n        page_actions.assert_element_visible(self.driver, *args, **kwargs)\n\n    def assert_element_present(self, *args, **kwargs):\n        page_actions.assert_element_present(self.driver, *args, **kwargs)\n\n    def assert_element_not_visible(self, *args, **kwargs):\n        page_actions.assert_element_not_visible(self.driver, *args, **kwargs)\n\n    def assert_text(self, *args, **kwargs):\n        page_actions.assert_text(self.driver, *args, **kwargs)\n\n    def assert_exact_text(self, *args, **kwargs):\n        page_actions.assert_exact_text(self.driver, *args, **kwargs)\n\n    def assert_non_empty_text(self, *args, **kwargs):\n        return page_actions.assert_non_empty_text(\n            self.driver, *args, **kwargs\n        )\n\n    def assert_text_not_visible(self, *args, **kwargs):\n        return page_actions.assert_text_not_visible(\n            self.driver, *args, **kwargs\n        )\n\n    def wait_for_element(self, *args, **kwargs):\n        return page_actions.wait_for_element(self.driver, *args, **kwargs)\n\n    def wait_for_element_visible(self, *args, **kwargs):\n        return page_actions.wait_for_element(self.driver, *args, **kwargs)\n\n    def wait_for_element_present(self, *args, **kwargs):\n        return page_actions.wait_for_selector(self.driver, *args, **kwargs)\n\n    def wait_for_element_absent(self, *args, **kwargs):\n        return page_actions.wait_for_element_absent(\n            self.driver, *args, **kwargs\n        )\n\n    def wait_for_element_not_visible(self, *args, **kwargs):\n        return page_actions.wait_for_element_not_visible(\n            self.driver, *args, **kwargs\n        )\n\n    def wait_for_selector(self, *args, **kwargs):\n        return page_actions.wait_for_selector(self.driver, *args, **kwargs)\n\n    def wait_for_text(self, *args, **kwargs):\n        return page_actions.wait_for_text(self.driver, *args, **kwargs)\n\n    def wait_for_exact_text(self, *args, **kwargs):\n        return page_actions.wait_for_exact_text(self.driver, *args, **kwargs)\n\n    def wait_for_non_empty_text(self, *args, **kwargs):\n        return page_actions.wait_for_non_empty_text(\n            self.driver, *args, **kwargs\n        )\n\n    def wait_for_text_not_visible(self, *args, **kwargs):\n        return page_actions.wait_for_text_not_visible(\n            self.driver, *args, **kwargs\n        )\n\n    def wait_for_and_accept_alert(self, *args, **kwargs):\n        return page_actions.wait_for_and_accept_alert(\n            self.driver, *args, **kwargs\n        )\n\n    def wait_for_and_dismiss_alert(self, *args, **kwargs):\n        return page_actions.wait_for_and_dismiss_alert(\n            self.driver, *args, **kwargs\n        )\n\n    def is_element_present(self, *args, **kwargs):\n        return page_actions.is_element_present(self.driver, *args, **kwargs)\n\n    def is_element_visible(self, *args, **kwargs):\n        return page_actions.is_element_visible(self.driver, *args, **kwargs)\n\n    def is_text_visible(self, *args, **kwargs):\n        return page_actions.is_text_visible(self.driver, *args, **kwargs)\n\n    def is_exact_text_visible(self, *args, **kwargs):\n        return page_actions.is_exact_text_visible(self.driver, *args, **kwargs)\n\n    def is_attribute_present(self, *args, **kwargs):\n        return page_actions.has_attribute(self.driver, *args, **kwargs)\n\n    def is_non_empty_text_visible(self, *args, **kwargs):\n        return page_actions.is_non_empty_text_visible(\n            self.driver, *args, **kwargs\n        )\n\n    def is_valid_url(self, url):\n        \"\"\"Return True if the url is a valid url.\"\"\"\n        return page_utils.is_valid_url(url)\n\n    def is_alert_present(self):\n        try:\n            self.driver.switch_to.alert\n            return True\n        except Exception:\n            return False\n\n    def is_online(self):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.evaluate(\"navigator.onLine;\")\n        return self.driver.execute_script(\"return navigator.onLine;\")\n\n    def is_connected(self):\n        \"\"\"\n        Return True if WebDriver is connected to the browser.\n        Note that the stealthy CDP-Driver isn't a WebDriver.\n        In CDP Mode, the CDP-Driver controls the web browser.\n        The CDP-Driver can be connected while WebDriver isn't.\n        \"\"\"\n        if shared_utils.is_windows():\n            return (\n                not hasattr(self.driver, \"_is_connected\")\n                or self.driver._is_connected\n            )\n        try:\n            self.driver.window_handles\n            return True\n        except Exception:\n            return False\n\n    def is_uc_mode_active(self):\n        \"\"\"Return True if the driver is using UC Mode. False otherwise.\"\"\"\n        return (\n            hasattr(self.driver, \"_is_using_uc\")\n            and self.driver._is_using_uc\n        )\n\n    def is_cdp_mode_active(self):\n        \"\"\"CDP Mode is a special mode within UC Mode. Activated separately.\n        Return True if CDP Mode is loaded in the driver. False otherwise.\"\"\"\n        return (\n            hasattr(self.driver, \"_is_using_cdp\")\n            and self.driver._is_using_cdp\n        )\n\n    def js_click(self, *args, **kwargs):\n        return page_actions.js_click(self.driver, *args, **kwargs)\n\n    def get_text(self, *args, **kwargs):\n        return page_actions.get_text(self.driver, *args, **kwargs)\n\n    def get_active_element_css(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_active_element_css()\n        return js_utils.get_active_element_css(self.driver, *args, **kwargs)\n\n    def get_locale_code(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_locale_code()\n        return js_utils.get_locale_code(self.driver, *args, **kwargs)\n\n    def get_screen_rect(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_screen_rect()\n        return js_utils.get_screen_rect(self.driver, *args, **kwargs)\n\n    def get_origin(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_origin()\n        return js_utils.get_origin(self.driver, *args, **kwargs)\n\n    def get_user_agent(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_user_agent()\n        return js_utils.get_user_agent(self.driver, *args, **kwargs)\n\n    def get_cookie_string(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            return self.driver.cdp.get_cookie_string()\n        return js_utils.get_cookie_string(self.driver, *args, **kwargs)\n\n    def highlight(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed():\n            selector = None\n            if \"selector\" in kwargs:\n                selector = kwargs[\"selector\"]\n            else:\n                selector = args[0]\n            self.driver.cdp.highlight(selector)\n            return\n        if \"scroll\" in kwargs:\n            kwargs.pop(\"scroll\")\n        w_args = kwargs.copy()\n        if \"loops\" in w_args:\n            w_args.pop(\"loops\")\n        element = page_actions.wait_for_element(self.driver, *args, **w_args)\n        browser = self.driver.capabilities[\"browserName\"].lower()\n        js_utils.slow_scroll_to_element(self.driver, element, browser)\n        if \"timeout\" in kwargs:\n            kwargs.pop(\"timeout\")\n        js_utils.highlight(self.driver, *args, **kwargs)\n\n    def highlight_click(self, *args, **kwargs):\n        self.highlight(*args, **kwargs)\n        if \"loops\" in kwargs:\n            kwargs.pop(\"loops\")\n        if \"scroll\" in kwargs:\n            kwargs.pop(\"scroll\")\n        page_actions.click(self.driver, *args, **kwargs)\n\n    def highlight_if_visible(\n        self, selector, by=\"css selector\", loops=4, scroll=True\n    ):\n        if self.is_element_visible(selector, by=by):\n            self.highlight(selector, by=by, loops=loops, scroll=scroll)\n\n    def switch_to_default_window(self):\n        self.driver.switch_to.window(self.driver.window_handles[0])\n\n    def switch_to_newest_window(self):\n        self.driver.switch_to.window(self.driver.window_handles[-1])\n\n    def open_new_window(self, switch_to=True):\n        if switch_to:\n            try:\n                self.driver.switch_to.new_window(\"tab\")\n            except Exception:\n                self.driver.execute_script(\"window.open('');\")\n                self.switch_to_newest_window()\n        else:\n            self.driver.execute_script(\"window.open('');\")\n\n    def open_new_tab(self, switch_to=True):\n        self.open_new_window(switch_to=switch_to)\n\n    def switch_to_window(self, *args, **kwargs):\n        page_actions.switch_to_window(self.driver, *args, **kwargs)\n\n    def switch_to_tab(self, *args, **kwargs):\n        self.switch_to_window(*args, **kwargs)\n\n    def switch_to_frame(self, frame=\"iframe\"):\n        if isinstance(frame, WebElement):\n            self.driver.switch_to.frame(frame)\n        else:\n            iframe = self.locator(frame)\n            self.driver.switch_to.frame(iframe)\n\n    def reset_window_size(self):\n        if self.__is_cdp_swap_needed():\n            self.driver.cdp.reset_window_size()\n            return\n        x = settings.WINDOW_START_X\n        y = settings.WINDOW_START_Y\n        width = settings.CHROME_START_WIDTH\n        height = settings.CHROME_START_HEIGHT\n        self.driver.set_window_rect(x, y, width, height)\n\n    def set_wire_proxy(self, string):\n        \"\"\"Set a proxy server for selenium-wire mode (\"--wire\")\n        Examples:  (ONLY avilable if using selenium-wire mode!)\n        driver.set_wire_proxy(\"SERVER:PORT\")\n        driver.set_wire_proxy(\"socks5://SERVER:PORT\")\n        driver.set_wire_proxy(\"USERNAME:PASSWORD@SERVER:PORT\")\n        \"\"\"\n        the_http = \"http\"\n        the_https = \"https\"\n        if string.startswith(\"socks4://\"):\n            the_http = \"socks4\"\n            the_https = \"socks4\"\n        elif string.startswith(\"socks5://\"):\n            the_http = \"socks5\"\n            the_https = \"socks5\"\n        string = string.split(\"//\")[-1]\n        if hasattr(self.driver, \"proxy\"):\n            self.driver.proxy = {\n                \"http\": \"%s://%s\" % (the_http, string),\n                \"https\": \"%s://%s\" % (the_https, string),\n                \"no_proxy\": \"localhost,127.0.0.1\",\n            }\n"
  },
  {
    "path": "seleniumbase/core/session_helper.py",
    "content": "from seleniumbase import config as sb_config\n\n\ndef end_reused_class_session_as_needed():\n    if (\n        getattr(sb_config, \"reuse_class_session\", None)\n        and getattr(sb_config, \"shared_driver\", None)\n    ):\n        if (\n            hasattr(sb_config.shared_driver, \"service\")\n            and sb_config.shared_driver.service.process\n        ):\n            try:\n                sb_config.shared_driver.quit()\n            except Exception:\n                sb_config.shared_driver = None\n"
  },
  {
    "path": "seleniumbase/core/settings_parser.py",
    "content": "import re\nfrom seleniumbase.config import settings\n\n\ndef set_settings(settings_file):\n    if not settings_file.endswith(\".py\"):\n        raise Exception(\"\\n\\n`%s` is not a Python file!\\n\\n\" % settings_file)\n\n    f = open(settings_file, \"r\")\n    all_code = f.read()\n    f.close()\n\n    override_settings = {}\n    num_settings = 0\n\n    code_lines = all_code.split(\"\\n\")\n    for line in code_lines:\n\n        # KEY = \"VALUE\"\n        data = re.match(r'^\\s*([\\S]+)\\s*=\\s*\"([\\S\\s]+)\"\\s*$', line)\n        if data:\n            key = data.group(1)\n            value = '\"' + data.group(2) + '\"'\n            override_settings[key] = value\n            num_settings += 1\n            continue\n\n        # KEY = 'VALUE'\n        data = re.match(r\"^\\s*([\\S]+)\\s*=\\s*'([\\S\\s]+)'\\s*$\", line)\n        if data:\n            key = data.group(1)\n            value = \"'\" + data.group(2) + \"'\"\n            override_settings[key] = value\n            num_settings += 1\n            continue\n\n        # KEY = VALUE\n        data = re.match(r\"^\\s*([\\S]+)\\s*=\\s*([\\S]+)\\s*$\", line)\n        if data:\n            key = data.group(1)\n            value = data.group(2)\n            override_settings[key] = value\n            num_settings += 1\n            continue\n\n    for key in override_settings.keys():\n        value = override_settings[key]\n        if value.replace(\".\", \"1\").isdigit():\n            if value.count(\".\") == 1:\n                override_settings[key] = float(value)\n            elif value.count(\".\") == 0:\n                override_settings[key] = int(value)\n            else:\n                continue\n        elif value == \"True\":\n            override_settings[key] = True\n        elif value == \"False\":\n            override_settings[key] = False\n        elif len(value) > 1 and value.startswith('\"') and value.endswith('\"'):\n            override_settings[key] = value[1:-1]\n        elif len(value) > 1 and value.startswith(\"'\") and value.endswith(\"'\"):\n            override_settings[key] = value[1:-1]\n        else:\n            continue\n\n        if key == \"MINI_TIMEOUT\":\n            settings.MINI_TIMEOUT = override_settings[key]\n        elif key == \"SMALL_TIMEOUT\":\n            settings.SMALL_TIMEOUT = override_settings[key]\n        elif key == \"LARGE_TIMEOUT\":\n            settings.LARGE_TIMEOUT = override_settings[key]\n        elif key == \"EXTREME_TIMEOUT\":\n            settings.EXTREME_TIMEOUT = override_settings[key]\n        elif key == \"PAGE_LOAD_TIMEOUT\":\n            settings.PAGE_LOAD_TIMEOUT = override_settings[key]\n        elif key == \"ARCHIVE_EXISTING_LOGS\":\n            settings.ARCHIVE_EXISTING_LOGS = override_settings[key]\n        elif key == \"ARCHIVE_EXISTING_DOWNLOADS\":\n            settings.ARCHIVE_EXISTING_DOWNLOADS = override_settings[key]\n        elif key == \"SCREENSHOT_WITH_BACKGROUND\":\n            settings.SCREENSHOT_WITH_BACKGROUND = override_settings[key]\n        elif key == \"SCREENSHOT_NAME\":\n            settings.SCREENSHOT_NAME = override_settings[key]\n        elif key == \"BASIC_INFO_NAME\":\n            settings.BASIC_INFO_NAME = override_settings[key]\n        elif key == \"PAGE_SOURCE_NAME\":\n            settings.PAGE_SOURCE_NAME = override_settings[key]\n        elif key == \"LATEST_REPORT_DIR\":\n            settings.LATEST_REPORT_DIR = override_settings[key]\n        elif key == \"REPORT_ARCHIVE_DIR\":\n            settings.REPORT_ARCHIVE_DIR = override_settings[key]\n        elif key == \"HTML_REPORT\":\n            settings.HTML_REPORT = override_settings[key]\n        elif key == \"RESULTS_TABLE\":\n            settings.RESULTS_TABLE = override_settings[key]\n        elif key == \"SWITCH_TO_NEW_TABS_ON_CLICK\":\n            settings.SWITCH_TO_NEW_TABS_ON_CLICK = override_settings[key]\n        elif key == \"WAIT_FOR_RSC_ON_PAGE_LOADS\":\n            settings.WAIT_FOR_RSC_ON_PAGE_LOADS = override_settings[key]\n        elif key == \"WAIT_FOR_RSC_ON_CLICKS\":\n            settings.WAIT_FOR_RSC_ON_CLICKS = override_settings[key]\n        elif key == \"WAIT_FOR_ANGULARJS\":\n            settings.WAIT_FOR_ANGULARJS = override_settings[key]\n        elif key == \"DEFAULT_DEMO_MODE_TIMEOUT\":\n            settings.DEFAULT_DEMO_MODE_TIMEOUT = override_settings[key]\n        elif key == \"HIGHLIGHTS\":\n            settings.HIGHLIGHTS = override_settings[key]\n        elif key == \"DEFAULT_MESSAGE_DURATION\":\n            settings.DEFAULT_MESSAGE_DURATION = override_settings[key]\n        elif key == \"DISABLE_CSP_ON_FIREFOX\":\n            settings.DISABLE_CSP_ON_FIREFOX = override_settings[key]\n        elif key == \"DISABLE_CSP_ON_CHROME\":\n            settings.DISABLE_CSP_ON_CHROME = override_settings[key]\n        elif key == \"RAISE_INVALID_PROXY_STRING_EXCEPTION\":\n            settings.RAISE_INVALID_PROXY_STRING_EXCEPTION = override_settings[\n                key\n            ]\n        elif key == \"WINDOW_START_X\":\n            settings.WINDOW_START_X = override_settings[key]\n        elif key == \"WINDOW_START_Y\":\n            settings.WINDOW_START_Y = override_settings[key]\n        elif key == \"CHROME_START_WIDTH\":\n            settings.CHROME_START_WIDTH = override_settings[key]\n        elif key == \"CHROME_START_HEIGHT\":\n            settings.CHROME_START_HEIGHT = override_settings[key]\n        elif key == \"HEADLESS_START_WIDTH\":\n            settings.HEADLESS_START_WIDTH = override_settings[key]\n        elif key == \"HEADLESS_START_HEIGHT\":\n            settings.HEADLESS_START_HEIGHT = override_settings[key]\n        elif key == \"HIDE_DRIVER_DOWNLOADS\":\n            settings.HIDE_DRIVER_DOWNLOADS = override_settings[key]\n        elif key == \"MASTERQA_DEFAULT_VALIDATION_MESSAGE\":\n            settings.MASTERQA_DEFAULT_VALIDATION_MESSAGE = override_settings[\n                key\n            ]\n        elif key == \"MASTERQA_WAIT_TIME_BEFORE_VERIFY\":\n            settings.MASTERQA_WAIT_TIME_BEFORE_VERIFY = override_settings[key]\n        elif key == \"MASTERQA_START_IN_FULL_SCREEN_MODE\":\n            settings.MASTERQA_START_IN_FULL_SCREEN_MODE = override_settings[\n                key\n            ]\n        elif key == \"MASTERQA_MAX_IDLE_TIME_BEFORE_QUIT\":\n            settings.MASTERQA_MAX_IDLE_TIME_BEFORE_QUIT = override_settings[\n                key\n            ]\n        elif key == \"TOTP_KEY\":\n            settings.TOTP_KEY = override_settings[key]\n        elif key == \"DB_HOST\":\n            settings.DB_HOST = override_settings[key]\n        elif key == \"DB_PORT\":\n            settings.DB_PORT = override_settings[key]\n        elif key == \"DB_USERNAME\":\n            settings.DB_USERNAME = override_settings[key]\n        elif key == \"DB_PASSWORD\":\n            settings.DB_PASSWORD = override_settings[key]\n        elif key == \"DB_SCHEMA\":\n            settings.DB_SCHEMA = override_settings[key]\n        elif key == \"S3_LOG_BUCKET\":\n            settings.S3_LOG_BUCKET = override_settings[key]\n        elif key == \"S3_BUCKET_URL\":\n            settings.S3_BUCKET_URL = override_settings[key]\n        elif key == \"S3_SELENIUM_ACCESS_KEY\":\n            settings.S3_SELENIUM_ACCESS_KEY = override_settings[key]\n        elif key == \"S3_SELENIUM_SECRET_KEY\":\n            settings.S3_SELENIUM_SECRET_KEY = override_settings[key]\n        elif key == \"ENCRYPTION_KEY\":\n            settings.ENCRYPTION_KEY = override_settings[key]\n        elif key == \"OBFUSCATION_START_TOKEN\":\n            settings.OBFUSCATION_START_TOKEN = override_settings[key]\n        elif key == \"OBFUSCATION_END_TOKEN\":\n            settings.OBFUSCATION_END_TOKEN = override_settings[key]\n        else:\n            continue\n\n    if num_settings == 0:\n        raise Exception(\"Unable to parse the settings file!\")\n\n    return override_settings\n"
  },
  {
    "path": "seleniumbase/core/style_sheet.py",
    "content": "import textwrap\nfrom seleniumbase.fixtures import constants\n\n\nclass Saved:\n    # Storing data to prevent extra loading\n    pass\n\n\ndef get_report_style():\n    if hasattr(Saved, \"report_style\"):\n        return Saved.report_style\n    # Uses caching to prevent extra method calls\n    REPORT_FAVICON = constants.Report.get_favicon()\n    title = \"\"\"<meta id=\"OGTitle\" property=\"og:title\" content=\"SeleniumBase\">\n        <title>Test Report</title>\n        <link rel=\"SHORTCUT ICON\"\n        href=\"%s\" />\"\"\" % REPORT_FAVICON\n    style = (\n        title\n        + \"\"\"<style type=\"text/css\">\n        html {\n            background-color: #9988ad;\n        }\n        html, body {\n            font-size: 100%;\n            box-sizing: border-box;\n        }\n        body {\n            background-image: none;\n            background-origin: padding-box;\n            background-color: #c6d6f0;\n            padding: 30;\n            margin: 10;\n            font-family: \"Proxima Nova\",\"proxima-nova\",\n            \"Helvetica Neue\",Helvetica,Arial,sans-serif !important;\n            text-rendering: optimizelegibility;\n            -moz-osx-font-smoothing: grayscale;\n            box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.24),\n            1px 2px 12px 0px rgba(0, 0, 0, 0.18) !important;\n        }\n        table {\n            width: 100%;\n            border-collapse: collapse;\n            border-spacing: 0;\n            box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.27),\n            1px 2px 12px 0px rgba(0, 0, 0, 0.21) !important;\n            transition: all 0.15s ease-out 0s;\n            transition-property: all;\n            transition-duration: 0.1s;\n            transition-timing-function: ease-out;\n            transition-delay: 0s;\n        }\n        table:hover {\n            box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.35),\n            1px 2px 12px 0px rgba(0, 0, 0, 0.28) !important;\n        }\n        thead th, thead td {\n            padding: 0.5rem 0.625rem 0.625rem;\n            font-weight: bold;\n            text-align: left;\n        }\n        thead {\n            text-align: center;\n            border: 1px solid #e1e1e1;\n            width: 150%;\n            color: #0C8CDF;\n            background-color: #c0f0ff;\n        }\n        tbody tr:nth-child(even) {\n            background-color: #f1f1f1;\n        }\n        tbody tr:nth-child(odd) {\n            background-color: #ffffff;\n        }\n        tbody tr:nth-child(even):hover {\n            background-color: #f8f8d2;\n        }\n        tbody tr:nth-child(odd):hover {\n            background-color: #ffffe0;\n        }\n        tbody th, tbody td {\n            padding: 0.5rem 0.625rem 0.625rem;\n        }\n        tbody {\n            border: 1px solid #e1e1e1;\n            background-color: #fefefe;\n        }\n        td {\n            padding: 5px 5px 5px 0;\n            vertical-align: top;\n        }\n        h1 table {\n            font-size: 27px;\n            text-align: left;\n            padding: 0.5rem 0.625rem 0.625rem;\n            font-weight: bold;\n            padding-right: 10px;\n            padding-left: 20px;\n            padding: 15px 15px 15px 0;\n        }\n        h2 table {\n            color: #0C8CDF;\n            font-size: 16px;\n            text-align: left;\n            font-weight: bold;\n            padding: 5px 5px 5px 0;\n            padding-right: 10px;\n            padding-left: 20px;\n        }\n        </style>\"\"\"\n    )\n    style = textwrap.dedent(style)\n    Saved.report_style = style\n    return style\n\n\ndef get_bt_backdrop_style():\n    # Bootstrap Tour Backdrop Style\n    if hasattr(Saved, \"bt_backdrop_style\"):\n        return Saved.bt_backdrop_style\n    bt_backdrop_style = \"\"\"\n        .tour-tour-element {\n            pointer-events: none !important;\n        }\n        :not(.tour-tour-element) .orphan.tour-tour {\n            box-shadow: 0 0 0 88422px rgba(0, 0, 0, 0.42);\n            pointer-events: auto !important;\n        }\"\"\"\n    bt_backdrop_style = textwrap.dedent(bt_backdrop_style)\n    Saved.bt_backdrop_style = bt_backdrop_style\n    return bt_backdrop_style\n\n\ndef get_dt_backdrop_style():\n    # DriverJS Tour Backdrop Style\n    if hasattr(Saved, \"dt_backdrop_style\"):\n        return Saved.dt_backdrop_style\n    dt_backdrop_style = \"\"\"\n        .driver-fix-stacking {\n            pointer-events: none !important;\n        }\n        #driver-popover-item, .popover-class {\n            pointer-events: auto !important;\n        }\n        button.driver-prev-btn.driver-disabled {\n            visibility: hidden;\n        }\"\"\"\n    dt_backdrop_style = textwrap.dedent(dt_backdrop_style)\n    Saved.dt_backdrop_style = dt_backdrop_style\n    return dt_backdrop_style\n\n\ndef get_messenger_style():\n    if hasattr(Saved, \"messenger_style\"):\n        return Saved.messenger_style\n    font_family = '\"open-sans\",Arial,sans-serif !important'\n    messenger_style = \"\"\"\n        .messenger-message-inner {\n            font-family: %s;\n            font-size: 17px;\n        }\n        ul.messenger-theme-flat, ul.messenger-theme-future {\n            box-shadow: 2px 2px 9px 4px rgba(32, 142, 120, 0.28),\n            2px 2px 9px 4px rgba(200, 240, 80, 0.34) !important;\n        }\"\"\" % font_family\n    messenger_style = textwrap.dedent(messenger_style)\n    Saved.messenger_style = messenger_style\n    return messenger_style\n\n\ndef get_sh_style_test():\n    if hasattr(Saved, \"sh_style_test\"):\n        return Saved.sh_style_test\n    sh_style_test = \"\"\"\n        var test_tour = new Shepherd.Tour({\n          defaults: {\n            classes: 'shepherd-theme-dark',\n            scrollTo: true\n          }\n        });\"\"\"\n    sh_style_test = textwrap.dedent(sh_style_test)\n    Saved.sh_style_test = sh_style_test\n    return sh_style_test\n\n\ndef get_hops_backdrop_style():\n    # Hopscotch Backdrop Style\n    if hasattr(Saved, \"hops_backdrop_style\"):\n        return Saved.hops_backdrop_style\n    hops_backdrop_style = \"\"\"\n        .hopscotch-bubble-container {\n            font-size: 110%;\n        }\"\"\"\n    hops_backdrop_style = textwrap.dedent(hops_backdrop_style)\n    Saved.hops_backdrop_style = hops_backdrop_style\n    return hops_backdrop_style\n\n\ndef get_introjs_style():\n    # IntroJS Style\n    if hasattr(Saved, \"introjs_style\"):\n        return Saved.introjs_style\n    introjs_style = \"\"\"\n        .introjs-button.introjs-nextbutton,\n        .introjs-button.introjs-donebutton {\n            color: #fff !important;\n            background-color: %s !important;\n            border: 1px solid %s !important;\n            text-shadow: none;\n            box-shadow: none;\n        }\n        .introjs-button.introjs-nextbutton:hover,\n        .introjs-button.introjs-donebutton:hover {\n            color: #fff !important;\n            background-color: %s !important;\n            border: 1px solid %s !important;\n        }\n        .introjs-button {\n            box-sizing: content-box;\n            text-decoration: none;\n        }\n        .introjs-button.introjs-skipbutton {\n            color: %s;\n        }\n        .introjs-tooltip, .introjs-floating {\n            box-sizing: content-box;\n            position: absolute;\n        }\"\"\"\n    introjs_style = textwrap.dedent(introjs_style)\n    Saved.introjs_style = introjs_style\n    return introjs_style\n\n\ndef get_sh_backdrop_style():\n    # Shepherd Backdrop Style\n    if hasattr(Saved, \"sh_backdrop_style\"):\n        return Saved.sh_backdrop_style\n    sh_backdrop_style = \"\"\"\n        body.shepherd-active .shepherd-target.shepherd-enabled {\n            box-shadow: 0 0 0 99999px rgba(0, 0, 0, 0.20);\n            pointer-events: none !important;\n            z-index: 9999;\n        }\n        body.shepherd-active .shepherd-orphan {\n            box-shadow: 0 0 0 99999px rgba(0, 0, 0, 0.20);\n            pointer-events: auto;\n            z-index: 9999;\n        }\n        body.shepherd-active\n            .shepherd-enabled.shepherd-element-attached-top {\n                position: relative;\n        }\n        body.shepherd-active\n            .shepherd-enabled.shepherd-element-attached-bottom {\n                position: relative;\n        }\n        body.shepherd-active .shepherd-step {\n            pointer-events: auto;\n            z-index: 9999;\n        }\n        body.shepherd-active {\n            pointer-events: none !important;\n        }\"\"\"\n    sh_backdrop_style = textwrap.dedent(sh_backdrop_style)\n    Saved.sh_backdrop_style = sh_backdrop_style\n    return sh_backdrop_style\n\n\ndef get_pytest_style():\n    # pytest html-report Style\n    if hasattr(Saved, \"pytest_style\"):\n        return Saved.pytest_style\n    pytest_style = \"\"\"\n        body {\n            font-family: Helvetica, Arial, sans-serif;\n            font-size: 12px;\n            min-width: 800px;\n            color: #999;\n        }\n        h1 {\n            font-size: 24px;\n            color: black;\n        }\n        h2 {\n            font-size: 16px;\n            color: black;\n        }\n        p {\n            color: black;\n        }\n        a {\n            color: #999;\n        }\n        table {\n            border-collapse: collapse;\n        }\n        #environment td {\n            padding: 5px;\n            border: 1px solid #E6E6E6;\n        }\n        #environment tr:nth-child(odd) {\n            background-color: #f6f6f6;\n        }\n        span.passed, .passed .col-result {\n            color: green;\n        }\n        span.skipped, span.xfailed, span.rerun, .skipped .col-result,\n        .xfailed .col-result, .rerun .col-result {\n            color: orange;\n        }\n        span.error, span.failed, span.xpassed, .error .col-result,\n        .failed .col-result, .xpassed .col-result  {\n            color: red;\n        }\n        #results-table {\n            border: 1px solid #e6e6e6;\n            color: #999;\n            font-size: 12px;\n            width: 100%\n        }\n        #results-table th, #results-table td {\n            padding: 5px;\n            border: 1px solid #E6E6E6;\n            text-align: left\n        }\n        #results-table th {\n            font-weight: bold\n        }\n        .log:only-child {\n            height: inherit\n        }\n        .log {\n            background-color: #e6e6e6;\n            border: 1px solid #e6e6e6;\n            color: black;\n            display: block;\n            font-family: \"Courier New\", Courier, monospace;\n            height: 230px;\n            overflow-y: scroll;\n            padding: 5px;\n            white-space: pre-wrap\n        }\n        div.image {\n            border: 1px solid #e6e6e6;\n            float: right;\n            height: 240px;\n            margin-left: 5px;\n            overflow: hidden;\n            width: 320px\n        }\n        div.image img {\n            width: 320px\n        }\n        .collapsed {\n            display: none;\n        }\n        .expander::after {\n            content: \" (show details)\";\n            color: #BBB;\n            font-style: italic;\n            cursor: pointer;\n        }\n        .collapser::after {\n            content: \" (hide details)\";\n            color: #BBB;\n            font-style: italic;\n            cursor: pointer;\n        }\n        .sortable {\n            cursor: pointer;\n        }\n        .sort-icon {\n            font-size: 0px;\n            float: left;\n            margin-right: 5px;\n            margin-top: 5px;\n            width: 0;\n            height: 0;\n            border-left: 8px solid transparent;\n            border-right: 8px solid transparent;\n        }\n        .inactive .sort-icon {\n            border-top: 8px solid #E6E6E6;\n        }\n        .asc.active .sort-icon {\n            border-bottom: 8px solid #999;\n        }\n        .desc.active .sort-icon {\n            border-top: 8px solid #999;\n        }\"\"\"\n    pytest_style = textwrap.dedent(pytest_style)\n    Saved.pytest_style = pytest_style\n    return pytest_style\n"
  },
  {
    "path": "seleniumbase/core/testcase_manager.py",
    "content": "from seleniumbase.core.mysql import DatabaseManager\n\n\nclass TestcaseManager:\n    def __init__(self, database_env):\n        self.database_env = database_env\n\n    def insert_execution_data(self, execution_query_payload):\n        \"\"\"Inserts a test execution row into the database.\n        Returns the execution guid.\n        \"execution_start_time\" is defined by milliseconds since the Epoch.\n        (See https://currentmillis.com to convert that to a real date.)\"\"\"\n\n        query = \"\"\"INSERT INTO test_execution\n                   (guid, execution_start, total_execution_time, username)\n                   VALUES (%(guid)s,%(execution_start_time)s,\n                           %(total_execution_time)s,%(username)s)\"\"\"\n        DatabaseManager(self.database_env).execute_query(\n            query, execution_query_payload.get_params()\n        )\n        return execution_query_payload.guid\n\n    def update_execution_data(self, execution_guid, execution_time):\n        \"\"\"Updates an existing test execution row in the database.\"\"\"\n        query = \"\"\"UPDATE test_execution\n                   SET total_execution_time=%(execution_time)s\n                   WHERE guid=%(execution_guid)s \"\"\"\n        DatabaseManager(self.database_env).execute_query(\n            query,\n            {\n                \"execution_guid\": execution_guid,\n                \"execution_time\": execution_time,\n            },\n        )\n\n    def insert_testcase_data(self, testcase_run_payload):\n        \"\"\"Inserts all data for the test in the DB. Returns new row guid.\"\"\"\n        query = \"\"\"INSERT INTO test_run_data(\n                   guid, browser, state, execution_guid, env, start_time,\n                   test_address, runtime, retry_count, message, stack_trace)\n                          VALUES (\n                              %(guid)s,\n                              %(browser)s,\n                              %(state)s,\n                              %(execution_guid)s,\n                              %(env)s,\n                              %(start_time)s,\n                              %(test_address)s,\n                              %(runtime)s,\n                              %(retry_count)s,\n                              %(message)s,\n                              %(stack_trace)s) \"\"\"\n        DatabaseManager(self.database_env).execute_query(\n            query, testcase_run_payload.get_params()\n        )\n\n    def update_testcase_data(self, testcase_payload):\n        \"\"\"Updates an existing test run in the database.\"\"\"\n        query = \"\"\"UPDATE test_run_data SET\n                            runtime=%(runtime)s,\n                            state=%(state)s,\n                            retry_count=%(retry_count)s,\n                            stack_trace=%(stack_trace)s,\n                            message=%(message)s\n                            WHERE guid=%(guid)s \"\"\"\n        DatabaseManager(self.database_env).execute_query(\n            query, testcase_payload.get_params()\n        )\n\n    def update_testcase_log_url(self, testcase_payload):\n        query = \"\"\"UPDATE test_run_data\n                   SET log_url=%(log_url)s\n                   WHERE guid=%(guid)s \"\"\"\n        DatabaseManager(self.database_env).execute_query(\n            query, testcase_payload.get_params()\n        )\n\n\nclass ExecutionQueryPayload:\n    def __init__(self):\n        self.execution_start_time = None\n        self.total_execution_time = -1\n        self.username = \"Default\"\n        self.guid = None\n\n    def get_params(self):\n        return {\n            \"execution_start_time\": self.execution_start_time,\n            \"total_execution_time\": self.total_execution_time,\n            \"username\": self.username,\n            \"guid\": self.guid,\n        }\n\n\nclass TestcaseDataPayload:\n    def __init__(self):\n        self.guid = None\n        self.test_address = None\n        self.browser = None\n        self.state = None\n        self.execution_guid = None\n        self.env = None\n        self.start_time = None\n        self.runtime = None\n        self.retry_count = 0\n        self.stack_trace = None\n        self.message = None\n        self.log_url = None\n\n    def get_params(self):\n        return {\n            \"guid\": self.guid,\n            \"test_address\": self.test_address,\n            \"browser\": self.browser,\n            \"state\": self.state,\n            \"execution_guid\": self.execution_guid,\n            \"env\": self.env,\n            \"start_time\": self.start_time,\n            \"runtime\": self.runtime,\n            \"retry_count\": self.retry_count,\n            \"stack_trace\": self.stack_trace,\n            \"message\": self.message,\n            \"log_url\": self.log_url,\n        }\n"
  },
  {
    "path": "seleniumbase/core/tour_helper.py",
    "content": "\"\"\"This module contains methods for running website tours.\nThese helper methods SHOULD NOT be called directly from tests.\"\"\"\nimport os\nimport re\nimport textwrap\nimport time\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import style_sheet\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import js_utils\nfrom seleniumbase.fixtures import page_actions\n\nEXPORTED_TOURS_FOLDER = constants.Tours.EXPORTED_TOURS_FOLDER\n\n\ndef activate_bootstrap(driver):\n    \"\"\"Allows you to use Bootstrap Tours with SeleniumBase\n    http://bootstraptour.com/\n    \"\"\"\n    bootstrap_tour_css = constants.BootstrapTour.MIN_CSS\n    bootstrap_tour_js = constants.BootstrapTour.MIN_JS\n\n    verify_script = \"\"\"// Verify Bootstrap Tour activated\n                     var tour2 = new Tour({\n                     });\"\"\"\n\n    backdrop_style = style_sheet.get_bt_backdrop_style()\n    js_utils.add_css_style(driver, backdrop_style)\n    js_utils.wait_for_ready_state_complete(driver)\n    js_utils.wait_for_angularjs(driver)\n    for x in range(4):\n        js_utils.activate_jquery(driver)\n        js_utils.add_css_link(driver, bootstrap_tour_css)\n        js_utils.add_js_link(driver, bootstrap_tour_js)\n        time.sleep(0.1)\n        for x in range(int(settings.MINI_TIMEOUT * 2.0)):\n            # Bootstrap needs a small amount of time to load & activate.\n            try:\n                driver.execute_script(verify_script)\n                time.sleep(0.05)\n                return\n            except Exception:\n                time.sleep(0.15)\n    js_utils.raise_unable_to_load_jquery_exception(driver)\n\n\ndef is_bootstrap_activated(driver):\n    verify_script = \"\"\"// Verify Bootstrap Tour activated\n                     var tour2 = new Tour({\n                     });\"\"\"\n    try:\n        driver.execute_script(verify_script)\n        return True\n    except Exception:\n        return False\n\n\ndef activate_driverjs(driver):\n    \"\"\"Allows you to use DriverJS Tours with SeleniumBase\n    https://kamranahmed.info/driver.js/\n    \"\"\"\n    backdrop_style = style_sheet.get_dt_backdrop_style()\n    driverjs_css = constants.DriverJS.MIN_CSS\n    driverjs_js = constants.DriverJS.MIN_JS\n\n    verify_script = \"\"\"// Verify DriverJS activated\n                     var driverjs2 = Driver.name;\n                     \"\"\"\n\n    js_utils.wait_for_ready_state_complete(driver)\n    js_utils.wait_for_angularjs(driver)\n    js_utils.add_css_style(driver, backdrop_style)\n    for x in range(4):\n        js_utils.activate_jquery(driver)\n        js_utils.add_css_link(driver, driverjs_css)\n        js_utils.add_js_link(driver, driverjs_js)\n        time.sleep(0.1)\n        for x in range(int(settings.MINI_TIMEOUT * 2.0)):\n            # DriverJS needs a small amount of time to load & activate.\n            try:\n                driver.execute_script(verify_script)\n                js_utils.wait_for_ready_state_complete(driver)\n                js_utils.wait_for_angularjs(driver)\n                time.sleep(0.05)\n                return\n            except Exception:\n                time.sleep(0.15)\n    js_utils.raise_unable_to_load_jquery_exception(driver)\n\n\ndef is_driverjs_activated(driver):\n    verify_script = \"\"\"// Verify DriverJS activated\n                     var driverjs2 = Driver.name;\n                     \"\"\"\n    try:\n        driver.execute_script(verify_script)\n        return True\n    except Exception:\n        return False\n\n\ndef activate_hopscotch(driver):\n    \"\"\"Allows you to use Hopscotch Tours with SeleniumBase\n    http://linkedin.github.io/hopscotch/\n    \"\"\"\n    hopscotch_css = constants.Hopscotch.MIN_CSS\n    hopscotch_js = constants.Hopscotch.MIN_JS\n    backdrop_style = style_sheet.get_hops_backdrop_style()\n\n    verify_script = \"\"\"// Verify Hopscotch activated\n                     var hops = hopscotch.isActive;\n                     \"\"\"\n\n    js_utils.wait_for_ready_state_complete(driver)\n    js_utils.wait_for_angularjs(driver)\n    js_utils.add_css_style(driver, backdrop_style)\n    for x in range(4):\n        js_utils.activate_jquery(driver)\n        js_utils.add_css_link(driver, hopscotch_css)\n        js_utils.add_js_link(driver, hopscotch_js)\n        time.sleep(0.1)\n        for x in range(int(settings.MINI_TIMEOUT * 2.0)):\n            # Hopscotch needs a small amount of time to load & activate.\n            try:\n                driver.execute_script(verify_script)\n                js_utils.wait_for_ready_state_complete(driver)\n                js_utils.wait_for_angularjs(driver)\n                time.sleep(0.05)\n                return\n            except Exception:\n                time.sleep(0.15)\n    js_utils.raise_unable_to_load_jquery_exception(driver)\n\n\ndef is_hopscotch_activated(driver):\n    verify_script = \"\"\"// Verify Hopscotch activated\n                     var hops = hopscotch.isActive;\n                     \"\"\"\n    try:\n        driver.execute_script(verify_script)\n        return True\n    except Exception:\n        return False\n\n\ndef activate_introjs(driver):\n    \"\"\"Allows you to use IntroJS Tours with SeleniumBase\n    https://introjs.com/\n    \"\"\"\n    intro_css = constants.IntroJS.MIN_CSS\n    intro_js = constants.IntroJS.MIN_JS\n\n    theme_color = sb_config.introjs_theme_color\n    hover_color = sb_config.introjs_hover_color\n    backdrop_style = style_sheet.get_introjs_style() % (\n        theme_color,\n        hover_color,\n        hover_color,\n        hover_color,\n        theme_color,\n    )\n\n    verify_script = \"\"\"// Verify IntroJS activated\n                     var intro2 = introJs();\n                     \"\"\"\n\n    js_utils.wait_for_ready_state_complete(driver)\n    js_utils.wait_for_angularjs(driver)\n    js_utils.add_css_style(driver, backdrop_style)\n    for x in range(4):\n        js_utils.activate_jquery(driver)\n        js_utils.add_css_link(driver, intro_css)\n        js_utils.add_js_link(driver, intro_js)\n        time.sleep(0.1)\n        for x in range(int(settings.MINI_TIMEOUT * 2.0)):\n            # IntroJS needs a small amount of time to load & activate.\n            try:\n                driver.execute_script(verify_script)\n                js_utils.wait_for_ready_state_complete(driver)\n                js_utils.wait_for_angularjs(driver)\n                time.sleep(0.05)\n                return\n            except Exception:\n                time.sleep(0.15)\n    js_utils.raise_unable_to_load_jquery_exception(driver)\n\n\ndef is_introjs_activated(driver):\n    verify_script = \"\"\"// Verify IntroJS activated\n                     var intro2 = introJs();\n                     \"\"\"\n    try:\n        driver.execute_script(verify_script)\n        return True\n    except Exception:\n        return False\n\n\ndef activate_shepherd(driver):\n    \"\"\"Allows you to use Shepherd Tours with SeleniumBase\n    https://cdnjs.com/libraries/shepherd/1.8.1\n    \"\"\"\n    shepherd_js = constants.Shepherd.MIN_JS\n    sh_theme_arrows_css = constants.Shepherd.THEME_ARROWS_CSS\n    sh_theme_arrows_fix_css = constants.Shepherd.THEME_ARR_FIX_CSS\n    sh_theme_default_css = constants.Shepherd.THEME_DEFAULT_CSS\n    sh_theme_dark_css = constants.Shepherd.THEME_DARK_CSS\n    sh_theme_sq_css = constants.Shepherd.THEME_SQ_CSS\n    sh_theme_sq_dark_css = constants.Shepherd.THEME_SQ_DK_CSS\n    tether_js = constants.Tether.MIN_JS\n    spinner_css = constants.Messenger.SPINNER_CSS\n    sh_style = style_sheet.get_sh_style_test()\n    backdrop_style = style_sheet.get_sh_backdrop_style()\n\n    js_utils.wait_for_ready_state_complete(driver)\n    js_utils.wait_for_angularjs(driver)\n    js_utils.add_css_style(driver, backdrop_style)\n    js_utils.wait_for_ready_state_complete(driver)\n    js_utils.wait_for_angularjs(driver)\n    for x in range(4):\n        js_utils.add_css_link(driver, spinner_css)\n        js_utils.add_css_link(driver, sh_theme_arrows_css)\n        js_utils.add_css_link(driver, sh_theme_arrows_fix_css)\n        js_utils.add_css_link(driver, sh_theme_default_css)\n        js_utils.add_css_link(driver, sh_theme_dark_css)\n        js_utils.add_css_link(driver, sh_theme_sq_css)\n        js_utils.add_css_link(driver, sh_theme_sq_dark_css)\n        js_utils.add_js_link(driver, tether_js)\n        js_utils.add_js_link(driver, shepherd_js)\n        time.sleep(0.1)\n        for x in range(int(settings.MINI_TIMEOUT * 2.0)):\n            # Shepherd needs a small amount of time to load & activate.\n            try:\n                driver.execute_script(sh_style)  # Verify Shepherd has loaded\n                js_utils.wait_for_ready_state_complete(driver)\n                js_utils.wait_for_angularjs(driver)\n                driver.execute_script(sh_style)  # Need it twice for ordering\n                js_utils.wait_for_ready_state_complete(driver)\n                js_utils.wait_for_angularjs(driver)\n                time.sleep(0.05)\n                return\n            except Exception:\n                time.sleep(0.15)\n    js_utils.raise_unable_to_load_jquery_exception(driver)\n\n\ndef is_shepherd_activated(driver):\n    sh_style = style_sheet.get_sh_style_test()\n    try:\n        driver.execute_script(sh_style)  # Verify Shepherd has loaded\n        return True\n    except Exception:\n        return False\n\n\ndef play_shepherd_tour(driver, tour_steps, msg_dur, name=None, interval=0):\n    \"\"\"Plays a Shepherd tour on the current website.\"\"\"\n    instructions = \"\"\n    for tour_step in tour_steps[name]:\n        instructions += tour_step\n    instructions += \"\"\"\n        // Start the tour\n        tour.start();\n        $tour = tour;\"\"\"\n    extra = \"\"\"\n        document.body.addEventListener('keyup', function (event) {\n        if (event.key === 'PageUp' || event.key === 'ArrowLeft') {\n            Shepherd.activeTour.back(); }\n        if (event.key === 'PageDown' || event.key === 'ArrowRight') {\n            Shepherd.activeTour.next(); }\n        })\"\"\"\n    autoplay = False\n    if interval and interval > 0:\n        autoplay = True\n        interval = float(interval)\n        if interval < 0.5:\n            interval = 0.5\n\n    if not is_shepherd_activated(driver):\n        instructions += extra\n        activate_shepherd(driver)\n\n    if len(tour_steps[name]) > 1:\n        try:\n            selector = re.search(\n                r\"[\\S\\s]+{element: '([\\S\\s]+)', on: [\\S\\s]+\",\n                tour_steps[name][1],\n            ).group(1)\n            selector = selector.replace(\"\\\\\", \"\")\n            page_actions.wait_for_element_present(\n                driver,\n                selector,\n                by=\"css selector\",\n                timeout=settings.SMALL_TIMEOUT,\n            )\n        except Exception:\n            js_utils.post_messenger_error_message(\n                driver, \"Tour Error: {'%s'} was not found!\" % selector, msg_dur\n            )\n            raise Exception(\n                \"Tour Error: {'%s'} was not found! \"\n                \"Exiting due to failure on first tour step!\"\n                \"\" % selector\n            )\n    driver.execute_script(instructions)\n    try:\n        page_actions.wait_for_element_visible(\n            driver, \"a.tour-button-right\", by=\"css selector\", timeout=1.2\n        )\n    except Exception:\n        pass\n    try:\n        driver.execute_script('document.activeElement.blur();')\n    except Exception:\n        pass\n    tour_on = True\n    if autoplay:\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (interval * 1000.0)\n        latest_element = None\n        latest_text = None\n    while tour_on:\n        try:\n            time.sleep(0.01)\n            result = driver.execute_script(\n                \"return Shepherd.activeTour.currentStep.isOpen()\"\n            )\n        except Exception:\n            tour_on = False\n            result = None\n        if result:\n            tour_on = True\n            if autoplay:\n                try:\n                    element = driver.execute_script(\n                        \"return Shepherd.activeTour.currentStep\"\n                        \".options.attachTo.element\"\n                    )\n                    shep_text = driver.execute_script(\n                        \"return Shepherd.activeTour.currentStep\"\n                        \".options.text\"\n                    )\n                except Exception:\n                    continue\n                if element != latest_element or shep_text != latest_text:\n                    latest_element = element\n                    latest_text = shep_text\n                    start_ms = time.time() * 1000.0\n                    stop_ms = start_ms + (interval * 1000.0)\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    if (element == latest_element) and (\n                        shep_text == latest_text\n                    ):\n                        driver.execute_script(\"Shepherd.activeTour.next()\")\n                        try:\n                            latest_element = driver.execute_script(\n                                \"return Shepherd.activeTour.currentStep\"\n                                \".options.attachTo.element\"\n                            )\n                            latest_text = driver.execute_script(\n                                \"return Shepherd.activeTour.currentStep\"\n                                \".options.text\"\n                            )\n                            start_ms = time.time() * 1000.0\n                            stop_ms = start_ms + (interval * 1000.0)\n                        except Exception:\n                            pass\n                        continue\n        else:\n            try:\n                time.sleep(0.01)\n                selector = driver.execute_script(\n                    \"return Shepherd.activeTour\"\n                    \".currentStep.options.attachTo.element\"\n                )\n                try:\n                    js_utils.wait_for_css_query_selector(\n                        driver, selector, timeout=settings.SMALL_TIMEOUT\n                    )\n                except Exception:\n                    remove_script = (\n                        \"jQuery('%s').remove()\" % \"div.shepherd-content\"\n                    )\n                    driver.execute_script(remove_script)\n                    js_utils.post_messenger_error_message(\n                        driver,\n                        \"Tour Error: {'%s'} was not found!\" % selector,\n                        msg_dur,\n                    )\n                    time.sleep(0.1)\n                driver.execute_script(\"Shepherd.activeTour.next()\")\n                if autoplay:\n                    start_ms = time.time() * 1000.0\n                    stop_ms = start_ms + (interval * 1000.0)\n                tour_on = True\n            except Exception:\n                tour_on = False\n                time.sleep(0.1)\n\n\ndef play_bootstrap_tour(\n    driver, tour_steps, browser, msg_dur, name=None, interval=0\n):\n    \"\"\"Plays a Bootstrap tour on the current website.\"\"\"\n    instructions = \"\"\n    for tour_step in tour_steps[name]:\n        instructions += tour_step\n    instructions += \"\"\"]);\n        // Initialize the tour\n        tour.init();\n        // Start the tour\n        tour.start();\n        // Fix timing issue by restarting tour immediately\n        tour.restart();\n        // Save for later\n        $tour = tour;\"\"\"\n    if interval and interval > 0:\n        if interval < 1:\n            interval = 1\n        interval = str(float(interval) * 1000.0)\n        instructions = instructions.replace(\n            \"duration: 0,\", \"duration: %s,\" % interval\n        )\n    if not is_bootstrap_activated(driver):\n        activate_bootstrap(driver)\n    if len(tour_steps[name]) > 1:\n        try:\n            if \"element: \" in tour_steps[name][1]:\n                selector = re.search(\n                    r\"[\\S\\s]+element: '([\\S\\s]+)',[\\S\\s]+title: '\",\n                    tour_steps[name][1],\n                ).group(1)\n                selector = selector.replace(\"\\\\\", \"\").replace(\":first\", \"\")\n                page_actions.wait_for_element_present(\n                    driver,\n                    selector,\n                    by=\"css selector\",\n                    timeout=settings.SMALL_TIMEOUT,\n                )\n            else:\n                selector = \"html\"\n        except Exception:\n            js_utils.post_messenger_error_message(\n                driver, \"Tour Error: {'%s'} was not found!\" % selector, msg_dur\n            )\n            raise Exception(\n                \"Tour Error: {'%s'} was not found! \"\n                \"Exiting due to failure on first tour step!\"\n                \"\" % selector\n            )\n    driver.execute_script(instructions)\n    tour_on = True\n    try:\n        page_actions.wait_for_element_visible(\n            driver, \".tour-tour\", by=\"css selector\", timeout=1.2\n        )\n    except Exception:\n        pass\n    try:\n        driver.execute_script('document.activeElement.blur();')\n    except Exception:\n        pass\n    while tour_on:\n        try:\n            time.sleep(0.01)\n            if browser != \"firefox\":\n                result = driver.execute_script(\"return $tour.ended()\")\n            else:\n                page_actions.wait_for_element_present(\n                    driver, \".tour-tour\", by=\"css selector\", timeout=0.48\n                )\n                result = False\n        except Exception:\n            tour_on = False\n            result = None\n        if result is False:\n            tour_on = True\n            time.sleep(0.05)\n        else:\n            try:\n                time.sleep(0.01)\n                if browser != \"firefox\":\n                    result = driver.execute_script(\"return $tour.ended()\")\n                else:\n                    page_actions.wait_for_element_present(\n                        driver, \".tour-tour\", by=\"css selector\", timeout=0.48\n                    )\n                    result = False\n                if result is False:\n                    time.sleep(0.05)\n                    continue\n                else:\n                    return\n            except Exception:\n                tour_on = False\n                time.sleep(0.1)\n\n\ndef play_driverjs_tour(\n    driver, tour_steps, browser, msg_dur, name=None, interval=0\n):\n    \"\"\"Plays a DriverJS tour on the current website.\"\"\"\n    instructions = \"\"\n    for tour_step in tour_steps[name]:\n        instructions += tour_step\n    instructions += \"\"\"]\n        );\n        // Start the tour!\n        tour.start();\n        $tour = tour;\"\"\"\n    extra = \"\"\"\n        document.body.addEventListener('keyup', function (event) {\n        if (event.key === 'PageUp') { $tour.movePrevious(); }\n        if (event.key === 'PageDown') { $tour.moveNext(); }\n        })\"\"\"\n    autoplay = False\n    if interval and interval > 0:\n        autoplay = True\n        interval = float(interval)\n        if interval < 0.5:\n            interval = 0.5\n\n    if not is_driverjs_activated(driver):\n        instructions += extra\n        activate_driverjs(driver)\n\n    if len(tour_steps[name]) > 1:\n        try:\n            if \"element: \" in tour_steps[name][1]:\n                selector = re.search(\n                    r\"[\\S\\s]+element: '([\\S\\s]+)',[\\S\\s]+popover: {\",\n                    tour_steps[name][1],\n                ).group(1)\n                selector = selector.replace(\"\\\\\", \"\").replace(\":first\", \"\")\n                page_actions.wait_for_element_present(\n                    driver,\n                    selector,\n                    by=\"css selector\",\n                    timeout=settings.SMALL_TIMEOUT,\n                )\n            else:\n                selector = \"html\"\n        except Exception:\n            js_utils.post_messenger_error_message(\n                driver, \"Tour Error: {'%s'} was not found!\" % selector, msg_dur\n            )\n            raise Exception(\n                \"Tour Error: {'%s'} was not found! \"\n                \"Exiting due to failure on first tour step!\"\n                \"\" % selector\n            )\n\n    driver.execute_script(instructions)\n    driver.execute_script(\n        'document.querySelector(\".driver-next-btn\").focus();'\n    )\n    tour_on = True\n    if autoplay:\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (interval * 1000.0)\n        latest_step = 0\n    while tour_on:\n        try:\n            time.sleep(0.01)\n            if browser != \"firefox\":\n                result = not driver.execute_script(\"return $tour.isActivated\")\n            else:\n                page_actions.wait_for_element_visible(\n                    driver,\n                    \"#driver-popover-item\",\n                    by=\"css selector\",\n                    timeout=1.1,\n                )\n                result = False\n        except Exception:\n            tour_on = False\n            result = None\n        if result is False:\n            tour_on = True\n            driver.execute_script(\n                'document.querySelector(\".driver-next-btn\").focus();'\n            )\n            if autoplay:\n                try:\n                    current_step = driver.execute_script(\n                        \"return $tour.currentStep\"\n                    )\n                except Exception:\n                    continue\n                if current_step != latest_step:\n                    latest_step = current_step\n                    start_ms = time.time() * 1000.0\n                    stop_ms = start_ms + (interval * 1000.0)\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    if current_step == latest_step:\n                        driver.execute_script(\"$tour.moveNext()\")\n                        try:\n                            latest_step = driver.execute_script(\n                                \"return $tour.currentStep\"\n                            )\n                            start_ms = time.time() * 1000.0\n                            stop_ms = start_ms + (interval * 1000.0)\n                        except Exception:\n                            pass\n                        continue\n        else:\n            try:\n                time.sleep(0.01)\n                if browser != \"firefox\":\n                    result = not driver.execute_script(\n                        \"return $tour.isActivated\"\n                    )\n                else:\n                    page_actions.wait_for_element_visible(\n                        driver,\n                        \"#driver-popover-item\",\n                        by=\"css selector\",\n                        timeout=1.1,\n                    )\n                    result = False\n                if result is False:\n                    time.sleep(0.1)\n                    continue\n                else:\n                    return\n            except Exception:\n                tour_on = False\n                time.sleep(0.1)\n\n\ndef play_hopscotch_tour(\n    driver, tour_steps, browser, msg_dur, name=None, interval=0\n):\n    \"\"\"Plays a Hopscotch tour on the current website.\"\"\"\n    instructions = \"\"\n    for tour_step in tour_steps[name]:\n        instructions += tour_step\n    instructions += \"\"\"]\n        };\n        // Start the tour!\n        hopscotch.startTour(tour);\n        $tour = hopscotch;\"\"\"\n    extra = \"\"\"\n        document.body.addEventListener('keyup', function (event) {\n        if (event.key === 'PageUp' || event.key === 'ArrowLeft') {\n            $tour.prevStep(); }\n        if (event.key === 'PageDown' || event.key === 'ArrowRight') {\n            $tour.nextStep(); }\n        })\"\"\"\n    autoplay = False\n    if interval and interval > 0:\n        autoplay = True\n        interval = float(interval)\n        if interval < 0.5:\n            interval = 0.5\n\n    if not is_hopscotch_activated(driver):\n        instructions += extra\n        activate_hopscotch(driver)\n\n    if len(tour_steps[name]) > 1:\n        try:\n            if \"target: \" in tour_steps[name][1]:\n                selector = re.search(\n                    r\"[\\S\\s]+target: '([\\S\\s]+)',[\\S\\s]+title: '\",\n                    tour_steps[name][1],\n                ).group(1)\n                selector = selector.replace(\"\\\\\", \"\").replace(\":first\", \"\")\n                page_actions.wait_for_element_present(\n                    driver,\n                    selector,\n                    by=\"css selector\",\n                    timeout=settings.SMALL_TIMEOUT,\n                )\n            else:\n                selector = \"html\"\n        except Exception:\n            js_utils.post_messenger_error_message(\n                driver, \"Tour Error: {'%s'} was not found!\" % selector, msg_dur\n            )\n            raise Exception(\n                \"Tour Error: {'%s'} was not found! \"\n                \"Exiting due to failure on first tour step!\"\n                \"\" % selector\n            )\n\n    driver.execute_script(instructions)\n    try:\n        page_actions.wait_for_element_visible(\n            driver, \"button.hopscotch-next\", by=\"css selector\", timeout=1.2\n        )\n    except Exception:\n        pass\n    try:\n        driver.execute_script('document.activeElement.blur();')\n    except Exception:\n        pass\n    tour_on = True\n    if autoplay:\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (interval * 1000.0)\n        latest_step = 0\n    while tour_on:\n        try:\n            time.sleep(0.01)\n            if browser != \"firefox\":\n                result = not driver.execute_script(\"return $tour.isActive\")\n            else:\n                page_actions.wait_for_element_present(\n                    driver,\n                    \".hopscotch-bubble\",\n                    by=\"css selector\",\n                    timeout=0.4,\n                )\n                result = False\n        except Exception:\n            tour_on = False\n            result = None\n        if result is False:\n            tour_on = True\n            if autoplay:\n                try:\n                    current_step = driver.execute_script(\n                        \"return $tour.getCurrStepNum()\"\n                    )\n                except Exception:\n                    continue\n                if current_step != latest_step:\n                    latest_step = current_step\n                    start_ms = time.time() * 1000.0\n                    stop_ms = start_ms + (interval * 1000.0)\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    if current_step == latest_step:\n                        driver.execute_script(\"$tour.nextStep()\")\n                        try:\n                            latest_step = driver.execute_script(\n                                \"return $tour.getCurrStepNum()\"\n                            )\n                            start_ms = time.time() * 1000.0\n                            stop_ms = start_ms + (interval * 1000.0)\n                        except Exception:\n                            pass\n                        continue\n        else:\n            try:\n                time.sleep(0.01)\n                if browser != \"firefox\":\n                    result = not driver.execute_script(\"return $tour.isActive\")\n                else:\n                    page_actions.wait_for_element_present(\n                        driver,\n                        \".hopscotch-bubble\",\n                        by=\"css selector\",\n                        timeout=0.4,\n                    )\n                    result = False\n                if result is False:\n                    time.sleep(0.1)\n                    continue\n                else:\n                    return\n            except Exception:\n                tour_on = False\n                time.sleep(0.1)\n\n\ndef play_introjs_tour(\n    driver, tour_steps, browser, msg_dur, name=None, interval=0\n):\n    \"\"\"Plays an IntroJS tour on the current website.\"\"\"\n    instructions = \"\"\n    for tour_step in tour_steps[name]:\n        instructions += tour_step\n    instructions += \"\"\"]\n        });\n        intro.setOption(\"disableInteraction\", true);\n        intro.setOption(\"overlayOpacity\", .29);\n        intro.setOption(\"scrollToElement\", true);\n        intro.setOption(\"keyboardNavigation\", true);\n        intro.setOption(\"exitOnEsc\", true);\n        intro.setOption(\"hidePrev\", true);\n        intro.setOption(\"nextToDone\", true);\n        intro.setOption(\"exitOnOverlayClick\", false);\n        intro.setOption(\"showStepNumbers\", false);\n        intro.setOption(\"showProgress\", false);\n        intro.start();\n        $tour = intro;\n        };\n        // Start the tour\n        startIntro();\n        \"\"\"\n    extra = \"\"\"\n        document.body.addEventListener('keyup', function (event) {\n        if (event.key === 'PageUp') { $tour.previousStep(); }\n        if (event.key === 'PageDown') { $tour.nextStep(); }\n        })\"\"\"\n    autoplay = False\n    if interval and interval > 0:\n        autoplay = True\n        interval = float(interval)\n        if interval < 0.5:\n            interval = 0.5\n\n    if not is_introjs_activated(driver):\n        instructions += extra\n        activate_introjs(driver)\n\n    if len(tour_steps[name]) > 1:\n        try:\n            if \"element: \" in tour_steps[name][1]:\n                selector = re.search(\n                    r\"[\\S\\s]+element: '([\\S\\s]+)',[\\S\\s]+intro: '\",\n                    tour_steps[name][1],\n                ).group(1)\n                selector = selector.replace(\"\\\\\", \"\")\n                page_actions.wait_for_element_present(\n                    driver,\n                    selector,\n                    by=\"css selector\",\n                    timeout=settings.SMALL_TIMEOUT,\n                )\n            else:\n                selector = \"html\"\n        except Exception:\n            js_utils.post_messenger_error_message(\n                driver, \"Tour Error: {'%s'} was not found!\" % selector, msg_dur\n            )\n            raise Exception(\n                \"Tour Error: {'%s'} was not found! \"\n                \"Exiting due to failure on first tour step!\"\n                \"\" % selector\n            )\n    driver.execute_script(instructions)\n    tour_on = True\n    if autoplay:\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (interval * 1000.0)\n        latest_step = 0\n    while tour_on:\n        try:\n            time.sleep(0.01)\n            if browser != \"firefox\":\n                result = driver.execute_script(\"return $tour._currentStep\")\n            else:\n                page_actions.wait_for_element_present(\n                    driver, \".introjs-tooltip\", by=\"css selector\", timeout=0.4\n                )\n                result = True\n        except Exception:\n            tour_on = False\n            result = None\n        if result is not None:\n            tour_on = True\n            if autoplay:\n                try:\n                    current_step = driver.execute_script(\n                        \"return $tour._currentStep\"\n                    )\n                except Exception:\n                    continue\n                if current_step != latest_step:\n                    latest_step = current_step\n                    start_ms = time.time() * 1000.0\n                    stop_ms = start_ms + (interval * 1000.0)\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    if current_step == latest_step:\n                        try:\n                            driver.execute_script(\"$tour.nextStep()\")\n                        except Exception:\n                            driver.execute_script(\"$tour.exit()\")\n                        try:\n                            latest_step = driver.execute_script(\n                                \"return $tour._currentStep\"\n                            )\n                            start_ms = time.time() * 1000.0\n                            stop_ms = start_ms + (interval * 1000.0)\n                        except Exception:\n                            pass\n                        continue\n        else:\n            try:\n                time.sleep(0.01)\n                if browser != \"firefox\":\n                    result = driver.execute_script(\"return $tour._currentStep\")\n                else:\n                    page_actions.wait_for_element_present(\n                        driver,\n                        \".introjs-tooltip\",\n                        by=\"css selector\",\n                        timeout=0.4,\n                    )\n                    result = True\n                if result is not None:\n                    time.sleep(0.1)\n                    continue\n                else:\n                    return\n            except Exception:\n                tour_on = False\n                time.sleep(0.1)\n\n\ndef export_tour(tour_steps, name=None, filename=\"my_tour.js\", url=None):\n    \"\"\"Exports a tour as a JS file.\n    It will include necessary resources as well, such as jQuery.\n    You'll be able to copy the tour directly into the Console of\n    any web browser to play the tour outside of SeleniumBase runs.\"\"\"\n    if not name:\n        name = \"default\"\n    if name not in tour_steps:\n        raise Exception(\"Tour {%s} does not exist!\" % name)\n    if not filename.endswith(\".js\"):\n        raise Exception('Tour file must end in \".js\"!')\n    if not url:\n        url = \"data:,\"\n\n    tour_type = None\n    if \"Bootstrap\" in tour_steps[name][0]:\n        tour_type = \"bootstrap\"\n    elif \"DriverJS\" in tour_steps[name][0]:\n        tour_type = \"driverjs\"\n    elif \"Hopscotch\" in tour_steps[name][0]:\n        tour_type = \"hopscotch\"\n    elif \"IntroJS\" in tour_steps[name][0]:\n        tour_type = \"introjs\"\n    elif \"Shepherd\" in tour_steps[name][0]:\n        tour_type = \"shepherd\"\n    else:\n        raise Exception(\"Unknown tour type!\")\n\n    instructions = (\n        \"\"\"////////  Load Tour Start Page (if not there now)  ////////\\n\\n\"\"\"\n        \"\"\"if (window.location.href != \"%s\") {\\n\"\"\"\n        \"\"\"    window.location.href=\"%s\";\\n\"\"\"\n        \"\"\"}\\n\\n\"\"\"\n        \"\"\"////////  Resources  ////////\\n\\n\"\"\"\n        \"\"\"function injectCSS(css_link) {\"\"\"\n        \"\"\"var head = document.getElementsByTagName(\"head\")[0];\"\"\"\n        \"\"\"var link = document.createElement(\"link\");\"\"\"\n        \"\"\"link.rel = \"stylesheet\";\"\"\"\n        \"\"\"link.type = \"text/css\";\"\"\"\n        \"\"\"link.href = css_link;\"\"\"\n        \"\"\"link.crossorigin = \"anonymous\";\"\"\"\n        \"\"\"head.appendChild(link);\"\"\"\n        \"\"\"};\\n\"\"\"\n        \"\"\"function injectJS(js_link) {\"\"\"\n        \"\"\"var head = document.getElementsByTagName(\"head\")[0];\"\"\"\n        \"\"\"var script = document.createElement(\"script\");\"\"\"\n        \"\"\"script.src = js_link;\"\"\"\n        \"\"\"script.defer;\"\"\"\n        \"\"\"script.type=\"text/javascript\";\"\"\"\n        \"\"\"script.crossorigin = \"anonymous\";\"\"\"\n        \"\"\"script.onload = function() { null };\"\"\"\n        \"\"\"head.appendChild(script);\"\"\"\n        \"\"\"};\\n\"\"\"\n        \"\"\"function injectStyle(css) {\"\"\"\n        \"\"\"var head = document.getElementsByTagName(\"head\")[0];\"\"\"\n        \"\"\"var style = document.createElement(\"style\");\"\"\"\n        \"\"\"style.type = \"text/css\";\"\"\"\n        \"\"\"style.appendChild(document.createTextNode(css));\"\"\"\n        \"\"\"head.appendChild(style);\"\"\"\n        \"\"\"};\\n\"\"\" % (url, url)\n    )\n\n    if tour_type == \"bootstrap\":\n        jquery_js = constants.JQuery.MIN_JS\n        bootstrap_tour_css = constants.BootstrapTour.MIN_CSS\n        bootstrap_tour_js = constants.BootstrapTour.MIN_JS\n        backdrop_style = style_sheet.get_bt_backdrop_style()\n        backdrop_style = backdrop_style.replace(\"\\n\", \"\")\n        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)\n        instructions += 'injectJS(\"%s\");\\n' % jquery_js\n        instructions += \"\\n\"\n        instructions += \"function loadResources() { \"\n        instructions += 'if ( typeof jQuery !== \"undefined\" ) {\\n'\n        instructions += 'injectCSS(\"%s\");\\n' % bootstrap_tour_css\n        instructions += 'injectStyle(\"%s\");\\n' % backdrop_style\n        instructions += 'injectJS(\"%s\");' % bootstrap_tour_js\n        instructions += '} else { window.setTimeout(\"loadResources();\",100); '\n        instructions += \"} }\\n\"\n        instructions += \"loadResources()\"\n\n    elif tour_type == \"driverjs\":\n        driverjs_css = constants.DriverJS.MIN_CSS\n        driverjs_js = constants.DriverJS.MIN_JS\n        backdrop_style = style_sheet.get_dt_backdrop_style()\n        backdrop_style = backdrop_style.replace(\"\\n\", \"\")\n        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)\n        instructions += 'injectCSS(\"%s\");\\n' % driverjs_css\n        instructions += 'injectStyle(\"%s\");\\n' % backdrop_style\n        instructions += 'injectJS(\"%s\");' % driverjs_js\n\n    elif tour_type == \"hopscotch\":\n        hopscotch_css = constants.Hopscotch.MIN_CSS\n        hopscotch_js = constants.Hopscotch.MIN_JS\n        backdrop_style = style_sheet.get_hops_backdrop_style()\n        backdrop_style = backdrop_style.replace(\"\\n\", \"\")\n        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)\n        instructions += 'injectCSS(\"%s\");\\n' % hopscotch_css\n        instructions += 'injectStyle(\"%s\");\\n' % backdrop_style\n        instructions += 'injectJS(\"%s\");' % hopscotch_js\n\n    elif tour_type == \"introjs\":\n        intro_css = constants.IntroJS.MIN_CSS\n        intro_js = constants.IntroJS.MIN_JS\n        theme_color = sb_config.introjs_theme_color\n        hover_color = sb_config.introjs_hover_color\n        backdrop_style = style_sheet.get_introjs_style() % (\n            theme_color,\n            hover_color,\n            hover_color,\n            hover_color,\n            theme_color,\n        )\n        backdrop_style = backdrop_style.replace(\"\\n\", \"\")\n        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)\n        instructions += 'injectCSS(\"%s\");\\n' % intro_css\n        instructions += 'injectStyle(\"%s\");\\n' % backdrop_style\n        instructions += 'injectJS(\"%s\");' % intro_js\n\n    elif tour_type == \"shepherd\":\n        jquery_js = constants.JQuery.MIN_JS\n        shepherd_js = constants.Shepherd.MIN_JS\n        sh_theme_arrows_css = constants.Shepherd.THEME_ARROWS_CSS\n        sh_theme_arrows_fix_css = constants.Shepherd.THEME_ARR_FIX_CSS\n        sh_theme_default_css = constants.Shepherd.THEME_DEFAULT_CSS\n        sh_theme_dark_css = constants.Shepherd.THEME_DARK_CSS\n        sh_theme_sq_css = constants.Shepherd.THEME_SQ_CSS\n        sh_theme_sq_dark_css = constants.Shepherd.THEME_SQ_DK_CSS\n        tether_js = constants.Tether.MIN_JS\n        spinner_css = constants.Messenger.SPINNER_CSS\n        backdrop_style = style_sheet.get_sh_backdrop_style()\n        backdrop_style = backdrop_style.replace(\"\\n\", \"\")\n        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)\n        instructions += 'injectCSS(\"%s\");\\n' % spinner_css\n        instructions += 'injectJS(\"%s\");\\n' % jquery_js\n        instructions += 'injectJS(\"%s\");\\n' % tether_js\n        instructions += \"\\n\"\n        instructions += \"function loadResources() { \"\n        instructions += 'if ( typeof jQuery !== \"undefined\" ) {\\n'\n        instructions += 'injectCSS(\"%s\");' % sh_theme_arrows_css\n        instructions += 'injectCSS(\"%s\");' % sh_theme_arrows_fix_css\n        instructions += 'injectCSS(\"%s\");' % sh_theme_default_css\n        instructions += 'injectCSS(\"%s\");' % sh_theme_dark_css\n        instructions += 'injectCSS(\"%s\");' % sh_theme_sq_css\n        instructions += 'injectCSS(\"%s\");\\n' % sh_theme_sq_dark_css\n        instructions += 'injectStyle(\"%s\");\\n' % backdrop_style\n        instructions += 'injectJS(\"%s\");\\n' % shepherd_js\n        instructions += '} else { window.setTimeout(\"loadResources();\",100); '\n        instructions += \"} }\\n\"\n        instructions += \"loadResources()\"\n\n    instructions += \"\\n\\n////////  Tour Code  ////////\\n\\n\"\n    first_instructions = instructions\n    instructions = \"        \"\n    if tour_type == \"bootstrap\":\n        instructions += \"function loadTour() { \"\n        instructions += 'if ( typeof Tour !== \"undefined\" ) {\\n'\n    elif tour_type == \"driverjs\":\n        instructions += \"function loadTour() { \"\n        instructions += 'if ( typeof Driver !== \"undefined\" ) {\\n'\n    elif tour_type == \"hopscotch\":\n        instructions += \"function loadTour() { \"\n        instructions += 'if ( typeof hopscotch !== \"undefined\" ) {\\n'\n    elif tour_type == \"introjs\":\n        instructions += \"function loadTour() { \"\n        instructions += 'if ( typeof introJs !== \"undefined\" ) {\\n'\n    elif tour_type == \"shepherd\":\n        instructions += \"function loadTour() { \"\n        instructions += 'if ( typeof Shepherd !== \"undefined\" ) {\\n'\n\n    for tour_step in tour_steps[name]:\n        instructions += tour_step\n\n    if tour_type == \"bootstrap\":\n        instructions += \"\"\"]);\n            // Initialize the tour\n            tour.init();\n            // Start the tour\n            tour.start();\n            $tour = tour;\n            $tour.restart();\\n\"\"\"\n    elif tour_type == \"driverjs\":\n        instructions += \"\"\"]\n            );\n            // Start the tour!\n            tour.start();\n            $tour = tour;\\n\"\"\"\n    elif tour_type == \"hopscotch\":\n        instructions += \"\"\"]\n            };\n            // Start the tour!\n            hopscotch.startTour(tour);\n            $tour = hopscotch;\\n\"\"\"\n    elif tour_type == \"introjs\":\n        instructions += \"\"\"]\n            });\n            intro.setOption(\"disableInteraction\", true);\n            intro.setOption(\"overlayOpacity\", .29);\n            intro.setOption(\"scrollToElement\", true);\n            intro.setOption(\"keyboardNavigation\", true);\n            intro.setOption(\"exitOnEsc\", true);\n            intro.setOption(\"hidePrev\", true);\n            intro.setOption(\"nextToDone\", true);\n            intro.setOption(\"exitOnOverlayClick\", false);\n            intro.setOption(\"showStepNumbers\", false);\n            intro.setOption(\"showProgress\", false);\n            intro.start();\n            $tour = intro;\n            };\n            startIntro();\\n\"\"\"\n    elif tour_type == \"shepherd\":\n        instructions += \"\"\"\n            tour.start();\n            $tour = tour;\\n\"\"\"\n    else:\n        pass\n    instructions = textwrap.dedent(instructions)\n    instructions = first_instructions + instructions\n    instructions += '\\n} else { window.setTimeout(\"loadTour();\",100); } '\n    instructions += \"}\\n\"\n    instructions += \"loadTour()\\n\"\n\n    exported_tours_folder = EXPORTED_TOURS_FOLDER\n    if exported_tours_folder.endswith(\"/\"):\n        exported_tours_folder = exported_tours_folder[:-1]\n    if not os.path.exists(exported_tours_folder):\n        try:\n            os.makedirs(exported_tours_folder)\n        except Exception:\n            pass\n\n    file_path = exported_tours_folder + \"/\" + filename\n    out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    out_file.writelines(instructions)\n    out_file.close()\n    print(\"\\n>>> [%s] was saved!\\n\" % file_path)\n"
  },
  {
    "path": "seleniumbase/core/visual_helper.py",
    "content": "import os\nfrom seleniumbase.core import log_helper\nfrom seleniumbase.fixtures import constants\n\nVISUAL_BASELINE_DIR = constants.VisualBaseline.STORAGE_FOLDER\nabs_path = os.path.abspath(\".\")\nvisual_baseline_path = os.path.join(abs_path, VISUAL_BASELINE_DIR)\n\n\ndef get_visual_baseline_folder():\n    return visual_baseline_path\n\n\ndef visual_baseline_folder_setup():\n    \"\"\"Handle Logging\"\"\"\n    if not os.path.exists(visual_baseline_path):\n        try:\n            os.makedirs(visual_baseline_path)\n        except Exception:\n            pass  # Should only be reachable during multi-threaded runs\n\n\ndef get_sbs_head():\n    # Uses caching to prevent extra method calls\n    SIDE_BY_SIDE_PNG = constants.SideBySide.get_favicon()\n    head = (\n        '<head><meta charset=\"utf-8\">'\n        '<meta name=\"viewport\" content=\"shrink-to-fit=no\">'\n        '<link rel=\"shortcut icon\" href=\"%s\">'\n        \"<title>Visual Comparison</title>\"\n        \"</head>\" % (SIDE_BY_SIDE_PNG)\n    )\n    return head\n\n\ndef get_sbs_table_row(baseline=\"baseline.png\", diff=\"baseline_diff.png\"):\n    row = (\n        '<tbody class=\"compare results-table-row\">'\n        '<tr style=\"background-color: #F4F4FE;\">'\n        '<td><img src=\"%s\" width=\"100%%\" /></td>'\n        '<td><img src=\"%s\" width=\"100%%\" /></td>'\n        \"</tr></tbody>\"\n        \"\" % (baseline, diff)\n    )\n    return row\n\n\ndef get_sbs_table_html(baseline=\"baseline.png\", diff=\"baseline_diff.png\"):\n    table_html = (\n        '<table border=\"3px solid #E6E6E6;\" width=\"100%;\" padding: 12px;'\n        ' font-size=\"16px;\" text-align=\"left;\" id=\"results-table\"'\n        ' style=\"background-color: #FAFAFA;\">'\n        '<thead id=\"results-table-head\">'\n        \"<tr>\"\n        '<th style=\"background-color: rgba(0, 128, 0, 0.25);\"'\n        ' col=\"baseline\">Baseline Screenshot</th>'\n        '<th style=\"background-color: rgba(128, 0, 0, 0.25);\"'\n        ' col=\"failure\">Visual Diff Failure Screenshot</th>'\n        \"</tr></thead>\"\n    )\n    row = get_sbs_table_row(baseline, diff)\n    table_html += row\n    table_html += \"</table>\"\n    return table_html\n\n\ndef get_sbs_gen_by():\n    gen_by = (\n        '<p><div>Generated by: <b><a href=\"https://seleniumbase.io/\">'\n        \"SeleniumBase</a></b></div></p><p></p>\"\n    )\n    return gen_by\n\n\ndef get_sbs_header_text():\n    header_text = \"SeleniumBase Visual Comparison\"\n    return header_text\n\n\ndef get_sbs_header():\n    header_text = get_sbs_header_text()\n    header = '<h3 align=\"center\">%s</h3>' % header_text\n    return header\n\n\ndef get_sbs_footer():\n    footer = \"<br /><b>Last updated:</b> \"\n    timestamp, the_date, the_time = log_helper.get_master_time()\n    last_updated = \"%s at %s\" % (the_date, the_time)\n    footer = footer + \"%s\" % last_updated\n    gen_by = get_sbs_gen_by()\n    footer = footer + gen_by\n    return footer\n\n\ndef get_sbs_html(baseline=\"baseline.png\", diff=\"baseline_diff.png\"):\n    head = get_sbs_head()\n    header = get_sbs_header()\n    table_html = get_sbs_table_html(baseline, diff)\n    footer = get_sbs_footer()\n    the_html = (\n        '<html lang=\"en\">'\n        + head\n        + '<body style=\"background-color: #FCFCF4;\">'\n        + header\n        + table_html\n        + footer\n        + \"</body>\"\n    )\n    return the_html\n"
  },
  {
    "path": "seleniumbase/drivers/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## <img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> SeleniumBase driver storage\n\nTo run web automation, you'll need webdrivers for each browser you plan on using.  With SeleniumBase, drivers are downloaded automatically as needed into the SeleniumBase `drivers` folder.\n\n🎛️ You can also download drivers manually with these commands:\n\n```zsh\nseleniumbase get chromedriver\nseleniumbase get geckodriver\nseleniumbase get edgedriver\n```\n\nAfter running the commands above, web drivers will get downloaded into the `seleniumbase/drivers/` folder. SeleniumBase uses those drivers during tests. (The drivers don't come with SeleniumBase by default.)\n\nIf the necessary driver is not found in this location while running tests, SeleniumBase will instead look for the driver on the System PATH. If the necessary driver is not on the System PATH either, SeleniumBase will automatically attempt to download the required driver.\n\n🎛️ You can also download specific versions of drivers. Examples:\n\n```zsh\nsbase get chromedriver 114\nsbase get chromedriver 114.0.5735.90\nsbase get chromedriver stable\nsbase get chromedriver beta\nsbase get chromedriver dev\nsbase get chromedriver canary\nsbase get chromedriver previous  # One major version before the stable version\nsbase get chromedriver mlatest  # Milestone latest version for detected browser\nsbase get edgedriver 115.0.1901.183\n```\n\n(NOTE: `sbase` is a shortcut for `seleniumbase`)\n\n--------\n\n**Browser Binaries**:\n\n🎛️ Use the `sbase get` command to download the `Chrome for Testing` and `Chrome-Headless-Shell` browser binaries. Example:\n\n```zsh\nsbase get chromium  # (For base `Chromium`)\nsbase get cft  # (For `Chrome for Testing`)\nsbase get chs  # (For `Chrome-Headless-Shell`)\n```\n\nThose commands download those binaries into the `seleniumbase/drivers` folder. (There are subfolders, such as `cft_drivers`, `chs_drivers`, and `chromium_drivers`.)\n\nTo use the base `Chromium` binary in SeleniumBase scripts, add `--use-chromium` on the command-line, or set `use_chromium=True` from within scripts.\n\nTo use the `cft` or `chs` binaries in SeleniumBase scripts, set the `binary_location` to `cft` or `chs`, use `--cft` / `--chs` or set `cft=True` / `chs=True`.\n\n(Source: https://googlechromelabs.github.io/chrome-for-testing/)\n\n--------\n\n[<img src=\"https://seleniumbase.github.io/cdn/img/sb_logo_b.png\" title=\"SeleniumBase\" width=\"280\">](https://github.com/seleniumbase/SeleniumBase)\n"
  },
  {
    "path": "seleniumbase/drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/drivers/atlas_drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/drivers/brave_drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/drivers/cft_drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/drivers/chromium_drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/drivers/chs_drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/drivers/comet_drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/drivers/opera_drivers/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/extensions/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n[<img src=\"https://seleniumbase.github.io/cdn/img/sb_text_f.png\" title=\"SeleniumBase\" align=\"center\" width=\"360\">](https://github.com/seleniumbase/SeleniumBase)\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> SeleniumBase browser extension storage</h2>\n\n<b>The List:</b>\n\n* ad_block.zip => This extension blocks certain types of iframe ads from loading.\n* disable_csp.zip => This extension disables a website's Content-Security-Policy.\n* recorder.zip => Save browser actions to sessionStorage with good CSS selectors.\n* sbase_ext.zip => A Chromium extension that does nothing. (Used for testing purposes)\n* firefox_addon.xpi => A Firefox add-on that does nothing. (Used for testing purposes)\n"
  },
  {
    "path": "seleniumbase/extensions/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/fixtures/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/fixtures/base_case.py",
    "content": "r\"\"\"----------------------------------------------------------------->\n|    ______     __           _                  ____                 |\n|   / ____/__  / /__  ____  (_)_  ______ ___   / _  \\____  ________  |\n|   \\__ \\/ _ \\/ / _ \\/ __ \\/ / / / / __ `__ \\ / /_) / __ \\/ ___/ _ \\ |\n|  ___/ /  __/ /  __/ / / / / /_/ / / / / / // /_) / (_/ /__  /  __/ |\n| /____/\\___/_/\\___/_/ /_/_/\\__,_/_/ /_/ /_//_____/\\__,_/____/\\___/  |\n|                                                                    |\n--------------------------------------------------------------------->\n\nThe BaseCase class is the main gateway for using The SeleniumBase Framework.\nIt inherits Python's unittest.TestCase class and runs with pytest or pynose.\nAll tests using BaseCase automatically launch WebDriver browsers for tests.\n\nExample Test:\n\n# --------------------------------------------------------------\nfrom seleniumbase import BaseCase\nBaseCase.main(__name__, __file__)\nclass MyTestClass(BaseCase):\n    def test_anything(self):\n        # Write your code here. Example:\n        self.open(\"https://github.com/\")\n        self.click('span[data-target*=\"inputButtonText\"]')\n        self.type(\"input#query-builder-test\", \"SeleniumBase\\n\")\n        self.click('a[href=\"/seleniumbase/SeleniumBase\"]')\n        self.assert_element(\"div.repository-content\")\n        self.assert_text(\"SeleniumBase\", \"strong a\")\n# --------------------------------------------------------------\n\nSeleniumBase methods expand and improve on existing WebDriver commands.\nImprovements include making WebDriver more robust, reliable, and flexible.\nPage elements are given enough time to load before WebDriver acts on them.\nCode becomes greatly simplified and easier to maintain.\"\"\"\n\nimport colorama\nimport fasteners\nimport json\nimport logging\nimport math\nimport os\nimport re\nimport shutil\nimport sys\nimport textwrap\nimport time\nimport unittest\nimport urllib3\nfrom contextlib import contextmanager, suppress\nfrom selenium.common.exceptions import (\n    ElementClickInterceptedException as ECI_Exception,\n    ElementNotInteractableException as ENI_Exception,\n    InvalidArgumentException,\n    MoveTargetOutOfBoundsException,\n    NoSuchElementException,\n    NoSuchWindowException,\n    StaleElementReferenceException as Stale_Exception,\n    TimeoutException,\n    WebDriverException,\n)\nfrom selenium.webdriver.common.action_chains import ActionChains\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.common.keys import Keys\nfrom selenium.webdriver.remote.remote_connection import LOGGER\nfrom selenium.webdriver.remote.webelement import WebElement\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.__version__ import __version__\nfrom seleniumbase.common import decorators\nfrom seleniumbase.common.exceptions import (\n    NotConnectedException,\n    NotUsingChromeException,\n    NotUsingChromiumException,\n    ProxyConnectionException,\n    OutOfScopeException,\n    VisualException,\n)\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import browser_launcher\nfrom seleniumbase.core import download_helper\nfrom seleniumbase.core import log_helper\nfrom seleniumbase.core import session_helper\nfrom seleniumbase.core import visual_helper\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import css_to_xpath\nfrom seleniumbase.fixtures import js_utils\nfrom seleniumbase.fixtures import page_actions\nfrom seleniumbase.fixtures import page_utils\nfrom seleniumbase.fixtures import shared_utils\nfrom seleniumbase.fixtures import unittest_helper\nfrom seleniumbase.fixtures import xpath_to_css\n\n__all__ = [\"BaseCase\"]\n\nlogging.getLogger(\"requests\").setLevel(logging.ERROR)\nlogging.getLogger(\"urllib3\").setLevel(logging.ERROR)\nlogging.getLogger(\"websocket\").setLevel(logging.CRITICAL)\nurllib3.disable_warnings()\nLOGGER.setLevel(logging.WARNING)\nis_linux = shared_utils.is_linux()\nis_windows = shared_utils.is_windows()\npython3_11_or_newer = False\nif sys.version_info >= (3, 11):\n    python3_11_or_newer = True\npy311_patch2 = constants.PatchPy311.PATCH\n\n\nclass BaseCase(unittest.TestCase):\n    \"\"\"<Class seleniumbase.BaseCase>\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.__initialize_variables()\n\n    def __initialize_variables(self):\n        self.driver = None\n        self.environment = None\n        self.env = None  # Add a shortened version of self.environment\n        self.version_list = shared_utils.make_version_list(__version__)\n        self.version_tuple = tuple(self.version_list)\n        self.version_info = self.version_tuple\n        self.time = time.time\n        self.__page_sources = []\n        self.__extra_actions = []\n        self.__js_start_time = 0\n        self.__set_c_from_switch = False\n        self.__frame_switch_layer = 0  # Used by Recorder-Mode\n        self.__frame_switch_multi = False  # Used by Recorder-Mode\n        self.__last_saved_url = None  # Used by Recorder-Mode\n        self.__uc_frame_layer = 0\n        self.__called_setup = False\n        self.__called_teardown = False\n        self.__start_time_ms = int(time.time() * 1000.0)\n        self.__requests_timeout = None\n        self.__page_source_count = 0\n        self.__screenshot_count = 0\n        self.__saved_pdf_count = 0\n        self.__logs_data_count = 0\n        self.__last_data_file = None\n        self.__level_0_visual_f = False\n        self.__will_be_skipped = False\n        self.__passed_then_skipped = False\n        self.__visual_baseline_copies = []\n        self.__last_url_of_deferred_assert = \"about:blank\"\n        self.__last_page_load_url = \"about:blank\"\n        self.__last_page_screenshot = None\n        self.__last_page_screenshot_png = None\n        self.__last_page_url = None\n        self.__last_page_source = None\n        self.__skip_reason = None\n        self.__origins_to_save = []\n        self.__actions_to_save = []\n        self.__dont_record_open = False\n        self.__dont_record_js_click = False\n        self.__new_window_on_rec_open = True\n        self.__overrided_default_timeouts = False\n        self.__added_pytest_html_extra = None\n        self.__deferred_assert_count = 0\n        self.__deferred_assert_failures = []\n        self.__device_width = None\n        self.__device_height = None\n        self.__device_pixel_ratio = None\n        self.__changed_jqc_theme = False\n        self.__jqc_default_theme = None\n        self.__jqc_default_color = None\n        self.__jqc_default_width = None\n        self.__saved_id = None\n        # Requires self._* instead of self.__* for external class use\n        self._language = \"English\"\n        self._presentation_slides = {}\n        self._presentation_transition = {}\n        self._output_file_saves = True  # For Presenter / ChartMaker\n        self._rec_overrides_switch = True  # Recorder-Mode uses set_c vs switch\n        self._sb_test_identifier = None\n        self._html_report_extra = []  # (Used by pytest_plugin.py)\n        self._last_page_screenshot = None\n        self._last_page_url = None\n        self._final_debug = None\n        self._default_driver = None\n        self._drivers_list = []\n        self._drivers_browser_map = {}\n        self._was_skipped = False\n        self._chart_data = {}\n        self._chart_count = 0\n        self._chart_label = {}\n        self._chart_xcount = 0\n        self._chart_first_series = {}\n        self._chart_series_count = {}\n        self._tour_steps = {}\n        self._xvfb_display = None\n        self._xvfb_width = None\n        self._xvfb_height = None\n\n    @classmethod\n    def main(self, name, file, *args):\n        \"\"\"Run pytest if file was called with \"python\".\n        Usage example:\n\n            from seleniumbase import BaseCase\n            BaseCase.main(__name__, __file__)\n\n            class MyTestClass(BaseCase):\n                def test_example(self):\n                    pass\n\n        The run command:\n            python my_test.py  # (Instead of \"pytest my_test.py\")\n\n        This is useful when sharing code with people who may not be aware\n        that SeleniumBase tests are run with \"pytest\" instead of \"python\".\n        Now, if they accidentally type \"python\", the tests will still run.\n        Eg. \"python my_test.py\" instead of \"pytest my_test.py\".\"\"\"\n        if name == \"__main__\":  # Test called with \"python\"\n            import subprocess\n            all_args = []\n            for arg in args:\n                all_args.append(arg)\n            for arg in sys.argv[1:]:\n                all_args.append(arg)\n            # See: https://stackoverflow.com/a/54666289/7058266\n            # from pytest import main as pytest_main\n            # pytest_main([file, \"-s\", *all_args])\n            subprocess.call(\n                [sys.executable, \"-m\", \"pytest\", file, \"-s\", *all_args]\n            )\n\n    def open(self, url, **kwargs):\n        \"\"\"Navigates the current browser window to the specified page.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.open(url, **kwargs)\n            return\n        elif (\n            getattr(self.driver, \"_is_using_uc\", None)\n            # and getattr(self.driver, \"_is_using_auth\", None)\n            and not getattr(self.driver, \"_is_using_cdp\", None)\n        ):\n            # Auth in UC Mode requires CDP Mode\n            # (and now we're always forcing it)\n            logging.info(\"open() in UC Mode now always activates CDP Mode.\")\n            self.activate_cdp_mode(url, **kwargs)\n            return\n        elif (\n            getattr(self.driver, \"_is_using_uc\", None)\n            and getattr(self.driver, \"_is_using_cdp\", None)\n        ):\n            self.disconnect()\n            self.cdp.open(url, **kwargs)\n            return\n        self._check_browser()\n        if self.__needs_minimum_wait():\n            time.sleep(0.04)\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        url = str(url).strip()  # Remove leading and trailing whitespace\n        if not self.__looks_like_a_page_url(url):\n            # url should start with one of the following:\n            # \"http:\", \"https:\", \"://\", \"data:\", \"file:\",\n            # \"about:\", \"chrome:\", or \"edge:\".\n            if page_utils.is_valid_url(\"https://\" + url):\n                url = \"https://\" + url\n            else:\n                raise Exception('Invalid URL: \"%s\"!' % url)\n        self.__last_page_load_url = None\n        js_utils.clear_out_console_logs(self.driver)\n        if url.startswith(\"://\"):\n            # Convert URLs such as \"://google.com\" into \"https://google.com\"\n            url = \"https\" + url\n        if self.recorder_mode and not self.__dont_record_open:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"o_url\", origin, url, str(int(time_stamp) - 1)]\n            self.__extra_actions.append(action)\n            action = [\"_url_\", origin, url, time_stamp]\n            self.__extra_actions.append(action)\n        if self.recorder_mode and self.__new_window_on_rec_open:\n            c_url = self.driver.current_url\n            if (\"http:\") in c_url or (\"https:\") in c_url or (\"file:\") in c_url:\n                if self.get_domain_url(url) != self.get_domain_url(c_url):\n                    self.open_new_window(switch_to=True)\n        try:\n            self.driver.get(url)\n        except Exception as e:\n            if not hasattr(e, \"msg\") and hasattr(self.driver, \"default_get\"):\n                try:\n                    self._check_browser()\n                    time.sleep(0.4)\n                except Exception:\n                    logging.debug(\"Browser crashed! Will open new browser!\")\n                    self.driver = self.get_new_driver()\n                self.driver.default_get(url)\n            elif (\n                \"ERR_CONNECTION_TIMED_OUT\" in e.msg\n                or \"ERR_CONNECTION_CLOSED\" in e.msg\n                or \"ERR_CONNECTION_RESET\" in e.msg\n                or \"ERR_NAME_NOT_RESOLVED\" in e.msg\n            ):\n                shared_utils.check_if_time_limit_exceeded()\n                self._check_browser()\n                time.sleep(0.8)\n                self.driver.get(url)\n            elif (\n                \"ERR_INTERNET_DISCONNECTED\" in e.msg\n                or \"neterror?e=dnsNotFound\" in e.msg\n                or \"ERR_PROXY_CONNECTION_FAILED\" in e.msg\n            ):\n                shared_utils.check_if_time_limit_exceeded()\n                self._check_browser()\n                time.sleep(1.05)\n                try:\n                    self.driver.get(url)\n                except Exception as e2:\n                    if (\n                        \"ERR_INTERNET_DISCONNECTED\" in e2.msg\n                        or \"neterror?e=dnsNotFound\" in e2.msg\n                    ):\n                        message = \"ERR_INTERNET_DISCONNECTED: \"\n                        message += \"Internet unreachable!\"\n                        raise NotConnectedException(message)\n                    elif \"ERR_PROXY_CONNECTION_FAILED\" in e2.msg:\n                        message = \"ERR_PROXY_CONNECTION_FAILED: \"\n                        message += \"Internet unreachable and/or invalid proxy!\"\n                        raise ProxyConnectionException(message)\n                    else:\n                        raise\n            elif \"Timed out receiving message from renderer\" in e.msg:\n                page_load_timeout = self.driver.timeouts.page_load\n                logging.info(\n                    \"The page load timed out after %s seconds! Will retry...\"\n                    % page_load_timeout\n                )\n                try:\n                    self.driver.get(url)\n                except Exception as e:\n                    if \"Timed out receiving message from renderer\" in e.msg:\n                        raise Exception(\n                            \"Retry of page load timed out after %s seconds!\"\n                            % page_load_timeout\n                        )\n                    else:\n                        raise\n            elif (\n                \"cannot determine loading status\" in e.msg\n                or \"unexpected command response\" in e.msg\n            ):\n                if self.__needs_minimum_wait():\n                    time.sleep(0.2)\n                    self.driver.get(url)\n                else:\n                    pass  # Odd issue where the open did happen. Continue.\n            elif \"invalid session id\" in e.msg:\n                logging.debug(\"Invalid session id. Will open new browser.\")\n                self.driver = self.get_new_driver()\n                self.driver.get(url)\n            else:\n                raise\n        try:\n            if (\n                self.driver.current_url == pre_action_url\n                and pre_action_url != url\n            ):\n                time.sleep(0.1)  # Make sure load happens\n        except Exception:\n            time.sleep(0.1)  # First see if waiting helps\n            try:\n                self._check_browser()\n                if not self.driver.current_url:\n                    raise Exception(\"No current URL!\")\n            except Exception:\n                # Spin up a new driver with the URL\n                self.driver = self.get_new_driver()\n                self.driver.get(url)\n                self._check_browser()\n        if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n            if not self.undetectable:\n                self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.08)  # Force a minimum wait, even if skipping waits.\n        if self.undetectable:\n            self.__uc_frame_layer = 0\n        if self.demo_mode:\n            if self.driver.current_url.startswith((\"http\", \"file\", \"data\")):\n                if not js_utils.is_jquery_activated(self.driver):\n                    with suppress(Exception):\n                        js_utils.add_js_link(\n                            self.driver, constants.JQuery.MIN_JS\n                        )\n            self.__demo_mode_pause_if_active()\n\n    def get(self, url):\n        \"\"\"If \"url\" looks like a page URL, open the URL in the web browser.\n        Otherwise, return self.get_element(URL_AS_A_SELECTOR)\n        Examples:\n            self.get(\"https://seleniumbase.io\")  # Navigates to the URL\n            self.get(\"input.class\")  # Finds and returns the WebElement \"\"\"\n        self.__check_scope()\n        if self.__looks_like_a_page_url(url):\n            self.open(url)\n        else:\n            return self.get_element(url)  # url is treated like a selector\n\n    def click(\n        self, selector, by=\"css selector\", timeout=None, delay=0, scroll=True\n    ):\n        self.__check_scope()\n        self.__skip_if_esc()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        original_by = by\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.click(selector, timeout=timeout)\n            return\n        if delay and (type(delay) in [int, float]) and delay > 0:\n            time.sleep(delay)\n        if page_utils.is_link_text_selector(selector) or by == By.LINK_TEXT:\n            if not self.is_link_text_visible(selector):\n                # Handle a special case of links hidden in dropdowns\n                self.click_link_text(selector, timeout=timeout)\n                return\n        if (\n            page_utils.is_partial_link_text_selector(selector)\n            or by == By.PARTIAL_LINK_TEXT\n        ):\n            if not self.is_partial_link_text_visible(selector):\n                # Handle a special case of partial links hidden in dropdowns\n                self.click_partial_link_text(selector, timeout=timeout)\n                return\n        if self.__is_shadow_selector(selector):\n            self.__shadow_click(selector, timeout)\n            return\n        if self.__needs_minimum_wait() or self.browser == \"safari\":\n            time.sleep(0.05)\n        element = page_actions.wait_for_element_visible(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n        self.__demo_mode_highlight_if_active(original_selector, original_by)\n        if scroll and not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        try:\n            if (\n                by == By.LINK_TEXT\n                and (self.browser == \"ie\" or self.browser == \"safari\")\n            ):\n                self.__jquery_click(selector, by=by)\n            elif self.browser == \"safari\":\n                self.execute_script(\"arguments[0].click();\", element)\n            else:\n                href = None\n                new_tab = False\n                onclick = None\n                with suppress(Exception):\n                    if self.headless and element.tag_name.lower() == \"a\":\n                        # Handle a special case of opening a new tab (headless)\n                        href = element.get_attribute(\"href\").strip()\n                        onclick = element.get_attribute(\"onclick\")\n                        target = element.get_attribute(\"target\")\n                        if target == \"_blank\":\n                            new_tab = True\n                        if new_tab and self.__looks_like_a_page_url(href):\n                            if onclick:\n                                with suppress(Exception):\n                                    self.execute_script(onclick)\n                            current_window = self.driver.current_window_handle\n                            self.open_new_window()\n                            with suppress(Exception):\n                                self.open(href)\n                            self.switch_to_window(current_window)\n                            return\n                # Normal click\n                self.__element_click(element)\n        except Stale_Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(0.16)\n            element = page_actions.wait_for_element_clickable(\n                self.driver,\n                selector,\n                by,\n                timeout=timeout,\n                original_selector=original_selector,\n            )\n            with suppress(Exception):\n                self.__scroll_to_element(element, selector, by)\n            if self.browser == \"safari\" and by == By.LINK_TEXT:\n                self.__jquery_click(selector, by=by)\n            elif self.browser == \"safari\":\n                self.execute_script(\"arguments[0].click();\", element)\n            else:\n                self.__element_click(element)\n        except ENI_Exception as e:\n            with suppress(Exception):\n                if (\n                    \"element has zero size\" in e.msg\n                    and element.tag_name.lower() == \"a\"\n                ):\n                    if \"contains(\" not in selector:\n                        self.js_click(selector, by=by)\n                    else:\n                        self.jquery_click(selector, by=by)\n                    if self.__needs_minimum_wait():\n                        time.sleep(0.04)\n                    return\n            self.wait_for_ready_state_complete()\n            time.sleep(0.1)\n            element = page_actions.wait_for_element_visible(\n                self.driver,\n                selector,\n                by,\n                timeout=timeout,\n                original_selector=original_selector,\n            )\n            if not page_actions.is_element_clickable(\n                self.driver, selector, by\n            ):\n                with suppress(Exception):\n                    self.wait_for_element_clickable(\n                        selector, by, timeout=1.8\n                    )\n                element = page_actions.wait_for_element_visible(\n                    self.driver,\n                    selector,\n                    by,\n                    timeout=timeout,\n                    original_selector=original_selector,\n                )\n            href = None\n            new_tab = False\n            onclick = None\n            with suppress(Exception):\n                if element.tag_name.lower() == \"a\":\n                    # Handle a special case of opening a new tab (non-headless)\n                    href = element.get_attribute(\"href\").strip()\n                    onclick = element.get_attribute(\"onclick\")\n                    target = element.get_attribute(\"target\")\n                    if target == \"_blank\":\n                        new_tab = True\n                    if new_tab and self.__looks_like_a_page_url(href):\n                        if onclick:\n                            with suppress(Exception):\n                                self.execute_script(onclick)\n                        current_window = self.driver.current_window_handle\n                        self.open_new_window()\n                        with suppress(Exception):\n                            self.open(href)\n                        self.switch_to_window(current_window)\n                        return\n            if scroll and not self.demo_mode and not self.slow_mode:\n                self.__scroll_to_element(element, selector, by)\n            if self.browser == \"firefox\" or self.browser == \"safari\":\n                if by == By.LINK_TEXT or \"contains(\" in selector:\n                    self.__jquery_click(selector, by=by)\n                else:\n                    self.__js_click(selector, by=by)\n            else:\n                try:\n                    self.__element_click(element)\n                except Exception:\n                    self.wait_for_ready_state_complete()\n                    element = page_actions.wait_for_element_visible(\n                        self.driver,\n                        selector,\n                        by,\n                        timeout=timeout,\n                        original_selector=original_selector,\n                    )\n                    self.__element_click(element)\n        except MoveTargetOutOfBoundsException:\n            self.wait_for_ready_state_complete()\n            try:\n                self.__js_click(selector, by=by)\n            except Exception:\n                try:\n                    self.__jquery_click(selector, by=by)\n                except Exception:\n                    # One more attempt to click on the element\n                    element = page_actions.wait_for_element_clickable(\n                        self.driver,\n                        selector,\n                        by,\n                        timeout=timeout,\n                        original_selector=original_selector,\n                    )\n                    self.__element_click(element)\n        except WebDriverException as e:\n            if (\n                \"cannot determine loading status\" in e.msg\n                or \"unexpected command response\" in e.msg\n            ):\n                pass  # Odd issue where the click did happen. Continue.\n            else:\n                self.wait_for_ready_state_complete()\n                try:\n                    self.__js_click(selector, by=by)\n                except Exception:\n                    try:\n                        self.__jquery_click(selector, by=by)\n                    except Exception:\n                        # One more attempt to click on the element\n                        element = page_actions.wait_for_element_visible(\n                            self.driver,\n                            selector,\n                            by,\n                            timeout=timeout,\n                            original_selector=original_selector,\n                        )\n                        self.__element_click(element)\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        elif (\n            latest_window_count == pre_window_count - 1\n            and latest_window_count > 0\n        ):\n            # If a click closes the active window,\n            # switch to the last one if it exists.\n            self.switch_to_window(-1)\n        if settings.WAIT_FOR_RSC_ON_CLICKS:\n            if not self.undetectable:\n                with suppress(Exception):\n                    self.wait_for_ready_state_complete()\n                if self.__needs_minimum_wait() or self.browser == \"safari\":\n                    time.sleep(0.05)\n            else:\n                time.sleep(0.08)\n        else:\n            if not self.undetectable:\n                # A smaller subset of self.wait_for_ready_state_complete()\n                with suppress(Exception):\n                    self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)\n                if self.__needs_minimum_wait() or self.browser == \"safari\":\n                    time.sleep(0.045)\n                try:\n                    if self.driver.current_url != pre_action_url:\n                        self.__ad_block_as_needed()\n                        self.__disable_beforeunload_as_needed()\n                        if self.__needs_minimum_wait():\n                            time.sleep(0.075)\n                except Exception:\n                    with suppress(Exception):\n                        self.wait_for_ready_state_complete()\n                    if self.__needs_minimum_wait():\n                        time.sleep(0.05)\n            else:\n                time.sleep(0.085)\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        self.__set_esc_skip()\n\n    def slow_click(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Similar to click(), but pauses for a brief moment before clicking.\n        When used in combination with setting the user-agent, you can often\n        bypass bot-detection by tricking websites into thinking that you're\n        not a bot. (Useful on websites that block web automation tools.)\n        To set the user-agent, use: ``--agent=AGENT``.\n        Here's an example message from GitHub's bot-blocker:\n        ``You have triggered an abuse detection mechanism...`` \"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if not self.demo_mode and not self.slow_mode:\n            self.click(selector, by=by, timeout=timeout, delay=1.05)\n        elif self.slow_mode:\n            self.click(selector, by=by, timeout=timeout, delay=0.65)\n        else:\n            # Demo Mode already includes a small delay\n            self.click(selector, by=by, timeout=timeout, delay=0.25)\n\n    def double_click(self, selector, by=\"css selector\", timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        original_by = by\n        selector, by = self.__recalculate_selector(selector, by)\n        element = page_actions.wait_for_element_visible(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n        self.__demo_mode_highlight_if_active(original_selector, original_by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.02)\n        # Find the element one more time in case scrolling hid it\n        element = page_actions.wait_for_element_visible(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        try:\n            if self.browser == \"safari\":\n                # Jump to the \"except\" block where the other script should work\n                raise Exception(\"This Exception will be caught.\")\n            actions = ActionChains(self.driver)\n            actions.double_click(element).perform()\n        except Exception:\n            css_selector = self.convert_to_css_selector(selector, by=by)\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            double_click_script = (\n                \"\"\"var targetElement1 = document.querySelector('%s');\n                var clickEvent1 = document.createEvent('MouseEvents');\n                clickEvent1.initEvent('dblclick', true, true);\n                targetElement1.dispatchEvent(clickEvent1);\"\"\"\n                % css_selector\n            )\n            if \":contains\\\\(\" not in css_selector:\n                self.execute_script(double_click_script)\n            else:\n                double_click_script = (\n                    \"\"\"var targetElement1 = arguments[0];\n                    var clickEvent1 = document.createEvent('MouseEvents');\n                    clickEvent1.initEvent('dblclick', true, true);\n                    targetElement1.dispatchEvent(clickEvent1);\"\"\"\n                )\n                self.execute_script(double_click_script, element)\n        if settings.WAIT_FOR_RSC_ON_CLICKS:\n            self.wait_for_ready_state_complete()\n        else:\n            # A smaller subset of self.wait_for_ready_state_complete()\n            self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)\n            if self.driver.current_url != pre_action_url:\n                self.__ad_block_as_needed()\n                self.__disable_beforeunload_as_needed()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        elif self.__needs_minimum_wait():\n            time.sleep(0.02)\n\n    def context_click(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"(A context click is a right-click that opens a context menu.)\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        original_by = by\n        selector, by = self.__recalculate_selector(selector, by)\n        element = page_actions.wait_for_element_visible(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n        self.__demo_mode_highlight_if_active(original_selector, original_by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.02)\n        # Find the element one more time in case scrolling hid it\n        element = page_actions.wait_for_element_visible(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        try:\n            if self.browser == \"safari\":\n                # Jump to the \"except\" block where the other script should work\n                raise Exception(\"This Exception will be caught.\")\n            actions = ActionChains(self.driver)\n            actions.context_click(element).perform()\n        except Exception:\n            css_selector = self.convert_to_css_selector(selector, by=by)\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            right_click_script = (\n                \"\"\"var targetElement1 = document.querySelector('%s');\n                var clickEvent1 = document.createEvent('MouseEvents');\n                clickEvent1.initEvent('contextmenu', true, true);\n                targetElement1.dispatchEvent(clickEvent1);\"\"\"\n                % css_selector\n            )\n            if \":contains\\\\(\" not in css_selector:\n                self.execute_script(right_click_script)\n            else:\n                right_click_script = (\n                    \"\"\"var targetElement1 = arguments[0];\n                    var clickEvent1 = document.createEvent('MouseEvents');\n                    clickEvent1.initEvent('contextmenu', true, true);\n                    targetElement1.dispatchEvent(clickEvent1);\"\"\"\n                )\n                self.execute_script(right_click_script, element)\n        if settings.WAIT_FOR_RSC_ON_CLICKS:\n            self.wait_for_ready_state_complete()\n        else:\n            # A smaller subset of self.wait_for_ready_state_complete()\n            self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)\n            if self.driver.current_url != pre_action_url:\n                self.__ad_block_as_needed()\n                self.__disable_beforeunload_as_needed()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        elif self.__needs_minimum_wait():\n            time.sleep(0.02)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if by == By.XPATH:\n                    selector = original_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"r_clk\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n\n    def click_chain(\n        self, selectors_list, by=\"css selector\", timeout=None, spacing=0\n    ):\n        \"\"\"This method clicks on a list of elements in succession.\n        @Params\n        selectors_list - The list of selectors to click on.\n        by - The type of selector to search by (Default: \"css selector\").\n        timeout - How long to wait for the selector to be visible.\n        spacing - The amount of time to wait between clicks (in seconds).\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        for selector in selectors_list:\n            self.click(selector, by=by, timeout=timeout)\n            if spacing > 0:\n                time.sleep(spacing)\n\n    def update_text(\n        self, selector, text, by=\"css selector\", timeout=None, retry=False\n    ):\n        \"\"\"This method updates an element's text field with new text.\n        Has multiple parts:\n        * Waits for the element to be visible.\n        * Waits for the element to be interactive.\n        * Clears the text field.\n        * Types in the new text.\n        * Hits Enter/Submit (if the text ends in \"\\n\").\n        @Params\n        selector - The selector of the text field.\n        text - The new text to type into the text field.\n        by - The type of selector to search by. (Default: \"css selector\")\n        timeout - How long to wait for the selector to be visible.\n        retry - If True, use JS if the Selenium text update fails.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.type(selector, text, timeout=timeout)\n            return\n        if self.__is_shadow_selector(selector):\n            self.__shadow_type(selector, text, timeout)\n            return\n        element = self.wait_for_element_clickable(\n            selector, by=by, timeout=timeout\n        )\n        self.__demo_mode_highlight_if_active(selector, by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n            if self.__needs_minimum_wait():\n                time.sleep(0.04)\n        try:\n            element.clear()  # May need https://stackoverflow.com/a/50691625\n            backspaces = Keys.BACK_SPACE * 42  # Is the answer to everything\n            element.send_keys(backspaces)  # In case autocomplete keeps text\n        except (Stale_Exception, ENI_Exception):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.16)\n            element = self.wait_for_element_clickable(\n                selector, by=by, timeout=timeout\n            )\n            with suppress(Exception):\n                element.clear()\n        except Exception:\n            pass  # Clearing the text field first might not be necessary\n        self.__demo_mode_pause_if_active(tiny=True)\n        pre_action_url = None\n        if self.demo_mode:\n            with suppress(Exception):\n                pre_action_url = self.driver.current_url\n        text = self.__get_type_checked_text(text)\n        try:\n            if not text.endswith(\"\\n\"):\n                element.send_keys(text)\n                if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                    self.wait_for_ready_state_complete()\n            else:\n                element.send_keys(text[:-1])\n                if self.slow_mode or self.demo_mode:\n                    self.__demo_mode_pause_if_active(tiny=True)\n                else:\n                    time.sleep(0.0135)\n                try:\n                    element.send_keys(Keys.RETURN)\n                except WebDriverException as e:\n                    if (\n                        \"cannot determine loading status\" in e.msg\n                        or \"unexpected command response\" in e.msg\n                    ):\n                        pass  # Odd issue where the click did happen. Continue.\n                    else:\n                        raise\n                if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                    self.wait_for_ready_state_complete()\n                    if self.__needs_minimum_wait():\n                        time.sleep(0.04)\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(0.14)\n            element = self.wait_for_element_visible(\n                selector, by=by, timeout=timeout\n            )\n            time.sleep(0.04)\n            if not text.endswith(\"\\n\"):\n                element.send_keys(text)\n            else:\n                element.send_keys(text[:-1])\n                if self.slow_mode or self.demo_mode:\n                    self.__demo_mode_pause_if_active(tiny=True)\n                else:\n                    time.sleep(0.0135)\n                try:\n                    element.send_keys(Keys.RETURN)\n                except WebDriverException as e:\n                    if (\n                        \"cannot determine loading status\" in e.msg\n                        or \"unexpected command response\" in e.msg\n                    ):\n                        pass  # Odd issue where the click did happen. Continue.\n                    else:\n                        raise\n                if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                    self.wait_for_ready_state_complete()\n                    if self.__needs_minimum_wait():\n                        time.sleep(0.03)\n                        if self.undetectable:\n                            time.sleep(0.025)\n        if (\n            retry\n            and element.get_attribute(\"value\") != text\n            and not text.endswith(\"\\n\")\n        ):\n            logging.debug(\"update_text() is falling back to JavaScript!\")\n            self.set_value(selector, text, by=by)\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def add_text(self, selector, text, by=\"css selector\", timeout=None):\n        \"\"\"The more-reliable version of driver.send_keys()\n        Similar to update_text(), but won't clear the text field first.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.send_keys(selector, text)\n            return\n        if self.__is_shadow_selector(selector):\n            self.__shadow_type(selector, text, timeout, clear_first=False)\n            return\n        if selector == \"html\" and text in [\"\\n\", Keys.ENTER, Keys.RETURN]:\n            # This is a shortcut for calling self.click_active_element().\n            # Use after \"\\t\" or Keys.TAB to cycle through elements first.\n            self.click_active_element()\n            return\n        element = self.wait_for_element_present(\n            selector, by=by, timeout=timeout\n        )\n        if (\n            selector == \"html\" and text.count(\"\\t\") >= 1\n            and text.count(\"\\n\") == 1 and text.endswith(\"\\n\")\n            and text.replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\" \", \"\") == \"\"\n        ):\n            # Shortcut to send multiple tabs followed by click_active_element()\n            self.wait_for_ready_state_complete()\n            element.send_keys(Keys.TAB * text.count(\"\\t\"))\n            self.click_active_element()\n            return\n        self.__demo_mode_highlight_if_active(selector, by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        pre_action_url = None\n        if self.demo_mode:\n            with suppress(Exception):\n                pre_action_url = self.driver.current_url\n        text = self.__get_type_checked_text(text)\n        try:\n            if not text.endswith(\"\\n\"):\n                element.send_keys(text)\n            else:\n                element.send_keys(text[:-1])\n                if self.slow_mode or self.demo_mode:\n                    self.__demo_mode_pause_if_active(tiny=True)\n                else:\n                    time.sleep(0.0135)\n                element.send_keys(Keys.RETURN)\n                if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                    self.wait_for_ready_state_complete()\n        except (Stale_Exception, ENI_Exception):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.16)\n            element = self.wait_for_element_visible(\n                selector, by=by, timeout=timeout\n            )\n            if not text.endswith(\"\\n\"):\n                element.send_keys(text)\n            else:\n                element.send_keys(text[:-1])\n                if self.slow_mode or self.demo_mode:\n                    self.__demo_mode_pause_if_active(tiny=True)\n                else:\n                    time.sleep(0.0135)\n                element.send_keys(Keys.RETURN)\n                if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                    self.wait_for_ready_state_complete()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def type(\n        self, selector, text, by=\"css selector\", timeout=None, retry=False\n    ):\n        \"\"\"Same as self.update_text()\n        This method updates an element's text field with new text.\n        Has multiple parts:\n        * Waits for the element to be visible.\n        * Waits for the element to be interactive.\n        * Clears the text field.\n        * Types in the new text.\n        * Hits Enter/Submit (if the text ends in \"\\n\").\n        @Params\n        selector - The selector of the text field.\n        text - The new text to type into the text field.\n        by - The type of selector to search by. (Default: \"css selector\")\n        timeout - How long to wait for the selector to be visible.\n        retry - If True, use JS if the Selenium text update fails.\n        DO NOT confuse self.type() with Python type()! They are different!\n        \"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        self.update_text(selector, text, by=by, timeout=timeout, retry=retry)\n\n    def send_keys(self, selector, text, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.add_text()\n        Similar to update_text(), but won't clear the text field first.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        self.add_text(selector, text, by=by, timeout=timeout)\n\n    def press_keys(self, selector, text, by=\"css selector\", timeout=None):\n        \"\"\"Use send_keys() to press one key at a time.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.press_keys(selector, text, timeout=timeout)\n            return\n        self.wait_for_ready_state_complete()\n        element = self.wait_for_element_present(\n            selector, by=by, timeout=timeout\n        )\n        if self.demo_mode:\n            selector, by = self.__recalculate_selector(selector, by)\n            css_selector = self.convert_to_css_selector(selector, by=by)\n            self.__demo_mode_highlight_if_active(css_selector, By.CSS_SELECTOR)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                css_selector = self.convert_to_css_selector(selector, by=by)\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                sel_tex = [css_selector, text]\n                action = [\"pkeys\", sel_tex, origin, time_stamp]\n                self.__extra_actions.append(action)\n        press_enter = False\n        if text.endswith(\"\\n\"):\n            text = text[:-1]\n            press_enter = True\n        for key in text:\n            element.send_keys(key)\n        if press_enter:\n            if self.slow_mode or self.demo_mode:\n                self.__demo_mode_pause_if_active(tiny=True)\n            else:\n                time.sleep(0.0135)\n            element.send_keys(Keys.RETURN)\n            if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                if not self.undetectable:\n                    self.wait_for_ready_state_complete()\n                else:\n                    time.sleep(0.15)\n        if self.demo_mode:\n            if press_enter:\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        elif self.__needs_minimum_wait():\n            time.sleep(0.05)\n            if self.undetectable:\n                time.sleep(0.02)\n\n    def submit(self, selector, by=\"css selector\"):\n        \"\"\"Alternative to self.driver.find_element_by_*(SELECTOR).submit()\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.submit(selector)\n            return\n        element = self.wait_for_element_clickable(\n            selector, by=by, timeout=settings.SMALL_TIMEOUT\n        )\n        element.submit()\n        self.__demo_mode_pause_if_active()\n\n    def clear(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"This method clears an element's text field.\n        A clear() is already included with most methods that type text,\n        such as self.type(), self.update_text(), etc.\n        Does not use Demo Mode highlights, mainly because we expect\n        that some users will be calling an unnecessary clear() before\n        calling a method that already includes clear() as part of it.\n        In case websites trigger an autofill after clearing a field,\n        add backspaces to make sure autofill doesn't undo the clear.\n        @Params\n        selector - The selector of the text field.\n        by - The type of selector to search by. (Default: \"css selector\")\n        timeout - How long to wait for the selector to be visible.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.clear_input(selector)\n            return\n        if self.__is_shadow_selector(selector):\n            self.__shadow_clear(selector, timeout)\n            return\n        element = self.wait_for_element_visible(\n            selector, by=by, timeout=timeout\n        )\n        self.scroll_to(selector, by=by, timeout=timeout)\n        try:\n            element.clear()\n            backspaces = Keys.BACK_SPACE * 42  # Autofill Defense\n            element.send_keys(backspaces)\n        except (Stale_Exception, ENI_Exception):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.16)\n            element = self.wait_for_element_visible(\n                selector, by=by, timeout=timeout\n            )\n            element.clear()\n            with suppress(Exception):\n                backspaces = Keys.BACK_SPACE * 42  # Autofill Defense\n                element.send_keys(backspaces)\n        except Exception:\n            element.clear()\n\n    def focus(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Make the current page focus on an interactable element.\n        If the element is not interactable, only scrolls to it.\n        The \"tab\" key is another way of setting the page focus.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.focus(selector)\n            return\n        element = self.wait_for_element_present(\n            selector, by=by, timeout=timeout\n        )\n        if not element.is_displayed():\n            css_selector = self.convert_to_css_selector(selector, by=by)\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = \"\"\"document.querySelector('%s').focus();\"\"\" % css_selector\n            self.execute_script(script)\n            self.__demo_mode_pause_if_active()\n            return\n        self.scroll_to(selector, by=by, timeout=timeout)\n        try:\n            element.send_keys(Keys.NULL)\n        except (Stale_Exception, ENI_Exception):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.12)\n            element = self.wait_for_element_visible(\n                selector, by=by, timeout=timeout\n            )\n            try:\n                element.send_keys(Keys.NULL)\n            except ENI_Exception:\n                # Non-interactable element. Skip focus and continue.\n                pass\n        self.__demo_mode_pause_if_active()\n\n    def refresh_page(self):\n        self.__check_scope()\n        self.__last_page_load_url = None\n        if self.__is_cdp_swap_needed():\n            self.cdp.reload()\n            return\n        js_utils.clear_out_console_logs(self.driver)\n        self.driver.refresh()\n        self.wait_for_ready_state_complete()\n\n    def refresh(self):\n        \"\"\"The shorter version of self.refresh_page()\"\"\"\n        self.refresh_page()\n\n    def get_current_url(self):\n        self.__check_scope()\n        current_url = None\n        if self.__is_cdp_swap_needed():\n            current_url = self.cdp.get_current_url()\n        else:\n            current_url = self.driver.current_url\n        if \"%\" in current_url:\n            try:\n                from urllib.parse import unquote\n\n                current_url = unquote(current_url, errors=\"strict\")\n            except Exception:\n                pass\n        return current_url\n\n    def get_origin(self):\n        self.__check_scope()\n        return self.execute_script(\"return window.location.origin;\")\n\n    def get_html(self, *args, **kwargs):\n        return self.get_page_source(*args, **kwargs)\n\n    def get_page_source(self, *args, **kwargs):\n        if self.__is_cdp_swap_needed(*args, **kwargs):\n            return self.cdp.get_page_source()\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait:\n            time.sleep(0.025)\n        return self.driver.page_source\n\n    def get_page_title(self):\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_title()\n        self.wait_for_ready_state_complete()\n        with suppress(Exception):\n            self.wait_for_element_present(\n                \"title\", by=\"css selector\", timeout=settings.MINI_TIMEOUT\n            )\n            time.sleep(0.025)\n        return self.driver.title\n\n    def get_title(self):\n        \"\"\"The shorter version of self.get_page_title()\"\"\"\n        return self.get_page_title()\n\n    def get_user_agent(self):\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_user_agent()\n        return self.execute_script(\"return navigator.userAgent;\")\n\n    def get_locale_code(self):\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_locale_code()\n        return self.execute_script(\n            \"return navigator.language || navigator.languages[0];\"\n        )\n\n    def go_back(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.go_back()\n            return\n        if getattr(self, \"recorder_mode\", None):\n            self.save_recorded_actions()\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        self.__last_page_load_url = None\n        self.driver.back()\n        with suppress(Exception):\n            if pre_action_url == self.driver.current_url:\n                self.driver.back()  # Again because the page was redirected\n        if self.recorder_mode:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"go_bk\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n        if self.browser == \"safari\":\n            self.wait_for_ready_state_complete()\n            time.sleep(0.02)\n            self.driver.refresh()\n            time.sleep(0.02)\n        self.wait_for_ready_state_complete()\n        self.__demo_mode_pause_if_active()\n\n    def go_forward(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.go_forward()\n            return\n        if getattr(self, \"recorder_mode\", None):\n            self.save_recorded_actions()\n        self.__last_page_load_url = None\n        self.driver.forward()\n        if self.recorder_mode:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"go_fw\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n        self.wait_for_ready_state_complete()\n        self.__demo_mode_pause_if_active()\n\n    def open_start_page(self):\n        \"\"\"Navigates the current browser window to the start_page.\n        You can set the start_page on the command-line in three ways:\n        '--start_page=URL', '--start-page=URL', or '--url=URL'.\n        If the start_page is not set, then \"about:blank\" will be used.\"\"\"\n        self.__check_scope()\n        start_page = self.start_page\n        if isinstance(start_page, str):\n            start_page = start_page.strip()  # Remove extra whitespace\n        if start_page and len(start_page) >= 4:\n            if page_utils.is_valid_url(start_page):\n                self.open(start_page)\n            else:\n                new_start_page = \"https://\" + start_page\n                if page_utils.is_valid_url(new_start_page):\n                    self.__dont_record_open = True\n                    self.open(new_start_page)\n                    self.__dont_record_open = False\n                else:\n                    logging.info('Invalid URL: \"%s\"!' % start_page)\n                    if self.get_current_url() != \"about:blank\":\n                        self.open(\"about:blank\")\n        else:\n            if self.get_current_url() != \"about:blank\":\n                self.open(\"about:blank\")\n\n    def open_if_not_url(self, url):\n        \"\"\"Opens the url in the browser if it's not the current url (*).\n        Parameters tagged on by search engines are ignored for this method.\n        Eg. If the current url is:\n            * https://www.bing.com/search?q=SeleniumBase&source=hp\n            And the url privided by this method call is:\n            * https://www.bing.com/search?q=SeleniumBase\n            Then the urls will be considered the same,\n            and no open() action will be performed.\n        This method is primarily used by Recorder Mode script generation,\n        where both clicks and opens are recorded. So if a click() action\n        leads to an open() action, then the script generator will attempt\n        to convert the open() action into open_if_not_url() so that the\n        same page isn't opened again if the user is already on the page.\"\"\"\n        self.__check_scope()\n        current_url = self.get_current_url()\n        if current_url != url:\n            if (\n                \"?q=\" not in current_url\n                or \"&\" not in current_url\n                or current_url.find(\"?q=\") >= current_url.find(\"&\")\n                or current_url.split(\"&\")[0] != url\n            ):\n                self.open(url)\n\n    def is_element_present(self, selector, by=\"css selector\"):\n        \"\"\"Returns whether the element exists in the HTML.\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.is_element_present(selector)\n        self.wait_for_ready_state_complete()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_element_present(selector)\n        return page_actions.is_element_present(self.driver, selector, by)\n\n    def is_element_visible(self, selector, by=\"css selector\"):\n        \"\"\"Returns whether the element is visible on the page.\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.is_element_visible(selector)\n        self.wait_for_ready_state_complete()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_element_visible(selector)\n        return page_actions.is_element_visible(self.driver, selector, by)\n\n    def is_element_clickable(self, selector, by=\"css selector\"):\n        self.wait_for_ready_state_complete()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_element_clickable(selector)\n        return page_actions.is_element_clickable(self.driver, selector, by)\n\n    def is_element_enabled(self, selector, by=\"css selector\"):\n        self.wait_for_ready_state_complete()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_element_enabled(selector)\n        return page_actions.is_element_enabled(self.driver, selector, by)\n\n    def is_text_visible(self, text, selector=\"body\", by=\"css selector\"):\n        \"\"\"Returns whether the text substring is visible in the element.\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.is_text_visible(text, selector)\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_text_visible(text, selector)\n        return page_actions.is_text_visible(self.driver, text, selector, by)\n\n    def is_exact_text_visible(self, text, selector=\"body\", by=\"css selector\"):\n        \"\"\"Returns whether the text is exactly equal to the element text.\n        (Leading and trailing whitespace is ignored in the verification.)\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.is_exact_text_visible(text, selector)\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_exact_text_visible(text, selector)\n        return page_actions.is_exact_text_visible(\n            self.driver, text, selector, by\n        )\n\n    def is_non_empty_text_visible(self, selector=\"body\", by=\"css selector\"):\n        \"\"\"Returns whether the element has any non-empty text visible.\n        Whitespace-only text is considered empty text.\"\"\"\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_non_empty_text_visible(selector)\n        return page_actions.is_non_empty_text_visible(\n            self.driver, selector, by=by\n        )\n\n    def is_attribute_present(\n        self, selector, attribute, value=None, by=\"css selector\"\n    ):\n        \"\"\"Returns True if the element attribute/value is found.\n        If the value is not specified, the attribute only needs to exist.\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.is_attribute_present(\n                selector, attribute, value=value\n            )\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__is_shadow_attribute_present(\n                selector, attribute, value\n            )\n        return page_actions.is_attribute_present(\n            self.driver, selector, attribute, value, by\n        )\n\n    def is_link_text_visible(self, link_text):\n        \"\"\"Returns whether there's an exact match for the link text.\"\"\"\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        return page_actions.is_element_visible(\n            self.driver, link_text, by=\"link text\"\n        )\n\n    def is_partial_link_text_visible(self, partial_link_text):\n        \"\"\"Returns whether there's a substring match for the link text.\"\"\"\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        return page_actions.is_element_visible(\n            self.driver, partial_link_text, by=\"partial link text\"\n        )\n\n    def is_link_text_present(self, link_text):\n        \"\"\"Returns True if the link text appears in the HTML of the page.\n        The element doesn't need to be visible,\n        such as elements hidden inside a dropdown selection.\"\"\"\n        self.wait_for_ready_state_complete()\n        soup = self.get_beautiful_soup()\n        html_links = soup.find_all(\"a\")\n        for html_link in html_links:\n            if html_link.text.strip() == link_text.strip():\n                return True\n        return False\n\n    def is_partial_link_text_present(self, link_text):\n        \"\"\"Returns True if the partial link appears in the HTML of the page.\n        The element doesn't need to be visible,\n        such as elements hidden inside a dropdown selection.\"\"\"\n        self.wait_for_ready_state_complete()\n        soup = self.get_beautiful_soup()\n        html_links = soup.find_all(\"a\")\n        for html_link in html_links:\n            if link_text.strip() in html_link.text.strip():\n                return True\n        return False\n\n    def get_link_attribute(self, link_text, attribute, hard_fail=True):\n        \"\"\"Finds a link by link text and then returns the attribute's value.\n        If the link text or attribute cannot be found, an exception will\n        get raised if hard_fail is True (otherwise None is returned).\"\"\"\n        self.wait_for_ready_state_complete()\n        soup = self.get_beautiful_soup()\n        html_links = soup.find_all(\"a\")\n        for html_link in html_links:\n            if html_link.text.strip() == link_text.strip():\n                if html_link.has_attr(attribute):\n                    attribute_value = html_link.get(attribute)\n                    return attribute_value\n                if hard_fail:\n                    raise Exception(\n                        \"Unable to find attribute {%s} from link text {%s}!\"\n                        % (attribute, link_text)\n                    )\n                else:\n                    return None\n        if hard_fail:\n            raise Exception(\"Link text {%s} was not found!\" % link_text)\n        else:\n            return None\n\n    def get_link_text_attribute(self, link_text, attribute, hard_fail=True):\n        \"\"\"Same as self.get_link_attribute()\n        Finds a link by link text and then returns the attribute's value.\n        If the link text or attribute cannot be found, an exception will\n        get raised if hard_fail is True (otherwise None is returned).\"\"\"\n        return self.get_link_attribute(link_text, attribute, hard_fail)\n\n    def get_partial_link_text_attribute(\n        self, link_text, attribute, hard_fail=True\n    ):\n        \"\"\"Finds a link by partial link text and then returns the attribute's\n        value. If the partial link text or attribute cannot be found, an\n        exception will get raised if hard_fail is True (otherwise None\n        is returned).\"\"\"\n        self.wait_for_ready_state_complete()\n        soup = self.get_beautiful_soup()\n        html_links = soup.find_all(\"a\")\n        for html_link in html_links:\n            if link_text.strip() in html_link.text.strip():\n                if html_link.has_attr(attribute):\n                    attribute_value = html_link.get(attribute)\n                    return attribute_value\n                if hard_fail:\n                    raise Exception(\n                        \"Unable to find attribute {%s} from \"\n                        \"partial link text {%s}!\" % (attribute, link_text)\n                    )\n                else:\n                    return None\n        if hard_fail:\n            msg = \"Partial Link text {%s} was not found!\" % link_text\n            page_actions.timeout_exception(\"LinkTextNotFoundException\", msg)\n        else:\n            return None\n\n    def click_link_text(self, link_text, timeout=None):\n        \"\"\"This method clicks link text on a page.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.find_element(link_text, timeout=timeout).click()\n            return\n        self.__skip_if_esc()\n        link_text = self.__get_type_checked_text(link_text)\n        if self.__is_cdp_swap_needed():\n            self.cdp.click_link(link_text)\n            return\n        if self.browser == \"safari\":\n            if self.demo_mode:\n                self.wait_for_link_text_present(link_text, timeout=timeout)\n                try:\n                    self.__jquery_slow_scroll_to(link_text, by=\"link text\")\n                except Exception:\n                    element = self.wait_for_link_text_visible(\n                        link_text, timeout=timeout\n                    )\n                    self.__slow_scroll_to_element(element)\n                o_bs = \"\"  # original_box_shadow\n                loops = settings.HIGHLIGHTS\n                selector = self.convert_to_css_selector(\n                    link_text, by=\"link text\"\n                )\n                selector = self.__make_css_match_first_element_only(selector)\n                try:\n                    selector = re.escape(selector)\n                    selector = self.__escape_quotes_if_needed(selector)\n                    self.__highlight_with_jquery(selector, loops, o_bs)\n                except Exception:\n                    pass  # JQuery probably couldn't load. Skip highlighting.\n            self.__jquery_click(link_text, by=\"link text\")\n            return\n        if not self.is_link_text_present(link_text):\n            self.wait_for_link_text_present(link_text, timeout=timeout)\n        if not self.demo_mode and not self.slow_mode:\n            if self.__needs_minimum_wait():\n                time.sleep(0.04)\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        try:\n            element = self.wait_for_link_text_visible(link_text, timeout=0.2)\n            self.__demo_mode_highlight_if_active(link_text, by=\"link text\")\n            try:\n                self.__element_click(element)\n            except (Stale_Exception, ENI_Exception, ECI_Exception):\n                self.wait_for_ready_state_complete()\n                time.sleep(0.16)\n                element = self.wait_for_link_text_visible(\n                    link_text, timeout=timeout\n                )\n                self.__element_click(element)\n        except Exception:\n            found_css = False\n            text_id = self.get_link_attribute(link_text, \"id\", False)\n            if text_id:\n                link_css = '[id=\"%s\"]' % link_text\n                found_css = True\n            if not found_css:\n                href = self.__get_href_from_link_text(link_text, False)\n                if href:\n                    if href.startswith(\"/\") or page_utils.is_valid_url(href):\n                        link_css = '[href=\"%s\"]' % href\n                        found_css = True\n            if not found_css:\n                ngclick = self.get_link_attribute(link_text, \"ng-click\", False)\n                if ngclick:\n                    link_css = '[ng-click=\"%s\"]' % ngclick\n                    found_css = True\n            if not found_css:\n                onclick = self.get_link_attribute(link_text, \"onclick\", False)\n                if onclick:\n                    link_css = '[onclick=\"%s\"]' % onclick\n                    found_css = True\n            success = False\n            if found_css:\n                if self.is_element_visible(link_css):\n                    self.click(link_css)\n                    success = True\n                else:\n                    # The link text might be hidden under a dropdown menu\n                    success = self.__click_dropdown_link_text(\n                        link_text, link_css\n                    )\n            if not success:\n                element = self.wait_for_link_text_visible(\n                    link_text, timeout=settings.MINI_TIMEOUT\n                )\n                self.__element_click(element)\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        elif (\n            latest_window_count == pre_window_count - 1\n            and latest_window_count > 0\n        ):\n            # If a click closes the active window,\n            # switch to the last one if it exists.\n            self.switch_to_window(-1)\n        if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n            with suppress(Exception):\n                self.wait_for_ready_state_complete()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        elif self.__needs_minimum_wait():\n            time.sleep(0.04)\n\n    def click_partial_link_text(self, partial_link_text, timeout=None):\n        \"\"\"This method clicks the partial link text on a page.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        partial_link_text = self.__get_type_checked_text(partial_link_text)\n        if self.__is_cdp_swap_needed():\n            self.cdp.find_element(partial_link_text, timeout=timeout).click()\n            return\n        if not self.is_partial_link_text_present(partial_link_text):\n            self.wait_for_partial_link_text_present(\n                partial_link_text, timeout=timeout\n            )\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        try:\n            element = self.wait_for_partial_link_text(\n                partial_link_text, timeout=0.2\n            )\n            self.__demo_mode_highlight_if_active(\n                partial_link_text, by=\"link text\"\n            )\n            try:\n                self.__element_click(element)\n            except (Stale_Exception, ENI_Exception, ECI_Exception):\n                self.wait_for_ready_state_complete()\n                time.sleep(0.16)\n                element = self.wait_for_partial_link_text(\n                    partial_link_text, timeout=timeout\n                )\n                self.__element_click(element)\n        except Exception:\n            found_css = False\n            text_id = self.get_partial_link_text_attribute(\n                partial_link_text, \"id\", False\n            )\n            if text_id:\n                link_css = '[id=\"%s\"]' % partial_link_text\n                found_css = True\n            if not found_css:\n                href = self.__get_href_from_partial_link_text(\n                    partial_link_text, False\n                )\n                if href:\n                    if href.startswith(\"/\") or page_utils.is_valid_url(href):\n                        link_css = '[href=\"%s\"]' % href\n                        found_css = True\n            if not found_css:\n                ngclick = self.get_partial_link_text_attribute(\n                    partial_link_text, \"ng-click\", False\n                )\n                if ngclick:\n                    link_css = '[ng-click=\"%s\"]' % ngclick\n                    found_css = True\n            if not found_css:\n                onclick = self.get_partial_link_text_attribute(\n                    partial_link_text, \"onclick\", False\n                )\n                if onclick:\n                    link_css = '[onclick=\"%s\"]' % onclick\n                    found_css = True\n            success = False\n            if found_css:\n                if self.is_element_visible(link_css):\n                    self.click(link_css)\n                    success = True\n                else:\n                    # The link text might be hidden under a dropdown menu\n                    success = self.__click_dropdown_partial_link_text(\n                        partial_link_text, link_css\n                    )\n            if not success:\n                element = self.wait_for_partial_link_text(\n                    partial_link_text, timeout=settings.MINI_TIMEOUT\n                )\n                self.__element_click(element)\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        elif (\n            latest_window_count == pre_window_count - 1\n            and latest_window_count > 0\n        ):\n            # If a click closes the active window,\n            # switch to the last one if it exists.\n            self.switch_to_window(-1)\n        if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n            with suppress(Exception):\n                self.wait_for_ready_state_complete()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def get_text(self, selector=\"body\", by=\"css selector\", timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_text(selector)\n        if self.__is_shadow_selector(selector):\n            return self.__get_shadow_text(selector, timeout)\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        element = page_actions.wait_for_element_visible(\n            self.driver, selector, by, timeout\n        )\n        try:\n            element_text = element.text\n            if self.browser == \"safari\":\n                if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                    element_text = element.get_attribute(\"value\")\n                else:\n                    element_text = element.get_attribute(\"innerText\")\n            elif element.tag_name.lower() in [\"input\", \"textarea\"]:\n                element_text = element.get_property(\"value\")\n        except (Stale_Exception, ENI_Exception, TimeoutException):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.14)\n            element = page_actions.wait_for_element_visible(\n                self.driver, selector, by, timeout\n            )\n            element_text = element.text\n            if self.browser == \"safari\":\n                if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                    element_text = element.get_attribute(\"value\")\n                else:\n                    element_text = element.get_attribute(\"innerText\")\n            elif element.tag_name.lower() in [\"input\", \"textarea\"]:\n                element_text = element.get_property(\"value\")\n        return element_text\n\n    def get_attribute(\n        self,\n        selector,\n        attribute,\n        by=\"css selector\",\n        timeout=None,\n        hard_fail=True,\n    ):\n        \"\"\"This method uses JavaScript to get the value of an attribute.\n        If the attribute doesn't exist or isn't found, an exception will\n        get raised if hard_fail is True (otherwise None is returned).\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            if hard_fail:\n                return self.cdp.get_element_attribute(selector, attribute)\n            else:\n                return self.cdp.get_attribute(selector, attribute)\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        if self.__is_shadow_selector(selector):\n            return self.__get_shadow_attribute(\n                selector, attribute, timeout=timeout\n            )\n        element = page_actions.wait_for_element_present(\n            self.driver, selector, by, timeout\n        )\n        try:\n            attribute_value = element.get_attribute(attribute)\n        except (Stale_Exception, ENI_Exception, TimeoutException):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.14)\n            element = page_actions.wait_for_element_present(\n                self.driver, selector, by, timeout\n            )\n            attribute_value = element.get_attribute(attribute)\n        if attribute_value is not None:\n            return attribute_value\n        else:\n            if hard_fail:\n                raise Exception(\n                    \"Element {%s} has no attribute {%s}!\"\n                    % (selector, attribute)\n                )\n            else:\n                return None\n\n    def set_attribute(\n        self,\n        selector,\n        attribute,\n        value,\n        by=\"css selector\",\n        timeout=None,\n        scroll=False,\n    ):\n        \"\"\"This method uses JavaScript to set/update an attribute.\n        Only the first matching selector from querySelector() is used.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        original_attribute = attribute\n        original_value = value\n        if scroll and self.is_element_visible(selector, by=by):\n            with suppress(Exception):\n                self.scroll_to(selector, by=by, timeout=timeout)\n        attribute = re.escape(attribute)\n        attribute = self.__escape_quotes_if_needed(attribute)\n        value = re.escape(value)\n        value = self.__escape_quotes_if_needed(value)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        script = (\n            \"\"\"document.querySelector('%s').setAttribute('%s','%s');\"\"\"\n            % (css_selector, attribute, value)\n        )\n        self.execute_script(script)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                sele_attr_val = [selector, original_attribute, original_value]\n                action = [\"s_at_\", sele_attr_val, origin, time_stamp]\n                self.__extra_actions.append(action)\n\n    def set_attributes(self, selector, attribute, value, by=\"css selector\"):\n        \"\"\"This method uses JavaScript to set/update a common attribute.\n        All matching selectors from querySelectorAll() are used.\n        Example => (Make all links on a website redirect to Google):\n        self.set_attributes(\"a\", \"href\", \"https://google.com\")\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.set_attributes(selector, attribute, value)\n            return\n        original_attribute = attribute\n        original_value = value\n        attribute = re.escape(attribute)\n        attribute = self.__escape_quotes_if_needed(attribute)\n        value = re.escape(value)\n        value = self.__escape_quotes_if_needed(value)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        script = \"\"\"var $elements = document.querySelectorAll('%s');\n                  var index = 0, length = $elements.length;\n                  for(; index < length; index++){\n                  $elements[index].setAttribute('%s','%s');}\"\"\" % (\n            css_selector,\n            attribute,\n            value,\n        )\n        with suppress(Exception):\n            self.execute_script(script)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                sele_attr_val = [selector, original_attribute, original_value]\n                action = [\"s_ats\", sele_attr_val, origin, time_stamp]\n                self.__extra_actions.append(action)\n\n    def set_attribute_all(self, selector, attribute, value, by=\"css selector\"):\n        \"\"\"Same as set_attributes(), but using querySelectorAll naming scheme.\n        This method uses JavaScript to set/update a common attribute.\n        All matching selectors from querySelectorAll() are used.\n        Example => (Make all links on a website redirect to Google):\n        self.set_attribute_all(\"a\", \"href\", \"https://google.com\")\"\"\"\n        self.set_attributes(selector, attribute, value, by=by)\n\n    def remove_attribute(\n        self, selector, attribute, by=\"css selector\", timeout=None\n    ):\n        \"\"\"This method uses JavaScript to remove an attribute.\n        Only the first matching selector from querySelector() is used.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.is_element_visible(selector, by=by):\n            with suppress(Exception):\n                self.scroll_to(selector, by=by, timeout=timeout)\n        attribute = re.escape(attribute)\n        attribute = self.__escape_quotes_if_needed(attribute)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        script = \"\"\"document.querySelector('%s').removeAttribute('%s');\"\"\" % (\n            css_selector,\n            attribute,\n        )\n        self.execute_script(script)\n\n    def remove_attributes(self, selector, attribute, by=\"css selector\"):\n        \"\"\"This method uses JavaScript to remove a common attribute.\n        All matching selectors from querySelectorAll() are used.\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by)\n        attribute = re.escape(attribute)\n        attribute = self.__escape_quotes_if_needed(attribute)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        script = \"\"\"var $elements = document.querySelectorAll('%s');\n                  var index = 0, length = $elements.length;\n                  for(; index < length; index++){\n                  $elements[index].removeAttribute('%s');}\"\"\" % (\n            css_selector,\n            attribute,\n        )\n        with suppress(Exception):\n            self.execute_script(script)\n\n    def internalize_links(self):\n        \"\"\"All `target=\"_blank\"` links become `target=\"_self\"`.\n        This prevents those links from opening in a new tab.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.internalize_links()\n            return\n        self.set_attributes('[target=\"_blank\"]', \"target\", \"_self\")\n\n    def get_parent(self, element, by=\"css selector\", timeout=None):\n        \"\"\"Returns the parent element.\n        If element is a string, then finds element first via selector.\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_parent(element)\n        if isinstance(element, str):\n            if not timeout:\n                timeout = settings.LARGE_TIMEOUT\n            element = self.wait_for_element_present(\n                element, by=by, timeout=timeout\n            )\n        return element.find_element(by=\"xpath\", value=\"..\")\n\n    def get_property(\n        self, selector, property, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Returns the property value of an element.\n        This is not the same as self.get_property_value(), which returns\n        the value of an element's computed style using a different algorithm.\n        If no result is found, an empty string (instead of None) is returned.\n        Example:\n            html_text = self.get_property(SELECTOR, \"textContent\") \"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        self.wait_for_ready_state_complete()\n        time.sleep(0.01)\n        element = page_actions.wait_for_element_present(\n            self.driver, selector, by, timeout\n        )\n        try:\n            property_value = element.get_property(property)\n        except (Stale_Exception, ENI_Exception, TimeoutException):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.14)\n            element = page_actions.wait_for_element_present(\n                self.driver, selector, by, timeout\n            )\n            property_value = element.get_property(property)\n        if not property_value:\n            return \"\"\n        return property_value\n\n    def get_text_content(\n        self, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Returns the text that appears in the HTML for an element.\n        This is different from \"self.get_text(selector, by=\"css selector\")\"\n        because that only returns the visible text on a page for an element,\n        rather than the HTML text that's being returned from this method.\"\"\"\n        self.__check_scope()\n        return self.get_property(\n            selector, property=\"textContent\", by=by, timeout=timeout\n        )\n\n    def get_property_value(\n        self, selector, property, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Returns the property value of a page element's computed style.\n        Example:\n            opacity = self.get_property_value(\"html body a\", \"opacity\")\n            self.assertTrue(float(opacity) > 0, \"Element not visible!\") \"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        self.wait_for_ready_state_complete()\n        element = page_actions.wait_for_element_present(\n            self.driver, selector, by, timeout\n        )\n        try:\n            selector = self.convert_to_css_selector(selector, by=by)\n        except Exception:\n            # If can't convert to CSS_Selector for JS, use element directly\n            script = (\n                \"\"\"var $elm = arguments[0];\n                $val = window.getComputedStyle($elm).getPropertyValue('%s');\n                return $val;\"\"\" % property\n            )\n            value = self.execute_script(script, element)\n            if value is not None:\n                return value\n            else:\n                return \"\"\n        selector = re.escape(selector)\n        selector = self.__escape_quotes_if_needed(selector)\n        script = \"\"\"var $elm = document.querySelector('%s');\n                  $val = window.getComputedStyle($elm).getPropertyValue('%s');\n                  return $val;\"\"\" % (selector, property)\n        value = self.execute_script(script)\n        if value is not None:\n            return value\n        else:\n            return \"\"  # Return an empty string if the property doesn't exist\n\n    def get_image_url(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Extracts the URL from an image element on the page.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.get_attribute(\n            selector, attribute=\"src\", by=by, timeout=timeout\n        )\n\n    def find_elements(self, selector, by=\"css selector\", limit=0):\n        \"\"\"Returns a list of matching WebElements.\n        Elements could be either hidden or visible on the page.\n        If \"limit\" is set and > 0, will only return that many elements.\"\"\"\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            elements = self.cdp.select_all(selector)\n            if limit and limit > 0 and len(elements) > limit:\n                elements = elements[:limit]\n            return elements\n        self.wait_for_ready_state_complete()\n        time.sleep(0.05)\n        elements = self.driver.find_elements(by=by, value=selector)\n        if limit and limit > 0 and len(elements) > limit:\n            elements = elements[:limit]\n        return elements\n\n    def find_visible_elements(self, selector, by=\"css selector\", limit=0):\n        \"\"\"Returns a list of matching WebElements that are visible.\n        If \"limit\" is set and > 0, will only return that many elements.\"\"\"\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            elements = self.cdp.find_visible_elements(selector)\n            if limit and limit > 0 and len(elements) > limit:\n                elements = elements[:limit]\n            return elements\n        self.wait_for_ready_state_complete()\n        time.sleep(0.05)\n        return page_actions.find_visible_elements(\n            self.driver, selector, by, limit\n        )\n\n    def click_visible_elements(\n        self, selector, by=\"css selector\", limit=0, timeout=None\n    ):\n        \"\"\"Finds all matching page elements and clicks visible ones in order.\n        If a click reloads or opens a new page, the clicking will stop.\n        If no matching elements appear, an Exception will be raised.\n        If \"limit\" is set and > 0, will only click that many elements.\n        Also clicks elements that become visible from previous clicks.\n        Works best for actions such as clicking all checkboxes on a page.\n        Example: self.click_visible_elements('input[type=\"checkbox\"]')\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.click_visible_elements(selector, limit)\n            return\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.12)\n            if self.undetectable:\n                time.sleep(0.06)\n        element = self.wait_for_element_present(\n            selector, by=by, timeout=timeout\n        )\n        with suppress(Exception):\n            # If the first element isn't visible, wait a little.\n            if not element.is_displayed():\n                time.sleep(0.16)\n                if self.undetectable:\n                    time.sleep(0.06)\n        elements = self.find_elements(selector, by=by)\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        click_count = 0\n        for element in elements:\n            if limit and limit > 0 and click_count >= limit:\n                return\n            try:\n                if element.is_displayed():\n                    self.__scroll_to_element(element)\n                    if self.browser == \"safari\":\n                        self.execute_script(\"arguments[0].click();\", element)\n                    else:\n                        element.click()\n                    click_count += 1\n                    self.wait_for_ready_state_complete()\n            except ECI_Exception:\n                continue  # (Overlay likely)\n            except (Stale_Exception, ENI_Exception):\n                self.wait_for_ready_state_complete()\n                time.sleep(0.12)\n                try:\n                    if element.is_displayed():\n                        self.__scroll_to_element(element)\n                        if self.browser == \"safari\":\n                            self.execute_script(\n                                \"arguments[0].click();\", element\n                            )\n                        else:\n                            element.click()\n                        click_count += 1\n                        self.wait_for_ready_state_complete()\n                except (Stale_Exception, ENI_Exception):\n                    latest_window_count = len(self.driver.window_handles)\n                    if (\n                        latest_window_count > pre_window_count\n                        and (\n                            self.recorder_mode\n                            or (\n                                settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                                and self.driver.current_url == pre_action_url\n                            )\n                        )\n                    ):\n                        self.__switch_to_newest_window_if_not_blank()\n                    return  # Probably on new page / Elements are all stale\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n\n    def click_nth_visible_element(\n        self, selector, number, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Finds all matching page elements and clicks the nth visible one.\n        Example: self.click_nth_visible_element('[type=\"checkbox\"]', 5)\n                (Clicks the 5th visible checkbox on the page.)\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.click_nth_visible_element(selector, number)\n            return\n        self.wait_for_ready_state_complete()\n        self.wait_for_element_present(selector, by=by, timeout=timeout)\n        elements = self.find_visible_elements(selector, by=by)\n        if len(elements) < number:\n            raise Exception(\n                \"Not enough matching {%s} elements of type {%s} to \"\n                \"click number %s!\" % (selector, by, number)\n            )\n        number = number - 1\n        if number < 0:\n            number = 0\n        element = elements[number]\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        try:\n            self.__scroll_to_element(element)\n            self.__element_click(element)\n        except (Stale_Exception, ENI_Exception, ECI_Exception):\n            time.sleep(0.12)\n            self.wait_for_ready_state_complete()\n            self.wait_for_element_present(selector, by=by, timeout=timeout)\n            elements = self.find_visible_elements(selector, by=by)\n            if len(elements) < number:\n                raise Exception(\n                    \"Not enough matching {%s} elements of type {%s} to \"\n                    \"click number %s!\" % (selector, by, number)\n                )\n            number = number - 1\n            if number < 0:\n                number = 0\n            element = elements[number]\n            self.__element_click(element)\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n\n    def click_if_visible(self, selector, by=\"css selector\", timeout=0):\n        \"\"\"If the page selector exists and is visible, clicks on the element.\n        This method only clicks on the first matching element found.\n        Use click_visible_elements() to click all matching elements.\n        If a \"timeout\" is provided, waits that long for the element\n        to appear before giving up and returning without a click().\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.click_if_visible(selector, timeout=timeout)\n            return\n        self.wait_for_ready_state_complete()\n        if self.is_element_visible(selector, by=by):\n            self.click(selector, by=by)\n        elif timeout > 0:\n            with suppress(Exception):\n                self.wait_for_element_visible(\n                    selector, by=by, timeout=timeout\n                )\n                self.sleep(0.2)\n            if self.is_element_visible(selector, by=by):\n                self.click(selector, by=by)\n\n    def click_active_element(self):\n        if self.__is_cdp_swap_needed():\n            self.cdp.click_active_element()\n            return\n        self.wait_for_ready_state_complete()\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        if self.recorder_mode:\n            selector = js_utils.get_active_element_css(self.driver)\n            self.click(selector)\n            return\n        else:\n            self.execute_script(\"document.activeElement.click();\")\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        if settings.WAIT_FOR_RSC_ON_CLICKS:\n            self.wait_for_ready_state_complete()\n        else:\n            # A smaller subset of self.wait_for_ready_state_complete()\n            self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)\n            if self.driver.current_url != pre_action_url:\n                self.__ad_block_as_needed()\n                self.__disable_beforeunload_as_needed()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def click_with_offset(\n        self,\n        selector,\n        x,\n        y,\n        by=\"css selector\",\n        mark=None,\n        timeout=None,\n        center=None,\n    ):\n        \"\"\"Click an element at an {X,Y}-offset location.\n        {0,0} is the top-left corner of the element.\n        If center==True, {0,0} becomes the center of the element.\n        If mark==True, will draw a dot at location. (Useful for debugging)\n        In Demo Mode, mark becomes True unless set to False. (Default: None)\"\"\"\n        self.__check_scope()\n        self.__click_with_offset(\n            selector,\n            x,\n            y,\n            by=by,\n            double=False,\n            mark=mark,\n            timeout=timeout,\n            center=center,\n        )\n\n    def double_click_with_offset(\n        self,\n        selector,\n        x,\n        y,\n        by=\"css selector\",\n        mark=None,\n        timeout=None,\n        center=None,\n    ):\n        \"\"\"Double click an element at an {X,Y}-offset location.\n        {0,0} is the top-left corner of the element.\n        If center==True, {0,0} becomes the center of the element.\n        If mark==True, will draw a dot at location. (Useful for debugging)\n        In Demo Mode, mark becomes True unless set to False. (Default: None)\"\"\"\n        self.__check_scope()\n        self.__click_with_offset(\n            selector,\n            x,\n            y,\n            by=by,\n            double=True,\n            mark=mark,\n            timeout=timeout,\n            center=center,\n        )\n\n    def is_checked(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Determines if a checkbox or a radio button element is checked.\n        Returns True if the element is checked.\n        Returns False if the element is not checked.\n        If the element is not present on the page, raises an exception.\n        If the element is not a checkbox or radio, raises an exception.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.is_checked(selector)\n        kind = self.get_attribute(selector, \"type\", by=by, timeout=timeout)\n        if kind != \"checkbox\" and kind != \"radio\":\n            raise Exception(\"Expecting a checkbox or a radio button element!\")\n        return bool(\n            self.get_attribute(\n                selector, \"checked\", by=by, timeout=timeout, hard_fail=False\n            )\n        )\n\n    def is_selected(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as is_checked()\"\"\"\n        return self.is_checked(selector, by=by, timeout=timeout)\n\n    def check_if_unchecked(self, selector, by=\"css selector\"):\n        \"\"\"If a checkbox or radio button is not checked, will check it.\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.check_if_unchecked(selector)\n            return\n        if not self.is_checked(selector, by=by):\n            if self.is_element_visible(selector, by=by):\n                self.click(selector, by=by)\n            else:\n                element = self.wait_for_element_present(selector, by=by)\n                opacity = self.execute_script(\n                    'return arguments[0].style.opacity;', element\n                )\n                # Handle switches that sit on checkboxes with zero opacity:\n                # Change the opacity a bit to allow the click to succeed.\n                with suppress(Exception):\n                    self.execute_script(\n                        'arguments[0].style.opacity=\"0.001\";', element\n                    )\n                if self.is_element_visible(selector, by=by):\n                    self.click(selector, by=by)\n                else:\n                    selector = self.convert_to_css_selector(selector, by=by)\n                    self.__dont_record_js_click = True\n                    self.js_click(selector, by=\"css selector\")\n                    self.__dont_record_js_click = False\n                with suppress(Exception):\n                    self.execute_script(\n                        'arguments[0].style.opacity=\"arguments[1]\";',\n                        element,\n                        opacity,\n                    )\n\n    def select_if_unselected(self, selector, by=\"css selector\"):\n        \"\"\"Same as check_if_unchecked()\"\"\"\n        self.check_if_unchecked(selector, by=by)\n\n    def uncheck_if_checked(self, selector, by=\"css selector\"):\n        \"\"\"If a checkbox is checked, will uncheck it.\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.uncheck_if_checked(selector)\n            return\n        if self.is_checked(selector, by=by):\n            if self.is_element_visible(selector, by=by):\n                self.click(selector, by=by)\n            else:\n                element = self.wait_for_element_present(selector, by=by)\n                opacity = self.execute_script(\n                    'return arguments[0].style.opacity;', element\n                )\n                # Handle switches that sit on checkboxes with zero opacity:\n                # Change the opacity a bit to allow the click to succeed.\n                with suppress(Exception):\n                    self.execute_script(\n                        'arguments[0].style.opacity=\"0.001\";', element\n                    )\n                if self.is_element_visible(selector, by=by):\n                    self.click(selector, by=by)\n                else:\n                    selector = self.convert_to_css_selector(selector, by=by)\n                    self.__dont_record_js_click = True\n                    self.js_click(selector, by=\"css selector\")\n                    self.__dont_record_js_click = False\n                with suppress(Exception):\n                    self.execute_script(\n                        'arguments[0].style.opacity=\"arguments[1]\";',\n                        element,\n                        opacity,\n                    )\n\n    def unselect_if_selected(self, selector, by=\"css selector\"):\n        \"\"\"Same as uncheck_if_checked()\"\"\"\n        self.uncheck_if_checked(selector, by=by)\n\n    def is_element_in_an_iframe(self, selector, by=\"css selector\"):\n        \"\"\"Returns True if the selector's element is located in an iframe.\n        Otherwise returns False.\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.is_element_present(selector, by=by):\n            return False\n        soup = self.get_beautiful_soup()\n        iframe_list = soup.select(\"iframe\")\n        for iframe in iframe_list:\n            iframe_identifier = None\n            if iframe.has_attr(\"name\") and len(iframe[\"name\"]) > 0:\n                iframe_identifier = iframe[\"name\"]\n            elif iframe.has_attr(\"id\") and len(iframe[\"id\"]) > 0:\n                iframe_identifier = iframe[\"id\"]\n            elif iframe.has_attr(\"class\") and len(iframe[\"class\"]) > 0:\n                iframe_class = \" \".join(iframe[\"class\"])\n                iframe_identifier = '[class=\"%s\"]' % iframe_class\n            else:\n                continue\n            self.switch_to_frame(iframe_identifier)\n            if self.is_element_present(selector, by=by):\n                self.switch_to_default_content()\n                return True\n            self.switch_to_default_content()\n        return False\n\n    def switch_to_frame_of_element(self, selector, by=\"css selector\"):\n        \"\"\"Set driver control to the iframe containing element (assuming the\n        element is in a single-nested iframe) and returns the iframe name.\n        If element is not in an iframe, returns None, and nothing happens.\n        May not work if multiple iframes are nested within each other.\"\"\"\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.04)\n            if self.undetectable:\n                time.sleep(0.04)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.is_element_present(selector, by=by):\n            return None\n        soup = self.get_beautiful_soup()\n        iframe_list = soup.select(\"iframe\")\n        for iframe in iframe_list:\n            iframe_identifier = None\n            if iframe.has_attr(\"name\") and len(iframe[\"name\"]) > 0:\n                iframe_identifier = iframe[\"name\"]\n            elif iframe.has_attr(\"id\") and len(iframe[\"id\"]) > 0:\n                iframe_identifier = iframe[\"id\"]\n            elif iframe.has_attr(\"class\") and len(iframe[\"class\"]) > 0:\n                iframe_class = \" \".join(iframe[\"class\"])\n                iframe_identifier = '[class=\"%s\"]' % iframe_class\n            else:\n                continue\n            with suppress(Exception):\n                self.switch_to_frame(iframe_identifier, timeout=1)\n                if self.__needs_minimum_wait():\n                    time.sleep(0.02)\n                if self.is_element_present(selector, by=by):\n                    return iframe_identifier\n            self.switch_to_default_content()\n            if self.__needs_minimum_wait():\n                time.sleep(0.02)\n        try:\n            self.switch_to_frame(selector, timeout=1)\n            if self.undetectable:\n                self.__uc_frame_layer += 1\n            return selector\n        except Exception:\n            if self.is_element_present(selector, by=by):\n                return \"\"\n            raise Exception(\n                \"Could not switch to iframe containing \"\n                \"element {%s}!\" % selector\n            )\n\n    def hover(self, selector, by=\"css selector\", timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        original_by = by\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.hover_element(selector)\n            return\n        self.wait_for_element_visible(\n            original_selector, by=original_by, timeout=timeout\n        )\n        self.__demo_mode_highlight_if_active(original_selector, original_by)\n        self.scroll_to(selector, by=by)\n        time.sleep(0.05)  # Settle down from scrolling before hovering\n        element = page_actions.hover_on_element(self.driver, selector, by)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if by == By.XPATH:\n                    selector = original_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"hover\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return element\n\n    def hover_and_click(\n        self,\n        hover_selector,\n        click_selector,\n        hover_by=\"css selector\",\n        click_by=\"css selector\",\n        timeout=None,\n        js_click=False,\n    ):\n        \"\"\"When you want to hover over an element or dropdown menu,\n        and then click an element that appears after that.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = hover_selector\n        original_by = hover_by\n        hover_selector, hover_by = self.__recalculate_selector(\n            hover_selector, hover_by\n        )\n        original_click_selector = click_selector\n        click_selector, click_by = self.__recalculate_selector(\n            click_selector, click_by\n        )\n        if self.__is_cdp_swap_needed():\n            self.cdp.hover_and_click(hover_selector, click_selector)\n            return\n        dropdown_element = self.wait_for_element_visible(\n            original_selector, by=original_by, timeout=timeout\n        )\n        self.__demo_mode_highlight_if_active(original_selector, original_by)\n        self.scroll_to(hover_selector, by=hover_by)\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if hover_by == By.XPATH:\n                    hover_selector = original_selector\n                if click_by == By.XPATH:\n                    click_selector = original_click_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                the_selectors = [hover_selector, click_selector]\n                action = [\"ho_cl\", the_selectors, origin, time_stamp]\n                self.__extra_actions.append(action)\n        outdated_driver = False\n        element = None\n        try:\n            if self.mobile_emulator:\n                # On mobile, click to hover the element\n                dropdown_element.click()\n            else:\n                page_actions.hover_element(self.driver, dropdown_element)\n        except Exception:\n            outdated_driver = True\n            element = self.wait_for_element_present(\n                click_selector, click_by, timeout\n            )\n            if click_by == By.LINK_TEXT:\n                self.open(self.__get_href_from_link_text(click_selector))\n            elif click_by == By.PARTIAL_LINK_TEXT:\n                self.open(\n                    self.__get_href_from_partial_link_text(click_selector)\n                )\n            else:\n                self.__dont_record_js_click = True\n                self.js_click(click_selector, by=click_by)\n                self.__dont_record_js_click = False\n        if outdated_driver:\n            pass  # Already did the click workaround\n        elif self.mobile_emulator:\n            self.click(click_selector, by=click_by)\n        elif not outdated_driver:\n            element = page_actions.hover_and_click(\n                self.driver,\n                hover_selector,\n                click_selector,\n                hover_by,\n                click_by,\n                timeout,\n                js_click,\n            )\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        elif self.browser == \"safari\":\n            # Release the hover by hovering elsewhere\n            with suppress(Exception):\n                page_actions.hover_on_element(self.driver, \"body\")\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        return element\n\n    def hover_and_js_click(\n        self,\n        hover_selector,\n        click_selector,\n        hover_by=\"css selector\",\n        click_by=\"css selector\",\n        timeout=None,\n    ):\n        self.hover_and_click(\n            hover_selector=hover_selector,\n            click_selector=click_selector,\n            hover_by=hover_by,\n            click_by=click_by,\n            timeout=timeout,\n            js_click=True,\n        )\n\n    def hover_and_double_click(\n        self,\n        hover_selector,\n        click_selector,\n        hover_by=\"css selector\",\n        click_by=\"css selector\",\n        timeout=None,\n    ):\n        \"\"\"When you want to hover over an element or dropdown menu,\n        and then double-click an element that appears after that.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = hover_selector\n        original_by = hover_by\n        hover_selector, hover_by = self.__recalculate_selector(\n            hover_selector, hover_by\n        )\n        hover_selector = self.convert_to_css_selector(hover_selector, hover_by)\n        hover_by = By.CSS_SELECTOR\n        click_selector, click_by = self.__recalculate_selector(\n            click_selector, click_by\n        )\n        dropdown_element = self.wait_for_element_visible(\n            original_selector, by=original_by, timeout=timeout\n        )\n        self.__demo_mode_highlight_if_active(original_selector, original_by)\n        self.scroll_to(hover_selector, by=hover_by)\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        outdated_driver = False\n        element = None\n        try:\n            page_actions.hover_element(self.driver, dropdown_element)\n        except Exception:\n            outdated_driver = True\n            element = self.wait_for_element_present(\n                click_selector, click_by, timeout\n            )\n            if click_by == By.LINK_TEXT:\n                self.open(self.__get_href_from_link_text(click_selector))\n            elif click_by == By.PARTIAL_LINK_TEXT:\n                self.open(\n                    self.__get_href_from_partial_link_text(click_selector)\n                )\n            else:\n                self.__dont_record_js_click = True\n                self.js_click(click_selector, click_by)\n                self.__dont_record_js_click = False\n        if not outdated_driver:\n            element = page_actions.hover_element_and_double_click(\n                self.driver,\n                dropdown_element,\n                click_selector,\n                click_by=\"css selector\",\n                timeout=timeout,\n            )\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        return element\n\n    def drag_and_drop(\n        self,\n        drag_selector,\n        drop_selector,\n        drag_by=\"css selector\",\n        drop_by=\"css selector\",\n        timeout=None,\n        jquery=False,\n    ):\n        \"\"\"Drag-and-drop an element from one selector to another.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        drag_selector, drag_by = self.__recalculate_selector(\n            drag_selector, drag_by\n        )\n        drop_selector, drop_by = self.__recalculate_selector(\n            drop_selector, drop_by\n        )\n        if self.__is_cdp_swap_needed():\n            self.cdp.gui_drag_and_drop(drag_selector, drop_selector)\n            return\n        drag_element = self.wait_for_element_clickable(\n            drag_selector, by=drag_by, timeout=timeout\n        )\n        self.__demo_mode_highlight_if_active(drag_selector, drag_by)\n        drop_element = self.wait_for_element_visible(\n            drop_selector, by=drop_by, timeout=timeout\n        )\n        self.__demo_mode_highlight_if_active(drop_selector, drop_by)\n        self.scroll_to(drop_selector, by=drop_by)\n        drag_selector = self.convert_to_css_selector(drag_selector, drag_by)\n        drop_selector = self.convert_to_css_selector(drop_selector, drop_by)\n        if not jquery:\n            drag_and_drop_script = js_utils.get_js_drag_and_drop_script()\n            self.execute_script(\n                drag_and_drop_script, drag_element, drop_element, 0, 0, 1, None\n            )\n        else:\n            drag_and_drop_script = js_utils.get_drag_and_drop_script()\n            self.safe_execute_script(\n                drag_and_drop_script\n                + (\n                    \"$('%s').simulateDragDrop(\"\n                    \"{dropTarget: \"\n                    \"'%s'});\" % (drag_selector, drop_selector)\n                )\n            )\n        if self.demo_mode:\n            self.__demo_mode_pause_if_active()\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        return drag_element\n\n    def drag_and_drop_with_offset(\n        self, selector, x, y, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Drag-and-drop an element to an {X,Y}-offset location.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        element = self.wait_for_element_visible(css_selector, timeout=timeout)\n        self.__demo_mode_highlight_if_active(css_selector, By.CSS_SELECTOR)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        script = js_utils.get_drag_and_drop_with_offset_script(\n            css_selector, x, y\n        )\n        self.execute_script(script)\n        if self.demo_mode:\n            self.__demo_mode_pause_if_active()\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n        return element\n\n    def __element_click(self, element):\n        self.__check_scope()\n        if (\n            not self.undetectable\n            or self.uc_cdp_events\n            or self.__uc_frame_layer > 0\n            or not hasattr(element, \"uc_click\")\n            or element.tag_name.lower() != \"a\"\n        ):\n            element.click()\n        else:\n            try:\n                href = element.get_attribute(\"href\")\n                target = element.get_attribute(\"target\")\n                if len(href) > 0 and target != \"_blank\":\n                    element.uc_click()\n                else:\n                    element.click()\n                time.sleep(0.012)\n            except Exception:\n                element.click()\n\n    def __select_option(\n        self,\n        dropdown_selector,\n        option,\n        dropdown_by=\"css selector\",\n        option_by=\"text\",\n        timeout=None,\n    ):\n        \"\"\"Selects an HTML <select> option by specification.\n        Option specifications are by \"text\", \"index\", or \"value\".\n        Defaults to \"text\" if option_by is unspecified or unknown.\"\"\"\n        from selenium.webdriver.support.ui import Select\n\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        dropdown_selector, dropdown_by = self.__recalculate_selector(\n            dropdown_selector, dropdown_by\n        )\n        self.wait_for_ready_state_complete()\n        element = self.wait_for_element_present(\n            dropdown_selector, by=dropdown_by, timeout=timeout\n        )\n        try:\n            element = self.wait_for_element_clickable(\n                dropdown_selector, by=dropdown_by, timeout=1.8\n            )\n        except Exception:\n            self.wait_for_ready_state_complete()\n        if self.is_element_visible(dropdown_selector, by=dropdown_by):\n            self.__demo_mode_highlight_if_active(\n                dropdown_selector, dropdown_by\n            )\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        try:\n            if option_by == \"index\":\n                Select(element).select_by_index(option)\n            elif option_by == \"value\":\n                Select(element).select_by_value(option)\n            else:\n                Select(element).select_by_visible_text(option)\n            time.sleep(0.05)\n            self.wait_for_ready_state_complete()\n        except Exception:\n            time.sleep(0.25)\n            self.wait_for_ready_state_complete()\n            element = self.wait_for_element_present(\n                dropdown_selector, by=dropdown_by, timeout=timeout\n            )\n            try:\n                element = self.wait_for_element_clickable(\n                    dropdown_selector, by=dropdown_by, timeout=1.8\n                )\n            except Exception:\n                self.wait_for_ready_state_complete()\n            if option_by == \"index\":\n                try:\n                    Select(element).select_by_index(option)\n                except Exception:\n                    msg = (\n                        \"Element {%s} has no selectable index option {%s}!\"\n                        % (dropdown_selector, option)\n                    )\n                    page_actions.timeout_exception(\n                        \"NoSuchOptionException\", msg\n                    )\n            elif option_by == \"value\":\n                try:\n                    Select(element).select_by_value(option)\n                except Exception:\n                    msg = (\n                        \"Element {%s} has no selectable value option {%s}!\"\n                        % (dropdown_selector, option)\n                    )\n                    page_actions.timeout_exception(\n                        \"NoSuchOptionException\", msg\n                    )\n            else:\n                try:\n                    Select(element).select_by_visible_text(option)\n                except Exception:\n                    msg = (\n                        \"Element {%s} has no selectable text option {%s}!\"\n                        % (dropdown_selector, option)\n                    )\n                    page_actions.timeout_exception(\n                        \"NoSuchOptionException\", msg\n                    )\n            time.sleep(0.05)\n            self.wait_for_ready_state_complete()\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        if settings.WAIT_FOR_RSC_ON_CLICKS:\n            self.wait_for_ready_state_complete()\n        else:\n            # A smaller subset of self.wait_for_ready_state_complete()\n            self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)\n            if self.driver.current_url != pre_action_url:\n                self.__ad_block_as_needed()\n                self.__disable_beforeunload_as_needed()\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def select_option_by_text(\n        self,\n        dropdown_selector,\n        option,\n        dropdown_by=\"css selector\",\n        timeout=None,\n    ):\n        \"\"\"Selects an HTML <select> option by option text.\n        @Params\n        dropdown_selector - the <select> selector.\n        option - the text of the option.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.select_option_by_text(dropdown_selector, option)\n            return\n        self.__select_option(\n            dropdown_selector,\n            option,\n            dropdown_by=dropdown_by,\n            option_by=\"text\",\n            timeout=timeout,\n        )\n\n    def select_option_by_index(\n        self,\n        dropdown_selector,\n        option,\n        dropdown_by=\"css selector\",\n        timeout=None,\n    ):\n        \"\"\"Selects an HTML <select> option by option index.\n        @Params\n        dropdown_selector - the <select> selector.\n        option - the index number of the option.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.select_option_by_index(dropdown_selector, option)\n            return\n        self.__select_option(\n            dropdown_selector,\n            option,\n            dropdown_by=dropdown_by,\n            option_by=\"index\",\n            timeout=timeout,\n        )\n\n    def select_option_by_value(\n        self,\n        dropdown_selector,\n        option,\n        dropdown_by=\"css selector\",\n        timeout=None,\n    ):\n        \"\"\"Selects an HTML <select> option by option value.\n        @Params\n        dropdown_selector - the <select> selector.\n        option - the value property of the option.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.select_option_by_value(dropdown_selector, option)\n            return\n        self.__select_option(\n            dropdown_selector,\n            option,\n            dropdown_by=dropdown_by,\n            option_by=\"value\",\n            timeout=timeout,\n        )\n\n    def get_select_options(\n        self,\n        dropdown_selector,\n        attribute=\"text\",\n        by=\"css selector\",\n        timeout=None,\n    ):\n        \"\"\"Returns a list of select options as attribute text (configurable).\n        @Params\n        dropdown_selector - The selector of the \"select\" element.\n        attribute - Choose from \"text\", \"index\", \"value\", or None (elements).\n        by - The \"by\" of the \"select\" selector to use. Default: \"css selector\".\n        timeout - Wait time for \"select\". If None: settings.SMALL_TIMEOUT.\"\"\"\n        self.wait_for_ready_state_complete()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector = dropdown_selector\n        allowed_attributes = [\"text\", \"index\", \"value\", None]\n        if attribute not in allowed_attributes:\n            raise Exception(\"The attribute must be in %s\" % allowed_attributes)\n        selector, by = self.__recalculate_selector(selector, by)\n        element = self.wait_for_element(selector, by=by, timeout=timeout)\n        if element.tag_name.lower() != \"select\":\n            raise Exception(\n                'Element tag_name for get_select_options(selector) must be a '\n                '\"select\"! Actual tag_name found was: \"%s\"'\n                % element.tag_name.lower()\n            )\n        if by != \"css selector\":\n            selector = self.convert_to_css_selector(selector, by=by)\n        option_selector = selector + \" option\"\n        option_elements = self.find_elements(option_selector)\n        if not attribute:\n            return option_elements\n        elif attribute == \"text\":\n            return [e.text for e in option_elements]\n        else:\n            return [e.get_attribute(attribute) for e in option_elements]\n\n    def load_html_string(self, html_string, new_page=True):\n        \"\"\"Loads an HTML string into the web browser.\n        If new_page==True, the page will switch to: \"data:text/html,\"\n        If new_page==False, will load HTML into the current page.\"\"\"\n        self.wait_for_ready_state_complete()\n        new_lines = []\n        lines = html_string.split(\"\\n\")\n        for line in lines:\n            if not line.strip().startswith(\"//\"):\n                new_lines.append(line)\n        html_string = \"\\n\".join(new_lines)\n        soup = self.get_beautiful_soup(html_string)\n        found_base = False\n        links = soup.find_all(\"link\")\n        href = None\n        for link in links:\n            if link.get(\"rel\") == [\"canonical\"] and link.get(\"href\"):\n                found_base = True\n                href = link.get(\"href\")\n                href = self.get_domain_url(href)\n        if (\n            found_base\n            and html_string.count(\"<head>\") == 1\n            and html_string.count(\"<base\") == 0\n        ):\n            html_string = html_string.replace(\n                \"<head>\", '<head><base href=\"%s\">' % href\n            )\n        elif not found_base:\n            bases = soup.find_all(\"base\")\n            for base in bases:\n                if base.get(\"href\"):\n                    href = base.get(\"href\")\n        if href:\n            html_string = html_string.replace('base: \".\"', 'base: \"%s\"' % href)\n\n        soup = self.get_beautiful_soup(html_string)\n        scripts = soup.find_all(\"script\")\n        for script in scripts:\n            if script.get(\"type\") != \"application/json\":\n                html_string = html_string.replace(str(script), \"\")\n        soup = self.get_beautiful_soup(html_string)\n\n        found_head = False\n        found_body = False\n        html_head = None\n        html_body = None\n        if soup.head and len(str(soup.head)) > 12:\n            found_head = True\n            html_head = str(soup.head)\n            html_head = re.escape(html_head)\n            html_head = self.__escape_quotes_if_needed(html_head)\n            html_head = html_head.replace(\"\\\\ \", \" \")\n        if soup.body and len(str(soup.body)) > 12:\n            found_body = True\n            html_body = str(soup.body)\n            html_body = html_body.replace(\"\\xc2\\xa0\", \"&#xA0;\")\n            html_body = html_body.replace(\"\\xc2\\xa1\", \"&#xA1;\")\n            html_body = html_body.replace(\"\\xc2\\xa9\", \"&#xA9;\")\n            html_body = html_body.replace(\"\\xc2\\xb7\", \"&#xB7;\")\n            html_body = html_body.replace(\"\\xc2\\xbf\", \"&#xBF;\")\n            html_body = html_body.replace(\"\\xc3\\x97\", \"&#xD7;\")\n            html_body = html_body.replace(\"\\xc3\\xb7\", \"&#xF7;\")\n            html_body = re.escape(html_body)\n            html_body = self.__escape_quotes_if_needed(html_body)\n            html_body = html_body.replace(\"\\\\ \", \" \")\n        html_string = re.escape(html_string)\n        html_string = self.__escape_quotes_if_needed(html_string)\n        html_string = html_string.replace(\"\\\\ \", \" \")\n\n        if new_page:\n            self.open(\"data:text/html,<head></head><body><div></div></body>\")\n        inner_head = \"\"\"document.getElementsByTagName(\"head\")[0].innerHTML\"\"\"\n        inner_body = \"\"\"document.getElementsByTagName(\"body\")[0].innerHTML\"\"\"\n        with suppress(Exception):\n            self.wait_for_element_present(\"body\", timeout=1)\n        if not found_body:\n            self.execute_script('''%s = \\\"%s\\\"''' % (inner_body, html_string))\n        elif found_body and not found_head:\n            self.execute_script('''%s = \\\"%s\\\"''' % (inner_body, html_body))\n        elif found_body and found_head:\n            self.execute_script('''%s = \\\"%s\\\"''' % (inner_head, html_head))\n            time.sleep(0.02)\n            self.execute_script('''%s = \\\"%s\\\"''' % (inner_body, html_body))\n        else:\n            raise Exception(\"Logic Error!\")\n\n        for script in scripts:\n            js_code = script.string\n            js_src = script.get(\"src\")\n            if js_code and script.get(\"type\") != \"application/json\":\n                js_code_lines = js_code.split(\"\\n\")\n                new_lines = []\n                for line in js_code_lines:\n                    line = line.strip()\n                    new_lines.append(line)\n                js_code = \"\\n\".join(new_lines)\n                js_code = re.escape(js_code)\n                js_utils.add_js_code(self.driver, js_code)\n            elif js_src:\n                js_utils.add_js_link(self.driver, js_src)\n            else:\n                pass\n\n    def set_content(self, html_string, new_page=False):\n        \"\"\"Same as load_html_string(), but \"new_page\" defaults to False.\"\"\"\n        self.load_html_string(html_string, new_page=new_page)\n\n    def load_html_file(self, html_file, new_page=True):\n        \"\"\"Loads a local html file into the browser from a relative file path.\n        If new_page==True, the page will switch to: \"data:text/html,\"\n        If new_page==False, will load HTML into the current page.\n        Local images and other local src content WILL BE IGNORED.\"\"\"\n        self.__check_scope()\n        if self.__looks_like_a_page_url(html_file):\n            self.open(html_file)\n            return\n        if len(html_file) < 6 or not html_file.endswith(\".html\"):\n            raise Exception('Expecting a \".html\" file!')\n        abs_path = os.path.abspath(\".\")\n        file_path = None\n        if abs_path in html_file:\n            file_path = html_file\n        else:\n            file_path = os.path.join(abs_path, html_file)\n        html_string = None\n        with open(file_path, \"r\") as f:\n            html_string = f.read().strip()\n        self.load_html_string(html_string, new_page)\n\n    def open_html_file(self, html_file):\n        \"\"\"Opens a local html file into the browser from a relative file path.\n        The URL displayed in the web browser will start with \"file://\".\"\"\"\n        self.__check_scope()\n        if self.__looks_like_a_page_url(html_file):\n            self.open(html_file)\n            return\n        if len(html_file) < 6 or not html_file.endswith(\".html\"):\n            raise Exception('Expecting a \".html\" file!')\n        abs_path = os.path.abspath(\".\")\n        file_path = None\n        if abs_path in html_file:\n            file_path = html_file\n        else:\n            file_path = os.path.join(abs_path, html_file)\n        self.open(\"file://\" + file_path)\n\n    def evaluate(self, expression):\n        \"\"\"Run a JavaScript expression and return the result.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return self.cdp.evaluate(expression)\n        self._check_browser()\n        original_expression = expression\n        expression = expression.strip()\n        exp_list = expression.split(\"\\n\")\n        if exp_list and exp_list[-1].strip().startswith(\"return \"):\n            expression = (\n                \"\\n\".join(exp_list[0:-1]) + \"\\n\"\n                + exp_list[-1].strip()[len(\"return \"):]\n            ).strip()\n        evaluation = self.driver.execute_cdp_cmd(\n            \"Runtime.evaluate\",\n            {\n                \"expression\": expression\n            },\n        )\n        if \"value\" in evaluation[\"result\"]:\n            return evaluation[\"result\"][\"value\"]\n        elif evaluation[\"result\"][\"type\"] == \"undefined\":\n            return None\n        elif \"exceptionDetails\" in evaluation:\n            raise Exception(evaluation[\"result\"][\"description\"], expression)\n        elif evaluation[\"result\"][\"type\"] == \"object\":\n            if \"return \" not in original_expression:\n                expression = \"return \" + original_expression.strip()\n            # Need to use execute_script() to return a WebDriver object.\n            # If this causes duplicate evaluation, don't use evaluate().\n            return self.execute_script(expression)\n        elif evaluation[\"result\"][\"type\"] == \"function\":\n            return {}  # This is what sb.cdp.evaluate returns\n        elif \"description\" in evaluation[\"result\"]:\n            # At this point, the description is the exception\n            raise Exception(evaluation[\"result\"][\"description\"], expression)\n        else:  # Possibly an unhandled case if reached\n            return None\n\n    def execute_script(self, script, *args, **kwargs):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return self.cdp.evaluate(script)\n        self._check_browser()\n        return self.driver.execute_script(script, *args, **kwargs)\n\n    def execute_cdp_cmd(self, script, *args, **kwargs):\n        self.__check_scope()\n        self._check_browser()\n        return self.driver.execute_cdp_cmd(script, *args, **kwargs)\n\n    def execute_async_script(self, script, timeout=None):\n        self.__check_scope()\n        self._check_browser()\n        if not timeout:\n            timeout = settings.EXTREME_TIMEOUT\n        return js_utils.execute_async_script(self.driver, script, timeout)\n\n    def safe_execute_script(self, script, *args, **kwargs):\n        \"\"\"When executing a script that contains a jQuery command,\n        it's important that the jQuery library has been loaded first.\n        This method will load jQuery if it wasn't already loaded.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if not js_utils.is_jquery_activated(self.driver):\n            self.activate_jquery()\n        return self.driver.execute_script(script, *args, **kwargs)\n\n    def get_element_at_x_y(self, x, y):\n        \"\"\"Return element at current window's x,y coordinates.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        return self.execute_script(\n            \"return document.elementFromPoint(%s, %s);\" % (x, y)\n        )\n\n    def get_gui_element_rect(self, selector, by=\"css selector\"):\n        \"\"\"Very similar to element.rect, but the x, y coordinates are\n        relative to the entire screen, rather than the browser window.\n        This is specifically for PyAutoGUI actions on the full screen.\n        (Note: There may be complications if iframes are involved.)\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_gui_element_rect(selector)\n        element = self.wait_for_element_present(selector, by=by, timeout=1)\n        element_rect = element.rect\n        e_width = element_rect[\"width\"]\n        e_height = element_rect[\"height\"]\n        i_x = 0\n        i_y = 0\n        iframe_switch = False\n        if self.__is_in_frame():\n            self.switch_to_parent_frame()\n            if self.__is_in_frame():\n                raise Exception(\"Nested iframes breaks get_gui_element_rect!\")\n            iframe_switch = True\n            iframe = self.wait_for_element_present(\"iframe\", timeout=1)\n            i_x = iframe.rect[\"x\"]\n            i_y = iframe.rect[\"y\"]\n        window_rect = self.get_window_rect()\n        w_bottom_y = window_rect[\"y\"] + window_rect[\"height\"]\n        viewport_height = self.execute_script(\"return window.innerHeight;\")\n        x = math.ceil(window_rect[\"x\"] + i_x + element_rect[\"x\"])\n        y = math.ceil(w_bottom_y - viewport_height + i_y + element_rect[\"y\"])\n        y_scroll_offset = self.execute_script(\"return window.pageYOffset;\")\n        y = int(y - y_scroll_offset)\n        if iframe_switch:\n            self.switch_to_frame()\n            if not self.is_element_present(selector, by=by):\n                self.switch_to_parent_frame()\n        return ({\"height\": e_height, \"width\": e_width, \"x\": x, \"y\": y})\n\n    def get_gui_element_center(self, selector, by=\"css selector\"):\n        \"\"\"Returns the x, y coordinates of the element's center based\n        on the entire GUI / screen, rather than on the browser window.\n        This is specifically for PyAutoGUI actions on the full screen.\n        (Note: There may be complications if iframes are involved.)\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_gui_element_center(selector)\n        element_rect = self.get_gui_element_rect(selector, by=by)\n        x = element_rect[\"x\"] + (element_rect[\"width\"] / 2.0) + 0.5\n        y = element_rect[\"y\"] + (element_rect[\"height\"] / 2.0) + 0.5\n        return (x, y)\n\n    def get_screen_rect(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_screen_rect()\n        self._check_browser()\n        return self.driver.get_screen_rect()\n\n    def get_window_rect(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_window_rect()\n        self._check_browser()\n        return self.driver.get_window_rect()\n\n    def get_window_size(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_window_size()\n        self._check_browser()\n        return self.driver.get_window_size()\n\n    def get_window_position(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_window_position()\n        self._check_browser()\n        return self.driver.get_window_position()\n\n    def set_window_rect(self, x, y, width, height):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.set_window_rect(x, y, width, height)\n            return\n        self._check_browser()\n        self.driver.set_window_rect(x, y, width, height)\n        self.__demo_mode_pause_if_active(tiny=True)\n\n    def set_window_size(self, width, height):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            position = self.cdp.get_window_position()\n            x = position[\"x\"]\n            y = position[\"y\"]\n            self.cdp.set_window_rect(x, y, width, height)\n            return\n        self._check_browser()\n        self.driver.set_window_size(width, height)\n        self.__demo_mode_pause_if_active(tiny=True)\n\n    def set_window_position(self, x, y):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            size = self.cdp.get_window_size()\n            width = size[\"width\"]\n            height = size[\"height\"]\n            self.cdp.set_window_rect(x, y, width, height)\n            return\n        self._check_browser()\n        self.driver.set_window_position(x, y)\n        self.__demo_mode_pause_if_active(tiny=True)\n\n    def maximize_window(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.maximize()\n            return\n        self._check_browser()\n        try:\n            self.driver.maximize_window()\n        except Exception:\n            with suppress(Exception):\n                width = self.execute_script(\"return screen.availWidth;\")\n                height = self.execute_script(\"return screen.availHeight;\")\n                self.set_window_rect(0, 0, width, height)\n        self.__demo_mode_pause_if_active(tiny=True)\n\n    def maximize(self):\n        self.maximize_window()\n\n    def minimize_window(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.minimize()\n            return\n        self._check_browser()\n        self.driver.minimize_window()\n        self.__demo_mode_pause_if_active(tiny=True)\n\n    def minimize(self):\n        self.minimize_window()\n\n    def reset_window_size(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.reset_window_size()\n            return\n        self._check_browser()\n        x = settings.WINDOW_START_X\n        y = settings.WINDOW_START_Y\n        width = settings.CHROME_START_WIDTH\n        height = settings.CHROME_START_HEIGHT\n        self.set_window_rect(x, y, width, height)\n        self.__demo_mode_pause_if_active(tiny=True)\n\n    def switch_to_frame(self, frame=\"iframe\", timeout=None, invisible=False):\n        \"\"\"Wait for an iframe to appear, and switch to it. This should be\n        usable as a drop-in replacement for driver.switch_to.frame().\n        The iframe identifier can be a selector, an index, an id, a name,\n        or a web element, but scrolling to the iframe first will only occur\n        for visible iframes with a string selector.\n        @Params\n        frame - the frame element, name, id, index, or selector\n        timeout - the time to wait for the alert in seconds\n        invisible - if True, the iframe can be invisible \"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__needs_minimum_wait():\n            time.sleep(0.05)\n            if self.undetectable:\n                time.sleep(0.05)\n        if isinstance(frame, str) and self.is_element_visible(frame):\n            try:\n                self.scroll_to(frame, timeout=1)\n                if self.__needs_minimum_wait():\n                    time.sleep(0.04)\n                    if self.undetectable:\n                        time.sleep(0.04)\n            except Exception:\n                time.sleep(0.02)\n        else:\n            if self.__needs_minimum_wait():\n                time.sleep(0.05)\n                if self.undetectable:\n                    time.sleep(0.05)\n        if self.undetectable:\n            self.__uc_frame_layer += 1\n        if (\n            self.recorder_mode\n            and self._rec_overrides_switch\n            and self.__current_url_is_recordable()\n        ):\n            r_a = self.get_session_storage_item(\"recorder_activated\")\n            if r_a == \"yes\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"sk_op\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n                time_stamp = self.execute_script(\"return Date.now();\")\n                self.__set_c_from_switch = True\n                self.set_content_to_frame(frame, timeout=timeout)\n                self.__set_c_from_switch = False\n                origin = self.get_origin()\n                action = [\"sw_fr\", frame, origin, time_stamp]\n                self.__extra_actions.append(action)\n                return\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.035)\n            if self.undetectable:\n                time.sleep(0.035)\n        page_actions.switch_to_frame(\n            self.driver, frame, timeout, invisible=invisible\n        )\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.015)\n            if self.undetectable:\n                time.sleep(0.015)\n\n    def switch_to_default_content(self):\n        \"\"\"Brings driver control outside the current iframe.\n        If the driver is currently set inside an iframe or nested iframes,\n        then the driver control will exit from all entered iframes.\n        If the driver is not currently set in an iframe, nothing happens.\"\"\"\n        self.__check_scope()\n        if (\n            self.recorder_mode\n            and self._rec_overrides_switch\n            and self.__current_url_is_recordable()\n        ):\n            r_a = self.get_session_storage_item(\"recorder_activated\")\n            if r_a == \"yes\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                self.__set_c_from_switch = True\n                self.set_content_to_default()\n                self.__set_c_from_switch = False\n                origin = self.get_origin()\n                action = [\"sw_dc\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n                return\n        self.driver.switch_to.default_content()\n        if self.undetectable:\n            self.__uc_frame_layer = 0\n\n    def switch_to_parent_frame(self):\n        \"\"\"Brings driver control outside the current iframe.\n        If the driver is currently set inside an iframe or nested iframes,\n        the driver control will be set to one level above the current frame.\n        If the driver is not currently set in an iframe, nothing happens.\"\"\"\n        self.__check_scope()\n        if (\n            self.recorder_mode\n            and self._rec_overrides_switch\n            and self.__current_url_is_recordable()\n        ):\n            r_a = self.get_session_storage_item(\"recorder_activated\")\n            if r_a == \"yes\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                self.__set_c_from_switch = True\n                self.set_content_to_default(nested=True)\n                self.__set_c_from_switch = False\n                origin = self.get_origin()\n                action = [\"sw_pf\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n                return\n        self.driver.switch_to.parent_frame()\n        if self.undetectable:\n            self.__uc_frame_layer -= 1\n            if self.__uc_frame_layer < 0:\n                self.__uc_frame_layer = 0\n\n    @contextmanager\n    def frame_switch(self, frame, timeout=None):\n        \"\"\" Context Manager for switching into iframes.\n        Usage example:\n            with self.frame_switch(\"iframe\"):\n                # Perform actions here that should be done within the iframe.\n            # The iframe is automatically exited after the \"with\" block ends.\n        \"\"\"\n        if self.recorder_mode:\n            self.__frame_switch_layer += 1\n            if self.__frame_switch_layer >= 2:\n                self.__frame_switch_multi = True\n        self.switch_to_frame(frame, timeout=timeout)\n        if self.undetectable:\n            self.__uc_frame_layer += 1\n        yield\n        if self.undetectable:\n            self.__uc_frame_layer -= 1\n            if self.__uc_frame_layer < 0:\n                self.__uc_frame_layer = 0\n        self.switch_to_parent_frame()\n        if self.recorder_mode:\n            self.__frame_switch_layer -= 1\n            if self.__frame_switch_layer < 0:\n                self.__frame_switch_layer = 0\n                self.__frame_switch_multi = False\n            if self.__frame_switch_layer == 0 and self.__frame_switch_multi:\n                self.refresh()\n                self.__frame_switch_multi = False\n\n    def set_content_to_frame(self, frame, timeout=None):\n        \"\"\"Replaces the page html with an iframe's html from that page.\n        If the iframe contains an \"src\" field that includes a valid URL,\n        then instead of replacing the current html, this method will then\n        open up the \"src\" URL of the iframe in a new browser tab.\n        To return to default content, use: self.set_content_to_default().\n        This method also sets the state of the browser window so that the\n        self.set_content_to_default() method can bring the user back to\n        the original content displayed, which is similar to how the methods\n        self.switch_to_frame(frame) and self.switch_to_default_content()\n        work together to get the user into frames and out of all of them.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        current_url = self.get_current_url()\n        c_tab = self.driver.current_window_handle\n        current_page_source = self.get_page_source()\n        self.execute_script(\"document.cframe_swap = 0;\")\n        page_actions.switch_to_frame(self.driver, frame, timeout)\n        iframe_html = self.get_page_source()\n        self.driver.switch_to.default_content()\n        self.wait_for_ready_state_complete()\n        frame_found = False\n        o_frame = frame\n        if self.is_element_present(frame):\n            frame_found = True\n        elif \" \" not in frame:\n            frame = 'iframe[name=\"%s\"]' % frame\n            if self.is_element_present(frame):\n                frame_found = True\n        time_stamp = 0\n        url = None\n        if frame_found:\n            url = self.execute_script(\n                \"\"\"return document.querySelector('%s').src;\"\"\" % frame\n            )\n            if url and len(url) > 0:\n                if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                    pass\n                else:\n                    url = None\n        cframe_tab = False\n        if url:\n            cframe_tab = True\n        self.__page_sources.append([current_url, current_page_source, c_tab])\n\n        if self.recorder_mode and not self.__set_c_from_switch:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"sk_op\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n            time_stamp = str(int(time_stamp) + 1)\n\n        if cframe_tab:\n            self.execute_script(\"document.cframe_tab = 1;\")\n            self.open_new_window(switch_to=True)\n            self.open(url)\n            self.execute_script(\"document.cframe_tab = 1;\")\n        else:\n            self.set_content(iframe_html)\n            if not self.execute_script(\"return document.cframe_swap;\"):\n                self.execute_script(\"document.cframe_swap = 1;\")\n            else:\n                self.execute_script(\"document.cframe_swap += 1;\")\n\n        if self.recorder_mode and not self.__set_c_from_switch:\n            origin = self.get_origin()\n            action = [\"s_c_f\", o_frame, origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def set_content_to_default(self, nested=False):\n        \"\"\"After using self.set_content_to_frame(), this reverts the page back.\n        If self.set_content_to_frame() hasn't been called here, only refreshes.\n        If \"nested\" is set to True when the content is set to a nested iframe,\n        then the page control will only exit from the current iframe entered,\n        instead of exiting out of all iframes entered.\"\"\"\n        self.__check_scope()\n        swap_cnt = self.execute_script(\"return document.cframe_swap;\")\n        tab_sta = self.execute_script(\"return document.cframe_tab;\")\n        time_stamp = 0\n\n        if self.recorder_mode and not self.__set_c_from_switch:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"sk_op\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n            time_stamp = str(int(time_stamp) + 1)\n\n        if not nested:\n            # Sets the page to the outer-most content.\n            # If page control was inside nested iframes, exits them all.\n            # If only in one iframe, has the same effect as nested=True.\n            if (\n                len(self.__page_sources) > 0\n                and (\n                    (swap_cnt and int(swap_cnt) > 0)\n                    or (tab_sta and int(tab_sta) > 0)\n                )\n            ):\n                past_content = self.__page_sources[0]\n                past_url = past_content[0]\n                past_source = past_content[1]\n                past_tab = past_content[2]\n                current_tab = self.driver.current_window_handle\n                if not current_tab == past_tab:\n                    if past_tab in self.driver.window_handles:\n                        self.switch_to_window(past_tab)\n                url_of_past_tab = self.get_current_url()\n                if self.recorder_mode and not self.__set_c_from_switch:\n                    time_stamp = self.execute_script(\"return Date.now();\")\n                if url_of_past_tab == past_url:\n                    self.set_content(past_source)\n                else:\n                    self.refresh_page()\n            else:\n                if self.recorder_mode and not self.__set_c_from_switch:\n                    time_stamp = self.execute_script(\"return Date.now();\")\n                self.refresh_page()\n            self.execute_script(\"document.cframe_swap = 0;\")\n            self.__page_sources = []\n        else:\n            # (If Nested is True)\n            # Sets the page to the content outside the current nested iframe.\n            # If only in one iframe, has the same effect as nested=True.\n            just_refresh = False\n            if swap_cnt and int(swap_cnt) > 0 and len(self.__page_sources) > 0:\n                self.execute_script(\"document.cframe_swap -= 1;\")\n                current_url = self.get_current_url()\n                past_content = self.__page_sources.pop()\n                past_url = past_content[0]\n                past_source = past_content[1]\n                if current_url == past_url:\n                    self.set_content(past_source)\n                else:\n                    just_refresh = True\n            elif tab_sta and int(tab_sta) > 0 and len(self.__page_sources) > 0:\n                past_content = self.__page_sources.pop()\n                past_tab = past_content[2]\n                if past_tab in self.driver.window_handles:\n                    self.switch_to_window(past_tab)\n                else:\n                    just_refresh = True\n            else:\n                just_refresh = True\n            if just_refresh:\n                if self.recorder_mode and not self.__set_c_from_switch:\n                    time_stamp = self.execute_script(\"return Date.now();\")\n                self.refresh_page()\n                self.execute_script(\"document.cframe_swap = 0;\")\n                self.__page_sources = []\n\n        if self.recorder_mode and not self.__set_c_from_switch:\n            origin = self.get_origin()\n            action = [\"s_c_d\", nested, origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def set_content_to_default_content(self, nested=False):\n        \"\"\"Same as self.set_content_to_default().\"\"\"\n        self.set_content_to_default(nested=nested)\n\n    def set_content_to_parent(self):\n        \"\"\"Same as self.set_content_to_parent_frame().\n        Same as self.set_content_to_default(nested=True).\n        Sets the page to the content outside the current nested iframe.\n        Reverts self.set_content_to_frame().\"\"\"\n        self.set_content_to_default(nested=True)\n\n    def set_content_to_parent_frame(self):\n        \"\"\"Same as self.set_content_to_parent().\n        Same as self.set_content_to_default(nested=True).\n        Sets the page to the content outside the current nested iframe.\n        Reverts self.set_content_to_frame().\"\"\"\n        self.set_content_to_default(nested=True)\n\n    def open_new_window(self, switch_to=True, **kwargs):\n        \"\"\"Opens a new browser tab/window and switches to it by default.\"\"\"\n        url = None\n        if self.__looks_like_a_page_url(str(switch_to)):\n            # Different API for CDP Mode: First arg is a `url`.\n            # (Also, don't break backwards compat for reg mode)\n            url = switch_to\n            switch_to = True\n        if self.__is_cdp_swap_needed():\n            self.cdp.open_new_tab(url=url, switch_to=switch_to, **kwargs)\n            return\n        elif (\n            getattr(self.driver, \"_is_using_uc\", None)\n            and getattr(self.driver, \"_is_using_cdp\", None)\n        ):\n            self.disconnect()\n            self.cdp.open_new_tab(url=url, switch_to=switch_to, **kwargs)\n            return\n        self.wait_for_ready_state_complete()\n        if switch_to:\n            try:\n                self.driver.switch_to.new_window(\"tab\")\n            except Exception:\n                self.driver.execute_script(\"window.open('');\")\n                self.switch_to_newest_window()\n        else:\n            self.driver.execute_script(\"window.open('');\")\n        time.sleep(0.01)\n        if self.browser == \"safari\":\n            self.wait_for_ready_state_complete()\n\n    def switch_to_window(self, window, timeout=None):\n        \"\"\"Switches control of the browser to the specified window.\n        The window can be an integer: 0 -> 1st tab, 1 -> 2nd tab, etc...\n            Or it can be a list item from self.driver.window_handles\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed() and not isinstance(window, str):\n            self.cdp.switch_to_tab(window)\n            return\n        page_actions.switch_to_window(self.driver, window, timeout)\n\n    def switch_to_default_window(self):\n        self.switch_to_window(0)\n\n    def switch_to_newest_window(self):\n        self.switch_to_window(-1)\n\n    def get_new_driver(\n        self,\n        browser=None,\n        headless=None,\n        locale_code=None,\n        protocol=None,\n        servername=None,\n        port=None,\n        proxy=None,\n        proxy_bypass_list=None,\n        proxy_pac_url=None,\n        multi_proxy=None,\n        agent=None,\n        switch_to=True,\n        cap_file=None,\n        cap_string=None,\n        recorder_ext=None,\n        disable_cookies=None,\n        disable_js=None,\n        disable_csp=None,\n        enable_ws=None,\n        enable_sync=None,\n        use_auto_ext=None,\n        undetectable=None,\n        uc_cdp_events=None,\n        uc_subprocess=None,\n        log_cdp_events=None,\n        no_sandbox=None,\n        disable_gpu=None,\n        headless1=None,\n        headless2=None,\n        incognito=None,\n        guest_mode=None,\n        dark_mode=None,\n        devtools=None,\n        remote_debug=None,\n        enable_3d_apis=None,\n        swiftshader=None,\n        ad_block_on=None,\n        host_resolver_rules=None,\n        block_images=None,\n        do_not_track=None,\n        chromium_arg=None,\n        firefox_arg=None,\n        firefox_pref=None,\n        user_data_dir=None,\n        extension_zip=None,\n        extension_dir=None,\n        disable_features=None,\n        binary_location=None,\n        driver_version=None,\n        page_load_strategy=None,\n        use_wire=None,\n        external_pdf=None,\n        is_mobile=None,\n        d_width=None,\n        d_height=None,\n        d_p_r=None,\n        **kwargs,\n    ):\n        \"\"\"This method spins up an extra browser for tests that require\n        more than one. The first browser is already provided by tests\n        that import base_case.BaseCase from seleniumbase. If parameters\n        aren't specified, the method uses the same as the default driver.\n        @Params\n        browser - the browser to use. (Ex: \"chrome\", \"firefox\")\n        headless - the option to run webdriver in headless mode\n        locale_code - the Language Locale Code for the web browser\n        protocol - if using a Selenium Grid, set the host protocol here\n        servername - if using a Selenium Grid, set the host address here\n        port - if using a Selenium Grid, set the host port here\n        proxy - if using a proxy server, specify the \"host:port\" combo here\n        proxy_bypass_list - \";\"-separated hosts to bypass (Eg. \"*.foo.com\")\n        proxy_pac_url - designates the proxy PAC URL to use (Chromium-only)\n        multi_proxy - allow multiple proxies with auth while multi-threaded\n        switch_to - the option to switch to the new driver (default = True)\n        cap_file - the file containing desired capabilities for the browser\n        cap_string - the string with desired capabilities for the browser\n        recorder_ext - the option to enable the SBase Recorder extension\n        disable_cookies - the option to disable Cookies (May break things!)\n        disable_js - the option to disable JavaScript (May break websites!)\n        disable_csp - an option to disable Chrome's Content Security Policy\n        enable_ws - the option to enable the Web Security feature (Chrome)\n        enable_sync - the option to enable the Chrome Sync feature (Chrome)\n        use_auto_ext - the option to enable Chrome's Automation Extension\n        undetectable - the option to use an undetectable chromedriver\n        uc_cdp_events - capture CDP events in \"undetectable\" mode (Chrome)\n        uc_subprocess - use the undetectable chromedriver as a subprocess\n        log_cdp_events - capture {\"performance\": \"ALL\", \"browser\": \"ALL\"})\n        no_sandbox - the option to enable the \"No-Sandbox\" feature (Chrome)\n        disable_gpu - the option to enable Chrome's \"Disable GPU\" feature\n        headless1 - the option to use the older headless mode (Chromium)\n        headless2 - the option to use the newer headless mode (Chromium)\n        incognito - the option to enable Chrome's Incognito mode (Chrome)\n        guest_mode - the option to enable Chrome's Guest mode (Chrome)\n        dark_mode - the option to enable Chrome's Dark mode (Chrome)\n        devtools - the option to open Chrome's DevTools on start (Chrome)\n        remote_debug - the option to enable Chrome's Remote Debugger\n        enable_3d_apis - the option to enable WebGL and 3D APIs (Chrome)\n        swiftshader - the option to use Chrome's swiftshader (Chrome-only)\n        ad_block_on - the option to block ads from loading (Chromium-only)\n        host_resolver_rules - Configure host-resolver-rules (Chromium-only)\n        block_images - the option to block images from loading (Chrome)\n        do_not_track - indicate that websites should not track you (Chrome)\n        chromium_arg - the option to add a Chromium arg to Chrome/Edge\n        firefox_arg - the option to add a Firefox arg to Firefox runs\n        firefox_pref - the option to add a Firefox pref:value set (Firefox)\n        user_data_dir - Chrome's User Data Directory to use (Chrome-only)\n        extension_zip - A Chrome Extension ZIP file to use (Chrome-only)\n        extension_dir - A Chrome Extension folder to use (Chrome-only)\n        disable_features - the option to disable features on Chrome/Edge\n        binary_location - the path of the browser binary to use (Chromium)\n        driver_version - the chromedriver or uc_driver version to force\n        page_load_strategy - the option to change pageLoadStrategy (Chrome)\n        use_wire - Use selenium-wire webdriver instead of the selenium one\n        external_pdf - \"plugins.always_open_pdf_externally\": True. (Chrome)\n        is_mobile - the option to use the mobile emulator (Chrome-only)\n        d_width - the device width of the mobile emulator (Chrome-only)\n        d_height - the device height of the mobile emulator (Chrome-only)\n        d_p_r - the device pixel ratio of the mobile emulator (Chrome-only)\n        \"\"\"\n        self.__check_scope()\n        if self.browser == \"remote\" and self.servername == \"localhost\":\n            raise Exception(\n                'Cannot use \"remote\" browser driver on localhost!'\n                \" Did you mean to connect to a remote Grid server\"\n                \" such as BrowserStack or Sauce Labs?\"\n                ' If so, you must specify the \"server\" and \"port\"'\n                \" parameters on the command line! \"\n                \"Example: \"\n                \"--server=user:key@hub.browserstack.com --port=80\"\n            )\n        browserstack_ref = \"https://browserstack.com/automate/capabilities\"\n        sauce_labs_ref = (\n            \"https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/\"\n        )\n        if self.browser == \"remote\" and not (self.cap_file or self.cap_string):\n            raise Exception(\n                \"Need to specify a desired capabilities file when \"\n                'using \"--browser=remote\". Add \"--cap_file=FILE\". '\n                \"File should be in the Python format used by: \"\n                \"%s OR %s \\n\"\n                \"(See SeleniumBase/examples/capabilities/sample_cap_file_BS.py\"\n                \" and SeleniumBase/examples/capabilities/sample_cap_file_SL.py\"\n                \" for examples!)\"\n                % (browserstack_ref, sauce_labs_ref)\n            )\n        shortcuts = [\"dark\", \"guest\", \"locale\", \"mobile\", \"pls\", \"uc\", \"wire\"]\n        if kwargs:\n            for key in kwargs.keys():\n                if key not in shortcuts:\n                    raise TypeError(\"Unexpected keyword argument '%s'\" % key)\n        if browser is None:\n            browser = self.browser\n        browser_name = browser\n        if headless is None:\n            headless = self.headless\n        if locale_code is None:\n            locale_code = self.locale_code\n        if \"locale\" in kwargs and not locale_code:\n            locale_code = kwargs[\"locale\"]\n        if protocol is None:\n            protocol = self.protocol\n        if servername is None:\n            servername = self.servername\n        if port is None:\n            port = self.port\n        use_grid = False\n        if servername != \"localhost\":\n            # Use Selenium Grid (Use \"127.0.0.1\" for localhost Grid)\n            use_grid = True\n        proxy_string = proxy\n        if proxy_string is None:\n            proxy_string = self.proxy_string\n        if proxy_bypass_list is None:\n            proxy_bypass_list = self.proxy_bypass_list\n        if proxy_pac_url is None:\n            proxy_pac_url = self.proxy_pac_url\n        if multi_proxy is None:\n            multi_proxy = self.multi_proxy\n        user_agent = agent\n        if user_agent is None:\n            user_agent = self.user_agent\n        if recorder_ext is None:\n            recorder_ext = self.recorder_ext\n        if disable_cookies is None:\n            disable_cookies = self.disable_cookies\n        if disable_js is None:\n            disable_js = self.disable_js\n        if disable_csp is None:\n            disable_csp = self.disable_csp\n        if enable_ws is None:\n            enable_ws = self.enable_ws\n        if enable_sync is None:\n            enable_sync = self.enable_sync\n        if use_auto_ext is None:\n            use_auto_ext = self.use_auto_ext\n        if undetectable is None:\n            undetectable = self.undetectable\n        if uc_cdp_events is None:\n            uc_cdp_events = self.uc_cdp_events\n        if uc_subprocess is None:\n            uc_subprocess = self.uc_subprocess\n        if \"uc\" in kwargs and not undetectable:\n            undetectable = kwargs[\"uc\"]\n        if log_cdp_events is None:\n            log_cdp_events = self.log_cdp_events\n        if no_sandbox is None:\n            no_sandbox = self.no_sandbox\n        if disable_gpu is None:\n            disable_gpu = self.disable_gpu\n        if headless1 is None:\n            headless1 = self.headless1\n        if headless2 is None:\n            headless2 = self.headless2\n        if incognito is None:\n            incognito = self.incognito\n        if guest_mode is None:\n            guest_mode = self.guest_mode\n        if \"guest\" in kwargs and not guest_mode:\n            guest_mode = kwargs[\"guest\"]\n        if dark_mode is None:\n            dark_mode = self.dark_mode\n        if \"dark\" in kwargs and not dark_mode:\n            dark_mode = kwargs[\"dark\"]\n        if devtools is None:\n            devtools = self.devtools\n        if remote_debug is None:\n            remote_debug = self.remote_debug\n        if enable_3d_apis is None:\n            enable_3d_apis = self.enable_3d_apis\n        if swiftshader is None:\n            swiftshader = self._swiftshader\n        if ad_block_on is None:\n            ad_block_on = self.ad_block_on\n        if host_resolver_rules is None:\n            host_resolver_rules = self.host_resolver_rules\n        if block_images is None:\n            block_images = self.block_images\n        if do_not_track is None:\n            do_not_track = self.do_not_track\n        if chromium_arg is None:\n            chromium_arg = self.chromium_arg\n        if firefox_arg is None:\n            firefox_arg = self.firefox_arg\n        if firefox_pref is None:\n            firefox_pref = self.firefox_pref\n        if user_data_dir is None:\n            user_data_dir = self.user_data_dir\n        if extension_zip is None:\n            extension_zip = self.extension_zip\n        if extension_dir is None:\n            extension_dir = self.extension_dir\n        if disable_features is None:\n            disable_features = self.disable_features\n        if binary_location is None:\n            binary_location = self.binary_location\n        if driver_version is None:\n            driver_version = self.driver_version\n        if page_load_strategy is None:\n            page_load_strategy = self.page_load_strategy\n        if \"pls\" in kwargs and not page_load_strategy:\n            page_load_strategy = kwargs[\"pls\"]\n        if use_wire is None:\n            use_wire = self.use_wire\n        if \"wire\" in kwargs and not use_wire:\n            use_wire = kwargs[\"wire\"]\n        if external_pdf is None:\n            external_pdf = self.external_pdf\n        test_id = self.__get_test_id()\n        if cap_file is None:\n            cap_file = self.cap_file\n        if cap_string is None:\n            cap_string = self.cap_string\n        if is_mobile is None:\n            is_mobile = self.mobile_emulator\n        if \"mobile\" in kwargs and not is_mobile:\n            is_mobile = kwargs[\"mobile\"]\n        if d_width is None:\n            d_width = self.__device_width\n        if d_height is None:\n            d_height = self.__device_height\n        if d_p_r is None:\n            d_p_r = self.__device_pixel_ratio\n        if is_mobile and not user_agent:\n            # Use a Pixel user agent by default if not specified\n            user_agent = constants.Mobile.AGENT\n        valid_browsers = constants.ValidBrowsers.valid_browsers\n        if browser_name not in valid_browsers:\n            raise Exception(\n                \"Browser: {%s} is not a valid browser option. \"\n                \"Valid options = {%s}\" % (browser, valid_browsers)\n            )\n        # Launch a web browser\n        new_driver = browser_launcher.get_driver(\n            browser_name=browser_name,\n            headless=headless,\n            locale_code=locale_code,\n            use_grid=use_grid,\n            protocol=protocol,\n            servername=servername,\n            port=port,\n            proxy_string=proxy_string,\n            proxy_bypass_list=proxy_bypass_list,\n            proxy_pac_url=proxy_pac_url,\n            multi_proxy=multi_proxy,\n            user_agent=user_agent,\n            cap_file=cap_file,\n            cap_string=cap_string,\n            recorder_ext=recorder_ext,\n            disable_cookies=disable_cookies,\n            disable_js=disable_js,\n            disable_csp=disable_csp,\n            enable_ws=enable_ws,\n            enable_sync=enable_sync,\n            use_auto_ext=use_auto_ext,\n            undetectable=undetectable,\n            uc_cdp_events=uc_cdp_events,\n            uc_subprocess=uc_subprocess,\n            log_cdp_events=log_cdp_events,\n            no_sandbox=no_sandbox,\n            disable_gpu=disable_gpu,\n            headless1=headless1,\n            headless2=headless2,\n            incognito=incognito,\n            guest_mode=guest_mode,\n            dark_mode=dark_mode,\n            devtools=devtools,\n            remote_debug=remote_debug,\n            enable_3d_apis=enable_3d_apis,\n            swiftshader=swiftshader,\n            ad_block_on=ad_block_on,\n            host_resolver_rules=host_resolver_rules,\n            block_images=block_images,\n            do_not_track=do_not_track,\n            chromium_arg=chromium_arg,\n            firefox_arg=firefox_arg,\n            firefox_pref=firefox_pref,\n            user_data_dir=user_data_dir,\n            extension_zip=extension_zip,\n            extension_dir=extension_dir,\n            disable_features=disable_features,\n            binary_location=binary_location,\n            driver_version=driver_version,\n            page_load_strategy=page_load_strategy,\n            use_wire=use_wire,\n            external_pdf=external_pdf,\n            test_id=test_id,\n            mobile_emulator=is_mobile,\n            device_width=d_width,\n            device_height=d_height,\n            device_pixel_ratio=d_p_r,\n            browser=browser_name,\n        )\n        self._drivers_list.append(new_driver)\n        self._drivers_browser_map[new_driver] = browser_name\n        if switch_to:\n            self.driver = new_driver\n            self.browser = browser_name\n            if self.headless or self.headless2 or self.xvfb:\n                # Make sure the invisible browser window is big enough\n                width = settings.HEADLESS_START_WIDTH\n                height = settings.HEADLESS_START_HEIGHT\n                if self.browser != \"chrome\" and self.browser != \"edge\":\n                    try:\n                        self.driver.set_window_size(width, height)\n                        # self.wait_for_ready_state_complete()\n                    except Exception:\n                        # This shouldn't fail, but in case it does,\n                        # get safely through setUp() so that\n                        # WebDrivers can get closed during tearDown().\n                        pass\n            else:\n                width = settings.CHROME_START_WIDTH\n                height = settings.CHROME_START_HEIGHT\n                if self.is_chromium():\n                    try:\n                        if self.maximize_option:\n                            self.maximize_window()\n                            self.wait_for_ready_state_complete()\n                        else:\n                            pass  # Now handled in browser_launcher.py\n                            # self.driver.set_window_size(width, height)\n                    except Exception:\n                        pass  # Keep existing browser resolution\n                elif self.browser == \"firefox\":\n                    try:\n                        if self.maximize_option:\n                            self.maximize_window()\n                            self.wait_for_ready_state_complete()\n                        else:\n                            with suppress(Exception):\n                                self.driver.set_window_size(width, height)\n                    except Exception:\n                        pass  # Keep existing browser resolution\n                elif self.browser == \"safari\":\n                    if self.maximize_option:\n                        try:\n                            self.maximize_window()\n                            self.wait_for_ready_state_complete()\n                        except Exception:\n                            pass  # Keep existing browser resolution\n                    else:\n                        with suppress(Exception):\n                            self.driver.set_window_rect(10, 46, width, height)\n            if self.start_page and len(self.start_page) >= 4:\n                if page_utils.is_valid_url(self.start_page):\n                    self.open(self.start_page)\n                else:\n                    new_start_page = \"https://\" + self.start_page\n                    if page_utils.is_valid_url(new_start_page):\n                        self.__dont_record_open = True\n                        self.open(new_start_page)\n                        self.__dont_record_open = False\n        if undetectable:\n            if hasattr(new_driver, \"cdp\"):\n                self.cdp = new_driver.cdp\n            if hasattr(new_driver, \"uc_open\"):\n                self.uc_open = new_driver.uc_open\n            if hasattr(new_driver, \"uc_open_with_tab\"):\n                self.uc_open_with_tab = new_driver.uc_open_with_tab\n            if hasattr(new_driver, \"uc_open_with_reconnect\"):\n                self.uc_open_with_reconnect = new_driver.uc_open_with_reconnect\n            if hasattr(new_driver, \"uc_open_with_cdp_mode\"):\n                self.uc_open_with_cdp_mode = new_driver.uc_open_with_cdp_mode\n            if hasattr(new_driver, \"uc_open_with_disconnect\"):\n                self.uc_open_with_disconnect = (\n                    new_driver.uc_open_with_disconnect\n                )\n            if hasattr(new_driver, \"reconnect\"):\n                self.reconnect = new_driver.reconnect\n            if hasattr(new_driver, \"disconnect\"):\n                self.disconnect = new_driver.disconnect\n            if hasattr(new_driver, \"connect\"):\n                self.connect = new_driver.connect\n            if hasattr(new_driver, \"uc_click\"):\n                self.uc_click = new_driver.uc_click\n            if hasattr(new_driver, \"uc_gui_press_key\"):\n                self.uc_gui_press_key = new_driver.uc_gui_press_key\n            if hasattr(new_driver, \"uc_gui_press_keys\"):\n                self.uc_gui_press_keys = new_driver.uc_gui_press_keys\n            if hasattr(new_driver, \"uc_gui_write\"):\n                self.uc_gui_write = new_driver.uc_gui_write\n            if hasattr(new_driver, \"uc_gui_click_x_y\"):\n                self.uc_gui_click_x_y = new_driver.uc_gui_click_x_y\n            if hasattr(new_driver, \"uc_gui_click_captcha\"):\n                self.uc_gui_click_captcha = new_driver.uc_gui_click_captcha\n            if hasattr(new_driver, \"uc_gui_click_cf\"):\n                self.uc_gui_click_cf = new_driver.uc_gui_click_cf\n            if hasattr(new_driver, \"uc_gui_click_rc\"):\n                self.uc_gui_click_rc = new_driver.uc_gui_click_rc\n            if hasattr(new_driver, \"uc_gui_handle_captcha\"):\n                self.uc_gui_handle_captcha = new_driver.uc_gui_handle_captcha\n            if hasattr(new_driver, \"uc_gui_handle_cf\"):\n                self.uc_gui_handle_cf = new_driver.uc_gui_handle_cf\n            if hasattr(new_driver, \"uc_gui_handle_rc\"):\n                self.uc_gui_handle_rc = new_driver.uc_gui_handle_rc\n            if hasattr(new_driver, \"uc_switch_to_frame\"):\n                self.uc_switch_to_frame = new_driver.uc_switch_to_frame\n        return new_driver\n\n    def switch_to_driver(self, driver):\n        \"\"\"Switches control of the browser to the specified driver.\n        Also sets the self.driver variable to the specified driver.\n        You may need this if using self.get_new_driver() in your code.\"\"\"\n        self.__check_scope()\n        self.driver = driver\n        if self.driver in self._drivers_browser_map:\n            self.browser = self._drivers_browser_map[self.driver]\n        if self.__is_cdp_swap_needed():\n            self.cdp._swap_driver(self.driver)\n        self.bring_active_window_to_front()\n\n    def switch_to_default_driver(self):\n        \"\"\"Sets self.driver to the default/initial driver.\"\"\"\n        self.__check_scope()\n        self.driver = self._default_driver\n        if self.driver in self._drivers_browser_map:\n            self.browser = self._drivers_browser_map[self.driver]\n        if self.__is_cdp_swap_needed():\n            self.cdp._swap_driver(self.driver)\n        self.bring_active_window_to_front()\n\n    def save_screenshot(\n        self, name, folder=None, selector=None, by=\"css selector\"\n    ):\n        \"\"\"Saves a screenshot of the current page.\n        If no folder is specified, uses the folder where pytest was called.\n        The screenshot will include the entire page unless a selector is given.\n        If a provided selector is not found, then takes a full-page screenshot.\n        If the folder provided doesn't exist, it will get created.\n        The screenshot will be in PNG format: (*.png)\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.save_screenshot(name, folder=folder, selector=selector)\n            return\n        self.wait_for_ready_state_complete()\n        if selector and by:\n            selector, by = self.__recalculate_selector(selector, by)\n            if page_actions.is_element_present(self.driver, selector, by):\n                return page_actions.save_screenshot(\n                    self.driver, name, folder, selector, by\n                )\n        if self.recorder_mode:\n            url = self.get_current_url()\n            if url and len(url) > 0:\n                if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                    if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                        time_stamp = self.execute_script(\"return Date.now();\")\n                        origin = self.get_origin()\n                        if not folder:\n                            action = [\"s_scr\", name, origin, time_stamp]\n                        else:\n                            name_folder = [name, folder]\n                            action = [\"ss_tf\", name_folder, origin, time_stamp]\n                        self.__extra_actions.append(action)\n        return page_actions.save_screenshot(self.driver, name, folder)\n\n    def save_screenshot_to_logs(\n        self, name=None, selector=None, by=\"css selector\"\n    ):\n        \"\"\"Saves a screenshot of the current page to the \"latest_logs/\" folder.\n        Naming is automatic:\n            If NO NAME provided: \"_1_screenshot.png\", \"_2_screenshot.png\", etc.\n            If NAME IS provided, it becomes: \"_1_name.png\", \"_2_name.png\", etc.\n        The screenshot will include the entire page unless a selector is given.\n        If a provided selector is not found, then takes a full-page screenshot.\n        (The last_page / failure screenshot is always \"screenshot.png\")\n        The screenshot will be in PNG format.\"\"\"\n        if not self.__is_cdp_swap_needed():\n            self.wait_for_ready_state_complete()\n        test_logpath = os.path.join(self.log_path, self.__get_test_id())\n        self.__create_log_path_as_needed(test_logpath)\n        if name:\n            name = str(name)\n        self.__screenshot_count += 1\n        if not name or len(name) == 0:\n            name = \"_%s_screenshot.png\" % self.__screenshot_count\n        else:\n            pre_name = \"_%s_\" % self.__screenshot_count\n            if len(name) >= 4 and name[-4:].lower() == \".png\":\n                name = name[:-4]\n                if len(name) == 0:\n                    name = \"screenshot\"\n            name = \"%s%s.png\" % (pre_name, name)\n        if selector and by:\n            selector, by = self.__recalculate_selector(selector, by)\n            if page_actions.is_element_present(self.driver, selector, by):\n                if self.__is_cdp_swap_needed():\n                    selector = self.convert_to_css_selector(selector, by=by)\n                    return self.cdp.save_screenshot(\n                        name, folder=test_logpath, selector=selector\n                    )\n                return page_actions.save_screenshot(\n                    self.driver, name, test_logpath, selector, by\n                )\n        if self.recorder_mode:\n            url = self.get_current_url()\n            if url and len(url) > 0:\n                if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                    if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                        time_stamp = self.execute_script(\"return Date.now();\")\n                        origin = self.get_origin()\n                        action = [\"ss_tl\", \"\", origin, time_stamp]\n                        self.__extra_actions.append(action)\n        sb_config._has_logs = True\n        if self.__is_cdp_swap_needed():\n            return self.cdp.save_screenshot(name, folder=test_logpath)\n        return page_actions.save_screenshot(self.driver, name, test_logpath)\n\n    def save_as_pdf(self, name, folder=None):\n        \"\"\"Same as self.print_to_pdf()\"\"\"\n        return self.print_to_pdf(name, folder=folder)\n\n    def save_as_pdf_to_logs(self, name=None):\n        \"\"\"Saves the page as a PDF to the \"latest_logs/\" folder.\n        Naming is automatic:\n            If NO NAME provided: \"_1_PDF.pdf\", \"_2_PDF.pdf\", etc.\n            If NAME IS provided, then: \"_1_name.pdf\", \"_2_name.pdf\", etc.\"\"\"\n        if not self.__is_cdp_swap_needed():\n            self.wait_for_ready_state_complete()\n        test_logpath = os.path.join(self.log_path, self.__get_test_id())\n        self.__create_log_path_as_needed(test_logpath)\n        if name:\n            name = str(name)\n        self.__saved_pdf_count += 1\n        if not name or len(name) == 0:\n            name = \"_%s_PDF.pdf\" % self.__saved_pdf_count\n        else:\n            pre_name = \"_%s_\" % self.__saved_pdf_count\n            if len(name) >= 4 and name[-4:].lower() == \".pdf\":\n                name = name[:-4]\n                if len(name) == 0:\n                    name = \"PDF\"\n            name = \"%s%s.pdf\" % (pre_name, name)\n        if self.recorder_mode:\n            url = self.get_current_url()\n            if url and len(url) > 0:\n                if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                    if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                        time_stamp = self.execute_script(\"return Date.now();\")\n                        origin = self.get_origin()\n                        action = [\"pdftl\", \"\", origin, time_stamp]\n                        self.__extra_actions.append(action)\n        sb_config._has_logs = True\n        if self.__is_cdp_swap_needed():\n            return self.cdp.print_to_pdf(name, folder=test_logpath)\n        return self.print_to_pdf(name, test_logpath)\n\n    def save_page_source_to_logs(self, name=None):\n        \"\"\"Saves the page HTML to the \"latest_logs/\" folder.\n        Naming is automatic:\n            If NO NAME provided: \"_1_source.html\", \"_2_source.html\", etc.\n            If NAME IS provided, then: \"_1_name.html\", \"_2_name.html\", etc.\n        (The last_page / failure page_source is always \"page_source.html\")\"\"\"\n        if not self.__is_cdp_swap_needed():\n            self.wait_for_ready_state_complete()\n        test_logpath = os.path.join(self.log_path, self.__get_test_id())\n        self.__create_log_path_as_needed(test_logpath)\n        if name:\n            name = str(name)\n        self.__page_source_count += 1\n        if not name or len(name) == 0:\n            name = \"_%s_source.html\" % self.__page_source_count\n        else:\n            pre_name = \"_%s_\" % self.__page_source_count\n            if len(name) >= 4 and name[-4:].lower() == \".html\":\n                name = name[:-4]\n                if len(name) == 0:\n                    name = \"source\"\n            name = \"%s%s.html\" % (pre_name, name)\n        if self.recorder_mode:\n            url = self.get_current_url()\n            if url and len(url) > 0:\n                if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                    if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                        time_stamp = self.execute_script(\"return Date.now();\")\n                        origin = self.get_origin()\n                        action = [\"spstl\", \"\", origin, time_stamp]\n                        self.__extra_actions.append(action)\n        sb_config._has_logs = True\n        return page_actions.save_page_source(self.driver, name, test_logpath)\n\n    def save_data_to_logs(self, data, file_name=None):\n        \"\"\"Saves data to the \"latest_logs/\" data folder of the current test.\n        If no file_name, file_name becomes: \"data_1.txt\", \"data_2.txt\", etc.\n        Useful variables for getting the \"latest_logs/\" or test data folders:\n            self.log_path OR self.log_abspath  (For the top-level folder)\n            self.data_path OR self.data_abspath  (Individual test folders)\n        If a file_name is given with no extension, adds \".txt\" to the end.\"\"\"\n        test_logpath = os.path.join(self.log_path, self.__get_test_id())\n        self.__create_log_path_as_needed(test_logpath)\n        if file_name:\n            file_name = str(file_name)\n        if not file_name or len(file_name) == 0:\n            self.__logs_data_count += 1\n            file_name = \"data_%s.txt\" % self.__logs_data_count\n        elif \".\" not in file_name:\n            file_name = \"%s.txt\" % file_name\n        self.__last_data_file = file_name\n        sb_config._has_logs = True\n        destination_folder = test_logpath\n        page_utils._save_data_as(data, destination_folder, file_name)\n\n    def append_data_to_logs(self, data, file_name=None):\n        \"\"\"Saves data to the \"latest_logs/\" folder of the current test.\n        If no file_name, file_name becomes the last data file used in\n            save_data_to_logs() or append_data_to_logs().\n        If neither method was called before, creates \"data_1.txt\".\"\"\"\n        test_logpath = os.path.join(self.log_path, self.__get_test_id())\n        self.__create_log_path_as_needed(test_logpath)\n        if file_name:\n            file_name = str(file_name)\n        if (not file_name or len(file_name) == 0) and self.__last_data_file:\n            file_name = self.__last_data_file\n        elif not file_name or len(file_name) == 0:\n            if self.__logs_data_count == 0:\n                self.__logs_data_count += 1\n            file_name = \"data_%s.txt\" % self.__logs_data_count\n        elif \".\" not in file_name:\n            file_name = \"%s.txt\" % file_name\n        self.__last_data_file = file_name\n        sb_config._has_logs = True\n        destination_folder = test_logpath\n        page_utils._append_data_to_file(data, destination_folder, file_name)\n\n    def save_page_source(self, name, folder=None):\n        \"\"\"Saves the page HTML to the current directory (or given subfolder).\n        If the folder specified doesn't exist, it will get created.\n        @Params\n        name - The file name to save the current page's HTML to.\n        folder - The folder to save the file to. (Default = current folder)\"\"\"\n        if not self.__is_cdp_swap_needed():\n            self.wait_for_ready_state_complete()\n        return page_actions.save_page_source(self.driver, name, folder)\n\n    def save_cookies(self, name=\"cookies.txt\"):\n        \"\"\"Saves the page cookies to the \"saved_cookies\" folder.\"\"\"\n        self.wait_for_ready_state_complete()\n        cookies = self.driver.get_cookies()\n        json_cookies = json.dumps(cookies)\n        if name.endswith(\"/\"):\n            raise Exception(\"Invalid filename for Cookies!\")\n        if \"/\" in name:\n            name = name.split(\"/\")[-1]\n        if \"\\\\\" in name:\n            name = name.split(\"\\\\\")[-1]\n        if len(name) < 1:\n            raise Exception(\"Filename for Cookies is too short!\")\n        if not name.endswith(\".txt\"):\n            name = name + \".txt\"\n        folder = constants.SavedCookies.STORAGE_FOLDER\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, folder)\n        if not os.path.exists(file_path):\n            os.makedirs(file_path)\n        cookies_file_path = os.path.join(file_path, name)\n        cookies_file = open(cookies_file_path, mode=\"w+\", encoding=\"utf-8\")\n        cookies_file.writelines(json_cookies)\n        cookies_file.close()\n\n    def load_cookies(self, name=\"cookies.txt\", expiry=False):\n        \"\"\"\n        Loads the page cookies from the \"saved_cookies\" folder.\n        Usage for setting expiry:\n        If expiry == 0 or False: Delete \"expiry\".\n        If expiry is True: Set \"expiry\" to 24 hours in the future.\n        If expiry == -1 (or < 0): Do not modify \"expiry\".\n        If expiry > 0: Set \"expiry\" to expiry minutes in the future.\n        \"\"\"\n        cookies = self.get_saved_cookies(name)\n        self.wait_for_ready_state_complete()\n        origin = self.get_origin()\n        trim_origin = origin.split(\"://\")[-1]\n        for cookie in cookies:\n            if \"domain\" in cookie:\n                if cookie[\"domain\"] not in origin:\n                    cookie[\"domain\"] = trim_origin\n            if \"expiry\" in cookie and (not expiry or expiry == 0):\n                del cookie[\"expiry\"]\n            elif expiry is True:\n                cookie[\"expiry\"] = int(time.time()) + 86400\n            elif isinstance(expiry, (int, float)) and expiry < 0:\n                pass\n            elif isinstance(expiry, (int, float)) and expiry > 0:\n                cookie[\"expiry\"] = int(time.time()) + int(expiry * 60.0)\n            self.driver.add_cookie(cookie)\n\n    def delete_all_cookies(self):\n        \"\"\"Deletes all cookies in the web browser.\n        Does NOT delete the saved cookies file.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.clear_cookies()\n            return\n        self.wait_for_ready_state_complete()\n        self.driver.delete_all_cookies()\n        if self.recorder_mode:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"d_a_c\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def delete_saved_cookies(self, name=\"cookies.txt\"):\n        \"\"\"Deletes the cookies file from the \"saved_cookies\" folder.\n        Does NOT delete the cookies from the web browser.\"\"\"\n        if name.endswith(\"/\"):\n            raise Exception(\"Invalid filename for Cookies!\")\n        if \"/\" in name:\n            name = name.split(\"/\")[-1]\n        if len(name) < 1:\n            raise Exception(\"Filename for Cookies is too short!\")\n        if not name.endswith(\".txt\"):\n            name = name + \".txt\"\n        folder = constants.SavedCookies.STORAGE_FOLDER\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, folder)\n        cookies_file_path = os.path.join(file_path, name)\n        if os.path.exists(cookies_file_path):\n            if cookies_file_path.endswith(\".txt\"):\n                os.remove(cookies_file_path)\n\n    def get_saved_cookies(self, name=\"cookies.txt\"):\n        \"\"\"Gets the page cookies from the \"saved_cookies\" folder.\"\"\"\n        if name.endswith(\"/\"):\n            raise Exception(\"Invalid filename for Cookies!\")\n        if \"/\" in name:\n            name = name.split(\"/\")[-1]\n        if \"\\\\\" in name:\n            name = name.split(\"\\\\\")[-1]\n        if len(name) < 1:\n            raise Exception(\"Filename for Cookies is too short!\")\n        if not name.endswith(\".txt\"):\n            name = name + \".txt\"\n        folder = constants.SavedCookies.STORAGE_FOLDER\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, folder)\n        cookies_file_path = os.path.join(file_path, name)\n        json_cookies = None\n        with open(cookies_file_path, \"r\") as f:\n            json_cookies = f.read().strip()\n        return json.loads(json_cookies)\n\n    def get_cookie(self, name):\n        self.__check_scope()\n        self._check_browser()\n        return self.driver.get_cookie(name)\n\n    def get_cookies(self):\n        self.__check_scope()\n        self._check_browser()\n        return self.driver.get_cookies()\n\n    def get_cookie_string(self):\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_cookie_string()\n        self._check_browser()\n        return self.execute_script(\"return document.cookie;\")\n\n    def add_cookie(self, cookie_dict, expiry=False):\n        \"\"\"Usage examples:\n        self.add_cookie({'name': 'foo', 'value': 'bar'})\n        self.add_cookie({'name': 'foo', 'value': 'bar', 'path': '/'})\n        self.add_cookie({'name': 'foo', 'value': 'bar', 'secure': True})\n        self.add_cookie({'name': 'foo', 'value': 'bar', 'sameSite': 'Strict'})\n        Usage for setting expiry:\n        If expiry == 0 or False: Delete \"expiry\".\n        If expiry is True: Set \"expiry\" to 24 hours in the future.\n        If expiry == -1 (or < 0): Do not modify \"expiry\".\n        If expiry > 0: Set \"expiry\" to expiry minutes in the future.\n        \"\"\"\n        self.__check_scope()\n        self._check_browser()\n        cookie = cookie_dict\n        if \"domain\" in cookie:\n            origin = self.get_origin()\n            trim_origin = origin.split(\"://\")[-1]\n            if cookie[\"domain\"] not in origin:\n                cookie[\"domain\"] = trim_origin\n        if \"expiry\" in cookie and (not expiry or expiry == 0):\n            del cookie[\"expiry\"]\n        elif expiry is True:\n            cookie[\"expiry\"] = int(time.time()) + 86400\n        elif isinstance(expiry, (int, float)) and expiry < 0:\n            pass\n        elif isinstance(expiry, (int, float)) and expiry > 0:\n            cookie[\"expiry\"] = int(time.time()) + int(expiry * 60.0)\n        self.driver.add_cookie(cookie_dict)\n\n    def add_cookies(self, cookies, expiry=False):\n        \"\"\"\n        Usage for setting expiry:\n        If expiry == 0 or False: Delete \"expiry\".\n        If expiry is True: Set \"expiry\" to 24 hours in the future.\n        If expiry == -1 (or < 0): Do not modify \"expiry\".\n        If expiry > 0: Set \"expiry\" to expiry minutes in the future.\n        \"\"\"\n        self.__check_scope()\n        self._check_browser()\n        origin = self.get_origin()\n        trim_origin = origin.split(\"://\")[-1]\n        for cookie in cookies:\n            if \"domain\" in cookie:\n                if cookie[\"domain\"] not in origin:\n                    cookie[\"domain\"] = trim_origin\n            if \"expiry\" in cookie and (not expiry or expiry == 0):\n                del cookie[\"expiry\"]\n            elif expiry is True:\n                cookie[\"expiry\"] = int(time.time()) + 86400\n            elif isinstance(expiry, (int, float)) and expiry < 0:\n                pass\n            elif isinstance(expiry, (int, float)) and expiry > 0:\n                cookie[\"expiry\"] = int(time.time()) + int(expiry * 60.0)\n            self.driver.add_cookie(cookie)\n\n    def __set_esc_skip(self):\n        if getattr(self, \"esc_end\", None):\n            script = (\n                \"\"\"document.onkeydown = function(evt) {\n                    evt = evt || window.event;\n                    var isEscape = false;\n                    if (\"key\" in evt) {\n                        isEscape = (evt.key === \"Escape\" || evt.key === \"Esc\");\n                    } else {\n                        isEscape = (evt.keyCode === 27);\n                    }\n                    if (isEscape) {\n                        document.sb_esc_end = 'yes';\n                    }\n                };\"\"\"\n            )\n            self.execute_script(script)\n\n    def __skip_if_esc(self):\n        if getattr(self, \"esc_end\", None):\n            if self.execute_script(\"return document.sb_esc_end;\") == \"yes\":\n                self.skip()\n\n    def wait_for_ready_state_complete(self, timeout=None):\n        \"\"\"Waits for the \"readyState\" of the page to be \"complete\".\n        Returns True when the method completes.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        self.__skip_if_esc()\n        if not timeout:\n            timeout = settings.EXTREME_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.EXTREME_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        js_utils.wait_for_ready_state_complete(self.driver, timeout)\n        self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)\n        if self.js_checking_on:\n            self.assert_no_js_errors()\n        self.__ad_block_as_needed()\n        self.__disable_beforeunload_as_needed()\n        if (\n            self.page_load_strategy == \"none\"\n            and getattr(settings, \"SKIP_JS_WAITS\", None)\n        ):\n            time.sleep(0.01)\n            if self.undetectable:\n                time.sleep(0.035)\n        self.__set_esc_skip()\n        return True\n\n    def wait_for_angularjs(self, timeout=None, **kwargs):\n        \"\"\"Waits for Angular components of the page to finish loading.\n        Returns True when the method completes.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        js_utils.wait_for_angularjs(self.driver, timeout, **kwargs)\n        return True\n\n    def sleep(self, seconds):\n        self.__check_scope()\n        if not getattr(sb_config, \"time_limit\", None):\n            time.sleep(seconds)\n        elif seconds < 0.4:\n            shared_utils.check_if_time_limit_exceeded()\n            time.sleep(seconds)\n            shared_utils.check_if_time_limit_exceeded()\n        else:\n            start_ms = time.time() * 1000.0\n            stop_ms = start_ms + (seconds * 1000.0)\n            for x in range(int(seconds * 5)):\n                shared_utils.check_if_time_limit_exceeded()\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.2)\n        if self.recorder_mode and getattr(sb_config, \"record_sleep\", None):\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"sleep\", seconds, origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def install_addon(self, xpi_file):\n        \"\"\"Installs a Firefox add-on instantly at run-time.\n        @Params\n        xpi_file - A file archive in .xpi format.\"\"\"\n        self.wait_for_ready_state_complete()\n        if self.browser != \"firefox\":\n            raise Exception(\n                \"install_addon(xpi_file) is for Firefox ONLY!\\n\"\n                \"To load a Chrome extension, use the comamnd-line:\\n\"\n                \"--extension_zip=CRX_FILE  OR  --extension_dir=DIR\"\n            )\n        xpi_path = os.path.abspath(xpi_file)\n        self.driver.install_addon(xpi_path, temporary=True)\n\n    def activate_jquery(self):\n        \"\"\"If \"jQuery is not defined\", use this method to activate it for use.\n        This happens because jQuery is not always defined on web sites.\"\"\"\n        self.wait_for_ready_state_complete()\n        js_utils.activate_jquery(self.driver)\n        self.wait_for_ready_state_complete()\n\n    def activate_demo_mode(self):\n        self.demo_mode = True\n        if self.recorder_mode:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"a_d_m\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def deactivate_demo_mode(self):\n        self.demo_mode = False\n        if self.recorder_mode:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"d_d_m\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def activate_design_mode(self):\n        # Activate Chrome's Design Mode, which lets you edit a site directly.\n        # See: https://twitter.com/sulco/status/1177559150563344384\n        self.wait_for_ready_state_complete()\n        script = \"\"\"document.designMode = 'on';\"\"\"\n        self.execute_script(script)\n\n    def deactivate_design_mode(self, url=None):\n        # Deactivate Chrome's Design Mode.\n        self.wait_for_ready_state_complete()\n        script = \"\"\"document.designMode = 'off';\"\"\"\n        self.execute_script(script)\n\n    def activate_cdp_mode(self, url=None, **kwargs):\n        \"\"\"Activate CDP Mode with the URL and kwargs.\"\"\"\n        if getattr(self.driver, \"_is_using_uc\", None):\n            if self.__is_cdp_swap_needed():\n                return  # CDP Mode is already active\n            if not self.is_connected():\n                self.driver.connect()\n            current_url = self.get_current_url()\n            if not current_url.startswith((\"about\", \"data\", \"chrome\")):\n                self.driver.get(\"about:blank\")\n            self.driver.uc_open_with_cdp_mode(url, **kwargs)\n        else:\n            self.get_new_driver(undetectable=True)\n            self.driver.uc_open_with_cdp_mode(url, **kwargs)\n        self.cdp = self.driver.cdp\n        if hasattr(self.cdp, \"solve_captcha\"):\n            self.solve_captcha = self.cdp.solve_captcha\n        if hasattr(self.cdp, \"click_captcha\"):\n            self.click_captcha = self.cdp.click_captcha\n        if hasattr(self.cdp, \"find_element_by_text\"):\n            self.find_element_by_text = self.cdp.find_element_by_text\n        if getattr(self.driver, \"_is_using_auth\", None):\n            with suppress(Exception):\n                self.cdp.loop.run_until_complete(self.cdp.page.wait(0.25))\n        self.undetectable = True\n\n    def activate_recorder(self):\n        \"\"\"Activate Recorder Mode on the newest tab / window.\"\"\"\n        from seleniumbase.js_code.recorder_js import recorder_js\n\n        if not self.is_chromium():\n            if not is_linux:\n                c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n                c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n                cr = colorama.Style.RESET_ALL\n                sc = c1 + \"Selenium\" + c2 + \"Base\" + cr\n            raise Exception(\n                \"The %s Recorder is for Chromium only!\\n\"\n                \"    (Supported browsers: Chrome and Edge)\" % sc\n            )\n        url = self.get_current_url()\n        if url.startswith((\"data:\", \"about:\", \"chrome:\", \"edge:\")):\n            message = (\n                \"The URL in Recorder-Mode cannot start with: \"\n                '\"data:\", \"about:\", \"chrome:\", or \"edge:\"!'\n            )\n            print(\"\\n\" + message)\n            return\n        if self.recorder_ext and not self.undetectable:\n            return  # The Recorder extension is already active\n        self.switch_to_newest_tab()\n        with suppress(Exception):\n            recorder_on = self.get_session_storage_item(\"recorder_activated\")\n            if not recorder_on == \"yes\":\n                self.execute_script(recorder_js)\n            self.recorder_mode = True\n            message = \"Recorder Mode ACTIVE. [ESC]: Pause. [~`]: Resume.\"\n            print(\"\\n\" + message)\n            p_msg = \"Recorder Mode ACTIVE.<br>[ESC]: Pause. [~`]: Resume.\"\n            self.post_message(p_msg, pause=False, style=\"error\")\n\n    def __current_url_is_recordable(self):\n        url = self.get_current_url()\n        if url and len(url) > 0:\n            if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                return True\n        return False\n\n    def save_recorded_actions(self):\n        \"\"\"(When using Recorder Mode, use this method if you plan on\n            navigating to a different domain/origin in the same tab.)\n        This method saves recorded actions from the active tab so that\n        a complete recording can be exported as a SeleniumBase file at the\n        end of the test. This is only needed in special cases because most\n        actions that result in a new origin, (such as clicking on a link),\n        should automatically open a new tab while Recorder Mode is enabled.\"\"\"\n        if self.driver is None:\n            return\n        url = self.get_current_url()\n        if url and len(url) > 0:\n            if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                origin = self.get_origin()\n                self.__origins_to_save.append(origin)\n                tab_actions = self.__get_recorded_actions_on_active_tab()\n                for n in range(len(tab_actions)):\n                    if (\n                        n > 2\n                        and tab_actions[n - 2][0] == \"sw_fr\"\n                        and tab_actions[n - 1][0] == \"sk_fo\"\n                        and tab_actions[n][0] != \"_url_\"\n                    ):\n                        origin = tab_actions[n - 2][2]\n                        time_stamp = str(int(tab_actions[n][3]) - 1)\n                        new_action = [\"sw_pf\", \"\", origin, time_stamp]\n                        tab_actions.append(new_action)\n                self.__actions_to_save.append(tab_actions)\n\n    def __get_recorded_actions_on_active_tab(self):\n        url = self.driver.current_url\n        if url.startswith((\"data:\", \"about:\", \"chrome:\", \"edge:\")):\n            return []\n        self.__origins_to_save.append(self.get_origin())\n        actions = self.get_session_storage_item(\"recorded_actions\")\n        if actions:\n            actions = json.loads(actions)\n            return actions\n        else:\n            return []\n\n    def __process_recorded_actions(self):\n        \"\"\"Generates code after the SeleniumBase Recorder runs.\"\"\"\n        if self.driver is None:\n            return\n        from seleniumbase.core import recorder_helper\n\n        raw_actions = []  # All raw actions from sessionStorage\n        srt_actions = []\n        sb_actions = []\n        action_dict = {}\n        for window in self.driver.window_handles:\n            self.switch_to_window(window)\n            tab_actions = self.__get_recorded_actions_on_active_tab()\n            for n in range(len(tab_actions)):\n                if (\n                    n > 2\n                    and tab_actions[n - 2][0] == \"sw_fr\"\n                    and tab_actions[n - 1][0] == \"sk_fo\"\n                    and tab_actions[n][0] != \"_url_\"\n                ):\n                    origin = tab_actions[n - 2][2]\n                    time_stamp = str(int(tab_actions[n][3]) - 1)\n                    new_action = [\"sw_pf\", \"\", origin, time_stamp]\n                    tab_actions.append(new_action)\n            for action in tab_actions:\n                if action not in raw_actions:\n                    raw_actions.append(action)\n        for tab_actions in self.__actions_to_save:\n            for action in tab_actions:\n                if action not in raw_actions:\n                    raw_actions.append(action)\n        for action in self.__extra_actions:\n            if action not in raw_actions:\n                raw_actions.append(action)\n        for action in raw_actions:\n            if int(action[3]) < int(self.__js_start_time):\n                continue\n            # Use key for sorting and preventing duplicates\n            key = str(action[3]) + \"-\" + str(action[0])\n            action_dict[key] = action\n        for key in sorted(action_dict):\n            # print(action_dict[key])  # For debugging purposes\n            srt_actions.append(action_dict[key])\n        for n in range(len(srt_actions)):\n            if srt_actions[n][0] == \"sk_fo\":\n                srt_actions[n][0] = \"sk_op\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 0\n                and srt_actions[n - 1][0] == \"sk_op\"\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"c_box\"\n                and n > 0\n                and (\n                    (\n                        srt_actions[n - 1][0] == \"click\"\n                        and int(srt_actions[n][3]) - int(srt_actions[n - 1][3])\n                        < 16\n                    )\n                    or (\n                        srt_actions[n - 1][0] == \"js_cl\"\n                        and int(srt_actions[n][3]) - int(srt_actions[n - 1][3])\n                        < 42\n                    )\n                    or (\n                        srt_actions[n - 1][0] == \"jq_cl\"\n                        and int(srt_actions[n][3]) - int(srt_actions[n - 1][3])\n                        < 440\n                    )\n                )\n            ):\n                srt_actions[n][0] = \"_skip\"\n                if srt_actions[n - 1][0] == \"click\":\n                    srt_actions[n - 1][0] = \"ch_cl\"\n        for n in range(len(srt_actions)):\n            if (\n                n > 0\n                and srt_actions[n - 1][0] == \"c_box\"\n                and (\n                    srt_actions[n][0] == \"click\"\n                    or srt_actions[n][0] == \"js_cl\"\n                    or srt_actions[n][0] == \"jq_cl\"\n                )\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 16\n                )\n            ):\n                srt_actions[n - 1][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 1\n                and srt_actions[n - 1][0] == \"_skip\"\n                and srt_actions[n - 2][0] == \"sk_op\"\n                and srt_actions[n][2] == srt_actions[n - 1][2]\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 0\n                and (\n                    srt_actions[n - 1][0] == \"click\"\n                    or srt_actions[n - 1][0] == \"js_cl\"\n                    or srt_actions[n - 1][0] == \"js_ca\"\n                    or srt_actions[n - 1][0] == \"jq_cl\"\n                    or srt_actions[n - 1][0] == \"jq_ca\"\n                )\n            ):\n                sel1 = srt_actions[n - 1][1]\n                url1 = srt_actions[n - 1][2]\n                if (\n                    srt_actions[n - 1][0] == \"js_cl\"\n                    or srt_actions[n - 1][0] == \"js_ca\"\n                    or srt_actions[n - 1][0] == \"jq_cl\"\n                    or srt_actions[n - 1][0] == \"jq_ca\"\n                ):\n                    url1 = srt_actions[n - 1][2][0]\n                if url1.endswith(\"/#/\"):\n                    url1 = url1[:-3]\n                elif url1.endswith(\"/\"):\n                    url1 = url1[:-1]\n                url2 = srt_actions[n][2]\n                if url2.endswith(\"/#/\"):\n                    url2 = url1[:-3]\n                elif url2.endswith(\"/\"):\n                    url2 = url2[:-1]\n                if (\n                    url1 == url2\n                    or url1 == url2.replace(\"www.\", \"\")\n                    or url1 == url2.replace(\"https://\", \"http://\")\n                    or sel1.split(\" \")[-1].startswith(\"a[href=\")\n                    or (len(url1) > 0\n                        and (url2.startswith(url1) or \"?search\" in url1)\n                        and (int(srt_actions[n][3]) - int(\n                            srt_actions[n - 1][3]) < 6500))\n                ):\n                    srt_actions[n][0] = \"f_url\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 0\n                and (\n                    srt_actions[n - 1][0] == \"begin\"\n                    or srt_actions[n - 1][0] == \"_url_\"\n                )\n            ):\n                url1 = srt_actions[n - 1][2]\n                if url1.endswith(\"/#/\"):\n                    url1 = url1[:-3]\n                elif url1.endswith(\"/\"):\n                    url1 = url1[:-1]\n                url2 = srt_actions[n][2]\n                if url2.endswith(\"/#/\"):\n                    url2 = url1[:-3]\n                elif url2.endswith(\"/\"):\n                    url2 = url2[:-1]\n                if url1.replace(\"www.\", \"\") == url2.replace(\"www.\", \"\"):\n                    srt_actions[n - 1][0] = \"_skip\"\n                elif url1.replace(\"http://\", \"https://\") == url2:\n                    srt_actions[n - 1][0] = \"_skip\"\n                elif url2.startswith(url1):\n                    srt_actions[n][0] = \"f_url\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"input\"\n                and n > 0\n                and srt_actions[n - 1][0] == \"input\"\n                and srt_actions[n - 1][2] == \"\"\n            ):\n                srt_actions[n - 1][0] = \"_skip\"\n            elif (\n                srt_actions[n][0] == \"input\"\n                and n > 1\n                and srt_actions[n - 2][0] == \"input\"\n                and srt_actions[n - 1][0] == \"submi\"\n                and srt_actions[n - 2][1].startswith(\"textarea\")\n                and srt_actions[n - 2][1] == srt_actions[n][1]\n            ):\n                srt_actions[n - 2][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 0\n                and (\n                    srt_actions[n - 1][0] == \"click\"\n                    or srt_actions[n - 1][0] == \"js_cl\"\n                    or srt_actions[n - 1][0] == \"js_ca\"\n                    or srt_actions[n - 1][0] == \"jq_cl\"\n                    or srt_actions[n - 1][0] == \"jq_ca\"\n                    or srt_actions[n - 1][0] == \"input\"\n                )\n                and int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 6500\n            ):\n                if (\n                    srt_actions[n - 1][0] == \"click\"\n                    or srt_actions[n - 1][0] == \"js_cl\"\n                    or srt_actions[n - 1][0] == \"js_ca\"\n                    or srt_actions[n - 1][0] == \"jq_cl\"\n                    or srt_actions[n - 1][0] == \"jq_ca\"\n                ):\n                    if srt_actions[n - 1][1].startswith((\"input\", \"button\")):\n                        srt_actions[n][0] = \"f_url\"\n                elif srt_actions[n - 1][0] == \"input\":\n                    if srt_actions[n - 1][2].endswith(\"\\n\"):\n                        srt_actions[n][0] = \"f_url\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"dbclk\"\n                and n > 1\n                and srt_actions[n - 2][0] == \"click\"\n                and srt_actions[n - 1][0] == \"click\"\n                and srt_actions[n - 2][1] == srt_actions[n - 1][1]\n            ):\n                srt_actions[n - 2][0] = \"_skip\"\n                srt_actions[n - 1][0] = \"_skip\"\n                srt_actions[n][1] = srt_actions[n - 1][1]\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"dbclk\"\n                and srt_actions[n][1] == \"\"\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"cho_f\"\n                and n > 0\n                and srt_actions[n - 1][0] == \"chfil\"\n            ):\n                srt_actions[n - 1][0] = \"_skip\"\n                srt_actions[n][2] = srt_actions[n - 1][1][1]\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"input\"\n                and n > 0\n                and srt_actions[n - 1][0] == \"e_mfa\"\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 0\n                and (\n                    srt_actions[n - 1][0] == \"submi\"\n                    or srt_actions[n - 1][0] == \"e_mfa\"\n                )\n            ):\n                srt_actions[n][0] = \"f_url\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 1\n                and srt_actions[n - 1][0] == \"_url_\"\n                and srt_actions[n - 2][0] == \"o_url\"\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 4800\n                )\n            ):\n                srt_actions[n][0] = \"f_url\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 0\n                and (\n                    srt_actions[n - 1][0] == \"click\"\n                    or srt_actions[n - 1][0] == \"js_cl\"\n                    or srt_actions[n - 1][0] == \"js_ca\"\n                    or srt_actions[n - 1][0] == \"jq_cl\"\n                    or srt_actions[n - 1][0] == \"jq_ca\"\n                )\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 5200\n                )\n            ):\n                srt_actions[n][0] = \"f_url\"\n        for n in range(len(srt_actions)):\n            if (\n                n > 1\n                and srt_actions[n][0] == \"f_url\"\n                and srt_actions[n - 1][0] == \"_url_\"\n            ):\n                url0 = srt_actions[n][2]\n                url1 = srt_actions[n - 1][2]\n                if url0.endswith(\"/\"):\n                    url0 = url0[0:-1]\n                if url1.endswith(\"/\"):\n                    url1 = url1[0:-1]\n                url0 = url0.replace(\"http://\", \"https://\")\n                url0 = url0.replace(\"://www.\", \"://\")\n                url1 = url1.replace(\"http://\", \"https://\")\n                url1 = url1.replace(\"://www.\", \"://\")\n                if url0 == url1:\n                    srt_actions[n][0] = \"_url_\"\n                    srt_actions[n - 1][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"f_url\"\n                and \"?q=\" in srt_actions[n][2]\n                and \"&\" in srt_actions[n][2]\n                and srt_actions[n][2].find(\"?q=\") < srt_actions[n][2].find(\"&\")\n            ):\n                srt_actions[n][2] = srt_actions[n][2].split(\"&\")[0]\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 2\n                and srt_actions[n - 1][0] == \"_skip\"\n                and srt_actions[n - 2][0] == \"o_url\"\n                and (\n                    srt_actions[n - 3][0] == \"sw_fr\"\n                    or srt_actions[n - 3][0] == \"sw_dc\"\n                    or srt_actions[n - 3][0] == \"sw_pf\"\n                    or srt_actions[n - 3][0] == \"s_c_d\"\n                    or srt_actions[n - 3][0] == \"s_c_f\"\n                )\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 3][3]) < 4200\n                )\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 0\n                and (\n                    srt_actions[n - 1][0] == \"sw_fr\"\n                    or srt_actions[n - 1][0] == \"sw_dc\"\n                    or srt_actions[n - 1][0] == \"sw_pf\"\n                    or srt_actions[n - 1][0] == \"s_c_d\"\n                    or srt_actions[n - 1][0] == \"s_c_f\"\n                )\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 4200\n                )\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                (srt_actions[n][0] == \"begin\" or srt_actions[n][0] == \"_url_\")\n                and n > 1\n                and (\n                    srt_actions[n - 1][0] == \"f_url\"\n                    or srt_actions[n - 1][0] == \"_url_\"\n                )\n                and srt_actions[n][2] == srt_actions[n - 1][2]\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 4800\n                )\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"input\"\n                and n > 2\n                and srt_actions[n - 1][0] == \"js_cl\"\n                and srt_actions[n - 2][0] == \"input\"\n                and srt_actions[n][2].endswith(\"\\n\")\n                and srt_actions[n][1] == srt_actions[n - 2][1]\n                and srt_actions[n][2][0:-1] == srt_actions[n - 2][2]\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 440\n                )\n            ):\n                srt_actions[n - 1][0] = \"_skip\"\n                srt_actions[n - 2][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"input\"\n                and n > 1\n                and srt_actions[n - 1][0] == \"jq_cl\"\n                and srt_actions[n][2].endswith(\"\\n\")\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 900\n                )\n            ):\n                srt_actions[n - 1][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"input\"\n                and n > 2\n                and srt_actions[n - 3][0] == \"js_ty\"\n                and srt_actions[n - 1][0] == \"_skip\"\n                and srt_actions[n - 2][0] == \"_skip\"\n                and srt_actions[n][2].endswith(\"\\n\")\n                and srt_actions[n][2][0:-1] == srt_actions[n - 3][1][1]\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 3][3]) < 440\n                )\n            ):\n                srt_actions[n - 3][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"input\"\n                and n > 1\n                and srt_actions[n - 2][0] == \"jq_ty\"\n                and srt_actions[n - 1][0] == \"_skip\"\n                and srt_actions[n][2].endswith(\"\\n\")\n                and srt_actions[n][2][0:-1] == srt_actions[n - 2][1][1]\n                and (\n                    int(srt_actions[n][3]) - int(srt_actions[n - 2][3]) < 900\n                )\n            ):\n                srt_actions[n - 2][0] = \"_skip\"\n        origins = []\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"begin\"\n                or srt_actions[n][0] == \"_url_\"\n                or srt_actions[n][0] == \"f_url\"\n            ):\n                origin = srt_actions[n][1]\n                if origin.endswith(\"/\"):\n                    origin = origin[0:-1]\n                if origin not in origins:\n                    origins.append(origin)\n        for origin in self.__origins_to_save:\n            origins.append(origin)\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"click\"\n                and n > 0\n                and srt_actions[n - 1][0] == \"ho_cl\"\n                and srt_actions[n - 1][2] in origins\n            ):\n                srt_actions[n - 1][0] = \"_skip\"\n                srt_actions[n][0] = \"h_clk\"\n                srt_actions[n][1] = srt_actions[n - 1][1][0]\n                srt_actions[n][2] = srt_actions[n - 1][1][1]\n        for n in range(len(srt_actions)):\n            if srt_actions[n][0] == \"chfil\" and srt_actions[n][2] in origins:\n                srt_actions[n][0] = \"cho_f\"\n                srt_actions[n][2] = srt_actions[n][1][1]\n                srt_actions[n][1] = srt_actions[n][1][0]\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"sh_fc\"\n                and n > 0\n                and srt_actions[n - 1][0] == \"sh_fc\"\n            ):\n                srt_actions[n - 1][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if srt_actions[n][0] == \"canva\":\n                srt_actions[n][1][1] = math.ceil(float(srt_actions[n][1][1]))\n                srt_actions[n][1][2] = math.ceil(float(srt_actions[n][1][2]))\n        ext_actions = []\n        ext_actions.append(\"_url_\")\n        ext_actions.append(\"js_cl\")\n        ext_actions.append(\"js_ca\")\n        ext_actions.append(\"js_ty\")\n        ext_actions.append(\"s_val\")\n        ext_actions.append(\"jq_cl\")\n        ext_actions.append(\"jq_ca\")\n        ext_actions.append(\"jq_ty\")\n        ext_actions.append(\"pkeys\")\n        ext_actions.append(\"r_clk\")\n        ext_actions.append(\"as_el\")\n        ext_actions.append(\"as_ep\")\n        ext_actions.append(\"asenv\")\n        ext_actions.append(\"acc_a\")\n        ext_actions.append(\"dis_a\")\n        ext_actions.append(\"hi_li\")\n        ext_actions.append(\"as_lt\")\n        ext_actions.append(\"as_ti\")\n        ext_actions.append(\"as_tc\")\n        ext_actions.append(\"a_url\")\n        ext_actions.append(\"a_u_c\")\n        ext_actions.append(\"as_df\")\n        ext_actions.append(\"do_fi\")\n        ext_actions.append(\"as_at\")\n        ext_actions.append(\"as_te\")\n        ext_actions.append(\"astnv\")\n        ext_actions.append(\"aetnv\")\n        ext_actions.append(\"as_et\")\n        ext_actions.append(\"asnet\")\n        ext_actions.append(\"wf_el\")\n        ext_actions.append(\"sw_fr\")\n        ext_actions.append(\"sw_dc\")\n        ext_actions.append(\"sw_pf\")\n        ext_actions.append(\"s_c_f\")\n        ext_actions.append(\"s_c_d\")\n        ext_actions.append(\"hover\")\n        ext_actions.append(\"sleep\")\n        ext_actions.append(\"sh_fc\")\n        ext_actions.append(\"s_at_\")\n        ext_actions.append(\"s_ats\")\n        ext_actions.append(\"a_d_m\")\n        ext_actions.append(\"d_d_m\")\n        ext_actions.append(\"c_l_s\")\n        ext_actions.append(\"c_s_s\")\n        ext_actions.append(\"d_a_c\")\n        ext_actions.append(\"e_mfa\")\n        ext_actions.append(\"go_bk\")\n        ext_actions.append(\"go_fw\")\n        ext_actions.append(\"s_scr\")\n        ext_actions.append(\"ss_tf\")\n        ext_actions.append(\"ss_tl\")\n        ext_actions.append(\"pdftl\")\n        ext_actions.append(\"spstl\")\n        ext_actions.append(\"da_el\")\n        ext_actions.append(\"da_ep\")\n        ext_actions.append(\"da_te\")\n        ext_actions.append(\"da_et\")\n        ext_actions.append(\"danet\")\n        ext_actions.append(\"pr_da\")\n        for n in range(len(srt_actions)):\n            if srt_actions[n][0] in ext_actions:\n                origin = srt_actions[n][2]\n                if (\n                    srt_actions[n][0] == \"js_cl\"\n                    or srt_actions[n][0] == \"js_ca\"\n                    or srt_actions[n][0] == \"jq_cl\"\n                    or srt_actions[n][0] == \"jq_ca\"\n                ):\n                    origin = srt_actions[n][2][1]\n                if origin.endswith(\"/\"):\n                    origin = origin[0:-1]\n                if srt_actions[n][0] == \"js_ty\":\n                    srt_actions[n][2] = srt_actions[n][1][1]\n                    srt_actions[n][1] = srt_actions[n][1][0]\n                if srt_actions[n][0] == \"jq_ty\":\n                    srt_actions[n][2] = srt_actions[n][1][1]\n                    srt_actions[n][1] = srt_actions[n][1][0]\n                if srt_actions[n][0] == \"pkeys\":\n                    srt_actions[n][2] = srt_actions[n][1][1]\n                    srt_actions[n][1] = srt_actions[n][1][0]\n                if srt_actions[n][0] == \"e_mfa\":\n                    srt_actions[n][2] = srt_actions[n][1][1]\n                    srt_actions[n][1] = srt_actions[n][1][0]\n                if srt_actions[n][0] == \"_url_\" and origin not in origins:\n                    origins.append(origin)\n                if origin not in origins:\n                    srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if (\n                srt_actions[n][0] == \"input\"\n                and n > 0\n                and (\n                    srt_actions[n - 1][0] == \"js_ty\"\n                    or srt_actions[n - 1][0] == \"jq_ty\"\n                    or srt_actions[n - 1][0] == \"pkeys\"\n                )\n                and srt_actions[n][2] == srt_actions[n - 1][2]\n            ):\n                srt_actions[n][0] = \"_skip\"\n        for n in range(len(srt_actions)):\n            if srt_actions[n][0] == \"ch_cl\":\n                srt_actions[n][0] = \"js_cl\"\n        for n in range(len(srt_actions)):\n            if srt_actions[n][0] == \"s_val\":\n                srt_actions[n][0] = \"set_v\"\n                srt_actions[n][2] = srt_actions[n][1][1]\n                srt_actions[n][1] = srt_actions[n][1][0]\n\n        # Generate the script from processed actions\n        sb_actions = recorder_helper.generate_sbase_code(srt_actions)\n        filename = self.__get_filename()\n        classname = self.__class__.__name__\n        methodname = self._testMethodName\n        context_filename = None\n        if (\n            getattr(sb_config, \"is_context_manager\", None)\n            and (filename == \"base_case.py\" or methodname == \"runTest\")\n        ):\n            import traceback\n            stack_base = traceback.format_stack()[0].split(os.sep)[-1]\n            test_base = stack_base.split(\", in \")[0]\n            if getattr(self, \"cm_filename\", None):\n                filename = self.cm_filename\n            else:\n                filename = test_base.split('\"')[0]\n            classname = \"SB_Test\"\n            methodname = \"test_line_\" + test_base.split(\", line \")[-1]\n            context_filename = filename.split(\".\")[0] + \"_rec.py\"\n        if hasattr(self, \"_using_sb_fixture\"):\n            test_id = sb_config._test_id\n            filename = test_id.split(\"::\")[0]\n            methodname = test_id.split(\"::\")[-1]\n            if test_id.count(\"::\") >= 2:\n                classname = test_id.split(\"::\")[-2]\n            else:\n                classname = \"MyTestClass\"\n            methodname = methodname.replace(\"[\", \"__\").replace(\"]\", \"\")\n            methodname = re.sub(r\"[\\W]\", \"_\", methodname)\n        if getattr(self, \"is_behave\", None):\n            classname = sb_config.behave_feature.name\n            classname = classname.replace(\"/\", \" \").replace(\" & \", \" \")\n            classname = re.sub(r\"[^\\w\" + r\"_ \" + r\"]\", \"\", classname)\n            classname_parts = classname.split(\" \")\n            new_classname = \"\"\n            for part in classname_parts:\n                new_classname += (part[0].upper() + part[1:])\n            classname = new_classname\n            methodname = sb_config.behave_scenario.name\n            methodname = methodname.replace(\"/\", \"_\").replace(\" & \", \"_\")\n            methodname = methodname.replace(\" + \", \" plus \")\n            methodname = methodname.replace(\" - \", \" minus \")\n            methodname = methodname.replace(\" × \", \" times \")\n            methodname = methodname.replace(\" ÷ \", \" divided by \")\n            methodname = methodname.replace(\" = \", \" equals \")\n            methodname = methodname.replace(\" %% \", \" percent \")\n            methodname = re.sub(r\"[^\\w\" + r\"_ \" + r\"]\", \"\", methodname)\n            methodname = methodname.replace(\" _ \", \"_\").lower()\n            methodname = methodname.replace(\" \", \"_\").lower()\n            if not methodname.startswith(\"test_\"):\n                methodname = \"test_\" + methodname\n        new_file = False\n        data = []\n        if filename not in sb_config._recorded_actions:\n            new_file = True\n            sb_config._recorded_actions[filename] = []\n            data.append(\"from seleniumbase import BaseCase\")\n            if \"--uc\" in sys.argv:\n                data.append('BaseCase.main(__name__, __file__, \"--uc\")')\n            else:\n                data.append(\"BaseCase.main(__name__, __file__)\")\n            data.append(\"\")\n            data.append(\"\")\n            data.append(\"class %s(BaseCase):\" % classname)\n        else:\n            data = sb_config._recorded_actions[filename]\n        if not new_file and classname not in \" \".join(data):\n            data.append(\"class %s(BaseCase):\" % classname)\n        data.append(\"    def %s(self):\" % methodname)\n        if len(sb_actions) > 0:\n            if \"--uc\" in sys.argv:\n                data.append(\"        self.activate_cdp_mode()\")\n            for action in sb_actions:\n                if \"--uc\" in sys.argv:\n                    action = action.replace(\n                        \"self.type(\", \"self.press_keys(\"\n                    )\n                data.append(\"        \" + action)\n        else:\n            data.append(\"        pass\")\n        data.append(\"\")\n        sb_config._recorded_actions[filename] = data\n        saved_data = data\n\n        recordings_folder = constants.Recordings.SAVED_FOLDER\n        if recordings_folder.endswith(\"/\"):\n            recordings_folder = recordings_folder[:-1]\n        if not os.path.exists(recordings_folder):\n            with suppress(Exception):\n                os.makedirs(recordings_folder)\n                sys.stdout.write(\"\\nCreated recordings%s\" % os.sep)\n\n        data = []\n        data.append(\"\")\n        extra_file_name = \"__init__.py\"\n        extra_file_path = os.path.join(recordings_folder, extra_file_name)\n        if not os.path.exists(extra_file_path):\n            out_file = open(extra_file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            sys.stdout.write(\"\\nCreated recordings%s__init__.py\" % os.sep)\n\n        data = []\n        data.append(\"[pytest]\")\n        data.append(\"addopts = --capture=no -p no:cacheprovider\")\n        data.append(\"norecursedirs = .* build dist recordings temp assets\")\n        data.append(\"filterwarnings =\")\n        data.append(\"    ignore::pytest.PytestWarning\")\n        data.append(\"    ignore:.*U.*mode is deprecated:DeprecationWarning\")\n        data.append(\"junit_family = legacy\")\n        data.append(\"python_files =\")\n        data.append(\"    test_*.py \")\n        data.append(\"    *_test.py \")\n        data.append(\"    *_tests.py\")\n        data.append(\"    *_suite.py\")\n        data.append(\"    *_feature.py\")\n        data.append(\"    *_test_rec.py\")\n        data.append(\"    *_feature_rec.py\")\n        data.append(\"python_classes = Test* *Test* *Test *Tests *Suite\")\n        data.append(\"python_functions = test_*\")\n        data.append(\"markers =\")\n        data.append(\"    marker1: custom marker\")\n        data.append(\"    marker2: custom marker\")\n        data.append(\"    marker3: custom marker\")\n        data.append(\"    marker_test_suite: custom marker\")\n        data.append(\"    expected_failure: custom marker\")\n        data.append(\"    local: custom marker\")\n        data.append(\"    remote: custom marker\")\n        data.append(\"    offline: custom marker\")\n        data.append(\"    develop: custom marker\")\n        data.append(\"    qa: custom marker\")\n        data.append(\"    ci: custom marker\")\n        data.append(\"    e2e: custom marker\")\n        data.append(\"    ready: custom marker\")\n        data.append(\"    smoke: custom marker\")\n        data.append(\"    deploy: custom marker\")\n        data.append(\"    active: custom marker\")\n        data.append(\"    master: custom marker\")\n        data.append(\"    release: custom marker\")\n        data.append(\"    staging: custom marker\")\n        data.append(\"    production: custom marker\")\n        data.append(\"\")\n        extra_file_name = \"pytest.ini\"\n        extra_file_path = os.path.join(recordings_folder, extra_file_name)\n        if not os.path.exists(extra_file_path):\n            out_file = open(extra_file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            sys.stdout.write(\"\\nCreated recordings%spytest.ini\" % os.sep)\n\n        data = []\n        data.append(\"[flake8]\")\n        data.append(\"exclude=recordings,temp\")\n        data.append(\"ignore=W503\")\n        data.append(\"\")\n        data.append(\"[nosetests]\")\n        data.append(\"nocapture=1\")\n        data.append(\"logging-level=INFO\")\n        data.append(\"\")\n        data.append(\"[behave]\")\n        data.append(\"show_skipped=false\")\n        data.append(\"show_timings=false\")\n        data.append(\"\")\n        extra_file_name = \"setup.cfg\"\n        extra_file_path = os.path.join(recordings_folder, extra_file_name)\n        if not os.path.exists(extra_file_path):\n            out_file = open(extra_file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            sys.stdout.write(\"\\nCreated recordings%ssetup.cfg\" % os.sep)\n\n        data = saved_data\n        file_name = self.__class__.__module__.split(\".\")[-1] + \"_rec.py\"\n        if hasattr(self, \"_using_sb_fixture\"):\n            test_id = sb_config._test_id\n            file_name = test_id.split(\"::\")[0].split(\"/\")[-1].split(\"\\\\\")[-1]\n            file_name = file_name.split(\".py\")[0] + \"_rec.py\"\n        if getattr(self, \"is_behave\", None):\n            file_name = sb_config.behave_scenario.filename.replace(\".\", \"_\")\n            file_name = file_name.split(\"/\")[-1].split(\"\\\\\")[-1] + \"_rec.py\"\n            file_name = file_name\n        elif context_filename:\n            file_name = context_filename\n        file_path = os.path.join(recordings_folder, file_name)\n        out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(\"\\r\\n\".join(data))\n        out_file.close()\n        rec_message = \">>> RECORDING SAVED as: \"\n        if not new_file:\n            rec_message = \">>> RECORDING ADDED to: \"\n        star_len = len(rec_message) + len(file_path)\n        with suppress(Exception):\n            terminal_size = os.get_terminal_size().columns\n            if terminal_size > 30 and star_len > terminal_size:\n                star_len = terminal_size\n        spc = \"\\n\\n\"\n        if getattr(self, \"rec_print\", None):\n            spc = \"\"\n            sys.stdout.write(\"\\nCreated recordings%s%s\" % (os.sep, file_name))\n            print()\n            if \" \" not in file_path:\n                os.system(\"sbase print %s -n\" % file_path)\n            elif '\"' not in file_path:\n                os.system('sbase print \"%s\" -n' % file_path)\n            else:\n                os.system(\"sbase print '%s' -n\" % file_path)\n        stars = \"*\" * star_len\n        c1 = \"\"\n        c2 = \"\"\n        cr = \"\"\n        if not is_linux:\n            c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n            c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX\n            cr = colorama.Style.RESET_ALL\n            rec_message = rec_message.replace(\">>>\", c2 + \">>>\" + cr)\n        print(\"%s%s%s%s%s\\n%s\" % (spc, rec_message, c1, file_path, cr, stars))\n\n        if getattr(self, \"rec_behave\", None):\n            # Also generate necessary behave-gherkin files.\n            self.__process_recorded_behave_actions(srt_actions, colorama)\n\n    def __process_recorded_behave_actions(self, srt_actions, colorama):\n        from seleniumbase.behave import behave_helper\n\n        behave_actions = behave_helper.generate_gherkin(srt_actions)\n        filename = self.__get_filename()\n        feature_class = None\n        scenario_test = None\n        if getattr(self, \"is_behave\", None):\n            feature_class = sb_config.behave_feature.name\n            scenario_test = sb_config.behave_scenario.name\n        else:\n            feature_class = self.__class__.__name__\n            scenario_test = self._testMethodName\n            if hasattr(self, \"_using_sb_fixture\"):\n                test_id = sb_config._test_id\n                filename = test_id.split(\"::\")[0]\n                scenario_test = test_id.split(\"::\")[-1]\n                if test_id.count(\"::\") >= 2:\n                    feature_class = test_id.split(\"::\")[-2]\n                else:\n                    feature_class = \"MyTestClass\"\n        new_file = False\n        data = []\n        if filename not in sb_config._behave_recorded_actions:\n            new_file = True\n            sb_config._behave_recorded_actions[filename] = []\n            data.append(\"Feature: %s\" % feature_class)\n            data.append(\"\")\n        else:\n            data = sb_config._behave_recorded_actions[filename]\n        data.append(\"  Scenario: %s\" % scenario_test)\n        if len(behave_actions) > 0:\n            count = 0\n            if \"--uc\" in sys.argv:\n                data.append(\"    Given Activate CDP Mode\")\n                count += 1\n            for action in behave_actions:\n                if count == 0:\n                    data.append(\"    Given \" + action)\n                else:\n                    data.append(\"    And \" + action)\n                count += 1\n        data.append(\"\")\n        sb_config._behave_recorded_actions[filename] = data\n\n        recordings_folder = constants.Recordings.SAVED_FOLDER\n        if recordings_folder.endswith(\"/\"):\n            recordings_folder = recordings_folder[:-1]\n        if not os.path.exists(recordings_folder):\n            with suppress(Exception):\n                os.makedirs(recordings_folder)\n        features_folder = os.path.join(recordings_folder, \"features\")\n        if not os.path.exists(features_folder):\n            with suppress(Exception):\n                os.makedirs(features_folder)\n        steps_folder = os.path.join(features_folder, \"steps\")\n        if not os.path.exists(steps_folder):\n            with suppress(Exception):\n                os.makedirs(steps_folder)\n\n        file_name = filename.split(\".\")[0]\n        if getattr(self, \"is_behave\", None):\n            file_name = sb_config.behave_scenario.filename.replace(\".\", \"_\")\n        file_name = file_name.split(\"/\")[-1].split(\"\\\\\")[-1] + \"_rec.feature\"\n        file_path = os.path.join(features_folder, file_name)\n        out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(\"\\r\\n\".join(data))\n        out_file.close()\n\n        rec_message = \">>> RECORDING SAVED as: \"\n        if not new_file:\n            rec_message = \">>> RECORDING ADDED to: \"\n        star_len = len(rec_message) + len(file_path)\n        with suppress(Exception):\n            terminal_size = os.get_terminal_size().columns\n            if terminal_size > 30 and star_len > terminal_size:\n                star_len = terminal_size\n        spc = \"\\n\"\n        if getattr(self, \"rec_print\", None):\n            spc = \"\"\n            print()\n            if \" \" not in file_path:\n                os.system(\"sbase print %s -n\" % file_path)\n            elif '\"' not in file_path:\n                os.system('sbase print \"%s\" -n' % file_path)\n            else:\n                os.system(\"sbase print '%s' -n\" % file_path)\n        stars = \"*\" * star_len\n        c1 = \"\"\n        c2 = \"\"\n        cr = \"\"\n        if not is_linux:\n            c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n            c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX\n            cr = colorama.Style.RESET_ALL\n            rec_message = rec_message.replace(\">>>\", c2 + \">>>\" + cr)\n        print(\"%s%s%s%s%s\\n%s\" % (spc, rec_message, c1, file_path, cr, stars))\n\n        data = []\n        data.append(\"\")\n        file_name = \"__init__.py\"\n        file_path = os.path.join(features_folder, file_name)\n        if not os.path.exists(file_path):\n            out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            print(\"Created recordings/features/__init__.py\")\n\n        data = []\n        data.append(\"[behave]\")\n        data.append(\"show_skipped=false\")\n        data.append(\"show_timings=false\")\n        data.append(\"\")\n        file_name = \"behave.ini\"\n        file_path = os.path.join(features_folder, file_name)\n        if not os.path.exists(file_path):\n            out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            print(\"Created recordings/features/behave.ini\")\n\n        data = []\n        data.append(\"from seleniumbase import BaseCase\")\n        data.append(\"from seleniumbase.behave import behave_sb\")\n        data.append(\n            \"behave_sb.set_base_class(BaseCase)  # Accepts a BaseCase subclass\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import before_all  # noqa\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import before_feature  # noqa\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import before_scenario  # noqa\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import before_step  # noqa\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import after_step  # noqa\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import after_scenario  # noqa\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import after_feature  # noqa\"\n        )\n        data.append(\n            \"from seleniumbase.behave.behave_sb import after_all  # noqa\"\n        )\n        data.append(\"\")\n        file_name = \"environment.py\"\n        file_path = os.path.join(features_folder, file_name)\n        if not os.path.exists(file_path):\n            out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            print(\"Created recordings/features/environment.py\")\n\n        data = []\n        data.append(\"\")\n        file_name = \"__init__.py\"\n        file_path = os.path.join(steps_folder, file_name)\n        if not os.path.exists(file_path):\n            out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            print(\"Created recordings/features/steps/__init__.py\")\n\n        data = []\n        data.append(\"from seleniumbase.behave import steps  # noqa\")\n        data.append(\"\")\n        file_name = \"imported.py\"\n        file_path = os.path.join(steps_folder, file_name)\n        if not os.path.exists(file_path):\n            out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(\"\\r\\n\".join(data))\n            out_file.close()\n            print(\"Created recordings/features/steps/imported.py\")\n\n    def bring_active_window_to_front(self):\n        \"\"\"Brings the active browser window to the front (on top).\n        Useful when multiple drivers are being used at the same time.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.bring_active_window_to_front()\n            return\n        with suppress(Exception):\n            if not self.__is_in_frame():\n                # Only bring the window to the front if not in a frame\n                # because the driver resets itself to default content.\n                self.switch_to_window(self.driver.current_window_handle)\n\n    def bring_to_front(self, selector, by=\"css selector\"):\n        \"\"\"Updates the Z-index of a page element to bring it into view.\n        Useful when getting a WebDriverException, such as the one below:\n            { Element is not clickable at point (#, #).\n              Other element would receive the click: ... }\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by)\n        element = self.wait_for_element_visible(\n            selector, by=by, timeout=settings.SMALL_TIMEOUT\n        )\n        try:\n            selector = self.convert_to_css_selector(selector, by=by)\n        except Exception:\n            # If can't convert to CSS_Selector for JS, use element directly\n            script = (\"\"\"arguments[0].style.zIndex = '999999';\"\"\")\n            self.execute_script(script, element)\n            return\n        selector = re.escape(selector)\n        selector = self.__escape_quotes_if_needed(selector)\n        script = (\n            \"\"\"document.querySelector('%s').style.zIndex = '999999';\"\"\"\n            % selector\n        )\n        self.execute_script(script)\n\n    def highlight_click(\n        self, selector, by=\"css selector\", loops=3, scroll=True, timeout=None,\n    ):\n        \"\"\"Highlights the element, and then clicks it.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.wait_for_element_visible(selector, by=by, timeout=timeout)\n        if not self.demo_mode:\n            self.__highlight(selector, by=by, loops=loops, scroll=scroll)\n        self.click(selector, by=by)\n\n    def highlight_update_text(\n        self,\n        selector,\n        text,\n        by=\"css selector\",\n        loops=3,\n        scroll=True,\n        timeout=None,\n    ):\n        \"\"\"Highlights the element and then types text into the field.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.wait_for_element_visible(selector, by=by, timeout=timeout)\n        if not self.demo_mode:\n            self.__highlight(selector, by=by, loops=loops, scroll=scroll)\n        self.update_text(selector, text, by=by)\n\n    def highlight_type(\n        self,\n        selector,\n        text,\n        by=\"css selector\",\n        loops=3,\n        scroll=True,\n        timeout=None,\n    ):\n        \"\"\"Same as self.highlight_update_text()\n        As above, highlights the element and then types text into the field.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.wait_for_element_visible(selector, by=by, timeout=timeout)\n        if not self.demo_mode:\n            self.__highlight(selector, by=by, loops=loops, scroll=scroll)\n        self.update_text(selector, text, by=by)\n\n    def highlight_if_visible(\n        self, selector, by=\"css selector\", loops=4, scroll=True,\n    ):\n        \"\"\"Highlights the element if the element is visible.\"\"\"\n        self.__check_scope()\n        if self.is_element_visible(selector, by=by):\n            self.__highlight(selector, by=by, loops=loops, scroll=scroll)\n\n    def __highlight_element(self, element, loops=None, scroll=True):\n        self.__check_scope()\n        if not loops:\n            loops = settings.HIGHLIGHTS\n        if scroll and self.browser != \"safari\":\n            with suppress(Exception):\n                self.__slow_scroll_to_element(element)\n        if self.highlights:\n            loops = self.highlights\n        if self.browser == \"ie\":\n            loops = 1  # Override previous setting because IE is slow\n        loops = int(loops)\n        if self.headless or self.headless2 or self.xvfb:\n            # Headless modes have less need for highlighting elements.\n            # However, highlight() may be used as a sleep alternative.\n            loops = int(math.ceil(loops * 0.5))\n        o_bs = \"\"  # original_box_shadow\n        try:\n            style = element.get_attribute(\"style\")\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(0.12)\n            style = element.get_attribute(\"style\")\n        if style:\n            if \"box-shadow: \" in style:\n                box_start = style.find(\"box-shadow: \")\n                box_end = style.find(\";\", box_start) + 1\n                original_box_shadow = style[box_start:box_end]\n                o_bs = original_box_shadow\n        self.__highlight_element_with_js(element, loops, o_bs)\n        time.sleep(0.065)\n\n    def __highlight(\n        self, selector, by=\"css selector\", loops=None, scroll=True\n    ):\n        \"\"\"This method uses fancy JavaScript to highlight an element.\n        (Automatically using in S_e_l_e_n_i_u_m_B_a_s_e Demo Mode)\"\"\"\n        self.__check_scope()\n        selector, by = self.__recalculate_selector(selector, by, xp_ok=False)\n        element = self.wait_for_element_visible(\n            selector, by=by, timeout=settings.SMALL_TIMEOUT\n        )\n        if not loops:\n            loops = settings.HIGHLIGHTS\n        if scroll:\n            try:\n                if self.browser != \"safari\":\n                    scroll_distance = js_utils.get_scroll_distance_to_element(\n                        self.driver, element\n                    )\n                    if abs(scroll_distance) > constants.Values.SSMD:\n                        self.__jquery_slow_scroll_to(selector, by)\n                    else:\n                        self.__slow_scroll_to_element(element)\n                else:\n                    self.__jquery_slow_scroll_to(selector, by)\n            except Exception:\n                self.wait_for_ready_state_complete()\n                time.sleep(0.12)\n                element = self.wait_for_element_visible(\n                    selector, by=by, timeout=settings.SMALL_TIMEOUT\n                )\n                self.__slow_scroll_to_element(element)\n        use_element_directly = False\n        try:\n            selector = self.convert_to_css_selector(selector, by=by)\n        except Exception:\n            # If can't convert to CSS_Selector for JS, use element directly\n            use_element_directly = True\n        if self.highlights:\n            loops = self.highlights\n        if self.browser == \"ie\":\n            loops = 1  # Override previous setting because IE is slow\n        loops = int(loops)\n        if self.headless or self.headless2 or self.xvfb:\n            # Headless modes have less need for highlighting elements.\n            # However, highlight() may be used as a sleep alternative.\n            loops = int(math.ceil(loops * 0.5))\n        o_bs = \"\"  # original_box_shadow\n        try:\n            style = element.get_attribute(\"style\")\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(0.12)\n            element = self.wait_for_element_visible(\n                selector, by=\"css selector\", timeout=settings.SMALL_TIMEOUT\n            )\n            style = element.get_attribute(\"style\")\n        if style:\n            if \"box-shadow: \" in style:\n                box_start = style.find(\"box-shadow: \")\n                box_end = style.find(\";\", box_start) + 1\n                original_box_shadow = style[box_start:box_end]\n                o_bs = original_box_shadow\n        if use_element_directly:\n            self.__highlight_element_with_js(element, loops, o_bs)\n        elif \":contains\" not in selector and \":first\" not in selector:\n            selector = re.escape(selector)\n            selector = self.__escape_quotes_if_needed(selector)\n            self.__highlight_with_js(selector, loops, o_bs)\n        else:\n            selector = self.__make_css_match_first_element_only(selector)\n            selector = re.escape(selector)\n            selector = self.__escape_quotes_if_needed(selector)\n            try:\n                self.__highlight_with_jquery(selector, loops, o_bs)\n            except Exception:\n                pass  # JQuery probably couldn't load. Skip highlighting.\n        time.sleep(0.065)\n\n    def highlight(\n        self,\n        selector,\n        by=\"css selector\",\n        loops=None,\n        scroll=True,\n        timeout=None,\n    ):\n        \"\"\"This method uses fancy JavaScript to highlight an element.\n        @Params\n        selector - the selector of the element to find (Accepts WebElement)\n        by - the type of selector to search by (Default: CSS)\n        loops - # of times to repeat the highlight animation\n                (Default: 4. Each loop lasts for about 0.2s)\n        scroll - the option to scroll to the element first (Default: True)\n        timeout - the time to wait for the element to appear \"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            if page_utils.is_xpath_selector(selector):\n                if \"contains(\" in selector:\n                    self.cdp.highlight(selector)\n                    return\n        else:\n            self._check_browser()\n        self.__skip_if_esc()\n        if isinstance(selector, WebElement):\n            self.__highlight_element(selector, loops=loops, scroll=scroll)\n            return\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        self.wait_for_element_visible(selector, by=by, timeout=timeout)\n        self.__highlight(selector=selector, by=by, loops=loops, scroll=scroll)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"hi_li\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n\n    def highlight_elements(\n        self,\n        selector,\n        by=\"css selector\",\n        loops=None,\n        scroll=True,\n        limit=0,\n    ):\n        if not limit:\n            limit = 0  # 0 means no limit\n        limit = int(limit)\n        count = 0\n        elements = self.find_elements(selector, by=by)\n        for element in elements:\n            with suppress(Exception):\n                if element.is_displayed():\n                    self.__highlight_element(\n                        element, loops=loops, scroll=scroll\n                    )\n                    count += 1\n            if limit > 0 and count >= limit:\n                break\n\n    def press_up_arrow(self, selector=\"body\", times=1, by=\"css selector\"):\n        \"\"\"Simulates pressing the UP Arrow on the keyboard.\n        By default, \"html\" will be used as the CSS Selector target.\n        You can specify how many times in-a-row the action happens.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if times < 1:\n            return\n        element = self.wait_for_element_present(selector)\n        self.__demo_mode_highlight_if_active(selector, by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        for i in range(int(times)):\n            try:\n                element.send_keys(Keys.ARROW_UP)\n            except Exception:\n                self.wait_for_ready_state_complete()\n                element = self.wait_for_element_visible(selector)\n                element.send_keys(Keys.ARROW_UP)\n            time.sleep(0.01)\n            if self.slow_mode:\n                time.sleep(0.1)\n\n    def press_down_arrow(self, selector=\"body\", times=1, by=\"css selector\"):\n        \"\"\"Simulates pressing the DOWN Arrow on the keyboard.\n        By default, \"html\" will be used as the CSS Selector target.\n        You can specify how many times in-a-row the action happens.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if times < 1:\n            return\n        element = self.wait_for_element_present(selector)\n        self.__demo_mode_highlight_if_active(selector, by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        for i in range(int(times)):\n            try:\n                element.send_keys(Keys.ARROW_DOWN)\n            except Exception:\n                self.wait_for_ready_state_complete()\n                element = self.wait_for_element_visible(selector)\n                element.send_keys(Keys.ARROW_DOWN)\n            time.sleep(0.01)\n            if self.slow_mode:\n                time.sleep(0.1)\n\n    def press_left_arrow(self, selector=\"body\", times=1, by=\"css selector\"):\n        \"\"\"Simulates pressing the LEFT Arrow on the keyboard.\n        By default, \"html\" will be used as the CSS Selector target.\n        You can specify how many times in-a-row the action happens.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if times < 1:\n            return\n        element = self.wait_for_element_present(selector)\n        self.__demo_mode_highlight_if_active(selector, by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        for i in range(int(times)):\n            try:\n                element.send_keys(Keys.ARROW_LEFT)\n            except Exception:\n                self.wait_for_ready_state_complete()\n                element = self.wait_for_element_visible(selector)\n                element.send_keys(Keys.ARROW_LEFT)\n            time.sleep(0.01)\n            if self.slow_mode:\n                time.sleep(0.1)\n\n    def press_right_arrow(self, selector=\"body\", times=1, by=\"css selector\"):\n        \"\"\"Simulates pressing the RIGHT Arrow on the keyboard.\n        By default, \"html\" will be used as the CSS Selector target.\n        You can specify how many times in-a-row the action happens.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if times < 1:\n            return\n        element = self.wait_for_element_present(selector)\n        self.__demo_mode_highlight_if_active(selector, by)\n        if not self.demo_mode and not self.slow_mode:\n            self.__scroll_to_element(element, selector, by)\n        for i in range(int(times)):\n            try:\n                element.send_keys(Keys.ARROW_RIGHT)\n            except Exception:\n                self.wait_for_ready_state_complete()\n                element = self.wait_for_element_visible(selector)\n                element.send_keys(Keys.ARROW_RIGHT)\n            time.sleep(0.01)\n            if self.slow_mode:\n                time.sleep(0.1)\n\n    def scroll_to(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Fast scroll to destination\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_into_view(selector)\n            return\n        if self.demo_mode or self.slow_mode:\n            self.slow_scroll_to(selector, by=by, timeout=timeout)\n            return\n        element = self.wait_for_element_visible(\n            selector, by=by, timeout=timeout\n        )\n        try:\n            self.__scroll_to_element(element, selector, by)\n        except (Stale_Exception, ENI_Exception):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.12)\n            element = self.wait_for_element_visible(\n                selector, by=by, timeout=timeout\n            )\n            self.__scroll_to_element(element, selector, by)\n\n    def scroll_to_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.scroll_to()\"\"\"\n        self.scroll_to(selector, by=by, timeout=timeout)\n\n    def slow_scroll_to(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Slow motion scroll to destination\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        original_by = by\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed() and \":contains(\" not in selector:\n            self.cdp.scroll_into_view(selector)\n            return\n        element = self.wait_for_element_visible(\n            original_selector, by=original_by, timeout=timeout\n        )\n        try:\n            if self.browser != \"safari\":\n                scroll_distance = js_utils.get_scroll_distance_to_element(\n                    self.driver, element\n                )\n                if abs(scroll_distance) > constants.Values.SSMD:\n                    self.__jquery_slow_scroll_to(selector, by)\n                else:\n                    self.__slow_scroll_to_element(element)\n            else:\n                self.__jquery_slow_scroll_to(selector, by)\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(0.12)\n            element = self.wait_for_element_visible(\n                original_selector, by=original_by, timeout=timeout\n            )\n            self.__slow_scroll_to_element(element)\n\n    def slow_scroll_to_element(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as self.slow_scroll_to()\"\"\"\n        self.slow_scroll_to(selector, by=by, timeout=timeout)\n\n    def scroll_into_view(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Uses the JS scrollIntoView() method to scroll to an element.\n        Unlike other scroll methods, (which put elements upper-center),\n        this method places elements at the very top of the screen.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_into_view(selector)\n            return\n        element = self.wait_for_element_visible(selector, by, timeout=timeout)\n        self.execute_script(\"arguments[0].scrollIntoView();\", element)\n\n    def scroll_to_top(self):\n        \"\"\"Scroll to the top of the page.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_to_top()\n            return\n        scroll_script = \"window.scrollTo(0, 0);\"\n        with suppress(Exception):\n            self.execute_script(scroll_script)\n            time.sleep(0.012)\n\n    def scroll_to_bottom(self):\n        \"\"\"Scroll to the bottom of the page.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_to_bottom()\n            return\n        scroll_script = \"window.scrollTo(0, 10000);\"\n        with suppress(Exception):\n            self.execute_script(scroll_script)\n            time.sleep(0.012)\n\n    def scroll_to_y(self, y):\n        \"\"\"Scroll to y position on the page.\"\"\"\n        self.__check_scope()\n        y = int(y)\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_to_y(y)\n            return\n        scroll_script = \"window.scrollTo(0, %s);\" % y\n        with suppress(Exception):\n            self.execute_script(scroll_script)\n            time.sleep(0.012)\n\n    def scroll_by_y(self, y):\n        \"\"\"Scrolls page by y pixels.\"\"\"\n        self.__check_scope()\n        y = int(y)\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_by_y(y)\n            return\n        scroll_script = \"window.scrollBy(0, %s);\" % y\n        with suppress(Exception):\n            self.execute_script(scroll_script)\n            time.sleep(0.012)\n\n    def scroll_up(self, amount=25):\n        \"\"\"Scrolls up as a percentage of the page.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_up(amount)\n            return\n        amount = self.get_window_size()[\"height\"] * amount / 100\n        self.execute_script(\"window.scrollBy(0, -%s);\" % amount)\n\n    def scroll_down(self, amount=25):\n        \"\"\"Scrolls down as a percentage of the page.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.scroll_down(amount)\n            return\n        amount = self.get_window_size()[\"height\"] * amount / 100\n        self.execute_script(\"window.scrollBy(0, %s);\" % amount)\n\n    def click_xpath(self, xpath):\n        \"\"\"Technically, self.click() automatically detects xpath selectors,\n        so self.click_xpath() is just a longer name for the same action.\"\"\"\n        self.click(xpath, by=\"xpath\")\n\n    def js_click(\n        self,\n        selector,\n        by=\"css selector\",\n        all_matches=False,\n        timeout=None,\n        scroll=True,\n    ):\n        \"\"\"Clicks an element using JavaScript.\n        Can be used to click hidden / invisible elements.\n        If \"all_matches\" is False, only the first match is clicked.\n        If \"scroll\" is False, won't scroll unless running in Demo Mode.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.click(selector, timeout=timeout)\n            return\n        self.wait_for_ready_state_complete()\n        if not timeout or timeout is True:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by, xp_ok=False)\n        if by == By.LINK_TEXT:\n            message = (\n                \"Pure JavaScript doesn't support clicking by Link Text. \"\n                \"You may want to use self.jquery_click() instead, which \"\n                \"allows this with :contains(), assuming jQuery isn't blocked. \"\n                \"For now, self.js_click() will use a regular WebDriver click.\"\n            )\n            logging.debug(message)\n            self.click(selector, by=by)\n            return\n        element = self.wait_for_element_present(\n            selector, by=by, timeout=timeout\n        )\n        if not page_actions.is_element_clickable(self.driver, selector, by):\n            self.wait_for_ready_state_complete()\n        scroll_done = False\n        if self.is_element_visible(selector, by=by):\n            scroll_done = True\n            self.__demo_mode_highlight_if_active(selector, by)\n            if scroll and not self.demo_mode and not self.slow_mode:\n                success = js_utils.scroll_to_element(self.driver, element)\n                if not success:\n                    self.wait_for_ready_state_complete()\n                    timeout = settings.SMALL_TIMEOUT\n                    element = page_actions.wait_for_element_present(\n                        self.driver, selector, by, timeout=timeout\n                    )\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        time_stamp = 0\n        action = [\"\", \"\", \"\", time_stamp]\n        pre_action_url = None\n        with suppress(Exception):\n            pre_action_url = self.driver.current_url\n        pre_window_count = len(self.driver.window_handles)\n        if self.recorder_mode and not self.__dont_record_js_click:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            tag_name = None\n            href = \"\"\n            if \":contains\\\\(\" not in css_selector:\n                tag_name = self.execute_script(\n                    \"return document.querySelector('%s').tagName.toLowerCase()\"\n                    \";\" % css_selector\n                )\n            if tag_name == \"a\":\n                href = self.execute_script(\n                    \"return document.querySelector('%s').href;\" % css_selector\n                )\n            origin = self.get_origin()\n            href_origin = [href, origin]\n            action = [\"js_cl\", selector, href_origin, time_stamp]\n            if all_matches:\n                action[0] = \"js_ca\"\n        if not self.is_element_visible(selector, by=by):\n            self.wait_for_ready_state_complete()\n            if self.is_element_visible(selector, by=by):\n                if scroll and not scroll_done:\n                    success = js_utils.scroll_to_element(self.driver, element)\n                    if not success:\n                        timeout = settings.SMALL_TIMEOUT\n                        element = page_actions.wait_for_element_present(\n                            self.driver, selector, by, timeout=timeout\n                        )\n        if self.recorder_mode and not self.__dont_record_js_click:\n            action[3] = self.execute_script(\"return Date.now();\")\n            self.__extra_actions.append(action)\n        if not all_matches:\n            if \":contains\\\\(\" not in css_selector:\n                try:\n                    self.__js_click(selector, by=by)\n                except Exception:\n                    current_url = self.driver.current_url\n                    if current_url == pre_action_url:\n                        self.__js_click_element(element)\n            else:\n                try:\n                    self.__js_click_element(element)\n                except Exception:\n                    self.wait_for_ready_state_complete()\n                    time.sleep(0.05)\n                    current_url = self.driver.current_url\n                    if current_url == pre_action_url:\n                        element = self.wait_for_element_present(\n                            selector, by, timeout=settings.SMALL_TIMEOUT\n                        )\n                    if (\n                        current_url == pre_action_url\n                        and self.is_element_visible(selector)\n                        and self.is_element_clickable(selector)\n                    ):\n                        try:\n                            self.__element_click(element)\n                        except Exception:\n                            try:\n                                self.__js_click_element(element)\n                            except Exception:\n                                element = self.wait_for_element_present(\n                                    selector, by, timeout=settings.MINI_TIMEOUT\n                                )\n                                self.__js_click_element(element)\n                    elif current_url == pre_action_url:\n                        try:\n                            self.__js_click_element(element)\n                        except Exception:\n                            current_url = self.driver.current_url\n                            if current_url == pre_action_url:\n                                element = self.wait_for_element_present(\n                                    selector, by, timeout=settings.MINI_TIMEOUT\n                                )\n                                self.__js_click_element(element)\n        else:\n            if \":contains\\\\(\" not in css_selector:\n                self.__js_click_all(selector, by=by)\n            else:\n                click_script = \"\"\"jQuery('%s').click();\"\"\" % css_selector\n                self.safe_execute_script(click_script)\n        latest_window_count = len(self.driver.window_handles)\n        if (\n            latest_window_count > pre_window_count\n            and (\n                self.recorder_mode\n                or (\n                    settings.SWITCH_TO_NEW_TABS_ON_CLICK\n                    and self.driver.current_url == pre_action_url\n                )\n            )\n        ):\n            self.__switch_to_newest_window_if_not_blank()\n        elif (\n            latest_window_count == pre_window_count - 1\n            and latest_window_count > 0\n        ):\n            # If a click closes the active window,\n            # switch to the last one if it exists.\n            self.switch_to_window(-1)\n        with suppress(Exception):\n            self.wait_for_ready_state_complete()\n        self.__demo_mode_pause_if_active()\n\n    def js_click_if_present(self, selector, by=\"css selector\", timeout=0):\n        \"\"\"If the page selector exists, js_click() the element.\n        This method only clicks on the first matching element found.\n        If a \"timeout\" is provided, waits that long for the element to\n        be present before giving up and returning without a js_click().\"\"\"\n        self.wait_for_ready_state_complete()\n        if self.is_element_present(selector, by=by):\n            self.js_click(selector, by=by)\n        elif timeout > 0:\n            with suppress(Exception):\n                self.wait_for_element_present(selector, by=by, timeout=timeout)\n            if self.is_element_present(selector, by=by):\n                self.js_click(selector, by=by)\n\n    def js_click_if_visible(self, selector, by=\"css selector\", timeout=0):\n        \"\"\"If the page selector exists and is visible, js_click() the element.\n        This method only clicks on the first matching element found.\n        If a \"timeout\" is provided, waits that long for the element\n        to appear before giving up and returning without a js_click().\"\"\"\n        self.wait_for_ready_state_complete()\n        if self.is_element_visible(selector, by=by):\n            self.js_click(selector, by=by)\n        elif timeout > 0:\n            with suppress(Exception):\n                self.wait_for_element_visible(selector, by=by, timeout=timeout)\n            if self.is_element_visible(selector, by=by):\n                self.js_click(selector, by=by)\n\n    def js_click_all(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Clicks all matching elements using pure JS. (No jQuery)\"\"\"\n        self.js_click(\n            selector, by=\"css selector\", all_matches=True, timeout=timeout\n        )\n\n    def jquery_click(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Clicks an element using jQuery. (Different from using pure JS.)\n        Can be used to click hidden / invisible elements.\"\"\"\n        self.__check_scope()\n        if not timeout or timeout is True:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by, xp_ok=False)\n        self.wait_for_element_present(selector, by=by, timeout=timeout)\n        if self.is_element_visible(selector, by=by):\n            self.__demo_mode_highlight_if_active(selector, by)\n        selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = selector\n        selector = self.__make_css_match_first_element_only(selector)\n        click_script = \"\"\"jQuery('%s')[0].click();\"\"\" % selector\n        if (\n            self.recorder_mode\n            and self.__current_url_is_recordable()\n            and self.get_session_storage_item(\"pause_recorder\") == \"no\"\n        ):\n            time_stamp = self.execute_script(\"return Date.now();\")\n            tag_name = None\n            href = \"\"\n            if \":contains\\\\(\" not in css_selector:\n                tag_name = self.execute_script(\n                    \"return document.querySelector('%s').tagName.toLowerCase()\"\n                    \";\" % css_selector\n                )\n            if tag_name == \"a\":\n                href = self.execute_script(\n                    \"return document.querySelector('%s').href;\" % css_selector\n                )\n            origin = self.get_origin()\n            href_origin = [href, origin]\n            action = [\"jq_cl\", original_selector, href_origin, time_stamp]\n            self.__extra_actions.append(action)\n        self.safe_execute_script(click_script)\n        self.__demo_mode_pause_if_active()\n\n    def jquery_click_all(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Clicks all matching elements using jQuery.\"\"\"\n        self.__check_scope()\n        if not timeout or timeout is True:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by, xp_ok=False)\n        self.wait_for_element_present(selector, by=by, timeout=timeout)\n        if self.is_element_visible(selector, by=by):\n            self.__demo_mode_highlight_if_active(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        click_script = \"\"\"jQuery('%s').click();\"\"\" % css_selector\n        if (\n            self.recorder_mode\n            and self.__current_url_is_recordable()\n            and self.get_session_storage_item(\"pause_recorder\") == \"no\"\n        ):\n            time_stamp = self.execute_script(\"return Date.now();\")\n            tag_name = None\n            href = \"\"\n            if \":contains\\\\(\" not in css_selector:\n                tag_name = self.execute_script(\n                    \"return document.querySelector('%s').tagName.toLowerCase()\"\n                    \";\" % css_selector\n                )\n            if tag_name == \"a\":\n                href = self.execute_script(\n                    \"return document.querySelector('%s').href;\" % css_selector\n                )\n            origin = self.get_origin()\n            href_origin = [href, origin]\n            action = [\"jq_ca\", original_selector, href_origin, time_stamp]\n            self.__extra_actions.append(action)\n        self.safe_execute_script(click_script)\n        self.__demo_mode_pause_if_active()\n\n    def hide_element(self, selector, by=\"css selector\"):\n        \"\"\"Hide the first element on the page that matches the selector.\"\"\"\n        self.__check_scope()\n        element = None\n        with suppress(Exception):\n            self.wait_for_element_visible(\"body\", timeout=1.5)\n            element = self.wait_for_element_present(\n                selector, by=by, timeout=0.5\n            )\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if \":contains(\" in css_selector and element:\n            script = (\n                'const e = arguments[0];'\n                'e.style.display=\"none\";e.style.visibility=\"hidden\";'\n            )\n            self.execute_script(script, element)\n        elif \":contains(\" in css_selector and not element:\n            selector = self.__make_css_match_first_element_only(css_selector)\n            script = \"\"\"jQuery('%s').hide();\"\"\" % selector\n            self.safe_execute_script(script)\n        else:\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = (\n                'const e = document.querySelector(\"%s\");'\n                'e.style.display=\"none\";e.style.visibility=\"hidden\";'\n                % css_selector\n            )\n            self.execute_script(script)\n\n    def hide_elements(self, selector, by=\"css selector\"):\n        \"\"\"Hide all elements on the page that match the selector.\"\"\"\n        self.__check_scope()\n        with suppress(Exception):\n            self.wait_for_element_visible(\"body\", timeout=1.5)\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if \":contains(\" in css_selector:\n            script = \"\"\"jQuery('%s').hide();\"\"\" % css_selector\n            self.safe_execute_script(script)\n        else:\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = (\n                \"\"\"var $elements = document.querySelectorAll('%s');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                $elements[index].style.display=\"none\";\n                $elements[index].style.visibility=\"hidden\";}\"\"\"\n                % css_selector\n            )\n            self.execute_script(script)\n\n    def show_element(self, selector, by=\"css selector\"):\n        \"\"\"Show the first element on the page that matches the selector.\"\"\"\n        self.__check_scope()\n        element = None\n        with suppress(Exception):\n            self.wait_for_element_visible(\"body\", timeout=1.5)\n            element = self.wait_for_element_present(selector, by=by, timeout=1)\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if \":contains(\" in css_selector and element:\n            script = (\n                'const e = arguments[0];'\n                'e.style.display=\"\";e.style.visibility=\"visible\";'\n            )\n            self.execute_script(script, element)\n        elif \":contains(\" in css_selector and not element:\n            selector = self.__make_css_match_first_element_only(css_selector)\n            script = \"\"\"jQuery('%s').show(0);\"\"\" % selector\n            self.safe_execute_script(script)\n        else:\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = (\n                'const e = document.querySelector(\"%s\");'\n                'e.style.display=\"\";e.style.visibility=\"visible\";'\n                % css_selector\n            )\n            self.execute_script(script)\n\n    def show_elements(self, selector, by=\"css selector\"):\n        \"\"\"Show all elements on the page that match the selector.\"\"\"\n        self.__check_scope()\n        with suppress(Exception):\n            self.wait_for_element_visible(\"body\", timeout=1.5)\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if \":contains(\" in css_selector:\n            script = \"\"\"jQuery('%s').show(0);\"\"\" % css_selector\n            self.safe_execute_script(script)\n        else:\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = (\n                \"\"\"var $elements = document.querySelectorAll('%s');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                $elements[index].style.display=\"\";\n                $elements[index].style.visibility=\"visible\";}\"\"\"\n                % css_selector\n            )\n            self.execute_script(script)\n\n    def remove_element(self, selector, by=\"css selector\"):\n        \"\"\"Remove the first element on the page that matches the selector.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.remove_element(selector)\n            return\n        element = None\n        with suppress(Exception):\n            self.wait_for_element_visible(\"body\", timeout=1.5)\n            element = self.wait_for_element_present(\n                selector, by=by, timeout=0.5\n            )\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if \":contains(\" in css_selector and element:\n            script = (\n                'const e = arguments[0];'\n                'e.parentElement.removeChild(e);'\n            )\n            self.execute_script(script, element)\n        elif \":contains(\" in css_selector and not element:\n            selector = self.__make_css_match_first_element_only(css_selector)\n            script = \"\"\"jQuery('%s').remove();\"\"\" % selector\n            self.safe_execute_script(script)\n        else:\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = (\n                'const e = document.querySelector(\"%s\");'\n                'e.parentElement.removeChild(e);'\n                % css_selector\n            )\n            self.execute_script(script)\n\n    def remove_elements(self, selector, by=\"css selector\"):\n        \"\"\"Remove all elements on the page that match the selector.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            self.cdp.remove_elements(selector)\n            return\n        with suppress(Exception):\n            self.wait_for_element_visible(\"body\", timeout=1.5)\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if \":contains(\" in css_selector:\n            script = \"\"\"jQuery('%s').remove();\"\"\" % css_selector\n            self.safe_execute_script(script)\n        else:\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = (\n                \"\"\"var $elements = document.querySelectorAll('%s');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                $elements[index].remove();}\"\"\"\n                % css_selector\n            )\n            self.execute_script(script)\n\n    def ad_block(self):\n        \"\"\"Block ads that appear on the current web page.\"\"\"\n        from seleniumbase.config import ad_block_list\n\n        self.__check_scope()  # Using wait_for_RSC would cause an infinite loop\n        for css_selector in ad_block_list.AD_BLOCK_LIST:\n            css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n            css_selector = self.__escape_quotes_if_needed(css_selector)\n            script = (\n                \"\"\"var $elements = document.querySelectorAll('%s');\n                var index = 0, length = $elements.length;\n                for(; index < length; index++){\n                $elements[index].remove();}\"\"\"\n                % css_selector\n            )\n            try:\n                self.execute_script(script)\n            except Exception:\n                pass  # Don't fail test if ad_blocking fails\n\n    def show_file_choosers(self):\n        \"\"\"Display hidden file-chooser input fields on sites if present.\"\"\"\n        self.wait_for_ready_state_complete()\n        css_selector = 'input[type=\"file\"]'\n        try:\n            self.wait_for_element_present(\n                css_selector, timeout=settings.MINI_TIMEOUT\n            )\n        except Exception:\n            time.sleep(0.14)\n        if self.__needs_minimum_wait():\n            time.sleep(0.05)\n        try:\n            self.show_elements(css_selector)\n        except Exception:\n            time.sleep(0.16)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        script = (\n            \"\"\"var $elements = document.querySelectorAll('%s');\n            var index = 0, length = $elements.length;\n            for(; index < length; index++){\n            the_class = $elements[index].getAttribute('class');\n            new_class = the_class.replaceAll('hidden', 'visible');\n            $elements[index].setAttribute('class', new_class);}\"\"\"\n            % css_selector\n        )\n        with suppress(Exception):\n            self.execute_script(script)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"sh_fc\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n\n    def disable_beforeunload(self):\n        \"\"\"This prevents: \"Leave Site? Changes you made may not be saved.\"\n                          on Chromium browsers (Chrome or Edge).\n        SB already sets \"dom.disable_beforeunload\" for Firefox options.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if (\n            self.is_chromium()\n            and self.driver.current_url.startswith(\"http\")\n        ):\n            with suppress(Exception):\n                self.driver.execute_script(\"window.onbeforeunload=null;\")\n\n    def get_domain_url(self, url):\n        self.__check_scope()\n        return page_utils.get_domain_url(url)\n\n    def get_active_element_css(self):\n        return js_utils.get_active_element_css(self.driver)\n\n    def get_beautiful_soup(self, source=None):\n        \"\"\"BeautifulSoup is a toolkit for dissecting an HTML document\n        and extracting what you need. It's great for screen-scraping!\n        See: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ \"\"\"\n        from bs4 import BeautifulSoup\n\n        if not source:\n            with suppress(Exception):\n                self.wait_for_element_visible(\n                    \"body\", timeout=settings.MINI_TIMEOUT\n                )\n            source = self.get_page_source()\n        return BeautifulSoup(source, \"html.parser\")\n\n    def get_unique_links(self):\n        \"\"\"Get all unique links in the html of the page source.\n        Page links include those obtained from:\n        \"a\"->\"href\", \"img\"->\"src\", \"link\"->\"href\", and \"script\"->\"src\".\"\"\"\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.08)\n            if self.undetectable:\n                time.sleep(0.02)\n        with suppress(Exception):\n            self.wait_for_element_present(\"body\", timeout=1.5)\n            self.wait_for_element_visible(\"body\", timeout=1.5)\n        if self.__needs_minimum_wait():\n            time.sleep(0.25)\n            if self.undetectable:\n                time.sleep(0.123)\n        soup = self.get_beautiful_soup(self.get_page_source())\n        page_url = self.get_current_url()\n        return page_utils._get_unique_links(page_url, soup)\n\n    def get_link_status_code(\n        self,\n        link,\n        allow_redirects=False,\n        timeout=5,\n        verify=False,\n    ):\n        \"\"\"Get the status code of a link.\n        If the timeout is set to less than 1, it becomes 1.\n        If the timeout is exceeded by requests.head(), it will return a 404.\n        If \"verify\" is False, will ignore certificate errors.\n        For a list of available status codes, see:\n        https://en.wikipedia.org/wiki/List_of_HTTP_status_codes \"\"\"\n        if self.__requests_timeout:\n            timeout = self.__requests_timeout\n        if timeout < 1:\n            timeout = 1\n        return page_utils._get_link_status_code(\n            link,\n            allow_redirects=allow_redirects,\n            timeout=timeout,\n            verify=verify,\n        )\n\n    def assert_link_status_code_is_not_404(self, link):\n        status_code = str(self.get_link_status_code(link))\n        bad_link_str = 'Error: \"%s\" returned a 404!' % link\n        self.assertNotEqual(status_code, \"404\", bad_link_str)\n\n    def __get_link_if_404_error(self, link):\n        status_code = str(self.get_link_status_code(link))\n        if status_code == \"404\":\n            # Verify again to be sure. (In case of multi-threading overload.)\n            status_code = str(self.get_link_status_code(link))\n            if status_code == \"404\":\n                return link\n            else:\n                return None\n        else:\n            return None\n\n    def assert_no_404_errors(self, multithreaded=True, timeout=None):\n        \"\"\"Assert no 404 errors from page links obtained from:\n        \"a\"->\"href\", \"img\"->\"src\", \"link\"->\"href\", and \"script\"->\"src\".\n        Timeout is on a per-link basis using the \"requests\" library.\n        If timeout is None, uses the one set in get_link_status_code().\n        (That timeout value is currently set to 5 seconds per link.)\n        (A 404 error represents a broken link on a web page.)\"\"\"\n        all_links = self.get_unique_links()\n        links = []\n        for link in all_links:\n            if (\n                \"data:\" not in link\n                and \"tel:\" not in link\n                and \"mailto:\" not in link\n                and \"javascript:\" not in link\n                and \"://fonts.gstatic.com\" not in link\n                and \"://fonts.googleapis.com\" not in link\n                and \"://googleads.g.doubleclick.net\" not in link\n            ):\n                links.append(link)\n        if timeout:\n            if not isinstance(timeout, (int, float)):\n                raise Exception('Expecting a numeric value for \"timeout\"!')\n            if timeout < 0:\n                raise Exception('The \"timeout\" cannot be a negative number!')\n            self.__requests_timeout = timeout\n        broken_links = []\n        if multithreaded:\n            from multiprocessing.dummy import Pool as ThreadPool\n\n            pool = ThreadPool(10)\n            results = pool.map(self.__get_link_if_404_error, links)\n            pool.close()\n            pool.join()\n            for result in results:\n                if result:\n                    broken_links.append(result)\n        else:\n            broken_links = []\n            for link in links:\n                if self.__get_link_if_404_error(link):\n                    broken_links.append(link)\n        self.__requests_timeout = None  # Reset the requests.head() timeout\n        if len(broken_links) > 0:\n            broken_links = sorted(broken_links)\n            bad_links_str = \"\\n\".join(broken_links)\n            if len(broken_links) == 1:\n                self.fail(\"Broken link detected:\\n%s\" % bad_links_str)\n            elif len(broken_links) > 1:\n                self.fail(\"Broken links detected:\\n%s\" % bad_links_str)\n        if self.demo_mode:\n            a_t = \"ASSERT NO 404 ERRORS\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_no_404_errors(self._language)\n            messenger_post = \"<b>%s</b>\" % a_t\n            self.__highlight_with_assert_success(messenger_post, \"html\")\n\n    def print_unique_links_with_status_codes(self):\n        \"\"\"Finds all unique links in the html of the page source\n        and then prints out those links with their status codes.\n        Format:  [\"link\"  ->  \"status_code\"]  (per line)\n        Page links include those obtained from:\n        \"a\"->\"href\", \"img\"->\"src\", \"link\"->\"href\", and \"script\"->\"src\".\"\"\"\n        page_url = self.get_current_url()\n        soup = self.get_beautiful_soup(self.get_page_source())\n        page_utils._print_unique_links_with_status_codes(page_url, soup)\n\n    def __fix_unicode_conversion(self, text):\n        \"\"\"Fixing Chinese characters when converting from PDF to HTML.\"\"\"\n        text = text.replace(\"\\u2f8f\", \"\\u884c\")\n        text = text.replace(\"\\u2f45\", \"\\u65b9\")\n        text = text.replace(\"\\u2f08\", \"\\u4eba\")\n        text = text.replace(\"\\u2f70\", \"\\u793a\")\n        text = text.replace(\"\\xe2\\xbe\\x8f\", \"\\xe8\\xa1\\x8c\")\n        text = text.replace(\"\\xe2\\xbd\\xb0\", \"\\xe7\\xa4\\xba\")\n        text = text.replace(\"\\xe2\\xbe\\x8f\", \"\\xe8\\xa1\\x8c\")\n        text = text.replace(\"\\xe2\\xbd\\x85\", \"\\xe6\\x96\\xb9\")\n        return text\n\n    def __get_type_checked_text(self, text):\n        \"\"\"Do type-checking on text. Then return it when valid.\n        If the text is acceptable, return the text or str(text).\n        If the text is not acceptable, raise a Python Exception.\"\"\"\n        if isinstance(text, str):\n            return text\n        elif isinstance(text, (int, float)):\n            return str(text)  # Convert num to string\n        elif isinstance(text, bool):\n            raise Exception(\"text must be a string! Boolean found!\")\n        elif type(text).__name__ == \"NoneType\":\n            raise Exception(\"text must be a string! NoneType found!\")\n        elif isinstance(text, list):\n            raise Exception(\"text must be a string! List found!\")\n        elif isinstance(text, tuple):\n            raise Exception(\"text must be a string! Tuple found!\")\n        elif isinstance(text, set):\n            raise Exception(\"text must be a string! Set found!\")\n        elif isinstance(text, dict):\n            raise Exception(\"text must be a string! Dict found!\")\n        else:\n            return str(text)\n\n    def get_pdf_text(\n        self,\n        pdf,\n        page=None,\n        maxpages=None,\n        password=None,\n        codec=\"utf-8\",\n        wrap=False,\n        nav=False,\n        override=False,\n        caching=True,\n    ):\n        \"\"\"Gets text from a PDF file.\n        PDF can be either a URL or a file path on the local file system.\n        @Params\n        pdf - The URL or file path of the PDF file.\n        page - The page number (or a list of page numbers) of the PDF.\n                If a page number is provided, looks only at that page.\n                    (1 is the first page, 2 is the second page, etc.)\n                If no page number is provided, returns all PDF text.\n        maxpages - Instead of providing a page number, you can provide\n                   the number of pages to use from the beginning.\n        password - If the PDF is password-protected, enter it here.\n        codec - The compression format for character encoding.\n                (The default codec used by this method is 'utf-8'.)\n        wrap - Replaces ' \\n' with ' ' so that individual sentences\n               from a PDF don't get broken up into separate lines when\n               getting converted into text format.\n        nav - If PDF is a URL, navigates to the URL in the browser first.\n              (Not needed because the PDF will be downloaded anyway.)\n        override - If the PDF file to be downloaded already exists in the\n                   downloaded_files/ folder, that PDF will be used\n                   instead of downloading it again.\n        caching - If resources should be cached via pdfminer.\"\"\"\n        import warnings\n\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n            pip_find_lock = fasteners.InterProcessLock(\n                constants.PipInstall.FINDLOCK\n            )\n            with pip_find_lock:\n                with suppress(Exception):\n                    shared_utils.make_writable(constants.PipInstall.FINDLOCK)\n                try:\n                    from pdfminer.high_level import extract_text\n                except Exception:\n                    shared_utils.pip_install(\"pdfminer.six\")\n                    from pdfminer.high_level import extract_text\n        if not password:\n            password = \"\"\n        if not maxpages:\n            maxpages = 0\n        if not pdf.lower().endswith(\".pdf\"):\n            raise Exception(\"%s is not a PDF file! (Expecting a .pdf)\" % pdf)\n        file_path = None\n        if page_utils.is_valid_url(pdf):\n            downloads_folder = download_helper.get_downloads_folder()\n            if nav:\n                if self.get_current_url() != pdf:\n                    self.open(pdf)\n            file_name = pdf.split(\"/\")[-1]\n            file_path = os.path.join(downloads_folder, file_name)\n            if not os.path.exists(file_path):\n                self.download_file(pdf)\n            elif override:\n                self.download_file(pdf)\n        else:\n            if not os.path.exists(pdf):\n                raise Exception(\"%s is not a valid URL or file path!\" % pdf)\n            file_path = os.path.abspath(pdf)\n        page_search = None  # (Pages are delimited by '\\x0c')\n        if isinstance(page, list):\n            pages = page\n            page_search = []\n            for page in pages:\n                page_search.append(page - 1)\n        elif isinstance(page, int):\n            page = page - 1\n            if page < 0:\n                page = 0\n            page_search = [page]\n        else:\n            page_search = None\n        logging.getLogger(\"pdfminer\").setLevel(logging.ERROR)\n        pdf_text = extract_text(\n            file_path,\n            password=\"\",\n            page_numbers=page_search,\n            maxpages=maxpages,\n            caching=caching,\n            codec=codec,\n        )\n        pdf_text = self.__fix_unicode_conversion(pdf_text)\n        if wrap:\n            pdf_text = pdf_text.replace(\" \\n\", \" \")\n        return pdf_text.strip()\n\n    def assert_pdf_text(\n        self,\n        pdf,\n        text,\n        page=None,\n        maxpages=None,\n        password=None,\n        codec=\"utf-8\",\n        wrap=True,\n        nav=False,\n        override=False,\n        caching=True,\n    ):\n        \"\"\"Asserts text in a PDF file.\n        PDF can be either a URL or a file path on the local file system.\n        @Params\n        pdf - The URL or file path of the PDF file.\n        text - The expected text to verify in the PDF.\n        page - The page number of the PDF to use (optional).\n                If a page number is provided, looks only at that page.\n                    (1 is the first page, 2 is the second page, etc.)\n                If no page number is provided, looks at all the pages.\n        maxpages - Instead of providing a page number, you can provide\n                   the number of pages to use from the beginning.\n        password - If the PDF is password-protected, enter it here.\n        codec - The compression format for character encoding.\n                (The default codec used by this method is 'utf-8'.)\n        wrap - Replaces ' \\n' with ' ' so that individual sentences\n               from a PDF don't get broken up into separate lines when\n               getting converted into text format.\n        nav - If PDF is a URL, navigates to the URL in the browser first.\n              (Not needed because the PDF will be downloaded anyway.)\n        override - If the PDF file to be downloaded already exists in the\n                   downloaded_files/ folder, that PDF will be used\n                   instead of downloading it again.\n        caching - If resources should be cached via pdfminer.\"\"\"\n        text = self.__fix_unicode_conversion(text)\n        if not codec:\n            codec = \"utf-8\"\n        pdf_text = self.get_pdf_text(\n            pdf,\n            page=page,\n            maxpages=maxpages,\n            password=password,\n            codec=codec,\n            wrap=wrap,\n            nav=nav,\n            override=override,\n            caching=caching,\n        )\n        if isinstance(page, int):\n            if text not in pdf_text:\n                self.fail(\n                    \"PDF [%s] is missing expected text [%s] on \"\n                    \"page [%s]!\" % (pdf, text, page)\n                )\n        else:\n            if text not in pdf_text:\n                self.fail(\n                    \"PDF [%s] is missing expected text [%s]!\" % (pdf, text)\n                )\n        return True\n\n    def create_folder(self, folder):\n        \"\"\"Creates a folder of the given name if it doesn't already exist.\"\"\"\n        if folder.endswith(\"/\"):\n            folder = folder[:-1]\n        if len(folder) < 1:\n            raise Exception(\"Minimum folder name length = 1.\")\n        if not os.path.exists(folder):\n            with suppress(Exception):\n                os.makedirs(folder)\n\n    def choose_file(\n        self, selector, file_path, by=\"css selector\", timeout=None\n    ):\n        \"\"\"This method is used to choose a file to upload to a website.\n        It works by populating a file-chooser \"input\" field of type=\"file\".\n        A relative file_path will get converted into an absolute file_path.\n\n        Example usage:\n            self.choose_file('input[type=\"file\"]', \"my_dir/my_file.txt\") \"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        abs_path = os.path.abspath(file_path)\n        if self.__needs_minimum_wait():\n            time.sleep(0.02)\n        element = self.wait_for_element_present(\n            selector, by=by, timeout=timeout\n        )\n        if self.__needs_minimum_wait():\n            time.sleep(0.08)\n        if self.is_element_visible(selector, by=by):\n            self.__demo_mode_highlight_if_active(selector, by)\n            if not self.demo_mode and not self.slow_mode:\n                self.__scroll_to_element(element, selector, by)\n        else:\n            choose_file_selector = 'input[type=\"file\"]'\n            if self.is_element_present(choose_file_selector):\n                if not self.is_element_visible(choose_file_selector):\n                    self.show_file_choosers()\n                    if self.is_element_visible(selector, by=by):\n                        self.__demo_mode_highlight_if_active(selector, by)\n                        if not self.demo_mode and not self.slow_mode:\n                            self.__scroll_to_element(element, selector, by)\n        pre_action_url = None\n        if self.demo_mode:\n            with suppress(Exception):\n                pre_action_url = self.driver.current_url\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                sele_file_path = [selector, file_path]\n                action = [\"chfil\", sele_file_path, origin, time_stamp]\n                self.__extra_actions.append(action)\n        if isinstance(abs_path, (int, float)):\n            abs_path = str(abs_path)\n        try:\n            if self.browser == \"safari\":\n                try:\n                    element.send_keys(abs_path)\n                except NoSuchElementException:\n                    pass  # May get this error on Safari even if upload works.\n            else:\n                element.send_keys(abs_path)\n        except (Stale_Exception, ENI_Exception):\n            self.wait_for_ready_state_complete()\n            time.sleep(0.16)\n            element = self.wait_for_element_present(\n                selector, by=by, timeout=timeout\n            )\n            if self.browser == \"safari\":\n                try:\n                    element.send_keys(abs_path)\n                except NoSuchElementException:\n                    pass  # May get this error on Safari even if upload works.\n            else:\n                element.send_keys(abs_path)\n        if self.demo_mode:\n            if self.driver.current_url != pre_action_url:\n                if not js_utils.is_jquery_activated(self.driver):\n                    js_utils.add_js_link(self.driver, constants.JQuery.MIN_JS)\n                self.__demo_mode_pause_if_active()\n            else:\n                self.__demo_mode_pause_if_active(tiny=True)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def save_element_as_image_file(\n        self, selector, file_name, folder=None, overlay_text=\"\"\n    ):\n        \"\"\"Take a screenshot of an element and save it as an image file.\n        If no folder is specified, will save it to the current folder.\n        If overlay_text is provided, will add that to the saved image.\"\"\"\n        element = self.wait_for_element_visible(selector)\n        element_png = element.screenshot_as_png\n        if len(file_name.split(\".\")[0]) < 1:\n            raise Exception(\"Error: file_name length must be > 0.\")\n        if not file_name.endswith(\".png\"):\n            file_name = file_name + \".png\"\n        image_file_path = None\n        if folder:\n            if folder.endswith(os.sep):\n                folder = folder[:-1]\n            if len(folder) > 0:\n                self.create_folder(folder)\n                image_file_path = os.path.join(folder, file_name)\n        if not image_file_path:\n            image_file_path = file_name\n        with open(image_file_path, \"wb\") as file:\n            file.write(element_png)\n        # Add a text overlay if given\n        if isinstance(overlay_text, str) and len(overlay_text) > 0:\n            pip_find_lock = fasteners.InterProcessLock(\n                constants.PipInstall.FINDLOCK\n            )\n            with pip_find_lock:\n                with suppress(Exception):\n                    shared_utils.make_writable(constants.PipInstall.FINDLOCK)\n                try:\n                    from PIL import Image, ImageDraw\n                except Exception:\n                    shared_utils.pip_install(\"Pillow\")\n                    try:\n                        from PIL import Image, ImageDraw\n                    except Exception as e:\n                        if (\n                            sys.version_info >= (3, 12)\n                            and \"symbol not found\" in e.msg\n                        ):\n                            raise Exception(\n                                \"PIL / Pillow is not supported on Python %s\"\n                                % \".\".join(str(s) for s in sys.version_info)\n                            )\n                        else:\n                            raise\n            text_rows = overlay_text.split(\"\\n\")\n            len_text_rows = len(text_rows)\n            max_width = 0\n            for text_row in text_rows:\n                if len(text_row) > max_width:\n                    max_width = len(text_row)\n            image = Image.open(image_file_path)\n            draw = ImageDraw.Draw(image)\n            draw.rectangle(\n                (0, 0, int(max_width * 8.32) + 10, 23 * len_text_rows + 2),\n                fill=(236, 236, 28),\n            )\n            draw.text(\n                (4, 2),  # Coordinates\n                overlay_text,  # Text\n                fill=(8, 38, 176),  # Color\n                font_size=18,  # Font Size\n            )\n            image.save(image_file_path, \"PNG\", quality=100, optimize=True)\n\n    def download_file(self, file_url, destination_folder=None):\n        \"\"\"Downloads the file from the url to the destination folder.\n        If no destination folder is specified, the default one is used.\n        (The default folder for downloads is \"./downloaded_files\")\"\"\"\n        download_file_lock = fasteners.InterProcessLock(\n            constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n        )\n        with download_file_lock:\n            with suppress(Exception):\n                shared_utils.make_writable(\n                    constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n                )\n            if not destination_folder:\n                destination_folder = constants.Files.DOWNLOADS_FOLDER\n            if not os.path.exists(destination_folder):\n                os.makedirs(destination_folder)\n        agent = self.get_user_agent()\n        headers = {\"user-agent\": agent}\n        page_utils._download_file_to(\n            file_url, destination_folder, headers=headers\n        )\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                url_dest = [file_url, destination_folder]\n                action = [\"do_fi\", url_dest, origin, time_stamp]\n                self.__extra_actions.append(action)\n\n    def save_file_as(self, file_url, new_file_name, destination_folder=None):\n        \"\"\"Similar to self.download_file(), except that you get to rename the\n        file being downloaded to whatever you want.\"\"\"\n        download_file_lock = fasteners.InterProcessLock(\n            constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n        )\n        with download_file_lock:\n            with suppress(Exception):\n                shared_utils.make_writable(\n                    constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n                )\n            if not destination_folder:\n                destination_folder = constants.Files.DOWNLOADS_FOLDER\n            if not os.path.exists(destination_folder):\n                os.makedirs(destination_folder)\n        agent = self.get_user_agent()\n        headers = {\"user-agent\": agent}\n        page_utils._download_file_to(\n            file_url, destination_folder, new_file_name, headers=headers\n        )\n\n    def save_data_as(self, data, file_name, destination_folder=None):\n        \"\"\"Saves the data specified to the file specified.\n        If no destination folder is specified, the default one is used.\n        (The default folder = \"./downloaded_files\")\n        Use \".\" as the destination folder for the current directory.\"\"\"\n        if not destination_folder:\n            destination_folder = constants.Files.DOWNLOADS_FOLDER\n        page_utils._save_data_as(data, destination_folder, file_name)\n\n    def append_data_to_file(self, data, file_name, destination_folder=None):\n        \"\"\"Appends the data specified to the file specified.\n        If no destination folder is specified, the default one is used.\n        (The default folder = \"./downloaded_files\")\n        Use \".\" as the folder for the current directory.\"\"\"\n        if not destination_folder:\n            destination_folder = constants.Files.DOWNLOADS_FOLDER\n        page_utils._append_data_to_file(data, destination_folder, file_name)\n\n    def get_file_data(self, file_name, folder=None):\n        \"\"\"Gets the data from the file specified.\n        If no folder is specified, the default one is used.\n        The default folder = \"./downloaded_files\"\n        For the \"latest_logs/\" test data folders, use:\n            self.data_path OR self.data_abspath\n        Use \".\" as the folder for the current directory.\"\"\"\n        if not folder:\n            folder = constants.Files.DOWNLOADS_FOLDER\n        return page_utils._get_file_data(folder, file_name)\n\n    def print_to_pdf(self, name, folder=None):\n        \"\"\"Saves the current page as a PDF.\n        If no folder is specified, uses the folder where pytest was called.\n        If the folder provided doesn't exist, it will get created.\n        @Params\n        name - The name to give the PDF file. Must end in \".pdf\".\n        folder - The directory where you want to save the PDF.\"\"\"\n        import base64\n        from selenium.webdriver.common.print_page_options import PrintOptions\n\n        if not name.lower().endswith(\".pdf\"):\n            raise Exception('PDF name {%s} must end in \".pdf\"!)' % name)\n        download_file_lock = fasteners.InterProcessLock(\n            constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n        )\n        if self.__is_cdp_swap_needed():\n            with download_file_lock:\n                with suppress(Exception):\n                    shared_utils.make_writable(\n                        constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n                    )\n                if folder and not os.path.exists(folder):\n                    os.makedirs(folder)\n                self.cdp.print_to_pdf(name, folder)\n            return\n        self.wait_for_ready_state_complete()\n        print_options = PrintOptions()\n        pdf_base64 = self.driver.print_page(print_options)\n        with download_file_lock:\n            with suppress(Exception):\n                shared_utils.make_writable(\n                    constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n                )\n            if folder and not os.path.exists(folder):\n                os.makedirs(folder)\n            filename = name\n            if folder:\n                filename = os.path.join(folder, name)\n            with open(filename, \"wb\") as f:\n                f.write(base64.b64decode(pdf_base64))\n\n    def get_downloads_folder(self):\n        \"\"\"Returns the path of the SeleniumBase \"downloaded_files/\" folder.\n        Calling self.download_file(file_url) will put that file in here.\n        With the exception of Safari, IE, and Chromium Guest Mode,\n            any clicks that download files will also use this folder\n            rather than using the browser's default \"downloads/\" path.\"\"\"\n        self.__check_scope()\n        return download_helper.get_downloads_folder()\n\n    def get_browser_downloads_folder(self):\n        \"\"\"Returns the path that is used when a click initiates a download.\n        SeleniumBase overrides the system path to be \"downloaded_files/\"\n        The path can't be changed on Safari, IE, or Chromium Guest Mode.\n        The same problem occurs when using an out-of-date chromedriver.\"\"\"\n        self.__check_scope()\n        if self.is_chromium() and self.guest_mode and not self.headless:\n            # Guest Mode (non-headless) can force the default downloads path\n            return os.path.join(os.path.expanduser(\"~\"), \"downloads\")\n        elif self.browser == \"safari\" or self.browser == \"ie\":\n            # Can't change the system [Downloads Folder] on Safari or IE\n            return os.path.join(os.path.expanduser(\"~\"), \"downloads\")\n        elif (\n            \"chrome\" in self.driver.capabilities\n            and int(self.get_chromedriver_version().split(\".\")[0]) < 73\n            and self.headless\n        ):\n            return os.path.join(os.path.expanduser(\"~\"), \"downloads\")\n        elif (\n            \"chrome\" in self.driver.capabilities\n            and int(self.get_chromedriver_version().split(\".\")[0]) >= 110\n            and int(self.get_chromedriver_version().split(\".\")[0]) <= 112\n            and self.headless\n        ):\n            return os.path.abspath(\".\")\n        else:\n            return download_helper.get_downloads_folder()\n        return os.path.join(os.path.expanduser(\"~\"), \"downloads\")\n\n    def get_downloaded_files(self, regex=None, browser=False):\n        \"\"\"Returns a list of files in the [Downloads Folder].\n        Depending on settings, that dir may have other files.\n        If regex is provided, uses that to filter results.\"\"\"\n        df = self.get_downloads_folder()\n        if browser:\n            df = self.get_browser_downloads_folder()\n        if not os.path.exists(df):\n            return []\n        elif regex:\n            return [fn for fn in os.listdir(df) if re.match(regex, fn)]\n        else:\n            return os.listdir(df)\n\n    def get_path_of_downloaded_file(self, file, browser=False):\n        \"\"\"Returns the full OS path of the downloaded file.\"\"\"\n        self.__check_scope()\n        if browser:\n            return os.path.join(self.get_browser_downloads_folder(), file)\n        else:\n            return os.path.join(self.get_downloads_folder(), file)\n\n    def get_data_from_downloaded_file(self, file, timeout=None, browser=False):\n        \"\"\"Returns the contents of the downloaded file specified.\"\"\"\n        self.assert_downloaded_file(file, timeout=timeout, browser=browser)\n        fpath = self.get_path_of_downloaded_file(file, browser=browser)\n        file_io_lock = fasteners.InterProcessLock(\n            constants.MultiBrowser.FILE_IO_LOCK\n        )\n        with file_io_lock:\n            with suppress(Exception):\n                shared_utils.make_writable(constants.MultiBrowser.FILE_IO_LOCK)\n            with open(fpath, \"r\") as f:\n                data = f.read().strip()\n        return data\n\n    def is_downloaded_file_present(self, file, browser=False):\n        \"\"\"Returns True if the file exists in the pre-set [Downloads Folder].\n        For browser click-initiated downloads, SeleniumBase will override\n            the system [Downloads Folder] to be \"./downloaded_files/\",\n            but that path can't be overridden when using Safari, IE,\n            or Chromium Guest Mode, which keeps the default system path.\n        self.download_file(file_url) will always use \"./downloaded_files/\".\n        @Params\n        file - The filename of the downloaded file.\n        browser - If True, uses the path set by click-initiated downloads.\n                  If False, uses the self.download_file(file_url) path.\n                  Those paths are usually the same. (browser-dependent).\"\"\"\n        return os.path.exists(\n            self.get_path_of_downloaded_file(file, browser=browser)\n        )\n\n    def is_downloaded_file_regex_present(self, regex, browser=False):\n        \"\"\"Returns True if the filename regex exists in the [Downloads Folder].\n        Uses Python regex via the \"re\" library for string-matching on the name.\n        @Params\n        regex - The filename regex of the downloaded file.\n        browser - If True, uses the path set by click-initiated downloads.\n                  If False, uses the self.download_file(file_url) path.\n                  Those paths are usually the same. (browser-dependent).\"\"\"\n        df = self.get_downloads_folder()\n        if browser:\n            df = self.get_browser_downloads_folder()\n        matches = [fn for fn in os.listdir(df) if re.match(regex, fn)]\n        return len(matches) >= 1\n\n    def delete_downloaded_file_if_present(self, file, browser=False):\n        \"\"\"Deletes the file from the [Downloads Folder] if the file exists.\n        For browser click-initiated downloads, SeleniumBase will override\n            the system [Downloads Folder] to be \"./downloaded_files/\",\n            but that path can't be overridden when using Safari, IE,\n            or Chromium Guest Mode, which keeps the default system path.\n        self.download_file(file_url) will always use \"./downloaded_files/\".\n        @Params\n        file - The filename to be deleted from the [Downloads Folder].\n        browser - If True, uses the path set by click-initiated downloads.\n                  If False, uses the self.download_file(file_url) path.\n                  Those paths are usually the same. (browser-dependent).\"\"\"\n        if self.is_downloaded_file_present(file, browser=browser):\n            file_path = self.get_path_of_downloaded_file(file, browser=browser)\n            with suppress(Exception):\n                os.remove(file_path)\n\n    def delete_downloaded_file(self, file, browser=False):\n        \"\"\"Same as self.delete_downloaded_file_if_present()\n        Deletes the file from the [Downloads Folder] if the file exists.\n        For browser click-initiated downloads, SeleniumBase will override\n            the system [Downloads Folder] to be \"./downloaded_files/\",\n            but that path can't be overridden when using Safari, IE,\n            or Chromium Guest Mode, which keeps the default system path.\n        self.download_file(file_url) will always use \"./downloaded_files/\".\n        @Params\n        file - The filename to be deleted from the [Downloads Folder].\n        browser - If True, uses the path set by click-initiated downloads.\n                  If False, uses the self.download_file(file_url) path.\n                  Those paths are usually the same. (browser-dependent).\"\"\"\n        if self.is_downloaded_file_present(file, browser=browser):\n            file_path = self.get_path_of_downloaded_file(file, browser=browser)\n            with suppress(Exception):\n                os.remove(file_path)\n\n    def assert_downloaded_file(self, file, timeout=None, browser=False):\n        \"\"\"Asserts that the file exists in SeleniumBase's [Downloads Folder].\n        For browser click-initiated downloads, SeleniumBase will override\n            the system [Downloads Folder] to be \"./downloaded_files/\",\n            but that path can't be overridden when using Safari, IE,\n            or Chromium Guest Mode, which keeps the default system path.\n        self.download_file(file_url) will always use \"./downloaded_files/\".\n        @Params\n        file - The filename of the downloaded file.\n        timeout - The time (seconds) to wait for the download to complete.\n        browser - If True, uses the path set by click-initiated downloads.\n                  If False, uses the self.download_file(file_url) path.\n                  Those paths are usually the same. (browser-dependent).\"\"\"\n        self.__check_scope()\n        df = self.get_downloads_folder()\n        if browser:\n            df = self.get_browser_downloads_folder()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        downloaded_file_path = self.get_path_of_downloaded_file(file, browser)\n        found = False\n        for x in range(int(timeout)):\n            shared_utils.check_if_time_limit_exceeded()\n            try:\n                self.assertTrue(\n                    os.path.exists(downloaded_file_path),\n                    \"File [%s] was not found in the downloads folder [%s]!\"\n                    % (file, df),\n                )\n                found = True\n                break\n            except Exception:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(1)\n        if not found and not os.path.exists(downloaded_file_path):\n            plural = \"s\"\n            if timeout == 1:\n                plural = \"\"\n            message = (\n                \"File {%s} was not found in the downloads folder {%s} \"\n                \"after %s second%s! (Or the download didn't complete!)\"\n                % (file, df, timeout, plural)\n            )\n            page_actions.timeout_exception(\"NoSuchFileException\", message)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"as_df\", file, origin, time_stamp]\n                self.__extra_actions.append(action)\n        if self.demo_mode:\n            messenger_post = \"<b>ASSERT DOWNLOADED FILE</b>: [%s]\" % file\n            with suppress(Exception):\n                js_utils.activate_jquery(self.driver)\n                js_utils.post_messenger_success_message(\n                    self.driver, messenger_post, self.message_duration\n                )\n\n    def assert_downloaded_file_regex(self, regex, timeout=None, browser=False):\n        \"\"\"Assert the filename regex exists in SeleniumBase's Downloads Folder.\n        Uses Python regex via the \"re\" library for string-matching on the name.\n        @Params\n        regex - The filename regex of the downloaded file.\n        timeout - The time (seconds) to wait for the download to complete.\n        browser - If True, uses the path set by click-initiated downloads.\n                  If False, uses the self.download_file(file_url) path.\n                  Those paths are usually the same. (browser-dependent).\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        found = False\n        df = self.get_downloads_folder()\n        if browser:\n            df = self.get_browser_downloads_folder()\n        for x in range(int(timeout)):\n            shared_utils.check_if_time_limit_exceeded()\n            try:\n                matches = [fn for fn in os.listdir(df) if re.match(regex, fn)]\n                self.assertTrue(\n                    len(matches) >= 1,\n                    \"Regex [%s] was not found in the downloads folder [%s]!\"\n                    % (regex, df),\n                )\n                found = True\n                break\n            except Exception:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(1)\n        if not found:\n            plural = \"s\"\n            if timeout == 1:\n                plural = \"\"\n            message = (\n                \"Regex {%s} was not found in the downloads folder {%s} \"\n                \"after %s second%s! (Or the download didn't complete!)\"\n                % (regex, df, timeout, plural)\n            )\n            page_actions.timeout_exception(\"NoSuchFileException\", message)\n        if self.demo_mode:\n            messenger_post = (\n                \"<b>ASSERT DOWNLOADED FILE REGEX</b>: [%s]\" % regex\n            )\n            with suppress(Exception):\n                js_utils.activate_jquery(self.driver)\n                js_utils.post_messenger_success_message(\n                    self.driver, messenger_post, self.message_duration\n                )\n\n    def assert_data_in_downloaded_file(\n        self, data, file, timeout=None, browser=False\n    ):\n        \"\"\"Assert that the expected data exists in the downloaded file.\"\"\"\n        self.assert_downloaded_file(file, timeout=timeout, browser=browser)\n        expected = data.strip()\n        actual = self.get_data_from_downloaded_file(file, browser=browser)\n        if expected not in actual:\n            message = (\n                \"Expected data [%s] is not in downloaded file [%s]!\"\n                % (expected, file)\n            )\n            raise Exception(message)\n        return True\n\n    def assert_true(self, expr, msg=None):\n        \"\"\"Asserts that the expression is True.\n        Raises an exception if the statement if False.\"\"\"\n        self.assertTrue(expr, msg=msg)\n\n    def assert_false(self, expr, msg=None):\n        \"\"\"Asserts that the expression is False.\n        Raises an exception if the statement if True.\"\"\"\n        self.assertFalse(expr, msg=msg)\n\n    def assert_equal(self, first, second, msg=None):\n        \"\"\"Asserts that the two values are equal.\n        Raises an exception if the values are not equal.\"\"\"\n        self.assertEqual(first, second, msg=msg)\n\n    def assert_not_equal(self, first, second, msg=None):\n        \"\"\"Asserts that the two values are not equal.\n        Raises an exception if the values are equal.\"\"\"\n        self.assertNotEqual(first, second, msg=msg)\n\n    def assert_in(self, first, second, msg=None):\n        \"\"\"Asserts that the first string is in the second string.\n        Raises an exception if the first string is not in the second.\"\"\"\n        self.assertIn(first, second, msg=msg)\n\n    def assert_not_in(self, first, second, msg=None):\n        \"\"\"Asserts that the first string is not in the second string.\n        Raises an exception if the first string is in the second string.\"\"\"\n        self.assertNotIn(first, second, msg=msg)\n\n    def assert_raises(self, *args, **kwargs):\n        \"\"\"Asserts that the following block of code raises an exception.\n        Raises an exception if the block of code has no exception.\n        Usage Example =>\n            # Verify that the expected exception is raised.\n            with self.assert_raises(Exception):\n                raise Exception(\"Expected Exception!\") \"\"\"\n        return self.assertRaises(*args, **kwargs)\n\n    def wait_for_attribute(\n        self, selector, attribute, value=None, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Raises an exception if the element attribute/value is not found.\n        If the value is not specified, the attribute only needs to exist.\n        Returns the element that contains the attribute if successful.\n        Default timeout = LARGE_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_element_attribute(selector, attribute, value)\n            return\n        elif self.__is_shadow_selector(selector):\n            return self.__wait_for_shadow_attribute_present(\n                selector, attribute, value=value, timeout=timeout\n            )\n        return page_actions.wait_for_attribute(\n            self.driver,\n            selector,\n            attribute,\n            value=value,\n            by=by,\n            timeout=timeout,\n        )\n\n    def assert_attribute(\n        self, selector, attribute, value=None, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Raises an exception if the element attribute/value is not found.\n        If the value is not specified, the attribute only needs to exist.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_element_attribute(selector, attribute, value)\n            return\n        self.wait_for_attribute(\n            selector, attribute, value=value, by=by, timeout=timeout\n        )\n        if (\n            self.demo_mode\n            and not self.__is_shadow_selector(selector)\n            and self.is_element_visible(selector, by=by)\n        ):\n            a_a = \"ASSERT ATTRIBUTE\"\n            i_n = \"in\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_a = SD.translate_assert_attribute(self._language)\n                i_n = SD.translate_in(self._language)\n            if not value:\n                messenger_post = \"<b>%s</b>: [%s] %s %s: %s\" % (\n                    a_a,\n                    attribute,\n                    i_n,\n                    by.upper(),\n                    selector,\n                )\n            else:\n                messenger_post = '<b>%s</b>: [%s=\"%s\"] %s %s: %s' % (\n                    a_a,\n                    attribute,\n                    value,\n                    i_n,\n                    by.upper(),\n                    selector,\n                )\n            self.__highlight_with_assert_success(messenger_post, selector, by)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                value = value.replace(\"\\\\\", \"\\\\\\\\\")\n                sel_att_val = [selector, attribute, value]\n                action = [\"as_at\", sel_att_val, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_title(self, title):\n        \"\"\"Asserts that the web page title matches the expected title.\n        When a web page initially loads, the title starts as the URL,\n            but then the title switches over to the actual page title.\n        In Recorder Mode, this assertion is skipped because the Recorder\n            changes the page title to the selector of the hovered element.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_title(title)\n            return\n        self.wait_for_ready_state_complete()\n        expected = title.strip()\n        actual = self.get_page_title().strip()\n        error = (\n            \"Expected page title [%s] does not match the actual title [%s]!\"\n        )\n        if not self.recorder_mode:\n            try:\n                self.assertEqual(expected, actual, error % (expected, actual))\n            except Exception:\n                self.wait_for_ready_state_complete()\n                time.sleep(2)\n                actual = self.get_page_title().strip()\n                try:\n                    self.assertEqual(\n                        expected, actual, error % (expected, actual)\n                    )\n                except Exception:\n                    self.wait_for_ready_state_complete()\n                    time.sleep(2)\n                    actual = self.get_page_title().strip()\n                    self.assertEqual(\n                        expected, actual, error % (expected, actual)\n                    )\n        if self.demo_mode and not self.recorder_mode:\n            a_t = \"ASSERT TITLE\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_title(self._language)\n            messenger_post = \"<b>%s</b>: {%s}\" % (a_t, expected)\n            self.__highlight_with_assert_success(messenger_post, \"html\")\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"as_ti\", expected, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_title_contains(self, substring):\n        \"\"\"Asserts that the title substring appears in the web page title.\n        When a web page initially loads, the title starts as the URL,\n            but then the title switches over to the actual page title.\n        In Recorder Mode, this assertion is skipped because the Recorder\n            changes the page title to the selector of the hovered element.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_title_contains(substring)\n            return\n        self.wait_for_ready_state_complete()\n        expected = substring.strip()\n        actual = self.get_page_title().strip()\n        error = (\n            \"Expected title substring [%s] does not appear \"\n            \"in the actual page title [%s]!\"\n        )\n        if not self.recorder_mode:\n            try:\n                self.assertIn(expected, actual, error % (expected, actual))\n            except Exception:\n                self.wait_for_ready_state_complete()\n                time.sleep(2)\n                actual = self.get_page_title().strip()\n                try:\n                    self.assertIn(\n                        expected, actual, error % (expected, actual)\n                    )\n                except Exception:\n                    self.wait_for_ready_state_complete()\n                    time.sleep(2)\n                    actual = self.get_page_title().strip()\n                    self.assertIn(\n                        expected, actual, error % (expected, actual)\n                    )\n        if self.demo_mode and not self.recorder_mode:\n            a_t = \"ASSERT TITLE CONTAINS\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_title_contains(self._language)\n            messenger_post = \"<b>%s</b>: {%s}\" % (a_t, expected)\n            self.__highlight_with_assert_success(messenger_post, \"html\")\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"as_tc\", expected, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_url(self, url):\n        \"\"\"Asserts that the web page URL matches the expected URL.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_url(url)\n            return\n        self.wait_for_ready_state_complete()\n        expected = url.strip()\n        actual = self.get_current_url().strip()\n        error = \"Expected URL [%s] does not match the actual URL [%s]!\"\n        try:\n            self.assertEqual(expected, actual, error % (expected, actual))\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(2)\n            actual = self.get_current_url().strip()\n            try:\n                self.assertEqual(expected, actual, error % (expected, actual))\n            except Exception:\n                self.wait_for_ready_state_complete()\n                time.sleep(2)\n                actual = self.get_current_url().strip()\n                self.assertEqual(expected, actual, error % (expected, actual))\n        if self.demo_mode and not self.recorder_mode:\n            a_u = \"ASSERT URL\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_u = SD.translate_assert_url(self._language)\n            messenger_post = \"<b>%s</b>: {%s}\" % (a_u, expected)\n            self.__highlight_with_assert_success(messenger_post, \"html\")\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"a_url\", expected, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_url_contains(self, substring):\n        \"\"\"Asserts that the URL substring appears in the full URL.\"\"\"\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_url_contains(substring)\n            return\n        self.wait_for_ready_state_complete()\n        expected = substring.strip()\n        actual = self.get_current_url().strip()\n        error = (\n            \"Expected URL substring [%s] does not appear \"\n            \"in the full URL [%s]!\"\n        )\n        try:\n            self.assertIn(expected, actual, error % (expected, actual))\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(2)\n            actual = self.get_current_url().strip()\n            try:\n                self.assertIn(expected, actual, error % (expected, actual))\n            except Exception:\n                self.wait_for_ready_state_complete()\n                time.sleep(2)\n                actual = self.get_current_url().strip()\n                self.assertIn(expected, actual, error % (expected, actual))\n        if self.demo_mode and not self.recorder_mode:\n            a_u = \"ASSERT URL CONTAINS\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_u = SD.translate_assert_url_contains(self._language)\n            messenger_post = \"<b>%s</b>: {%s}\" % (a_u, expected)\n            self.__highlight_with_assert_success(messenger_post, \"html\")\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"a_u_c\", expected, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_no_js_errors(self, exclude=[]):\n        \"\"\"Asserts current URL has no \"SEVERE\"-level JavaScript errors.\n        Works ONLY on Chromium browsers (Chrome or Edge).\n        Does NOT work on Firefox, IE, Safari, or some other browsers:\n            * See https://github.com/SeleniumHQ/selenium/issues/1161\n        Based on the following Stack Overflow solution:\n            * https://stackoverflow.com/a/41150512/7058266\n        @Params\n        exclude -->\n            A list of substrings or a single comma-separated string of\n            substrings for filtering out error URLs that contain them.\n            URLs that contain any excluded substring will get excluded\n            from the final errors list that's used with the assertion.\n        Examples:\n            self.assert_no_js_errors()\n            self.assert_no_js_errors(exclude=[\"/api.\", \"/analytics.\"])\n            self.assert_no_js_errors(exclude=\"//api.go,/analytics.go\")\n            self.assert_no_js_errors(exclude=[\"Uncaught SyntaxError\"])\n            self.assert_no_js_errors(exclude=[\"TypeError\", \"SyntaxE\"]) \"\"\"\n        self.__check_scope()\n        if exclude and not isinstance(exclude, (list, tuple)):\n            exclude = str(exclude).replace(\" \", \"\").split(\",\")\n        time.sleep(0.1)  # May take a moment for errors to appear after loads.\n        try:\n            browser_logs = self.driver.get_log(\"browser\")\n        except (ValueError, WebDriverException):\n            # If unable to get browser logs, skip the assert and return.\n            return\n        messenger_library = \"//cdnjs.cloudflare.com/ajax/libs/messenger\"\n        underscore_library = \"//cdnjs.cloudflare.com/ajax/libs/underscore\"\n        errors = []\n        for entry in browser_logs:\n            if entry[\"level\"] == \"SEVERE\":\n                if (\n                    messenger_library not in entry[\"message\"]\n                    and underscore_library not in entry[\"message\"]\n                ):\n                    # Add errors if not caused by SeleniumBase dependencies\n                    if not exclude:\n                        errors.append(entry)\n                    else:\n                        found = False\n                        message = entry[\"message\"]\n                        for substring in exclude:\n                            substring = str(substring)\n                            if (\n                                len(substring) > 0\n                                and substring in message\n                            ):\n                                found = True\n                                break\n                        if not found:\n                            errors.append(entry)\n        if len(errors) > 0:\n            for n in range(len(errors)):\n                f_t_l_r = \" - Failed to load resource\"\n                u_c_s_e = \" Uncaught SyntaxError: \"\n                u_c_t_e = \" Uncaught TypeError: \"\n                if f_t_l_r in errors[n][\"message\"]:\n                    url = errors[n][\"message\"].split(f_t_l_r)[0]\n                    if \"status of 400\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 400 (Bad Request)\": url}\n                    elif \"status of 401\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 401 (Unauthorized)\": url}\n                    elif \"status of 402\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 402 (Payment Required)\": url}\n                    elif \"status of 403\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 403 (Forbidden)\": url}\n                    elif \"status of 404\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 404 (Not Found)\": url}\n                    elif \"status of 405\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 405 (Method Not Allowed)\": url}\n                    elif \"status of 406\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 406 (Not Acceptable)\": url}\n                    elif \"status of 407\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 407 (Proxy Auth Required)\": url}\n                    elif \"status of 408\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 408 (Request Timeout)\": url}\n                    elif \"status of 409\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 409 (Conflict)\": url}\n                    elif \"status of 410\" in errors[n][\"message\"]:\n                        errors[n] = {\"Error 410 (Gone)\": url}\n                    else:\n                        errors[n] = {\"Failed to load resource\": url}\n                elif u_c_s_e in errors[n][\"message\"]:\n                    url = errors[n][\"message\"].split(u_c_s_e)[0]\n                    error = errors[n][\"message\"].split(u_c_s_e)[1]\n                    errors[n] = {\"Uncaught SyntaxError (%s)\" % error: url}\n                elif u_c_t_e in errors[n][\"message\"]:\n                    url = errors[n][\"message\"].split(u_c_t_e)[0]\n                    error = errors[n][\"message\"].split(u_c_t_e)[1]\n                    errors[n] = {\"Uncaught TypeError (%s)\" % error: url}\n            er_str = str(errors)\n            er_str = er_str.replace(\"[{\", \"[\\n{\").replace(\"}, {\", \"},\\n{\")\n            current_url = self.get_current_url()\n            self.fail(\n                \"JavaScript errors found on %s => %s\" % (current_url, er_str)\n            )\n        if self.demo_mode:\n            if self.is_chromium():\n                a_t = \"ASSERT NO JS ERRORS\"\n                if self._language != \"English\":\n                    from seleniumbase.fixtures.words import SD\n\n                    a_t = SD.translate_assert_no_js_errors(self._language)\n                messenger_post = \"<b>%s</b>\" % a_t\n                self.__highlight_with_assert_success(messenger_post, \"html\")\n\n    def __activate_html_inspector(self):\n        self.wait_for_ready_state_complete()\n        time.sleep(0.05)\n        js_utils.activate_html_inspector(self.driver)\n\n    def inspect_html(self):\n        \"\"\"Inspects the Page HTML with HTML-Inspector.\n        (https://github.com/philipwalton/html-inspector)\n        (https://cdnjs.com/libraries/html-inspector)\n        Prints the results and also returns them.\"\"\"\n        self.__activate_html_inspector()\n        self.wait_for_ready_state_complete()\n        script = \"\"\"HTMLInspector.inspect();\"\"\"\n        try:\n            self.execute_script(script)\n        except Exception:\n            # If unable to load the JavaScript, skip inspection and return.\n            msg = \"(Unable to load HTML-Inspector JS! Inspection Skipped!)\"\n            print(\"\\n\" + msg)\n            return msg\n        time.sleep(0.1)\n        browser_logs = []\n        try:\n            browser_logs = self.driver.get_log(\"browser\")\n        except (ValueError, WebDriverException):\n            # If unable to get browser logs, skip the assert and return.\n            msg = \"(Unable to Inspect HTML! -> Only works on Chromium!)\"\n            print(\"\\n\" + msg)\n            return msg\n        messenger_library = \"//cdnjs.cloudflare.com/ajax/libs/messenger\"\n        url = self.get_current_url()\n        header = \"\\n* HTML Inspection Results: %s\" % url\n        results = [header]\n        row_count = 0\n        for entry in browser_logs:\n            message = entry[\"message\"]\n            if \"0:6053 \" in message:\n                message = message.split(\"0:6053\")[1]\n            message = message.replace(\"\\\\u003C\", \"<\")\n            message = message.replace(\" and should not be used\", \"\")\n            if message.startswith(' \"') and message.count('\"') == 2:\n                message = message.split('\"')[1]\n            if \"but not found in any stylesheet\" in message:\n                continue\n            if not is_windows:\n                message = \"⚠️  \" + message\n            else:\n                message = \"!-> \" + message  # CMD prompt compatibility\n            if messenger_library not in message:\n                if message not in results:\n                    results.append(message)\n                    row_count += 1\n        if row_count > 0:\n            results.append(\"* (See the Console output for details!)\")\n        else:\n            results.append(\"* (No issues detected!)\")\n        results = \"\\n\".join(results)\n        print(results)\n        return results\n\n    def is_valid_url(self, url):\n        \"\"\"Return True if the url is a valid url.\"\"\"\n        return page_utils.is_valid_url(url)\n\n    def is_alert_present(self):\n        try:\n            self.driver.switch_to.alert\n            return True\n        except Exception:\n            return False\n\n    def is_online(self):\n        \"\"\"Return True if connected to the Internet.\"\"\"\n        if self.__is_cdp_swap_needed():\n            return self.cdp.evaluate(\"navigator.onLine;\")\n        return self.execute_script(\"return navigator.onLine;\")\n\n    def is_connected(self):\n        \"\"\"\n        Return True if WebDriver is connected to the browser.\n        Note that the stealthy CDP-Driver isn't a WebDriver.\n        In CDP Mode, the CDP-Driver controls the web browser.\n        The CDP-Driver can be connected while WebDriver isn't.\n        \"\"\"\n        if hasattr(self.driver, \"is_connected\"):\n            return self.driver.is_connected()\n        else:\n            return True\n\n    def is_chromium(self):\n        \"\"\"Return True if the browser is Chrome or Edge.\"\"\"\n        self.__check_scope()\n        if self.__is_cdp_swap_needed():\n            return True\n        chromium = False\n        if (\n            \"chrome\" in self.driver.capabilities\n            or \"msedge\" in self.driver.capabilities\n        ):\n            chromium = True\n        return chromium\n\n    def __fail_if_not_using_chrome(self, method):\n        chrome = False\n        if \"chrome\" in self.driver.capabilities:\n            chrome = True\n        if not chrome:\n            browser_name = self.driver.capabilities[\"browserName\"]\n            message = (\n                'Error: \"%s\" should only be called by tests '\n                'running with \"--browser=chrome\" / \"--chrome\"! '\n                'You should add an \"if\" statement to your code before calling '\n                \"this method if using browsers that are Not Chrome! \"\n                'The browser detected was: \"%s\".' % (method, browser_name)\n            )\n            raise NotUsingChromeException(message)\n\n    def __fail_if_not_using_chromium(self, method):\n        if not self.is_chromium():\n            browser_name = self.driver.capabilities[\"browserName\"]\n            message = (\n                'Error: \"%s\" should only be called by tests '\n                'running with a Chromium browser! (Chrome or Edge) '\n                'You should add an \"if\" statement to your code before calling '\n                \"this method if using browsers that are Not Chromium! \"\n                'The browser detected was: \"%s\".' % (method, browser_name)\n            )\n            raise NotUsingChromiumException(message)\n\n    def get_chrome_version(self):\n        self.__check_scope()\n        self.__fail_if_not_using_chrome(\"get_chrome_version()\")\n        if \"browserVersion\" in self.driver.capabilities:\n            chrome_version = self.driver.capabilities[\"browserVersion\"]\n        else:\n            chrome_version = \"(Unknown Version)\"\n        return chrome_version\n\n    def get_chromium_version(self):\n        self.__check_scope()\n        self.__fail_if_not_using_chromium(\"get_chromium_version()\")\n        return self.__get_major_browser_version()\n\n    def get_chromedriver_version(self):\n        self.__check_scope()\n        self.__fail_if_not_using_chrome(\"get_chromedriver_version()\")\n        chrome_dict = self.driver.capabilities[\"chrome\"]\n        chromedriver_version = chrome_dict[\"chromedriverVersion\"]\n        chromedriver_version = chromedriver_version.split(\" \")[0]\n        return chromedriver_version\n\n    def get_chromium_driver_version(self):\n        self.__check_scope()\n        self.__fail_if_not_using_chromium(\"get_chromium_version()\")\n        driver_version = None\n        if \"chrome\" in self.driver.capabilities:\n            chrome_dict = self.driver.capabilities[\"chrome\"]\n            driver_version = chrome_dict[\"chromedriverVersion\"]\n            driver_version = driver_version.split(\" \")[0]\n        elif \"msedge\" in self.driver.capabilities:\n            edge_dict = self.driver.capabilities[\"msedge\"]\n            driver_version = edge_dict[\"msedgedriverVersion\"]\n            driver_version = driver_version.split(\" \")[0]\n        return driver_version\n\n    def get_mfa_code(self, totp_key=None):\n        \"\"\"Same as get_totp_code() and get_google_auth_password().\n        Returns a time-based one-time password based on the Google\n        Authenticator algorithm for multi-factor authentication.\"\"\"\n        return shared_utils.get_mfa_code(totp_key)\n\n    def enter_mfa_code(\n        self, selector, totp_key=None, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Enters into the field a Multi-Factor Authentication TOTP Code.\n        If the \"totp_key\" is not specified, this method defaults\n        to using the one provided in [seleniumbase/config/settings.py].\n        The TOTP code is generated by the Google Authenticator Algorithm.\n        This method will automatically press ENTER after typing the code.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.__is_cdp_swap_needed():\n            mfa_code = self.get_mfa_code(totp_key)\n            self.cdp.type(selector, mfa_code + \"\\n\", timeout=timeout)\n            return\n        self.wait_for_element_visible(selector, by=by, timeout=timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                css_selector = self.convert_to_css_selector(selector, by=by)\n                time_stamp = self.execute_script(\"return Date.now();\")\n                sel_key = [css_selector, totp_key]\n                origin = self.get_origin()\n                action = [\"e_mfa\", sel_key, origin, time_stamp]\n                self.__extra_actions.append(action)\n            # Sometimes Sign-In leaves the origin... Save work first.\n            self.save_recorded_actions()\n        mfa_code = self.get_mfa_code(totp_key)\n        self.update_text(selector, mfa_code + \"\\n\", by=by, timeout=timeout)\n\n    def convert_css_to_xpath(self, css):\n        return css_to_xpath.convert_css_to_xpath(css)\n\n    def convert_xpath_to_css(self, xpath):\n        return xpath_to_css.convert_xpath_to_css(xpath)\n\n    def convert_to_css_selector(self, selector, by):\n        \"\"\"This method converts a selector to a CSS_SELECTOR.\n        jQuery commands require a CSS_SELECTOR for finding elements.\n        This method should only be used for jQuery/JavaScript actions.\n        Pure JavaScript doesn't support using a:contains(\"LINK_TEXT\").\"\"\"\n        return js_utils.convert_to_css_selector(selector, by)\n\n    def set_value(\n        self, selector, text, by=\"css selector\", timeout=None, scroll=True\n    ):\n        \"\"\"This method uses JavaScript to update a text field.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by, xp_ok=False)\n        if self.__is_cdp_swap_needed():\n            self.cdp.set_value(selector, text)\n            return\n        self.wait_for_ready_state_complete()\n        self.wait_for_element_present(selector, by=by, timeout=timeout)\n        original_selector = selector\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        self.__demo_mode_highlight_if_active(original_selector, by)\n        if scroll and not self.demo_mode and not self.slow_mode:\n            self.scroll_to(original_selector, by=by, timeout=timeout)\n            if self.__needs_minimum_wait():\n                time.sleep(0.04)\n        if not scroll and not self.demo_mode and not self.slow_mode:\n            if self.__needs_minimum_wait():\n                time.sleep(0.06)\n        text = self.__get_type_checked_text(text)\n        value = re.escape(text)\n        value = self.__escape_quotes_if_needed(value)\n        pre_escape_css_selector = css_selector\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        the_type = None\n        if \":contains\\\\(\" not in css_selector:\n            get_type_script = (\n                \"\"\"return document.querySelector('%s').getAttribute('type');\"\"\"\n                % css_selector\n            )\n            the_type = self.execute_script(get_type_script)  # Used later\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                sel_tex = [pre_escape_css_selector, text]\n                if the_type == \"range\" and \":contains\\\\(\" not in css_selector:\n                    action = [\"s_val\", sel_tex, origin, time_stamp]\n                else:\n                    action = [\"js_ty\", sel_tex, origin, time_stamp]\n                self.__extra_actions.append(action)\n        if \":contains\\\\(\" not in css_selector:\n            script = \"\"\"document.querySelector('%s').value='%s';\"\"\" % (\n                css_selector,\n                value,\n            )\n            self.execute_script(script)\n        else:\n            element = self.wait_for_element_present(\n                original_selector, by=by, timeout=timeout\n            )\n            script = \"\"\"arguments[0].value='%s';\"\"\" % value\n            self.execute_script(script, element)\n        if text.endswith(\"\\n\"):\n            element = self.wait_for_element_present(\n                original_selector, by=by, timeout=timeout\n            )\n            element.send_keys(Keys.RETURN)\n            if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                self.wait_for_ready_state_complete()\n        else:\n            if the_type == \"range\" and \":contains\\\\(\" not in css_selector:\n                # Some input sliders need a mouse event to trigger listeners.\n                with suppress(Exception):\n                    mouse_move_script = (\n                        \"\"\"m_elm = document.querySelector('%s');\"\"\"\n                        \"\"\"m_evt = new Event('mousemove');\"\"\"\n                        \"\"\"m_elm.dispatchEvent(m_evt);\"\"\" % css_selector\n                    )\n                    self.execute_script(mouse_move_script)\n            elif the_type == \"range\" and \":contains\\\\(\" in css_selector:\n                with suppress(Exception):\n                    element = self.wait_for_element_present(\n                        original_selector, by=by, timeout=1\n                    )\n                    mouse_move_script = (\n                        \"\"\"m_elm = arguments[0];\"\"\"\n                        \"\"\"m_evt = new Event('mousemove');\"\"\"\n                        \"\"\"m_elm.dispatchEvent(m_evt);\"\"\"\n                    )\n                    self.execute_script(mouse_move_script, element)\n        self.__demo_mode_pause_if_active()\n        if not self.demo_mode and not self.slow_mode:\n            if self.__needs_minimum_wait():\n                time.sleep(0.04)\n\n    def js_update_text(self, selector, text, by=\"css selector\", timeout=None):\n        \"\"\"JavaScript + send_keys are used to update a text field.\n        Performs self.set_value() and triggers event listeners.\n        If text ends in \"\\n\", set_value() presses RETURN after.\n        Works faster than send_keys() alone due to the JS call.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        text = self.__get_type_checked_text(text)\n        self.set_value(selector, text, by=by, timeout=timeout)\n        if not text.endswith(\"\\n\"):\n            with suppress(Exception):\n                element = page_actions.wait_for_element_present(\n                    self.driver, selector, by, timeout=0.2\n                )\n                element.send_keys(\" \" + Keys.BACK_SPACE)\n\n    def js_type(self, selector, text, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.js_update_text()\n        JavaScript + send_keys are used to update a text field.\n        Performs self.set_value() and triggers event listeners.\n        If text ends in \"\\n\", set_value() presses RETURN after.\n        Works faster than send_keys() alone due to the JS call.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        self.js_update_text(selector, text, by=by, timeout=timeout)\n\n    def set_text(self, selector, text, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.js_update_text()\n        JavaScript + send_keys are used to update a text field.\n        Performs self.set_value() and triggers event listeners.\n        If text ends in \"\\n\", set_value() presses RETURN after.\n        Works faster than send_keys() alone due to the JS call.\n        If not an input or textarea, sets textContent instead.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.set_value(selector, text)\n            return\n        self.wait_for_ready_state_complete()\n        element = page_actions.wait_for_element_present(\n            self.driver, selector, by, timeout\n        )\n        if element.tag_name.lower() in [\"input\", \"textarea\"]:\n            self.js_update_text(selector, text, by=by, timeout=timeout)\n        else:\n            self.set_text_content(selector, text, by=by, timeout=timeout)\n\n    def set_text_content(\n        self, selector, text, by=\"css selector\", timeout=None, scroll=False\n    ):\n        \"\"\"This method uses JavaScript to set an element's textContent.\n        If the element is an input or textarea, sets the value instead.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        element = None\n        if self.__is_cdp_swap_needed():\n            element = self.cdp.select(selector, timeout=timeout)\n        else:\n            self.wait_for_ready_state_complete()\n            element = page_actions.wait_for_element_present(\n                self.driver, selector, by, timeout\n            )\n        if element.tag_name.lower() in [\"input\", \"textarea\"]:\n            self.js_update_text(selector, text, by=by, timeout=timeout)\n            return\n        original_selector = selector\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if scroll:\n            self.__demo_mode_highlight_if_active(original_selector, by)\n            if not self.demo_mode and not self.slow_mode:\n                self.scroll_to(original_selector, by=by, timeout=timeout)\n        text = self.__get_type_checked_text(text)\n        value = re.escape(text)\n        value = self.__escape_quotes_if_needed(value)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        if \":contains\\\\(\" not in css_selector:\n            script = \"\"\"document.querySelector('%s').textContent='%s';\"\"\" % (\n                css_selector,\n                value,\n            )\n            self.execute_script(script)\n        else:\n            script = \"\"\"arguments[0].textContent='%s';\"\"\" % value\n            self.execute_script(script, element)\n        self.__demo_mode_pause_if_active()\n\n    def jquery_update_text(\n        self, selector, text, by=\"css selector\", timeout=None\n    ):\n        \"\"\"This method uses jQuery to update a text field.\n        If the text string ends with the newline character,\n        Selenium finishes the call, which simulates pressing\n        {Enter/Return} after the text is entered.\n        This method also triggers event listeners.\"\"\"\n        self.__check_scope()\n        original_selector = selector\n        original_by = by\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by, xp_ok=False)\n        element = self.wait_for_element_visible(\n            selector, by=by, timeout=timeout\n        )\n        self.__demo_mode_highlight_if_active(selector, by)\n        self.scroll_to(selector, by=by)\n        selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = selector\n        selector = self.__make_css_match_first_element_only(selector)\n        selector = self.__escape_quotes_if_needed(selector)\n        text = re.escape(text)\n        text = self.__escape_quotes_if_needed(text)\n        update_text_script = \"\"\"jQuery('%s').val('%s');\"\"\" % (selector, text)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                sel_tex = [css_selector, text]\n                action = [\"jq_ty\", sel_tex, origin, time_stamp]\n                self.__extra_actions.append(action)\n        self.safe_execute_script(update_text_script)\n        if text.endswith(\"\\n\"):\n            element = self.wait_for_element_present(\n                original_selector, by=original_by, timeout=0.2\n            )\n            element.send_keys(Keys.RETURN)\n        else:\n            with suppress(Exception):\n                element = self.wait_for_element_present(\n                    original_selector, by=original_by, timeout=0.2\n                )\n                element.send_keys(\" \" + Keys.BACK_SPACE)\n        self.__demo_mode_pause_if_active()\n\n    def jquery_type(self, selector, text, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.jquery_update_text()\n        JQuery is used to update a text field.\n        Performs jQuery(selector).val(text); and triggers event listeners.\n        If text ends in \"\\n\", presses RETURN after.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        self.jquery_update_text(selector, text, by=by, timeout=timeout)\n\n    def get_value(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"This method uses JavaScript to get the value of an input field.\n        (Works on both input fields and textarea fields.)\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        self.wait_for_ready_state_complete()\n        self.wait_for_element_present(selector, by=by, timeout=timeout)\n        original_selector = selector\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        self.__demo_mode_highlight_if_active(original_selector, by)\n        if not self.demo_mode and not self.slow_mode:\n            self.scroll_to(original_selector, by=by, timeout=timeout)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        if \":contains\\\\(\" not in css_selector:\n            script = \"\"\"return document.querySelector('%s').value;\"\"\" % (\n                css_selector\n            )\n            value = self.execute_script(script)\n        else:\n            element = self.wait_for_element_present(selector, by=by, timeout=1)\n            script = \"\"\"return arguments[0].value;\"\"\"\n            value = self.execute_script(script, element)\n        return value\n\n    def set_time_limit(self, time_limit):\n        self.__check_scope()\n        if time_limit:\n            try:\n                sb_config.time_limit = float(time_limit)\n            except Exception:\n                sb_config.time_limit = None\n        else:\n            sb_config.time_limit = None\n        if sb_config.time_limit and sb_config.time_limit > 0:\n            sb_config.time_limit_ms = int(sb_config.time_limit * 1000.0)\n            self.time_limit = sb_config.time_limit\n        else:\n            self.time_limit = None\n            sb_config.time_limit = None\n            sb_config.time_limit_ms = None\n\n    def set_default_timeout(self, timeout):\n        \"\"\"This method changes the default timeout values of test methods\n        for the duration of the current test.\n        Effected timeouts: (used by methods that wait for elements)\n            * settings.SMALL_TIMEOUT - (default value: 6 seconds)\n            * settings.LARGE_TIMEOUT - (default value: 10 seconds)\n        The minimum allowable default timeout is: 0.5 seconds.\n        The maximum allowable default timeout is: 60.0 seconds.\n        (Test methods can still override timeouts outside that range.)\"\"\"\n        self.__check_scope()\n        if not isinstance(timeout, (int, float)):\n            raise Exception('Expecting a numeric value for \"timeout\"!')\n        if timeout < 0:\n            raise Exception('The \"timeout\" cannot be a negative number!')\n        timeout = float(timeout)\n        # Min default timeout: 0.5 seconds. Max default timeout: 60.0 seconds.\n        min_timeout = 0.5\n        max_timeout = 60.0\n        if timeout < min_timeout:\n            logging.info(\"Minimum default timeout = %s\" % min_timeout)\n            timeout = min_timeout\n        elif timeout > max_timeout:\n            logging.info(\"Maximum default timeout = %s\" % max_timeout)\n            timeout = max_timeout\n        self.__overrided_default_timeouts = True\n        sb_config._is_timeout_changed = True\n        settings.SMALL_TIMEOUT = timeout\n        settings.LARGE_TIMEOUT = timeout\n\n    def reset_default_timeout(self):\n        \"\"\"Reset default timeout values to the original from settings.py\n        This method reverts the changes made by set_default_timeout()\"\"\"\n        if self.__overrided_default_timeouts:\n            if sb_config._SMALL_TIMEOUT and sb_config._LARGE_TIMEOUT:\n                settings.SMALL_TIMEOUT = sb_config._SMALL_TIMEOUT\n                settings.LARGE_TIMEOUT = sb_config._LARGE_TIMEOUT\n                sb_config._is_timeout_changed = False\n                self.__overrided_default_timeouts = False\n\n    def fail(self, msg=\"fail() called!\"):\n        \"\"\"Fail immediately, with the given message.\"\"\"\n        super().fail(msg)\n\n    def skip(self, reason=\"\"):\n        \"\"\"Mark the test as Skipped.\"\"\"\n        self.__check_scope()\n        if self.dashboard:\n            test_id = self.__get_test_id_2()\n            if hasattr(self, \"_using_sb_fixture\"):\n                test_id = sb_config._test_id\n            if (\n                test_id in sb_config._results.keys()\n                and sb_config._results[test_id] == \"Passed\"\n            ):\n                # Duplicate tearDown() called where test already passed\n                self.__passed_then_skipped = True\n            self.__will_be_skipped = True\n            sb_config._results[test_id] = \"Skipped\"\n        if getattr(self, \"with_db_reporting\", None):\n            if self.is_pytest:\n                self.__skip_reason = reason\n            else:\n                self._nose_skip_reason = reason\n        # Add skip reason to the logs\n        if not hasattr(self, \"_using_sb_fixture\"):\n            test_id = self.__get_test_id()  # Recalculate the test id\n        test_logpath = os.path.join(self.log_path, test_id)\n        self.__create_log_path_as_needed(test_logpath)\n        browser = self.browser\n        if not reason:\n            reason = \"No skip reason given\"\n        log_helper.log_skipped_test_data(\n            self, test_logpath, self.driver, browser, reason\n        )\n        self._was_skipped = True\n        # Finally skip the test for real\n        self.skipTest(reason)\n\n    ############\n\n    # Console Log controls\n\n    def start_recording_console_logs(self):\n        \"\"\"Starts recording console logs. Logs are saved to: \"console.logs\".\n        To get those logs later, call \"self.get_recorded_console_logs()\".\n        If navigating to a new page, then the current recorded logs will be\n        lost, and you'll have to call start_recording_console_logs() again.\n        # Link1: https://stackoverflow.com/a/19846113/7058266\n        # Link2: https://stackoverflow.com/a/74196986/7058266 \"\"\"\n        self.execute_script(\n            \"\"\"\n            console.stdlog = console.log.bind(console);\n            console.logs = [];\n            console.log = function(){\n                console.logs.push(Array.from(arguments));\n                console.stdlog.apply(console, arguments);\n            }\n            \"\"\"\n        )\n\n    def console_log_string(self, string):\n        \"\"\"Log a string to the Web Browser's Console.\n        Example:\n        self.console_log_string(\"Hello World!\") \"\"\"\n        self.execute_script(\"\"\"console.log(`%s`);\"\"\" % string)\n\n    def console_log_script(self, script):\n        \"\"\"Log output of JavaScript to the Web Browser's Console.\n        Example:\n        self.console_log_script('document.querySelector(\"h2\").textContent') \"\"\"\n        self.execute_script(\"\"\"console.log(%s);\"\"\" % script)\n\n    def get_recorded_console_logs(self):\n        \"\"\"Get console logs recorded after \"start_recording_console_logs()\".\"\"\"\n        logs = []\n        with suppress(Exception):\n            logs = self.execute_script(\"return console.logs;\")\n        return logs\n\n    ############\n\n    # Application \"Local Storage\" controls\n\n    def __is_valid_storage_url(self):\n        url = self.get_current_url()\n        if url and len(url) > 0:\n            if (\"http:\") in url or (\"https:\") in url or (\"file:\") in url:\n                return True\n        return False\n\n    def set_local_storage_item(self, key, value):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Local Storage is not available here!\")\n        if self.__is_cdp_swap_needed():\n            self.cdp.set_local_storage_item(key, value)\n            return\n        self.execute_script(\n            \"window.localStorage.setItem('{}', '{}');\".format(key, value)\n        )\n\n    def get_local_storage_item(self, key):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Local Storage is not available here!\")\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_local_storage_item(key)\n        return self.execute_script(\n            \"return window.localStorage.getItem('{}');\".format(key)\n        )\n\n    def remove_local_storage_item(self, key):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Local Storage is not available here!\")\n        self.execute_script(\n            \"window.localStorage.removeItem('{}');\".format(key)\n        )\n\n    def clear_local_storage(self):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            return\n        self.execute_script(\"window.localStorage.clear();\")\n        if self.recorder_mode:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"c_l_s\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def get_local_storage_keys(self):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Local Storage is not available here!\")\n        return self.execute_script(\n            \"var ls = window.localStorage, keys = []; \"\n            \"for (var i = 0; i < ls.length; ++i) \"\n            \"  keys[i] = ls.key(i); \"\n            \"return keys;\"\n        )\n\n    def get_local_storage_items(self):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Local Storage is not available here!\")\n        return self.execute_script(\n            r\"var ls = window.localStorage, items = {}; \"\n            \"for (var i = 0, k; i < ls.length; ++i) \"\n            \"  items[k = ls.key(i)] = ls.getItem(k); \"\n            \"return items;\"\n        )\n\n    # Application \"Session Storage\" controls\n\n    def set_session_storage_item(self, key, value):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Session Storage is not available here!\")\n        if self.__is_cdp_swap_needed():\n            self.cdp.set_session_storage_item(key, value)\n            return\n        self.execute_script(\n            \"window.sessionStorage.setItem('{}', '{}');\".format(key, value)\n        )\n\n    def get_session_storage_item(self, key):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Session Storage is not available here!\")\n        if self.__is_cdp_swap_needed():\n            return self.cdp.get_session_storage_item(key)\n        return self.execute_script(\n            \"return window.sessionStorage.getItem('{}');\".format(key)\n        )\n\n    def remove_session_storage_item(self, key):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Session Storage is not available here!\")\n        self.execute_script(\n            \"window.sessionStorage.removeItem('{}');\".format(key)\n        )\n\n    def clear_session_storage(self):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            return\n        if not self.recorder_mode:\n            self.execute_script(\"window.sessionStorage.clear();\")\n        else:\n            recorder_keys = [\n                \"recorder_mode\",\n                \"recorded_actions\",\n                \"recorder_title\",\n                \"pause_recorder\",\n                \"recorder_activated\",\n            ]\n            keys = self.get_session_storage_keys()\n            for key in keys:\n                if key not in recorder_keys:\n                    self.remove_session_storage_item(key)\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"c_s_s\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n\n    def get_session_storage_keys(self):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Session Storage is not available here!\")\n        return self.execute_script(\n            \"var ls = window.sessionStorage, keys = []; \"\n            \"for (var i = 0; i < ls.length; ++i) \"\n            \"  keys[i] = ls.key(i); \"\n            \"return keys;\"\n        )\n\n    def get_session_storage_items(self):\n        self.__check_scope()\n        if not self.__is_valid_storage_url():\n            raise WebDriverException(\"Session Storage is not available here!\")\n        return self.execute_script(\n            r\"var ls = window.sessionStorage, items = {}; \"\n            \"for (var i = 0, k; i < ls.length; ++i) \"\n            \"  items[k = ls.key(i)] = ls.getItem(k); \"\n            \"return items;\"\n        )\n\n    ############\n\n    # Methods ONLY for the selenium-wire integration (\"--wire\")\n\n    def set_wire_proxy(self, string):\n        \"\"\"Set a proxy server for selenium-wire mode (\"--wire\")\n        NOTE: This method ONLY works while using \"--wire\" mode!\n        Examples:\n            self.set_wire_proxy(\"SERVER:PORT\")\n            self.set_wire_proxy(\"socks5://SERVER:PORT\")\n            self.set_wire_proxy(\"USERNAME:PASSWORD@SERVER:PORT\") \"\"\"\n        if not string:\n            self.driver.proxy = {}\n            return\n        the_http = \"http\"\n        the_https = \"https\"\n        if string.startswith(\"socks4://\"):\n            the_http = \"socks4\"\n            the_https = \"socks4\"\n        elif string.startswith(\"socks5://\"):\n            the_http = \"socks5\"\n            the_https = \"socks5\"\n        string = string.split(\"//\")[-1]\n        self.driver.proxy = {\n            \"http\": \"%s://%s\" % (the_http, string),\n            \"https\": \"%s://%s\" % (the_https, string),\n            \"no_proxy\": \"localhost,127.0.0.1\",\n        }\n\n    ############\n\n    # Duplicates (Avoids name confusion when migrating from other frameworks.)\n\n    def open_url(self, url):\n        \"\"\"Same as self.open()\"\"\"\n        self.open(url)\n\n    def visit(self, url):\n        \"\"\"Same as self.open()\"\"\"\n        self.open(url)\n\n    def visit_url(self, url):\n        \"\"\"Same as self.open()\"\"\"\n        self.open(url)\n\n    def goto(self, url):\n        \"\"\"Same as self.open()\"\"\"\n        self.open(url)\n\n    def go_to(self, url):\n        \"\"\"Same as self.open()\"\"\"\n        self.open(url)\n\n    def reload(self):\n        \"\"\"Same as self.refresh_page()\"\"\"\n        self.refresh_page()\n\n    def reload_page(self):\n        \"\"\"Same as self.refresh_page()\"\"\"\n        self.refresh_page()\n\n    def open_new_tab(self, switch_to=True, **kwargs):\n        \"\"\"Same as self.open_new_window()\"\"\"\n        self.open_new_window(switch_to=switch_to, **kwargs)\n\n    def switch_to_tab(self, tab, timeout=None):\n        \"\"\"Same as self.switch_to_window()\n        Switches control of the browser to the specified window.\n        The window can be an integer: 0 -> 1st tab, 1 -> 2nd tab, etc...\n            Or it can be a list item from self.driver.window_handles \"\"\"\n        self.switch_to_window(window=tab, timeout=timeout)\n\n    def switch_to_default_tab(self):\n        \"\"\"Same as self.switch_to_default_window()\"\"\"\n        self.switch_to_default_window()\n\n    def switch_to_newest_tab(self):\n        \"\"\"Same as self.switch_to_newest_window()\"\"\"\n        self.switch_to_newest_window()\n\n    def save_as_html(self, name, folder=None):\n        \"\"\"Same as self.save_page_source()\"\"\"\n        self.save_page_source(name, folder=folder)\n\n    def input(\n        self, selector, text, by=\"css selector\", timeout=None, retry=False\n    ):\n        \"\"\"Same as self.update_text()\"\"\"\n        self.update_text(selector, text, by=by, timeout=timeout, retry=retry)\n\n    def fill(\n        self, selector, text, by=\"css selector\", timeout=None, retry=False\n    ):\n        \"\"\"Same as self.update_text()\"\"\"\n        self.update_text(selector, text, by=by, timeout=timeout, retry=retry)\n\n    def write(\n        self, selector, text, by=\"css selector\", timeout=None, retry=False\n    ):\n        \"\"\"Same as self.update_text()\"\"\"\n        self.update_text(selector, text, by=by, timeout=timeout, retry=retry)\n\n    def click_link(self, link_text, timeout=None):\n        \"\"\"Same as self.click_link_text()\"\"\"\n        self.click_link_text(link_text, timeout=timeout)\n\n    def click_partial_link(self, partial_link_text, timeout=None):\n        \"\"\"Same as self.click_partial_link_text()\"\"\"\n        self.click_partial_link_text(partial_link_text, timeout=timeout)\n\n    def right_click(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.context_click()\"\"\"\n        self.context_click(selector, by=by, timeout=timeout)\n\n    def hover_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.hover()\"\"\"\n        return self.hover(selector, by=by, timeout=timeout)\n\n    def hover_on_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.hover()\"\"\"\n        return self.hover(selector, by=by, timeout=timeout)\n\n    def hover_over_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as self.hover()\"\"\"\n        return self.hover(selector, by=by, timeout=timeout)\n\n    def wait_for_element_visible(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as self.wait_for_element()\"\"\"\n        self.__check_scope()\n        self.__skip_if_esc()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.select(selector, timeout=timeout)\n        if self.__is_shadow_selector(selector):\n            return self.__get_shadow_element(selector, timeout)\n        return page_actions.wait_for_element_visible(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n\n    def wait_for_element_clickable(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Waits for the element to be clickable, but does NOT click it.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.select(selector, timeout=timeout)\n        elif self.__is_shadow_selector(selector):\n            # If a shadow selector, use visible instead of clickable\n            return self.__wait_for_shadow_element_visible(selector, timeout)\n        return page_actions.wait_for_element_clickable(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n\n    def wait_for_element_not_present(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as self.wait_for_element_absent()\n        Waits for an element to no longer appear in the HTML of a page.\n        A hidden element still counts as appearing in the page HTML.\n        If waiting for elements to be hidden instead of nonexistent,\n        use wait_for_element_not_visible() instead.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.wait_for_element_absent(selector, timeout=timeout)\n            return True\n        return page_actions.wait_for_element_absent(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n\n    def wait_for_any_of_elements_visible(self, *args, **kwargs):\n        \"\"\"Waits for at least one of the elements to be visible.\n        Returns the first element that is found.\n        The input is a list of elements. (Should be CSS selectors or XPath)\n        Optional kwargs include: \"timeout\" (used by all selectors).\n        Raises an exception if no elements are visible by the timeout.\n        Allows flexible inputs (Eg. Multiple args or a list of args)\n        Examples:\n            self.wait_for_any_of_elements_visible(\"h1\", \"h2\", \"h3\")\n            OR\n            self.wait_for_any_of_elements_visible([\"h1\", \"h2\", \"h3\"]) \"\"\"\n        self.__check_scope()\n        selectors = []\n        timeout = None\n        for kwarg in kwargs:\n            if kwarg == \"timeout\":\n                timeout = kwargs[\"timeout\"]\n            elif kwarg == \"by\":\n                pass  # Autodetected\n            elif kwarg == \"selector\" or kwarg == \"selectors\":\n                selector = kwargs[kwarg]\n                if isinstance(selector, str):\n                    selectors.append(selector)\n                elif isinstance(selector, list):\n                    selectors_list = selector\n                    for selector in selectors_list:\n                        if isinstance(selector, str):\n                            selectors.append(selector)\n            else:\n                raise Exception('Unknown kwarg: \"%s\"!' % kwarg)\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        for arg in args:\n            if isinstance(arg, list):\n                for selector in arg:\n                    if isinstance(selector, str):\n                        selectors.append(selector)\n            elif isinstance(arg, str):\n                selectors.append(arg)\n        if not selectors:\n            raise Exception(\"The selectors list was empty!\")\n        original_selectors = selectors\n        updated_selectors = []\n        for selector in selectors:\n            by = \"css selector\"\n            if page_utils.is_xpath_selector(selector):\n                by = \"xpath\"\n            selector, by = self.__recalculate_selector(selector, by)\n            updated_selectors.append(selector)\n        selectors = updated_selectors\n        if self.__is_cdp_swap_needed():\n            return self.cdp.wait_for_any_of_elements_visible(\n                selectors, timeout=timeout\n            )\n        return page_actions.wait_for_any_of_elements_visible(\n            self.driver,\n            selectors,\n            timeout=timeout,\n            original_selectors=original_selectors,\n        )\n\n    def wait_for_any_of_elements_present(self, *args, **kwargs):\n        \"\"\"Waits for at least one of the elements to be present.\n        Visibility not required, but element must be in the DOM.\n        Returns the first element that is found.\n        The input is a list of elements. (Should be CSS selectors or XPath)\n        Optional kwargs include: \"timeout\" (used by all selectors).\n        Raises an exception if no elements are present by the timeout.\n        Allows flexible inputs (Eg. Multiple args or a list of args)\n        Examples:\n            self.wait_for_any_of_elements_present(\"style\", \"script\")\n            OR\n            self.wait_for_any_of_elements_present([\"style\", \"script\"]) \"\"\"\n        self.__check_scope()\n        selectors = []\n        timeout = None\n        for kwarg in kwargs:\n            if kwarg == \"timeout\":\n                timeout = kwargs[\"timeout\"]\n            elif kwarg == \"by\":\n                pass  # Autodetected\n            elif kwarg == \"selector\" or kwarg == \"selectors\":\n                selector = kwargs[kwarg]\n                if isinstance(selector, str):\n                    selectors.append(selector)\n                elif isinstance(selector, list):\n                    selectors_list = selector\n                    for selector in selectors_list:\n                        if isinstance(selector, str):\n                            selectors.append(selector)\n            else:\n                raise Exception('Unknown kwarg: \"%s\"!' % kwarg)\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        for arg in args:\n            if isinstance(arg, list):\n                for selector in arg:\n                    if isinstance(selector, str):\n                        selectors.append(selector)\n            elif isinstance(arg, str):\n                selectors.append(arg)\n        if not selectors:\n            raise Exception(\"The selectors list was empty!\")\n        original_selectors = selectors\n        updated_selectors = []\n        for selector in selectors:\n            by = \"css selector\"\n            if page_utils.is_xpath_selector(selector):\n                by = \"xpath\"\n            selector, by = self.__recalculate_selector(selector, by)\n            updated_selectors.append(selector)\n        selectors = updated_selectors\n        if self.__is_cdp_swap_needed():\n            return self.cdp.wait_for_any_of_elements_present(\n                selectors, timeout=timeout\n            )\n        return page_actions.wait_for_any_of_elements_present(\n            self.driver,\n            selectors,\n            timeout=timeout,\n            original_selectors=original_selectors,\n        )\n\n    def assert_any_of_elements_visible(self, *args, **kwargs):\n        \"\"\"Similar to wait_for_any_of_elements_visible(), but returns nothing.\n        As above, raises an exception if none of the set elements are visible.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\n        Allows flexible inputs (Eg. Multiple args or a list of args)\n        Examples:\n            self.assert_any_of_elements_visible(\"h1\", \"h2\", \"h3\")\n            OR\n            self.assert_any_of_elements_visible([\"h1\", \"h2\", \"h3\"]) \"\"\"\n        if \"timeout\" not in kwargs:\n            kwargs[\"timeout\"] = settings.SMALL_TIMEOUT\n        self.wait_for_any_of_elements_visible(*args, **kwargs)\n        return True\n\n    def assert_any_of_elements_present(self, *args, **kwargs):\n        \"\"\"Similar to wait_for_any_of_elements_present(), but returns nothing.\n        As above, raises an exception if none of the given elements are found.\n        Visibility is not required, but element must exist in the DOM.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\n        Allows flexible inputs (Eg. Multiple args or a list of args)\n        Examples:\n            self.assert_any_of_elements_present(\"h1\", \"h2\", \"h3\")\n            OR\n            self.assert_any_of_elements_present([\"h1\", \"h2\", \"h3\"]) \"\"\"\n        if \"timeout\" not in kwargs:\n            kwargs[\"timeout\"] = settings.SMALL_TIMEOUT\n        self.wait_for_any_of_elements_present(*args, **kwargs)\n        return True\n\n    def select_all(self, selector, by=\"css selector\", limit=0):\n        return self.find_elements(selector, by=by, limit=limit)\n\n    def assert_link(self, link_text, timeout=None):\n        \"\"\"Same as self.assert_link_text()\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.assert_link_text(link_text, timeout=timeout)\n\n    def assert_element_not_present(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as self.assert_element_absent()\n        Raises an exception if the element stays present.\n        A hidden element counts as a present element, which fails this assert.\n        If you want to assert that elements are hidden instead of nonexistent,\n        use assert_element_not_visible() instead.\n        (Note that hidden elements are still present in the HTML of the page.)\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.wait_for_element_absent(selector, by=by, timeout=timeout)\n        return True\n\n    def get_google_auth_password(self, totp_key=None):\n        \"\"\"Same as self.get_mfa_code()\"\"\"\n        return self.get_mfa_code(totp_key=totp_key)\n\n    def get_google_auth_code(self, totp_key=None):\n        \"\"\"Same as self.get_mfa_code()\"\"\"\n        return self.get_mfa_code(totp_key=totp_key)\n\n    def get_totp_code(self, totp_key=None):\n        \"\"\"Same as self.get_mfa_code()\"\"\"\n        return self.get_mfa_code(totp_key=totp_key)\n\n    def enter_totp_code(\n        self, selector, totp_key=None, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as self.enter_mfa_code()\"\"\"\n        return self.enter_mfa_code(\n            selector=selector, totp_key=totp_key, by=by, timeout=timeout\n        )\n\n    def clear_all_cookies(self):\n        \"\"\"Same as self.delete_all_cookies()\"\"\"\n        self.delete_all_cookies()\n\n    def delete_local_storage(self):\n        \"\"\"Same as self.clear_local_storage()\"\"\"\n        self.clear_local_storage()\n\n    def delete_session_storage(self):\n        \"\"\"Same as clear_session_storage()\"\"\"\n        self.clear_session_storage()\n\n    def assert_no_broken_links(self, multithreaded=True):\n        \"\"\"Same as self.assert_no_404_errors()\"\"\"\n        self.assert_no_404_errors(multithreaded=multithreaded)\n\n    def wait(self, seconds):\n        \"\"\"Same as self.sleep() - Some JS frameworks use this method name.\"\"\"\n        self.sleep(seconds)\n\n    def block_ads(self):\n        \"\"\"Same as self.ad_block()\"\"\"\n        self.ad_block()\n\n    def _check_browser(self):\n        \"\"\"This method raises an exception if the active window is closed.\n        (This provides a much cleaner exception message in this situation.)\"\"\"\n        page_actions._reconnect_if_disconnected(self.driver)\n        active_window = None\n        with suppress(Exception):\n            active_window = self.driver.current_window_handle  # Fails if None\n        if not active_window:\n            raise NoSuchWindowException(\"Active window was already closed!\")\n\n    def _print(self, msg):\n        \"\"\"Same as Python's print(), but also prints during multithreaded runs.\n        Normally, Python's print() command won't print for multithreaded tests.\n        Here's an example of running tests using multithreading: \"pytest -n=4\".\n        Here's how to print directly from sys without using a print() command:\n        To force a print during multithreaded tests, use: \"sys.stderr.write()\".\n        To print without the new-line character end, use: \"sys.stdout.write()\".\n        \"\"\"\n        if getattr(sb_config, \"_multithreaded\", None):\n            if not isinstance(msg, str):\n                with suppress(Exception):\n                    msg = str(msg)\n            sys.stderr.write(msg + \"\\n\")\n        else:\n            print(msg)\n\n    ############\n\n    def add_css_link(self, css_link):\n        self.__check_scope()\n        self._check_browser()\n        js_utils.add_css_link(self.driver, css_link)\n\n    def add_js_link(self, js_link):\n        self.__check_scope()\n        self._check_browser()\n        js_utils.add_js_link(self.driver, js_link)\n\n    def add_css_style(self, css_style):\n        self.__check_scope()\n        self._check_browser()\n        js_utils.add_css_style(self.driver, css_style)\n\n    def add_js_code_from_link(self, js_link):\n        self.__check_scope()\n        self._check_browser()\n        js_utils.add_js_code_from_link(self.driver, js_link)\n\n    def add_js_code(self, js_code):\n        self.__check_scope()\n        self._check_browser()\n        js_utils.add_js_code(self.driver, js_code)\n\n    def add_meta_tag(self, http_equiv=None, content=None):\n        self.__check_scope()\n        self._check_browser()\n        js_utils.add_meta_tag(\n            self.driver, http_equiv=http_equiv, content=content\n        )\n\n    ############\n\n    def activate_messenger(self):\n        self.__check_scope()\n        if not self.__is_cdp_swap_needed():\n            self._check_browser()\n        js_utils.activate_messenger(self.driver)\n        if not self.__is_cdp_swap_needed():\n            self.wait_for_ready_state_complete()\n\n    def set_messenger_theme(\n        self, theme=\"default\", location=\"default\", max_messages=\"default\"\n    ):\n        \"\"\"Sets a theme for posting messages.\n        Themes: [\"flat\", \"future\", \"block\", \"air\", \"ice\"]\n        Locations: [\"top_left\", \"top_center\", \"top_right\",\n                    \"bottom_left\", \"bottom_center\", \"bottom_right\"]\n        max_messages: The limit of concurrent messages to display.\"\"\"\n        self.__check_scope()\n        if not self.__is_cdp_swap_needed():\n            self._check_browser()\n        if not theme:\n            theme = \"default\"  # \"flat\"\n        if not location:\n            location = \"default\"  # \"bottom_right\"\n        if not max_messages:\n            max_messages = \"default\"  # \"8\"\n        else:\n            max_messages = str(max_messages)  # Value must be in string format\n        js_utils.set_messenger_theme(\n            self.driver,\n            theme=theme,\n            location=location,\n            max_messages=max_messages,\n        )\n\n    def post_message(self, message, duration=None, pause=True, style=\"info\"):\n        \"\"\"Post a message on the screen with Messenger.\n        Arguments:\n            message: The message to display.\n            duration: The time until the message vanishes. (Default: 2.55s)\n            pause: If True, the program waits until the message completes.\n            style: \"info\", \"success\", or \"error\".\n\n        You can also post messages by using =>\n            self.execute_script('Messenger().post(\"My Message\")') \"\"\"\n        self.__check_scope()\n        if not self.__is_cdp_swap_needed():\n            self._check_browser()\n        if style not in [\"info\", \"success\", \"error\"]:\n            style = \"info\"\n        if not duration:\n            if not self.message_duration:\n                duration = settings.DEFAULT_MESSAGE_DURATION\n            else:\n                duration = self.message_duration\n        if (\n            (self.headless or self.headless2 or self.xvfb)\n            and float(duration) > 0.75\n        ):\n            duration = 0.75\n        try:\n            js_utils.post_message(self.driver, message, duration, style=style)\n        except Exception:\n            print(\" * %s message: %s\" % (style.upper(), message))\n        if pause:\n            duration = float(duration) + 0.15\n            time.sleep(float(duration))\n\n    def post_message_and_highlight(self, message, selector, by=\"css selector\"):\n        \"\"\"Post a message on the screen and highlight an element.\n        Arguments:\n            message: The message to display.\n            selector: The selector of the Element to highlight.\n            by: The type of selector to search by. (Default: \"css selector\")\"\"\"\n        self.__check_scope()\n        self.__highlight_with_assert_success(message, selector, by=by)\n\n    def post_success_message(self, message, duration=None, pause=True):\n        \"\"\"Post a success message on the screen with Messenger.\n        Arguments:\n            message: The success message to display.\n            duration: The time until the message vanishes. (Default: 2.55s)\n            pause: If True, the program waits until the message completes.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if not duration:\n            if not self.message_duration:\n                duration = settings.DEFAULT_MESSAGE_DURATION\n            else:\n                duration = self.message_duration\n        if (\n            (self.headless or self.headless2 or self.xvfb)\n            and float(duration) > 0.75\n        ):\n            duration = 0.75\n        try:\n            js_utils.post_message(\n                self.driver, message, duration, style=\"success\"\n            )\n        except Exception:\n            print(\" * SUCCESS message: %s\" % message)\n        if pause:\n            duration = float(duration) + 0.15\n            time.sleep(float(duration))\n\n    def post_error_message(self, message, duration=None, pause=True):\n        \"\"\"Post an error message on the screen with Messenger.\n        Arguments:\n            message: The error message to display.\n            duration: The time until the message vanishes. (Default: 2.55s)\n            pause: If True, the program waits until the message completes.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        if not duration:\n            if not self.message_duration:\n                duration = settings.DEFAULT_MESSAGE_DURATION\n            else:\n                duration = self.message_duration\n        if (\n            (self.headless or self.headless2 or self.xvfb)\n            and float(duration) > 0.75\n        ):\n            duration = 0.75\n        try:\n            js_utils.post_message(\n                self.driver, message, duration, style=\"error\"\n            )\n        except Exception:\n            print(\" * ERROR message: %s\" % message)\n        if pause:\n            duration = float(duration) + 0.15\n            time.sleep(float(duration))\n\n    ############\n\n    def generate_referral(self, start_page, destination_page, selector=None):\n        \"\"\"This method opens the start_page, creates a referral link there,\n        and clicks on that link, which goes to the destination_page.\n        If a selector is given, clicks that on the destination_page,\n        which can prevent an artificial rise in website bounce-rate.\n        (This generates real traffic for testing analytics software.)\"\"\"\n        self.__check_scope()\n        if not page_utils.is_valid_url(destination_page):\n            raise Exception(\n                \"Exception: destination_page {%s} is not a valid URL!\"\n                % destination_page\n            )\n        if start_page:\n            if not page_utils.is_valid_url(start_page):\n                raise Exception(\n                    \"Exception: start_page {%s} is not a valid URL! \"\n                    \"(Use an empty string or None to start from current page.)\"\n                    % start_page\n                )\n            self.open(start_page)\n            time.sleep(0.08)\n            self.wait_for_ready_state_complete()\n        referral_link = (\n            \"\"\"<body>\"\"\"\n            \"\"\"<a class='analytics referral test' href='%s' \"\"\"\n            \"\"\"style='font-family: Arial,sans-serif; \"\"\"\n            \"\"\"font-size: 30px; color: #18a2cd'>\"\"\"\n            \"\"\"Magic Link Button</a></body>\"\"\" % destination_page\n        )\n        self.execute_script(\n            '''document.body.outerHTML = \\\"%s\\\"''' % referral_link\n        )\n        # Now click the generated button\n        self.click(\"a.analytics.referral.test\", timeout=2)\n        time.sleep(0.15)\n        if selector:\n            self.click(selector)\n            time.sleep(0.15)\n\n    def generate_traffic(\n        self, start_page, destination_page, loops=1, selector=None\n    ):\n        \"\"\"Similar to generate_referral(), but can do multiple loops.\n        If a selector is given, clicks that on the destination_page,\n        which can prevent an artificial rise in website bounce-rate.\"\"\"\n        self.__check_scope()\n        for loop in range(loops):\n            self.generate_referral(\n                start_page, destination_page, selector=selector\n            )\n            time.sleep(0.05)\n\n    def generate_referral_chain(self, pages):\n        \"\"\"Use this method to chain the action of creating button links on\n        one website page that will take you to the next page.\n        (When you want to create a referral to a website for traffic\n        generation without increasing the bounce rate, you'll want to visit\n        at least one additional page on that site with a button click.)\"\"\"\n        self.__check_scope()\n        if not isinstance(pages, (list, tuple)):\n            raise Exception(\n                \"Exception: Expecting a list of website pages for chaining!\"\n            )\n        if len(pages) < 2:\n            raise Exception(\n                \"Exception: At least two website pages required for chaining!\"\n            )\n        for page in pages:\n            if not page_utils.is_valid_url(page):\n                raise Exception(\n                    \"Exception: Website page {%s} is not a valid URL!\" % page\n                )\n        for page in pages:\n            self.generate_referral(None, page)\n\n    def generate_traffic_chain(self, pages, loops=1):\n        \"\"\"Similar to generate_referral_chain(), but for multiple loops.\"\"\"\n        self.__check_scope()\n        for loop in range(loops):\n            self.generate_referral_chain(pages)\n            time.sleep(0.05)\n\n    ############\n\n    def wait_for_element_present(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Waits for an element to appear in the HTML of a page.\n        The element does not need be visible (it may be hidden).\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.select(selector, timeout=timeout)\n        elif self.__is_shadow_selector(selector):\n            return self.__wait_for_shadow_element_present(selector, timeout)\n        return page_actions.wait_for_element_present(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n\n    def wait_for_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Waits for an element to appear in the HTML of a page.\n        The element must be visible (it cannot be hidden).\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.select(selector, timeout=timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if by == By.XPATH:\n                    selector = original_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"wf_el\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        if self.__is_shadow_selector(selector):\n            return self.__get_shadow_element(selector, timeout)\n        return page_actions.wait_for_element_visible(\n            self.driver, selector, by, timeout\n        )\n\n    def get_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as wait_for_element_present() - returns the element.\n        The element does not need be visible (it may be hidden).\"\"\"\n        return self.wait_for_element_present(selector, by=by, timeout=timeout)\n\n    def locator(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as wait_for_element_present() - returns the element.\n        The element does not need be visible (it may be hidden).\"\"\"\n        return self.wait_for_element_present(selector, by=by, timeout=timeout)\n\n    def wait_for_selector(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as wait_for_element_present() - returns the element.\n        The element does not need be visible (it may be hidden).\"\"\"\n        return self.wait_for_element_present(selector, by=by, timeout=timeout)\n\n    def wait_for_query_selector(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Waits for an element to appear in the HTML of a page.\n        The element does not need be visible (it may be hidden).\n        This method uses document.querySelector() over Selenium.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.select(css_selector, timeout=timeout)\n        return js_utils.wait_for_css_query_selector(\n            self.driver, css_selector, timeout\n        )\n\n    def assert_element_present(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Similar to wait_for_element_present(), but returns nothing.\n        Waits for an element to appear in the HTML of a page.\n        The element does not need be visible (it may be hidden).\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if isinstance(selector, list):\n            self.assert_elements_present(selector, by=by, timeout=timeout)\n            return True\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_element_present(selector, timeout=timeout)\n            return True\n        if self.__is_shadow_selector(selector):\n            self.__assert_shadow_element_present(selector)\n            return True\n        self.wait_for_element_present(selector, by=by, timeout=timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"as_ep\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_elements_present(self, *args, **kwargs):\n        \"\"\"Similar to self.assert_element_present(),\n            but can assert that multiple elements are present in the HTML.\n        The input is a list of elements.\n        Optional kwargs include \"by\" and \"timeout\" (used by all selectors).\n        Raises an exception if any of the elements are not visible.\n        Allows flexible inputs (Eg. Multiple args or a list of args)\n        Examples:\n            self.assert_elements_present(\"head\", \"style\", \"script\", \"body\")\n            OR\n            self.assert_elements_present([\"head\", \"body\", \"h1\", \"h2\"]) \"\"\"\n        self.__check_scope()\n        selectors = []\n        timeout = None\n        by = By.CSS_SELECTOR\n        for kwarg in kwargs:\n            if kwarg == \"timeout\":\n                timeout = kwargs[\"timeout\"]\n            elif kwarg == \"by\":\n                by = kwargs[\"by\"]\n            elif kwarg == \"selector\" or kwarg == \"selectors\":\n                selector = kwargs[\"selector\"]\n                if isinstance(selector, str):\n                    selectors.append(selector)\n                elif isinstance(selector, list):\n                    selectors_list = selector\n                    for selector in selectors_list:\n                        if isinstance(selector, str):\n                            selectors.append(selector)\n            else:\n                raise Exception('Unknown kwarg: \"%s\"!' % kwarg)\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        for arg in args:\n            if isinstance(arg, list):\n                for selector in arg:\n                    if isinstance(selector, str):\n                        selectors.append(selector)\n            elif isinstance(arg, str):\n                selectors.append(arg)\n        for selector in selectors:\n            if self.__is_shadow_selector(selector):\n                self.__assert_shadow_element_visible(selector)\n                continue\n            self.wait_for_element_present(selector, by=by, timeout=timeout)\n            continue\n        return True\n\n    def find_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Same as wait_for_element_visible() - returns the element\"\"\"\n        return self.wait_for_element_visible(selector, by=by, timeout=timeout)\n\n    def assert_element(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Similar to wait_for_element_visible(), but returns nothing.\n        As above, raises an exception if nothing can be found.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            if self.demo_mode:\n                selector, by = self.__recalculate_selector(\n                    selector, by, xp_ok=False\n                )\n                a_t = \"ASSERT\"\n                if self._language != \"English\":\n                    from seleniumbase.fixtures.words import SD\n\n                    a_t = SD.translate_assert(self._language)\n                messenger_post = \"<b>%s %s</b>: %s\" % (\n                    a_t, by.upper(), selector\n                )\n                self.__highlight_with_assert_success(\n                    messenger_post, selector, by\n                )\n            self.cdp.assert_element(selector, timeout=timeout)\n            return True\n        if isinstance(selector, list):\n            self.assert_elements(selector, by=by, timeout=timeout)\n            return True\n        if self.__is_shadow_selector(selector):\n            self.__assert_shadow_element_visible(selector)\n            return True\n        self.wait_for_element_visible(selector, by=by, timeout=timeout)\n        original_selector = selector\n        if self.demo_mode:\n            selector, by = self.__recalculate_selector(\n                selector, by, xp_ok=False\n            )\n            a_t = \"ASSERT\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert(self._language)\n            messenger_post = \"<b>%s %s</b>: %s\" % (a_t, by.upper(), selector)\n            self.__highlight_with_assert_success(messenger_post, selector, by)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if by == By.XPATH:\n                    selector = original_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"as_el\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_element_visible(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as self.assert_element()\n        As above, raises an exception if nothing can be found.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.assert_element(selector, by=by, timeout=timeout)\n        return True\n\n    def assert_elements(self, *args, **kwargs):\n        \"\"\"Similar to self.assert_element(), but can assert multiple elements.\n        The input is a list of elements.\n        Optional kwargs include \"by\" and \"timeout\" (used by all selectors).\n        Raises an exception if any of the elements are not visible.\n        Allows flexible inputs (Eg. Multiple args or a list of args)\n        Examples:\n            self.assert_elements(\"h1\", \"h2\", \"h3\")\n            OR\n            self.assert_elements([\"h1\", \"h2\", \"h3\"]) \"\"\"\n        self.__check_scope()\n        selectors = []\n        timeout = None\n        by = By.CSS_SELECTOR\n        for kwarg in kwargs:\n            if kwarg == \"timeout\":\n                timeout = kwargs[\"timeout\"]\n            elif kwarg == \"by\":\n                by = kwargs[\"by\"]\n            elif kwarg == \"selector\" or kwarg == \"selectors\":\n                selector = kwargs[\"selector\"]\n                if isinstance(selector, str):\n                    selectors.append(selector)\n                elif isinstance(selector, list):\n                    selectors_list = selector\n                    for selector in selectors_list:\n                        if isinstance(selector, str):\n                            selectors.append(selector)\n            else:\n                raise Exception('Unknown kwarg: \"%s\"!' % kwarg)\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        for arg in args:\n            if isinstance(arg, list):\n                for selector in arg:\n                    if isinstance(selector, str):\n                        selectors.append(selector)\n            elif isinstance(arg, str):\n                selectors.append(arg)\n        for selector in selectors:\n            if self.__is_shadow_selector(selector):\n                self.__assert_shadow_element_visible(selector)\n                continue\n            self.wait_for_element_visible(selector, by=by, timeout=timeout)\n            if self.demo_mode:\n                selector, by = self.__recalculate_selector(selector, by)\n                a_t = \"ASSERT\"\n                if self._language != \"English\":\n                    from seleniumbase.fixtures.words import SD\n\n                    a_t = SD.translate_assert(self._language)\n                messenger_post = \"<b>%s %s</b>: %s\" % (\n                    a_t, by.upper(), selector\n                )\n                self.__highlight_with_assert_success(\n                    messenger_post, selector, by\n                )\n            continue\n        return True\n\n    def assert_elements_visible(self, *args, **kwargs):\n        \"\"\"Same as self.assert_elements()\n        Raises an exception if any element cannot be found.\"\"\"\n        return self.assert_elements(*args, **kwargs)\n\n    ############\n\n    def wait_for_text_visible(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        text = self.__get_type_checked_text(text)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.wait_for_text(text, selector, timeout=timeout)\n        elif self.__is_shadow_selector(selector):\n            return self.__wait_for_shadow_text_visible(text, selector, timeout)\n        return page_actions.wait_for_text_visible(\n            self.driver, text, selector, by, timeout\n        )\n\n    def wait_for_exact_text_visible(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__wait_for_exact_shadow_text_visible(\n                text, selector, timeout\n            )\n        return page_actions.wait_for_exact_text_visible(\n            self.driver, text, selector, by, timeout\n        )\n\n    def wait_for_non_empty_text_visible(\n        self, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Searches for any text in the element of the given selector.\n        Returns the element if it has visible text within the timeout.\n        Raises an exception if the element has no text within the timeout.\n        Whitespace-only text is considered empty text.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            return self.__wait_for_non_empty_shadow_text_visible(\n                selector, timeout\n            )\n        return page_actions.wait_for_non_empty_text_visible(\n            self.driver, selector, by=by, timeout=timeout\n        )\n\n    def wait_for_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"The shorter version of wait_for_text_visible()\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_text_visible(\n            text, selector, by=by, timeout=timeout\n        )\n\n    def wait_for_exact_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"The shorter version of wait_for_exact_text_visible()\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_exact_text_visible(\n            text, selector, by=by, timeout=timeout\n        )\n\n    def wait_for_non_empty_text(\n        self, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"The shorter version of wait_for_non_empty_text_visible()\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_non_empty_text_visible(\n            selector, by=by, timeout=timeout\n        )\n\n    def find_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as wait_for_text_visible() - returns the element\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_text_visible(\n            text, selector, by=by, timeout=timeout\n        )\n\n    def find_exact_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as wait_for_exact_text_visible() - returns the element\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_exact_text_visible(\n            text, selector, by=by, timeout=timeout\n        )\n\n    def find_non_empty_text(\n        self, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as wait_for_non_empty_text_visible() - returns the element\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_non_empty_text_visible(\n            selector, by=by, timeout=timeout\n        )\n\n    def assert_text_visible(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Same as assert_text()\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.assert_text(text, selector, by=by, timeout=timeout)\n\n    def assert_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Similar to wait_for_text_visible()\n        Raises an exception if the element or the text is not found.\n        The text only needs to be a subset within the complete text.\n        The text can be a string or a list/tuple of text substrings.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if isinstance(text, (list, tuple)):\n            text_list = text\n            for _text in text_list:\n                self.wait_for_text_visible(\n                    _text, selector, by=by, timeout=timeout\n                )\n                if self.demo_mode:\n                    a_t = \"ASSERT TEXT\"\n                    i_n = \"in\"\n                    if self._language != \"English\":\n                        from seleniumbase.fixtures.words import SD\n\n                        a_t = SD.translate_assert_text(self._language)\n                        i_n = SD.translate_in(self._language)\n                    messenger_post = \"<b>%s</b>: {%s} %s %s: %s\" % (\n                        a_t, _text, i_n, by.upper(), selector\n                    )\n                    self.__highlight_with_assert_success(\n                        messenger_post, selector, by\n                    )\n        elif self.__is_cdp_swap_needed():\n            if self.demo_mode:\n                a_t = \"ASSERT TEXT\"\n                i_n = \"in\"\n                if self._language != \"English\":\n                    from seleniumbase.fixtures.words import SD\n\n                    a_t = SD.translate_assert_text(self._language)\n                    i_n = SD.translate_in(self._language)\n                messenger_post = \"<b>%s</b>: {%s} %s %s: %s\" % (\n                    a_t, text, i_n, by.upper(), selector\n                )\n                self.__highlight_with_assert_success(\n                    messenger_post, selector, by\n                )\n            self.cdp.assert_text(text, selector, timeout=timeout)\n            return True\n        elif self.__is_shadow_selector(selector):\n            if hasattr(self, \"connect\") and not self.is_connected():\n                self.connect()\n            self.__assert_shadow_text_visible(text, selector, timeout)\n            return True\n        else:\n            if hasattr(self, \"connect\") and not self.is_connected():\n                self.connect()\n            self.wait_for_text_visible(text, selector, by=by, timeout=timeout)\n            if self.demo_mode:\n                a_t = \"ASSERT TEXT\"\n                i_n = \"in\"\n                if self._language != \"English\":\n                    from seleniumbase.fixtures.words import SD\n\n                    a_t = SD.translate_assert_text(self._language)\n                    i_n = SD.translate_in(self._language)\n                messenger_post = \"<b>%s</b>: {%s} %s %s: %s\" % (\n                    a_t, text, i_n, by.upper(), selector\n                )\n                self.__highlight_with_assert_success(\n                    messenger_post, selector, by\n                )\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if by == By.XPATH:\n                    selector = original_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                text_selector = [text, selector]\n                action = [\"as_te\", text_selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_exact_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Similar to assert_text(), but the text must be exact,\n        rather than exist as a subset of the full text.\n        (Extra whitespace at the beginning or the end doesn't count.)\n        Raises an exception if the element or the text is not found.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_exact_text(text, selector, timeout=timeout)\n            return True\n        if self.__is_shadow_selector(selector):\n            self.__assert_exact_shadow_text_visible(text, selector, timeout)\n            return True\n        self.wait_for_exact_text_visible(\n            text, selector, by=by, timeout=timeout\n        )\n        if self.demo_mode:\n            a_t = \"ASSERT EXACT TEXT\"\n            i_n = \"in\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_exact_text(self._language)\n                i_n = SD.translate_in(self._language)\n            messenger_post = \"<b>%s</b>: {%s} %s %s: %s\" % (\n                a_t, text, i_n, by.upper(), selector\n            )\n            self.__highlight_with_assert_success(messenger_post, selector, by)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if by == By.XPATH:\n                    selector = original_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                text_selector = [text, selector]\n                action = [\"as_et\", text_selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_non_empty_text(\n        self, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Assert that the element has any non-empty text visible.\n        Raises an exception if the element has no text within the timeout.\n        Whitespace-only text is considered empty text.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_shadow_selector(selector):\n            self.__assert_non_empty_shadow_text_visible(selector, timeout)\n            return True\n        self.wait_for_non_empty_text_visible(selector, by=by, timeout=timeout)\n        if self.demo_mode:\n            a_t = \"ASSERT NON-EMPTY TEXT\"\n            i_n = \"in\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_non_empty_text(self._language)\n                i_n = SD.translate_in(self._language)\n            messenger_post = \"<b>%s</b> %s %s: %s\" % (\n                a_t, i_n, by.upper(), selector\n            )\n            self.__highlight_with_assert_success(messenger_post, selector, by)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                if by == By.XPATH:\n                    selector = original_selector\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"asnet\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    ############\n\n    def wait_for_link_text_present(self, link_text, timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        for x in range(int(timeout * 5)):\n            shared_utils.check_if_time_limit_exceeded()\n            try:\n                if not self.is_link_text_present(link_text):\n                    raise Exception(\n                        \"Link text {%s} was not found!\" % link_text\n                    )\n                return\n            except Exception:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.2)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        message = \"Link text {%s} was not found after %s second%s!\" % (\n            link_text,\n            timeout,\n            plural,\n        )\n        page_actions.timeout_exception(\"LinkTextNotFoundException\", message)\n\n    def wait_for_partial_link_text_present(self, link_text, timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (timeout * 1000.0)\n        for x in range(int(timeout * 5)):\n            shared_utils.check_if_time_limit_exceeded()\n            try:\n                if not self.is_partial_link_text_present(link_text):\n                    raise Exception(\n                        \"Partial Link text {%s} was not found!\" % link_text\n                    )\n                return\n            except Exception:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.2)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        message = (\n            \"Partial Link text {%s} was not found after %s second%s!\"\n            \"\" % (link_text, timeout, plural)\n        )\n        page_actions.timeout_exception(\"LinkTextNotFoundException\", message)\n\n    def wait_for_link_text_visible(self, link_text, timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.find_element_by_text(text=link_text, tag_name=\"a\")\n        return self.wait_for_element_visible(\n            link_text, by=\"link text\", timeout=timeout\n        )\n\n    def wait_for_link_text(self, link_text, timeout=None):\n        \"\"\"The shorter version of wait_for_link_text_visible()\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_link_text_visible(link_text, timeout=timeout)\n\n    def find_link_text(self, link_text, timeout=None):\n        \"\"\"Same as wait_for_link_text_visible() - returns the element\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_link_text_visible(link_text, timeout=timeout)\n\n    def assert_link_text(self, link_text, timeout=None):\n        \"\"\"Similar to wait_for_link_text_visible(), but returns nothing.\n        As above, raises an exception if nothing can be found.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.find_element(link_text, timeout=timeout)\n            return\n        self.wait_for_link_text_visible(link_text, timeout=timeout)\n        if self.demo_mode:\n            a_t = \"ASSERT LINK TEXT\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_link_text(self._language)\n            messenger_post = \"<b>%s</b>: {%s}\" % (a_t, link_text)\n            self.__highlight_with_assert_success(\n                messenger_post, link_text, by=\"link text\"\n            )\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"as_lt\", link_text, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def wait_for_partial_link_text(self, partial_link_text, timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_element_visible(\n            partial_link_text, by=\"partial link text\", timeout=timeout\n        )\n\n    def find_partial_link_text(self, partial_link_text, timeout=None):\n        \"\"\"Same as wait_for_partial_link_text() - returns the element.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_partial_link_text(\n            partial_link_text, timeout=timeout\n        )\n\n    def assert_partial_link_text(self, partial_link_text, timeout=None):\n        \"\"\"Similar to wait_for_partial_link_text(), but returns nothing.\n        As above, raises an exception if nothing can be found.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.wait_for_partial_link_text(partial_link_text, timeout=timeout)\n        if self.demo_mode:\n            a_t = \"ASSERT PARTIAL LINK TEXT\"\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_link_text(self._language)\n            messenger_post = \"<b>%s</b>: {%s}\" % (a_t, partial_link_text)\n            self.__highlight_with_assert_success(\n                messenger_post, partial_link_text, by=\"partial link text\"\n            )\n        return True\n\n    ############\n\n    def wait_for_element_absent(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Waits for an element to no longer appear in the HTML of a page.\n        A hidden element counts as a present element, which fails this assert.\n        If waiting for elements to be hidden instead of nonexistent,\n        use wait_for_element_not_visible() instead.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.wait_for_element_absent(selector, timeout=timeout)\n            return True\n        return page_actions.wait_for_element_absent(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n\n    def assert_element_absent(self, selector, by=\"css selector\", timeout=None):\n        \"\"\"Similar to wait_for_element_absent()\n        As above, raises an exception if the element stays present.\n        A hidden element counts as a present element, which fails this assert.\n        If you want to assert that elements are hidden instead of nonexistent,\n        use assert_element_not_visible() instead.\n        (Note that hidden elements are still present in the HTML of the page.)\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_element_absent(selector, timeout=timeout)\n            return True\n        self.wait_for_element_absent(selector, by=by, timeout=timeout)\n        return True\n\n    ############\n\n    def wait_for_element_not_visible(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Waits for an element to no longer be visible on a page.\n        The element can be non-existent in the HTML or hidden on the page\n        to qualify as not visible.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        original_selector = selector\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            self.cdp.wait_for_element_not_visible(selector, timeout=timeout)\n            return True\n        return page_actions.wait_for_element_not_visible(\n            self.driver,\n            selector,\n            by,\n            timeout=timeout,\n            original_selector=original_selector,\n        )\n\n    def assert_element_not_visible(\n        self, selector, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Similar to wait_for_element_not_visible()\n        As above, raises an exception if the element stays visible.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        if self.__is_cdp_swap_needed():\n            self.cdp.assert_element_not_visible(selector, timeout=timeout)\n            return True\n        self.wait_for_element_not_visible(selector, by=by, timeout=timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"asenv\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    ############\n\n    def wait_for_text_not_visible(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        if self.__is_cdp_swap_needed():\n            return self.cdp.wait_for_text_not_visible(\n                text, selector=selector, timeout=timeout\n            )\n        return page_actions.wait_for_text_not_visible(\n            self.driver, text, selector, by, timeout\n        )\n\n    def wait_for_exact_text_not_visible(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        return page_actions.wait_for_exact_text_not_visible(\n            self.driver, text, selector, by, timeout\n        )\n\n    def assert_text_not_visible(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Similar to wait_for_text_not_visible()\n        Raises an exception if the text is still visible after timeout.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.wait_for_text_not_visible(text, selector, by=by, timeout=timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                text_selector = [text, selector]\n                action = [\"astnv\", text_selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    def assert_exact_text_not_visible(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None\n    ):\n        \"\"\"Similar to wait_for_exact_text_not_visible()\n        Raises an exception if the exact text is still visible after timeout.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.wait_for_exact_text_not_visible(\n            text, selector, by=by, timeout=timeout\n        )\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                text_selector = [text, selector]\n                action = [\"aetnv\", text_selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        return True\n\n    ############\n\n    def wait_for_attribute_not_present(\n        self, selector, attribute, value=None, by=\"css selector\", timeout=None\n    ):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        return page_actions.wait_for_attribute_not_present(\n            self.driver, selector, attribute, value, by, timeout\n        )\n\n    def assert_attribute_not_present(\n        self, selector, attribute, value=None, by=\"css selector\", timeout=None\n    ):\n        \"\"\"Similar to wait_for_attribute_not_present()\n        Raises an exception if the attribute is still present after timeout.\n        Returns True if successful. Default timeout = SMALL_TIMEOUT.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return self.wait_for_attribute_not_present(\n            selector, attribute, value=value, by=by, timeout=timeout\n        )\n\n    ############\n\n    def wait_for_and_accept_alert(self, timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        alert = page_actions.wait_for_and_accept_alert(self.driver, timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"acc_a\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n        return alert\n\n    def wait_for_and_dismiss_alert(self, timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        alert = page_actions.wait_for_and_dismiss_alert(self.driver, timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"dis_a\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n        return alert\n\n    def wait_for_and_switch_to_alert(self, timeout=None):\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.LARGE_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)\n\n    ############\n\n    def accept_alert(self, timeout=None):\n        \"\"\"Same as wait_for_and_accept_alert(), but smaller default T_O\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        alert = page_actions.wait_for_and_accept_alert(self.driver, timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"acc_a\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n        return alert\n\n    def dismiss_alert(self, timeout=None):\n        \"\"\"Same as wait_for_and_dismiss_alert(), but smaller default T_O\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        alert = page_actions.wait_for_and_dismiss_alert(self.driver, timeout)\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"dis_a\", \"\", origin, time_stamp]\n                self.__extra_actions.append(action)\n        return alert\n\n    def switch_to_alert(self, timeout=None):\n        \"\"\"Same as wait_for_and_switch_to_alert(), but smaller default T_O\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)\n\n    ############\n\n    def quit_extra_driver(self, driver=None):\n        \"\"\"Quits the driver only if it's not the default/initial driver.\n        If a driver is given, quits that, otherwise quits the active driver.\n        Raises an Exception if quitting the default/initial driver.\n        Should only be called if a test has already called get_new_driver().\n        Afterwards, self.driver points to the default/initial driver\n        if self.driver was the one being quit.\n        ----\n        If a test never calls get_new_driver(), this method isn't needed.\n        SeleniumBase automatically quits browsers after tests have ended.\n        Even if tests do call get_new_driver(), you don't need to use this\n        method unless you want to quit extra browsers before a test ends.\n        ----\n        Terminology and important details:\n        * Active driver: The one self.driver is set to. Used within methods.\n        * Default/initial driver: The one that is spun up when tests start.\n        Initially, the active driver and the default driver are the same.\n        The active driver can change when one of these methods is called:\n        > self.get_new_driver()\n        > self.switch_to_default_driver()\n        > self.switch_to_driver()\n        > self.quit_extra_driver() \"\"\"\n        self.__check_scope()\n        if not driver:\n            driver = self.driver\n        if type(driver).__name__ == \"NoneType\":\n            raise Exception(\"The driver to quit was a NoneType variable!\")\n        elif (\n            not hasattr(driver, \"get\")\n            or not hasattr(driver, \"name\")\n            or not hasattr(driver, \"quit\")\n            or not hasattr(driver, \"capabilities\")\n            or not hasattr(driver, \"window_handles\")\n        ):\n            raise Exception(\"The driver to quit does not match a Driver!\")\n        elif self._reuse_session and driver == self._default_driver:\n            raise Exception(\n                \"Cannot quit the initial driver in --reuse-session mode!\\n\"\n                \"This is done automatically after all tests have ended.\\n\"\n                \"Use this method only if get_new_driver() has been called.\"\n            )\n        elif (\n            driver == self._default_driver\n            or (driver in self._drivers_list and len(self._drivers_list) == 1)\n        ):\n            raise Exception(\n                \"Cannot quit the default/initial driver!\\n\"\n                \"This is done automatically at the end of each test.\\n\"\n                \"Use this method only if get_new_driver() has been called.\"\n            )\n        try:\n            if (\n                not is_windows\n                or self.browser == \"ie\"\n                or driver.service.process\n            ):\n                driver.quit()\n        except AttributeError:\n            pass\n        except Exception:\n            pass\n        if driver in self._drivers_list:\n            self._drivers_list.remove(driver)\n            if driver in self._drivers_browser_map:\n                del self._drivers_browser_map[driver]\n        # If the driver to quit was the active driver, switch drivers\n        if driver == self.driver:\n            self.switch_to_default_driver()\n        try:\n            self._check_browser()\n        except Exception:\n            self._default_driver = self._drivers_list[-1]\n            self.switch_to_default_driver()\n\n    ############\n\n    def __assert_eq(self, *args, **kwargs):\n        \"\"\"Minified assert_equal() using only the list diff.\"\"\"\n        minified_exception = None\n        try:\n            self.assertEqual(*args, **kwargs)\n        except Exception as e:\n            str_e = str(e)\n            minified_exception = \"\\nAssertionError:\\n\"\n            lines = str_e.split(\"\\n\")\n            countdown = 3\n            countdown_on = False\n            first_differing = False\n            skip_lines = False\n            for line in lines:\n                if countdown_on:\n                    if not skip_lines:\n                        minified_exception += line + \"\\n\"\n                    countdown = countdown - 1\n                    if countdown == 0:\n                        countdown_on = False\n                        skip_lines = False\n                elif line.startswith(\"First differing\"):\n                    first_differing = True\n                    countdown_on = True\n                    countdown = 3\n                    minified_exception += line + \"\\n\"\n                elif line.startswith(\"First list\"):\n                    countdown_on = True\n                    countdown = 3\n                    if not first_differing:\n                        minified_exception += line + \"\\n\"\n                    else:\n                        skip_lines = True\n                elif line.startswith(\"F\"):\n                    countdown_on = True\n                    countdown = 3\n                    minified_exception += line + \"\\n\"\n                elif line.startswith((\"+\", \"-\")):\n                    minified_exception += line + \"\\n\"\n                elif line.startswith(\"?\"):\n                    minified_exception += line + \"\\n\"\n                elif line.strip().startswith(\"*\"):\n                    minified_exception += line + \"\\n\"\n        if minified_exception:\n            raise VisualException(minified_exception)\n\n    def _process_visual_baseline_logs(self):\n        if not (\n            (python3_11_or_newer and py311_patch2)\n            or \"--pdb\" in sys.argv\n        ):\n            return\n        self.__process_visual_baseline_logs()\n\n    def __process_visual_baseline_logs(self):\n        \"\"\"Save copies of baseline PNGs in \"./latest_logs\" during failures.\n        Also create a side_by_side.html file for visual comparisons.\"\"\"\n        test_logpath = os.path.join(self.log_path, self.__get_test_id())\n        for baseline_copy_tuple in self.__visual_baseline_copies:\n            baseline_path = baseline_copy_tuple[0]\n            baseline_copy_name = baseline_copy_tuple[1]\n            b_c_alt_name = baseline_copy_tuple[2]\n            latest_png_path = baseline_copy_tuple[3]\n            latest_copy_name = baseline_copy_tuple[4]\n            l_c_alt_name = baseline_copy_tuple[5]\n            baseline_copy_path = os.path.join(test_logpath, baseline_copy_name)\n            b_c_alt_path = os.path.join(test_logpath, b_c_alt_name)\n            latest_copy_path = os.path.join(test_logpath, latest_copy_name)\n            l_c_alt_path = os.path.join(test_logpath, l_c_alt_name)\n            if len(self.__visual_baseline_copies) == 1:\n                baseline_copy_path = b_c_alt_path\n                latest_copy_path = l_c_alt_path\n            if (\n                os.path.exists(baseline_path)\n                and not os.path.exists(baseline_copy_path)\n            ):\n                self.__create_log_path_as_needed(test_logpath)\n                shutil.copy(baseline_path, baseline_copy_path)\n            if (\n                os.path.exists(latest_png_path)\n                and not os.path.exists(latest_copy_path)\n            ):\n                self.__create_log_path_as_needed(test_logpath)\n                shutil.copy(latest_png_path, latest_copy_path)\n        if len(self.__visual_baseline_copies) != 1:\n            return  # Skip the rest when deferred visual asserts are used\n        the_html = visual_helper.get_sbs_html()\n        file_path = os.path.join(test_logpath, constants.SideBySide.HTML_FILE)\n        out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(the_html)\n        out_file.close()\n\n    def check_window(\n        self,\n        name=\"default\",\n        level=0,\n        baseline=False,\n        check_domain=True,\n        full_diff=False,\n    ):\n        \"\"\"***  Automated Visual Testing with SeleniumBase  ***\n\n        The first time a test calls self.check_window() for a unique \"name\"\n        parameter provided, it will set a visual baseline, meaning that it\n        creates a folder, saves the URL to a file, saves the current window\n        screenshot to a file, and creates the following three files\n        with the listed data saved:\n        tags_level1.txt  ->  HTML tags from the window\n        tags_level2.txt  ->  HTML tags + attributes from the window\n        tags_level3.txt  ->  HTML tags + attributes/values from the window\n\n        Baseline folders are named based on the test name and the name\n        parameter passed to self.check_window(). The same test can store\n        multiple baseline folders.\n\n        If the baseline is being set/reset, the \"level\" doesn't matter.\n\n        After the first run of self.check_window(), it will compare the\n        HTML tags of the latest window to the one from the initial run.\n        Here's how the level system works:\n        * level=0 ->\n            DRY RUN ONLY - Will perform comparisons to the baseline (and\n                           print out any differences that are found) but\n                           won't fail the test even if differences exist.\n        * level=1 ->\n            HTML tags are compared to tags_level1.txt\n        * level=2 ->\n            HTML tags are compared to tags_level1.txt and\n            HTML tags/attributes are compared to tags_level2.txt\n        * level=3 ->\n            HTML tags are compared to tags_level1.txt and\n            HTML tags + attributes are compared to tags_level2.txt and\n            HTML tags + attributes/values are compared to tags_level3.txt\n        As shown, Level-3 is the most strict, Level-1 is the least strict.\n        If the comparisons from the latest window to the existing baseline\n        don't match, the current test will fail, except for Level-0 tests.\n\n        You can reset the visual baseline on the command line by using:\n            --visual_baseline\n        As long as \"--visual_baseline\" is used on the command line while\n        running tests, the self.check_window() method cannot fail because\n        it will rebuild the visual baseline rather than comparing the html\n        tags of the latest run to the existing baseline. If there are any\n        expected layout changes to a website that you're testing, you'll\n        need to reset the baseline to prevent unnecessary failures.\n\n        self.check_window() will fail with \"Page Domain Mismatch Failure\"\n        if the page domain doesn't match the domain of the baseline,\n        unless \"check_domain\" is set to False when calling check_window().\n\n        If you want to use self.check_window() to compare a web page to\n        a later version of itself from within the same test run, you can\n        add the parameter \"baseline=True\" to the first time you call\n        self.check_window() in a test to use that as the baseline. This\n        only makes sense if you're calling self.check_window() more than\n        once with the same name parameter in the same test.\n\n        If \"full_diff\" is set to False, the error output will only\n        include the first differing element in the list comparison.\n        Set \"full_diff\" to True if you want to see the full output.\n\n        Automated Visual Testing with self.check_window() is not very\n        effective for websites that have dynamic content that changes\n        the layout and structure of web pages. For those, you're much\n        better off using regular SeleniumBase functional testing.\n\n        Example usage:\n            self.check_window(name=\"testing\", level=0)\n            self.check_window(name=\"xkcd_home\", level=1)\n            self.check_window(name=\"github_page\", level=2)\n            self.check_window(name=\"wikipedia_page\", level=3) \"\"\"\n        self.wait_for_ready_state_complete()\n        with suppress(Exception):\n            self.wait_for_element_visible(\n                \"body\", timeout=settings.MINI_TIMEOUT\n            )\n        if self.__needs_minimum_wait():\n            time.sleep(0.08)\n        if level == \"0\":\n            level = 0\n        if level == \"1\":\n            level = 1\n        if level == \"2\":\n            level = 2\n        if level == \"3\":\n            level = 3\n        if level != 0 and level != 1 and level != 2 and level != 3:\n            raise Exception('Parameter \"level\" must be set to 0, 1, 2, or 3!')\n\n        if self.demo_mode:\n            message = (\n                \"WARNING: Using check_window() from Demo Mode may lead \"\n                \"to unexpected results caused by Demo Mode HTML changes.\"\n            )\n            logging.info(message)\n\n        test_id = self.__get_display_id().split(\"::\")[-1]\n\n        if not name or len(name) < 1:\n            name = \"default\"\n        name = str(name)\n\n        visual_helper.visual_baseline_folder_setup()\n        baseline_dir = constants.VisualBaseline.STORAGE_FOLDER\n        visual_baseline_path = os.path.join(baseline_dir, test_id, name)\n        page_url_file = os.path.join(visual_baseline_path, \"page_url.txt\")\n        baseline_png = \"baseline.png\"\n        baseline_png_path = os.path.join(visual_baseline_path, baseline_png)\n        latest_png = \"latest.png\"\n        latest_png_path = os.path.join(visual_baseline_path, latest_png)\n        level_1_file = os.path.join(visual_baseline_path, \"tags_level_1.txt\")\n        level_2_file = os.path.join(visual_baseline_path, \"tags_level_2.txt\")\n        level_3_file = os.path.join(visual_baseline_path, \"tags_level_3.txt\")\n\n        set_baseline = False\n        if baseline or self.visual_baseline:\n            set_baseline = True\n        if not os.path.exists(visual_baseline_path):\n            set_baseline = True\n            try:\n                os.makedirs(visual_baseline_path)\n            except Exception:\n                pass  # Only reachable during multi-threaded test runs\n        if not os.path.exists(page_url_file):\n            set_baseline = True\n        if not os.path.exists(baseline_png_path):\n            set_baseline = True\n        if not os.path.exists(level_1_file):\n            set_baseline = True\n        if not os.path.exists(level_2_file):\n            set_baseline = True\n        if not os.path.exists(level_3_file):\n            set_baseline = True\n\n        page_url = self.get_current_url()\n        soup = self.get_beautiful_soup()\n        html_tags = soup.body.find_all()\n        level_1 = [[tag.name] for tag in html_tags]\n        level_1 = json.loads(json.dumps(level_1))  # Tuples become lists\n        level_2 = [[tag.name, sorted(tag.attrs.keys())] for tag in html_tags]\n        level_2 = json.loads(json.dumps(level_2))  # Tuples become lists\n        level_3 = [[tag.name, sorted(tag.attrs.items())] for tag in html_tags]\n        level_3 = json.loads(json.dumps(level_3))  # Tuples become lists\n\n        if set_baseline:\n            self.save_screenshot(\n                baseline_png, visual_baseline_path, selector=\"body\"\n            )\n            out_file = open(page_url_file, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(page_url)\n            out_file.close()\n            out_file = open(level_1_file, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(json.dumps(level_1))\n            out_file.close()\n            out_file = open(level_2_file, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(json.dumps(level_2))\n            out_file.close()\n            out_file = open(level_3_file, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(json.dumps(level_3))\n            out_file.close()\n\n        baseline_path = os.path.join(visual_baseline_path, baseline_png)\n        baseline_copy_name = \"baseline_%s.png\" % name\n        b_c_alt_name = \"baseline.png\"\n        latest_copy_name = \"baseline_diff_%s.png\" % name\n        l_c_alt_name = \"baseline_diff.png\"\n        baseline_copy_tuple = (\n            baseline_path, baseline_copy_name, b_c_alt_name,\n            latest_png_path, latest_copy_name, l_c_alt_name,\n        )\n        self.__visual_baseline_copies.append(baseline_copy_tuple)\n\n        is_level_0_failure = False\n        if not set_baseline:\n            self.save_screenshot(\n                latest_png, visual_baseline_path, selector=\"body\"\n            )\n            f = open(page_url_file, \"r\")\n            page_url_data = f.read().strip()\n            f.close()\n            f = open(level_1_file, \"r\")\n            level_1_data = json.loads(f.read())\n            f.close()\n            f = open(level_2_file, \"r\")\n            level_2_data = json.loads(f.read())\n            f.close()\n            f = open(level_3_file, \"r\")\n            level_3_data = json.loads(f.read())\n            f.close()\n\n            domain_fail = (\n                \"\\n*\\nPage Domain Mismatch Failure: \"\n                \"Current Page Domain doesn't match the Page Domain of the \"\n                \"Baseline! Can't compare two completely different sites! \"\n                \"Run with --visual_baseline to reset the baseline!\"\n            )\n            level_1_failure = (\n                \"\\n*\\n*** Exception: <Level 1> Visual Diff Failure:\\n\"\n                \"* HTML tags don't match the baseline!\"\n            )\n            level_2_failure = (\n                \"\\n*\\n*** Exception: <Level 2> Visual Diff Failure:\\n\"\n                \"* HTML tag attribute names don't match the baseline!\"\n            )\n            level_3_failure = (\n                \"\\n*\\n*** Exception: <Level 3> Visual Diff Failure:\\n\"\n                \"* HTML tag attribute values don't match the baseline!\"\n            )\n\n            page_domain = self.get_domain_url(page_url)\n            page_data_domain = self.get_domain_url(page_url_data)\n            unittest.TestCase.maxDiff = 65536  # 2^16\n            if level != 0 and check_domain:\n                self.assertEqual(page_data_domain, page_domain, domain_fail)\n            if level == 3:\n                if not full_diff:\n                    self.__assert_eq(level_3_data, level_3, level_3_failure)\n                else:\n                    self.assertEqual(level_3_data, level_3, level_3_failure)\n            if level == 2:\n                if not full_diff:\n                    self.__assert_eq(level_2_data, level_2, level_2_failure)\n                else:\n                    self.assertEqual(level_2_data, level_2, level_2_failure)\n            if level == 1:\n                if not full_diff:\n                    self.__assert_eq(level_1_data, level_1, level_1_failure)\n                else:\n                    self.assertEqual(level_1_data, level_1, level_1_failure)\n            if level == 0:\n                try:\n                    if check_domain:\n                        self.assertEqual(\n                            page_domain, page_data_domain, domain_fail\n                        )\n                    try:\n                        if not full_diff:\n                            self.__assert_eq(\n                                level_1_data, level_1, level_1_failure\n                            )\n                        else:\n                            self.assertEqual(\n                                level_1_data, level_1, level_1_failure\n                            )\n                    except Exception as e:\n                        print(e)\n                    try:\n                        if not full_diff:\n                            self.__assert_eq(\n                                level_2_data, level_2, level_2_failure\n                            )\n                        else:\n                            self.assertEqual(\n                                level_2_data, level_2, level_2_failure\n                            )\n                    except Exception as e:\n                        print(e)\n                    if not full_diff:\n                        self.__assert_eq(\n                            level_3_data, level_3, level_3_failure\n                        )\n                    else:\n                        self.assertEqual(\n                            level_3_data, level_3, level_3_failure\n                        )\n                except Exception as e:\n                    print(e)  # Level-0 Dry Run (Only print the differences)\n                    is_level_0_failure = True\n            unittest.TestCase.maxDiff = None  # Reset unittest.TestCase.maxDiff\n        # Since the check passed, do not save an extra copy of the baseline\n        del self.__visual_baseline_copies[-1]  # .pop() returns the element\n        if is_level_0_failure:\n            # Generating the side_by_side.html file for Level-0 failures\n            test_logpath = os.path.join(self.log_path, self.__get_test_id())\n            if (\n                not os.path.exists(baseline_path)\n                or not os.path.exists(latest_png_path)\n            ):\n                return\n            self.__level_0_visual_f = True\n            if not os.path.exists(test_logpath):\n                self.__create_log_path_as_needed(test_logpath)\n            baseline_copy_path = os.path.join(test_logpath, baseline_copy_name)\n            latest_copy_path = os.path.join(test_logpath, latest_copy_name)\n            if (\n                not os.path.exists(baseline_copy_path)\n                and not os.path.exists(latest_copy_path)\n            ):\n                shutil.copy(baseline_path, baseline_copy_path)\n                shutil.copy(latest_png_path, latest_copy_path)\n            the_html = visual_helper.get_sbs_html(\n                baseline_copy_name, latest_copy_name\n            )\n            alpha_n_d_name = \"\".join([x if x.isalnum() else \"_\" for x in name])\n            side_by_side_name = \"side_by_side_%s.html\" % alpha_n_d_name\n            file_path = os.path.join(test_logpath, side_by_side_name)\n            out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n            out_file.writelines(the_html)\n            out_file.close()\n\n    ############\n\n    def __get_new_timeout(self, timeout):\n        \"\"\"When using --timeout_multiplier=#.#\"\"\"\n        self.__check_scope()\n        try:\n            timeout_multiplier = float(self.timeout_multiplier)\n            if timeout_multiplier <= 0.5:\n                timeout_multiplier = 0.5\n            timeout = int(math.ceil(timeout_multiplier * timeout))\n            return timeout\n        except Exception:\n            # Wrong data type for timeout_multiplier (expecting int or float)\n            return timeout\n\n    ############\n\n    def __is_cdp_swap_needed(self):\n        \"\"\"If the driver is disconnected, use a CDP method when available.\"\"\"\n        cdp_swap_needed = shared_utils.is_cdp_swap_needed(self.driver)\n        if cdp_swap_needed:\n            if not self.cdp:\n                self.cdp = self.driver.cdp\n                self.undetectable = True\n            return True\n        else:\n            return False\n\n    ############\n\n    def __check_scope(self):\n        if hasattr(self, \"browser\"):  # self.browser stores the type of browser\n            return  # All good: setUp() already initialized variables in \"self\"\n        else:\n            message = (\n                \"\\n It looks like you are trying to call a SeleniumBase method\"\n                \"\\n from outside the scope of your test class's `self` object,\"\n                \"\\n which is initialized by calling BaseCase's setUp() method.\"\n                \"\\n The `self` object is where all test variables are defined.\"\n                \"\\n If you created a custom setUp() method (that overrided the\"\n                \"\\n the default one), make sure to call super().setUp() in it.\"\n                \"\\n When using page objects, be sure to pass the `self` object\"\n                \"\\n from your test class into your page object methods so that\"\n                \"\\n they can call BaseCase class methods with all the required\"\n                \"\\n variables, which are initialized during the setUp() method\"\n                \"\\n that runs automatically before all tests called by pytest.\"\n            )\n            raise OutOfScopeException(message)\n\n    ############\n\n    def __get_exception_message(self):\n        \"\"\"This method extracts the message from an exception if there\n        was an exception that occurred during the test, assuming\n        that the exception was in a try/except block and not thrown.\"\"\"\n        exception_info = sys.exc_info()[1]\n        if hasattr(exception_info, \"msg\"):\n            exc_message = exception_info.msg\n        elif hasattr(exception_info, \"message\"):\n            exc_message = exception_info.message\n        elif hasattr(exception_info, \"args\") and len(exception_info.args) == 1:\n            exc_message = exception_info.args[0]\n        else:\n            exc_message = sys.exc_info()\n        return exc_message\n\n    def __add_deferred_assert_failure(self, fs=False):\n        \"\"\"Add a deferred_assert failure to a list for future processing.\"\"\"\n        self.__check_scope()\n        current_url = self.driver.current_url\n        message = self.__get_exception_message()\n        count = self.__deferred_assert_count\n        self.__deferred_assert_failures.append(\n            \"DEFERRED ASSERT #%s: (%s) %s\\n\" % (count, current_url, message)\n        )\n        if fs:\n            self.save_screenshot_to_logs(name=\"deferred_#%s\" % count)\n\n    ############\n\n    def deferred_assert_element(\n        self, selector, by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"A non-terminating assertion for an element visible on the page.\n        Failures will be saved until the process_deferred_asserts()\n        method is called from inside a test, likely at the end of it.\n        If \"fs\" is set to True, a failure screenshot is saved to the\n        \"latest_logs/\" folder for that assertion failure. Otherwise,\n        only the last page screenshot is taken for all failures when\n        calling the process_deferred_asserts() method.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.__deferred_assert_count += 1\n        with suppress(Exception):\n            url = self.get_current_url()\n            if url == self.__last_url_of_deferred_assert:\n                timeout = 0.6  # Was already on page (full wait not needed)\n            else:\n                self.__last_url_of_deferred_assert = url\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"da_el\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        try:\n            self.wait_for_element_visible(selector, by=by, timeout=timeout)\n            return True\n        except Exception:\n            self.__add_deferred_assert_failure(fs=fs)\n            return False\n\n    def deferred_assert_element_present(\n        self, selector, by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"A non-terminating assertion for an element present in the page html.\n        Failures will be saved until the process_deferred_asserts()\n        method is called from inside a test, likely at the end of it.\n        If \"fs\" is set to True, a failure screenshot is saved to the\n        \"latest_logs/\" folder for that assertion failure. Otherwise,\n        only the last page screenshot is taken for all failures when\n        calling the process_deferred_asserts() method.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.__deferred_assert_count += 1\n        with suppress(Exception):\n            url = self.get_current_url()\n            if url == self.__last_url_of_deferred_assert:\n                timeout = 0.6  # Was already on page (full wait not needed)\n            else:\n                self.__last_url_of_deferred_assert = url\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"da_ep\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        try:\n            self.wait_for_element_present(selector, by=by, timeout=timeout)\n            return True\n        except Exception:\n            self.__add_deferred_assert_failure(fs=fs)\n            return False\n\n    def deferred_assert_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"A non-terminating assertion for text from an element on a page.\n        Failures will be saved until the process_deferred_asserts()\n        method is called from inside a test, likely at the end of it.\n        If \"fs\" is set to True, a failure screenshot is saved to the\n        \"latest_logs/\" folder for that assertion failure. Otherwise,\n        only the last page screenshot is taken for all failures when\n        calling the process_deferred_asserts() method.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.__deferred_assert_count += 1\n        with suppress(Exception):\n            url = self.get_current_url()\n            if url == self.__last_url_of_deferred_assert:\n                timeout = 0.6  # Was already on page (full wait not needed)\n            else:\n                self.__last_url_of_deferred_assert = url\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                text_selector = [text, selector]\n                action = [\"da_te\", text_selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        try:\n            self.wait_for_text_visible(text, selector, by=by, timeout=timeout)\n            return True\n        except Exception:\n            self.__add_deferred_assert_failure(fs=fs)\n            return False\n\n    def deferred_assert_exact_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"A non-terminating assertion for exact text from an element.\n        Failures will be saved until the process_deferred_asserts()\n        method is called from inside a test, likely at the end of it.\n        If \"fs\" is set to True, a failure screenshot is saved to the\n        \"latest_logs/\" folder for that assertion failure. Otherwise,\n        only the last page screenshot is taken for all failures when\n        calling the process_deferred_asserts() method.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.__deferred_assert_count += 1\n        with suppress(Exception):\n            url = self.get_current_url()\n            if url == self.__last_url_of_deferred_assert:\n                timeout = 0.6  # Was already on page (full wait not needed)\n            else:\n                self.__last_url_of_deferred_assert = url\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                text_selector = [text, selector]\n                action = [\"da_et\", text_selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        try:\n            self.wait_for_exact_text_visible(\n                text, selector, by=by, timeout=timeout\n            )\n            return True\n        except Exception:\n            self.__add_deferred_assert_failure(fs=fs)\n            return False\n\n    def deferred_assert_non_empty_text(\n        self,\n        selector=\"body\",\n        by=\"css selector\",\n        timeout=None,\n        fs=False,\n    ):\n        \"\"\"A non-terminating assertion for non-empty element text.\n        Failures will be saved until the process_deferred_asserts()\n        method is called from inside a test, likely at the end of it.\n        If \"fs\" is set to True, a failure screenshot is saved to the\n        \"latest_logs/\" folder for that assertion failure. Otherwise,\n        only the last page screenshot is taken for all failures when\n        calling the process_deferred_asserts() method.\"\"\"\n        self.__check_scope()\n        if not timeout:\n            timeout = settings.MINI_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.MINI_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.__deferred_assert_count += 1\n        with suppress(Exception):\n            url = self.get_current_url()\n            if url == self.__last_url_of_deferred_assert:\n                timeout = 0.6  # Was already on page (full wait not needed)\n            else:\n                self.__last_url_of_deferred_assert = url\n        if self.recorder_mode and self.__current_url_is_recordable():\n            if self.get_session_storage_item(\"pause_recorder\") == \"no\":\n                time_stamp = self.execute_script(\"return Date.now();\")\n                origin = self.get_origin()\n                action = [\"danet\", selector, origin, time_stamp]\n                self.__extra_actions.append(action)\n        try:\n            self.wait_for_non_empty_text_visible(\n                selector, by=by, timeout=timeout\n            )\n            return True\n        except Exception:\n            self.__add_deferred_assert_failure(fs=fs)\n            return False\n\n    def deferred_check_window(\n        self,\n        name=\"default\",\n        level=0,\n        baseline=False,\n        check_domain=True,\n        full_diff=False,\n        fs=False,\n    ):\n        \"\"\"A non-terminating assertion for the check_window() method.\n        Failures will be saved until the process_deferred_asserts()\n        method is called from inside a test, likely at the end of it.\n        If \"fs\" is set to True, a failure screenshot is saved to the\n        \"latest_logs/\" folder for that assertion failure. Otherwise,\n        only the last page screenshot is taken for all failures when\n        calling the process_deferred_asserts() method.\"\"\"\n        self.__check_scope()\n        self.__deferred_assert_count += 1\n        try:\n            self.check_window(\n                name=name,\n                level=level,\n                baseline=baseline,\n                check_domain=check_domain,\n                full_diff=full_diff,\n            )\n            return True\n        except Exception:\n            self.__add_deferred_assert_failure(fs=fs)\n            return False\n\n    def process_deferred_asserts(self, print_only=False):\n        \"\"\"To be used with any test that uses deferred_asserts, which are\n        non-terminating verifications that only raise exceptions\n        after this method is called.\n        This is useful for pages with multiple elements to be checked when\n        you want to find as many bugs as possible in a single test run\n        before having all the exceptions get raised simultaneously.\n        Might be more useful if this method is called after processing all\n        the deferred asserts on a single html page so that the failure\n        screenshot matches the location of the deferred asserts.\n        If \"print_only\" is set to True, the exception won't get raised.\"\"\"\n        if self.recorder_mode:\n            time_stamp = self.execute_script(\"return Date.now();\")\n            origin = self.get_origin()\n            action = [\"pr_da\", \"\", origin, time_stamp]\n            self.__extra_actions.append(action)\n        if self.__deferred_assert_failures:\n            exception_output = \"\"\n            exception_output += \"\\n***** DEFERRED ASSERTION FAILURES:\\n\"\n            exception_output += \"TEST: %s\\n\\n\" % self.id()\n            all_failing_checks = self.__deferred_assert_failures\n            self.__deferred_assert_failures = []\n            for tb in all_failing_checks:\n                exception_output += \"%s\\n\" % tb\n            if print_only:\n                print(exception_output)\n            else:\n                raise Exception(exception_output.replace(\"\\\\n\", \"\\n\"))\n\n    ############\n\n    # Alternate naming scheme for the \"deferred_assert\" methods.\n\n    def delayed_assert_element(\n        self, selector, by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"Same as self.deferred_assert_element()\"\"\"\n        return self.deferred_assert_element(\n            selector=selector, by=by, timeout=timeout, fs=fs\n        )\n\n    def delayed_assert_element_present(\n        self, selector, by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"Same as self.deferred_assert_element_present()\"\"\"\n        return self.deferred_assert_element_present(\n            selector=selector, by=by, timeout=timeout, fs=fs\n        )\n\n    def delayed_assert_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"Same as self.deferred_assert_text()\"\"\"\n        return self.deferred_assert_text(\n            text=text, selector=selector, by=by, timeout=timeout, fs=fs\n        )\n\n    def delayed_assert_exact_text(\n        self, text, selector=\"body\", by=\"css selector\", timeout=None, fs=False\n    ):\n        \"\"\"Same as self.deferred_assert_exact_text()\"\"\"\n        return self.deferred_assert_exact_text(\n            text=text, selector=selector, by=by, timeout=timeout, fs=fs\n        )\n\n    def delayed_assert_non_empty_text(\n        self,\n        selector=\"body\",\n        by=\"css selector\",\n        timeout=None,\n        fs=False,\n    ):\n        \"\"\"Same as self.deferred_assert_non_empty_text()\"\"\"\n        return self.deferred_assert_non_empty_text(\n            selector=selector, by=by, timeout=timeout, fs=fs\n        )\n\n    def delayed_check_window(\n        self,\n        name=\"default\",\n        level=0,\n        baseline=False,\n        check_domain=True,\n        full_diff=False,\n        fs=False,\n    ):\n        \"\"\"Same as self.deferred_check_window()\"\"\"\n        return self.deferred_check_window(\n            name=name,\n            level=level,\n            baseline=baseline,\n            check_domain=check_domain,\n            full_diff=full_diff,\n            fs=fs,\n        )\n\n    def process_delayed_asserts(self, print_only=False):\n        \"\"\"Same as self.process_deferred_asserts()\"\"\"\n        self.process_deferred_asserts(print_only=print_only)\n\n    ############\n\n    def create_presentation(\n        self, name=None, theme=\"default\", transition=\"default\"\n    ):\n        \"\"\"Creates a Reveal-JS presentation that you can add slides to.\n        @Params\n        name - If creating multiple presentations at the same time,\n               use this to specify the name of the current presentation.\n        theme - Set a theme with a unique style for the presentation.\n                Valid themes: \"serif\" (default), \"sky\", \"white\", \"black\",\n                              \"simple\", \"league\", \"moon\", \"night\",\n                              \"beige\", \"blood\", and \"solarized\".\n        transition - Set a transition between slides.\n                     Valid transitions: \"none\" (default), \"slide\", \"fade\",\n                                        \"zoom\", \"convex\", and \"concave\".\"\"\"\n        if not name:\n            name = \"default\"\n        if not theme or theme == \"default\":\n            theme = \"serif\"\n        valid_themes = [\n            \"serif\",\n            \"white\",\n            \"black\",\n            \"beige\",\n            \"simple\",\n            \"sky\",\n            \"league\",\n            \"moon\",\n            \"night\",\n            \"blood\",\n            \"solarized\",\n        ]\n        theme = theme.lower()\n        if theme not in valid_themes:\n            raise Exception(\n                \"Theme {%s} not found! Valid themes: %s\"\n                % (theme, valid_themes)\n            )\n        if not transition or transition == \"default\":\n            transition = \"none\"\n        valid_transitions = [\n            \"none\",\n            \"slide\",\n            \"fade\",\n            \"zoom\",\n            \"convex\",\n            \"concave\",\n        ]\n        transition = transition.lower()\n        if transition not in valid_transitions:\n            raise Exception(\n                \"Transition {%s} not found! Valid transitions: %s\"\n                % (transition, valid_transitions)\n            )\n        reveal_theme_css = None\n        if theme == \"serif\":\n            reveal_theme_css = constants.Reveal.SERIF_MIN_CSS\n        elif theme == \"sky\":\n            reveal_theme_css = constants.Reveal.SKY_MIN_CSS\n        elif theme == \"white\":\n            reveal_theme_css = constants.Reveal.WHITE_MIN_CSS\n        elif theme == \"black\":\n            reveal_theme_css = constants.Reveal.BLACK_MIN_CSS\n        elif theme == \"simple\":\n            reveal_theme_css = constants.Reveal.SIMPLE_MIN_CSS\n        elif theme == \"league\":\n            reveal_theme_css = constants.Reveal.LEAGUE_MIN_CSS\n        elif theme == \"moon\":\n            reveal_theme_css = constants.Reveal.MOON_MIN_CSS\n        elif theme == \"night\":\n            reveal_theme_css = constants.Reveal.NIGHT_MIN_CSS\n        elif theme == \"beige\":\n            reveal_theme_css = constants.Reveal.BEIGE_MIN_CSS\n        elif theme == \"blood\":\n            reveal_theme_css = constants.Reveal.BLOOD_MIN_CSS\n        elif theme == \"solarized\":\n            reveal_theme_css = constants.Reveal.SOLARIZED_MIN_CSS\n        else:\n            # Use the default if unable to determine the theme\n            reveal_theme_css = constants.Reveal.SERIF_MIN_CSS\n        new_presentation = (\n            \"<html>\\n\"\n            \"<head>\\n\"\n            '<meta charset=\"utf-8\">\\n'\n            '<meta http-equiv=\"Content-Type\" content=\"text/html\">\\n'\n            '<meta name=\"viewport\" content=\"shrink-to-fit=no\">\\n'\n            '<link rel=\"stylesheet\" href=\"%s\">\\n'\n            '<link rel=\"stylesheet\" href=\"%s\">\\n'\n            \"<style>\\n\"\n            \"pre{background-color:#fbe8d4;border-radius:8px;}\\n\"\n            \"div[flex_div]{height:68vh;margin:0;align-items:center;\"\n            \"justify-content:center;}\\n\"\n            \"img[rounded]{border-radius:16px;max-width:64%%;}\\n\"\n            \"</style>\\n\"\n            \"</head>\\n\\n\"\n            \"<body>\\n\"\n            \"<!-- Generated by SeleniumBase - https://seleniumbase.io -->\\n\"\n            '<div class=\"reveal\">\\n'\n            '<div class=\"slides\">\\n'\n            % (constants.Reveal.MIN_CSS, reveal_theme_css)\n        )\n        self._presentation_slides[name] = []\n        self._presentation_slides[name].append(new_presentation)\n        self._presentation_transition[name] = transition\n\n    def add_slide(\n        self,\n        content=None,\n        image=None,\n        code=None,\n        iframe=None,\n        content2=None,\n        notes=None,\n        transition=None,\n        name=None,\n    ):\n        \"\"\"Allows the user to add slides to a presentation.\n        @Params\n        content - The HTML content to display on the presentation slide.\n        image - Attach an image (from a URL link) to the slide.\n        code - Attach code of any programming language to the slide.\n               Language-detection will be used to add syntax formatting.\n        iframe - Attach an iframe (from a URL link) to the slide.\n        content2 - HTML content to display after adding an image or code.\n        notes - Additional notes to include with the slide.\n                ONLY SEEN if show_notes is set for the presentation.\n        transition - Set a transition between slides. (overrides previous)\n                     Valid transitions: \"none\" (default), \"slide\", \"fade\",\n                                        \"zoom\", \"convex\", and \"concave\".\n        name - If creating multiple presentations at the same time,\n               use this to select the presentation to add slides to.\"\"\"\n        if not name:\n            name = \"default\"\n        if name not in self._presentation_slides:\n            # Create a presentation if it doesn't already exist\n            self.create_presentation(name=name)\n        if not content:\n            content = \"\"\n        if not content2:\n            content2 = \"\"\n        if not notes:\n            notes = \"\"\n        if not transition:\n            transition = self._presentation_transition[name]\n        elif transition == \"default\":\n            transition = \"none\"\n        valid_transitions = [\n            \"none\",\n            \"slide\",\n            \"fade\",\n            \"zoom\",\n            \"convex\",\n            \"concave\",\n        ]\n        transition = transition.lower()\n        if transition not in valid_transitions:\n            raise Exception(\n                \"Transition {%s} not found! Valid transitions: %s\"\n                \"\" % (transition, valid_transitions)\n            )\n        add_line = \"\"\n        if content.startswith(\"<\"):\n            add_line = \"\\n\"\n        html = '\\n<section data-transition=\"%s\">%s%s' % (\n            transition,\n            add_line,\n            content,\n        )\n        if image:\n            html += '\\n<div flex_div><img rounded src=\"%s\" /></div>' % image\n        if code:\n            html += \"\\n<div></div>\"\n            html += '\\n<pre class=\"prettyprint\">\\n%s</pre>' % code\n        if iframe:\n            html += (\n                \"\\n<div></div>\"\n                '\\n<iframe src=\"%s\" style=\"width:92%%;height:550px;\" '\n                'title=\"iframe content\"></iframe>' % iframe\n            )\n        add_line = \"\"\n        if content2.startswith(\"<\"):\n            add_line = \"\\n\"\n        if content2:\n            html += \"%s%s\" % (add_line, content2)\n        html += '\\n<aside class=\"notes\">%s</aside>' % notes\n        html += \"\\n</section>\\n\"\n        if \"<mk-0>\" not in html and \"<mk-1>\" not in html:\n            self._presentation_slides[name].append(html)\n        else:\n            # Generate multiple slides with <mark> and </mark>\n            replacements = False\n            for num in range(32):\n                if \"<mk-%s>\" % num in html and \"</mk-%s>\" % num in html:\n                    replacements = True\n                    new_html = html\n                    new_html = new_html.replace(\"<mk-%s>\" % num, \"<mark>\")\n                    new_html = new_html.replace(\"</mk-%s>\" % num, \"</mark>\")\n                    for num2 in range(32):\n                        if num2 == num:\n                            continue\n                        if \"<mk-%s>\" % num2 not in new_html and num2 >= 2:\n                            break\n                        new_html = new_html.replace(\"<mk-%s>\" % num2, \"\")\n                        new_html = new_html.replace(\"</mk-%s>\" % num2, \"\")\n                    self._presentation_slides[name].append(new_html)\n                else:\n                    if num >= 2:\n                        break\n            if not replacements:\n                # A <mark> is missing a closing tag. Do one.\n                self._presentation_slides[name].append(html)\n\n    def save_presentation(\n        self, name=None, filename=None, show_notes=False, interval=0\n    ):\n        \"\"\"Saves a Reveal-JS Presentation to a file for later use.\n        @Params\n        name - If creating multiple presentations at the same time,\n               use this to select the one you wish to use.\n        filename - The name of the HTML file that you wish to\n                   save the presentation to. (filename must end in \".html\")\n        show_notes - When set to True, the Notes feature becomes enabled,\n                     which allows presenters to see notes next to slides.\n        interval - The delay time between autoplaying slides. (in seconds)\n                   If set to 0 (default), autoplay is disabled.\"\"\"\n        if not name:\n            name = \"default\"\n        if not filename:\n            filename = \"my_presentation.html\"\n        if name not in self._presentation_slides:\n            raise Exception(\"Presentation {%s} does not exist!\" % name)\n        if not filename.endswith(\".html\"):\n            raise Exception('Presentation file must end in \".html\"!')\n        if not interval:\n            interval = 0\n        if interval == 0 and self.interval:\n            interval = float(self.interval)\n        if not isinstance(interval, (int, float)):\n            raise Exception('Expecting a numeric value for \"interval\"!')\n        if interval < 0:\n            raise Exception('The \"interval\" cannot be a negative number!')\n        interval_ms = float(interval) * 1000.0\n        show_notes_str = \"false\"\n        if show_notes:\n            show_notes_str = \"true\"\n        the_html = \"\"\n        for slide in self._presentation_slides[name]:\n            the_html += slide\n        the_html += (\n            \"\\n</div>\\n\"\n            \"</div>\\n\"\n            '<script src=\"%s\"></script>\\n'\n            '<script src=\"%s\"></script>\\n'\n            \"<script>Reveal.initialize(\"\n            \"{showNotes: %s, slideNumber: true, progress: true, hash: false, \"\n            \"autoSlide: %s,});\"\n            \"</script>\\n\"\n            \"</body>\\n\"\n            \"</html>\\n\"\n            % (\n                constants.Reveal.MIN_JS,\n                constants.PrettifyJS.RUN_PRETTIFY_JS,\n                show_notes_str,\n                interval_ms,\n            )\n        )\n\n        # Remove duplicate ChartMaker library declarations\n        chart_libs = \"\"\"\n            <script src=\"%s\"></script>\n            <script src=\"%s\"></script>\n            <script src=\"%s\"></script>\n            <script src=\"%s\"></script>\n            \"\"\" % (\n            constants.HighCharts.HC_JS,\n            constants.HighCharts.EXPORTING_JS,\n            constants.HighCharts.EXPORT_DATA_JS,\n            constants.HighCharts.ACCESSIBILITY_JS,\n        )\n        if the_html.count(chart_libs) > 1:\n            chart_libs_comment = \"<!-- HighCharts Libraries Imported -->\"\n            the_html = the_html.replace(chart_libs, chart_libs_comment)\n            # Only need to import the HighCharts libraries once\n            the_html = the_html.replace(chart_libs_comment, chart_libs, 1)\n        saved_presentations_folder = constants.Presentations.SAVED_FOLDER\n        if saved_presentations_folder.endswith(\"/\"):\n            saved_presentations_folder = saved_presentations_folder[:-1]\n        if not os.path.exists(saved_presentations_folder):\n            with suppress(Exception):\n                os.makedirs(saved_presentations_folder)\n        file_path = os.path.join(saved_presentations_folder, filename)\n        out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(the_html)\n        out_file.close()\n        if self._output_file_saves:\n            print(\"\\n>>> [%s] was saved!\\n\" % file_path)\n        return file_path\n\n    def begin_presentation(\n        self, name=None, filename=None, show_notes=False, interval=0\n    ):\n        \"\"\"Begin a Reveal-JS Presentation in the web browser.\n        @Params\n        name - If creating multiple presentations at the same time,\n               use this to select the one you wish to use.\n        filename - The name of the HTML file that you wish to\n                   save the presentation to. (filename must end in \".html\")\n        show_notes - When set to True, the Notes feature becomes enabled,\n                     which allows presenters to see notes next to slides.\n        interval - The delay time between autoplaying slides. (in seconds)\n                   If set to 0 (default), autoplay is disabled.\"\"\"\n        if self.headless or self.headless2 or self.xvfb:\n            return  # Presentations should not run in headless mode.\n        if not name:\n            name = \"default\"\n        if not filename:\n            filename = \"my_presentation.html\"\n        if name not in self._presentation_slides:\n            raise Exception(\"Presentation {%s} does not exist!\" % name)\n        if not filename.endswith(\".html\"):\n            raise Exception('Presentation file must end in \".html\"!')\n        if not interval:\n            interval = 0\n        if interval == 0 and self.interval:\n            interval = float(self.interval)\n        if not isinstance(interval, (int, float)):\n            raise Exception('Expecting a numeric value for \"interval\"!')\n        if interval < 0:\n            raise Exception('The \"interval\" cannot be a negative number!')\n        end_slide = (\n            '\\n<section data-transition=\"none\">\\n'\n            '<p class=\"End_Presentation_Now\"> </p>\\n</section>\\n'\n        )\n        self._presentation_slides[name].append(end_slide)\n        file_path = self.save_presentation(\n            name=name,\n            filename=filename,\n            show_notes=show_notes,\n            interval=interval,\n        )\n        self._presentation_slides[name].pop()\n        self.open_html_file(file_path)\n        presentation_folder = constants.Presentations.SAVED_FOLDER\n        with suppress(Exception):\n            while (\n                len(self.driver.window_handles) > 0\n                and presentation_folder in self.get_current_url()\n            ):\n                time.sleep(0.05)\n                if self.is_element_visible(\n                    \"section.present p.End_Presentation_Now\"\n                ):\n                    break\n                time.sleep(0.05)\n\n    ############\n\n    def create_pie_chart(\n        self,\n        chart_name=None,\n        title=None,\n        subtitle=None,\n        data_name=None,\n        unit=None,\n        libs=True,\n        labels=True,\n        legend=True,\n    ):\n        \"\"\"Creates a JavaScript pie chart using \"HighCharts\".\n        @Params\n        chart_name - If creating multiple charts,\n                     use this to select which one.\n        title - The title displayed for the chart.\n        subtitle - The subtitle displayed for the chart.\n        data_name - The series name. Useful for multi-series charts.\n                    If no data_name, will default to using \"Series 1\".\n        unit - The description label given to the chart's y-axis values.\n        libs - The option to include Chart libraries (JS and CSS files).\n               Should be set to True (default) for the first time creating\n               a chart on a web page. If creating multiple charts on the\n               same web page, you won't need to re-import the libraries\n               when creating additional charts.\n        labels - If True, displays labels on the chart for data points.\n        legend - If True, displays the data point legend on the chart.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if not data_name:\n            data_name = \"\"\n        style = \"pie\"\n        self.__create_highchart(\n            chart_name=chart_name,\n            title=title,\n            subtitle=subtitle,\n            style=style,\n            data_name=data_name,\n            unit=unit,\n            libs=libs,\n            labels=labels,\n            legend=legend,\n        )\n\n    def create_bar_chart(\n        self,\n        chart_name=None,\n        title=None,\n        subtitle=None,\n        data_name=None,\n        unit=None,\n        libs=True,\n        labels=True,\n        legend=True,\n    ):\n        \"\"\"Creates a JavaScript bar chart using \"HighCharts\".\n        @Params\n        chart_name - If creating multiple charts,\n                     use this to select which one.\n        title - The title displayed for the chart.\n        subtitle - The subtitle displayed for the chart.\n        data_name - The series name. Useful for multi-series charts.\n                    If no data_name, will default to using \"Series 1\".\n        unit - The description label given to the chart's y-axis values.\n        libs - The option to include Chart libraries (JS and CSS files).\n               Should be set to True (default) for the first time creating\n               a chart on a web page. If creating multiple charts on the\n               same web page, you won't need to re-import the libraries\n               when creating additional charts.\n        labels - If True, displays labels on the chart for data points.\n        legend - If True, displays the data point legend on the chart.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if not data_name:\n            data_name = \"\"\n        style = \"bar\"\n        self.__create_highchart(\n            chart_name=chart_name,\n            title=title,\n            subtitle=subtitle,\n            style=style,\n            data_name=data_name,\n            unit=unit,\n            libs=libs,\n            labels=labels,\n            legend=legend,\n        )\n\n    def create_column_chart(\n        self,\n        chart_name=None,\n        title=None,\n        subtitle=None,\n        data_name=None,\n        unit=None,\n        libs=True,\n        labels=True,\n        legend=True,\n    ):\n        \"\"\"Creates a JavaScript column chart using \"HighCharts\".\n        @Params\n        chart_name - If creating multiple charts,\n                     use this to select which one.\n        title - The title displayed for the chart.\n        subtitle - The subtitle displayed for the chart.\n        data_name - The series name. Useful for multi-series charts.\n                    If no data_name, will default to using \"Series 1\".\n        unit - The description label given to the chart's y-axis values.\n        libs - The option to include Chart libraries (JS and CSS files).\n               Should be set to True (default) for the first time creating\n               a chart on a web page. If creating multiple charts on the\n               same web page, you won't need to re-import the libraries\n               when creating additional charts.\n        labels - If True, displays labels on the chart for data points.\n        legend - If True, displays the data point legend on the chart.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if not data_name:\n            data_name = \"\"\n        style = \"column\"\n        self.__create_highchart(\n            chart_name=chart_name,\n            title=title,\n            subtitle=subtitle,\n            style=style,\n            data_name=data_name,\n            unit=unit,\n            libs=libs,\n            labels=labels,\n            legend=legend,\n        )\n\n    def create_line_chart(\n        self,\n        chart_name=None,\n        title=None,\n        subtitle=None,\n        data_name=None,\n        unit=None,\n        zero=False,\n        libs=True,\n        labels=True,\n        legend=True,\n    ):\n        \"\"\"Creates a JavaScript line chart using \"HighCharts\".\n        @Params\n        chart_name - If creating multiple charts,\n                     use this to select which one.\n        title - The title displayed for the chart.\n        subtitle - The subtitle displayed for the chart.\n        data_name - The series name. Useful for multi-series charts.\n                    If no data_name, will default to using \"Series 1\".\n        unit - The description label given to the chart's y-axis values.\n        zero - If True, the y-axis always starts at 0. (Default: False).\n        libs - The option to include Chart libraries (JS and CSS files).\n               Should be set to True (default) for the first time creating\n               a chart on a web page. If creating multiple charts on the\n               same web page, you won't need to re-import the libraries\n               when creating additional charts.\n        labels - If True, displays labels on the chart for data points.\n        legend - If True, displays the data point legend on the chart.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if not data_name:\n            data_name = \"\"\n        style = \"line\"\n        self.__create_highchart(\n            chart_name=chart_name,\n            title=title,\n            subtitle=subtitle,\n            style=style,\n            data_name=data_name,\n            unit=unit,\n            zero=zero,\n            libs=libs,\n            labels=labels,\n            legend=legend,\n        )\n\n    def create_area_chart(\n        self,\n        chart_name=None,\n        title=None,\n        subtitle=None,\n        data_name=None,\n        unit=None,\n        zero=False,\n        libs=True,\n        labels=True,\n        legend=True,\n    ):\n        \"\"\"Creates a JavaScript area chart using \"HighCharts\".\n        @Params\n        chart_name - If creating multiple charts,\n                     use this to select which one.\n        title - The title displayed for the chart.\n        subtitle - The subtitle displayed for the chart.\n        data_name - The series name. Useful for multi-series charts.\n                    If no data_name, will default to using \"Series 1\".\n        unit - The description label given to the chart's y-axis values.\n        zero - If True, the y-axis always starts at 0. (Default: False).\n        libs - The option to include Chart libraries (JS and CSS files).\n               Should be set to True (default) for the first time creating\n               a chart on a web page. If creating multiple charts on the\n               same web page, you won't need to re-import the libraries\n               when creating additional charts.\n        labels - If True, displays labels on the chart for data points.\n        legend - If True, displays the data point legend on the chart.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if not data_name:\n            data_name = \"\"\n        style = \"area\"\n        self.__create_highchart(\n            chart_name=chart_name,\n            title=title,\n            subtitle=subtitle,\n            style=style,\n            data_name=data_name,\n            unit=unit,\n            zero=zero,\n            libs=libs,\n            labels=labels,\n            legend=legend,\n        )\n\n    def __create_highchart(\n        self,\n        chart_name=None,\n        title=None,\n        subtitle=None,\n        style=None,\n        data_name=None,\n        unit=None,\n        zero=False,\n        libs=True,\n        labels=True,\n        legend=True,\n    ):\n        \"\"\"Creates a JavaScript chart using the \"HighCharts\" library.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if not title:\n            title = \"\"\n        if not subtitle:\n            subtitle = \"\"\n        if not style:\n            style = \"pie\"\n        if not data_name:\n            data_name = \"Series 1\"\n        if not unit:\n            unit = \"Values\"\n        if labels:\n            labels = \"true\"\n        else:\n            labels = \"false\"\n        if legend:\n            legend = \"true\"\n        else:\n            legend = \"false\"\n        title = title.replace(\"'\", \"\\\\'\")\n        subtitle = subtitle.replace(\"'\", \"\\\\'\")\n        unit = unit.replace(\"'\", \"\\\\'\")\n        self._chart_count += 1\n        # If chart_libs format is changed, also change: save_presentation()\n        chart_libs = \"\"\"\n            <script src=\"%s\"></script>\n            <script src=\"%s\"></script>\n            <script src=\"%s\"></script>\n            <script src=\"%s\"></script>\n            \"\"\" % (\n            constants.HighCharts.HC_JS,\n            constants.HighCharts.EXPORTING_JS,\n            constants.HighCharts.EXPORT_DATA_JS,\n            constants.HighCharts.ACCESSIBILITY_JS,\n        )\n        if not libs:\n            chart_libs = \"\"\n        chart_css = \"\"\"\n            <style>\n            .highcharts-figure, .highcharts-data-table table {\n                min-width: 320px;\n                max-width: 660px;\n                margin: 1em auto;\n            }\n            .highcharts-data-table table {\n                font-family: Verdana, sans-serif;\n                border-collapse: collapse;\n                border: 1px solid #EBEBEB;\n                margin: 10px auto;\n                text-align: center;\n                width: 100%;\n                max-width: 500px;\n            }\n            .highcharts-data-table caption {\n                padding: 1em 0;\n                font-size: 1.2em;\n                color: #555;\n            }\n            .highcharts-data-table th {\n                font-weight: 600;\n                padding: 0.5em;\n            }\n            .highcharts-data-table td, .highcharts-data-table th,\n            .highcharts-data-table caption {\n                padding: 0.5em;\n            }\n            .highcharts-data-table thead tr,\n            .highcharts-data-table tr:nth-child(even) {\n                background: #f8f8f8;\n            }\n            .highcharts-data-table tr:hover {\n                background: #f1f7ff;\n            }\n            </style>\n            \"\"\"\n        if not libs:\n            chart_css = \"\"\n        chart_description = \"\"\n        chart_figure = \"\"\"\n            <figure class=\"highcharts-figure\">\n                <div id=\"chartcontainer_num_%s\"></div>\n                <p class=\"highcharts-description\">%s</p>\n            </figure>\n            \"\"\" % (\n            self._chart_count,\n            chart_description,\n        )\n        min_zero = \"\"\n        if zero:\n            min_zero = \"min: 0,\"\n        chart_init_1 = \"\"\"\n            <script>\n            // Build the chart\n            Highcharts.chart('chartcontainer_num_%s', {\n            credits: {\n                enabled: false\n            },\n            title: {\n                text: '%s'\n            },\n            subtitle: {\n                text: '%s'\n            },\n            xAxis: { },\n            yAxis: {\n                %s\n                title: {\n                    text: '%s',\n                    style: {\n                        fontSize: '14px'\n                    }\n                },\n                labels: {\n                    useHTML: true,\n                    style: {\n                        fontSize: '14px'\n                    }\n                }\n            },\n            chart: {\n                renderTo: 'statusChart',\n                plotBackgroundColor: null,\n                plotBorderWidth: null,\n                plotShadow: false,\n                type: '%s'\n            },\n            \"\"\" % (\n            self._chart_count,\n            title,\n            subtitle,\n            min_zero,\n            unit,\n            style,\n        )\n        #  \"{series.name}:\"\n        point_format = (\n            r\"<b>{point.y}</b><br />\" r\"<b>{point.percentage:.1f}%</b>\"\n        )\n        if style != \"pie\":\n            point_format = r\"<b>{point.y}</b>\"\n        chart_init_2 = (\n            \"\"\"\n            tooltip: {\n                enabled: true,\n                useHTML: true,\n                style: {\n                    padding: '6px',\n                    fontSize: '14px'\n                },\n                backgroundColor: {\n                    linearGradient: {\n                        x1: 0,\n                        y1: 0,\n                        x2: 0,\n                        y2: 1\n                    },\n                    stops: [\n                        [0, 'rgba(255, 255, 255, 0.78)'],\n                        [0.5, 'rgba(235, 235, 235, 0.76)'],\n                        [1, 'rgba(244, 252, 255, 0.74)']\n                    ]\n                },\n                hideDelay: 40,\n                pointFormat: '%s'\n            },\n            \"\"\"\n            % point_format\n        )\n        chart_init_3 = \"\"\"\n            accessibility: {\n                point: {\n                    valueSuffix: '%%'\n                }\n            },\n            plotOptions: {\n                series: {\n                    states: {\n                        inactive: {\n                            opacity: 0.85\n                        }\n                    }\n                },\n                pie: {\n                    size: \"95%%\",\n                    allowPointSelect: true,\n                    animation: false,\n                    cursor: 'pointer',\n                    dataLabels: {\n                        enabled: %s,\n                        formatter: function() {\n                          if (this.y > 0) {\n                            return this.point.name + ': ' + this.point.y\n                          }\n                        }\n                    },\n                    states: {\n                        hover: {\n                            enabled: true\n                        }\n                    },\n                    showInLegend: %s\n                }\n            },\n            \"\"\" % (\n            labels,\n            legend,\n        )\n        if style != \"pie\":\n            chart_init_3 = \"\"\"\n                allowPointSelect: true,\n                cursor: 'pointer',\n                legend: {\n                    layout: 'vertical',\n                    align: 'right',\n                    verticalAlign: 'middle'\n                },\n                states: {\n                    hover: {\n                        enabled: true\n                    }\n                },\n                plotOptions: {\n                    series: {\n                        dataLabels: {\n                            enabled: %s\n                        },\n                        showInLegend: %s,\n                        animation: false,\n                        shadow: false,\n                        lineWidth: 3,\n                        fillOpacity: 0.5,\n                        marker: {\n                            enabled: true\n                        }\n                    }\n                },\n                \"\"\" % (\n                labels,\n                legend,\n            )\n        chart_init = chart_init_1 + chart_init_2 + chart_init_3\n        color_by_point = \"true\"\n        if style != \"pie\":\n            color_by_point = \"false\"\n        series = \"\"\"\n            series: [{\n            name: '%s',\n            colorByPoint: %s,\n            data: [\n            \"\"\" % (\n            data_name,\n            color_by_point,\n        )\n        new_chart = chart_libs + chart_css + chart_figure + chart_init + series\n        new_chart = textwrap.dedent(new_chart)\n        self._chart_data[chart_name] = []\n        self._chart_label[chart_name] = []\n        self._chart_data[chart_name].append(new_chart)\n        self._chart_first_series[chart_name] = True\n        self._chart_series_count[chart_name] = 1\n\n    def add_series_to_chart(self, data_name=None, chart_name=None):\n        \"\"\"Add a new data series to an existing chart.\n        This allows charts to have multiple data sets.\n        @Params\n        data_name - Set the series name. Useful for multi-series charts.\n        chart_name - If creating multiple charts,\n                     use this to select which one.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        self._chart_series_count[chart_name] += 1\n        if not data_name:\n            data_name = \"Series %s\" % self._chart_series_count[chart_name]\n        series = (\n            \"\"\"\n            ]\n            },\n            {\n            name: '%s',\n            colorByPoint: false,\n            data: [\n            \"\"\"\n            % data_name\n        )\n        self._chart_data[chart_name].append(series)\n        self._chart_first_series[chart_name] = False\n\n    def add_data_point(self, label, value, color=None, chart_name=None):\n        \"\"\"Add a data point to a SeleniumBase-generated chart.\n        @Params\n        label - The label name for the data point.\n        value - The numeric value of the data point.\n        color - The HTML color of the data point.\n                Can be an RGB color. Eg: \"#55ACDC\".\n                Can also be a named color. Eg: \"Teal\".\n        chart_name - If creating multiple charts,\n                     use this to select which one.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if chart_name not in self._chart_data:\n            # Create a chart if it doesn't already exist\n            self.create_pie_chart(chart_name=chart_name)\n        if not value:\n            value = 0\n        if not isinstance(value, (int, float)):\n            raise Exception('Expecting a numeric value for \"value\"!')\n        if not color:\n            color = \"\"\n        else:\n            color = color.replace(\"'\", \"\\\\'\")\n        label = label.replace(\"'\", \"\\\\'\")\n        if color:\n            data_point = \"\"\"\n                {\n                name: '%s',\n                y: %s,\n                color: '%s'\n                },\n                \"\"\" % (\n                label,\n                value,\n                color,\n            )\n        else:\n            data_point = \"\"\"\n                {\n                name: '%s',\n                y: %s,\n                },\n                \"\"\" % (\n                label,\n                value,\n            )\n        data_point = textwrap.dedent(data_point)\n        self._chart_data[chart_name].append(data_point)\n        if self._chart_first_series[chart_name]:\n            self._chart_label[chart_name].append(label)\n\n    def save_chart(self, chart_name=None, filename=None, folder=None):\n        \"\"\"Saves a SeleniumBase-generated chart to a file for later use.\n        @Params\n        chart_name - If creating multiple charts at the same time,\n                     use this to select the one you wish to use.\n        filename - The name of the HTML file that you wish to\n                   save the chart to. (filename must end in \".html\")\n        folder - The name of the folder where you wish to\n                 save the HTML file. (Default: \"./saved_charts/\")\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if not filename:\n            filename = \"my_chart.html\"\n        if chart_name not in self._chart_data:\n            raise Exception(\"Chart {%s} does not exist!\" % chart_name)\n        if not filename.endswith(\".html\"):\n            raise Exception('Chart file must end in \".html\"!')\n        the_html = '<meta charset=\"utf-8\">\\n'\n        the_html += '<meta http-equiv=\"Content-Type\" content=\"text/html\">\\n'\n        the_html += '<meta name=\"viewport\" content=\"shrink-to-fit=no\">\\n'\n        for chart_data_point in self._chart_data[chart_name]:\n            the_html += chart_data_point\n        the_html += \"\"\"\n            ]\n                }]\n            });\n            </script>\n            \"\"\"\n        axis = \"xAxis: {\\n\"\n        axis += \"    labels: {\\n\"\n        axis += \"        useHTML: true,\\n\"\n        axis += \"        style: {\\n\"\n        axis += \"            fontSize: '14px',\\n\"\n        axis += \"        },\\n\"\n        axis += \"    },\\n\"\n        axis += \"categories: [\"\n        for label in self._chart_label[chart_name]:\n            axis += \"'%s',\" % label\n        axis += \"], crosshair: false},\"\n        the_html = the_html.replace(\"xAxis: { },\", axis)\n        if not folder:\n            saved_charts_folder = constants.Charts.SAVED_FOLDER\n        else:\n            saved_charts_folder = folder\n        if saved_charts_folder.endswith(\"/\"):\n            saved_charts_folder = saved_charts_folder[:-1]\n        if not os.path.exists(saved_charts_folder):\n            with suppress(Exception):\n                os.makedirs(saved_charts_folder)\n        file_path = os.path.join(saved_charts_folder, filename)\n        out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(the_html)\n        out_file.close()\n        if self._output_file_saves:\n            print(\"\\n>>> [%s] was saved!\" % file_path)\n        return file_path\n\n    def display_chart(self, chart_name=None, filename=None, interval=0):\n        \"\"\"Displays a SeleniumBase-generated chart in the browser window.\n        @Params\n        chart_name - If creating multiple charts at the same time,\n                     use this to select the one you wish to use.\n        filename - The name of the HTML file that you wish to\n                   save the chart to. (filename must end in \".html\")\n        interval - The delay time for auto-advancing charts. (in seconds)\n                   If set to 0 (default), auto-advancing is disabled.\"\"\"\n        if self.headless or self.headless2 or self.xvfb:\n            interval = 1  # Race through chart if running in headless mode\n        if not chart_name:\n            chart_name = \"default\"\n        if not filename:\n            filename = \"my_chart.html\"\n        if not interval:\n            interval = 0\n        if interval == 0 and self.interval:\n            interval = float(self.interval)\n        if not isinstance(interval, (int, float)):\n            raise Exception('Expecting a numeric value for \"interval\"!')\n        if interval < 0:\n            raise Exception('The \"interval\" cannot be a negative number!')\n        if chart_name not in self._chart_data:\n            raise Exception(\"Chart {%s} does not exist!\" % chart_name)\n        if not filename.endswith(\".html\"):\n            raise Exception('Chart file must end in \".html\"!')\n        file_path = self.save_chart(chart_name=chart_name, filename=filename)\n        self.open_html_file(file_path)\n        chart_folder = constants.Charts.SAVED_FOLDER\n        if interval == 0:\n            with suppress(Exception):\n                print(\"\\n*** Close the browser window to continue ***\")\n                # Will also continue if manually navigating to a new page\n                while len(self.driver.window_handles) > 0 and (\n                    chart_folder in self.get_current_url()\n                ):\n                    time.sleep(0.05)\n        else:\n            with suppress(Exception):\n                start_ms = time.time() * 1000.0\n                stop_ms = start_ms + (interval * 1000.0)\n                for x in range(int(interval * 10)):\n                    now_ms = time.time() * 1000.0\n                    if now_ms >= stop_ms:\n                        break\n                    if len(self.driver.window_handles) == 0:\n                        break\n                    if chart_folder not in self.get_current_url():\n                        break\n                    time.sleep(0.1)\n\n    def extract_chart(self, chart_name=None):\n        \"\"\"Extracts the HTML from a SeleniumBase-generated chart.\n        @Params\n        chart_name - If creating multiple charts at the same time,\n                     use this to select the one you wish to use.\"\"\"\n        if not chart_name:\n            chart_name = \"default\"\n        if chart_name not in self._chart_data:\n            raise Exception(\"Chart {%s} does not exist!\" % chart_name)\n        the_html = \"\"\n        for chart_data_point in self._chart_data[chart_name]:\n            the_html += chart_data_point\n        the_html += \"\"\"\n            ]\n                }]\n            });\n            </script>\n            \"\"\"\n        axis = \"xAxis: {\\n\"\n        axis += \"    labels: {\\n\"\n        axis += \"        useHTML: true,\\n\"\n        axis += \"        style: {\\n\"\n        axis += \"            fontSize: '14px',\\n\"\n        axis += \"        },\\n\"\n        axis += \"    },\\n\"\n        axis += \"categories: [\"\n        for label in self._chart_label[chart_name]:\n            axis += \"'%s',\" % label\n        axis += \"], crosshair: false},\"\n        the_html = the_html.replace(\"xAxis: { },\", axis)\n        self._chart_xcount += 1\n        the_html = the_html.replace(\n            \"chartcontainer_num_\", \"chartcontainer_%s_\" % self._chart_xcount\n        )\n        return the_html\n\n    ############\n\n    def create_tour(self, name=None, theme=None):\n        \"\"\"Creates a guided tour for any website.\n        The default theme is the IntroJS Library.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        theme - Sets the default theme for the website tour. Available themes:\n                \"Bootstrap\", \"DriverJS\", \"Hopscotch\", \"IntroJS\", \"Shepherd\".\n                The \"Shepherd\" library includes variations: \"light\"/\"arrows\",\n                \"dark\", \"default\", \"square\", and \"square-dark\".\"\"\"\n        if not name:\n            name = \"default\"\n        if theme:\n            if theme.lower() == \"bootstrap\":\n                self.create_bootstrap_tour(name)\n            elif theme.lower() == \"hopscotch\":\n                self.create_hopscotch_tour(name)\n            elif theme.lower() == \"intro\":\n                self.create_introjs_tour(name)\n            elif theme.lower() == \"introjs\":\n                self.create_introjs_tour(name)\n            elif theme.lower() == \"driver\":\n                self.create_driverjs_tour(name)\n            elif theme.lower() == \"driverjs\":\n                self.create_driverjs_tour(name)\n            elif theme.lower() == \"shepherd\":\n                self.create_shepherd_tour(name, theme=\"light\")\n            elif theme.lower() == \"light\":\n                self.create_shepherd_tour(name, theme=\"light\")\n            elif theme.lower() == \"arrows\":\n                self.create_shepherd_tour(name, theme=\"light\")\n            elif theme.lower() == \"dark\":\n                self.create_shepherd_tour(name, theme=\"dark\")\n            elif theme.lower() == \"square\":\n                self.create_shepherd_tour(name, theme=\"square\")\n            elif theme.lower() == \"square-dark\":\n                self.create_shepherd_tour(name, theme=\"square-dark\")\n            elif theme.lower() == \"default\":\n                self.create_shepherd_tour(name, theme=\"default\")\n            else:\n                self.create_introjs_tour(name)\n        else:\n            self.create_introjs_tour(name)\n\n    def create_shepherd_tour(self, name=None, theme=None):\n        \"\"\"Creates a Shepherd JS website tour.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        theme - Sets the default theme for the tour.\n                Choose from \"light\"/\"arrows\", \"dark\", \"default\", \"square\",\n                and \"square-dark\". (\"light\" is used if None is selected.)\"\"\"\n        shepherd_theme = \"shepherd-theme-arrows\"\n        if theme:\n            if theme.lower() == \"default\":\n                shepherd_theme = \"shepherd-theme-default\"\n            elif theme.lower() == \"dark\":\n                shepherd_theme = \"shepherd-theme-dark\"\n            elif theme.lower() == \"light\":\n                shepherd_theme = \"shepherd-theme-arrows\"\n            elif theme.lower() == \"arrows\":\n                shepherd_theme = \"shepherd-theme-arrows\"\n            elif theme.lower() == \"square\":\n                shepherd_theme = \"shepherd-theme-square\"\n            elif theme.lower() == \"square-dark\":\n                shepherd_theme = \"shepherd-theme-square-dark\"\n        if not name:\n            name = \"default\"\n        new_tour = (\n            \"\"\"\n            // Shepherd Tour\n            var tour = new Shepherd.Tour({\n                defaults: {\n                    classes: '%s',\n                    scrollTo: true\n                }\n            });\n            var allButtons = {\n                skip: {\n                    text: \"Skip\",\n                    action: tour.cancel,\n                    classes: 'shepherd-button-secondary tour-button-left'\n                },\n                back: {\n                    text: \"Back\",\n                    action: tour.back,\n                    classes: 'shepherd-button-secondary'\n                },\n                next: {\n                    text: \"Next\",\n                    action: tour.next,\n                    classes: 'shepherd-button-primary tour-button-right'\n                },\n            };\n            var firstStepButtons = [allButtons.skip, allButtons.next];\n            var midTourButtons = [allButtons.back, allButtons.next];\n            \"\"\"\n            % shepherd_theme\n        )\n        self._tour_steps[name] = []\n        self._tour_steps[name].append(new_tour)\n\n    def create_bootstrap_tour(self, name=None):\n        \"\"\"Creates a Bootstrap tour for a website.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\"\"\"\n        if not name:\n            name = \"default\"\n        new_tour = \"\"\"\n            // Bootstrap Tour\n            var tour = new Tour({\n            container: 'body',\n            animation: true,\n            keyboard: true,\n            orphan: true,\n            smartPlacement: true,\n            autoscroll: true,\n            backdrop: true,\n            backdropContainer: 'body',\n            backdropPadding: 3,\n            });\n            tour.addSteps([\n            \"\"\"\n        self._tour_steps[name] = []\n        self._tour_steps[name].append(new_tour)\n\n    def create_driverjs_tour(self, name=None):\n        \"\"\"Creates a DriverJS tour for a website.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\"\"\"\n        if not name:\n            name = \"default\"\n        new_tour = \"\"\"\n            // DriverJS Tour\n            var tour = new Driver({\n                opacity: 0.24,  // Background opacity (0: no popover / overlay)\n                padding: 6,    // Distance of element from around the edges\n                allowClose: false, // Whether clicking on overlay should close\n                overlayClickNext: false, // Move to next step on overlay click\n                doneBtnText: 'Done', // Text that appears on the Done button\n                closeBtnText: 'Close', // Text appearing on the Close button\n                nextBtnText: 'Next', // Text that appears on the Next button\n                prevBtnText: 'Previous', // Text appearing on Previous button\n                showButtons: true, // This shows control buttons in the footer\n                keyboardControl: true, // (escape to close, arrow keys to move)\n                animate: true,   // Animate while changing highlighted element\n            });\n            tour.defineSteps([\n            \"\"\"\n        self._tour_steps[name] = []\n        self._tour_steps[name].append(new_tour)\n\n    def create_hopscotch_tour(self, name=None):\n        \"\"\"Creates a Hopscotch tour for a website.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\"\"\"\n        if not name:\n            name = \"default\"\n\n        new_tour = \"\"\"\n            // Hopscotch Tour\n            var tour = {\n            id: \"hopscotch_tour\",\n            steps: [\n            \"\"\"\n\n        self._tour_steps[name] = []\n        self._tour_steps[name].append(new_tour)\n\n    def create_introjs_tour(self, name=None):\n        \"\"\"Creates an IntroJS tour for a website.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\"\"\"\n        if not hasattr(sb_config, \"introjs_theme_color\"):\n            sb_config.introjs_theme_color = constants.TourColor.theme_color\n        if not hasattr(sb_config, \"introjs_hover_color\"):\n            sb_config.introjs_hover_color = constants.TourColor.hover_color\n        if not name:\n            name = \"default\"\n        new_tour = \"\"\"\n            // IntroJS Tour\n            function startIntro(){\n            var intro = introJs();\n            intro.setOptions({\n            steps: [\n            \"\"\"\n        self._tour_steps[name] = []\n        self._tour_steps[name].append(new_tour)\n\n    def set_introjs_colors(self, theme_color=None, hover_color=None):\n        \"\"\"Use this method to set the theme colors for IntroJS tours.\n        Args must be hex color values that start with a \"#\" sign.\n        If a color isn't specified, the color will reset to the default.\n        The border color of buttons is set to the hover color.\n        @Params\n        theme_color - The color of buttons.\n        hover_color - The color of buttons after hovering over them.\"\"\"\n        if not hasattr(sb_config, \"introjs_theme_color\"):\n            sb_config.introjs_theme_color = constants.TourColor.theme_color\n        if not hasattr(sb_config, \"introjs_hover_color\"):\n            sb_config.introjs_hover_color = constants.TourColor.hover_color\n        if theme_color:\n            match = re.search(r\"^#(?:[0-9a-fA-F]{3}){1,2}$\", theme_color)\n            if not match:\n                raise Exception(\n                    'Expecting a hex value color that starts with \"#\"!'\n                )\n            sb_config.introjs_theme_color = theme_color\n        else:\n            sb_config.introjs_theme_color = constants.TourColor.theme_color\n        if hover_color:\n            match = re.search(r\"^#(?:[0-9a-fA-F]{3}){1,2}$\", hover_color)\n            if not match:\n                raise Exception(\n                    'Expecting a hex value color that starts with \"#\"!'\n                )\n            sb_config.introjs_hover_color = hover_color\n        else:\n            sb_config.introjs_hover_color = constants.TourColor.hover_color\n\n    def add_tour_step(\n        self,\n        message,\n        selector=None,\n        name=None,\n        title=None,\n        theme=None,\n        alignment=None,\n        duration=None,\n    ):\n        \"\"\"Allows the user to add tour steps for a website.\n        @Params\n        message - The message to display.\n        selector - The CSS Selector of the Element to attach to.\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        title - Additional header text that appears above the message.\n        theme - (Shepherd Tours ONLY) The styling of the tour step.\n                Choose from \"light\"/\"arrows\", \"dark\", \"default\", \"square\",\n                and \"square-dark\". (\"arrows\" is used if None is selected.)\n        alignment - Choose from \"top\", \"bottom\", \"left\", and \"right\".\n                    (\"top\" is default, except for Hopscotch and DriverJS).\n        duration - (Bootstrap Tours ONLY) The amount of time, in seconds,\n                   before automatically advancing to the next tour step.\"\"\"\n        if not selector:\n            selector = \"html\"\n        if page_utils.is_name_selector(selector):\n            name = page_utils.get_name_from_selector(selector)\n            selector = '[name=\"%s\"]' % name\n        if page_utils.is_xpath_selector(selector):\n            selector = self.convert_to_css_selector(selector, By.XPATH)\n        selector = self.__escape_quotes_if_needed(selector)\n        if not name:\n            name = \"default\"\n        if name not in self._tour_steps:\n            # By default, will create an IntroJS tour if no tours exist\n            self.create_tour(name=name, theme=\"introjs\")\n        if not title:\n            title = \"\"\n        title = self.__escape_quotes_if_needed(title)\n        if message:\n            message = self.__escape_quotes_if_needed(message)\n        else:\n            message = \"\"\n        if not alignment or alignment not in [\n            \"top\",\n            \"bottom\",\n            \"left\",\n            \"right\",\n        ]:\n            t_name = self._tour_steps[name][0]\n            if \"Hopscotch\" not in t_name and \"DriverJS\" not in t_name:\n                alignment = \"top\"\n            else:\n                alignment = \"bottom\"\n        if \"Bootstrap\" in self._tour_steps[name][0]:\n            self.__add_bootstrap_tour_step(\n                message,\n                selector=selector,\n                name=name,\n                title=title,\n                alignment=alignment,\n                duration=duration,\n            )\n        elif \"DriverJS\" in self._tour_steps[name][0]:\n            self.__add_driverjs_tour_step(\n                message,\n                selector=selector,\n                name=name,\n                title=title,\n                alignment=alignment,\n            )\n        elif \"Hopscotch\" in self._tour_steps[name][0]:\n            self.__add_hopscotch_tour_step(\n                message,\n                selector=selector,\n                name=name,\n                title=title,\n                alignment=alignment,\n            )\n        elif \"IntroJS\" in self._tour_steps[name][0]:\n            self.__add_introjs_tour_step(\n                message,\n                selector=selector,\n                name=name,\n                title=title,\n                alignment=alignment,\n            )\n        else:\n            self.__add_shepherd_tour_step(\n                message,\n                selector=selector,\n                name=name,\n                title=title,\n                theme=theme,\n                alignment=alignment,\n            )\n\n    def __add_shepherd_tour_step(\n        self,\n        message,\n        selector=None,\n        name=None,\n        title=None,\n        theme=None,\n        alignment=None,\n    ):\n        \"\"\"Allows the user to add tour steps for a website.\n        @Params\n        message - The message to display.\n        selector - The CSS Selector of the Element to attach to.\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        title - Additional header text that appears above the message.\n        theme - (Shepherd Tours ONLY) The styling of the tour step.\n                Choose from \"light\"/\"arrows\", \"dark\", \"default\", \"square\",\n                and \"square-dark\". (\"arrows\" is used if None is selected.)\n        alignment - Choose from \"top\", \"bottom\", \"left\", and \"right\".\n                    (\"top\" is the default alignment).\"\"\"\n        if theme == \"default\":\n            shepherd_theme = \"shepherd-theme-default\"\n        elif theme == \"dark\":\n            shepherd_theme = \"shepherd-theme-dark\"\n        elif theme == \"light\":\n            shepherd_theme = \"shepherd-theme-arrows\"\n        elif theme == \"arrows\":\n            shepherd_theme = \"shepherd-theme-arrows\"\n        elif theme == \"square\":\n            shepherd_theme = \"shepherd-theme-square\"\n        elif theme == \"square-dark\":\n            shepherd_theme = \"shepherd-theme-square-dark\"\n        else:\n            shepherd_base_theme = re.search(\n                r\"[\\S\\s]+classes: '([\\S\\s]+)',[\\S\\s]+\",\n                self._tour_steps[name][0],\n            ).group(1)\n            shepherd_theme = shepherd_base_theme\n        shepherd_classes = shepherd_theme\n        if selector == \"html\":\n            shepherd_classes += \" shepherd-orphan\"\n        buttons = \"firstStepButtons\"\n        if len(self._tour_steps[name]) > 1:\n            buttons = \"midTourButtons\"\n        step = \"\"\"tour.addStep('%s', {\n                    title: '%s',\n                    classes: '%s',\n                    text: '%s',\n                    attachTo: {element: '%s', on: '%s'},\n                    buttons: %s,\n                    advanceOn: '.docs-link click'\n                });\"\"\" % (\n            name,\n            title,\n            shepherd_classes,\n            message,\n            selector,\n            alignment,\n            buttons,\n        )\n        self._tour_steps[name].append(step)\n\n    def __add_bootstrap_tour_step(\n        self,\n        message,\n        selector=None,\n        name=None,\n        title=None,\n        alignment=None,\n        duration=None,\n    ):\n        \"\"\"Allows the user to add tour steps for a website.\n        @Params\n        message - The message to display.\n        selector - The CSS Selector of the Element to attach to.\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        title - Additional header text that appears above the message.\n        alignment - Choose from \"top\", \"bottom\", \"left\", and \"right\".\n                    (\"top\" is the default alignment).\n        duration - (Bootstrap Tours ONLY) The amount of time, in seconds,\n                   before automatically advancing to the next tour step.\"\"\"\n        if selector != \"html\":\n            selector = self.__make_css_match_first_element_only(selector)\n            element_row = \"element: '%s',\" % selector\n        else:\n            element_row = \"\"\n        if not duration:\n            duration = \"0\"\n        else:\n            duration = str(float(duration) * 1000.0)\n        bd = \"backdrop: true,\"\n        if selector == \"html\":\n            bd = \"backdrop: false,\"\n        step = \"\"\"{\n                %s\n                title: '%s',\n                content: '%s',\n                orphan: true,\n                autoscroll: true,\n                %s\n                placement: '%s',\n                smartPlacement: true,\n                duration: %s,\n                },\"\"\" % (\n            element_row,\n            title,\n            message,\n            bd,\n            alignment,\n            duration,\n        )\n        self._tour_steps[name].append(step)\n\n    def __add_driverjs_tour_step(\n        self, message, selector=None, name=None, title=None, alignment=None\n    ):\n        \"\"\"Allows the user to add tour steps for a website.\n        @Params\n        message - The message to display.\n        selector - The CSS Selector of the Element to attach to.\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        title - Additional header text that appears above the message.\n        alignment - Choose from \"top\", \"bottom\", \"left\", and \"right\".\n                    (\"top\" is the default alignment).\"\"\"\n        message = (\n            '<font size=\"3\" color=\"#33477B\"><b>' + message + \"</b></font>\"\n        )\n        title_row = \"\"\n        if not title:\n            title_row = \"title: '%s',\" % message\n            message = \"\"\n        else:\n            title_row = \"title: '%s',\" % title\n        align_row = \"position: '%s',\" % alignment\n        ani_row = \"animate: true,\"\n        if not selector or selector == \"html\" or selector == \"body\":\n            selector = \"body\"\n            ani_row = \"animate: false,\"\n            align_row = \"position: '%s',\" % \"mid-center\"\n        element_row = \"element: '%s',\" % selector\n        desc_row = \"description: '%s',\" % message\n        step = \"\"\"{\n                %s\n                %s\n                popover: {\n                  className: 'popover-class',\n                  %s\n                  %s\n                  %s\n                }\n                },\"\"\" % (\n            element_row,\n            ani_row,\n            title_row,\n            desc_row,\n            align_row,\n        )\n        self._tour_steps[name].append(step)\n\n    def __add_hopscotch_tour_step(\n        self, message, selector=None, name=None, title=None, alignment=None\n    ):\n        \"\"\"Allows the user to add tour steps for a website.\n        @Params\n        message - The message to display.\n        selector - The CSS Selector of the Element to attach to.\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        title - Additional header text that appears above the message.\n        alignment - Choose from \"top\", \"bottom\", \"left\", and \"right\".\n                    (\"bottom\" is the default alignment).\"\"\"\n        arrow_offset_row = None\n        if not selector or selector == \"html\":\n            selector = \"head\"\n            alignment = \"bottom\"\n            arrow_offset_row = \"arrowOffset: '200',\"\n        else:\n            arrow_offset_row = \"\"\n        step = \"\"\"{\n                target: '%s',\n                title: '%s',\n                content: '%s',\n                %s\n                showPrevButton: 'true',\n                scrollDuration: '550',\n                placement: '%s'},\n                \"\"\" % (\n            selector,\n            title,\n            message,\n            arrow_offset_row,\n            alignment,\n        )\n        self._tour_steps[name].append(step)\n\n    def __add_introjs_tour_step(\n        self, message, selector=None, name=None, title=None, alignment=None\n    ):\n        \"\"\"Allows the user to add tour steps for a website.\n        @Params\n        message - The message to display.\n        selector - The CSS Selector of the Element to attach to.\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        title - Additional header text that appears above the message.\n        alignment - Choose from \"top\", \"bottom\", \"left\", and \"right\".\n                    (\"top\" is the default alignment).\"\"\"\n        if selector != \"html\":\n            element_row = \"element: '%s',\" % selector\n        else:\n            element_row = \"\"\n        if title:\n            message = \"<center><b>\" + title + \"</b></center><hr>\" + message\n        message = '<font size=\"3\" color=\"#33477B\">' + message + \"</font>\"\n        step = \"\"\"{%s\n            intro: '%s',\n            position: '%s'},\"\"\" % (\n            element_row,\n            message,\n            alignment,\n        )\n        self._tour_steps[name].append(step)\n\n    def play_tour(self, name=None, interval=0):\n        \"\"\"Plays a tour on the current website.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        interval - The delay time between autoplaying tour steps. (Seconds)\n                   If set to 0 (default), the tour is fully manual control.\"\"\"\n        from seleniumbase.core import tour_helper\n\n        if self.headless or self.headless2 or self.xvfb:\n            return  # Tours should not run in headless mode.\n        self.wait_for_ready_state_complete()\n        if not interval:\n            interval = 0\n        if interval == 0 and self.interval:\n            interval = float(self.interval)\n        if not name:\n            name = \"default\"\n        if name not in self._tour_steps:\n            raise Exception(\"Tour {%s} does not exist!\" % name)\n        if \"Bootstrap\" in self._tour_steps[name][0]:\n            tour_helper.play_bootstrap_tour(\n                self.driver,\n                self._tour_steps,\n                self.browser,\n                self.message_duration,\n                name=name,\n                interval=interval,\n            )\n        elif \"DriverJS\" in self._tour_steps[name][0]:\n            tour_helper.play_driverjs_tour(\n                self.driver,\n                self._tour_steps,\n                self.browser,\n                self.message_duration,\n                name=name,\n                interval=interval,\n            )\n        elif \"Hopscotch\" in self._tour_steps[name][0]:\n            tour_helper.play_hopscotch_tour(\n                self.driver,\n                self._tour_steps,\n                self.browser,\n                self.message_duration,\n                name=name,\n                interval=interval,\n            )\n        elif \"IntroJS\" in self._tour_steps[name][0]:\n            tour_helper.play_introjs_tour(\n                self.driver,\n                self._tour_steps,\n                self.browser,\n                self.message_duration,\n                name=name,\n                interval=interval,\n            )\n        else:\n            tour_helper.play_shepherd_tour(\n                self.driver,\n                self._tour_steps,\n                self.message_duration,\n                name=name,\n                interval=interval,\n            )\n\n    def start_tour(self, name=None, interval=0):\n        \"\"\"Same as self.play_tour()\"\"\"\n        self.play_tour(name=name, interval=interval)\n\n    def export_tour(self, name=None, filename=\"my_tour.js\", url=None):\n        \"\"\"Exports a tour as a JS file.\n        You can call self.export_tour() anywhere where you would\n        normally use self.play_tour() to play a website tour.\n        It will include necessary resources as well, such as jQuery.\n        You'll be able to copy the tour directly into the Console of\n        any web browser to play the tour outside of SeleniumBase runs.\n        @Params\n        name - If creating multiple tours at the same time,\n               use this to select the tour you wish to add steps to.\n        filename - The name of the JavaScript file that you wish to\n                   save the tour to.\n        url - The URL where the tour starts. If not specified, the URL\n              of the current page will be used.\"\"\"\n        from seleniumbase.core import tour_helper\n\n        if not url:\n            url = self.get_current_url()\n        tour_helper.export_tour(\n            self._tour_steps, name=name, filename=filename, url=url\n        )\n\n    ############\n\n    def activate_jquery_confirm(self):\n        \"\"\"See https://craftpip.github.io/jquery-confirm/ for usage.\"\"\"\n        self.__check_scope()\n        self._check_browser()\n        js_utils.activate_jquery_confirm(self.driver)\n        self.wait_for_ready_state_complete()\n\n    def set_jqc_theme(self, theme, color=None, width=None):\n        \"\"\"Sets the default jquery-confirm theme and width (optional).\n        Available themes: \"bootstrap\", \"modern\", \"material\", \"supervan\",\n                          \"light\", \"dark\", and \"seamless\".\n        Available colors: (This sets the BORDER color, NOT the button color.)\n            \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\n        Width can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\"\"\"\n        if not self.__changed_jqc_theme:\n            self.__jqc_default_theme = constants.JqueryConfirm.DEFAULT_THEME\n            self.__jqc_default_color = constants.JqueryConfirm.DEFAULT_COLOR\n            self.__jqc_default_width = constants.JqueryConfirm.DEFAULT_WIDTH\n        valid_themes = [\n            \"bootstrap\",\n            \"modern\",\n            \"material\",\n            \"supervan\",\n            \"light\",\n            \"dark\",\n            \"seamless\",\n        ]\n        if theme.lower() not in valid_themes:\n            raise Exception(\n                \"%s is not a valid jquery-confirm theme! \"\n                \"Select from %s\" % (theme.lower(), valid_themes)\n            )\n        constants.JqueryConfirm.DEFAULT_THEME = theme.lower()\n        if color:\n            valid_colors = [\n                \"blue\",\n                \"default\",\n                \"green\",\n                \"red\",\n                \"purple\",\n                \"orange\",\n                \"dark\",\n            ]\n            if color.lower() not in valid_colors:\n                raise Exception(\n                    \"%s is not a valid jquery-confirm border color! \"\n                    \"Select from %s\" % (color.lower(), valid_colors)\n                )\n            constants.JqueryConfirm.DEFAULT_COLOR = color.lower()\n        if width:\n            if isinstance(width, (int, float)):\n                # Convert to a string if a number is given\n                width = str(width)\n            if width.isnumeric():\n                if int(width) <= 0:\n                    raise Exception(\"Width must be set to a positive number!\")\n                elif int(width) <= 100:\n                    width = str(width) + \"%\"\n                else:\n                    width = str(width) + \"px\"  # Use pixels if width is > 100\n            if not width.endswith(\"%\") and not width.endswith(\"px\"):\n                raise Exception(\n                    \"jqc width must end with %% for percent or px for pixels!\"\n                )\n            value = None\n            if width.endswith(\"%\"):\n                value = width[:-1]\n            if width.endswith(\"px\"):\n                value = width[:-2]\n            try:\n                value = float(value)\n            except Exception:\n                raise Exception(\"%s is not a numeric value!\" % value)\n            if value <= 0:\n                raise Exception(\"%s is not a positive number!\" % value)\n            constants.JqueryConfirm.DEFAULT_WIDTH = width\n\n    def reset_jqc_theme(self):\n        \"\"\"Resets the jqc theme settings to factory defaults.\"\"\"\n        if self.__changed_jqc_theme:\n            constants.JqueryConfirm.DEFAULT_THEME = self.__jqc_default_theme\n            constants.JqueryConfirm.DEFAULT_COLOR = self.__jqc_default_color\n            constants.JqueryConfirm.DEFAULT_WIDTH = self.__jqc_default_width\n            self.__changed_jqc_theme = False\n\n    def get_jqc_button_input(self, message, buttons, options=None):\n        \"\"\"\n        Pop up a jquery-confirm box and return the text of the button clicked.\n        If running in headless mode, the last button text is returned.\n        @Params\n        message: The message to display in the jquery-confirm dialog.\n        buttons: A list of tuples for text and color.\n            Example: [(\"Yes!\", \"green\"), (\"No!\", \"red\")]\n            Available colors: blue, green, red, orange, purple, default, dark.\n            A simple text string also works: \"My Button\". (Uses default color.)\n        options: A list of tuples for options to set.\n            Example: [(\"theme\", \"bootstrap\"), (\"width\", \"450px\")]\n            Available theme options: bootstrap, modern, material, supervan,\n                                     light, dark, and seamless.\n            Available colors: (For the BORDER color, NOT the button color.)\n                \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\n            Example option for changing the border color: (\"color\", \"default\")\n            Width can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\"\"\"\n        from seleniumbase.core import jqc_helper\n\n        if message and not isinstance(message, str):\n            raise Exception('Expecting a string for arg: \"message\"!')\n        if not isinstance(buttons, (list, tuple)):\n            raise Exception('Expecting a list or tuple for arg: \"button\"!')\n        if len(buttons) < 1:\n            raise Exception('List \"buttons\" requires at least one button!')\n        new_buttons = []\n        for button in buttons:\n            if isinstance(button, (list, tuple)) and (len(button) == 1):\n                new_buttons.append(button[0])\n            elif isinstance(button, (list, tuple)) and (len(button) > 1):\n                new_buttons.append((button[0], str(button[1]).lower()))\n            else:\n                new_buttons.append((str(button), \"\"))\n        buttons = new_buttons\n        if options:\n            for option in options:\n                if not isinstance(option, (list, tuple)):\n                    raise Exception('\"options\" should be a list of tuples!')\n        if self.headless or self.headless2 or self.xvfb:\n            return buttons[-1][0]\n        jqc_helper.jquery_confirm_button_dialog(\n            self.driver, message, buttons, options\n        )\n        time.sleep(0.02)\n        jf = \"document.querySelector('.jconfirm-box').focus();\"\n        with suppress(Exception):\n            self.execute_script(jf)\n        waiting_for_response = True\n        while waiting_for_response:\n            time.sleep(0.05)\n            jqc_open = self.execute_script(\"return jconfirm.instances.length;\")\n            if str(jqc_open) == \"0\":\n                break\n        time.sleep(0.1)\n        status = None\n        try:\n            status = self.execute_script(\"return $jqc_status;\")\n        except Exception:\n            status = self.execute_script(\"return jconfirm.lastButtonText;\")\n        return status\n\n    def get_jqc_text_input(self, message, button=None, options=None):\n        \"\"\"\n        Pop up a jquery-confirm box and return the text submitted by the input.\n        If running in headless mode, the text returned is \"\" by default.\n        @Params\n        message: The message to display in the jquery-confirm dialog.\n        button: A 2-item list or tuple for text and color. Or just the text.\n            Example: [\"Submit\", \"blue\"] -> (default button if not specified)\n            Available colors: blue, green, red, orange, purple, default, dark.\n            A simple text string also works: \"My Button\". (Uses default color.)\n        options: A list of tuples for options to set.\n            Example: [(\"theme\", \"bootstrap\"), (\"width\", \"450px\")]\n            Available theme options: bootstrap, modern, material, supervan,\n                                     light, dark, and seamless.\n            Available colors: (For the BORDER color, NOT the button color.)\n                \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\n            Example option for changing the border color: (\"color\", \"default\")\n            Width can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\"\"\"\n        from seleniumbase.core import jqc_helper\n\n        if message and not isinstance(message, str):\n            raise Exception('Expecting a string for arg: \"message\"!')\n        if button:\n            if isinstance(button, (list, tuple)) and (len(button) == 1):\n                button = (str(button[0]), \"\")\n            elif isinstance(button, (list, tuple)) and (len(button) > 1):\n                valid_colors = [\n                    \"blue\",\n                    \"default\",\n                    \"green\",\n                    \"red\",\n                    \"purple\",\n                    \"orange\",\n                    \"dark\",\n                ]\n                detected_color = str(button[1]).lower()\n                if str(button[1]).lower() not in valid_colors:\n                    raise Exception(\n                        \"%s is an invalid jquery-confirm button color!\\n\"\n                        \"Select from %s\" % (detected_color, valid_colors)\n                    )\n                button = (str(button[0]), str(button[1]).lower())\n            else:\n                button = (str(button), \"\")\n        else:\n            button = (\"Submit\", \"blue\")\n        if options:\n            for option in options:\n                if not isinstance(option, (list, tuple)):\n                    raise Exception('\"options\" should be a list of tuples!')\n        if self.headless or self.headless2 or self.xvfb:\n            return \"\"\n        jqc_helper.jquery_confirm_text_dialog(\n            self.driver, message, button, options\n        )\n        time.sleep(0.02)\n        jf = \"document.querySelector('.jconfirm-box input.jqc_input').focus();\"\n        with suppress(Exception):\n            self.execute_script(jf)\n        waiting_for_response = True\n        while waiting_for_response:\n            time.sleep(0.05)\n            jqc_open = self.execute_script(\"return jconfirm.instances.length;\")\n            if str(jqc_open) == \"0\":\n                break\n        time.sleep(0.1)\n        status = None\n        try:\n            status = self.execute_script(\"return $jqc_input;\")\n        except Exception:\n            status = self.execute_script(\"return jconfirm.lastInputText;\")\n        return status\n\n    def get_jqc_form_inputs(self, message, buttons, options=None):\n        \"\"\"\n        Pop up a jquery-confirm box and return the input/button texts as tuple.\n        If running in headless mode, returns the (\"\", buttons[-1][0]) tuple.\n        @Params\n        message: The message to display in the jquery-confirm dialog.\n        buttons: A list of tuples for text and color.\n            Example: [(\"Yes!\", \"green\"), (\"No!\", \"red\")]\n            Available colors: blue, green, red, orange, purple, default, dark.\n            A simple text string also works: \"My Button\". (Uses default color.)\n        options: A list of tuples for options to set.\n            Example: [(\"theme\", \"bootstrap\"), (\"width\", \"450px\")]\n            Available theme options: bootstrap, modern, material, supervan,\n                                     light, dark, and seamless.\n            Available colors: (For the BORDER color, NOT the button color.)\n                \"blue\", \"default\", \"green\", \"red\", \"purple\", \"orange\", \"dark\".\n            Example option for changing the border color: (\"color\", \"default\")\n            Width can be set using percent or pixels. Eg: \"36.0%\", \"450px\".\"\"\"\n        from seleniumbase.core import jqc_helper\n\n        if message and not isinstance(message, str):\n            raise Exception('Expecting a string for arg: \"message\"!')\n        if not isinstance(buttons, (list, tuple)):\n            raise Exception('Expecting a list or tuple for arg: \"button\"!')\n        if len(buttons) < 1:\n            raise Exception('List \"buttons\" requires at least one button!')\n        new_buttons = []\n        for button in buttons:\n            if isinstance(button, (list, tuple)) and (len(button) == 1):\n                new_buttons.append(button[0])\n            elif isinstance(button, (list, tuple)) and (len(button) > 1):\n                new_buttons.append((button[0], str(button[1]).lower()))\n            else:\n                new_buttons.append((str(button), \"\"))\n        buttons = new_buttons\n        if options:\n            for option in options:\n                if not isinstance(option, (list, tuple)):\n                    raise Exception('\"options\" should be a list of tuples!')\n        if self.headless or self.headless2 or self.xvfb:\n            return (\"\", buttons[-1][0])\n        jqc_helper.jquery_confirm_full_dialog(\n            self.driver, message, buttons, options\n        )\n        time.sleep(0.02)\n        jf = \"document.querySelector('.jconfirm-box input.jqc_input').focus();\"\n        with suppress(Exception):\n            self.execute_script(jf)\n        waiting_for_response = True\n        while waiting_for_response:\n            time.sleep(0.05)\n            jqc_open = self.execute_script(\"return jconfirm.instances.length;\")\n            if str(jqc_open) == \"0\":\n                break\n        time.sleep(0.1)\n        text_status = None\n        button_status = None\n        try:\n            text_status = self.execute_script(\"return $jqc_input;\")\n            button_status = self.execute_script(\"return $jqc_status;\")\n        except Exception:\n            text_status = self.execute_script(\"return jconfirm.lastInputText;\")\n            button_status = self.execute_script(\n                \"return jconfirm.lastButtonText;\"\n            )\n        return (text_status, button_status)\n\n    ############\n\n    def __are_quotes_escaped(self, string):\n        return js_utils.are_quotes_escaped(string)\n\n    def __escape_quotes_if_needed(self, string):\n        return js_utils.escape_quotes_if_needed(string)\n\n    def __is_in_frame(self):\n        return js_utils.is_in_frame(self.driver)\n\n    ############\n\n    def __js_click(self, selector, by=\"css selector\"):\n        \"\"\"Clicks an element using pure JS. Does not use jQuery.\"\"\"\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        is_visible = self.is_element_visible(selector, by=by)\n        current_url = self.get_current_url()\n        script = (\n            \"\"\"var simulateClick = function (elem) {\n                   var evt = new MouseEvent('click', {\n                       bubbles: true,\n                       cancelable: true,\n                       view: window\n                   });\n                   var canceled = !elem.dispatchEvent(evt);\n               };\n               var someLink = document.querySelector('%s');\n               simulateClick(someLink);\"\"\"\n            % css_selector\n        )\n        if getattr(self, \"recorder_mode\", None):\n            self.save_recorded_actions()\n        try:\n            self.execute_script(script)\n        except Exception as e:\n            # If element was visible but no longer, or on a different page now,\n            # assume that the click actually worked and continue with the test.\n            if (\n                (is_visible and not self.is_element_visible(selector, by=by))\n                or current_url != self.get_current_url()\n            ):\n                return  # The click worked, but threw an Exception. Keep going.\n            # It appears the first click didn't work. Make another attempt.\n            self.wait_for_ready_state_complete()\n            if \"Cannot read properties of null\" in e.msg:\n                page_actions.wait_for_element_present(\n                    self.driver, selector, by, timeout=5\n                )\n                if not page_actions.is_element_clickable(\n                    self.driver, selector, by\n                ):\n                    with suppress(Exception):\n                        self.wait_for_element_clickable(\n                            selector, by, timeout=1.8\n                        )\n            # If the regular mouse-simulated click fails, do a basic JS click\n            script = (\n                \"\"\"document.querySelector('%s').click();\"\"\"\n                % css_selector\n            )\n            self.execute_script(script)\n\n    def __js_click_element(self, element):\n        \"\"\"Clicks an element using pure JS. Does not use jQuery.\"\"\"\n        is_visible = element.is_displayed()\n        current_url = self.get_current_url()\n        script = (\n            \"\"\"var simulateClick = function (elem) {\n                   var evt = new MouseEvent('click', {\n                       bubbles: true,\n                       cancelable: true,\n                       view: window\n                   });\n                   var canceled = !elem.dispatchEvent(evt);\n               };\n               var someLink = arguments[0];\n               simulateClick(someLink);\"\"\"\n        )\n        if getattr(self, \"recorder_mode\", None):\n            self.save_recorded_actions()\n        try:\n            self.execute_script(script, element)\n        except Exception:\n            # If element was visible but no longer, or on a different page now,\n            # assume that the click actually worked and continue with the test.\n            if (\n                (is_visible and not element.is_displayed())\n                or current_url != self.get_current_url()\n            ):\n                return  # The click worked, but threw an Exception. Keep going.\n            # It appears the first click didn't work. Make another attempt.\n            self.wait_for_ready_state_complete()\n            # If the regular mouse-simulated click fails, do a basic JS click\n            script = (\"\"\"arguments[0].click();\"\"\")\n            self.execute_script(script, element)\n\n    def __js_click_all(self, selector, by=\"css selector\"):\n        \"\"\"Clicks all matching elements using pure JS. (No jQuery)\"\"\"\n        selector, by = self.__recalculate_selector(selector, by)\n        css_selector = self.convert_to_css_selector(selector, by=by)\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\n        css_selector = self.__escape_quotes_if_needed(css_selector)\n        script = (\n            \"\"\"var simulateClick = function (elem) {\n                   var evt = new MouseEvent('click', {\n                       bubbles: true,\n                       cancelable: true,\n                       view: window\n                   });\n                   var canceled = !elem.dispatchEvent(evt);\n               };\n               var $elements = document.querySelectorAll('%s');\n               var index = 0, length = $elements.length;\n               for(; index < length; index++){\n               simulateClick($elements[index]);}\"\"\"\n            % css_selector\n        )\n        self.execute_script(script)\n\n    def __click_with_offset(\n        self,\n        selector,\n        x,\n        y,\n        by=\"css selector\",\n        double=False,\n        mark=None,\n        timeout=None,\n        center=None,\n    ):\n        if self.__is_cdp_swap_needed():\n            self.cdp.click_with_offset(selector, x, y, center=center)\n            return\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.14)\n        if not timeout:\n            timeout = settings.SMALL_TIMEOUT\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        selector, by = self.__recalculate_selector(selector, by)\n        element = page_actions.wait_for_element_visible(\n            self.driver, selector, by, timeout\n        )\n        if self.demo_mode:\n            self.__highlight(selector, by=by, loops=1)\n        elif self.slow_mode:\n            self.__slow_scroll_to_element(element)\n        else:\n            self.__scroll_to_element(element, selector, by)\n        self.wait_for_ready_state_complete()\n        if self.__needs_minimum_wait():\n            time.sleep(0.03)\n        if self.demo_mode and mark is None:\n            mark = True\n        if mark:\n            selector = self.convert_to_css_selector(selector, by=by)\n            selector = re.escape(selector)\n            selector = self.__escape_quotes_if_needed(selector)\n            m_x = x\n            m_y = y\n            if center:\n                element_rect = element.rect\n                left_offset = element_rect[\"width\"] / 2\n                top_offset = element_rect[\"height\"] / 2\n                m_x = left_offset + (m_x or 0)\n                m_y = top_offset + (m_y or 0)\n            px = m_x - 3\n            py = m_y - 3\n            script = (\n                \"var canvas = document.querySelector('%s');\"\n                \"var ctx = canvas.getContext('2d');\"\n                \"ctx.fillStyle = '#F8F808';\"\n                \"ctx.fillRect(%s, %s, 7, 7);\"\n                \"ctx.fillStyle = '#F80808';\"\n                \"ctx.fillRect(%s+1, %s+1, 5, 5);\" % (selector, px, py, px, py)\n            )\n            self.execute_script(script)\n        try:\n            element_location = element.location[\"y\"]\n            element_location = element_location - constants.Scroll.Y_OFFSET + y\n            if element_location < 0:\n                element_location = 0\n            scroll_script = \"window.scrollTo(0, %s);\" % element_location\n            self.execute_script(scroll_script)\n            time.sleep(0.12)\n        except Exception:\n            time.sleep(0.05)\n        if self.__needs_minimum_wait():\n            time.sleep(0.05)\n        try:\n            if not center:\n                element_rect = element.rect\n                left_offset = element_rect[\"width\"] / 2\n                top_offset = element_rect[\"height\"] / 2\n                x = -left_offset + (math.ceil(float(x)) or 0)\n                y = -top_offset + (math.ceil(float(y)) or 0)\n            action_chains = ActionChains(self.driver)\n            action_chains.move_to_element_with_offset(element, x, y)\n            if not double:\n                action_chains.click().perform()\n            else:\n                action_chains.double_click().perform()\n        except MoveTargetOutOfBoundsException:\n            message = (\n                \"Target coordinates for click are out-of-bounds!\\n\"\n                \"The offset must stay inside the target element!\"\n            )\n            raise Exception(message)\n        except InvalidArgumentException:\n            if not self.is_chromium():\n                raise\n            chrome_version = self.driver.capabilities[\"browserVersion\"]\n            major_chrome_version = chrome_version.split(\".\")[0]\n            chrome_dict = self.driver.capabilities[\"chrome\"]\n            chromedriver_version = chrome_dict[\"chromedriverVersion\"]\n            chromedriver_version = chromedriver_version.split(\" \")[0]\n            major_chromedriver_version = chromedriver_version.split(\".\")[0]\n            if (\n                int(major_chromedriver_version) >= 76\n                and int(major_chrome_version) >= 76\n            ):\n                raise\n            install_sb = (\n                \"seleniumbase get chromedriver %s\" % major_chrome_version\n            )\n            if int(major_chromedriver_version) < int(major_chrome_version):\n                # Upgrading the driver is needed for performing canvas actions\n                message = (\n                    \"You need to upgrade to a newer\\n\"\n                    \"version of chromedriver to perform canvas actions!\\n\"\n                    \"Reason: github.com/SeleniumHQ/selenium/issues/7000\"\n                    \"\\nYour version of chromedriver is: %s\\n\"\n                    \"And your version of Chrome is: %s\\n\"\n                    \"You can fix this issue by running:\\n>>> %s\\n\"\n                    % (chromedriver_version, chrome_version, install_sb)\n                )\n                raise Exception(message)\n            else:\n                raise\n        if self.demo_mode:\n            self.__demo_mode_pause_if_active()\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def __jquery_slow_scroll_to(self, selector, by=\"css selector\"):\n        selector, by = self.__recalculate_selector(selector, by)\n        element = self.wait_for_element_present(\n            selector, by=by, timeout=settings.SMALL_TIMEOUT\n        )\n        dist = js_utils.get_scroll_distance_to_element(self.driver, element)\n        time_offset = 0\n        try:\n            if dist and abs(dist) > constants.Values.SSMD:\n                time_offset = int(\n                    float(abs(dist) - constants.Values.SSMD) / 12.5\n                )\n                if time_offset > 950:\n                    time_offset = 950\n        except Exception:\n            time_offset = 0\n        scroll_time_ms = 550 + time_offset\n        sleep_time = 0.625 + (float(time_offset) / 1000.0)\n        selector = self.convert_to_css_selector(selector, by=by)\n        selector = self.__make_css_match_first_element_only(selector)\n        scroll_script = (\n            \"\"\"jQuery([document.documentElement, document.body]).animate({\"\"\"\n            \"\"\"scrollTop: jQuery('%s').offset().top - %s}, %s);\"\"\"\n            % (selector, constants.Scroll.Y_OFFSET, scroll_time_ms)\n        )\n        if js_utils.is_jquery_activated(self.driver):\n            self.execute_script(scroll_script)\n        else:\n            self.__slow_scroll_to_element(element)\n        time.sleep(sleep_time)\n\n    def __jquery_click(self, selector, by=\"css selector\"):\n        \"\"\"Clicks an element using jQuery. Different from using pure JS.\"\"\"\n        selector, by = self.__recalculate_selector(selector, by)\n        self.wait_for_element_present(\n            selector, by=by, timeout=settings.SMALL_TIMEOUT\n        )\n        selector = self.convert_to_css_selector(selector, by=by)\n        selector = self.__make_css_match_first_element_only(selector)\n        click_script = \"\"\"jQuery('%s')[0].click();\"\"\" % selector\n        if getattr(self, \"recorder_mode\", None):\n            self.save_recorded_actions()\n        self.safe_execute_script(click_script)\n\n    def __get_major_browser_version(self):\n        version = self.driver.__dict__[\"caps\"][\"browserVersion\"]\n        return version.split(\".\")[0]\n\n    def __get_href_from_link_text(self, link_text, hard_fail=True):\n        href = self.get_link_attribute(link_text, \"href\", hard_fail)\n        if not href:\n            return None\n        if href.startswith(\"//\"):\n            link = \"http:\" + href\n        elif href.startswith(\"/\"):\n            url = self.driver.current_url\n            domain_url = self.get_domain_url(url)\n            link = domain_url + href\n        else:\n            link = href\n        return link\n\n    def __click_dropdown_link_text(self, link_text, link_css):\n        \"\"\"When a link may be hidden under a dropdown menu, use this.\"\"\"\n        soup = self.get_beautiful_soup()\n        drop_down_list = []\n        for item in soup.select(\"li[class]\"):\n            drop_down_list.append(item)\n        csstype = link_css.split(\"[\")[1].split(\"=\")[0]\n        for item in drop_down_list:\n            item_text_list = item.text.split(\"\\n\")\n            if link_text in item_text_list and csstype in item.decode():\n                dropdown_css = \"\"\n                try:\n                    for css_class in item[\"class\"]:\n                        dropdown_css += \".\"\n                        dropdown_css += css_class\n                except Exception:\n                    continue\n                dropdown_css = item.name + dropdown_css\n                matching_dropdowns = self.find_visible_elements(dropdown_css)\n                for dropdown in matching_dropdowns:\n                    # The same class names might be used for multiple dropdowns\n                    if dropdown.is_displayed():\n                        with suppress(Exception):\n                            try:\n                                page_actions.hover_element(\n                                    self.driver,\n                                    dropdown,\n                                )\n                            except Exception:\n                                # If hovering fails, driver is likely outdated\n                                # Time to go directly to the hidden link text\n                                self.open(\n                                    self.__get_href_from_link_text(link_text)\n                                )\n                                return True\n                            page_actions.hover_element_and_click(\n                                self.driver,\n                                dropdown,\n                                link_text,\n                                click_by=\"link text\",\n                                timeout=0.12,\n                            )\n                            return True\n        return False\n\n    def __get_href_from_partial_link_text(self, link_text, hard_fail=True):\n        href = self.get_partial_link_text_attribute(\n            link_text, \"href\", hard_fail\n        )\n        if not href:\n            return None\n        if href.startswith(\"//\"):\n            link = \"http:\" + href\n        elif href.startswith(\"/\"):\n            url = self.driver.current_url\n            domain_url = self.get_domain_url(url)\n            link = domain_url + href\n        else:\n            link = href\n        return link\n\n    def __click_dropdown_partial_link_text(self, link_text, link_css):\n        \"\"\"When a partial link may be hidden under a dropdown, use this.\"\"\"\n        soup = self.get_beautiful_soup()\n        drop_down_list = []\n        for item in soup.select(\"li[class]\"):\n            drop_down_list.append(item)\n        csstype = link_css.split(\"[\")[1].split(\"=\")[0]\n        for item in drop_down_list:\n            item_text_list = item.text.split(\"\\n\")\n            if link_text in item_text_list and csstype in item.decode():\n                dropdown_css = \"\"\n                try:\n                    for css_class in item[\"class\"]:\n                        dropdown_css += \".\"\n                        dropdown_css += css_class\n                except Exception:\n                    continue\n                dropdown_css = item.name + dropdown_css\n                matching_dropdowns = self.find_visible_elements(dropdown_css)\n                for dropdown in matching_dropdowns:\n                    # The same class names might be used for multiple dropdowns\n                    if dropdown.is_displayed():\n                        with suppress(Exception):\n                            try:\n                                page_actions.hover_element(\n                                    self.driver, dropdown\n                                )\n                            except Exception:\n                                # If hovering fails, driver is likely outdated\n                                # Time to go directly to the hidden link text\n                                self.open(\n                                    self.__get_href_from_partial_link_text(\n                                        link_text\n                                    )\n                                )\n                                return True\n                            page_actions.hover_element_and_click(\n                                self.driver,\n                                dropdown,\n                                link_text,\n                                click_by=\"link text\",\n                                timeout=0.12,\n                            )\n                            return True\n        return False\n\n    def __recalculate_selector(self, selector, by, xp_ok=True):\n        \"\"\"Use autodetection to return the correct selector with \"by\" updated.\n        If \"xp_ok\" is False, don't call convert_css_to_xpath(), which is\n        used to make the \":contains()\" selector valid outside of JS calls.\n        Returns a (selector, by) tuple.\"\"\"\n        return page_utils.recalculate_selector(selector, by, xp_ok=xp_ok)\n\n    def __looks_like_a_page_url(self, url):\n        return page_utils.looks_like_a_page_url(url)\n\n    def __make_css_match_first_element_only(self, selector):\n        # Only get the first match\n        return page_utils.make_css_match_first_element_only(selector)\n\n    def __switch_to_newest_window_if_not_blank(self):\n        current_window = self.driver.current_window_handle\n        try:\n            self.switch_to_window(-1)\n            if self.get_current_url() == \"about:blank\":\n                self.switch_to_window(current_window)\n        except Exception:\n            with suppress(Exception):\n                self.switch_to_window(current_window)\n\n    def __needs_minimum_wait(self):\n        if (\n            self.page_load_strategy == \"none\"\n            and getattr(settings, \"SKIP_JS_WAITS\", None)\n        ):\n            return True\n        else:\n            return False\n\n    def __demo_mode_pause_if_active(self, tiny=False):\n        if self.demo_mode:\n            wait_time = settings.DEFAULT_DEMO_MODE_TIMEOUT\n            if self.demo_sleep:\n                wait_time = float(self.demo_sleep)\n            if not tiny:\n                time.sleep(wait_time)\n            else:\n                time.sleep(wait_time / 3.4)\n        elif self.slow_mode:\n            self.__slow_mode_pause_if_active()\n\n    def __slow_mode_pause_if_active(self):\n        if self.slow_mode:\n            wait_time = settings.DEFAULT_DEMO_MODE_TIMEOUT\n            if self.demo_sleep:\n                wait_time = float(self.demo_sleep)\n            time.sleep(wait_time)\n\n    def __demo_mode_scroll_if_active(self, selector, by):\n        if self.demo_mode:\n            self.slow_scroll_to(selector, by=by)\n\n    def __demo_mode_highlight_if_active(self, selector, by):\n        self.__skip_if_esc()\n        if self.demo_mode:\n            # Includes self.slow_scroll_to(selector, by=by) by default\n            self.__highlight(selector, by=by)\n        elif self.slow_mode:\n            # Just do the slow scroll part of the highlight() method\n            time.sleep(0.08)\n            selector, by = self.__recalculate_selector(selector, by)\n            element = self.wait_for_element_visible(\n                selector, by=by, timeout=settings.SMALL_TIMEOUT\n            )\n            try:\n                if self.browser != \"safari\":\n                    scroll_distance = js_utils.get_scroll_distance_to_element(\n                        self.driver, element\n                    )\n                    if abs(scroll_distance) > constants.Values.SSMD:\n                        self.__jquery_slow_scroll_to(selector, by)\n                    else:\n                        self.__slow_scroll_to_element(element)\n                else:\n                    self.__jquery_slow_scroll_to(selector, by)\n            except (Stale_Exception, ENI_Exception):\n                self.wait_for_ready_state_complete()\n                time.sleep(0.12)\n                element = self.wait_for_element_visible(\n                    selector, by=by, timeout=settings.SMALL_TIMEOUT\n                )\n                self.__slow_scroll_to_element(element)\n            time.sleep(0.12)\n\n    def __scroll_to_element(self, element, selector=None, by=\"css selector\"):\n        success = js_utils.scroll_to_element(self.driver, element)\n        if not success and selector:\n            self.wait_for_ready_state_complete()\n            element = page_actions.wait_for_element_visible(\n                self.driver, selector, by, timeout=settings.SMALL_TIMEOUT\n            )\n        self.__demo_mode_pause_if_active(tiny=True)\n\n    def __slow_scroll_to_element(self, element):\n        try:\n            js_utils.slow_scroll_to_element(self.driver, element)\n        except Exception:\n            # Scroll to the element instantly if the slow scroll fails\n            js_utils.scroll_to_element(self.driver, element)\n\n    def __highlight_with_js(self, selector, loops, o_bs):\n        if not self.__is_cdp_swap_needed():\n            self.wait_for_ready_state_complete()\n        js_utils.highlight_with_js(self.driver, selector, loops, o_bs)\n\n    def __highlight_element_with_js(self, element, loops, o_bs):\n        self.wait_for_ready_state_complete()\n        js_utils.highlight_element_with_js(self.driver, element, loops, o_bs)\n\n    def __highlight_with_jquery(self, selector, loops, o_bs):\n        if not self.__is_cdp_swap_needed():\n            self.wait_for_ready_state_complete()\n        js_utils.highlight_with_jquery(self.driver, selector, loops, o_bs)\n\n    def __highlight_with_js_2(self, message, selector, o_bs):\n        duration = self.message_duration\n        if not duration:\n            duration = settings.DEFAULT_MESSAGE_DURATION\n        if (\n            (self.headless or self.headless2 or self.xvfb)\n            and float(duration) > 0.75\n        ):\n            duration = 0.75\n        js_utils.highlight_with_js_2(\n            self.driver, message, selector, o_bs, duration\n        )\n\n    def __highlight_element_with_js_2(self, message, element, o_bs):\n        duration = self.message_duration\n        if not duration:\n            duration = settings.DEFAULT_MESSAGE_DURATION\n        if (\n            (self.headless or self.headless2 or self.xvfb)\n            and float(duration) > 0.75\n        ):\n            duration = 0.75\n        js_utils.highlight_element_with_js_2(\n            self.driver, message, element, o_bs, duration\n        )\n\n    def __highlight_with_jquery_2(self, message, selector, o_bs):\n        duration = self.message_duration\n        if not duration:\n            duration = settings.DEFAULT_MESSAGE_DURATION\n        if (\n            (self.headless or self.headless2 or self.xvfb)\n            and float(duration) > 0.75\n        ):\n            duration = 0.75\n        js_utils.highlight_with_jquery_2(\n            self.driver, message, selector, o_bs, duration\n        )\n\n    def __highlight_with_assert_success(\n        self, message, selector, by=\"css selector\"\n    ):\n        selector, by = self.__recalculate_selector(selector, by, xp_ok=False)\n        element = self.wait_for_element_visible(\n            selector, by=by, timeout=settings.SMALL_TIMEOUT\n        )\n        try:\n            if self.browser != \"safari\":\n                scroll_distance = js_utils.get_scroll_distance_to_element(\n                    self.driver, element\n                )\n                if abs(scroll_distance) > constants.Values.SSMD:\n                    self.__jquery_slow_scroll_to(selector, by)\n                else:\n                    self.__slow_scroll_to_element(element)\n            else:\n                self.__jquery_slow_scroll_to(selector, by)\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(0.12)\n            element = self.wait_for_element_visible(\n                selector, by=by, timeout=settings.SMALL_TIMEOUT\n            )\n            self.__slow_scroll_to_element(element)\n        use_element_directly = False\n        try:\n            selector = self.convert_to_css_selector(selector, by=by)\n        except Exception:\n            # If can't convert to CSS_Selector for JS, use element directly\n            use_element_directly = True\n\n        o_bs = \"\"  # original_box_shadow\n        try:\n            style = element.get_attribute(\"style\")\n        except Exception:\n            self.wait_for_ready_state_complete()\n            time.sleep(0.12)\n            element = self.wait_for_element_visible(\n                selector, by=\"css selector\", timeout=settings.SMALL_TIMEOUT\n            )\n            style = element.get_attribute(\"style\")\n        if style:\n            if \"box-shadow: \" in style:\n                box_start = style.find(\"box-shadow: \")\n                box_end = style.find(\";\", box_start) + 1\n                original_box_shadow = style[box_start:box_end]\n                o_bs = original_box_shadow\n\n        if use_element_directly:\n            self.__highlight_element_with_js_2(message, element, o_bs)\n        elif \":contains\" not in selector and \":first\" not in selector:\n            selector = re.escape(selector)\n            selector = self.__escape_quotes_if_needed(selector)\n            self.__highlight_with_js_2(message, selector, o_bs)\n        else:\n            selector = self.__make_css_match_first_element_only(selector)\n            selector = re.escape(selector)\n            selector = self.__escape_quotes_if_needed(selector)\n            try:\n                self.__highlight_with_jquery_2(message, selector, o_bs)\n            except Exception:\n                pass  # JQuery probably couldn't load. Skip highlighting.\n        time.sleep(0.065)\n\n    def __activate_standard_virtual_display(self):\n        from sbvirtualdisplay import Display\n        width = settings.HEADLESS_START_WIDTH\n        height = settings.HEADLESS_START_HEIGHT\n        with suppress(Exception):\n            self._xvfb_display = Display(\n                visible=0, size=(width, height)\n            )\n            self._xvfb_display.start()\n            self.headless_active = True\n            if not self.undetectable:\n                sb_config._virtual_display = self._xvfb_display\n                sb_config.headless_active = True\n            if self._reuse_session and hasattr(sb_config, \"_vd_list\"):\n                if isinstance(sb_config._vd_list, list):\n                    sb_config._vd_list.append(self._xvfb_display)\n\n    def __activate_virtual_display(self):\n        if self.undetectable and not (self.headless or self.headless2):\n            from sbvirtualdisplay import Display\n            import Xlib.display\n            try:\n                if not self._xvfb_width:\n                    self._xvfb_width = 1366\n                if not self._xvfb_height:\n                    self._xvfb_height = 768\n                self._xvfb_display = Display(\n                    visible=True,\n                    size=(self._xvfb_width, self._xvfb_height),\n                    backend=\"xvfb\",\n                    use_xauth=True,\n                )\n                if \"--debug-display\" in sys.argv:\n                    print(\n                        \"Starting VDisplay from base_case: (%s, %s)\"\n                        % (self._xvfb_width, self._xvfb_height)\n                    )\n                self._xvfb_display.start()\n                sb_config._virtual_display = self._xvfb_display\n                if \"DISPLAY\" not in os.environ.keys():\n                    print(\n                        \"\\n  X11 display failed! Is Xvfb installed? \"\n                        \"\\n  Try this: `sudo apt install -y xvfb`\"\n                    )\n                    self.__activate_standard_virtual_display()\n                else:\n                    self.headless_active = True\n                    if self._reuse_session and hasattr(sb_config, \"_vd_list\"):\n                        if isinstance(sb_config._vd_list, list):\n                            sb_config._vd_list.append(self._xvfb_display)\n            except Exception as e:\n                if hasattr(e, \"msg\"):\n                    print(\"\\n\" + str(e.msg))\n                else:\n                    print(e)\n                print(\n                    \"\\n  X11 display failed! Is Xvfb installed? \"\n                    \"\\n  Try this: `sudo apt install -y xvfb`\"\n                )\n                self.__activate_standard_virtual_display()\n                return\n            pyautogui_is_installed = False\n            try:\n                import pyautogui\n                with suppress(Exception):\n                    use_pyautogui_ver = constants.PyAutoGUI.VER\n                    u_pv = shared_utils.make_version_tuple(use_pyautogui_ver)\n                    pv = shared_utils.make_version_tuple(pyautogui.__version__)\n                    if pv < u_pv:\n                        del pyautogui  # To get newer ver\n                        shared_utils.pip_install(\"pyautogui\", version=\"Latest\")\n                        import pyautogui\n                pyautogui_is_installed = True\n            except Exception:\n                message = (\n                    \"PyAutoGUI is required for UC Mode on Linux! \"\n                    \"Installing now...\"\n                )\n                print(\"\\n\" + message)\n                shared_utils.pip_install(\"pyautogui\", version=\"Latest\")\n                import pyautogui\n                pyautogui_is_installed = True\n            if (\n                pyautogui_is_installed\n                and hasattr(pyautogui, \"_pyautogui_x11\")\n            ):\n                try:\n                    pyautogui._pyautogui_x11._display = (\n                        Xlib.display.Display(os.environ['DISPLAY'])\n                    )\n                    sb_config._pyautogui_x11_display = (\n                        pyautogui._pyautogui_x11._display\n                    )\n                except Exception as e:\n                    if hasattr(e, \"msg\"):\n                        print(\"\\n\" + str(e.msg))\n                    else:\n                        print(e)\n        else:\n            self.__activate_standard_virtual_display()\n\n    def __activate_virtual_display_as_needed(self):\n        \"\"\"This is only needed on Linux.\n        The \"--xvfb\" arg is still useful, as it prevents headless mode,\n        which is the default mode on Linux unless using another arg.\"\"\"\n        if is_linux and (not self.headed or self.xvfb):\n            pip_find_lock = fasteners.InterProcessLock(\n                constants.PipInstall.FINDLOCK\n            )\n            try:\n                with pip_find_lock:\n                    pass\n            except Exception:\n                # Since missing permissions, skip the locks\n                self.__activate_virtual_display()\n                return\n            with pip_find_lock:  # Prevent issues with multiple processes\n                with suppress(Exception):\n                    shared_utils.make_writable(constants.PipInstall.FINDLOCK)\n                self.__activate_virtual_display()\n\n    def __ad_block_as_needed(self):\n        \"\"\"This is an internal method for handling ad-blocking.\n        Use \"pytest --ad-block\" to enable this during tests.\n        When not Chromium or in headless mode, use the hack.\"\"\"\n        if self.ad_block_on and (self.headless or not self.is_chromium()):\n            # (Chromium browsers in headed mode use the extension instead)\n            current_url = self.get_current_url()\n            if not current_url == self.__last_page_load_url:\n                if page_actions.is_element_present(\n                    self.driver, \"iframe\", By.CSS_SELECTOR\n                ):\n                    self.ad_block()\n                self.__last_page_load_url = current_url\n\n    def __disable_beforeunload_as_needed(self):\n        \"\"\"Disables beforeunload as needed. Also resets frame_switch state.\"\"\"\n        if getattr(self, \"_disable_beforeunload\", None):\n            self.disable_beforeunload()\n        if self.recorder_mode:\n            try:\n                current_url = self.get_current_url\n            except Exception:\n                current_url = None\n                self.__last_saved_url = None\n            if current_url != self.__last_saved_url:\n                self.__frame_switch_layer = 0\n                self.__frame_switch_multi = False\n                self.__last_saved_url = current_url\n\n    ############\n\n    @decorators.deprecated(\"You should use re.escape() instead.\")\n    def jq_format(self, code):\n        # DEPRECATED - re.escape() already performs this action.\n        return js_utils._jq_format(code)\n\n    ############\n\n    # Shadow DOM / Shadow-root methods\n\n    def __get_shadow_element(\n        self, selector, timeout=None, must_be_visible=False\n    ):\n        self.wait_for_ready_state_complete()\n        if timeout is None:\n            timeout = settings.SMALL_TIMEOUT\n        elif timeout == 0:\n            timeout = 0.1  # Use for: is_shadow_element_* (* = present/visible)\n        if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:\n            timeout = self.__get_new_timeout(timeout)\n        self.__fail_if_invalid_shadow_selector_usage(selector)\n        if \"::shadow \" not in selector:\n            raise Exception(\n                'A Shadow DOM selector must contain at least one \"::shadow \"!'\n            )\n        selectors = selector.split(\"::shadow \")\n        element = self.get_element(selectors[0])\n        selector_chain = selectors[0]\n        is_present = False\n        for selector_part in selectors[1:]:\n            shadow_root = None\n            if self.is_chromium() or self.browser == \"firefox\":\n                try:\n                    shadow_root = element.shadow_root\n                except Exception:\n                    if timeout != 0.1:  # Skip wait for special 0.1 (See above)\n                        time.sleep(2)\n                    try:\n                        shadow_root = element.shadow_root\n                    except Exception:\n                        raise Exception(\n                            \"Element {%s} has no shadow root!\" % selector_chain\n                        )\n            else:\n                try:\n                    shadow_root = self.execute_script(\n                        \"return arguments[0].shadowRoot;\", element\n                    )\n                except Exception:\n                    time.sleep(2)\n                    shadow_root = self.execute_script(\n                        \"return arguments[0].shadowRoot;\", element\n                    )\n            if timeout == 0.1 and not shadow_root:\n                raise Exception(\n                    \"Element {%s} has no shadow root!\" % selector_chain\n                )\n            elif not shadow_root:\n                time.sleep(2)  # Wait two seconds for the shadow root to appear\n                shadow_root = self.execute_script(\n                    \"return arguments[0].shadowRoot;\", element\n                )\n                if not shadow_root:\n                    raise Exception(\n                        \"Element {%s} has no shadow root!\" % selector_chain\n                    )\n            selector_chain += \"::shadow \"\n            selector_chain += selector_part\n            try:\n                if (\n                    (self.is_chromium() or self.browser == \"firefox\")\n                    and int(self.__get_major_browser_version()) >= 96\n                ):\n                    if timeout == 0.1:\n                        element = shadow_root.find_element(\n                            By.CSS_SELECTOR, value=selector_part\n                        )\n                    else:\n                        found = False\n                        for i in range(int(timeout) * 4):\n                            try:\n                                element = shadow_root.find_element(\n                                    By.CSS_SELECTOR, value=selector_part\n                                )\n                                is_present = True\n                                if (\n                                    selector_part == selectors[-1]\n                                    and must_be_visible\n                                ):\n                                    if not element.is_displayed():\n                                        raise Exception(\n                                            \"Shadow Root element not visible!\"\n                                        )\n                                found = True\n                                break\n                            except Exception:\n                                time.sleep(0.2)\n                                continue\n                        if not found:\n                            element = shadow_root.find_element(\n                                By.CSS_SELECTOR, value=selector_part\n                            )\n                            is_present = True\n                            if (\n                                selector_part == selectors[-1]\n                                and must_be_visible\n                                and not element.is_displayed()\n                            ):\n                                raise Exception(\n                                    \"Shadow Root element not visible!\"\n                                )\n                else:\n                    element = page_actions.wait_for_element_present(\n                        shadow_root,\n                        selector_part,\n                        by=\"css selector\",\n                        timeout=timeout,\n                    )\n            except Exception:\n                error = \"not present\"\n                the_exception = \"NoSuchElementException\"\n                if must_be_visible and is_present:\n                    error = \"not visible\"\n                    the_exception = \"ElementNotVisibleException\"\n                plural = \"s\"\n                if timeout == 1:\n                    plural = \"\"\n                msg = (\n                    \"Shadow DOM Element {%s} was %s after %s second%s!\"\n                    % (selector_chain, error, timeout, plural)\n                )\n                page_actions.timeout_exception(the_exception, msg)\n        return element\n\n    def __fail_if_invalid_shadow_selector_usage(self, selector):\n        if selector.strip().endswith(\"::shadow\"):\n            msg = (\n                \"A Shadow DOM selector cannot end on a shadow root element!\"\n                \" End the selector with an element inside the shadow root!\"\n            )\n            raise Exception(msg)\n\n    def __is_shadow_selector(self, selector):\n        self.__fail_if_invalid_shadow_selector_usage(selector)\n        if \"::shadow \" in selector:\n            return True\n        return False\n\n    def __shadow_click(self, selector, timeout):\n        element = self.__get_shadow_element(\n            selector, timeout=timeout, must_be_visible=True\n        )\n        element.click()\n\n    def __shadow_type(self, selector, text, timeout, clear_first=True):\n        element = self.__get_shadow_element(\n            selector, timeout=timeout, must_be_visible=True\n        )\n        if clear_first:\n            with suppress(Exception):\n                element.clear()\n                backspaces = Keys.BACK_SPACE * 42  # Autofill Defense\n                element.send_keys(backspaces)\n        text = self.__get_type_checked_text(text)\n        if not text.endswith(\"\\n\"):\n            element.send_keys(text)\n            if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                self.wait_for_ready_state_complete()\n        else:\n            element.send_keys(text[:-1])\n            element.send_keys(Keys.RETURN)\n            if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:\n                self.wait_for_ready_state_complete()\n\n    def __shadow_clear(self, selector, timeout):\n        element = self.__get_shadow_element(\n            selector, timeout=timeout, must_be_visible=True\n        )\n        with suppress(Exception):\n            element.clear()\n            backspaces = Keys.BACK_SPACE * 42  # Autofill Defense\n            element.send_keys(backspaces)\n\n    def __get_shadow_text(self, selector, timeout):\n        element = self.__get_shadow_element(\n            selector, timeout=timeout, must_be_visible=True\n        )\n        element_text = element.text\n        if self.browser == \"safari\":\n            element_text = element.get_attribute(\"innerText\")\n        return element_text\n\n    def __get_shadow_attribute(self, selector, attribute, timeout):\n        element = self.__get_shadow_element(selector, timeout=timeout)\n        return element.get_attribute(attribute)\n\n    def __wait_for_shadow_text_visible(self, text, selector, timeout):\n        text = str(text)\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (settings.SMALL_TIMEOUT * 1000.0)\n        for x in range(int(settings.SMALL_TIMEOUT * 10)):\n            try:\n                actual_text = self.__get_shadow_text(\n                    selector, timeout=1\n                ).strip()\n                text = text.strip()\n                if text not in actual_text:\n                    msg = (\n                        \"Expected text {%s} in element {%s} was not visible!\"\n                        % (text, selector)\n                    )\n                    page_actions.timeout_exception(\n                        \"ElementNotVisibleException\", msg\n                    )\n                return True\n            except Exception:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.1)\n        actual_text = self.__get_shadow_text(selector, timeout=1).strip()\n        text = text.strip()\n        if text not in actual_text:\n            msg = \"Expected text {%s} in element {%s} was not visible!\" % (\n                text,\n                selector,\n            )\n            page_actions.timeout_exception(\"TextNotVisibleException\", msg)\n        return True\n\n    def __wait_for_exact_shadow_text_visible(self, text, selector, timeout):\n        text = str(text)\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (settings.SMALL_TIMEOUT * 1000.0)\n        for x in range(int(settings.SMALL_TIMEOUT * 10)):\n            try:\n                actual_text = self.__get_shadow_text(\n                    selector, timeout=1\n                ).strip()\n                text = text.strip()\n                if text != actual_text:\n                    msg = (\n                        \"Expected exact text {%s} in element {%s} not visible!\"\n                        \"\" % (text, selector)\n                    )\n                    page_actions.timeout_exception(\n                        \"TextNotVisibleException\", msg\n                    )\n                return True\n            except Exception:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.1)\n        actual_text = self.__get_shadow_text(selector, timeout=1).strip()\n        text = text.strip()\n        if text != actual_text:\n            msg = (\n                \"Expected exact text {%s} in element {%s} was not visible!\"\n                % (text, selector)\n            )\n            page_actions.timeout_exception(\"TextNotVisibleException\", msg)\n        return True\n\n    def __wait_for_non_empty_shadow_text_visible(self, selector, timeout):\n        start_ms = time.time() * 1000.0\n        stop_ms = start_ms + (settings.SMALL_TIMEOUT * 1000.0)\n        for x in range(int(settings.SMALL_TIMEOUT * 10)):\n            try:\n                actual_text = self.__get_shadow_text(selector, timeout=1)\n                actual_text = actual_text.strip()\n                if len(actual_text) == 0:\n                    msg = \"Element {%s} has no visible text!\" % selector\n                    page_actions.timeout_exception(\n                        \"TextNotVisibleException\", msg\n                    )\n                return True\n            except Exception:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.1)\n        actual_text = self.__get_shadow_text(selector, timeout=1)\n        actual_text = actual_text.strip()\n        if len(actual_text) == 0:\n            msg = \"Element {%s} has no visible text!\" % selector\n            page_actions.timeout_exception(\"TextNotVisibleException\", msg)\n        return True\n\n    def __assert_shadow_text_visible(self, text, selector, timeout):\n        self.__wait_for_shadow_text_visible(text, selector, timeout)\n        if self.demo_mode:\n            a_t = \"ASSERT TEXT\"\n            i_n = \"in\"\n            by = By.CSS_SELECTOR\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_text(self._language)\n                i_n = SD.translate_in(self._language)\n            messenger_post = \"<b>%s</b>: {%s} %s %s: %s\" % (\n                a_t, text, i_n, by.upper(), selector\n            )\n            with suppress(Exception):\n                js_utils.activate_jquery(self.driver)\n                js_utils.post_messenger_success_message(\n                    self.driver, messenger_post, self.message_duration\n                )\n\n    def __assert_exact_shadow_text_visible(self, text, selector, timeout):\n        self.__wait_for_exact_shadow_text_visible(text, selector, timeout)\n        if self.demo_mode:\n            a_t = \"ASSERT EXACT TEXT\"\n            i_n = \"in\"\n            by = By.CSS_SELECTOR\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_exact_text(self._language)\n                i_n = SD.translate_in(self._language)\n            messenger_post = \"<b>%s</b>: {%s} %s %s: %s\" % (\n                a_t, text, i_n, by.upper(), selector\n            )\n            with suppress(Exception):\n                js_utils.activate_jquery(self.driver)\n                js_utils.post_messenger_success_message(\n                    self.driver, messenger_post, self.message_duration\n                )\n\n    def __assert_non_empty_shadow_text_visible(self, selector, timeout, strip):\n        self.__wait_for_non_empty_shadow_text_visible(selector, timeout, strip)\n        if self.demo_mode:\n            a_t = \"ASSERT NON-EMPTY TEXT\"\n            i_n = \"in\"\n            by = By.CSS_SELECTOR\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert_non_empty_text(self._language)\n                i_n = SD.translate_in(self._language)\n            messenger_post = \"<b>%s</b> %s %s: %s\" % (\n                a_t, i_n, by.upper(), selector\n            )\n            with suppress(Exception):\n                js_utils.activate_jquery(self.driver)\n                js_utils.post_messenger_success_message(\n                    self.driver, messenger_post, self.message_duration\n                )\n\n    def __is_shadow_element_present(self, selector):\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            return element is not None\n        except Exception:\n            return False\n\n    def __is_shadow_element_visible(self, selector):\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            return element.is_displayed()\n        except Exception:\n            return False\n\n    def __is_shadow_element_clickable(self, selector):\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            if element.is_displayed() and element.is_enabled():\n                return True\n            return False\n        except Exception:\n            return False\n\n    def __is_shadow_element_enabled(self, selector):\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            return element.is_enabled()\n        except Exception:\n            return False\n\n    def __is_shadow_text_visible(self, text, selector):\n        text = str(text)\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            if self.browser == \"safari\":\n                return (\n                    element.is_displayed()\n                    and text in element.get_attribute(\"innerText\")\n                )\n            return element.is_displayed() and text in element.text\n        except Exception:\n            return False\n\n    def __is_shadow_exact_text_visible(self, text, selector):\n        text = str(text)\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            if self.browser == \"safari\":\n                return (\n                    element.is_displayed()\n                    and text in element.get_attribute(\"innerText\")\n                )\n            return (\n                element.is_displayed()\n                and text.strip() == element.text.strip()\n            )\n        except Exception:\n            return False\n\n    def __is_shadow_non_empty_text_visible(self, selector):\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            if self.browser == \"safari\":\n                return (\n                    element.is_displayed()\n                    and len(element.get_attribute(\"innerText\").strip() > 0)\n                )\n            return element.is_displayed() and len(element.text.strip()) > 0\n        except Exception:\n            return False\n\n    def __is_shadow_attribute_present(self, selector, attribute, value=None):\n        try:\n            element = self.__get_shadow_element(selector, timeout=0.1)\n            found_value = element.get_attribute(attribute)\n            if found_value is None:\n                return False\n            if value is not None:\n                if found_value == value:\n                    return True\n                else:\n                    return False\n            else:\n                return True\n        except Exception:\n            return False\n\n    def __wait_for_shadow_element_present(self, selector, timeout):\n        element = self.__get_shadow_element(selector, timeout=timeout)\n        return element\n\n    def __wait_for_shadow_element_visible(self, selector, timeout):\n        element = self.__get_shadow_element(\n            selector, timeout=timeout, must_be_visible=True\n        )\n        return element\n\n    def __wait_for_shadow_attribute_present(\n        self, selector, attribute, value=None, timeout=None\n    ):\n        element = self.__get_shadow_element(selector, timeout=timeout)\n        actual_value = element.get_attribute(attribute)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        if value is None:\n            # The element attribute only needs to exist\n            if actual_value is not None:\n                return element\n            else:\n                # The element does not have the attribute\n                message = (\n                    \"Expected attribute {%s} of element {%s} \"\n                    \"was not present after %s second%s!\"\n                    % (attribute, selector, timeout, plural)\n                )\n                page_actions.timeout_exception(\n                    \"NoSuchAttributeException\", message\n                )\n        else:\n            if actual_value == value:\n                return element\n            else:\n                message = (\n                    \"Expected value {%s} for attribute {%s} of element \"\n                    \"{%s} was not present after %s second%s! \"\n                    \"(Actual value was {%s})\"\n                    % (\n                        value,\n                        attribute,\n                        selector,\n                        timeout,\n                        plural,\n                        actual_value,\n                    )\n                )\n                page_actions.timeout_exception(\n                    \"NoSuchAttributeException\", message\n                )\n\n    def __assert_shadow_element_present(self, selector):\n        self.__get_shadow_element(selector)\n        if self.demo_mode:\n            a_t = \"ASSERT\"\n            by = By.CSS_SELECTOR\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert(self._language)\n            messenger_post = \"<b>%s %s</b>: %s\" % (a_t, by.upper(), selector)\n            with suppress(Exception):\n                js_utils.activate_jquery(self.driver)\n                js_utils.post_messenger_success_message(\n                    self.driver, messenger_post, self.message_duration\n                )\n\n    def __assert_shadow_element_visible(self, selector):\n        element = self.__get_shadow_element(selector)\n        if not element.is_displayed():\n            msg = \"Shadow DOM Element {%s} was not visible!\" % selector\n            page_actions.timeout_exception(\"NoSuchElementException\", msg)\n        if self.demo_mode:\n            a_t = \"ASSERT\"\n            by = By.CSS_SELECTOR\n            if self._language != \"English\":\n                from seleniumbase.fixtures.words import SD\n\n                a_t = SD.translate_assert(self._language)\n            messenger_post = \"<b>%s %s</b>: %s\" % (a_t, by.upper(), selector)\n            with suppress(Exception):\n                js_utils.activate_jquery(self.driver)\n                js_utils.post_messenger_success_message(\n                    self.driver, messenger_post, self.message_duration\n                )\n\n    ############\n\n    def setUp(self, masterqa_mode=False):\n        \"\"\"This method runs before every test begins.\n        Be careful if a subclass of BaseCase overrides setUp().\n        If so, add the following line to the subclass setUp() method:\n        super().setUp() \"\"\"\n        if not hasattr(self, \"_using_sb_fixture\") and self.__called_setup:\n            return  # This test already called setUp()\n        self.__called_setup = True\n        self.__called_teardown = False\n        self.is_pytest = None\n        self.log_path = constants.Logs.LATEST\n        self.masterqa_mode = masterqa_mode\n        try:\n            # This raises an exception if the test is not coming from pytest\n            self.is_pytest = sb_config.is_pytest\n        except Exception:\n            # Not using pytest (could be pynose, behave, or raw python)\n            self.is_pytest = False\n        if self.is_pytest:\n            # pytest-specific code\n            test_id = self.__get_test_id()\n            self.test_id = test_id\n            self.is_behave = False\n            if hasattr(self, \"_using_sb_fixture\"):\n                self.test_id = sb_config._test_id\n                if hasattr(sb_config, \"_sb_pdb_driver\"):\n                    sb_config._sb_pdb_driver = None\n            self.browser = sb_config.browser\n            self.account = sb_config.account\n            self.data = sb_config.data\n            self.var1 = sb_config.var1\n            self.var2 = sb_config.var2\n            self.var3 = sb_config.var3\n            variables = sb_config.variables\n            if variables and isinstance(variables, str) and len(variables) > 0:\n                import ast\n\n                bad_input = False\n                if (\n                    not variables.startswith(\"{\")\n                    or not variables.endswith(\"}\")\n                ):\n                    bad_input = True\n                else:\n                    try:\n                        variables = ast.literal_eval(variables)\n                        if not isinstance(variables, dict):\n                            bad_input = True\n                    except Exception:\n                        bad_input = True\n                if bad_input:\n                    raise Exception(\n                        '\\nExpecting a Python dictionary for \"variables\"!'\n                        \"\\nEg. --variables=\\\"{'KEY1':'VALUE', 'KEY2':123}\\\"\"\n                    )\n            elif isinstance(variables, dict):\n                pass  # Already processed\n            else:\n                variables = {}\n            sb_config.variables = variables\n            self.variables = sb_config.variables\n            self.slow_mode = sb_config.slow_mode\n            self.demo_mode = sb_config.demo_mode\n            self.demo_sleep = sb_config.demo_sleep\n            self.highlights = sb_config.highlights\n            self.time_limit = sb_config._time_limit\n            sb_config.time_limit = sb_config._time_limit  # Reset between tests\n            self.environment = sb_config.environment\n            self.env = self.environment  # Add a shortened version\n            self.with_selenium = sb_config.with_selenium  # Should be True\n            self.headless = sb_config.headless\n            self.headless1 = sb_config.headless1\n            if self.headless1:\n                self.headless = True\n            self.headless2 = sb_config.headless2\n            self.headless_active = False\n            sb_config.headless_active = False\n            self.headed = sb_config.headed\n            self.xvfb = sb_config.xvfb\n            self.xvfb_metrics = sb_config.xvfb_metrics\n            self.locale_code = sb_config.locale_code\n            self.interval = sb_config.interval\n            self.start_page = sb_config.start_page\n            self.log_path = sb_config.log_path\n            self.with_testing_base = sb_config.with_testing_base\n            self.with_basic_test_info = sb_config.with_basic_test_info\n            self.with_screen_shots = sb_config.with_screen_shots\n            self.with_page_source = sb_config.with_page_source\n            self.with_db_reporting = sb_config.with_db_reporting\n            self.with_s3_logging = sb_config.with_s3_logging\n            self.protocol = sb_config.protocol\n            self.servername = sb_config.servername\n            self.port = sb_config.port\n            self.proxy_string = sb_config.proxy_string\n            self.proxy_bypass_list = sb_config.proxy_bypass_list\n            self.proxy_pac_url = sb_config.proxy_pac_url\n            self.multi_proxy = sb_config.multi_proxy\n            self.user_agent = sb_config.user_agent\n            self.mobile_emulator = sb_config.mobile_emulator\n            self.device_metrics = sb_config.device_metrics\n            self.cap_file = sb_config.cap_file\n            self.cap_string = sb_config.cap_string\n            self.settings_file = sb_config.settings_file\n            self.database_env = sb_config.database_env\n            self.message_duration = sb_config.message_duration\n            self.js_checking_on = sb_config.js_checking_on\n            self.ad_block_on = sb_config.ad_block_on\n            self.host_resolver_rules = sb_config.host_resolver_rules\n            self.block_images = sb_config.block_images\n            self.do_not_track = sb_config.do_not_track\n            self.chromium_arg = sb_config.chromium_arg\n            self.firefox_arg = sb_config.firefox_arg\n            self.firefox_pref = sb_config.firefox_pref\n            self.verify_delay = sb_config.verify_delay\n            self.esc_end = sb_config.esc_end\n            self.recorder_mode = sb_config.recorder_mode\n            self.recorder_ext = sb_config.recorder_mode\n            self.rec_print = sb_config.rec_print\n            self.rec_behave = sb_config.rec_behave\n            self.record_sleep = sb_config.record_sleep\n            if self.rec_print and not self.recorder_mode:\n                self.recorder_mode = True\n                self.recorder_ext = True\n            elif self.rec_behave and not self.recorder_mode:\n                self.recorder_mode = True\n                self.recorder_ext = True\n            elif self.record_sleep and not self.recorder_mode:\n                self.recorder_mode = True\n                self.recorder_ext = True\n            self.disable_cookies = sb_config.disable_cookies\n            self.disable_js = sb_config.disable_js\n            self.disable_csp = sb_config.disable_csp\n            self.disable_ws = sb_config.disable_ws\n            self.enable_ws = sb_config.enable_ws\n            if not self.disable_ws:\n                self.enable_ws = True\n            self.enable_sync = sb_config.enable_sync\n            self.use_auto_ext = sb_config.use_auto_ext\n            self.undetectable = sb_config.undetectable\n            self.uc_cdp_events = sb_config.uc_cdp_events\n            self.uc_subprocess = sb_config.uc_subprocess\n            self.log_cdp_events = sb_config.log_cdp_events\n            self.no_sandbox = sb_config.no_sandbox\n            self.disable_gpu = sb_config.disable_gpu\n            self.incognito = sb_config.incognito\n            self.guest_mode = sb_config.guest_mode\n            self.dark_mode = sb_config.dark_mode\n            self.devtools = sb_config.devtools\n            self.remote_debug = sb_config.remote_debug\n            self._multithreaded = sb_config._multithreaded\n            self._reuse_session = sb_config.reuse_session\n            self._crumbs = sb_config.crumbs\n            self._disable_beforeunload = sb_config._disable_beforeunload\n            self.dashboard = sb_config.dashboard\n            self._dash_initialized = sb_config._dashboard_initialized\n            if self.dashboard and self._multithreaded:\n                self.dash_lock = fasteners.InterProcessLock(\n                    constants.Dashboard.LOCKFILE\n                )\n            self.enable_3d_apis = sb_config.enable_3d_apis\n            self._swiftshader = sb_config.swiftshader\n            self.user_data_dir = sb_config.user_data_dir\n            self.extension_zip = sb_config.extension_zip\n            self.extension_dir = sb_config.extension_dir\n            self.disable_features = sb_config.disable_features\n            self.binary_location = sb_config.binary_location\n            self.driver_version = sb_config.driver_version\n            self.page_load_strategy = sb_config.page_load_strategy\n            self.use_wire = sb_config.use_wire\n            self.external_pdf = sb_config.external_pdf\n            self._final_debug = sb_config.final_debug\n            self.window_position = sb_config.window_position\n            self.window_size = sb_config.window_size\n            self.maximize_option = sb_config.maximize_option\n            self.save_screenshot_after_test = sb_config.save_screenshot\n            self.no_screenshot_after_test = sb_config.no_screenshot\n            self.visual_baseline = sb_config.visual_baseline\n            self.timeout_multiplier = sb_config.timeout_multiplier\n            self.pytest_html_report = sb_config.pytest_html_report\n            self.report_on = False\n            if self.pytest_html_report:\n                self.report_on = True\n            self.use_grid = False\n            if self.servername != \"localhost\":\n                # Use Selenium Grid (Use --server=\"127.0.0.1\" for a local Grid)\n                self.use_grid = True\n            if self.with_db_reporting:\n                import getpass\n                import uuid\n                from seleniumbase.core.application_manager import (\n                    ApplicationManager,\n                )\n                from seleniumbase.core.testcase_manager import (\n                    ExecutionQueryPayload,\n                    TestcaseDataPayload,\n                    TestcaseManager,\n                )\n\n                self.execution_guid = str(uuid.uuid4())\n                self.testcase_guid = None\n                self.execution_start_time = 0\n                self.case_start_time = 0\n                self.testcase_manager = None\n                self.testcase_manager = TestcaseManager(self.database_env)\n                #\n                exec_payload = ExecutionQueryPayload()\n                exec_payload.execution_start_time = int(time.time() * 1000.0)\n                self.execution_start_time = exec_payload.execution_start_time\n                exec_payload.guid = self.execution_guid\n                exec_payload.username = getpass.getuser()\n                self.testcase_manager.insert_execution_data(exec_payload)\n                #\n                data_payload = TestcaseDataPayload()\n                self.testcase_guid = str(uuid.uuid4())\n                data_payload.guid = self.testcase_guid\n                data_payload.execution_guid = self.execution_guid\n                if self.with_selenium:\n                    data_payload.browser = self.browser\n                else:\n                    data_payload.browser = \"N/A\"\n                data_payload.test_address = test_id\n                application = ApplicationManager.generate_application_string(\n                    self\n                )\n                data_payload.env = application.split(\".\")[0]\n                data_payload.start_time = application.split(\".\")[1]\n                data_payload.state = constants.State.UNTESTED\n                self.__skip_reason = None\n                self.testcase_manager.insert_testcase_data(data_payload)\n                self.case_start_time = int(time.time() * 1000.0)\n        elif getattr(self, \"is_behave\", None):\n            self.__initialize_variables()\n        elif getattr(self, \"is_nosetest\", None):\n            pass  # Setup performed in plugins for pynose\n        else:\n            # Pure Python run. (Eg. SB() and Driver() Managers)\n            pass  # Variables initialized in respective plugins\n\n        # Verify SeleniumBase is installed successfully, and used correctly\n        if not hasattr(self, \"browser\"):\n            raise Exception(\n                \"SeleniumBase plugins DID NOT load!\\n\"\n                'Either \"seleniumbase\" is not installed on your system,\\n'\n                'or you called \"setUp()\" directly without initialization!\\n'\n                \"*** To install SeleniumBase in Develop Mode from a clone:\\n\"\n                '    >>> \"pip install -e .\"     (Run in DIR with setup.py)\\n'\n                \"*** To install the latest SeleniumBase version from PyPI:\\n\"\n                '    >>> \"pip install -U seleniumbase\"    (Run in any DIR)\\n'\n                'NOTE: \"setUp()\" can ONLY be called after vars are set.\\n'\n                '    If using \"pytest\", then \"pytest_plugin.py\" sets them.\\n'\n                '    The SeleniumBase \"SB()\" Manager can also set them.\\n'\n                \"See SeleniumBase/help_docs/syntax_formats.md for details!\"\n            )\n\n        if not hasattr(sb_config, \"_is_timeout_changed\"):\n            # Should only be reachable from pure Python runs\n            sb_config._is_timeout_changed = False\n            sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT\n            sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT\n\n        if sb_config._is_timeout_changed:\n            if sb_config._SMALL_TIMEOUT and sb_config._LARGE_TIMEOUT:\n                settings.SMALL_TIMEOUT = sb_config._SMALL_TIMEOUT\n                settings.LARGE_TIMEOUT = sb_config._LARGE_TIMEOUT\n\n        if not hasattr(self, \"_swiftshader\"):\n            # Not swiftshader: options.add_argument(\"--disable-gpu\")\n            self._swiftshader = False\n\n        if not hasattr(sb_config, \"_recorded_actions\"):\n            # Only filled when Recorder Mode is enabled\n            sb_config._recorded_actions = {}\n            sb_config._behave_recorded_actions = {}\n\n        if not hasattr(settings, \"SWITCH_TO_NEW_TABS_ON_CLICK\"):\n            # If using an older settings file, set the new definitions manually\n            settings.SWITCH_TO_NEW_TABS_ON_CLICK = True\n\n        # Parse the settings file\n        if self.settings_file:\n            from seleniumbase.core import settings_parser\n\n            settings_parser.set_settings(self.settings_file)\n\n        # Set variables that may be useful to developers\n        self.log_abspath = os.path.abspath(self.log_path)\n        self.data_path = os.path.join(self.log_path, self.__get_test_id())\n        self.data_abspath = os.path.abspath(self.data_path)\n\n        # Add _test_logpath value to sb_config\n        test_id = self.__get_test_id()\n        test_logpath = os.path.join(self.log_path, test_id)\n        sb_config._test_logpath = test_logpath\n\n        # Add _process_dashboard_entry method to sb_config\n        sb_config._process_dashboard_entry = self._process_dashboard_entry\n\n        # Add _add_pytest_html_extra method to sb_config\n        sb_config._add_pytest_html_extra = self._add_pytest_html_extra\n\n        # Add _process_visual_baseline_logs method to sb_config\n        sb_config._process_v_baseline_logs = self._process_visual_baseline_logs\n\n        # Add _log_fail_data method to sb_config\n        sb_config._log_fail_data = self._log_fail_data\n\n        # Reset the last_page_screenshot variables\n        sb_config._last_page_screenshot = None\n        sb_config._last_page_screenshot_png = None\n\n        # Indictate to pytest reports that SeleniumBase is being used\n        sb_config._sbase_detected = True\n        sb_config._only_unittest = False\n\n        # Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio\n        if self.device_metrics:\n            metrics_string = self.device_metrics\n            metrics_string = metrics_string.replace(\" \", \"\")\n            metrics_list = metrics_string.split(\",\")\n            exception_string = (\n                \"Invalid input for Mobile Emulator device metrics!\\n\"\n                \"Expecting a comma-separated string with integer values\\n\"\n                \"for Width/Height, and an int or float for Pixel-Ratio.\\n\"\n                'Example: --metrics=\"412,732,3\" '\n            )\n            if len(metrics_list) != 3:\n                raise Exception(exception_string)\n            try:\n                self.__device_width = int(metrics_list[0])\n                self.__device_height = int(metrics_list[1])\n                self.__device_pixel_ratio = float(metrics_list[2])\n                self.mobile_emulator = True\n            except Exception:\n                raise Exception(exception_string)\n\n        window_position = self.window_position\n        if window_position:\n            if window_position.count(\",\") != 1:\n                message = (\n                    '\\n\\n  window_position expects an \"x,y\" string!'\n                    '\\n  (Your input was: \"%s\")\\n' % window_position\n                )\n                raise Exception(message)\n            window_position = window_position.replace(\" \", \"\")\n            win_x = None\n            win_y = None\n            try:\n                win_x = int(window_position.split(\",\")[0])\n                win_y = int(window_position.split(\",\")[1])\n            except Exception:\n                message = (\n                    '\\n\\n  Expecting integer values for \"x,y\"!'\n                    '\\n  (window_position input was: \"%s\")\\n'\n                    % window_position\n                )\n                raise Exception(message)\n            settings.WINDOW_START_X = win_x\n            settings.WINDOW_START_Y = win_y\n\n        window_size = self.window_size\n        if window_size:\n            if window_size.count(\",\") != 1:\n                message = (\n                    '\\n\\n  window_size expects a \"width,height\" string!'\n                    '\\n  (Your input was: \"%s\")\\n' % window_size\n                )\n                raise Exception(message)\n            window_size = window_size.replace(\" \", \"\")\n            width = None\n            height = None\n            try:\n                width = int(window_size.split(\",\")[0])\n                height = int(window_size.split(\",\")[1])\n            except Exception:\n                message = (\n                    '\\n\\n  Expecting integer values for \"width,height\"!'\n                    '\\n  (window_size input was: \"%s\")\\n' % window_size\n                )\n                raise Exception(message)\n            settings.CHROME_START_WIDTH = width\n            settings.CHROME_START_HEIGHT = height\n            settings.HEADLESS_START_WIDTH = width\n            settings.HEADLESS_START_HEIGHT = height\n\n        if self.xvfb_metrics:\n            metrics_string = self.xvfb_metrics\n            metrics_string = metrics_string.replace(\" \", \"\")\n            metrics_list = metrics_string.split(\",\")[0:2]\n            exception_string = (\n                \"Invalid input for xvfb_metrics!\\n\"\n                \"Expecting a comma-separated string\\n\"\n                \"with integer values for Width/Height.\\n\"\n                'Eg. --xvfb-metrics=\"1920,1080\".\\n'\n                \"(Minimum: 1024,768) (Default: 1366,768)\"\n            )\n            if len(metrics_list) != 2:\n                raise Exception(exception_string)\n            try:\n                self._xvfb_width = int(metrics_list[0])\n                self._xvfb_height = int(metrics_list[1])\n                # The minimum width,height is: 1024,768\n                if self._xvfb_width < 1024:\n                    self._xvfb_width = 1024\n                sb_config._xvfb_width = self._xvfb_width\n                if self._xvfb_height < 768:\n                    self._xvfb_height = 768\n                sb_config._xvfb_height = self._xvfb_height\n                self.xvfb = True\n            except Exception:\n                raise Exception(exception_string)\n\n        if self.mobile_emulator and not self.user_agent:\n            # Use a Pixel user agent by default if not specified\n            self.user_agent = constants.Mobile.AGENT\n\n        if self.browser in [\"firefox\", \"ie\", \"safari\"]:\n            # The Recorder Mode browser extension is only for Chrome/Edge.\n            if self.recorder_mode:\n                message = (\n                    \"Recorder Mode ONLY supports Chrome and Edge!\\n\"\n                    '(Your browser choice was: \"%s\")' % self.browser\n                )\n                raise Exception(message)\n\n        if not getattr(self, \"is_nosetest\", None):\n            # Xvfb Virtual Display activation for Linux\n            self.__activate_virtual_display_as_needed()\n\n        # Dashboard pre-processing:\n        if self.dashboard:\n            if self._multithreaded:\n                with self.dash_lock:\n                    with suppress(Exception):\n                        shared_utils.make_writable(\n                            constants.Dashboard.LOCKFILE\n                        )\n                    if not self._dash_initialized:\n                        sb_config._dashboard_initialized = True\n                        self._dash_initialized = True\n                        self.__process_dashboard(False, init=True)\n            else:\n                if not self._dash_initialized:\n                    sb_config._dashboard_initialized = True\n                    self._dash_initialized = True\n                    self.__process_dashboard(False, init=True)\n\n        # Set the JS start time for Recorder Mode.\n        # Use this to skip saving recorded actions from previous tests.\n        if self.recorder_mode:\n            self.__js_start_time = int(time.time() * 1000.0)\n\n        has_url = False\n        if self._reuse_session:\n            if not hasattr(sb_config, \"shared_driver\"):\n                sb_config.shared_driver = None\n            if sb_config.shared_driver:\n                with suppress(Exception):\n                    self._default_driver = sb_config.shared_driver\n                    self.driver = sb_config.shared_driver\n                    self._drivers_list = [sb_config.shared_driver]\n                    url = self.get_current_url()\n                    if url is not None:\n                        has_url = True\n                    if len(self.driver.window_handles) > 1:\n                        while len(self.driver.window_handles) > 1:\n                            self.switch_to_window(-1)\n                            self.driver.close()\n                        self.switch_to_window(0)\n                    if self._crumbs:\n                        if self.binary_location == \"chs\":\n                            self.delete_session_storage()\n                        else:\n                            self.wait_for_ready_state_complete()\n                            with suppress(Exception):\n                                self.driver.delete_all_cookies()\n        if self._reuse_session and sb_config.shared_driver and has_url:\n            good_start_page = False\n            if self.recorder_ext:\n                self.__js_start_time = int(time.time() * 1000.0)\n            if self.start_page and len(self.start_page) >= 4:\n                if page_utils.is_valid_url(self.start_page):\n                    good_start_page = True\n                    self.__new_window_on_rec_open = False\n                    self.open(self.start_page)\n                    self.__new_window_on_rec_open = True\n                else:\n                    new_start_page = \"https://\" + self.start_page\n                    if page_utils.is_valid_url(new_start_page):\n                        good_start_page = True\n                        self.__dont_record_open = True\n                        self.open(new_start_page)\n                        self.__dont_record_open = False\n            if self.recorder_ext or (self._crumbs and not good_start_page):\n                if self.get_current_url() != \"about:blank\":\n                    self.__new_window_on_rec_open = False\n                    self.open(\"about:blank\")\n                    self.__new_window_on_rec_open = True\n                    if self.recorder_ext:\n                        self.__js_start_time = int(time.time() * 1000.0)\n        else:\n            # Launch WebDriver for both pytest and pynose\n            self.driver = self.get_new_driver(\n                browser=self.browser,\n                headless=self.headless,\n                locale_code=self.locale_code,\n                protocol=self.protocol,\n                servername=self.servername,\n                port=self.port,\n                proxy=self.proxy_string,\n                proxy_bypass_list=self.proxy_bypass_list,\n                proxy_pac_url=self.proxy_pac_url,\n                multi_proxy=self.multi_proxy,\n                agent=self.user_agent,\n                switch_to=True,\n                cap_file=self.cap_file,\n                cap_string=self.cap_string,\n                recorder_ext=self.recorder_ext,\n                disable_cookies=self.disable_cookies,\n                disable_js=self.disable_js,\n                disable_csp=self.disable_csp,\n                enable_ws=self.enable_ws,\n                enable_sync=self.enable_sync,\n                use_auto_ext=self.use_auto_ext,\n                undetectable=self.undetectable,\n                uc_cdp_events=self.uc_cdp_events,\n                uc_subprocess=self.uc_subprocess,\n                log_cdp_events=self.log_cdp_events,\n                no_sandbox=self.no_sandbox,\n                disable_gpu=self.disable_gpu,\n                headless1=self.headless1,\n                headless2=self.headless2,\n                incognito=self.incognito,\n                guest_mode=self.guest_mode,\n                dark_mode=self.dark_mode,\n                devtools=self.devtools,\n                remote_debug=self.remote_debug,\n                enable_3d_apis=self.enable_3d_apis,\n                swiftshader=self._swiftshader,\n                ad_block_on=self.ad_block_on,\n                host_resolver_rules=self.host_resolver_rules,\n                block_images=self.block_images,\n                do_not_track=self.do_not_track,\n                chromium_arg=self.chromium_arg,\n                firefox_arg=self.firefox_arg,\n                firefox_pref=self.firefox_pref,\n                user_data_dir=self.user_data_dir,\n                extension_zip=self.extension_zip,\n                extension_dir=self.extension_dir,\n                disable_features=self.disable_features,\n                binary_location=self.binary_location,\n                driver_version=self.driver_version,\n                page_load_strategy=self.page_load_strategy,\n                use_wire=self.use_wire,\n                external_pdf=self.external_pdf,\n                is_mobile=self.mobile_emulator,\n                d_width=self.__device_width,\n                d_height=self.__device_height,\n                d_p_r=self.__device_pixel_ratio,\n            )\n            try:\n                if self.driver.timeouts.implicit_wait > 0:\n                    self.driver.implicitly_wait(0)\n            except Exception:\n                with suppress(Exception):\n                    self.driver.implicitly_wait(0)\n            self._default_driver = self.driver\n            if self._reuse_session:\n                sb_config.shared_driver = self.driver\n            if len(self._drivers_list) == 0:\n                # The user is overriding self.get_new_driver()\n                # (Otherwise this code shouldn't be reachable)\n                self._drivers_list.append(self.driver)\n                self._drivers_browser_map[self.driver] = self.browser\n\n        if self.browser in [\"firefox\", \"ie\", \"safari\"]:\n            # Only Chrome and Edge browsers have the mobile emulator.\n            # Some actions such as hover-clicking are different on mobile.\n            self.mobile_emulator = False\n\n        # Configure the test time limit (if used).\n        self.set_time_limit(self.time_limit)\n\n        # Configure the page load timeout\n        with suppress(Exception):\n            if hasattr(settings, \"PAGE_LOAD_TIMEOUT\"):\n                self.driver.set_page_load_timeout(settings.PAGE_LOAD_TIMEOUT)\n            else:\n                self.driver.set_page_load_timeout(120)  # Selenium uses 300\n\n        # Set the start time for the test (in ms).\n        # Although the pytest clock starts before setUp() begins,\n        # the time-limit clock starts at the end of the setUp() method.\n        sb_config.start_time_ms = int(time.time() * 1000.0)\n        self.__start_time_ms = sb_config.start_time_ms\n\n    def __set_last_page_screenshot(self):\n        \"\"\"self.__last_page_screenshot is only for pytest html report logs.\n        self.__last_page_screenshot_png is for all screenshot log files.\"\"\"\n        SCREENSHOT_SKIPPED = constants.Warnings.SCREENSHOT_SKIPPED\n        SCREENSHOT_UNDEFINED = constants.Warnings.SCREENSHOT_UNDEFINED\n        if getattr(self, \"no_screenshot_after_test\", None):\n            from seleniumbase.core import encoded_images\n\n            NO_SCREENSHOT = encoded_images.get_no_screenshot_png()\n            self.__last_page_screenshot = NO_SCREENSHOT\n            self.__last_page_screenshot_png = SCREENSHOT_SKIPPED\n            sb_config._last_page_screenshot_png = NO_SCREENSHOT\n            return\n        element = None\n        if (\n            not self.__last_page_screenshot\n            and not self.__last_page_screenshot_png\n        ):\n            with suppress(Exception):\n                try:\n                    element = page_actions.wait_for_element_visible(\n                        self.driver,\n                        \"body\",\n                        \"css selector\",\n                        timeout=0.1,\n                        ignore_test_time_limit=True,\n                    )\n                except Exception:\n                    element = page_actions.wait_for_element_present(\n                        self.driver,\n                        \"body\",\n                        \"css selector\",\n                        timeout=0.1,\n                        ignore_test_time_limit=True,\n                    )\n                try:\n                    if getattr(settings, \"SCREENSHOT_WITH_BACKGROUND\", None):\n                        self.__last_page_screenshot = (\n                            self.driver.get_screenshot_as_base64()\n                        )\n                    else:\n                        self.__last_page_screenshot = (\n                            element.screenshot_as_base64\n                        )\n                except Exception:\n                    with suppress(Exception):\n                        self.__last_page_screenshot = (\n                            self.driver.get_screenshot_as_base64()\n                        )\n            if not self.__last_page_screenshot:\n                self.__last_page_screenshot = SCREENSHOT_UNDEFINED\n                self.__last_page_screenshot_png = SCREENSHOT_UNDEFINED\n                if element:\n                    try:\n                        self.__last_page_screenshot_png = (\n                            element.screenshot_as_png\n                        )\n                    except Exception:\n                        with suppress(Exception):\n                            self.__last_page_screenshot_png = (\n                                self.driver.get_screenshot_as_png()\n                            )\n            else:\n                import base64\n\n                try:\n                    self.__last_page_screenshot_png = (\n                        base64.b64decode(self.__last_page_screenshot)\n                    )\n                except Exception:\n                    if element:\n                        try:\n                            self.__last_page_screenshot_png = (\n                                element.screenshot_as_png\n                            )\n                        except Exception:\n                            with suppress(Exception):\n                                self.__last_page_screenshot_png = (\n                                    self.driver.get_screenshot_as_png()\n                                )\n        sb_config._last_page_screenshot_png = self.__last_page_screenshot_png\n\n    def __set_last_page_url(self):\n        if not self.__last_page_url:\n            try:\n                self.__last_page_url = log_helper.get_last_page(self.driver)\n            except Exception:\n                self.__last_page_url = None\n\n    def __set_last_page_source(self):\n        if not self.__last_page_source:\n            try:\n                self.__last_page_source = (\n                    log_helper.get_html_source_with_base_href(\n                        self.driver, self.driver.page_source\n                    )\n                )\n            except Exception:\n                self.__last_page_source = (\n                    constants.Warnings.PAGE_SOURCE_UNDEFINED\n                )\n            sb_config._last_page_source = self.__last_page_source\n\n    def __get_exception_info(self):\n        exc_message = None\n        if (\n            hasattr(self, \"_outcome\")\n            and getattr(self._outcome, \"errors\", None)\n        ):\n            try:\n                exc_message = self._outcome.errors[-1][1][1]\n            except Exception:\n                exc_message = \"(Unknown Exception)\"\n        else:\n            try:\n                exc_message = sys.last_value\n            except Exception:\n                exc_message = \"(Unknown Exception)\"\n        return str(exc_message)\n\n    def __insert_test_result(self, state, err):\n        from seleniumbase.core.testcase_manager import TestcaseDataPayload\n\n        data_payload = TestcaseDataPayload()\n        data_payload.runtime = int(time.time() * 1000.0) - self.case_start_time\n        data_payload.guid = self.testcase_guid\n        data_payload.execution_guid = self.execution_guid\n        data_payload.state = state\n        if err:\n            import traceback\n            tb_string = traceback.format_exc()\n            if \"Message: \" in tb_string:\n                data_payload.message = (\n                    \"Message: \" + tb_string.split(\"Message: \")[-1]\n                )\n            elif \"Exception: \" in tb_string:\n                data_payload.message = tb_string.split(\"Exception: \")[-1]\n            elif \"Error: \" in tb_string:\n                data_payload.message = tb_string.split(\"Error: \")[-1]\n            else:\n                data_payload.message = self.__get_exception_info()\n        else:\n            test_id = self.__get_test_id_2()\n            if (\n                self.is_pytest\n                and test_id in sb_config._results.keys()\n                and (sb_config._results[test_id] == \"Skipped\")\n            ):\n                if self.__skip_reason:\n                    data_payload.message = \"Skipped:   \" + self.__skip_reason\n                else:\n                    data_payload.message = \"Skipped:   (no reason given)\"\n        self.testcase_manager.update_testcase_data(data_payload)\n\n    def _add_pytest_html_extra(self):\n        if not (\n            (python3_11_or_newer and py311_patch2)\n            or \"--pdb\" in sys.argv\n        ):\n            return\n        self.__add_pytest_html_extra()\n\n    def __add_pytest_html_extra(self):\n        if not self.__added_pytest_html_extra:\n            with suppress(Exception):\n                if self.with_selenium:\n                    if not self.__last_page_screenshot:\n                        self.__set_last_page_screenshot()\n                        self.__set_last_page_url()\n                        self.__set_last_page_source()\n                    if self.report_on:\n                        extra_url = {}\n                        extra_url[\"name\"] = \"URL\"\n                        extra_url[\"format\"] = \"url\"\n                        extra_url[\"format_type\"] = \"url\"\n                        extra_url[\"content\"] = self.__last_page_url\n                        extra_url[\"mime_type\"] = None\n                        extra_url[\"extension\"] = None\n                        extra_image = {}\n                        extra_image[\"name\"] = \"Screenshot\"\n                        extra_image[\"format\"] = \"image\"\n                        extra_image[\"format_type\"] = \"image\"\n                        extra_image[\"content\"] = self.__last_page_screenshot\n                        extra_image[\"mime_type\"] = \"image/png\"\n                        extra_image[\"extension\"] = \"png\"\n                        self.__added_pytest_html_extra = True\n                        if self.__last_page_screenshot != (\n                            constants.Warnings.SCREENSHOT_UNDEFINED\n                        ):\n                            self._html_report_extra.append(extra_url)\n                            self._html_report_extra.append(extra_image)\n\n    def __delay_driver_quit(self):\n        delay_driver_quit = False\n        if (\n            getattr(self, \"_using_sb_fixture\", None)\n            and \"--pdb\" in sys.argv\n            and self.__has_exception()\n            and len(self._drivers_list) == 1\n            and self.driver == self._default_driver\n        ):\n            # Special case: Using sb fixture, --pdb, and has error.\n            # Keep the driver open for debugging and quit it later.\n            delay_driver_quit = True\n        return delay_driver_quit\n\n    def __quit_all_drivers(self):\n        if self._reuse_session and sb_config.shared_driver:\n            if len(self._drivers_list) > 0:\n                if self._drivers_list[0] != sb_config.shared_driver:\n                    if sb_config.shared_driver in self._drivers_list:\n                        self._drivers_list.remove(sb_config.shared_driver)\n                    self._drivers_list.insert(0, sb_config.shared_driver)\n                self._default_driver = self._drivers_list[0]\n                self.switch_to_default_driver()\n            if len(self._drivers_list) > 1:\n                self._drivers_list = self._drivers_list[1:]\n            else:\n                self._drivers_list = []\n        # Close all open browser windows\n        delay_driver_quit = self.__delay_driver_quit()\n        self._drivers_list.reverse()  # Last In, First Out\n        for driver in self._drivers_list:\n            try:\n                if (\n                    not is_windows\n                    or self.browser == \"ie\"\n                    or self.servername != \"localhost\"\n                    or (\n                        hasattr(driver, \"service\")\n                        and driver.service.process\n                    )\n                ):\n                    if not delay_driver_quit:\n                        driver.quit()\n                    else:\n                        # Save it for later to quit it later.\n                        sb_config._sb_pdb_driver = driver\n            except AttributeError:\n                pass\n            except Exception:\n                pass\n        if not delay_driver_quit:\n            self.driver = None\n            self._default_driver = None\n            self._drivers_list = []\n\n    def __has_exception(self):\n        has_exception = False\n        if hasattr(sys, \"last_traceback\") and sys.last_traceback is not None:\n            has_exception = True\n        elif getattr(self, \"is_context_manager\", None):\n            if self.with_testing_base and self._has_failure:\n                return True\n            else:\n                return False\n        elif hasattr(self, \"_outcome\") and hasattr(self._outcome, \"errors\"):\n            if python3_11_or_newer:\n                if (\n                    self._outcome.errors\n                    and self._outcome.errors[-1]\n                    and self._outcome.errors[-1][1]\n                ):\n                    has_exception = True\n            else:\n                if self._outcome.errors:\n                    has_exception = True\n        else:\n            has_exception = sys.exc_info()[1] is not None\n        if self.__will_be_skipped and hasattr(self, \"_using_sb_fixture\"):\n            has_exception = False\n        return has_exception\n\n    def __get_test_id(self):\n        \"\"\"The id used in various places such as the test log path.\"\"\"\n        if getattr(self, \"is_behave\", None):\n            file_name = sb_config.behave_scenario.filename\n            file_name = file_name.replace(\"/\", \".\").replace(\"\\\\\", \".\")\n            scenario_name = sb_config.behave_scenario.name\n            if \" -- @\" in scenario_name:\n                scenario_name = scenario_name.split(\" # \")[0].rstrip()\n            scenario_name = re.sub(r\"[^\\w\" + r\"_ \" + r\"]\", \"\", scenario_name)\n            scenario_name = scenario_name.replace(\" \", \"_\")\n            test_id = \"%s.%s\" % (file_name, scenario_name)\n            return test_id\n        elif getattr(self, \"is_context_manager\", None):\n            if hasattr(self, \"_manager_saved_id\"):\n                self.__saved_id = self._manager_saved_id\n            if self.__saved_id:\n                return self.__saved_id\n            filename = self.__class__.__module__.split(\".\")[-1] + \".py\"\n            methodname = self._testMethodName\n            context_id = None\n            if filename == \"base_case.py\" or methodname == \"runTest\":\n                import traceback\n                stack_base = traceback.format_stack()[0].split(os.sep)[-1]\n                test_base = stack_base.split(\", in \")[0]\n                if getattr(self, \"cm_filename\", None):\n                    filename = self.cm_filename\n                else:\n                    filename = test_base.split('\"')[0]\n                methodname = \".line_\" + test_base.split(\", line \")[-1]\n                context_id = filename.split(\".\")[0] + methodname\n                self.__saved_id = context_id\n                return context_id\n        test_id = \"%s.%s.%s\" % (\n            self.__class__.__module__,\n            self.__class__.__name__,\n            self._testMethodName,\n        )\n        if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:\n            test_id = self._sb_test_identifier\n        elif getattr(self, \"_using_sb_fixture\", None):\n            test_id = sb_config._latest_display_id\n        test_id = test_id.replace(\".py::\", \".\").replace(\"::\", \".\")\n        test_id = test_id.replace(\"/\", \".\").replace(\"\\\\\", \".\")\n        test_id = test_id.replace(\" \", \"_\")\n        # Linux filename length limit for `open(filename)` = 255\n        # 255 - len(\"latest_logs/\") - len(\"/basic_test_info.txt\") = 223\n        if len(test_id) <= 223:\n            return test_id\n        else:\n            # 223 - len(\"__TRUNCATED__\") = 210\n            # 210 / 2 = 105\n            return test_id[:105] + \"__TRUNCATED__\" + test_id[-105:]\n\n    def __get_test_id_2(self):\n        \"\"\"The id for SeleniumBase Dashboard entries.\"\"\"\n        if \"PYTEST_CURRENT_TEST\" in os.environ:\n            full_name = os.environ[\"PYTEST_CURRENT_TEST\"]\n            if \"] \" in full_name:\n                return full_name.split(\"] \")[0] + \"]\"\n            else:\n                return full_name.split(\" \")[0]\n        if getattr(self, \"is_behave\", None):\n            return self.__get_test_id()\n        test_id = \"%s.%s.%s\" % (\n            self.__class__.__module__.split(\".\")[-1],\n            self.__class__.__name__,\n            self._testMethodName,\n        )\n        if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:\n            test_id = self._sb_test_identifier\n            if test_id.count(\".\") > 1:\n                test_id = \".\".join(test_id.split(\".\")[1:])\n        return test_id\n\n    def __get_display_id(self):\n        \"\"\"The id for running a test from pytest. (Displayed on Dashboard)\"\"\"\n        if \"PYTEST_CURRENT_TEST\" in os.environ:\n            full_name = os.environ[\"PYTEST_CURRENT_TEST\"]\n            if \"] \" in full_name:\n                return full_name.split(\"] \")[0] + \"]\"\n            else:\n                return full_name.split(\" \")[0]\n        if getattr(self, \"is_behave\", None):\n            file_name = sb_config.behave_scenario.filename\n            line_num = sb_config.behave_line_num\n            scenario_name = sb_config.behave_scenario.name\n            if \" -- @\" in scenario_name:\n                scenario_name = scenario_name.split(\" # \")[0].rstrip()\n            test_id = \"%s:%s => %s\" % (file_name, line_num, scenario_name)\n            return test_id\n        test_id = \"%s.py::%s::%s\" % (\n            self.__class__.__module__.replace(\".\", \"/\"),\n            self.__class__.__name__,\n            self._testMethodName,\n        )\n        if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:\n            test_id = self._sb_test_identifier\n            if hasattr(self, \"_using_sb_fixture_class\"):\n                if test_id.count(\".\") >= 2:\n                    parts = test_id.split(\".\")\n                    full = parts[-3] + \".py::\" + parts[-2] + \"::\" + parts[-1]\n                    test_id = full\n            elif hasattr(self, \"_using_sb_fixture_no_class\"):\n                if test_id.count(\".\") >= 1:\n                    parts = test_id.split(\".\")\n                    full = parts[-2] + \".py::\" + parts[-1]\n                    test_id = full\n        return test_id\n\n    def __get_filename(self):\n        \"\"\"The filename of the current SeleniumBase test. (NOT Path)\"\"\"\n        filename = None\n        if \"PYTEST_CURRENT_TEST\" in os.environ:\n            test_id = os.environ[\"PYTEST_CURRENT_TEST\"].split(\" \")[0]\n            filename = test_id.split(\"::\")[0].split(\"/\")[-1]\n        elif getattr(self, \"is_behave\", None):\n            filename = sb_config.behave_scenario.filename\n            filename = filename.split(\"/\")[-1].split(\"\\\\\")[-1]\n        else:\n            filename = self.__class__.__module__.split(\".\")[-1] + \".py\"\n        return filename\n\n    def __create_log_path_as_needed(self, test_logpath):\n        if not os.path.exists(test_logpath):\n            try:\n                os.makedirs(test_logpath)\n            except Exception:\n                pass  # Only reachable during multi-threaded runs\n\n    def _process_dashboard_entry(self, has_exception, init=False):\n        if self._multithreaded:\n            self.dash_lock = fasteners.InterProcessLock(\n                constants.Dashboard.LOCKFILE\n            )\n            with self.dash_lock:\n                with suppress(Exception):\n                    shared_utils.make_writable(constants.Dashboard.LOCKFILE)\n                self.__process_dashboard(has_exception, init)\n        else:\n            self.__process_dashboard(has_exception, init)\n\n    def __process_dashboard(self, has_exception, init=False):\n        \"\"\"SeleniumBase Dashboard Processing\"\"\"\n        if (\n            self.is_pytest\n            and \"--pdb\" in sys.argv\n            and has_exception\n        ):\n            sb_config._pdb_failure = True\n        elif (\n            self.is_pytest\n            and getattr(sb_config, \"_pdb_failure\", None)\n            and not has_exception\n        ):\n            return  # Handle case where \"pytest --pdb\" marks failures as Passed\n        if self._multithreaded:\n            existing_res = sb_config._results  # For recording \"Skipped\" tests\n            abs_path = os.path.abspath(\".\")\n            dash_json_loc = constants.Dashboard.DASH_JSON\n            dash_jsonpath = os.path.join(abs_path, dash_json_loc)\n            if not init and os.path.exists(dash_jsonpath):\n                with open(dash_jsonpath, \"r\") as f:\n                    dash_json = f.read().strip()\n                dash_data, d_id, dash_rt, tlp, d_stats = json.loads(dash_json)\n                num_passed, num_failed, num_skipped, num_untested = d_stats\n                sb_config._results = dash_data\n                sb_config._display_id = d_id\n                sb_config._duration = dash_rt  # Dashboard Run Time\n                sb_config._d_t_log_path = tlp  # Test Log Path\n                sb_config.item_count_passed = num_passed\n                sb_config.item_count_failed = num_failed\n                sb_config.item_count_skipped = num_skipped\n                sb_config.item_count_untested = num_untested\n        if len(sb_config._extra_dash_entries) > 0:\n            # First take care of existing entries from non-SeleniumBase tests\n            for test_id in sb_config._extra_dash_entries:\n                if test_id in sb_config._results.keys():\n                    if sb_config._results[test_id] == \"Skipped\":\n                        sb_config.item_count_skipped += 1\n                        sb_config.item_count_untested -= 1\n                    elif sb_config._results[test_id] == \"Failed\":\n                        sb_config.item_count_failed += 1\n                        sb_config.item_count_untested -= 1\n                    elif sb_config._results[test_id] == \"Passed\":\n                        sb_config.item_count_passed += 1\n                        sb_config.item_count_untested -= 1\n                    else:  # Mark \"Skipped\" if unknown\n                        sb_config.item_count_skipped += 1\n                        sb_config.item_count_untested -= 1\n            sb_config._extra_dash_entries = []  # Reset the list to empty\n        # Process new entries\n        log_dir = self.log_path\n        ft_id = self.__get_test_id()  # Full test id with path to log files\n        test_id = self.__get_test_id_2()  # The test id used by the DashBoard\n        dud = \"seleniumbase/plugins/pytest_plugin.py::BaseClass::base_method\"\n        dud2 = \"pytest_plugin.BaseClass.base_method\"\n        if hasattr(self, \"_using_sb_fixture\") and self.__will_be_skipped:\n            test_id = sb_config._test_id\n        if not init:\n            duration_ms = int(time.time() * 1000.0) - self.__start_time_ms\n            duration = float(duration_ms) / 1000.0\n            duration = \"{:.2f}\".format(duration)\n            sb_config._duration[test_id] = duration\n            if (\n                has_exception\n                or self.save_screenshot_after_test\n                or self.__screenshot_count > 0\n                or self.__logs_data_count > 0\n                or self.__level_0_visual_f\n                or self.__will_be_skipped\n            ):\n                sb_config._d_t_log_path[test_id] = os.path.join(log_dir, ft_id)\n            else:\n                sb_config._d_t_log_path[test_id] = None\n            if test_id not in sb_config._display_id.keys():\n                sb_config._display_id[test_id] = self.__get_display_id()\n            if sb_config._display_id[test_id] == dud:\n                return\n            if (\n                hasattr(self, \"_using_sb_fixture\")\n                and test_id not in sb_config._results.keys()\n            ):\n                if test_id.count(\".\") > 1:\n                    alt_test_id = \".\".join(test_id.split(\".\")[1:])\n                    if alt_test_id in sb_config._results.keys():\n                        sb_config._results.pop(alt_test_id)\n                elif test_id.count(\".\") == 1:\n                    alt_test_id = sb_config._display_id[test_id]\n                    alt_test_id = alt_test_id.replace(\".py::\", \".\")\n                    alt_test_id = alt_test_id.replace(\"::\", \".\")\n                    alt_test_id = alt_test_id.replace(\" \", \"_\")\n                    if alt_test_id in sb_config._results.keys():\n                        sb_config._results.pop(alt_test_id)\n            if test_id in sb_config._results.keys() and (\n                sb_config._results[test_id] == \"Skipped\"\n            ):\n                if self.__passed_then_skipped:\n                    # Multiple calls of setUp() and tearDown() in the same test\n                    sb_config.item_count_passed -= 1\n                    sb_config.item_count_untested += 1\n                    self.__passed_then_skipped = False\n                sb_config._results[test_id] = \"Skipped\"\n                sb_config.item_count_skipped += 1\n                sb_config.item_count_untested -= 1\n            elif (\n                self._multithreaded\n                and test_id in existing_res.keys()\n                and existing_res[test_id] == \"Skipped\"\n            ):\n                sb_config._results[test_id] = \"Skipped\"\n                sb_config.item_count_skipped += 1\n                sb_config.item_count_untested -= 1\n            elif has_exception:\n                if test_id not in sb_config._results.keys():\n                    sb_config._results[test_id] = \"Failed\"\n                    sb_config.item_count_failed += 1\n                    sb_config.item_count_untested -= 1\n                elif not sb_config._results[test_id] == \"Failed\":\n                    # tearDown() was called more than once in the test\n                    if sb_config._results[test_id] == \"Passed\":\n                        # Passed earlier, but last run failed\n                        sb_config._results[test_id] = \"Failed\"\n                        sb_config.item_count_failed += 1\n                        sb_config.item_count_passed -= 1\n                    else:\n                        sb_config._results[test_id] = \"Failed\"\n                        sb_config.item_count_failed += 1\n                        sb_config.item_count_untested -= 1\n                else:\n                    # pytest-rerunfailures caused a duplicate failure\n                    sb_config._results[test_id] = \"Failed\"\n            else:\n                if (\n                    test_id in sb_config._results.keys()\n                    and sb_config._results[test_id] == \"Failed\"\n                ):\n                    # pytest-rerunfailures reran a test that failed\n                    sb_config._d_t_log_path[test_id] = os.path.join(\n                        log_dir, ft_id\n                    )\n                    sb_config.item_count_failed -= 1\n                    sb_config.item_count_untested += 1\n                elif (\n                    test_id in sb_config._results.keys()\n                    and sb_config._results[test_id] == \"Passed\"\n                ):\n                    # tearDown() was called more than once in the test\n                    sb_config.item_count_passed -= 1\n                    sb_config.item_count_untested += 1\n                sb_config._results[test_id] = \"Passed\"\n                sb_config.item_count_passed += 1\n                sb_config.item_count_untested -= 1\n        else:\n            pass  # Only initialize the Dashboard on the first processing\n        num_passed = sb_config.item_count_passed\n        num_failed = sb_config.item_count_failed\n        num_skipped = sb_config.item_count_skipped\n        num_untested = sb_config.item_count_untested\n        self.create_pie_chart(title=constants.Dashboard.TITLE)\n        self.add_data_point(\"Passed\", num_passed, color=\"#84d474\")\n        self.add_data_point(\"Untested\", num_untested, color=\"#eaeaea\")\n        self.add_data_point(\"Skipped\", num_skipped, color=\"#efd8b4\")\n        self.add_data_point(\"Failed\", num_failed, color=\"#f17476\")\n        style = (\n            '<link rel=\"stylesheet\" charset=\"utf-8\" '\n            'href=\"%s\">' % constants.Dashboard.STYLE_CSS\n        )\n        auto_refresh_html = \"\"\n        if num_untested > 0:\n            # Refresh every X seconds when waiting for more test results\n            auto_refresh_html = constants.Dashboard.META_REFRESH_HTML\n        else:\n            # The tests are complete\n            if sb_config._using_html_report:\n                # Add the pie chart to the pytest html report\n                sb_config._saved_dashboard_pie = self.extract_chart()\n                if self._multithreaded:\n                    abs_path = os.path.abspath(\".\")\n                    dash_pie = json.dumps(sb_config._saved_dashboard_pie)\n                    dash_pie_loc = constants.Dashboard.DASH_PIE\n                    pie_path = os.path.join(abs_path, dash_pie_loc)\n                    pie_file = open(pie_path, mode=\"w+\", encoding=\"utf-8\")\n                    pie_file.writelines(dash_pie)\n                    pie_file.close()\n        DASH_PIE_PNG_1 = constants.Dashboard.get_dash_pie_1()\n        head = (\n            '<head><meta charset=\"utf-8\">'\n            '<meta name=\"viewport\" content=\"shrink-to-fit=no\">'\n            '<link rel=\"shortcut icon\" href=\"%s\">'\n            \"%s\"\n            \"<title>Dashboard</title>\"\n            \"%s</head>\"\n            % (DASH_PIE_PNG_1, auto_refresh_html, style)\n        )\n        table_html = (\n            \"<div></div>\"\n            '<table border=\"1px solid #e6e6e6;\" width=\"100%;\" padding: 5px;'\n            ' font-size=\"12px;\" text-align=\"left;\" id=\"results-table\">'\n            '<thead id=\"results-table-head\">'\n            '<tr style=\"background-color: #F7F7FD;\">'\n            '<th col=\"result\">Result</th><th col=\"name\">Test</th>'\n            '<th col=\"duration\">Duration</th><th col=\"links\">Links</th>'\n            \"</tr></thead>\"\n        )\n        the_failed = []\n        the_skipped = []\n        the_passed_hl = []  # Passed and has logs\n        the_passed_nl = []  # Passed and no logs\n        the_untested = []\n        if dud2 in sb_config._results.keys():\n            sb_config._results.pop(dud2)\n        for key in sb_config._results.keys():\n            t_res = sb_config._results[key]\n            t_dur = sb_config._duration[key]\n            t_d_id = sb_config._display_id[key]\n            t_l_path = sb_config._d_t_log_path[key]\n            res_low = t_res.lower()\n            if sb_config._results[key] == \"Failed\":\n                if not sb_config._d_t_log_path[key]:\n                    sb_config._d_t_log_path[key] = os.path.join(log_dir, ft_id)\n                the_failed.append([res_low, t_res, t_d_id, t_dur, t_l_path])\n            elif sb_config._results[key] == \"Skipped\":\n                the_skipped.append([res_low, t_res, t_d_id, t_dur, t_l_path])\n            elif sb_config._results[key] == \"Passed\" and t_l_path:\n                the_passed_hl.append([res_low, t_res, t_d_id, t_dur, t_l_path])\n            elif sb_config._results[key] == \"Passed\" and not t_l_path:\n                the_passed_nl.append([res_low, t_res, t_d_id, t_dur, t_l_path])\n            elif sb_config._results[key] == \"Untested\":\n                the_untested.append([res_low, t_res, t_d_id, t_dur, t_l_path])\n        for row in the_failed:\n            row = (\n                '<tbody class=\"%s results-table-row\">'\n                '<tr style=\"background-color: #FFF8F8;\">'\n                '<td class=\"col-result\">%s</td><td>%s</td><td>%s</td>'\n                '<td><a href=\"%s\">Logs</a> / <a href=\"%s/\">Data</a>'\n                \"</td></tr></tbody>\"\n                \"\" % (row[0], row[1], row[2], row[3], log_dir, row[4])\n            )\n            table_html += row\n        for row in the_skipped:\n            if not row[4]:\n                row = (\n                    '<tbody class=\"%s results-table-row\">'\n                    '<tr style=\"background-color: #FEFEF9;\">'\n                    '<td class=\"col-result\">%s</td><td>%s</td><td>%s</td>'\n                    \"<td>-</td></tr></tbody>\"\n                    % (row[0], row[1], row[2], row[3])\n                )\n            else:\n                row = (\n                    '<tbody class=\"%s results-table-row\">'\n                    '<tr style=\"background-color: #FEFEF9;\">'\n                    '<td class=\"col-result\">%s</td><td>%s</td><td>%s</td>'\n                    '<td><a href=\"%s\">Logs</a> / <a href=\"%s/\">Data</a>'\n                    \"</td></tr></tbody>\"\n                    \"\" % (row[0], row[1], row[2], row[3], log_dir, row[4])\n                )\n            table_html += row\n        for row in the_passed_hl:\n            # Passed and has logs\n            row = (\n                '<tbody class=\"%s results-table-row\">'\n                '<tr style=\"background-color: #F8FFF8;\">'\n                '<td class=\"col-result\">%s</td><td>%s</td><td>%s</td>'\n                '<td><a href=\"%s\">Logs</a> / <a href=\"%s/\">Data</a>'\n                \"</td></tr></tbody>\"\n                \"\" % (row[0], row[1], row[2], row[3], log_dir, row[4])\n            )\n            table_html += row\n        for row in the_passed_nl:\n            # Passed and no logs\n            row = (\n                '<tbody class=\"%s results-table-row\">'\n                '<tr style=\"background-color: #F8FFF8;\">'\n                '<td class=\"col-result\">%s</td><td>%s</td><td>%s</td>'\n                \"<td>-</td></tr></tbody>\" % (row[0], row[1], row[2], row[3])\n            )\n            table_html += row\n        for row in the_untested:\n            row = (\n                '<tbody class=\"%s results-table-row\"><tr>'\n                '<td class=\"col-result\">%s</td><td>%s</td><td>%s</td>'\n                \"<td>-</td></tr></tbody>\" % (row[0], row[1], row[2], row[3])\n            )\n            table_html += row\n        table_html += \"</table>\"\n        add_more = \"<br /><b>Last updated:</b> \"\n        timestamp, the_date, the_time = log_helper.get_master_time()\n        last_updated = \"%s at %s\" % (the_date, the_time)\n        add_more = add_more + \"%s\" % last_updated\n        status = \"<p></p><div><b>Status:</b> Awaiting results...\"\n        status += \" (Refresh the page for updates)\"\n        if num_untested == 0:\n            status = \"<p></p><div><b>Status:</b> Test Run Complete:\"\n            if num_failed == 0:\n                if num_passed > 0:\n                    if num_skipped == 0:\n                        status += \" <b>Success!</b> (All tests passed)\"\n                    else:\n                        status += \" <b>Success!</b> (No failing tests)\"\n                else:\n                    status += \" All tests were skipped!\"\n            else:\n                latest_logs_dir = constants.Logs.LATEST + \"/\"\n                log_msg = \"See latest logs for details\"\n                if num_failed == 1:\n                    status += (\n                        \" <b>1 test failed!</b> --- \"\n                        '(<b><a href=\"%s\">%s</a></b>)'\n                        \"\" % (latest_logs_dir, log_msg)\n                    )\n                else:\n                    status += (\n                        \" <b>%s tests failed!</b> --- \"\n                        '(<b><a href=\"%s\">%s</a></b>)'\n                        \"\" % (num_failed, latest_logs_dir, log_msg)\n                    )\n        status += \"</div><p></p>\"\n        add_more = add_more + status\n        gen_by = (\n            '<p><div>Generated by: <b><a href=\"https://seleniumbase.io/\">'\n            \"SeleniumBase</a></b></div></p><p></p>\"\n        )\n        add_more = add_more + gen_by\n        # Have dashboard auto-refresh on updates when using an http server\n        refresh_line = (\n            '<script type=\"text/javascript\" src=\"%s\">'\n            \"</script>\" % constants.Dashboard.LIVE_JS\n        )\n        if num_untested == 0 and sb_config._using_html_report:\n            sb_config._dash_final_summary = status\n        add_more = add_more + refresh_line\n        the_html = (\n            '<html lang=\"en\">'\n            + head\n            + self.extract_chart()\n            + table_html\n            + add_more\n        )\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, \"dashboard.html\")\n        out_file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(the_html)\n        out_file.close()\n        sb_config._dash_html = the_html\n        if self._multithreaded:\n            d_stats = (num_passed, num_failed, num_skipped, num_untested)\n            _results = sb_config._results\n            _display_id = sb_config._display_id\n            _rt = sb_config._duration  # Run Time (RT)\n            _tlp = sb_config._d_t_log_path  # Test Log Path (TLP)\n            dash_json = json.dumps((_results, _display_id, _rt, _tlp, d_stats))\n            dash_json_loc = constants.Dashboard.DASH_JSON\n            dash_jsonpath = os.path.join(abs_path, dash_json_loc)\n            dash_json_file = open(dash_jsonpath, mode=\"w+\", encoding=\"utf-8\")\n            dash_json_file.writelines(dash_json)\n            dash_json_file.close()\n\n    def __activate_behave_post_mortem_debug_mode(self):\n        \"\"\"Activate Post Mortem Debug Mode for failing tests that use Behave\"\"\"\n        import pdb\n\n        pdb.post_mortem(sb_config.behave_step.exc_traceback)\n        # Post Mortem Debug Mode (\"behave -D pdb\")\n\n    def __activate_sb_mgr_post_mortem_debug_mode(self):\n        \"\"\"Activate Post Mortem Debug Mode for failing tests that use SB Mgr\"\"\"\n        import pdb\n\n        pdb.post_mortem()\n        # Post Mortem Debug Mode (\"python --pdb\")\n\n    def __activate_debug_mode_in_teardown(self):\n        \"\"\"Activate Final Trace / Debug Mode\"\"\"\n        import pdb\n\n        pdb.set_trace()\n        # Final Trace (\"--ftrace\")\n\n    def has_exception(self):\n        \"\"\"(This method should ONLY be used in custom tearDown() methods.)\n        This method returns True if the test failed or raised an exception.\n        This is useful for performing additional steps in your tearDown()\n        method (based on whether or not the test passed or failed).\n        Example use cases:\n            * Performing cleanup steps if a test didn't complete.\n            * Sending test data and/or results to a dashboard service.\n        \"\"\"\n        return self.__has_exception()\n\n    def save_teardown_screenshot(self):\n        \"\"\"(Should ONLY be used at the start of custom tearDown() methods.)\n        This method takes a screenshot of the active page for FAILING tests\n        (or when using \"--screenshot\" / \"--save-screenshot\" / \"--ss\").\n        That way your tearDown() method can navigate away from the last\n        page where the test failed, and still get the correct screenshot\n        before performing tearDown() steps on other pages. If this method\n        is not included in your custom tearDown() method, a screenshot\n        will still be taken after calling \"super().tearDown()\" there.\n        This method also saves recorded actions when using Recorder Mode.\n        \"\"\"\n        try:\n            self.__check_scope()\n        except Exception:\n            return\n        if getattr(self, \"recorder_mode\", None):\n            # In case tearDown() leaves the origin, save actions first.\n            self.save_recorded_actions()\n        if (\n            self.__has_exception()\n            or self.save_screenshot_after_test\n            or (python3_11_or_newer and py311_patch2)\n            or \"--pdb\" in sys.argv\n        ):\n            self.__set_last_page_screenshot()\n            self.__set_last_page_url()\n            self.__set_last_page_source()\n            if self.__has_exception() or self.save_screenshot_after_test:\n                if self.is_pytest:\n                    self.__add_pytest_html_extra()\n\n    def _log_fail_data(self):\n        if not (\n            (python3_11_or_newer and py311_patch2)\n            or \"--pdb\" in sys.argv\n        ):\n            return\n        test_id = self.__get_test_id()\n        test_logpath = os.path.join(self.log_path, test_id)\n        log_helper.log_test_failure_data(\n            self,\n            test_logpath,\n            self.driver,\n            self.browser,\n            self.__last_page_url,\n        )\n\n    def _get_browser_version(self):\n        driver_capabilities = None\n        if hasattr(self, \"driver\") and hasattr(self.driver, \"capabilities\"):\n            driver_capabilities = self.driver.capabilities\n        elif hasattr(sb_config, \"_browser_version\"):\n            return sb_config._browser_version\n        else:\n            return \"(Unknown Version)\"\n        if \"browserVersion\" in driver_capabilities:\n            browser_version = driver_capabilities[\"browserVersion\"]\n        else:\n            browser_version = \"(Unknown Version)\"\n        return browser_version\n\n    def _get_driver_name_and_version(self):\n        if not hasattr(self.driver, \"capabilities\"):\n            if hasattr(sb_config, \"_driver_name_version\"):\n                return sb_config._driver_name_version\n            else:\n                return None\n        driver = self.driver\n        if \"chrome\" in driver.capabilities:\n            cap_dict = driver.capabilities[\"chrome\"]\n            return (\n                \"chromedriver\", cap_dict[\"chromedriverVersion\"].split(\" \")[0]\n            )\n        elif \"msedge\" in driver.capabilities:\n            cap_dict = driver.capabilities[\"msedge\"]\n            return (\n                \"msedgedriver\", cap_dict[\"msedgedriverVersion\"].split(\" \")[0]\n            )\n        elif driver.capabilities[\"browserName\"].lower() == \"firefox\":\n            return (\n                \"geckodriver\", driver.capabilities[\"moz:geckodriverVersion\"]\n            )\n        elif driver.capabilities[\"browserName\"].lower() == \"safari\":\n            return (\"safaridriver\", self._get_browser_version())\n        elif driver.capabilities[\"browserName\"].lower() == \"internet explorer\":\n            return (\"iedriver\", self._get_browser_version())\n        else:\n            return None\n\n    def _get_num_handles(self):\n        return len(self.driver.window_handles)\n\n    def _get_rec_shift_esc_script(self):\n        return (\n            \"\"\"document.onkeydown = function(evt) {\n                evt = evt || window.event;\n                var isEscape = false;\n                if (\"key\" in evt) {\n                    isEscape = (evt.key === \"Escape\" || evt.key === \"Esc\");\n                    last_key = evt.key;\n                } else {\n                    isEscape = (evt.keyCode === 27);\n                    last_key = evt.keyCode;\n                    if (last_key === 16) {\n                        last_key = \"Shift\";\n                    }\n                }\n                if (isEscape && document.sb_last_key === \"Shift\") {\n                    document.sb_esc_end = \"yes\";\n                }\n                document.sb_last_key = last_key;\n            };\"\"\"\n        )\n\n    def _addSkip(self, result, test_case, reason):\n        \"\"\"This method should NOT be called directly from tests.\"\"\"\n        addSkip = getattr(result, 'addSkip', None)\n        if addSkip is not None:\n            addSkip(test_case, reason)\n        else:\n            import warnings\n            warnings.warn(\n                \"TestResult has no addSkip method! Skips not reported!\",\n                RuntimeWarning, 2\n            )\n            result.addSuccess(test_case)\n\n    def _callTestMethod(self, method):\n        \"\"\"This method should NOT be called directly from tests.\"\"\"\n        method()\n\n    def run(self, result=None):\n        \"\"\"Overwrite the unittest run() method for Python 3.11 or newer.\n        This method should NOT be called directly from tests.\"\"\"\n        if not python3_11_or_newer:\n            return super().run(result=result)\n        if result is None:\n            result = self.defaultTestResult()\n            startTestRun = getattr(result, 'startTestRun', None)\n            stopTestRun = getattr(result, 'stopTestRun', None)\n            if startTestRun is not None:\n                startTestRun()\n        else:\n            stopTestRun = None\n        result.startTest(self)\n        try:\n            testMethod = getattr(self, self._testMethodName)\n            if (\n                getattr(self.__class__, \"__unittest_skip__\", False)\n                or getattr(testMethod, \"__unittest_skip__\", False)\n            ):\n                skip_why = (\n                    getattr(self.__class__, '__unittest_skip_why__', '')\n                    or getattr(testMethod, '__unittest_skip_why__', '')\n                )\n                self._addSkip(result, self, skip_why)\n                return result\n            expecting_failure = (\n                getattr(self, \"__unittest_expecting_failure__\", False)\n                or getattr(testMethod, \"__unittest_expecting_failure__\", False)\n            )\n            outcome = unittest_helper._Outcome(result)\n            try:\n                self._outcome = outcome\n                with outcome.testPartExecutor(self):\n                    self._callSetUp()\n                if outcome.success:\n                    outcome.expecting_failure = expecting_failure\n                    with outcome.testPartExecutor(self, isTest=True):\n                        self._callTestMethod(testMethod)\n                    outcome.expecting_failure = False\n                    with outcome.testPartExecutor(self):\n                        self._callTearDown()\n                self.doCleanups()\n                for test, reason in outcome.skipped:\n                    self._addSkip(result, test, reason)\n                for test, exc_info in outcome.errors:\n                    if exc_info is not None:\n                        if issubclass(exc_info[0], self.failureException):\n                            result.addFailure(test, exc_info)\n                        else:\n                            result.addError(test, exc_info)\n                if outcome.success:\n                    if expecting_failure:\n                        if outcome.expectedFailure:\n                            self._addExpectedFailure(\n                                result, outcome.expectedFailure\n                            )\n                        else:\n                            self._addUnexpectedSuccess(result)\n                    else:\n                        result.addSuccess(self)\n                return result\n            finally:\n                outcome.errors.clear()\n                outcome.expectedFailure = None\n                self._outcome = None\n        finally:\n            result.stopTest(self)\n            if stopTestRun is not None:\n                stopTestRun()\n\n    def tearDown(self):\n        \"\"\"This method runs after every test completes.\n        Be careful if a subclass of BaseCase overrides setUp().\n        If so, add the following line to the subclass's tearDown() method:\n        super().tearDown() \"\"\"\n        if not hasattr(self, \"_using_sb_fixture\") and self.__called_teardown:\n            # This test already called tearDown()\n            return\n        if getattr(self, \"recorder_mode\", None):\n            page_actions._reconnect_if_disconnected(self.driver)\n            try:\n                self.__process_recorded_actions()\n            except Exception as e:\n                print(\"\\n (Recorder) Code-generation exception:\")\n                if hasattr(e, \"msg\"):\n                    print(\"\\n\" + str(e.msg))\n                else:\n                    print(e)\n        self.__called_teardown = True\n        self.__called_setup = False\n        try:\n            is_pytest = self.is_pytest  # This fails if overriding setUp()\n            if is_pytest:\n                with_selenium = self.with_selenium\n        except Exception:\n            sub_class_name = (\n                str(self.__class__.__bases__[0]).split(\".\")[-1].split(\"'\")[0]\n            )\n            sub_file_name = str(self.__class__.__bases__[0]).split(\".\")[-2]\n            sub_file_name = sub_file_name + \".py\"\n            class_name = str(self.__class__).split(\".\")[-1].split(\"'\")[0]\n            file_name = str(self.__class__).split(\".\")[-2] + \".py\"\n            class_name_used = sub_class_name\n            file_name_used = sub_file_name\n            if sub_class_name == \"BaseCase\":\n                class_name_used = class_name\n                file_name_used = file_name\n            message = (\n                \"You're overriding SeleniumBase's BaseCase setUp() \"\n                \"method with your own setUp() method, which breaks \"\n                \"SeleniumBase. You can fix this by going to your \"\n                \"%s class located in %s, and adding the \"\n                \"following line AT THE BEGINNING of your \"\n                \"setUp() method:\\nsuper().setUp()\\n\\n\"\n                \"Also make sure the following line is AT THE END \"\n                \"of your tearDown() method:\\nsuper().tearDown()\\n\"\n                % (class_name_used, file_name_used)\n            )\n            raise Exception(message)\n        # *** Start tearDown() officially ***\n        page_actions._reconnect_if_disconnected(self.driver)\n        self.__slow_mode_pause_if_active()\n        has_exception = self.__has_exception()\n        sb_config._has_exception = has_exception\n        sb_config._browser_version = self._get_browser_version()\n        sb_config._driver_name_version = self._get_driver_name_and_version()\n\n        if self.__overrided_default_timeouts:\n            # Reset default timeouts in case there are more tests\n            # These were changed in set_default_timeout()\n            if sb_config._SMALL_TIMEOUT and sb_config._LARGE_TIMEOUT:\n                settings.SMALL_TIMEOUT = sb_config._SMALL_TIMEOUT\n                settings.LARGE_TIMEOUT = sb_config._LARGE_TIMEOUT\n                sb_config._is_timeout_changed = False\n                self.__overrided_default_timeouts = False\n        deferred_exception = None\n        if self.__deferred_assert_failures:\n            print(\n                \"\\nWhen using self.deferred_assert_*() methods in your tests, \"\n                \"remember to call self.process_deferred_asserts() afterwards. \"\n                \"Now calling in tearDown()...\\nFailures Detected:\"\n            )\n            if not has_exception:\n                try:\n                    self.process_deferred_asserts()\n                except Exception as e:\n                    deferred_exception = e\n            else:\n                self.process_deferred_asserts(print_only=True)\n        if self.is_pytest:\n            # pytest-specific code\n            test_id = self.__get_test_id()\n            if with_selenium:\n                # Save a screenshot if logging is on when an exception occurs\n                if has_exception:\n                    self.__add_pytest_html_extra()\n                    sb_config._has_exception = True\n                    sb_config._has_logs = True\n                if (\n                    self.with_testing_base\n                    and not has_exception\n                    and self.save_screenshot_after_test\n                ):\n                    test_logpath = os.path.join(self.log_path, test_id)\n                    self.__create_log_path_as_needed(test_logpath)\n                    if not self.__last_page_screenshot_png:\n                        self.__set_last_page_screenshot()\n                        self.__set_last_page_url()\n                        self.__set_last_page_source()\n                    log_helper.log_screenshot(\n                        test_logpath,\n                        self.driver,\n                        self.__last_page_screenshot_png,\n                    )\n                    self.__add_pytest_html_extra()\n                    sb_config._has_logs = True\n                elif (\n                    (\n                        (python3_11_or_newer and py311_patch2)\n                        or \"--pdb\" in sys.argv\n                    )\n                    and not has_exception\n                ):\n                    # Handle a bug where exceptions aren't seen\n                    self.__set_last_page_screenshot()\n                    self.__set_last_page_url()\n                    self.__set_last_page_source()\n                if self.with_testing_base and has_exception:\n                    test_logpath = os.path.join(self.log_path, test_id)\n                    self.__create_log_path_as_needed(test_logpath)\n                    if (\n                        not self.with_screen_shots\n                        and not self.with_basic_test_info\n                        and not self.with_page_source\n                    ):\n                        # Log everything if nothing specified (if testing_base)\n                        if not self.__last_page_screenshot_png:\n                            self.__set_last_page_screenshot()\n                            self.__set_last_page_url()\n                            self.__set_last_page_source()\n                        log_helper.log_screenshot(\n                            test_logpath,\n                            self.driver,\n                            self.__last_page_screenshot_png,\n                        )\n                        log_helper.log_test_failure_data(\n                            self,\n                            test_logpath,\n                            self.driver,\n                            self.browser,\n                            self.__last_page_url,\n                        )\n                        log_helper.log_page_source(\n                            test_logpath, self.driver, self.__last_page_source\n                        )\n                    else:\n                        if self.with_screen_shots:\n                            if not self.__last_page_screenshot_png:\n                                self.__set_last_page_screenshot()\n                                self.__set_last_page_url()\n                                self.__set_last_page_source()\n                            log_helper.log_screenshot(\n                                test_logpath,\n                                self.driver,\n                                self.__last_page_screenshot_png,\n                            )\n                        if self.with_basic_test_info:\n                            log_helper.log_test_failure_data(\n                                self,\n                                test_logpath,\n                                self.driver,\n                                self.browser,\n                                self.__last_page_url,\n                            )\n                        if self.with_page_source:\n                            log_helper.log_page_source(\n                                test_logpath,\n                                self.driver,\n                                self.__last_page_source,\n                            )\n                if self.dashboard:\n                    if self._multithreaded:\n                        with self.dash_lock:\n                            with suppress(Exception):\n                                shared_utils.make_writable(\n                                    constants.Dashboard.LOCKFILE\n                                )\n                            self.__process_dashboard(has_exception)\n                    else:\n                        self.__process_dashboard(has_exception)\n                if self._final_debug:\n                    self.__activate_debug_mode_in_teardown()\n                # (Pytest) Finally close all open browser windows\n                self.__quit_all_drivers()\n            if self.with_db_reporting:\n                if has_exception:\n                    self.__insert_test_result(constants.State.FAILED, True)\n                else:\n                    test_id = self.__get_test_id_2()\n                    if test_id in sb_config._results.keys() and (\n                        sb_config._results[test_id] == \"Skipped\"\n                    ):\n                        self.__insert_test_result(\n                            constants.State.SKIPPED, False\n                        )\n                    else:\n                        self.__insert_test_result(\n                            constants.State.PASSED, False\n                        )\n                runtime = int(time.time() * 1000.0) - self.execution_start_time\n                self.testcase_manager.update_execution_data(\n                    self.execution_guid, runtime\n                )\n            if self.with_s3_logging and has_exception:\n                \"\"\"If enabled, upload logs to S3 during test exceptions.\"\"\"\n                import uuid\n                from seleniumbase.core.s3_manager import S3LoggingBucket\n\n                s3_bucket = S3LoggingBucket()\n                guid = str(uuid.uuid4().hex)\n                path = os.path.join(self.log_path, test_id)\n                uploaded_files = []\n                for logfile in os.listdir(path):\n                    logfile_name = \"%s/%s/%s\" % (\n                        guid,\n                        test_id,\n                        logfile.split(path)[-1],\n                    )\n                    s3_bucket.upload_file(\n                        logfile_name, \"%s\" % os.path.join(path, logfile)\n                    )\n                    uploaded_files.append(logfile_name)\n                s3_bucket.save_uploaded_file_names(uploaded_files)\n                index_file = s3_bucket.upload_index_file(\n                    test_id, guid, self.data_path, self.save_data_to_logs\n                )\n                print(\"\\n*** Log files uploaded: ***\\n%s\\n\" % index_file)\n                logging.info(\n                    \"\\n*** Log files uploaded: ***\\n%s\\n\" % index_file\n                )\n                if self.with_db_reporting:\n                    from seleniumbase.core.testcase_manager import (\n                        TestcaseDataPayload,\n                        TestcaseManager,\n                    )\n\n                    self.testcase_manager = TestcaseManager(self.database_env)\n                    data_payload = TestcaseDataPayload()\n                    data_payload.guid = self.testcase_guid\n                    data_payload.logURL = index_file\n                    self.testcase_manager.update_testcase_log_url(data_payload)\n        else:\n            # (Pynose / Behave / Pure Python)\n            if getattr(self, \"is_behave\", None):\n                if sb_config.behave_scenario.status.name == \"failed\":\n                    has_exception = True\n                    sb_config._has_exception = True\n                    msg = \"   ❌ Scenario Failed!  (Skipping remaining steps:)\"\n                    if is_windows:\n                        c1 = colorama.Fore.RED + colorama.Back.LIGHTRED_EX\n                        cr = colorama.Style.RESET_ALL\n                        msg = msg.replace(\"❌\", c1 + \"><\" + cr)\n                    print(msg)\n                else:\n                    msg = \"   ✅ Scenario Passed!\"\n                    if is_windows:\n                        c2 = colorama.Fore.GREEN + colorama.Back.LIGHTGREEN_EX\n                        cr = colorama.Style.RESET_ALL\n                        msg = msg.replace(\"✅\", c2 + \"<>\" + cr)\n                    print(msg)\n                if self.dashboard:\n                    self.__process_dashboard(has_exception)\n            if has_exception:\n                test_id = self.__get_test_id()\n                test_logpath = os.path.join(self.log_path, test_id)\n                self.__create_log_path_as_needed(test_logpath)\n                log_helper.log_test_failure_data(\n                    self,\n                    test_logpath,\n                    self.driver,\n                    self.browser,\n                    self.__last_page_url,\n                )\n                if len(self._drivers_list) > 0:\n                    if not self.__last_page_screenshot_png:\n                        self.__set_last_page_screenshot()\n                        self.__set_last_page_url()\n                        self.__set_last_page_source()\n                    log_helper.log_screenshot(\n                        test_logpath,\n                        self.driver,\n                        self.__last_page_screenshot_png,\n                    )\n                    log_helper.log_page_source(\n                        test_logpath, self.driver, self.__last_page_source\n                    )\n            elif self.save_screenshot_after_test:\n                test_id = self.__get_test_id()\n                test_logpath = os.path.join(self.log_path, test_id)\n                self.__create_log_path_as_needed(test_logpath)\n                if not self.__last_page_screenshot_png:\n                    self.__set_last_page_screenshot()\n                    self.__set_last_page_url()\n                    self.__set_last_page_source()\n                log_helper.log_screenshot(\n                    test_logpath, self.driver, self.__last_page_screenshot_png\n                )\n            if self.report_on:\n                self._last_page_screenshot = self.__last_page_screenshot_png\n                try:\n                    self._last_page_url = self.get_current_url()\n                except Exception:\n                    self._last_page_url = \"(Error: Unknown URL)\"\n            if getattr(self, \"is_behave\", None) and has_exception:\n                if getattr(sb_config, \"pdb_option\", None):\n                    if (\n                        hasattr(sb_config, \"behave_step\")\n                        and hasattr(sb_config.behave_step, \"exc_traceback\")\n                    ):\n                        self.__activate_behave_post_mortem_debug_mode()\n            if self._final_debug:\n                self.__activate_debug_mode_in_teardown()\n            elif getattr(sb_config, \"_do_sb_post_mortem\", None):\n                self.__activate_sb_mgr_post_mortem_debug_mode()\n            elif getattr(sb_config, \"_do_sb_final_trace\", None):\n                self.__activate_debug_mode_in_teardown()\n            # (Pynose / Behave / Pure Python) Close all open browser windows\n            self.__quit_all_drivers()\n        # Resume tearDown() for all test runners, (Pytest / Pynose / Behave)\n        if getattr(self, \"_xvfb_display\", None) and not self._reuse_session:\n            # Stop the Xvfb virtual display launched from BaseCase\n            try:\n                if hasattr(self._xvfb_display, \"stop\"):\n                    self._xvfb_display.stop()\n                self._xvfb_display = None\n                self.headless_active = False\n            except AttributeError:\n                pass\n            except Exception:\n                pass\n        if (\n            getattr(sb_config, \"_virtual_display\", None)\n            and hasattr(sb_config._virtual_display, \"stop\")\n            and not getattr(sb_config, \"reuse_session\", None)\n        ):\n            # CDP Mode may launch a 2nd Xvfb virtual display\n            try:\n                sb_config._virtual_display.stop()\n                sb_config._virtual_display = None\n                sb_config.headless_active = False\n            except AttributeError:\n                pass\n            except Exception:\n                pass\n        if self.__visual_baseline_copies:\n            sb_config._visual_baseline_copies = True\n            if has_exception:\n                self.__process_visual_baseline_logs()\n        if deferred_exception:\n            # User forgot to call \"self.process_deferred_asserts()\" in test\n            raise deferred_exception\n\n    @classmethod\n    def setUpClass(self):\n        # Only used when: \"--rcs\" / \"--reuse-class-session\"\n        # Close existing sessions before the class starts.\n        session_helper.end_reused_class_session_as_needed()\n\n    @classmethod\n    def tearDownClass(self):\n        # Only used when: \"--rcs\" / \"--reuse-class-session\"\n        # Close existing sessions after the class finishes.\n        session_helper.end_reused_class_session_as_needed()\n\n\nr\"\"\"----------------------------------------------------------------->\n|    ______     __           _                  ____                 |\n|   / ____/__  / /__  ____  (_)_  ______ ___   / _  \\____  ________  |\n|   \\__ \\/ _ \\/ / _ \\/ __ \\/ / / / / __ `__ \\ / /_) / __ \\/ ___/ _ \\ |\n|  ___/ /  __/ /  __/ / / / / /_/ / / / / / // /_) / (_/ /__  /  __/ |\n| /____/\\___/_/\\___/_/ /_/_/\\__,_/_/ /_/ /_//_____/\\__,_/____/\\___/  |\n|                                                                    |\n------------------------------------------------------------------>\"\"\"\n"
  },
  {
    "path": "seleniumbase/fixtures/constants.py",
    "content": "\"\"\"SeleniumBase constants\"\"\"\nfrom seleniumbase.core import encoded_images\n\n\nclass Environment:\n    # Usage Example => \"--env=qa\" => Then access value in tests with \"self.env\"\n    QA = \"qa\"\n    RC = \"rc\"\n    STAGING = \"staging\"\n    DEVELOP = \"develop\"\n    PRODUCTION = \"production\"\n    PERFORMANCE = \"performance\"\n    REPLICA = \"replica\"\n    FEDRAMP = \"fedramp\"\n    OFFLINE = \"offline\"\n    ONLINE = \"online\"\n    MASTER = \"master\"\n    REMOTE = \"remote\"\n    LEGACY = \"legacy\"\n    LOCAL = \"local\"\n    ALPHA = \"alpha\"\n    BETA = \"beta\"\n    DEMO = \"demo\"\n    GDPR = \"gdpr\"\n    MAIN = \"main\"\n    TEST = \"test\"\n    GOV = \"gov\"\n    NEW = \"new\"\n    OLD = \"old\"\n    UAT = \"uat\"\n\n\nclass ValidEnvs:\n    valid_envs = [\n        \"qa\",\n        \"rc\",\n        \"staging\",\n        \"develop\",\n        \"production\",\n        \"performance\",\n        \"replica\",\n        \"fedramp\",\n        \"offline\",\n        \"online\",\n        \"master\",\n        \"remote\",\n        \"legacy\",\n        \"local\",\n        \"alpha\",\n        \"beta\",\n        \"demo\",\n        \"gdpr\",\n        \"main\",\n        \"test\",\n        \"gov\",\n        \"new\",\n        \"old\",\n        \"uat\",\n    ]\n\n\nclass PatchPy311:\n    # Now that unittest is \"patched/fixed\" in Python 3.11 and up,\n    # this second patch might not be needed to fix error-handling.\n    # Enabling this might slow things slightly to fix some things.\n    PATCH = False\n\n\nclass PageLoadStrategy:\n    # Usage Example => \"--pls=none\"\n    NORMAL = \"normal\"\n    EAGER = \"eager\"\n    NONE = \"none\"\n\n\nclass Files:\n    # This is a special downloads folder for files downloaded by tests.\n    # The \"downloaded_files\" folder is DELETED when starting new tests.\n    # Add \"--archive-downloads\" to save a copy in \"archived_files\".\n    # (These folder names should NOT be changed.)\n    DOWNLOADS_FOLDER = \"downloaded_files\"\n    ARCHIVED_DOWNLOADS_FOLDER = \"archived_files\"\n\n\nclass Logs:\n    # This is where log files from the latest run get saved.\n    # The \"latest_logs\" folder is DELETED when starting new tests.\n    # Add \"--archive-logs\" to save a copy of logs in \"archived_logs\".\n    # (These folder names should NOT be changed.)\n    LATEST = \"latest_logs\"\n    SAVED = \"archived_logs\"\n\n\nclass Presentations:\n    SAVED_FOLDER = \"saved_presentations\"\n\n\nclass Charts:\n    SAVED_FOLDER = \"saved_charts\"\n\n\nclass Recordings:\n    SAVED_FOLDER = \"recordings\"\n\n\nclass Dashboard:\n    TITLE = \"SeleniumBase Dashboard ⚪\"\n    # STYLE_CSS = \"https://seleniumbase.io/cdn/css/pytest_style.css\"\n    STYLE_CSS = \"assets/pytest_style.css\"  # Generated before tests\n    META_REFRESH_HTML = '<meta http-equiv=\"refresh\" content=\"12\">'\n    # LIVE_JS = \"https://livejs.com/live.js#html\"\n    # LIVE_JS = \"https://seleniumbase.io/cdn/js/live.js#html\"\n    LIVE_JS = \"assets/live.js#html\"  # Generated before tests\n    LOCKFILE = Files.DOWNLOADS_FOLDER + \"/dashboard.lock\"\n    DASH_JSON = Files.DOWNLOADS_FOLDER + \"/dashboard.json\"\n    DASH_PIE = Files.DOWNLOADS_FOLDER + \"/dash_pie.json\"\n\n    def get_dash_pie_1():\n        if not hasattr(encoded_images, \"DASH_PIE_PNG_1\"):\n            encoded_images.DASH_PIE_PNG_1 = encoded_images.get_dash_pie_png1()\n        return encoded_images.DASH_PIE_PNG_1\n\n    def get_dash_pie_2():\n        if not hasattr(encoded_images, \"DASH_PIE_PNG_2\"):\n            encoded_images.DASH_PIE_PNG_2 = encoded_images.get_dash_pie_png2()\n        return encoded_images.DASH_PIE_PNG_2\n\n    def get_dash_pie_3():\n        if not hasattr(encoded_images, \"DASH_PIE_PNG_3\"):\n            encoded_images.DASH_PIE_PNG_3 = encoded_images.get_dash_pie_png3()\n        return encoded_images.DASH_PIE_PNG_3\n\n\nclass PipInstall:\n    # FINDLOCK - Checking to see if a package is installed\n    # (Make sure a package isn't installed multiple times)\n    FINDLOCK = Files.DOWNLOADS_FOLDER + \"/pipfinding.lock\"\n    # LOCKFILE - Locking before performing any pip install\n    # (Make sure that only one package installs at a time)\n    LOCKFILE = Files.DOWNLOADS_FOLDER + \"/pipinstall.lock\"\n\n\nclass Report:\n    def get_favicon():\n        if not hasattr(encoded_images, \"REPORT_FAVICON\"):\n            encoded_images.REPORT_FAVICON = encoded_images.get_report_favicon()\n        return encoded_images.REPORT_FAVICON\n\n\nclass SideBySide:\n    HTML_FILE = \"side_by_side.html\"\n\n    def get_favicon():\n        if not hasattr(encoded_images, \"SIDE_BY_SIDE_PNG\"):\n            encoded_images.SIDE_BY_SIDE_PNG = (\n                encoded_images.get_side_by_side_png()\n            )\n        return encoded_images.SIDE_BY_SIDE_PNG\n\n\nclass MultiBrowser:\n    DRIVER_FIXING_LOCK = Files.DOWNLOADS_FOLDER + \"/driver_fixing.lock\"\n    DRIVER_REPAIRED = Files.DOWNLOADS_FOLDER + \"/driver_fixed.lock\"\n    CERT_FIXING_LOCK = Files.DOWNLOADS_FOLDER + \"/cert_fixing.lock\"\n    DOWNLOAD_FILE_LOCK = Files.DOWNLOADS_FOLDER + \"/downloading.lock\"\n    FILE_IO_LOCK = Files.DOWNLOADS_FOLDER + \"/file_io.lock\"\n    PYAUTOGUILOCK = Files.DOWNLOADS_FOLDER + \"/pyautogui.lock\"\n\n\nclass SavedCookies:\n    STORAGE_FOLDER = \"saved_cookies\"\n\n\nclass Tours:\n    EXPORTED_TOURS_FOLDER = \"tours_exported\"\n\n\nclass VisualBaseline:\n    STORAGE_FOLDER = \"visual_baseline\"\n\n\nclass Values:\n    # Demo Mode has slow scrolling to see where you are on the page better.\n    # However, a regular slow scroll takes too long to cover big distances.\n    # If the scroll distance is greater than SSMD, a slow scroll speeds up.\n    SSMD = 500  # Smooth Scroll Minimum Distance (for advanced slow scroll)\n\n\nclass Scroll:\n    Y_OFFSET = 182\n\n\nclass Warnings:\n    SCREENSHOT_SKIPPED = \"Skipping screenshot!\"\n    SCREENSHOT_UNDEFINED = \"Unable to get screenshot!\"\n    PAGE_SOURCE_UNDEFINED = \"Unable to get page source!\"\n    INVALID_RUN_COMMAND = \"INVALID RUN COMMAND!\"\n\n\nclass JQuery:\n    VER = \"3.7.1\"\n    MIN_JS = \"https://cdn.jsdelivr.net/npm/jquery@%s/dist/jquery.min.js\" % VER\n\n\nclass Messenger:\n    LIB = \"https://cdn.jsdelivr.net/npm/messenger-hubspot\"\n    VER = \"1.5.0\"\n    THEME = \"messenger-theme\"\n    MIN_CSS = \"%s@%s/build/css/messenger.min.css\" % (LIB, VER)\n    MIN_JS = \"%s@%s/build/js/messenger.min.js\" % (LIB, VER)\n    THEME_FLAT_JS = \"%s@%s/build/js/%s-flat.min.js\" % (LIB, VER, THEME)\n    THEME_FUTURE_JS = \"%s@%s/build/js/%s-future.min.js\" % (LIB, VER, THEME)\n    THEME_FLAT_CSS = \"%s@%s/build/css/%s-flat.min.css\" % (LIB, VER, THEME)\n    THEME_FUTURE_CSS = \"%s@%s/build/css/%s-future.min.css\" % (LIB, VER, THEME)\n    THEME_BLOCK_CSS = \"%s@%s/build/css/%s-block.min.css\" % (LIB, VER, THEME)\n    THEME_AIR_CSS = \"%s@%s/build/css/%s-air.min.css\" % (LIB, VER, THEME)\n    THEME_ICE_CSS = \"%s@%s/build/css/%s-ice.min.css\" % (LIB, VER, THEME)\n    SPINNER_CSS = \"%s@%s/build/css/messenger-spinner.min.css\" % (LIB, VER)\n\n\nclass Underscore:\n    VER = \"1.13.6\"\n    MIN_JS = (\n        \"https://cdn.jsdelivr.net/npm/underscore@%s/underscore.min.js\" % VER\n    )\n\n\nclass Backbone:\n    VER = \"1.6.0\"\n    MIN_JS = \"https://cdn.jsdelivr.net/npm/backbone@%s/backbone.min.js\" % VER\n\n\nclass HtmlInspector:\n    VER = \"0.8.2\"\n    MIN_JS = (\n        \"https://cdnjs.cloudflare.com/ajax/libs/\"\n        \"html-inspector/%s/html-inspector.min.js\" % VER\n    )\n\n\nclass PrettifyJS:\n    RUN_PRETTIFY_JS = (\n        \"https://cdn.jsdelivr.net/gh/google/\"\n        \"code-prettify@master/loader/run_prettify.js\"\n    )\n\n\nclass Reveal:\n    LIB = \"https://cdn.jsdelivr.net/npm/reveal.js\"\n    VER = \"3.8.0\"\n    MIN_CSS = \"%s@%s/css/reveal.min.css\" % (LIB, VER)\n    SERIF_MIN_CSS = \"%s@%s/css/theme/serif.min.css\" % (LIB, VER)\n    WHITE_MIN_CSS = \"%s@%s/css/theme/white.min.css\" % (LIB, VER)\n    BLACK_MIN_CSS = \"%s@%s/css/theme/black.min.css\" % (LIB, VER)\n    SKY_MIN_CSS = \"%s@%s/css/theme/sky.min.css\" % (LIB, VER)\n    MOON_MIN_CSS = \"%s@%s/css/theme/moon.min.css\" % (LIB, VER)\n    NIGHT_MIN_CSS = \"%s@%s/css/theme/night.min.css\" % (LIB, VER)\n    LEAGUE_MIN_CSS = \"%s@%s/css/theme/league.min.css\" % (LIB, VER)\n    BEIGE_MIN_CSS = \"%s@%s/css/theme/beige.min.css\" % (LIB, VER)\n    BLOOD_MIN_CSS = \"%s@%s/css/theme/blood.min.css\" % (LIB, VER)\n    SIMPLE_MIN_CSS = \"%s@%s/css/theme/simple.min.css\" % (LIB, VER)\n    SOLARIZED_MIN_CSS = \"%s@%s/css/theme/solarized.min.css\" % (LIB, VER)\n    MIN_JS = \"%s@%s/js/reveal.min.js\" % (LIB, VER)\n\n\nclass HighCharts:\n    LIB = \"https://cdn.jsdelivr.net/npm/highcharts\"\n    VER = \"10.3.3\"\n    HC_CSS = \"%s@%s/css/highcharts.css\" % (LIB, VER)\n    HC_JS = \"%s@%s/highcharts.js\" % (LIB, VER)\n    EXPORTING_JS = \"%s@%s/modules/exporting.js\" % (LIB, VER)\n    EXPORT_DATA_JS = \"%s@%s/modules/export-data.js\" % (LIB, VER)\n    ACCESSIBILITY_JS = \"%s@%s/modules/accessibility.js\" % (LIB, VER)\n\n\nclass BootstrapTour:\n    LIB = \"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tour\"\n    VER = \"0.12.0\"\n    MIN_CSS = \"%s/%s/css/bootstrap-tour-standalone.min.css\" % (LIB, VER)\n    # MIN_JS = \"%s/%s/js/bootstrap-tour-standalone.min.js\" % (LIB, VER)\n    MIN_JS = \"https://seleniumbase.github.io/cdn/js/bootstraptour.min.js\"\n\n\nclass DriverJS:\n    LIB = \"https://cdn.jsdelivr.net/npm/driver.js\"\n    VER = \"0.9.8\"\n    MIN_CSS = \"%s@%s/dist/driver.min.css\" % (LIB, VER)\n    MIN_JS = \"%s@%s/dist/driver.min.js\" % (LIB, VER)\n\n\nclass Hopscotch:\n    LIB = \"https://cdn.jsdelivr.net/npm/hopscotch\"\n    VER = \"0.3.1\"\n    MIN_CSS = \"%s@%s/dist/css/hopscotch.min.css\" % (LIB, VER)\n    MIN_JS = \"%s@%s/dist/js/hopscotch.min.js\" % (LIB, VER)\n\n\nclass IntroJS:\n    VER = \"5.1.0\"\n    MIN_CSS = (\n        \"https://cdn.jsdelivr.net/npm/\"\n        \"intro.js@%s/minified/introjs.min.css\" % VER\n    )\n    MIN_JS = \"https://cdn.jsdelivr.net/npm/intro.js@%s/intro.min.js\" % VER\n\n\nclass TourColor:\n    # Used for button colors in IntroJS Tours\n    # theme_color = \"#f26721\"  # Orange\n    # hover_color = \"#db5409\"  # Darker Orange\n    theme_color = \"#367be5\"  # Blue\n    hover_color = \"#245ac0\"  # Darker Blue\n\n\nclass JqueryConfirm:\n    LIB = \"https://cdn.jsdelivr.net/npm/jquery-confirm\"\n    VER = \"3.3.4\"\n    MIN_CSS = \"%s@%s/css/jquery-confirm.min.css\" % (LIB, VER)\n    MIN_JS = \"%s@%s/js/jquery-confirm.min.js\" % (LIB, VER)\n    DEFAULT_THEME = \"bootstrap\"\n    DEFAULT_COLOR = \"blue\"\n    DEFAULT_WIDTH = \"38%\"\n\n\nclass Shepherd:\n    LIB = \"https://cdnjs.cloudflare.com/ajax/libs/shepherd\"\n    VER = \"1.8.1\"\n    MIN_JS = \"%s/%s/js/shepherd.min.js\" % (LIB, VER)\n    THEME_ARROWS_CSS = \"%s/%s/css/shepherd-theme-arrows.css\" % (LIB, VER)\n    THEME_ARR_FIX_CSS = \"%s/%s/css/shepherd-theme-arrows-fix.css\" % (LIB, VER)\n    THEME_DEFAULT_CSS = \"%s/%s/css/shepherd-theme-default.css\" % (LIB, VER)\n    THEME_DARK_CSS = \"%s/%s/css/shepherd-theme-dark.css\" % (LIB, VER)\n    THEME_SQ_CSS = \"%s/%s/css/shepherd-theme-square.css\" % (LIB, VER)\n    THEME_SQ_DK_CSS = \"%s/%s/css/shepherd-theme-square-dark.css\" % (LIB, VER)\n\n\nclass Tether:\n    VER = \"1.4.7\"\n    MIN_JS = (\n        \"https://cdn.jsdelivr.net/npm/tether@%s/dist/js/tether.min.js\" % VER\n    )\n\n\nclass ProxyPy:\n    # The version installed if proxy.py is not installed\n    VER = \"2.4.3\"\n\n\nclass SeleniumWire:\n    # The version installed if selenium-wire is not installed\n    VER = \"5.1.0\"\n    BLINKER_VER = \"1.7.0\"  # The \"blinker\" dependency version\n\n\nclass PyAutoGUI:\n    # The version installed if PyAutoGUI is not installed\n    VER = \"0.9.54\"\n\n\nclass Mobile:\n    # Default values for mobile settings\n    WIDTH = 390\n    HEIGHT = 715\n    RATIO = 3\n    AGENT = (\n        \"Mozilla/5.0 (Linux; Android 13; Pixel 7 XL \"\n        \"Build/SP2A.220505.006.A1; wv) \"\n        \"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 \"\n        \"Chrome/110.0.5028.105 Mobile Safari/537.36\"\n    )\n\n\nclass UC:\n    RECONNECT_TIME = 2.4  # Seconds\n    CDP_MODE_OPEN_WAIT = 0.9  # Seconds\n    EXTRA_WINDOWS_WAIT = 0.3  # Seconds\n\n\nclass ValidBrowsers:\n    valid_browsers = [\n        \"chrome\",\n        \"edge\",\n        \"firefox\",\n        \"ie\",\n        \"safari\",\n        \"remote\",\n        \"opera\",\n        \"brave\",\n        \"comet\",\n        \"atlas\",\n    ]\n\n\nclass ChromiumSubs:\n    # Chromium browsers that still use chromedriver\n    chromium_subs = [\n        \"opera\",\n        \"brave\",\n        \"comet\",\n        \"atlas\",\n    ]\n\n\nclass ValidBinaries:\n    valid_chrome_binaries_on_linux = [\n        \"google-chrome\",\n        \"google-chrome-stable\",\n        \"chrome\",\n        \"chromium\",\n        \"chromium-browser\",\n        \"google-chrome-beta\",\n        \"google-chrome-dev\",\n        \"google-chrome-unstable\",\n        \"chrome-headless-shell\",\n        \"brave-browser\",\n        \"brave-browser-stable\",\n        \"brave\",\n        \"opera\",\n        \"opera-stable\",\n        \"comet\",\n        \"comet-browser\",\n        \"comet-stable\",\n        \"atlas\",\n        \"atlas-browser\",\n        \"atlas-stable\",\n        \"chrome.exe\",  # WSL (Windows Subsystem for Linux)\n        \"chromium.exe\",  # WSL (Windows Subsystem for Linux)\n    ]\n    valid_edge_binaries_on_linux = [\n        \"microsoft-edge\",\n        \"microsoft-edge-stable\",\n        \"microsoft-edge-beta\",\n        \"microsoft-edge-dev\",\n        \"msedge.exe\",  # WSL (Windows Subsystem for Linux)\n    ]\n    valid_chrome_binaries_on_macos = [\n        \"Google Chrome\",\n        \"Chromium\",\n        \"Google Chrome for Testing\",\n        \"chrome-headless-shell\",\n        \"Google Chrome Beta\",\n        \"Google Chrome Dev\",\n        \"Brave Browser\",\n        \"Brave\",\n        \"Opera Browser\",\n        \"Opera\",\n        \"Comet Browser\",\n        \"Comet\",\n        \"ChatGPT Atlas\",\n        \"Atlas Browser\",\n        \"Atlas\",\n    ]\n    valid_edge_binaries_on_macos = [\n        \"Microsoft Edge\",\n    ]\n    valid_chrome_binaries_on_windows = [\n        \"chrome.exe\",\n        \"chromium.exe\",\n        \"chrome-headless-shell.exe\",\n        \"brave.exe\",\n        \"opera.exe\",\n        \"comet.exe\",\n        \"atlas.exe\",\n    ]\n    valid_edge_binaries_on_windows = [\n        \"msedge.exe\",\n    ]\n\n\nclass Browser:\n    GOOGLE_CHROME = \"chrome\"\n    EDGE = \"edge\"\n    FIREFOX = \"firefox\"\n    INTERNET_EXPLORER = \"ie\"\n    SAFARI = \"safari\"\n    REMOTE = \"remote\"\n\n    VERSION = {\n        \"chrome\": None,\n        \"edge\": None,\n        \"firefox\": None,\n        \"ie\": None,\n        \"safari\": None,\n        \"remote\": None,\n    }\n\n    LATEST = {\n        \"chrome\": None,\n        \"edge\": None,\n        \"firefox\": None,\n        \"ie\": None,\n        \"safari\": None,\n        \"remote\": None,\n    }\n\n\nclass Protocol:\n    HTTP = \"http\"\n    HTTPS = \"https\"\n\n\nclass State:\n    PASSED = \"Passed\"\n    FAILED = \"Failed\"\n    SKIPPED = \"Skipped\"\n    UNTESTED = \"Untested\"\n    ERROR = \"Error\"\n    BLOCKED = \"Blocked\"\n    DEPRECATED = \"Deprecated\"\n"
  },
  {
    "path": "seleniumbase/fixtures/css_to_xpath.py",
    "content": "\"\"\"Convert CSS selectors into XPath selectors\"\"\"\nfrom cssselect.xpath import GenericTranslator\n\n\nclass ConvertibleToCssTranslator(GenericTranslator):\n    \"\"\"An implementation of :py:class:`cssselect.GenericTranslator` with\n    XPath output that more readily converts back to CSS selectors.\n    The simplified examples in https://devhints.io/xpath were used as a\n    reference here.\"\"\"\n\n    def css_to_xpath(self, css, prefix=\"//\"):\n        return super().css_to_xpath(css, prefix)\n\n    def xpath_attrib_equals(self, xpath, name, value):\n        xpath.add_condition(\"%s=%s\" % (name, self.xpath_literal(value)))\n        return xpath\n\n    def xpath_attrib_includes(self, xpath, name, value):\n        from cssselect.xpath import is_non_whitespace\n\n        if is_non_whitespace(value):\n            xpath.add_condition(\n                \"contains(%s, %s)\" % (name, self.xpath_literal(value))\n            )\n        else:\n            xpath.add_condition(\"0\")\n        return xpath\n\n    def xpath_attrib_substringmatch(self, xpath, name, value):\n        if value:\n            # Attribute selectors are case sensitive\n            xpath.add_condition(\n                \"contains(%s, %s)\" % (name, self.xpath_literal(value))\n            )\n        else:\n            xpath.add_condition(\"0\")\n        return xpath\n\n    def xpath_class(self, class_selector):\n        xpath = self.xpath(class_selector.selector)\n        return self.xpath_attrib_includes(\n            xpath, \"@class\", class_selector.class_name\n        )\n\n    def xpath_descendant_combinator(self, left, right):\n        \"\"\"right is a child, grand-child or further descendant of left\"\"\"\n        return left.join(\"//\", right)\n\n\ndef convert_css_to_xpath(css):\n    \"\"\"Convert CSS Selectors to XPath Selectors.\n    Example:\n        convert_css_to_xpath('button:contains(\"Next\")')\n        Output => \"//button[contains(., 'Next')]\"\n    \"\"\"\n    xpath = ConvertibleToCssTranslator().css_to_xpath(css)\n    return xpath\n"
  },
  {
    "path": "seleniumbase/fixtures/errors.py",
    "content": "\"\"\"SeleniumBase MySQL-related exceptions.\n\nThis feature is DEPRECATED!\nUse self.skip() for skipping tests!\n\nRaising one of these in a test will cause the\ntest-state to be logged appropriately in the DB\nfor tests that use the SeleniumBase MySQL option.\"\"\"\n\n\nclass BlockedTest(Exception):\n    \"\"\"Raise this to mark a test as Blocked in the DB.\"\"\"\n    pass\n\n\nclass SkipTest(Exception):\n    \"\"\"Raise this to mark a test as Skipped in the DB.\"\"\"\n    pass\n\n\nclass DeprecatedTest(Exception):\n    \"\"\"Raise this to mark a test as Deprecated in the DB.\"\"\"\n    pass\n"
  },
  {
    "path": "seleniumbase/fixtures/js_utils.py",
    "content": "\"\"\"This module contains useful Javascript utility methods for BaseCase.\"\"\"\nimport re\nimport requests\nimport time\nfrom contextlib import suppress\nfrom selenium.common.exceptions import NoSuchElementException\nfrom selenium.common.exceptions import WebDriverException\nfrom selenium.webdriver.common.by import By\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import css_to_xpath\nfrom seleniumbase.fixtures import shared_utils\nfrom seleniumbase.fixtures import xpath_to_css\n\n\ndef execute_script(driver, script, *args, **kwargs):\n    if shared_utils.is_cdp_swap_needed(driver):\n        return driver.cdp.evaluate(script)\n    return driver.execute_script(script, *args, **kwargs)\n\n\ndef wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT):\n    \"\"\"The DOM (Document Object Model) has a property called \"readyState\".\n    When the value of this becomes \"complete\", page resources are considered\n      fully loaded (although AJAX and other loads might still be happening).\n    This method will wait until document.readyState == \"complete\".\n    This may be redundant, as methods already wait for page elements to load.\n    If the timeout is exceeded, the test will still continue\n      because readyState == \"interactive\" may be good enough.\n    (Previously, tests would fail immediately if exceeding the timeout.)\"\"\"\n    if hasattr(driver, \"_swap_driver\"):\n        return\n    if getattr(settings, \"SKIP_JS_WAITS\", None):\n        return\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        if sb_config.time_limit and not sb_config.recorder_mode:\n            shared_utils.check_if_time_limit_exceeded()\n        try:\n            ready_state = execute_script(driver, \"return document.readyState;\")\n        except WebDriverException:\n            # Bug fix for: [Permission denied to access property \"document\"]\n            time.sleep(0.03)\n            return True\n        if ready_state == \"complete\":\n            time.sleep(0.002)\n            return True\n        else:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    return False  # readyState stayed \"interactive\" (Not \"complete\")\n\n\ndef execute_async_script(driver, script, timeout=settings.LARGE_TIMEOUT):\n    if hasattr(driver, \"set_script_timeout\"):\n        driver.set_script_timeout(timeout)\n    if hasattr(driver, \"execute_async_script\"):\n        return driver.execute_async_script(script)\n    else:\n        return None\n\n\ndef wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs):\n    if getattr(settings, \"SKIP_JS_WAITS\", None):\n        return\n    with suppress(Exception):\n        # This closes pop-up alerts\n        execute_script(driver, \"\")\n    if (\n        getattr(driver, \"_is_using_uc\", None)\n        or not settings.WAIT_FOR_ANGULARJS\n    ):\n        wait_for_ready_state_complete(driver)\n        return\n    if timeout == settings.MINI_TIMEOUT:\n        timeout = settings.MINI_TIMEOUT / 6.0\n    NG_WRAPPER = (\n        \"%(prefix)s\"\n        \"var $elm=document.querySelector(\"\n        \"'[data-ng-app],[ng-app],.ng-scope')||document;\"\n        \"if(window.angular && angular.getTestability){\"\n        \"angular.getTestability($elm).whenStable(%(handler)s)\"\n        \"}else{\"\n        \"var $inj;try{$inj=angular.element($elm).injector()||\"\n        \"angular.injector(['ng'])}catch(ex){\"\n        \"$inj=angular.injector(['ng'])};$inj.get=$inj.get||\"\n        \"$inj;$inj.get('$browser').\"\n        \"notifyWhenNoOutstandingRequests(%(handler)s)}\"\n        \"%(suffix)s\"\n    )\n    def_pre = \"var cb=arguments[arguments.length-1];if(window.angular){\"\n    prefix = kwargs.pop(\"prefix\", def_pre)\n    handler = kwargs.pop(\"handler\", \"function(){cb(true)}\")\n    suffix = kwargs.pop(\"suffix\", \"}else{cb(false)}\")\n    script = NG_WRAPPER % {\n        \"prefix\": prefix,\n        \"handler\": handler,\n        \"suffix\": suffix,\n    }\n    with suppress(Exception):\n        execute_async_script(driver, script, timeout=timeout)\n\n\ndef convert_to_css_selector(selector, by=By.CSS_SELECTOR):\n    if by == By.CSS_SELECTOR:\n        return selector\n    elif by == By.ID:\n        return \"#%s\" % selector\n    elif by == By.CLASS_NAME:\n        return \".%s\" % selector\n    elif by == By.NAME:\n        return '[name=\"%s\"]' % selector\n    elif by == By.TAG_NAME:\n        return selector\n    elif (\n        by == By.XPATH\n        or (\n            selector.startswith(\"/\")\n            or selector.startswith(\"./\")\n            or selector.startswith(\"(\")\n        )\n    ):\n        return xpath_to_css.convert_xpath_to_css(selector)\n    elif by == By.LINK_TEXT:\n        return 'a:contains(\"%s\")' % selector\n    elif by == By.PARTIAL_LINK_TEXT:\n        return 'a:contains(\"%s\")' % selector\n    else:\n        raise Exception(\n            \"Exception: Could not convert {%s}(by=%s) to CSS_SELECTOR!\"\n            % (selector, by)\n        )\n\n\ndef is_html_inspector_activated(driver):\n    try:\n        execute_script(driver, \"HTMLInspector;\")  # Fails if not defined\n        return True\n    except Exception:\n        return False\n\n\ndef is_jquery_activated(driver):\n    try:\n        execute_script(driver, \"jQuery('html');\")  # Fails if jq is not defined\n        return True\n    except Exception:\n        return False\n\n\ndef wait_for_jquery_active(driver, timeout=None):\n    if not timeout:\n        timeout = 2\n    else:\n        timeout = int(timeout * 10.0)\n    for x in range(timeout):\n        # jQuery needs a small amount of time to activate.\n        try:\n            execute_script(driver, \"jQuery('html');\")\n            wait_for_ready_state_complete(driver)\n            wait_for_angularjs(driver)\n            return\n        except Exception:\n            time.sleep(0.1)\n\n\ndef raise_unable_to_load_jquery_exception(driver):\n    has_csp_error = False\n    csp_violation = \"violates the following Content Security Policy directive\"\n    browser_logs = []\n    try:\n        browser_logs = driver.get_log(\"browser\")\n    except (ValueError, WebDriverException):\n        pass\n    for entry in browser_logs:\n        if entry[\"level\"] == \"SEVERE\":\n            if csp_violation in entry[\"message\"]:\n                has_csp_error = True\n    if has_csp_error:\n        raise Exception(\n            \"\"\"Unable to load jQuery on \"%s\" due to a violation \"\"\"\n            \"\"\"of the website's Content Security Policy directive. \"\"\"\n            \"\"\"To override this policy, add \"--disable-csp\" on the \"\"\"\n            \"\"\"command-line when running your tests.\"\"\" % driver.current_url\n        )\n    else:\n        raise Exception(\n            \"\"\"Unable to load jQuery on \"%s\" because this website may be \"\"\"\n            \"\"\"restricting external JavaScript resources from loading.\"\"\"\n            % driver.current_url\n        )\n\n\ndef activate_jquery(driver):\n    # If \"jQuery is not defined\" on a website, use this method to activate it.\n    # This method is needed because jQuery is not always defined on web sites.\n    with suppress(Exception):\n        # Let's first find out if jQuery is already defined.\n        execute_script(driver, \"jQuery('html');\")\n        # Since that command worked, jQuery is defined. Let's return.\n        return\n    # jQuery is not defined. It will be loaded in the next part.\n    jquery_js = constants.JQuery.MIN_JS\n    add_js_link(driver, jquery_js)\n    for x in range(36):\n        # jQuery needs a small amount of time to activate.\n        try:\n            execute_script(driver, \"jQuery('html');\")\n            return\n        except TypeError as e:\n            if (\n                (\n                    shared_utils.is_cdp_swap_needed(driver)\n                    or hasattr(driver, \"_swap_driver\")\n                )\n                and \"cannot unpack non-iterable\" in str(e)\n            ):\n                pass\n            else:\n                if x == 18:\n                    add_js_link(driver, jquery_js)\n                time.sleep(0.1)\n        except Exception:\n            if x == 18:\n                add_js_link(driver, jquery_js)\n            time.sleep(0.1)\n    # Since jQuery still isn't activating, give up and raise an exception\n    raise_unable_to_load_jquery_exception(driver)\n\n\ndef are_quotes_escaped(string):\n    if string.count(\"\\\\'\") != string.count(\"'\") or (\n        string.count('\\\\\"') != string.count('\"')\n    ):\n        return True\n    return False\n\n\ndef escape_quotes_if_needed(string):\n    \"\"\"re.escape() works differently in Python 3.7.0 than earlier versions:\n\n    Python 3.6.5:\n    >>> import re\n    >>> re.escape('\"')\n    '\\\\\"'\n\n    Python 3.7.0:\n    >>> import re\n    >>> re.escape('\"')\n    '\"'\n\n    SeleniumBase needs quotes to be properly escaped for Javascript calls.\n    \"\"\"\n    if are_quotes_escaped(string):\n        if string.count(\"'\") != string.count(\"\\\\'\"):\n            string = string.replace(\"'\", \"\\\\'\")\n        if string.count('\"') != string.count('\\\\\"'):\n            string = string.replace('\"', '\\\\\"')\n    return string\n\n\ndef is_in_frame(driver):\n    # Returns True if the driver has switched to a frame.\n    # Returns False if the driver was on default content.\n    if shared_utils.is_cdp_swap_needed(driver):\n        return False\n    in_basic_frame = execute_script(\n        driver,\n        \"\"\"\n        var frame = window.frameElement;\n        if (frame) {\n            return true;\n        }\n        else {\n            return false;\n        }\n        \"\"\"\n    )\n    location_href = execute_script(driver, \"\"\"return window.location.href;\"\"\")\n    in_external_frame = False\n    if driver.current_url != location_href:\n        in_external_frame = True\n    if in_basic_frame or in_external_frame:\n        return True\n    return False\n\n\ndef safe_execute_script(driver, script):\n    \"\"\"When executing a script that contains a jQuery command,\n    it's important that the jQuery library has been loaded first.\n    This method will load jQuery if it wasn't already loaded.\"\"\"\n    try:\n        execute_script(driver, script)\n    except TypeError as e:\n        if (\n            (\n                shared_utils.is_cdp_swap_needed(driver)\n                or hasattr(driver, \"_swap_driver\")\n            )\n            and \"cannot unpack non-iterable\" in str(e)\n        ):\n            pass\n        else:\n            activate_jquery(driver)  # It's a good thing we can define it here\n            execute_script(driver, script)\n    except Exception:\n        # The likely reason this fails is because: \"jQuery is not defined\"\n        activate_jquery(driver)  # It's a good thing we can define it here\n        execute_script(driver, script)\n\n\ndef remove_extra_slashes(selector):\n    if selector.count('\\\\\"') > 0:\n        if selector.count('\\\\\"') == selector.count('\"'):\n            selector = selector.replace('\\\\\"', '\"')\n        elif selector.count('\\\\\"') == selector[1:-1].count('\"') and (\n            \"'\" not in selector[1:-1]\n        ):\n            selector = \"'\" + selector[1:-1].replace('\\\\\"', '\"') + \"'\"\n        else:\n            pass\n    if selector.count(\"\\\\'\") > 0:\n        if selector.count(\"\\\\'\") == selector.count(\"'\"):\n            selector = selector.replace(\"\\\\'\", \"'\")\n        elif selector.count(\"\\\\'\") == selector[1:-1].count(\"'\") and (\n            '\"' not in selector[1:-1]\n        ):\n            selector = '\"' + selector[1:-1].replace(\"\\\\'\", \"'\") + '\"'\n        else:\n            pass\n    return selector\n\n\ndef optimize_selector(selector):\n    if (len(selector) > 2 and selector[0] == \"'\") and (\n        selector[-1] == \"'\" and '\"' not in selector[1:-1]\n    ):\n        selector = '\"' + selector[1:-1] + '\"'\n    if (\n        selector.count('\"') == 0\n        and selector.count(\"'\") >= 2\n        and selector.count(\"'\") % 2 == 0\n        and \"='\" in selector\n        and \"']\" in selector\n    ):\n        swap_char = \"*_SWAP_CHAR_*\"\n        selector = selector.replace(\"'\", swap_char)\n        selector = selector.replace('\"', \"'\")\n        selector = selector.replace(swap_char, '\"')\n    return selector\n\n\ndef wait_for_css_query_selector(\n    driver, selector, timeout=settings.LARGE_TIMEOUT\n):\n    element = None\n    selector = escape_quotes_if_needed(selector)\n    selector = remove_extra_slashes(selector)\n    selector = optimize_selector(selector)\n    script = \"\"\"return document.querySelector('%s');\"\"\" % selector\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        try:\n            element = execute_script(driver, script)\n            if element:\n                return element\n        except Exception:\n            element = None\n        if not element:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    raise NoSuchElementException(\n        \"Element {%s} was not present after %s seconds!\" % (selector, timeout)\n    )\n\n\ndef is_valid_by(by):\n    return by in [\n        \"css selector\", \"class name\", \"id\", \"name\",\n        \"link text\", \"xpath\", \"tag name\", \"partial link text\",\n    ]\n\n\ndef swap_selector_and_by_if_reversed(selector, by):\n    if not is_valid_by(by) and is_valid_by(selector):\n        selector, by = by, selector\n    return (selector, by)\n\n\ndef call_me_later(driver, script, ms):\n    \"\"\"Call script after ms passed.\"\"\"\n    call = \"function() {%s}\" % script\n    execute_script(driver, \"window.setTimeout(%s, %s);\" % (call, ms))\n\n\ndef highlight(driver, selector, by=\"css selector\", loops=4):\n    \"\"\"For driver.highlight() / driver.page.highlight()\"\"\"\n    swap_selector_and_by_if_reversed(selector, by)\n    if \":contains(\" in selector:\n        by = \"xpath\"\n        selector = css_to_xpath.convert_css_to_xpath(selector)\n    element = None\n    try:\n        element = driver.find_element(by, selector)\n    except Exception:\n        time.sleep(1)\n        element = driver.find_element(by, selector)\n    o_bs = \"\"  # original_box_shadow\n    style = element.get_attribute(\"style\")\n    if style and \"box-shadow: \" in style:\n        box_start = style.find(\"box-shadow: \")\n        box_end = style.find(\";\", box_start) + 1\n        original_box_shadow = style[box_start:box_end]\n        o_bs = original_box_shadow\n    highlight_element_with_js(driver, element, loops=loops, o_bs=o_bs)\n\n\ndef highlight_with_js(driver, selector, loops=4, o_bs=\"\"):\n    with suppress(Exception):\n        # This closes any pop-up alerts\n        execute_script(driver, \"\")\n    if selector == \"html\":\n        selector = \"body\"\n    selector_no_spaces = selector.replace(\" \", \"\")\n    early_exit = False\n    if '[style=\\\\\"' in selector_no_spaces:\n        if \"box\\\\-shadow:\" in selector:\n            early_exit = True  # Changing the box-shadow changes the selector\n        elif '[style=\\\\\"' in selector:\n            selector = selector.replace('[style=\\\\\"', '[style\\\\*=\\\\\"')\n        else:\n            early_exit = True  # Changing the box-shadow changes the selector\n        if early_exit:\n            return\n    script = (\n        \"\"\"document.querySelector('%s').style.boxShadow =\n        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    for n in range(loops):\n        script = (\n            \"\"\"document.querySelector('%s').style.boxShadow =\n            '0px 0px 6px 6px rgba(255, 0, 0, 1)';\"\"\"\n            % selector\n        )\n        try:\n            execute_script(driver, script)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"document.querySelector('%s').style.boxShadow =\n            '0px 0px 6px 6px rgba(128, 0, 128, 1)';\"\"\"\n            % selector\n        )\n        try:\n            execute_script(driver, script)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"document.querySelector('%s').style.boxShadow =\n            '0px 0px 6px 6px rgba(0, 0, 255, 1)';\"\"\"\n            % selector\n        )\n        try:\n            execute_script(driver, script)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"document.querySelector('%s').style.boxShadow =\n            '0px 0px 6px 6px rgba(0, 255, 0, 1)';\"\"\"\n            % selector\n        )\n        try:\n            execute_script(driver, script)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"document.querySelector('%s').style.boxShadow =\n            '0px 0px 6px 6px rgba(128, 128, 0, 1)';\"\"\"\n            % selector\n        )\n        try:\n            execute_script(driver, script)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"document.querySelector('%s').style.boxShadow =\n            '0px 0px 6px 6px rgba(128, 0, 128, 1)';\"\"\"\n            % selector\n        )\n        try:\n            execute_script(driver, script)\n        except Exception:\n            return\n        time.sleep(0.0181)\n    script = \"\"\"document.querySelector('%s').style.boxShadow =\n        '%s';\"\"\" % (\n        selector,\n        o_bs,\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n\n\ndef highlight_element_with_js(driver, element, loops=4, o_bs=\"\"):\n    with suppress(Exception):\n        # This closes any pop-up alerts\n        execute_script(driver, \"\")\n    script = (\n        \"\"\"arguments[0].style.boxShadow =\n        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';\"\"\"\n    )\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n    for n in range(loops):\n        script = (\n            \"\"\"arguments[0].style.boxShadow =\n            '0px 0px 6px 6px rgba(255, 0, 0, 1)';\"\"\"\n        )\n        try:\n            execute_script(driver, script, element)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"arguments[0].style.boxShadow =\n            '0px 0px 6px 6px rgba(128, 0, 128, 1)';\"\"\"\n        )\n        try:\n            execute_script(driver, script, element)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"arguments[0].style.boxShadow =\n            '0px 0px 6px 6px rgba(0, 0, 255, 1)';\"\"\"\n        )\n        try:\n            execute_script(driver, script, element)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"arguments[0].style.boxShadow =\n            '0px 0px 6px 6px rgba(0, 255, 0, 1)';\"\"\"\n        )\n        try:\n            execute_script(driver, script, element)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"arguments[0].style.boxShadow =\n            '0px 0px 6px 6px rgba(128, 128, 0, 1)';\"\"\"\n        )\n        try:\n            execute_script(driver, script, element)\n        except Exception:\n            return\n        time.sleep(0.0181)\n        script = (\n            \"\"\"arguments[0].style.boxShadow =\n            '0px 0px 6px 6px rgba(128, 0, 128, 1)';\"\"\"\n        )\n        try:\n            execute_script(driver, script, element)\n        except Exception:\n            return\n        time.sleep(0.0181)\n    script = \"\"\"arguments[0].style.boxShadow = '%s';\"\"\" % (o_bs)\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n\n\ndef highlight_with_jquery(driver, selector, loops=4, o_bs=\"\"):\n    with suppress(Exception):\n        # This closes any pop-up alerts\n        execute_script(driver, \"\")\n    if selector == \"html\":\n        selector = \"body\"\n    selector_no_spaces = selector.replace(\" \", \"\")\n    early_exit = False\n    if '[style=\\\\\"' in selector_no_spaces:\n        if \"box\\\\-shadow:\" in selector:\n            early_exit = True  # Changing the box-shadow changes the selector\n        elif '[style=\\\\\"' in selector:\n            selector = selector.replace('[style=\\\\\"', '[style\\\\*=\\\\\"')\n        else:\n            early_exit = True  # Changing the box-shadow changes the selector\n        if early_exit:\n            return\n    script = (\n        \"\"\"jQuery('%s').css('box-shadow',\n        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)');\"\"\"\n        % selector\n    )\n    with suppress(Exception):\n        safe_execute_script(driver, script)\n    for n in range(loops):\n        script = (\n            \"\"\"jQuery('%s').css('box-shadow',\n            '0px 0px 6px 6px rgba(255, 0, 0, 1)');\"\"\"\n            % selector\n        )\n        with suppress(Exception):\n            execute_script(driver, script)\n        time.sleep(0.0181)\n        script = (\n            \"\"\"jQuery('%s').css('box-shadow',\n            '0px 0px 6px 6px rgba(128, 0, 128, 1)');\"\"\"\n            % selector\n        )\n        with suppress(Exception):\n            execute_script(driver, script)\n        time.sleep(0.0181)\n        script = (\n            \"\"\"jQuery('%s').css('box-shadow',\n            '0px 0px 6px 6px rgba(0, 0, 255, 1)');\"\"\"\n            % selector\n        )\n        with suppress(Exception):\n            execute_script(driver, script)\n        time.sleep(0.0181)\n        script = (\n            \"\"\"jQuery('%s').css('box-shadow',\n            '0px 0px 6px 6px rgba(0, 255, 0, 1)');\"\"\"\n            % selector\n        )\n        with suppress(Exception):\n            execute_script(driver, script)\n        time.sleep(0.0181)\n        script = (\n            \"\"\"jQuery('%s').css('box-shadow',\n            '0px 0px 6px 6px rgba(128, 128, 0, 1)');\"\"\"\n            % selector\n        )\n        with suppress(Exception):\n            execute_script(driver, script)\n        time.sleep(0.0181)\n        script = (\n            \"\"\"jQuery('%s').css('box-shadow',\n            '0px 0px 6px 6px rgba(128, 0, 128, 1)');\"\"\"\n            % selector\n        )\n        with suppress(Exception):\n            execute_script(driver, script)\n        time.sleep(0.0181)\n    script = \"\"\"jQuery('%s').css('box-shadow', '%s');\"\"\" % (selector, o_bs)\n    with suppress(Exception):\n        execute_script(driver, script)\n\n\ndef add_css_link(driver, css_link):\n    script_to_add_css = \"\"\"function injectCSS(css) {\n          var head_tag=document.getElementsByTagName(\"head\")[0];\n          var link_tag=document.createElement(\"link\");\n          link_tag.rel=\"stylesheet\";\n          link_tag.type=\"text/css\";\n          link_tag.href=css;\n          link_tag.crossorigin=\"anonymous\";\n          head_tag.appendChild(link_tag);\n       }\n       injectCSS(\"%s\");\"\"\"\n    css_link = escape_quotes_if_needed(css_link)\n    execute_script(driver, script_to_add_css % css_link)\n\n\ndef add_js_link(driver, js_link):\n    script_to_add_js = \"\"\"function injectJS(link) {\n          var body_tag=document.getElementsByTagName(\"body\")[0];\n          var script_tag=document.createElement(\"script\");\n          script_tag.src=link;\n          script_tag.type=\"text/javascript\";\n          script_tag.crossorigin=\"anonymous\";\n          script_tag.defer;\n          script_tag.onload=function() { null };\n          body_tag.appendChild(script_tag);\n       }\n       injectJS(\"%s\");\"\"\"\n    js_link = escape_quotes_if_needed(js_link)\n    execute_script(driver, script_to_add_js % js_link)\n\n\ndef add_css_style(driver, css_style):\n    add_css_style_script = \"\"\"function injectStyle(css) {\n          var head_tag=document.getElementsByTagName(\"head\")[0];\n          var style_tag=document.createElement(\"style\");\n          style_tag.type=\"text/css\";\n          style_tag.appendChild(document.createTextNode(css));\n          head_tag.appendChild(style_tag);\n       }\n       injectStyle(\"%s\");\"\"\"\n    css_style = css_style.replace(\"\\n\", \"\")\n    css_style = escape_quotes_if_needed(css_style)\n    execute_script(driver, add_css_style_script % css_style)\n\n\ndef add_js_code_from_link(driver, js_link):\n    if js_link.startswith(\"//\"):\n        js_link = \"http:\" + js_link\n    js_code = requests.get(js_link, timeout=5).text\n    add_js_code_script = (\n        \"\"\"var body_tag=document.getElementsByTagName('body').item(0);\"\"\"\n        \"\"\"var script_tag=document.createElement(\"script\");\"\"\"\n        \"\"\"script_tag.type=\"text/javascript\";\"\"\"\n        \"\"\"script_tag.onload=function() { null };\"\"\"\n        \"\"\"script_tag.appendChild(document.createTextNode(\"%s\"));\"\"\"\n        \"\"\"body_tag.appendChild(script_tag);\"\"\"\n    )\n    js_code = js_code.replace(\"\\n\", \" \")\n    js_code = escape_quotes_if_needed(js_code)\n    execute_script(driver, add_js_code_script % js_code)\n\n\ndef add_js_code(driver, js_code):\n    add_js_code_script = (\n        \"\"\"var body_tag=document.getElementsByTagName('body').item(0);\"\"\"\n        \"\"\"var script_tag=document.createElement(\"script\");\"\"\"\n        \"\"\"script_tag.type=\"text/javascript\";\"\"\"\n        \"\"\"script_tag.onload=function() { null };\"\"\"\n        \"\"\"script_tag.appendChild(document.createTextNode(\"%s\"));\"\"\"\n        \"\"\"body_tag.appendChild(script_tag);\"\"\"\n    )\n    js_code = js_code.replace(\"\\n\", \" \")\n    js_code = escape_quotes_if_needed(js_code)\n    execute_script(driver, add_js_code_script % js_code)\n\n\ndef add_meta_tag(driver, http_equiv=None, content=None):\n    if http_equiv is None:\n        http_equiv = \"Content-Security-Policy\"\n    if content is None:\n        content = (\n            \"default-src *; style-src 'self' 'unsafe-inline'; \"\n            \"script-src: 'self' 'unsafe-inline' 'unsafe-eval'\"\n        )\n    script_to_add_meta = \"\"\"function injectMeta() {\n           var meta_tag=document.createElement('meta');\n           meta_tag.httpEquiv=\"%s\";\n           meta_tag.content=\"%s\";\n           document.getElementsByTagName('head')[0].appendChild(meta_tag);\n        }\n        injectMeta();\"\"\" % (\n        http_equiv,\n        content,\n    )\n    execute_script(driver, script_to_add_meta)\n\n\ndef is_jquery_confirm_activated(driver):\n    try:\n        execute_script(driver, \"jconfirm;\")  # Fails if jconfirm is not defined\n        return True\n    except Exception:\n        return False\n\n\ndef activate_jquery_confirm(driver):\n    jquery_js = constants.JQuery.MIN_JS\n    jq_confirm_css = constants.JqueryConfirm.MIN_CSS\n    jq_confirm_js = constants.JqueryConfirm.MIN_JS\n\n    if not is_jquery_activated(driver):\n        add_js_link(driver, jquery_js)\n        wait_for_jquery_active(driver, timeout=1.2)\n    add_css_link(driver, jq_confirm_css)\n    add_js_link(driver, jq_confirm_js)\n\n    for x in range(28):\n        # jQuery-Confirm needs a small amount of time to load & activate.\n        if x == 14:\n            add_css_link(driver, jq_confirm_css)\n            add_js_link(driver, jq_confirm_js)\n        try:\n            execute_script(driver, \"jconfirm;\")\n            wait_for_ready_state_complete(driver)\n            wait_for_angularjs(driver)\n            return\n        except Exception:\n            time.sleep(0.1)\n\n\ndef activate_html_inspector(driver):\n    jquery_js = constants.JQuery.MIN_JS\n    html_inspector_js = constants.HtmlInspector.MIN_JS\n\n    if is_html_inspector_activated(driver):\n        return\n    if not is_jquery_activated(driver):\n        add_js_link(driver, jquery_js)\n        wait_for_jquery_active(driver, timeout=1.2)\n        wait_for_ready_state_complete(driver)\n        wait_for_angularjs(driver)\n    add_js_link(driver, html_inspector_js)\n    wait_for_ready_state_complete(driver)\n    wait_for_angularjs(driver)\n\n    for x in range(25):\n        # HTML-Inspector needs a small amount of time to load & activate.\n        try:\n            execute_script(driver, \"HTMLInspector;\")\n            wait_for_ready_state_complete(driver)\n            wait_for_angularjs(driver)\n            return\n        except Exception:\n            time.sleep(0.1)\n    wait_for_ready_state_complete(driver)\n    wait_for_angularjs(driver)\n\n\ndef activate_messenger(driver):\n    from seleniumbase.core.style_sheet import get_messenger_style\n\n    jquery_js = constants.JQuery.MIN_JS\n    messenger_css = constants.Messenger.MIN_CSS\n    messenger_js = constants.Messenger.MIN_JS\n    msgr_theme_flat_js = constants.Messenger.THEME_FLAT_JS\n    msgr_theme_future_js = constants.Messenger.THEME_FUTURE_JS\n    msgr_theme_flat_css = constants.Messenger.THEME_FLAT_CSS\n    msgr_theme_future_css = constants.Messenger.THEME_FUTURE_CSS\n    msgr_theme_block_css = constants.Messenger.THEME_BLOCK_CSS\n    msgr_theme_air_css = constants.Messenger.THEME_AIR_CSS\n    msgr_theme_ice_css = constants.Messenger.THEME_ICE_CSS\n    spinner_css = constants.Messenger.SPINNER_CSS\n    underscore_js = constants.Underscore.MIN_JS\n\n    msg_style = (\n        \"Messenger.options = {'maxMessages': 8, \"\n        \"extraClasses: 'messenger-fixed \"\n        \"messenger-on-bottom messenger-on-right', \"\n        \"theme: 'future'}\"\n    )\n\n    if not is_jquery_activated(driver):\n        add_js_link(driver, jquery_js)\n        wait_for_jquery_active(driver, timeout=1.1)\n    add_css_link(driver, messenger_css)\n    add_css_link(driver, msgr_theme_flat_css)\n    add_css_link(driver, msgr_theme_future_css)\n    add_css_link(driver, msgr_theme_block_css)\n    add_css_link(driver, msgr_theme_air_css)\n    add_css_link(driver, msgr_theme_ice_css)\n    add_js_link(driver, underscore_js)\n    add_css_link(driver, spinner_css)\n    add_js_link(driver, messenger_js)\n    add_css_style(driver, get_messenger_style())\n\n    for x in range(10):\n        # Messenger needs a small amount of time to load & activate.\n        try:\n            result = execute_script(\n                driver,\n                \"\"\" if (typeof Messenger === 'undefined') { return \"U\"; } \"\"\"\n            )\n            if result == \"U\":\n                time.sleep(0.022)\n                continue\n            else:\n                break\n        except Exception:\n            time.sleep(0.02)\n    try:\n        execute_script(driver, msg_style)\n        add_js_link(driver, msgr_theme_flat_js)\n        add_js_link(driver, msgr_theme_future_js)\n        wait_for_ready_state_complete(driver)\n        wait_for_angularjs(driver)\n        return\n    except Exception:\n        time.sleep(0.1)\n\n\ndef set_messenger_theme(\n    driver, theme=\"default\", location=\"default\", max_messages=\"default\"\n):\n    if theme == \"default\":\n        theme = \"future\"\n    if location == \"default\":\n        location = \"bottom_right\"\n        if getattr(sb_config, \"mobile_emulator\", None):\n            location = \"top_center\"\n    if max_messages == \"default\":\n        max_messages = \"8\"\n\n    valid_themes = [\"flat\", \"future\", \"block\", \"air\", \"ice\"]\n    if theme not in valid_themes:\n        raise Exception(\"Theme: %s is not in %s!\" % (theme, valid_themes))\n    valid_locations = [\n        \"top_left\",\n        \"top_center\",\n        \"top_right\",\n        \"bottom_left\",\n        \"bottom_center\",\n        \"bottom_right\",\n    ]\n    if location not in valid_locations:\n        raise Exception(\n            \"Location: %s is not in %s!\" % (location, valid_locations)\n        )\n\n    if location == \"top_left\":\n        messenger_location = \"messenger-on-top messenger-on-left\"\n    elif location == \"top_center\":\n        messenger_location = \"messenger-on-top\"\n    elif location == \"top_right\":\n        messenger_location = \"messenger-on-top messenger-on-right\"\n    elif location == \"bottom_left\":\n        messenger_location = \"messenger-on-bottom messenger-on-left\"\n    elif location == \"bottom_center\":\n        messenger_location = \"messenger-on-bottom\"\n    elif location == \"bottom_right\":\n        messenger_location = \"messenger-on-bottom messenger-on-right\"\n\n    msg_style = (\n        \"Messenger.options = {'maxMessages': %s, \"\n        \"extraClasses: 'messenger-fixed %s', theme: '%s'}\"\n        % (max_messages, messenger_location, theme)\n    )\n    try:\n        time.sleep(0.015)\n        execute_script(driver, msg_style)\n    except Exception:\n        time.sleep(0.035)\n        activate_messenger(driver)\n        time.sleep(0.175)\n        with suppress(Exception):\n            execute_script(driver, msg_style)\n            time.sleep(0.035)\n    time.sleep(0.055)\n\n\ndef post_message(driver, message, msg_dur=None, style=\"info\"):\n    \"\"\"A helper method to post a message on the screen with Messenger.\n    (Should only be called from post_message() in base_case.py)\"\"\"\n    if not msg_dur:\n        msg_dur = settings.DEFAULT_MESSAGE_DURATION\n    msg_dur = float(msg_dur)\n    message = re.escape(message)\n    message = escape_quotes_if_needed(message)\n    messenger_script = (\n        \"\"\"Messenger().post({message: \"%s\", type: \"%s\", \"\"\"\n        \"\"\"hideAfter: %s, hideOnNavigate: true});\"\"\"\n        % (message, style, msg_dur)\n    )\n    retry = False\n    try:\n        execute_script(driver, messenger_script)\n    except TypeError as e:\n        if (\n            (\n                shared_utils.is_cdp_swap_needed(driver)\n                or hasattr(driver, \"_swap_driver\")\n            )\n            and \"cannot unpack non-iterable\" in str(e)\n        ):\n            pass\n        else:\n            retry = True\n    except Exception:\n        retry = True\n    if retry:\n        activate_messenger(driver)\n        set_messenger_theme(driver)\n        try:\n            execute_script(driver, messenger_script)\n        except TypeError:\n            pass\n        except Exception:\n            time.sleep(0.17)\n            activate_messenger(driver)\n            time.sleep(0.17)\n            set_messenger_theme(driver)\n            time.sleep(0.27)\n            execute_script(driver, messenger_script)\n\n\ndef post_messenger_success_message(driver, message, msg_dur=None):\n    if not msg_dur:\n        msg_dur = settings.DEFAULT_MESSAGE_DURATION\n    msg_dur = float(msg_dur)\n    with suppress(Exception):\n        theme = \"future\"\n        location = \"bottom_right\"\n        if getattr(sb_config, \"mobile_emulator\", None):\n            location = \"top_right\"\n        set_messenger_theme(driver, theme=theme, location=location)\n        post_message(driver, message, msg_dur, style=\"success\")\n        time.sleep(msg_dur + 0.07)\n\n\ndef post_messenger_error_message(driver, message, msg_dur=None):\n    if not msg_dur:\n        msg_dur = settings.DEFAULT_MESSAGE_DURATION\n    msg_dur = float(msg_dur)\n    with suppress(Exception):\n        set_messenger_theme(driver, theme=\"block\", location=\"top_center\")\n        post_message(driver, message, msg_dur, style=\"error\")\n        time.sleep(msg_dur + 0.07)\n\n\ndef highlight_with_js_2(driver, message, selector, o_bs, msg_dur):\n    with suppress(Exception):\n        # This closes any pop-up alerts\n        execute_script(driver, \"\")\n    if selector == \"html\":\n        selector = \"body\"\n    selector_no_spaces = selector.replace(\" \", \"\")\n    early_exit = False\n    if '[style=\\\\\"' in selector_no_spaces:\n        if \"box\\\\-shadow:\" in selector:\n            early_exit = True  # Changing the box-shadow changes the selector\n        elif '[style=\\\\\"' in selector:\n            selector = selector.replace('[style=\\\\\"', '[style\\\\*=\\\\\"')\n        else:\n            early_exit = True  # Changing the box-shadow changes the selector\n        if early_exit:\n            with suppress(Exception):\n                activate_jquery(driver)\n                post_messenger_success_message(driver, message, msg_dur)\n            return\n    script = (\n        \"\"\"document.querySelector('%s').style.boxShadow =\n        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"document.querySelector('%s').style.boxShadow =\n        '0px 0px 6px 6px rgba(205, 30, 0, 1)';\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"document.querySelector('%s').style.boxShadow =\n        '0px 0px 6px 6px rgba(128, 0, 128, 1)';\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"document.querySelector('%s').style.boxShadow =\n        '0px 0px 6px 6px rgba(50, 50, 128, 1)';\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"document.querySelector('%s').style.boxShadow =\n        '0px 0px 6px 6px rgba(50, 205, 50, 1)';\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    with suppress(Exception):\n        activate_jquery(driver)\n        post_messenger_success_message(driver, message, msg_dur)\n    script = \"\"\"document.querySelector('%s').style.boxShadow = '%s';\"\"\" % (\n        selector,\n        o_bs,\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n\n\ndef highlight_element_with_js_2(driver, message, element, o_bs, msg_dur):\n    with suppress(Exception):\n        # This closes any pop-up alerts\n        execute_script(driver, \"\")\n    script = (\n        \"\"\"arguments[0].style.boxShadow =\n        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';\"\"\"\n    )\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"arguments[0].style.boxShadow =\n        '0px 0px 6px 6px rgba(205, 30, 0, 1)';\"\"\"\n    )\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"arguments[0].style.boxShadow =\n        '0px 0px 6px 6px rgba(128, 0, 128, 1)';\"\"\"\n    )\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"arguments[0].style.boxShadow =\n        '0px 0px 6px 6px rgba(50, 50, 128, 1)';\"\"\"\n    )\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"arguments[0].style.boxShadow =\n        '0px 0px 6px 6px rgba(50, 205, 50, 1)';\"\"\"\n    )\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    with suppress(Exception):\n        activate_jquery(driver)\n        post_messenger_success_message(driver, message, msg_dur)\n    script = \"\"\"arguments[0].style.boxShadow = '%s';\"\"\" % (o_bs)\n    try:\n        execute_script(driver, script, element)\n    except Exception:\n        return\n\n\ndef highlight_with_jquery_2(driver, message, selector, o_bs, msg_dur):\n    if selector == \"html\":\n        selector = \"body\"\n    selector_no_spaces = selector.replace(\" \", \"\")\n    early_exit = False\n    if '[style=\\\\\"' in selector_no_spaces:\n        if \"box\\\\-shadow:\" in selector:\n            early_exit = True  # Changing the box-shadow changes the selector\n        elif '[style=\\\\\"' in selector:\n            selector = selector.replace('[style=\\\\\"', '[style\\\\*=\\\\\"')\n        else:\n            early_exit = True  # Changing the box-shadow changes the selector\n        if early_exit:\n            with suppress(Exception):\n                activate_jquery(driver)\n                post_messenger_success_message(driver, message, msg_dur)\n            return\n    script = (\n        \"\"\"jQuery('%s').css('box-shadow',\n        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)');\"\"\"\n        % selector\n    )\n    try:\n        safe_execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"jQuery('%s').css('box-shadow',\n        '0px 0px 6px 6px rgba(205, 30, 0, 1)');\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"jQuery('%s').css('box-shadow',\n        '0px 0px 6px 6px rgba(128, 0, 128, 1)');\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"jQuery('%s').css('box-shadow',\n        '0px 0px 6px 6px rgba(50, 50, 200, 1)');\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n    script = (\n        \"\"\"jQuery('%s').css('box-shadow',\n        '0px 0px 6px 6px rgba(50, 205, 50, 1)');\"\"\"\n        % selector\n    )\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n    time.sleep(0.0181)\n\n    with suppress(Exception):\n        activate_jquery(driver)\n        post_messenger_success_message(driver, message, msg_dur)\n\n    script = \"\"\"jQuery('%s').css('box-shadow', '%s');\"\"\" % (selector, o_bs)\n    try:\n        execute_script(driver, script)\n    except Exception:\n        return\n\n\ndef get_active_element_css(driver):\n    from seleniumbase.js_code import active_css_js\n\n    if shared_utils.is_cdp_swap_needed(driver):\n        return driver.cdp.get_active_element_css()\n    return execute_script(driver, active_css_js.get_active_element_css)\n\n\ndef get_locale_code(driver):\n    script = \"return navigator.language || navigator.languages[0];\"\n    return execute_script(driver, script)\n\n\ndef get_screen_rect(driver):\n    return execute_script(driver, \"return window.screen;\")\n\n\ndef get_origin(driver):\n    return execute_script(driver, \"return window.location.origin;\")\n\n\ndef get_user_agent(driver):\n    return execute_script(driver, \"return navigator.userAgent;\")\n\n\ndef get_cookie_string(driver):\n    return execute_script(driver, \"return document.cookie;\")\n\n\ndef get_scroll_distance_to_element(driver, element):\n    try:\n        scroll_position = execute_script(driver, \"return window.scrollY;\")\n        element_location = None\n        element_location = element.location[\"y\"]\n        element_location = element_location - constants.Scroll.Y_OFFSET\n        if element_location < 0:\n            element_location = 0\n        distance = element_location - scroll_position\n        return distance\n    except Exception:\n        return 0\n\n\ndef scroll_to_element(driver, element):\n    element_location_y = None\n    element_location_x = None\n    element_width = 0\n    screen_width = 0\n    try:\n        element_location_y = element.location[\"y\"]\n    except Exception:\n        return False\n    try:\n        element_location_x = element.location[\"x\"]\n    except Exception:\n        element_location_x = 0\n    try:\n        element_width = element.size[\"width\"]\n    except Exception:\n        element_width = 0\n    try:\n        screen_width = driver.get_window_size()[\"width\"]\n    except Exception:\n        screen_width = execute_script(driver, \"return window.innerWidth;\")\n    element_location_y = element_location_y - constants.Scroll.Y_OFFSET\n    if element_location_y < 0:\n        element_location_y = 0\n    element_location_x_fix = element_location_x - 400\n    if element_location_x_fix < 0:\n        element_location_x_fix = 0\n    if element_location_x + element_width <= screen_width:\n        element_location_x_fix = 0\n    scroll_script = \"window.scrollTo(%s, %s);\" % (\n        element_location_x_fix, element_location_y\n    )\n    # The old jQuery scroll_script required by=By.CSS_SELECTOR\n    # scroll_script = \"jQuery('%s')[0].scrollIntoView()\" % selector\n    # This other scroll_script does not centralize the element\n    # execute_script(driver, \"arguments[0].scrollIntoView();\", element)\n    try:\n        execute_script(driver, scroll_script)\n        return True\n    except Exception:\n        return False\n\n\ndef slow_scroll_to_element(driver, element, *args, **kwargs):\n    if driver.capabilities[\"browserName\"] == \"internet explorer\":\n        # IE breaks on slow-scrolling. Do a fast scroll instead.\n        scroll_to_element(driver, element)\n        return\n    scroll_position = execute_script(driver, \"return window.scrollY;\")\n    element_location_y = None\n    try:\n        if shared_utils.is_cdp_swap_needed(driver):\n            element_location_y = element.get_position().y\n        else:\n            element_location_y = element.location[\"y\"]\n    except Exception:\n        if shared_utils.is_cdp_swap_needed(driver):\n            element.scroll_into_view()\n        else:\n            element.location_once_scrolled_into_view\n        return\n    try:\n        if shared_utils.is_cdp_swap_needed(driver):\n            element_location_x = element.get_position().x\n        else:\n            element_location_x = element.location[\"x\"]\n    except Exception:\n        element_location_x = 0\n    try:\n        if shared_utils.is_cdp_swap_needed(driver):\n            element_width = element.get_position().width\n        else:\n            element_width = element.size[\"width\"]\n    except Exception:\n        element_width = 0\n    try:\n        if shared_utils.is_cdp_swap_needed(driver):\n            screen_width = driver.cdp.get_window_size()[\"width\"]\n        else:\n            screen_width = driver.get_window_size()[\"width\"]\n    except Exception:\n        screen_width = execute_script(driver, \"return window.innerWidth;\")\n    element_location_y = element_location_y - constants.Scroll.Y_OFFSET\n    if element_location_y < 0:\n        element_location_y = 0\n    element_location_x_fix = element_location_x - 400\n    if element_location_x_fix < 0:\n        element_location_x_fix = 0\n    if element_location_x + element_width <= screen_width:\n        element_location_x_fix = 0\n    if shared_utils.is_cdp_swap_needed(driver):\n        distance = element_location_y\n    else:\n        distance = element_location_y - scroll_position\n    if distance != 0:\n        total_steps = int(abs(distance) / 50.0) + 2.0\n        step_value = float(distance) / total_steps\n        new_position = scroll_position\n        for y in range(int(total_steps)):\n            time.sleep(0.011)\n            new_position += step_value\n            scroll_script = \"window.scrollTo(0, %s);\" % new_position\n            execute_script(driver, scroll_script)\n    time.sleep(0.01)\n    scroll_script = \"window.scrollTo(%s, %s);\" % (\n        element_location_x_fix, element_location_y\n    )\n    if not shared_utils.is_cdp_swap_needed(driver):\n        execute_script(driver, scroll_script)\n    time.sleep(0.01)\n    if distance > 430 or distance < -300:\n        # Add small recovery time for long-distance slow-scrolling\n        time.sleep(0.162)\n    else:\n        time.sleep(0.045)\n\n\ndef get_drag_and_drop_script():\n    # This script uses jQuery to perform a Drag-and-Drop action.\n    # (Requires the Drag-selector and the Drop-selector to work)\n    script = r\"\"\"(function( $ ) {\n        $.fn.simulateDragDrop = function(options) {\n                return this.each(function() {\n                        new $.simulateDragDrop(this, options);\n                });\n        };\n        $.simulateDragDrop = function(elem, options) {\n                this.options = options;\n                this.simulateEvent(elem, options);\n        };\n        $.extend($.simulateDragDrop.prototype, {\n                simulateEvent: function(elem, options) {\n                        /*Simulating drag start*/\n                        var type = 'dragstart';\n                        var event = this.createEvent(type);\n                        this.dispatchEvent(elem, type, event);\n\n                        /*Simulating drop*/\n                        type = 'drop';\n                        var dropEvent = this.createEvent(type, {});\n                        dropEvent.dataTransfer = event.dataTransfer;\n                        this.dispatchEvent(\n                            $(options.dropTarget)[0], type, dropEvent);\n\n                        /*Simulating drag end*/\n                        type = 'dragend';\n                        var dragEndEvent = this.createEvent(type, {});\n                        dragEndEvent.dataTransfer = event.dataTransfer;\n                        this.dispatchEvent(elem, type, dragEndEvent);\n                },\n                createEvent: function(type) {\n                        var event = document.createEvent(\"CustomEvent\");\n                        event.initCustomEvent(type, true, true, null);\n                        event.dataTransfer = {\n                                data: {\n                                },\n                                setData: function(type, val){\n                                        this.data[type] = val;\n                                },\n                                getData: function(type){\n                                        return this.data[type];\n                                }\n                        };\n                        return event;\n                },\n                dispatchEvent: function(elem, type, event) {\n                        if(elem.dispatchEvent) {\n                                elem.dispatchEvent(event);\n                        }else if( elem.fireEvent ) {\n                                elem.fireEvent(\"on\"+type, event);\n                        }\n                }\n        });\n        })(jQuery);\"\"\"\n    return script\n\n\ndef get_js_drag_and_drop_script():\n    # HTML5 Drag-and-Drop script (Requires extra parameters to work)\n    # param1 (WebElement): Source element to drag\n    # param2 (WebElement): Target element for the drop (Optional)\n    # param3 (int): Optional - Drop offset x relative to the target\n    # param4 (int): Optional - Drop offset y relative to the target\n    # param4 (int): Optional - Delay in milliseconds (default = 1ms)\n    # param5 (string): Optional - Key pressed (ALT or CTRL or SHIFT)\n    script = \"\"\"var t=arguments,e=t[0],n=t[1],i=t[2]||0,o=t[3]||0,r=t[4]||1,\n        a=t[5]||'',s='alt'===a||'\\ue00a'===a,l='ctrl'===a||'\\ue009'===a,\n        c='shift'===a||'\\ue008'===a,u=e.ownerDocument,\n        f=e.getBoundingClientRect(),g=n?n.getBoundingClientRect():f,\n        p=f.left+f.width/2,d=f.top+f.height/2,h=g.left+(i||g.width/2),\n        m=g.top+(o||g.height/2),v=u.elementFromPoint(p,d),\n        y=u.elementFromPoint(h,m);if(!v||!y){\n        var E=new Error('source or target element is not interactable');\n        throw E.code=15,E}var _={constructor:DataTransfer,effectAllowed:null,\n        dropEffect:null,types:[],files:Object.setPrototypeOf([],null),\n        _items:Object.setPrototypeOf([],{add:function(t,e){\n        this[this.length]={_data:''+t,kind:'string',\n        type:e,getAsFile:function(){},getAsString:function(t){t(this._data)}},\n        _.types.push(e)},remove:function(t){\n        Array.prototype.splice.call(this,65535&t,1),_.types.splice(65535&t,1)},\n        clear:function(t,e){this.length=0,_.types.length=0}}),\n        setData:function(t,e){this.clearData(t),this._items.add(e,t)},\n        getData:function(t){for(var e=this._items.length;\n        e--&&this._items[e].type!==t;);return e>=0?this._items[e]._data:null},\n        clearData:function(t){for(var e=this._items.length;\n        e--&&this._items[e].type!==t;);this._items.remove(e)},\n        setDragImage:function(t){}};function w(t,e,n,i){\n        for(var o=0;o<e.length;++o){var r=u.createEvent('MouseEvent');\n        r.initMouseEvent(e[o],!0,!0,u.defaultView,0,0,0,p,d,l,s,c,!1,0,null),\n        t.dispatchEvent(r)}i&&setTimeout(i,n)}function D(t,e,n,i){\n        var o=u.createEvent('DragEvent');o.initMouseEvent(\n        e,!0,!0,u.defaultView,0,0,0,p,d,l,s,c,!1,0,null),Object.setPrototypeOf(\n        o,null),o.dataTransfer=_,Object.setPrototypeOf(o,DragEvent.prototype),\n        t.dispatchEvent(o),i&&setTimeout(i,n)}\n        'items'in DataTransfer.prototype&&(_.items=_._items),\n        w(v,['pointerdown','mousedown'],1,function(){\n        for(var t=v;t&&!t.draggable;)t=t.parentElement;if(t&&t.contains(v)){\n        var e=y.getBoundingClientRect();D(v,'dragstart',r,function(){\n        var t=y.getBoundingClientRect();p=t.left+h-e.left,d=t.top+m-e.top,D(\n        y,'dragenter',1,function(){D(y,'dragover',r,\n        function(){D(u.elementFromPoint(p,d),'drop',1,function(){D(v,'dragend',\n        1,function(){w(u.elementFromPoint(p,d),\n        ['mouseup','pointerup'])})})})})})}})\"\"\"\n    return script\n\n\ndef get_drag_and_drop_with_offset_script(selector, x, y):\n    # This script uses pure JS (No jQuery)\n    script_a = \"\"\"\n        var source = document.querySelector(\"%s\");\n        var offsetX = %f;\n        var offsetY = %f;\n        \"\"\" % (\n        selector,\n        x,\n        y,\n    )\n    script_b = r\"\"\"\n        var rect = source.getBoundingClientRect();\n        var dragPt = {x: rect.left + (rect.width >> 1),\n                      y: rect.top + (rect.height >> 1)};\n        var dropPt = {x: dragPt.x + offsetX, y: dragPt.y + offsetY};\n        var target = document.elementFromPoint(dropPt.x, dropPt.y);\n        var dataTransfer = {\n          dropEffect: '',\n          effectAllowed: 'all',\n          files: [],\n          items: {},\n          types: [],\n          setData: function (format, data) {\n            this.items[format] = data;\n            this.types.push(format);\n          },\n          getData: function (format) {\n            return this.items[format];\n          },\n          clearData: function (format) { }\n        };\n        var emit = function (event, target, pt) {\n          var evt = document.createEvent('MouseEvent');\n          evt.initMouseEvent(event, true, true, window, 0, 0, 0, pt.x, pt.y,\n                             false, false, false, false, 0, null);\n          evt.dataTransfer = dataTransfer;\n          target.dispatchEvent(evt);\n        };\n        emit('mousedown', source, dragPt);\n        emit('mousemove', source, dragPt);\n        emit('mousemove', source, dropPt);\n        emit('mouseup',   source, dropPt);\"\"\"\n    script = script_a + script_b\n    return script\n\n\ndef clear_out_console_logs(driver):\n    with suppress(Exception):\n        # Clear out the current page log before navigating to a new page\n        # (To make sure that assert_no_js_errors() uses current results)\n        driver.get_log(\"browser\")\n\n\ndef _jq_format(code):\n    \"\"\"\n    DEPRECATED - Use re.escape() instead, which performs the intended action.\n    Use before throwing raw code such as 'div[tab=\"advanced\"]' into jQuery.\n    Selectors with quotes inside of quotes would otherwise break jQuery.\n    If you just want to escape quotes, there's escape_quotes_if_needed().\n    This is similar to \"json.dumps(value)\", but with one less layer of quotes.\n    \"\"\"\n    code = code.replace(\"\\\\\", \"\\\\\\\\\").replace(\"\\t\", \"\\\\t\").replace(\"\\n\", \"\\\\n\")\n    code = code.replace('\"', '\\\\\"').replace(\"'\", \"\\\\'\")\n    code = code.replace(\"\\v\", \"\\\\v\").replace(\"\\a\", \"\\\\a\").replace(\"\\f\", \"\\\\f\")\n    code = code.replace(\"\\b\", \"\\\\b\").replace(r\"\\u\", \"\\\\u\").replace(\"\\r\", \"\\\\r\")\n    return code\n"
  },
  {
    "path": "seleniumbase/fixtures/page_actions.py",
    "content": "\"\"\"This module contains useful methods for waiting on elements to load.\n\nThese methods improve and expand on existing WebDriver commands.\nImprovements include making WebDriver commands more robust and more reliable\nby giving page elements enough time to load before taking action on them.\n\nThe default option for searching for elements is by \"css selector\".\nThis can be changed by overriding the \"By\" parameter from this import:\n> from selenium.webdriver.common.by import By\nOptions are:\nBy.CSS_SELECTOR        # \"css selector\"\nBy.CLASS_NAME          # \"class name\"\nBy.ID                  # \"id\"\nBy.NAME                # \"name\"\nBy.LINK_TEXT           # \"link text\"\nBy.XPATH               # \"xpath\"\nBy.TAG_NAME            # \"tag name\"\nBy.PARTIAL_LINK_TEXT   # \"partial link text\"\n\"\"\"\nimport os\nimport time\nfrom contextlib import suppress\nfrom filelock import FileLock\nfrom selenium.common.exceptions import ElementNotInteractableException\nfrom selenium.common.exceptions import ElementNotVisibleException\nfrom selenium.common.exceptions import NoAlertPresentException\nfrom selenium.common.exceptions import NoSuchAttributeException\nfrom selenium.common.exceptions import NoSuchElementException\nfrom selenium.common.exceptions import NoSuchWindowException\nfrom selenium.common.exceptions import StaleElementReferenceException\nfrom selenium.webdriver.common.action_chains import ActionChains\nfrom selenium.webdriver.common.keys import Keys\nfrom seleniumbase.common.exceptions import LinkTextNotFoundException\nfrom seleniumbase.common.exceptions import TextNotVisibleException\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import page_utils\nfrom seleniumbase.fixtures import shared_utils\n\n\ndef is_element_present(driver, selector, by=\"css selector\"):\n    \"\"\"\n    Returns whether the specified element selector is present on the page.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is element present)\n    \"\"\"\n    if __is_cdp_swap_needed(driver):\n        return driver.cdp.is_element_present(selector)\n    selector, by = page_utils.swap_selector_and_by_if_reversed(selector, by)\n    try:\n        driver.find_element(by=by, value=selector)\n        return True\n    except Exception:\n        return False\n\n\ndef is_element_visible(driver, selector, by=\"css selector\"):\n    \"\"\"\n    Returns whether the specified element selector is visible on the page.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is element visible)\n    \"\"\"\n    if __is_cdp_swap_needed(driver):\n        return driver.cdp.is_element_visible(selector)\n    selector, by = page_utils.swap_selector_and_by_if_reversed(selector, by)\n    try:\n        element = driver.find_element(by=by, value=selector)\n        return element.is_displayed()\n    except Exception:\n        return False\n\n\ndef is_element_clickable(driver, selector, by=\"css selector\"):\n    \"\"\"\n    Returns whether the specified element selector is clickable.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is element clickable)\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    try:\n        element = driver.find_element(by=by, value=selector)\n        if element.is_displayed() and element.is_enabled():\n            return True\n        return False\n    except Exception:\n        return False\n\n\ndef is_element_enabled(driver, selector, by=\"css selector\"):\n    \"\"\"\n    Returns whether the specified element selector is enabled on the page.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is element enabled)\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    try:\n        element = driver.find_element(by=by, value=selector)\n        return element.is_enabled()\n    except Exception:\n        return False\n\n\ndef is_text_visible(driver, text, selector=\"html\", by=\"css selector\"):\n    \"\"\"\n    Returns whether the text substring is visible in the given selector.\n    @Params\n    driver - the webdriver object (required)\n    text - the text string to search for (required)\n    selector - the locator for identifying the page element\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is text visible)\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    selector, by = page_utils.swap_selector_and_by_if_reversed(selector, by)\n    text = str(text)\n    try:\n        element = driver.find_element(by=by, value=selector)\n        element_text = element.text\n        if shared_utils.is_safari(driver):\n            if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                element_text = element.get_attribute(\"value\")\n            else:\n                element_text = element.get_attribute(\"innerText\")\n        elif element.tag_name.lower() in [\"input\", \"textarea\"]:\n            element_text = element.get_property(\"value\")\n        return element.is_displayed() and text in element_text\n    except Exception:\n        return False\n\n\ndef is_exact_text_visible(driver, text, selector, by=\"css selector\"):\n    \"\"\"\n    Returns whether the exact text is visible in the given selector.\n    (Ignores leading and trailing whitespace)\n    @Params\n    driver - the webdriver object (required)\n    text - the text string to search for (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is text visible)\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    selector, by = page_utils.swap_selector_and_by_if_reversed(selector, by)\n    text = str(text)\n    try:\n        element = driver.find_element(by=by, value=selector)\n        element_text = element.text\n        if shared_utils.is_safari(driver):\n            if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                element_text = element.get_attribute(\"value\")\n            else:\n                element_text = element.get_attribute(\"innerText\")\n        elif element.tag_name.lower() in [\"input\", \"textarea\"]:\n            element_text = element.get_property(\"value\")\n        return (\n            element.is_displayed()\n            and text.strip() == element_text.strip()\n        )\n    except Exception:\n        return False\n\n\ndef is_attribute_present(\n    driver, selector, attribute, value=None, by=\"css selector\"\n):\n    \"\"\"\n    Returns whether the specified attribute is present in the given selector.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    attribute - the attribute that is expected for the element (required)\n    value - the attribute value that is expected (Default: None)\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is attribute present)\n    \"\"\"\n    if __is_cdp_swap_needed(driver):\n        return driver.cdp.is_attribute_present(\n            selector, attribute, value=value\n        )\n    _reconnect_if_disconnected(driver)\n    try:\n        element = driver.find_element(by=by, value=selector)\n        found_value = element.get_attribute(attribute)\n        if found_value is None:\n            return False\n        if value is not None:\n            if found_value == value:\n                return True\n            else:\n                return False\n        else:\n            return True\n    except Exception:\n        return False\n\n\ndef is_non_empty_text_visible(driver, selector, by=\"css selector\"):\n    \"\"\"\n    Returns whether the element has any text visible for the given selector.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    @Returns\n    Boolean (is any text visible in the element with the selector)\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    try:\n        element = driver.find_element(by=by, value=selector)\n        element_text = element.text\n        if shared_utils.is_safari(driver):\n            if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                element_text = element.get_attribute(\"value\")\n            else:\n                element_text = element.get_attribute(\"innerText\")\n        elif element.tag_name.lower() in [\"input\", \"textarea\"]:\n            element_text = element.get_property(\"value\")\n        element_text = element_text.strip()\n        return element.is_displayed() and len(element_text) > 0\n    except Exception:\n        return False\n\n\ndef hover_on_element(driver, selector, by=\"css selector\"):\n    \"\"\"\n    Fires the hover event for the specified element by the given selector.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    element = driver.find_element(by=by, value=selector)\n    hover = ActionChains(driver).move_to_element(element)\n    hover.perform()\n    return element\n\n\ndef hover_element(driver, element):\n    \"\"\"\n    Similar to hover_on_element(), but uses found element, not a selector.\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    hover = ActionChains(driver).move_to_element(element)\n    hover.perform()\n    return element\n\n\ndef timeout_exception(exception, message):\n    exc, msg = shared_utils.format_exc(exception, message)\n    raise exc(msg)\n\n\ndef hover_and_click(\n    driver,\n    hover_selector,\n    click_selector,\n    hover_by=\"css selector\",\n    click_by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n    js_click=False,\n):\n    \"\"\"\n    Fires the hover event for a specified element by a given selector, then\n    clicks on another element specified. Useful for dropdown hover based menus.\n    @Params\n    driver - the webdriver object (required)\n    hover_selector - the css selector to hover over (required)\n    click_selector - the css selector to click on (required)\n    hover_by - the hover selector type to search by (Default: \"css selector\")\n    click_by - the click selector type to search by (Default: \"css selector\")\n    timeout - number of seconds to wait for click element to appear after hover\n    js_click - the option to use js_click() instead of click() on the last part\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    element = driver.find_element(by=hover_by, value=hover_selector)\n    hover = ActionChains(driver).move_to_element(element)\n    for x in range(int(timeout * 10)):\n        try:\n            hover.perform()\n            element = driver.find_element(by=click_by, value=click_selector)\n            if js_click:\n                driver.execute_script(\"arguments[0].click();\", element)\n            else:\n                element.click()\n            return element\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    message = \"Element {%s} was not present after %s second%s!\" % (\n        click_selector,\n        timeout,\n        plural,\n    )\n    timeout_exception(NoSuchElementException, message)\n\n\ndef hover_element_and_click(\n    driver,\n    element,\n    click_selector,\n    click_by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n):\n    \"\"\"\n    Similar to hover_and_click(), but assumes top element is already found.\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    hover = ActionChains(driver).move_to_element(element)\n    for x in range(int(timeout * 10)):\n        try:\n            hover.perform()\n            element = driver.find_element(by=click_by, value=click_selector)\n            element.click()\n            return element\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    message = \"Element {%s} was not present after %s second%s!\" % (\n        click_selector,\n        timeout,\n        plural,\n    )\n    timeout_exception(NoSuchElementException, message)\n\n\ndef hover_element_and_double_click(\n    driver,\n    element,\n    click_selector,\n    click_by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n):\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    hover = ActionChains(driver).move_to_element(element)\n    for x in range(int(timeout * 10)):\n        try:\n            hover.perform()\n            element_2 = driver.find_element(by=click_by, value=click_selector)\n            actions = ActionChains(driver)\n            actions.move_to_element(element_2)\n            actions.double_click(element_2)\n            actions.perform()\n            return element_2\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    message = \"Element {%s} was not present after %s second%s!\" % (\n        click_selector,\n        timeout,\n        plural,\n    )\n    timeout_exception(NoSuchElementException, message)\n\n\ndef wait_for_element_present(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n    original_selector=None,\n    ignore_test_time_limit=False,\n):\n    \"\"\"\n    Searches for the specified element by the given selector. Returns the\n    element object if it exists in the HTML. (The element can be invisible.)\n    Raises NoSuchElementException if the element does not exist in the HTML\n    within the specified timeout.\n    @Params\n    driver - the webdriver object\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    original_selector - handle pre-converted \":contains(TEXT)\" selector\n    ignore_test_time_limit - ignore test time limit (NOT related to timeout)\n    @Returns\n    A web element object\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    element = None\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        if not ignore_test_time_limit:\n            shared_utils.check_if_time_limit_exceeded()\n        try:\n            element = driver.find_element(by=by, value=selector)\n            return element\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if not element:\n        if (\n            original_selector\n            and \":contains(\" in original_selector\n            and \"contains(.\" in selector\n        ):\n            selector = original_selector\n        message = \"Element {%s} was not present after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(NoSuchElementException, message)\n    else:\n        return element\n\n\ndef wait_for_element_visible(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n    original_selector=None,\n    ignore_test_time_limit=False,\n):\n    \"\"\"\n    Searches for the specified element by the given selector. Returns the\n    element object if the element is present and visible on the page.\n    Raises NoSuchElementException if the element does not exist in the HTML\n    within the specified timeout.\n    Raises ElementNotVisibleException if the element exists in the HTML,\n    but is not visible (eg. opacity is \"0\") within the specified timeout.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    original_selector - handle pre-converted \":contains(TEXT)\" selector\n    ignore_test_time_limit - ignore test time limit (NOT related to timeout)\n    @Returns\n    A web element object\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    element = None\n    is_present = False\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        if not ignore_test_time_limit:\n            shared_utils.check_if_time_limit_exceeded()\n        try:\n            element = driver.find_element(by=by, value=selector)\n            is_present = True\n            if element.is_displayed():\n                return element\n            else:\n                element = None\n                raise Exception()\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if not element and by != \"link text\":\n        if (\n            original_selector\n            and \":contains(\" in original_selector\n            and \"contains(.\" in selector\n        ):\n            selector = original_selector\n        if not is_present:\n            # The element does not exist in the HTML\n            message = \"Element {%s} was not present after %s second%s!\" % (\n                selector,\n                timeout,\n                plural,\n            )\n            timeout_exception(NoSuchElementException, message)\n        # The element exists in the HTML, but is not visible\n        message = \"Element {%s} was not visible after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(ElementNotVisibleException, message)\n    elif not element and by == \"link text\":\n        message = \"Link text {%s} was not found after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(LinkTextNotFoundException, message)\n    else:\n        return element\n\n\ndef wait_for_text_visible(\n    driver,\n    text,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    \"\"\"\n    Searches for the specified element by the given selector. Returns the\n    element object if the text is present in the element and visible\n    on the page.\n    Raises NoSuchElementException if the element does not exist in the HTML\n    within the specified timeout.\n    Raises ElementNotVisibleException if the element exists in the HTML,\n    but the text is not visible within the specified timeout.\n    @Params\n    driver - the webdriver object (required)\n    text - the text that is being searched for in the element (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    browser - used to handle a special edge case when using Safari\n    @Returns\n    A web element object that contains the text searched for\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    element = None\n    is_present = False\n    full_text = None\n    text = str(text)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        full_text = None\n        try:\n            element = driver.find_element(by=by, value=selector)\n            is_present = True\n            if (\n                element.tag_name.lower() in [\"input\", \"textarea\"]\n                and not shared_utils.is_safari(driver)\n            ):\n                if (\n                    element.is_displayed()\n                    and text in element.get_property(\"value\")\n                ):\n                    return element\n                else:\n                    if element.is_displayed():\n                        full_text = element.get_property(\"value\").strip()\n                    element = None\n                    raise Exception()\n            elif shared_utils.is_safari(driver):\n                text_attr = \"innerText\"\n                if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                    text_attr = \"value\"\n                if (\n                    element.is_displayed()\n                    and text in element.get_attribute(text_attr)\n                ):\n                    return element\n                else:\n                    if element.is_displayed():\n                        full_text = element.get_attribute(text_attr)\n                        full_text = full_text.strip()\n                    element = None\n                    raise Exception()\n            else:\n                if element.is_displayed() and text in element.text:\n                    return element\n                else:\n                    if element.is_displayed():\n                        full_text = element.text.strip()\n                    element = None\n                    raise Exception()\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if not element:\n        if not is_present:\n            # The element does not exist in the HTML\n            message = \"Element {%s} was not present after %s second%s!\" % (\n                selector,\n                timeout,\n                plural,\n            )\n            timeout_exception(NoSuchElementException, message)\n        # The element exists in the HTML, but the text is not visible\n        message = None\n        if not full_text or len(str(full_text.replace(\"\\n\", \"\"))) > 320:\n            message = (\n                \"Expected text substring {%s} for {%s} was not visible \"\n                \"after %s second%s!\" % (text, selector, timeout, plural)\n            )\n        else:\n            full_text = full_text.replace(\"\\n\", \"\\\\n \")\n            message = (\n                \"Expected text substring {%s} for {%s} was not visible \"\n                \"after %s second%s!\\n (Actual string found was {%s})\"\n                % (text, selector, timeout, plural, full_text)\n            )\n        timeout_exception(TextNotVisibleException, message)\n    else:\n        return element\n\n\ndef wait_for_exact_text_visible(\n    driver,\n    text,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    \"\"\"\n    Searches for the specified element by the given selector. Returns the\n    element object if the text matches exactly with the text in the element,\n    and the text is visible.\n    Raises NoSuchElementException if the element does not exist in the HTML\n    within the specified timeout.\n    Raises ElementNotVisibleException if the element exists in the HTML,\n    but the exact text is not visible within the specified timeout.\n    @Params\n    driver - the webdriver object (required)\n    text - the exact text that is expected for the element (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    browser - used to handle a special edge case when using Safari\n    @Returns\n    A web element object that contains the text searched for\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    element = None\n    is_present = False\n    actual_text = None\n    text = str(text)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        actual_text = None\n        try:\n            element = driver.find_element(by=by, value=selector)\n            is_present = True\n            if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                if (\n                    element.is_displayed()\n                    and text.strip() == element.get_property(\"value\").strip()\n                ):\n                    return element\n                else:\n                    if element.is_displayed():\n                        actual_text = element.get_property(\"value\").strip()\n                    element = None\n                    raise Exception()\n            elif shared_utils.is_safari(driver):\n                text_attr = \"innerText\"\n                if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                    text_attr = \"value\"\n                if element.is_displayed() and (\n                    text.strip() == element.get_attribute(text_attr).strip()\n                ):\n                    return element\n                else:\n                    if element.is_displayed():\n                        actual_text = element.get_attribute(text_attr)\n                        actual_text = actual_text.strip()\n                    element = None\n                    raise Exception()\n            else:\n                if (\n                    element.is_displayed()\n                    and text.strip() == element.text.strip()\n                ):\n                    return element\n                else:\n                    if element.is_displayed():\n                        actual_text = element.text.strip()\n                    element = None\n                    raise Exception()\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if not element:\n        if not is_present:\n            # The element does not exist in the HTML\n            message = \"Element {%s} was not present after %s second%s!\" % (\n                selector,\n                timeout,\n                plural,\n            )\n            timeout_exception(NoSuchElementException, message)\n        # The element exists in the HTML, but the exact text is not visible\n        message = None\n        if not actual_text or len(str(actual_text)) > 120:\n            message = (\n                \"Expected exact text {%s} for {%s} was not visible \"\n                \"after %s second%s!\" % (text, selector, timeout, plural)\n            )\n        else:\n            actual_text = actual_text.replace(\"\\n\", \"\\\\n\")\n            message = (\n                \"Expected exact text {%s} for {%s} was not visible \"\n                \"after %s second%s!\\n (Actual text was {%s})\"\n                % (text, selector, timeout, plural, actual_text)\n            )\n        timeout_exception(TextNotVisibleException, message)\n    else:\n        return element\n\n\ndef wait_for_any_of_elements_visible(\n    driver,\n    selectors,\n    timeout=settings.LARGE_TIMEOUT,\n    original_selectors=[],\n    ignore_test_time_limit=False,\n):\n    \"\"\"\n    Waits for at least one of the elements in the selector list to be visible.\n    Returns the first element that is found.\n    Raises NoSuchElementException if none of the elements exist in the HTML\n    within the specified timeout.\n    Raises ElementNotVisibleException if the element exists in the HTML,\n    but is not visible (eg. opacity is \"0\") within the specified timeout.\n    @Params\n    driver - the webdriver object (required)\n    selectors - the list of selectors for identifying page elements (required)\n    timeout - the time to wait for elements in seconds\n    original_selectors - handle pre-converted \":contains(TEXT)\" selectors\n    ignore_test_time_limit - ignore test time limit (NOT related to timeout)\n    @Returns\n    A web element object\n    \"\"\"\n    if not isinstance(selectors, (list, tuple)):\n        raise Exception(\"`selectors` must be a list or tuple!\")\n    if not selectors:\n        raise Exception(\"`selectors` cannot be an empty list!\")\n    _reconnect_if_disconnected(driver)\n    element = None\n    any_present = False\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        if not ignore_test_time_limit:\n            shared_utils.check_if_time_limit_exceeded()\n        try:\n            for selector in selectors:\n                by = \"css selector\"\n                if page_utils.is_xpath_selector(selector):\n                    by = \"xpath\"\n                try:\n                    element = driver.find_element(by=by, value=selector)\n                    any_present = True\n                    if element.is_displayed():\n                        return element\n                    element = None\n                except Exception:\n                    pass\n            raise Exception(\"Nothing found yet!\")\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if original_selectors:\n        selectors = original_selectors\n    if not element:\n        if not any_present:\n            # None of the elements exist in the HTML\n            message = (\n                \"None of the elements {%s} were present after %s second%s!\" % (\n                    str(selectors),\n                    timeout,\n                    plural,\n                )\n            )\n            timeout_exception(NoSuchElementException, message)\n        # At least one element exists in the HTML, but none are visible\n        message = \"None of the elements %s were visible after %s second%s!\" % (\n            str(selectors),\n            timeout,\n            plural,\n        )\n        timeout_exception(ElementNotVisibleException, message)\n    else:\n        return element\n\n\ndef wait_for_any_of_elements_present(\n    driver,\n    selectors,\n    timeout=settings.LARGE_TIMEOUT,\n    original_selectors=[],\n    ignore_test_time_limit=False,\n):\n    \"\"\"\n    Waits for at least one of the elements in the selector list to be present.\n    Visibility not required. (Eg. <head> hidden in the HTML)\n    Returns the first element that is found.\n    Raises NoSuchElementException if none of the elements exist in the HTML\n    within the specified timeout.\n    @Params\n    driver - the webdriver object (required)\n    selectors - the list of selectors for identifying page elements (required)\n    timeout - the time to wait for elements in seconds\n    original_selectors - handle pre-converted \":contains(TEXT)\" selectors\n    ignore_test_time_limit - ignore test time limit (NOT related to timeout)\n    @Returns\n    A web element object\n    \"\"\"\n    if not isinstance(selectors, (list, tuple)):\n        raise Exception(\"`selectors` must be a list or tuple!\")\n    if not selectors:\n        raise Exception(\"`selectors` cannot be an empty list!\")\n    _reconnect_if_disconnected(driver)\n    element = None\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        if not ignore_test_time_limit:\n            shared_utils.check_if_time_limit_exceeded()\n        try:\n            for selector in selectors:\n                by = \"css selector\"\n                if page_utils.is_xpath_selector(selector):\n                    by = \"xpath\"\n                try:\n                    element = driver.find_element(by=by, value=selector)\n                    return element\n                except Exception:\n                    pass\n            raise Exception(\"Nothing found yet!\")\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if original_selectors:\n        selectors = original_selectors\n    if not element:\n        # None of the elements exist in the HTML\n        message = (\n            \"None of the elements %s were present after %s second%s!\" % (\n                str(selectors),\n                timeout,\n                plural,\n            )\n        )\n        timeout_exception(NoSuchElementException, message)\n    else:\n        return element\n\n\ndef wait_for_attribute(\n    driver,\n    selector,\n    attribute,\n    value=None,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    \"\"\"\n    Searches for the specified element attribute by the given selector.\n    Returns the element object if the expected attribute is present\n    and the expected attribute value is present (if specified).\n    Raises NoSuchElementException if the element does not exist in the HTML\n    within the specified timeout.\n    Raises NoSuchAttributeException if the element exists in the HTML,\n    but the expected attribute/value is not present within the timeout.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    attribute - the attribute that is expected for the element (required)\n    value - the attribute value that is expected (Default: None)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for the element attribute in seconds\n    @Returns\n    A web element object that contains the expected attribute/value\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    element = None\n    element_present = False\n    attribute_present = False\n    found_value = None\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        try:\n            element = driver.find_element(by=by, value=selector)\n            element_present = True\n            attribute_present = False\n            found_value = element.get_attribute(attribute)\n            if found_value is not None:\n                attribute_present = True\n            else:\n                element = None\n                raise Exception()\n\n            if value is not None:\n                if found_value == value:\n                    return element\n                else:\n                    element = None\n                    raise Exception()\n            else:\n                return element\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if not element:\n        if not element_present:\n            # The element does not exist in the HTML\n            message = \"Element {%s} was not present after %s second%s!\" % (\n                selector,\n                timeout,\n                plural,\n            )\n            timeout_exception(NoSuchElementException, message)\n        if not attribute_present:\n            # The element does not have the attribute\n            message = (\n                \"Expected attribute {%s} of element {%s} was not present \"\n                \"after %s second%s!\" % (attribute, selector, timeout, plural)\n            )\n            timeout_exception(NoSuchAttributeException, message)\n        # The element attribute exists, but the expected value does not match\n        message = (\n            \"Expected value {%s} for attribute {%s} of element {%s} was not \"\n            \"present after %s second%s! (The actual value was {%s})\"\n            % (value, attribute, selector, timeout, plural, found_value)\n        )\n        timeout_exception(NoSuchAttributeException, message)\n    else:\n        return element\n\n\ndef wait_for_element_clickable(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n    original_selector=None,\n):\n    \"\"\"\n    Searches for the specified element by the given selector. Returns the\n    element object if the element is present, visible, & clickable on the page.\n    Raises NoSuchElementException if the element does not exist in the HTML\n    within the specified timeout.\n    Raises ElementNotVisibleException if the element exists in the HTML,\n    but is not visible (eg. opacity is \"0\") within the specified timeout.\n    Raises ElementNotInteractableException if the element is not clickable.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    original_selector - handle pre-converted \":contains(TEXT)\" selector\n    @Returns\n    A web element object\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    element = None\n    is_present = False\n    is_visible = False\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        try:\n            element = driver.find_element(by=by, value=selector)\n            is_present = True\n            if element.is_displayed():\n                is_visible = True\n                if element.is_enabled():\n                    return element\n                else:\n                    element = None\n                    raise Exception()\n            else:\n                element = None\n                raise Exception()\n        except Exception:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if not element and by != \"link text\":\n        if (\n            original_selector\n            and \":contains(\" in original_selector\n            and \"contains(.\" in selector\n        ):\n            selector = original_selector\n        if not is_present:\n            # The element does not exist in the HTML\n            message = \"Element {%s} was not present after %s second%s!\" % (\n                selector,\n                timeout,\n                plural,\n            )\n            timeout_exception(NoSuchElementException, message)\n        if not is_visible:\n            # The element exists in the HTML, but is not visible\n            message = \"Element {%s} was not visible after %s second%s!\" % (\n                selector,\n                timeout,\n                plural,\n            )\n            timeout_exception(ElementNotVisibleException, message)\n        # The element is visible in the HTML, but is not clickable\n        message = \"Element {%s} was not clickable after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(ElementNotInteractableException, message)\n    elif not element and by == \"link text\" and not is_visible:\n        message = \"Link text {%s} was not found after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(LinkTextNotFoundException, message)\n    elif not element and by == \"link text\" and is_visible:\n        message = \"Link text {%s} was not clickable after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(ElementNotInteractableException, message)\n    else:\n        return element\n\n\ndef wait_for_element_absent(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n    original_selector=None,\n):\n    \"\"\"\n    Searches for the specified element by the given selector.\n    Raises an exception if the element is still present after the\n    specified timeout.\n    @Params\n    driver - the webdriver object\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    original_selector - handle pre-converted \":contains(TEXT)\" selector\n    \"\"\"\n    if __is_cdp_swap_needed(driver):\n        if page_utils.is_valid_by(by):\n            original_selector = selector\n        elif page_utils.is_valid_by(selector):\n            original_selector = by\n        selector, by = page_utils.recalculate_selector(original_selector, by)\n        driver.cdp.wait_for_element_absent(selector)\n        return True\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        try:\n            driver.find_element(by=by, value=selector)\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n        except Exception:\n            return True\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if (\n        original_selector\n        and \":contains(\" in original_selector\n        and \"contains(.\" in selector\n    ):\n        selector = original_selector\n    message = \"Element {%s} was still present after %s second%s!\" % (\n        selector,\n        timeout,\n        plural,\n    )\n    timeout_exception(Exception, message)\n\n\ndef wait_for_element_not_visible(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n    original_selector=None,\n):\n    \"\"\"\n    Searches for the specified element by the given selector.\n    Raises an exception if the element is still visible after the\n    specified timeout.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for the element in seconds\n    original_selector - handle pre-converted \":contains(TEXT)\" selector\n    \"\"\"\n    if __is_cdp_swap_needed(driver):\n        if page_utils.is_valid_by(by):\n            original_selector = selector\n        elif page_utils.is_valid_by(selector):\n            original_selector = by\n        selector, by = page_utils.recalculate_selector(original_selector, by)\n        driver.cdp.wait_for_element_not_visible(selector)\n        return True\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        try:\n            element = driver.find_element(by=by, value=selector)\n            if element.is_displayed():\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.1)\n            else:\n                return True\n        except Exception:\n            return True\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if (\n        original_selector\n        and \":contains(\" in original_selector\n        and \"contains(.\" in selector\n    ):\n        selector = original_selector\n    message = \"Element {%s} was still visible after %s second%s!\" % (\n        selector,\n        timeout,\n        plural,\n    )\n    timeout_exception(Exception, message)\n\n\ndef wait_for_text_not_visible(\n    driver,\n    text,\n    selector=\"html\",\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    \"\"\"\n    Searches for the text in the element of the given selector on the page.\n    Returns True if the text is not visible on the page within the timeout.\n    Raises an exception if the text is still present after the timeout.\n    @Params\n    driver - the webdriver object (required)\n    text - the text that is being searched for in the element (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    @Returns\n    A web element object that contains the text searched for\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    text = str(text)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        if not is_text_visible(driver, text, selector, by=by):\n            return True\n        now_ms = time.time() * 1000.0\n        if now_ms >= stop_ms:\n            break\n        time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    message = \"Text {%s} in {%s} was still visible after %s second%s!\" % (\n        text,\n        selector,\n        timeout,\n        plural,\n    )\n    timeout_exception(Exception, message)\n\n\ndef wait_for_exact_text_not_visible(\n    driver,\n    text,\n    selector=\"html\",\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    \"\"\"\n    Searches for the text in the element of the given selector on the page.\n    Returns True if the element is missing the exact text within the timeout.\n    Raises an exception if the exact text is still present after the timeout.\n    @Params\n    driver - the webdriver object (required)\n    text - the text that is being searched for in the element (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    @Returns\n    A web element object that contains the text searched for\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    text = str(text)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        if not is_exact_text_visible(driver, text, selector, by=by):\n            return True\n        now_ms = time.time() * 1000.0\n        if now_ms >= stop_ms:\n            break\n        time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    message = (\n        \"Exact text {%s} for {%s} was still visible after %s second%s!\" % (\n            text,\n            selector,\n            timeout,\n            plural,\n        )\n    )\n    timeout_exception(Exception, message)\n\n\ndef wait_for_non_empty_text_visible(\n    driver, selector, by=\"css selector\", timeout=settings.LARGE_TIMEOUT,\n):\n    \"\"\"\n    Searches for any text in the element of the given selector.\n    Returns the element if it has visible text within the timeout.\n    Raises an exception if the element has no text within the timeout.\n    Whitespace-only text is considered empty text.\n    @Params\n    driver - the webdriver object (required)\n    text - the text that is being searched for in the element (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for elements in seconds\n    @Returns\n    The web element object that has text\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    element = None\n    visible = None\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        try:\n            element = None\n            visible = False\n            element = driver.find_element(by=by, value=selector)\n            if element.is_displayed():\n                visible = True\n            element_text = element.text\n            if shared_utils.is_safari(driver):\n                if element.tag_name.lower() in [\"input\", \"textarea\"]:\n                    element_text = element.get_attribute(\"value\")\n                else:\n                    element_text = element.get_attribute(\"innerText\")\n            elif element.tag_name.lower() in [\"input\", \"textarea\"]:\n                element_text = element.get_property(\"value\")\n            element_text = element_text.strip()\n            if element.is_displayed() and len(element_text) > 0:\n                return element\n        except Exception:\n            element = None\n        now_ms = time.time() * 1000.0\n        if now_ms >= stop_ms:\n            break\n        time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    if not element:\n        # The element does not exist in the HTML\n        message = \"Element {%s} was not present after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(NoSuchElementException, message)\n    elif not visible:\n        # The element exists in the HTML, but is not visible\n        message = \"Element {%s} was not visible after %s second%s!\" % (\n            selector,\n            timeout,\n            plural,\n        )\n        timeout_exception(ElementNotVisibleException, message)\n    else:\n        # The element exists in the HTML, but has no visible text\n        message = (\n            \"Element {%s} has no visible text after %s second%s!\"\n            \"\" % (selector, timeout, plural)\n        )\n        timeout_exception(TextNotVisibleException, message)\n\n\ndef wait_for_attribute_not_present(\n    driver,\n    selector,\n    attribute,\n    value=None,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    \"\"\"\n    Searches for the specified element attribute by the given selector.\n    Returns True if the attribute isn't present on the page within the timeout.\n    Also returns True if the element is not present within the timeout.\n    Raises an exception if the attribute is still present after the timeout.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    attribute - the element attribute (required)\n    value - the attribute value (Default: None)\n    by - the type of selector being used (Default: \"css selector\")\n    timeout - the time to wait for the element attribute in seconds\n    \"\"\"\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        if not is_attribute_present(\n            driver, selector, attribute, value=value, by=by\n        ):\n            return True\n        now_ms = time.time() * 1000.0\n        if now_ms >= stop_ms:\n            break\n        time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    message = (\n        \"Attribute {%s} of element {%s} was still present after %s second%s!\"\n        \"\" % (attribute, selector, timeout, plural)\n    )\n    if value:\n        message = (\n            \"Value {%s} for attribute {%s} of element {%s} was still present \"\n            \"after %s second%s!\"\n            \"\" % (value, attribute, selector, timeout, plural)\n        )\n    timeout_exception(Exception, message)\n\n\ndef find_visible_elements(driver, selector, by=\"css selector\", limit=0):\n    \"\"\"\n    Finds all WebElements that match a selector and are visible.\n    Similar to webdriver.find_elements().\n    If \"limit\" is set and > 0, will only return that many elements.\n    @Params\n    driver - the webdriver object (required)\n    selector - the locator for identifying the page element (required)\n    by - the type of selector being used (Default: \"css selector\")\n    limit - the maximum number of elements to return if > 0.\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    elements = driver.find_elements(by=by, value=selector)\n    if limit and limit > 0 and len(elements) > limit:\n        elements = elements[:limit]\n    try:\n        v_elems = [element for element in elements if element.is_displayed()]\n        return v_elems\n    except (StaleElementReferenceException, ElementNotInteractableException):\n        time.sleep(0.1)\n        elements = driver.find_elements(by=by, value=selector)\n        extra_elements = []\n        if limit and limit > 0 and len(elements) > limit:\n            elements = elements[:limit]\n            extra_elements = elements[limit:]\n        v_elems = []\n        for element in elements:\n            if element.is_displayed():\n                v_elems.append(element)\n        if extra_elements and limit and len(v_elems) < limit:\n            for element in extra_elements:\n                if element.is_displayed():\n                    v_elems.append(element)\n                    if len(v_elems) >= limit:\n                        break\n        return v_elems\n\n\ndef save_screenshot(\n    driver, name, folder=None, selector=None, by=\"css selector\"\n):\n    \"\"\"\n    Saves a screenshot of the current page.\n    If no folder is specified, uses the folder where pytest was called.\n    The screenshot will include the entire page unless a selector is given.\n    If a provided selector is not found, then takes a full-page screenshot.\n    If the folder provided doesn't exist, it will get created.\n    The screenshot will be in PNG format: (*.png)\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    if not name.endswith(\".png\"):\n        name = name + \".png\"\n    if folder:\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, folder)\n        if not os.path.exists(file_path):\n            os.makedirs(file_path)\n        screenshot_path = os.path.join(file_path, name)\n    else:\n        screenshot_path = name\n    if selector:\n        try:\n            element = driver.find_element(by=by, value=selector)\n            element_png = element.screenshot_as_png\n            with open(screenshot_path, \"wb\") as file:\n                file.write(element_png)\n        except Exception:\n            if driver:\n                driver.get_screenshot_as_file(screenshot_path)\n            else:\n                pass\n    else:\n        if driver:\n            driver.get_screenshot_as_file(screenshot_path)\n        else:\n            pass\n\n\ndef save_page_source(driver, name, folder=None):\n    \"\"\"\n    Saves the page HTML to the current directory (or given subfolder).\n    If the folder specified doesn't exist, it will get created.\n    @Params\n    name - The file name to save the current page's HTML to.\n    folder - The folder to save the file to. (Default = current folder)\n    \"\"\"\n    from seleniumbase.core import log_helper\n\n    if not __is_cdp_swap_needed(driver):\n        _reconnect_if_disconnected(driver)  # If disconnected without CDP\n    if not name.endswith(\".html\"):\n        name = name + \".html\"\n    if folder:\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, folder)\n        if not os.path.exists(file_path):\n            os.makedirs(file_path)\n        html_file_path = os.path.join(file_path, name)\n    else:\n        html_file_path = name\n    page_source = None\n    if __is_cdp_swap_needed(driver):\n        page_source = driver.cdp.get_page_source()\n    else:\n        page_source = driver.page_source\n    rendered_source = log_helper.get_html_source_with_base_href(\n        driver, page_source\n    )\n    html_file = open(html_file_path, mode=\"w+\", encoding=\"utf-8\")\n    html_file.write(rendered_source)\n    html_file.close()\n\n\ndef wait_for_and_accept_alert(driver, timeout=settings.LARGE_TIMEOUT):\n    \"\"\"\n    Wait for and accept an alert. Returns the text from the alert.\n    @Params\n    driver - the webdriver object (required)\n    timeout - the time to wait for the alert in seconds\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    alert = wait_for_and_switch_to_alert(driver, timeout)\n    alert_text = alert.text\n    alert.accept()\n    return alert_text\n\n\ndef wait_for_and_dismiss_alert(driver, timeout=settings.LARGE_TIMEOUT):\n    \"\"\"\n    Wait for and dismiss an alert. Returns the text from the alert.\n    @Params\n    driver - the webdriver object (required)\n    timeout - the time to wait for the alert in seconds\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    alert = wait_for_and_switch_to_alert(driver, timeout)\n    alert_text = alert.text\n    alert.dismiss()\n    return alert_text\n\n\ndef wait_for_and_switch_to_alert(driver, timeout=settings.LARGE_TIMEOUT):\n    \"\"\"\n    Wait for a browser alert to appear, and switch to it. This should be usable\n    as a drop-in replacement for driver.switch_to.alert when the alert box\n    may not exist yet.\n    @Params\n    driver - the webdriver object (required)\n    timeout - the time to wait for the alert in seconds\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        try:\n            alert = driver.switch_to.alert\n            # Raises exception if no alert present\n            dummy_variable = alert.text  # noqa\n            return alert\n        except NoAlertPresentException:\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    message = \"Alert was not present after %s seconds!\" % timeout\n    timeout_exception(Exception, message)\n\n\ndef switch_to_frame(\n    driver, frame, timeout=settings.SMALL_TIMEOUT, invisible=False\n):\n    \"\"\"\n    Wait for an iframe to appear, and switch to it. This should be\n    usable as a drop-in replacement for driver.switch_to.frame().\n    @Params\n    driver - the webdriver object (required)\n    frame - the frame element, name, id, index, or selector\n    timeout - the time to wait for the alert in seconds\n    invisible - if True, the iframe can be invisible\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    for x in range(int(timeout * 10)):\n        shared_utils.check_if_time_limit_exceeded()\n        try:\n            driver.switch_to.frame(frame)\n            return True\n        except Exception:\n            if isinstance(frame, str):\n                by = None\n                if page_utils.is_xpath_selector(frame):\n                    by = \"xpath\"\n                else:\n                    by = \"css selector\"\n                if (\n                    is_element_visible(driver, frame, by=by)\n                    or (invisible and is_element_present(driver, frame, by=by))\n                ):\n                    with suppress(Exception):\n                        element = driver.find_element(by=by, value=frame)\n                        driver.switch_to.frame(element)\n                        return True\n            now_ms = time.time() * 1000.0\n            if now_ms >= stop_ms:\n                break\n            time.sleep(0.1)\n    plural = \"s\"\n    if timeout == 1:\n        plural = \"\"\n    presence = \"visible\"\n    if invisible:\n        presence = \"present\"\n    message = \"Frame {%s} was not %s after %s second%s!\" % (\n        frame,\n        presence,\n        timeout,\n        plural,\n    )\n    timeout_exception(Exception, message)\n\n\ndef __switch_to_window(driver, window_handle, uc_lock=True):\n    if getattr(driver, \"_is_using_uc\", None) and uc_lock:\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\n        with gui_lock:\n            driver.switch_to.window(window_handle)\n    else:\n        driver.switch_to.window(window_handle)\n    return True\n\n\ndef switch_to_window(\n    driver,\n    window,\n    timeout=settings.SMALL_TIMEOUT,\n    uc_lock=True,\n):\n    \"\"\"\n    Wait for a window to appear, and switch to it. This should be usable\n    as a drop-in replacement for driver.switch_to.window().\n    @Params\n    driver - the webdriver object (required)\n    window - the window index or window handle\n    timeout - the time to wait for the window in seconds\n    uc_lock - if UC Mode and True, switch_to_window() uses thread-locking\n    \"\"\"\n    _reconnect_if_disconnected(driver)\n    if window == -1:\n        window = len(driver.window_handles) - 1\n    start_ms = time.time() * 1000.0\n    stop_ms = start_ms + (timeout * 1000.0)\n    if isinstance(window, int):\n        if shared_utils.is_safari(driver):\n            # Reversed window_handles on Safari\n            window = len(driver.window_handles) - 1 - window\n            if window < 0:\n                window = 0\n        for x in range(int(timeout * 10)):\n            shared_utils.check_if_time_limit_exceeded()\n            try:\n                window_handle = driver.window_handles[window]\n                __switch_to_window(driver, window_handle, uc_lock=uc_lock)\n                return True\n            except IndexError:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.1)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        message = \"Window {%s} was not present after %s second%s!\" % (\n            window,\n            timeout,\n            plural,\n        )\n        timeout_exception(Exception, message)\n    else:\n        window_handle = window\n        for x in range(int(timeout * 10)):\n            shared_utils.check_if_time_limit_exceeded()\n            try:\n                __switch_to_window(driver, window_handle, uc_lock=uc_lock)\n                return True\n            except NoSuchWindowException:\n                now_ms = time.time() * 1000.0\n                if now_ms >= stop_ms:\n                    break\n                time.sleep(0.1)\n        plural = \"s\"\n        if timeout == 1:\n            plural = \"\"\n        message = \"Window {%s} was not present after %s second%s!\" % (\n            window,\n            timeout,\n            plural,\n        )\n        timeout_exception(Exception, message)\n\n\n############\n\n# Special methods for use with UC Mode\n\ndef _reconnect_if_disconnected(driver):\n    if (\n        getattr(driver, \"_is_using_uc\", None)\n        and hasattr(driver, \"is_connected\")\n        and not driver.is_connected()\n    ):\n        with suppress(Exception):\n            driver.connect()\n\n\ndef __is_cdp_swap_needed(driver):\n    \"\"\"If the driver is disconnected, use a CDP method when available.\"\"\"\n    return shared_utils.is_cdp_swap_needed(driver)\n\n\n############\n\n# Support methods for direct use from driver\n\ndef open_url(driver, url):\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.open(url)\n        return\n    elif (\n        getattr(driver, \"_is_using_uc\", None)\n        # and getattr(driver, \"_is_using_auth\", None)\n        and not getattr(driver, \"_is_using_cdp\", None)\n    ):\n        # Auth in UC Mode requires CDP Mode\n        # (and now we're always forcing it)\n        driver.uc_activate_cdp_mode(url)\n        return\n    elif (\n        getattr(driver, \"_is_using_uc\", None)\n        and getattr(driver, \"_is_using_cdp\", None)\n    ):\n        driver.disconnect()\n        driver.cdp.open(url)\n        return\n    url = str(url).strip()  # Remove leading and trailing whitespace\n    if not page_utils.looks_like_a_page_url(url):\n        if page_utils.is_valid_url(\"https://\" + url):\n            url = \"https://\" + url\n    driver.get(url)\n\n\ndef click(driver, selector, by=\"css selector\", timeout=settings.SMALL_TIMEOUT):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.click(selector)\n        return\n    element = wait_for_element_clickable(\n        driver, selector, by=by, timeout=timeout\n    )\n    element.click()\n\n\ndef click_link(driver, link_text, timeout=settings.SMALL_TIMEOUT):\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.click_link(link_text)\n        return\n    element = wait_for_element_clickable(\n        driver, link_text, by=\"link text\", timeout=timeout\n    )\n    element.click()\n\n\ndef click_if_visible(\n    driver, selector, by=\"css selector\", timeout=0\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.click_if_visible(selector)\n        return\n    if is_element_visible(driver, selector, by=by):\n        click(driver, selector, by=by, timeout=1)\n    elif timeout > 0:\n        with suppress(Exception):\n            wait_for_element_visible(\n                driver, selector, by=by, timeout=timeout\n            )\n        if is_element_visible(driver, selector, by=by):\n            click(driver, selector, by=by, timeout=1)\n\n\ndef click_active_element(driver):\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.click_active_element()\n        return\n    driver.execute_script(\"document.activeElement.click();\")\n\n\ndef js_click(\n    driver, selector, by=\"css selector\", timeout=settings.SMALL_TIMEOUT\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.click(selector)\n        return\n    element = wait_for_element_present(\n        driver, selector, by=by, timeout=timeout\n    )\n    if not element.is_displayed() or not element.is_enabled():\n        time.sleep(0.2)  # If not clickable, wait a bit longer before clicking\n        element = wait_for_element_present(driver, selector, by=by, timeout=1)\n    script = (\n        \"\"\"var simulateClick = function (elem) {\n               var evt = new MouseEvent('click', {\n                   bubbles: true,\n                   cancelable: true,\n                   view: window\n               });\n               var canceled = !elem.dispatchEvent(evt);\n           };\n           var someLink = arguments[0];\n           simulateClick(someLink);\"\"\"\n    )\n    driver.execute_script(script, element)\n\n\ndef send_keys(\n    driver, selector, text, by=\"css selector\", timeout=settings.LARGE_TIMEOUT\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.send_keys(selector, text)\n        return\n    element = wait_for_element_present(\n        driver, selector, by=by, timeout=timeout\n    )\n    if not text.endswith(\"\\n\"):\n        element.send_keys(text)\n    else:\n        element.send_keys(text[:-1])\n        element.submit()\n\n\ndef press_keys(\n    driver, selector, text, by=\"css selector\", timeout=settings.LARGE_TIMEOUT\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.press_keys(selector, text)\n        return\n    element = wait_for_element_present(\n        driver, selector, by=by, timeout=timeout\n    )\n    if not text.endswith(\"\\n\"):\n        for key in text:\n            element.send_keys(key)\n    else:\n        for key in text[:-1]:\n            element.send_keys(key)\n        element.send_keys(Keys.RETURN)\n\n\ndef update_text(\n    driver, selector, text, by=\"css selector\", timeout=settings.LARGE_TIMEOUT\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.type(selector, text)\n        return\n    element = wait_for_element_clickable(\n        driver, selector, by=by, timeout=timeout\n    )\n    element.clear()\n    if not text.endswith(\"\\n\"):\n        element.send_keys(text)\n    else:\n        element.send_keys(text[:-1])\n        element.submit()\n\n\ndef submit(driver, selector, by=\"css selector\"):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.send_keys(selector, \"\\r\\n\")\n        return\n    element = wait_for_element_clickable(\n        driver, selector, by=by, timeout=settings.SMALL_TIMEOUT\n    )\n    element.submit()\n\n\ndef has_attribute(\n    driver, selector, attribute, value=None, by=\"css selector\"\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    return is_attribute_present(\n        driver, selector, attribute, value=value, by=by\n    )\n\n\ndef assert_element_visible(\n    driver, selector, by=\"css selector\", timeout=settings.SMALL_TIMEOUT\n):\n    original_selector = None\n    if page_utils.is_valid_by(by):\n        original_selector = selector\n    elif page_utils.is_valid_by(selector):\n        original_selector = by\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.assert_element(selector)\n        return True\n    wait_for_element_visible(\n        driver,\n        selector,\n        by=by,\n        timeout=timeout,\n        original_selector=original_selector,\n    )\n\n\ndef assert_element_present(\n    driver, selector, by=\"css selector\", timeout=settings.SMALL_TIMEOUT\n):\n    original_selector = None\n    if page_utils.is_valid_by(by):\n        original_selector = selector\n    elif page_utils.is_valid_by(selector):\n        original_selector = by\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.assert_element_present(selector)\n        return True\n    wait_for_element_present(\n        driver,\n        selector,\n        by=by,\n        timeout=timeout,\n        original_selector=original_selector,\n    )\n\n\ndef assert_element_not_visible(\n    driver, selector, by=\"css selector\", timeout=settings.SMALL_TIMEOUT\n):\n    original_selector = None\n    if page_utils.is_valid_by(by):\n        original_selector = selector\n    elif page_utils.is_valid_by(selector):\n        original_selector = by\n    selector, by = page_utils.recalculate_selector(selector, by)\n    wait_for_element_not_visible(\n        driver,\n        selector,\n        by=by,\n        timeout=timeout,\n        original_selector=original_selector,\n    )\n\n\ndef assert_text(\n    driver,\n    text,\n    selector=\"html\",\n    by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.assert_text(text, selector)\n        return True\n    wait_for_text_visible(\n        driver, text.strip(), selector, by=by, timeout=timeout\n    )\n\n\ndef assert_exact_text(\n    driver,\n    text,\n    selector=\"html\",\n    by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        driver.cdp.assert_exact_text(text, selector)\n        return True\n    wait_for_exact_text_visible(\n        driver, text.strip(), selector, by=by, timeout=timeout\n    )\n\n\ndef assert_non_empty_text(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    wait_for_non_empty_text_visible(\n        driver, selector, by=by, timeout=timeout\n    )\n\n\ndef assert_text_not_visible(\n    driver,\n    text,\n    selector=\"html\",\n    by=\"css selector\",\n    timeout=settings.SMALL_TIMEOUT,\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    wait_for_text_not_visible(\n        driver, text.strip(), selector, by=by, timeout=timeout\n    )\n\n\ndef wait_for_element(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    original_selector = None\n    if page_utils.is_valid_by(by):\n        original_selector = selector\n    elif page_utils.is_valid_by(selector):\n        original_selector = by\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        return driver.cdp.select(selector)\n    return wait_for_element_visible(\n        driver=driver,\n        selector=selector,\n        by=by,\n        timeout=timeout,\n        original_selector=original_selector,\n    )\n\n\ndef wait_for_selector(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    original_selector = None\n    if page_utils.is_valid_by(by):\n        original_selector = selector\n    elif page_utils.is_valid_by(selector):\n        original_selector = by\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        return driver.cdp.select(selector)\n    return wait_for_element_present(\n        driver=driver,\n        selector=selector,\n        by=by,\n        timeout=timeout,\n        original_selector=original_selector,\n    )\n\n\ndef wait_for_text(\n    driver,\n    text,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    if __is_cdp_swap_needed(driver):\n        return driver.cdp.find_element(selector)\n    return wait_for_text_visible(\n        driver=driver,\n        text=text,\n        selector=selector,\n        by=by,\n        timeout=timeout,\n    )\n\n\ndef wait_for_exact_text(\n    driver,\n    text,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    return wait_for_exact_text_visible(\n        driver=driver,\n        text=text,\n        selector=selector,\n        by=by,\n        timeout=timeout,\n    )\n\n\ndef wait_for_non_empty_text(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT,\n):\n    selector, by = page_utils.recalculate_selector(selector, by)\n    return wait_for_non_empty_text_visible(\n        driver=driver,\n        selector=selector,\n        by=by,\n        timeout=timeout,\n    )\n\n\ndef get_text(\n    driver,\n    selector,\n    by=\"css selector\",\n    timeout=settings.LARGE_TIMEOUT\n):\n    if __is_cdp_swap_needed(driver):\n        return driver.cdp.get_text(selector)\n    element = wait_for_element(\n        driver=driver,\n        selector=selector,\n        by=by,\n        timeout=timeout,\n    )\n    element_text = element.text\n    if shared_utils.is_safari(driver):\n        if element.tag_name.lower() in [\"input\", \"textarea\"]:\n            element_text = element.get_attribute(\"value\")\n        else:\n            element_text = element.get_attribute(\"innerText\")\n    elif element.tag_name.lower() in [\"input\", \"textarea\"]:\n        element_text = element.get_property(\"value\")\n    return element_text\n"
  },
  {
    "path": "seleniumbase/fixtures/page_utils.py",
    "content": "\"\"\"This module contains useful utility methods\"\"\"\nimport fasteners\nimport os\nimport re\nimport requests\nfrom selenium.webdriver.common.by import By\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import css_to_xpath\n\n\ndef get_domain_url(url):\n    \"\"\"\n    Use this to convert a url like this:\n    https://blog.xkcd.com/2014/07/22/what-if-book-tour/\n    Into this:\n    https://blog.xkcd.com\n    \"\"\"\n    if not url.startswith((\"http://\", \"https://\")):\n        return url\n    url_header = url.split(\"://\")[0]\n    simple_url = url.split(\"://\")[1]\n    base_url = simple_url.split(\"/\")[0]\n    domain_url = url_header + \"://\" + base_url\n    return domain_url\n\n\ndef is_valid_by(by):\n    return by in [\n        \"css selector\", \"class name\", \"id\", \"name\",\n        \"link text\", \"xpath\", \"tag name\", \"partial link text\",\n    ]\n\n\ndef swap_selector_and_by_if_reversed(selector, by):\n    if not is_valid_by(by) and is_valid_by(selector):\n        selector, by = by, selector\n    return (selector, by)\n\n\ndef is_xpath_selector(selector):\n    \"\"\"Determine if a selector is an xpath selector.\"\"\"\n    return selector.startswith((\"/\", \"./\", \"(\"))\n\n\ndef is_link_text_selector(selector):\n    \"\"\"Determine if a selector is a link text selector.\"\"\"\n    return selector.startswith((\"link=\", \"link_text=\", \"text=\"))\n\n\ndef is_partial_link_text_selector(selector):\n    \"\"\"Determine if a selector is a partial link text selector.\"\"\"\n    return selector.startswith((\n        \"partial_link=\", \"partial_link_text=\", \"partial_text=\",\n        \"p_link=\", \"p_link_text=\", \"p_text=\"\n    ))\n\n\ndef is_name_selector(selector):\n    \"\"\"Determine if a selector is a name selector.\"\"\"\n    return selector.startswith((\"name=\", \"&\"))\n\n\ndef recalculate_selector(selector, by, xp_ok=True):\n    \"\"\"Use autodetection to return the correct selector with \"by\" updated.\n    If \"xp_ok\" is False, don't call convert_css_to_xpath(), which is\n    used to make the \":contains()\" selector valid outside of JS calls.\n    Returns a (selector, by) tuple.\"\"\"\n    _type = type(selector)\n    if _type is not str:\n        msg = \"Expecting a selector of type: \\\"<class 'str'>\\\" (string)!\"\n        raise Exception('Invalid selector type: \"%s\"\\n%s' % (_type, msg))\n    _by_type = type(by)\n    if _by_type is not str:\n        msg = \"Expecting a `by` of type: \\\"<class 'str'>\\\" (string)!\"\n        raise Exception('Invalid `by` type: \"%s\"\\n%s' % (_by_type, msg))\n    if not is_valid_by(by) and is_valid_by(selector):\n        selector, by = swap_selector_and_by_if_reversed(selector, by)\n    if is_xpath_selector(selector):\n        by = By.XPATH\n    if is_link_text_selector(selector):\n        selector = get_link_text_from_selector(selector)\n        by = By.LINK_TEXT\n    if is_partial_link_text_selector(selector):\n        selector = get_partial_link_text_from_selector(selector)\n        by = By.PARTIAL_LINK_TEXT\n    if is_name_selector(selector):\n        name = get_name_from_selector(selector)\n        selector = '[name=\"%s\"]' % name\n        by = By.CSS_SELECTOR\n    if xp_ok and \":contains(\" in selector and by == By.CSS_SELECTOR:\n        selector = css_to_xpath.convert_css_to_xpath(selector)\n        by = By.XPATH\n    if by == \"\":\n        by = By.CSS_SELECTOR\n    if not is_valid_by(by):\n        valid_by_options = [\n            \"css selector\", \"link text\", \"partial link text\",\n            \"name\", \"xpath\", \"id\", \"tag name\", \"class name\",\n        ]\n        msg = \"Choose a `by` from: %s.\" % valid_by_options\n        raise Exception('Invalid `by`: \"%s\"\\n%s' % (by, msg))\n    return (selector, by)\n\n\ndef looks_like_a_page_url(url):\n    \"\"\"Returns True if the url parameter looks like a URL. This method\n    is slightly more lenient than page_utils.is_valid_url(url) due to\n    possible typos when calling self.get(url), which will try to\n    navigate to the page if a URL is detected, but will instead call\n    self.get_element(URL_AS_A_SELECTOR) if the input is not a URL.\"\"\"\n    return url.startswith((\n        \"http:\", \"https:\", \"://\", \"about:\", \"blob:\", \"chrome:\", \"opera:\",\n        \"data:\", \"edge:\", \"file:\", \"view-source:\", \"chrome-search:\",\n        \"chrome-extension:\", \"chrome-untrusted:\", \"isolated-app:\",\n        \"chrome-devtools:\", \"devtools:\", \"brave:\", \"comet:\", \"atlas:\"\n    ))\n\n\ndef get_link_text_from_selector(selector):\n    \"\"\"Get the link text from a link text selector.\"\"\"\n    if selector.startswith(\"link=\"):\n        return selector[len(\"link=\"):]\n    elif selector.startswith(\"link_text=\"):\n        return selector[len(\"link_text=\"):]\n    elif selector.startswith(\"text=\"):\n        return selector[len(\"text=\"):]\n    return selector\n\n\ndef get_partial_link_text_from_selector(selector):\n    \"\"\"Get the partial link text from a partial link selector.\"\"\"\n    if selector.startswith(\"partial_link=\"):\n        return selector[len(\"partial_link=\"):]\n    elif selector.startswith(\"partial_link_text=\"):\n        return selector[len(\"partial_link_text=\"):]\n    elif selector.startswith(\"partial_text=\"):\n        return selector[len(\"partial_text=\"):]\n    elif selector.startswith(\"p_link=\"):\n        return selector[len(\"p_link=\"):]\n    elif selector.startswith(\"p_link_text=\"):\n        return selector[len(\"p_link_text=\"):]\n    elif selector.startswith(\"p_text=\"):\n        return selector[len(\"p_text=\"):]\n    return selector\n\n\ndef get_name_from_selector(selector):\n    \"\"\"Get the name from a name selector.\"\"\"\n    if selector.startswith(\"name=\"):\n        return selector[len(\"name=\"):]\n    if selector.startswith(\"&\"):\n        return selector[len(\"&\"):]\n    return selector\n\n\ndef is_valid_url(url):\n    regex = re.compile(\n        r\"^(?:http)s?://\"  # http:// or https://\n        r\"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+\"\n        r\"(?:[A-Z]{2,6}\\.?|[A-Z0-9-]{2,}\\.?)|\"  # domain...\n        r\"localhost|\"  # localhost...\n        r\"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\"  # ...or ip\n        r\"(?::\\d+)?\"  # optional port\n        r\"(?:/?|[/?]\\S+)$\",\n        re.IGNORECASE,\n    )\n    if (\n        regex.match(url)\n        or url.startswith((\n            \"about:\", \"blob:\", \"chrome:\", \"data:\", \"edge:\", \"file:\"\n        ))\n    ):\n        return True\n    else:\n        return False\n\n\ndef _get_unique_links(page_url, soup):\n    \"\"\"Returns all unique links.\n    Includes:\n        \"a\"->\"href\", \"img\"->\"src\", \"link\"->\"href\", and \"script\"->\"src\" links.\n    \"\"\"\n    if not page_url.startswith(\"http://\") and not page_url.startswith(\n        \"https://\"\n    ):\n        return []\n    prefix = \"http:\"\n    if page_url.startswith(\"https:\"):\n        prefix = \"https:\"\n    simple_url = page_url.split(\"://\")[1]\n    base_url = simple_url.split(\"/\")[0]\n    full_base_url = prefix + \"//\" + base_url\n\n    raw_links = []\n    raw_unique_links = []\n\n    # Get \"href\" from all \"a\" tags\n    links = soup.find_all(\"a\")\n    for link in links:\n        raw_links.append(link.get(\"href\"))\n\n    # Get \"src\" from all \"img\" tags\n    img_links = soup.find_all(\"img\")\n    for img_link in img_links:\n        raw_links.append(img_link.get(\"src\"))\n\n    # Get \"href\" from all \"link\" tags\n    links = soup.find_all(\"link\")\n    for link in links:\n        raw_links.append(link.get(\"href\"))\n\n    # Get \"src\" from all \"script\" tags\n    img_links = soup.find_all(\"script\")\n    for img_link in img_links:\n        raw_links.append(img_link.get(\"src\"))\n\n    for link in raw_links:\n        if link not in raw_unique_links:\n            raw_unique_links.append(link)\n\n    unique_links = []\n    for link in raw_unique_links:\n        if link and len(link) > 1:\n            if link.startswith(\"//\"):\n                link = prefix + link\n            elif link.startswith(\"/\"):\n                link = full_base_url + link\n            elif link == \"./\":\n                link = page_url\n            elif link.startswith(\"./\"):\n                f_b_url = full_base_url\n                if len(simple_url.split(\"/\")) > 1:\n                    f_b_url = full_base_url + \"/\" + simple_url.split(\"/\")[1]\n                link = f_b_url + link[1:]\n            elif link.startswith(\"../\"):\n                if page_url.endswith(\"/\"):\n                    link = page_url + link\n                else:\n                    link = page_url + \"/\" + link\n            elif link.startswith(\"#\"):\n                link = full_base_url + link\n            elif \"//\" not in link:\n                f_b_url = full_base_url\n                if len(simple_url.split(\"/\")) > 1:\n                    f_b_url = full_base_url + \"/\" + simple_url.split(\"/\")[1]\n                link = f_b_url + \"/\" + link\n            elif link.startswith('\"') and link.endswith('\"') and len(link) > 4:\n                link = link[1:-1]\n            else:\n                pass\n            unique_links.append(link)\n\n    links = unique_links\n    links = list(set(links))  # Make sure all duplicates were removed\n    links = sorted(links)  # Sort all the links alphabetically\n    return links\n\n\ndef _get_link_status_code(\n    link,\n    allow_redirects=False,\n    timeout=5,\n    verify=False,\n):\n    \"\"\"Get the status code of a link.\n    If the timeout is exceeded, will return a 404.\n    If \"verify\" is False, will ignore certificate errors.\n    For a list of available status codes, see:\n    https://en.wikipedia.org/wiki/List_of_HTTP_status_codes \"\"\"\n    status_code = None\n    try:\n        response = requests.head(\n            link,\n            allow_redirects=allow_redirects,\n            timeout=timeout,\n            verify=verify,\n        )\n        status_code = response.status_code\n    except Exception:\n        status_code = 404\n    return status_code\n\n\ndef _print_unique_links_with_status_codes(page_url, soup):\n    \"\"\"Finds all unique links in the html of the page source\n    and then prints out those links with their status codes.\n    Format:  [\"link\"  ->  \"status_code\"]  (per line)\n    Page links include those obtained from:\n    \"a\"->\"href\", \"img\"->\"src\", \"link\"->\"href\", and \"script\"->\"src\". \"\"\"\n    links = _get_unique_links(page_url, soup)\n    for link in links:\n        status_code = _get_link_status_code(link)\n        print(link, \" -> \", status_code)\n\n\ndef _download_file_to(\n    file_url, destination_folder, new_file_name=None, headers=None\n):\n    if new_file_name:\n        file_name = new_file_name\n    else:\n        file_name = file_url.split(\"/\")[-1]\n    r = requests.get(file_url, headers=headers, timeout=5)\n    file_path = os.path.join(destination_folder, file_name)\n    download_file_lock = fasteners.InterProcessLock(\n        constants.MultiBrowser.DOWNLOAD_FILE_LOCK\n    )\n    with download_file_lock:\n        with open(file_path, mode=\"wb\") as code:\n            code.write(r.content)\n\n\ndef _save_data_as(data, destination_folder, file_name):\n    file_io_lock = fasteners.InterProcessLock(\n        constants.MultiBrowser.FILE_IO_LOCK\n    )\n    with file_io_lock:\n        out_file = open(\n            os.path.join(destination_folder, file_name),\n            mode=\"w+\",\n            encoding=\"utf-8\",\n        )\n        out_file.writelines(data)\n        out_file.close()\n\n\ndef _append_data_to_file(data, destination_folder, file_name):\n    file_io_lock = fasteners.InterProcessLock(\n        constants.MultiBrowser.FILE_IO_LOCK\n    )\n    with file_io_lock:\n        existing_data = \"\"\n        if os.path.exists(os.path.join(destination_folder, file_name)):\n            with open(\n                os.path.join(destination_folder, file_name), mode=\"r\"\n            ) as f:\n                existing_data = f.read()\n            if not existing_data.split(\"\\n\")[-1] == \"\":\n                existing_data += \"\\n\"\n        out_file = open(\n            os.path.join(destination_folder, file_name),\n            mode=\"w+\",\n            encoding=\"utf-8\",\n        )\n        out_file.writelines(\"%s%s\" % (existing_data, data))\n        out_file.close()\n\n\ndef _get_file_data(folder, file_name):\n    file_io_lock = fasteners.InterProcessLock(\n        constants.MultiBrowser.FILE_IO_LOCK\n    )\n    with file_io_lock:\n        if not os.path.exists(os.path.join(folder, file_name)):\n            raise Exception(\"File not found!\")\n        with open(os.path.join(folder, file_name), mode=\"r\") as f:\n            data = f.read()\n        return data\n\n\ndef make_css_match_first_element_only(selector):\n    # Only get the first match\n    last_syllable = selector.split(\" \")[-1]\n    if \":first\" not in last_syllable:\n        selector += \":first\"\n    return selector\n"
  },
  {
    "path": "seleniumbase/fixtures/shared_utils.py",
    "content": "\"\"\"Shared utility methods\"\"\"\nimport colorama\nimport os\nimport pathlib\nimport platform\nimport sys\nimport time\nfrom contextlib import suppress\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import constants\n\n\ndef pip_install(package, version=None):\n    import fasteners\n    import subprocess\n\n    pip_install_lock = fasteners.InterProcessLock(\n        constants.PipInstall.LOCKFILE\n    )\n    upgrade_to_latest = False\n    if (\n        version\n        and (\"U\" in str(version).upper() or \"L\" in str(version).upper())\n    ):\n        # Upgrade to Latest when specified with \"U\" or \"L\"\n        upgrade_to_latest = True\n    with pip_install_lock:\n        if not version:\n            subprocess.check_call(\n                [sys.executable, \"-m\", \"pip\", \"install\", package]\n            )\n        elif not upgrade_to_latest:\n            package_and_version = package + \"==\" + str(version)\n            subprocess.check_call(\n                [sys.executable, \"-m\", \"pip\", \"install\", package_and_version]\n            )\n        else:\n            subprocess.check_call(\n                [sys.executable, \"-m\", \"pip\", \"install\", \"-U\", package]\n            )\n\n\ndef make_version_list(version_str):\n    return [int(i) for i in version_str.split(\".\") if i.isdigit()]\n\n\ndef make_version_tuple(version_str):\n    return tuple(make_version_list(version_str))\n\n\ndef get_mfa_code(totp_key=None):\n    \"\"\"Returns a time-based one-time password based on the\n    Google Authenticator algorithm for multi-factor authentication.\n    If the \"totp_key\" is not specified, this method defaults\n    to using the one provided in [seleniumbase/config/settings.py].\n    Google Authenticator codes expire & change at 30-sec intervals.\n    If the fetched password expires in the next 1.2 seconds, waits\n    for a new one before returning it (may take up to 1.2 seconds).\n    See https://pyotp.readthedocs.io/en/latest/ for details.\"\"\"\n    import pyotp\n\n    if not totp_key:\n        totp_key = settings.TOTP_KEY\n    epoch_interval = time.time() / 30.0\n    cycle_lifespan = float(epoch_interval) - int(epoch_interval)\n    if float(cycle_lifespan) > 0.96:\n        # Password expires in the next 1.2 seconds. Wait for a new one.\n        for i in range(30):\n            time.sleep(0.04)\n            epoch_interval = time.time() / 30.0\n            cycle_lifespan = float(epoch_interval) - int(epoch_interval)\n            if not float(cycle_lifespan) > 0.96:\n                # The new password cycle has begun\n                break\n    totp = pyotp.TOTP(totp_key)\n    return str(totp.now())\n\n\ndef is_arm_linux():\n    \"\"\"Returns True if machine is ARM Linux.\n    This will be useful once Google adds\n    support for ARM Linux ChromeDriver.\n    (Raspberry Pi uses ARM architecture.)\"\"\"\n    return (\n        platform.system() == \"Linux\"\n        and platform.machine() == \"aarch64\"\n    )\n\n\ndef is_arm_mac():\n    \"\"\"Returns True if machine is ARM Mac.\n    (Eg. M1 / M2 Macs use ARM processors)\"\"\"\n    return (\n        \"darwin\" in sys.platform\n        and (\n            \"arm\" in platform.processor().lower()\n            or \"arm64\" in platform.version().lower()\n        )\n    )\n\n\ndef is_mac():\n    return \"darwin\" in sys.platform\n\n\ndef is_linux():\n    return \"linux\" in sys.platform\n\n\ndef is_windows():\n    return \"win32\" in sys.platform\n\n\ndef is_safari(driver):\n    return driver.capabilities[\"browserName\"].lower() == \"safari\"\n\n\ndef get_terminal_width():\n    width = 80  # default\n    try:\n        width = os.get_terminal_size().columns\n    except Exception:\n        try:\n            import shutil\n\n            width = shutil.get_terminal_size((80, 20)).columns\n        except Exception:\n            pass\n    return width\n\n\ndef fix_colorama_if_windows():\n    if is_windows():\n        colorama.just_fix_windows_console()\n\n\ndef fix_url_as_needed(url):\n    if not url:\n        url = \"data:,\"\n    elif url.startswith(\"//\"):\n        url = \"https:\" + url\n    elif \":\" not in url:\n        url = \"https://\" + url\n    return url\n\n\ndef reconnect_if_disconnected(driver):\n    if (\n        getattr(driver, \"_is_using_uc\", None)\n        and hasattr(driver, \"is_connected\")\n        and not driver.is_connected()\n    ):\n        with suppress(Exception):\n            driver.connect()\n\n\ndef is_cdp_swap_needed(driver):\n    \"\"\"\n    When someone is using CDP Mode with a disconnected webdriver,\n    but they forget to reconnect before calling a webdriver method,\n    this method is used to substitute the webdriver method for a\n    CDP Mode method instead, which keeps CDP Stealth Mode enabled.\n    For other webdriver methods, SeleniumBase will reconnect first.\n    \"\"\"\n    return (\n        hasattr(driver, \"is_cdp_mode_active\")\n        and driver.is_cdp_mode_active()\n        and hasattr(driver, \"is_connected\")\n        and not driver.is_connected()\n    )\n\n\ndef is_chrome_130_or_newer(self, binary_location=None):\n    from seleniumbase.core import detect_b_ver\n\n    \"\"\"Due to changes in Chrome-130, UC Mode freezes at start-up\n    unless the user-data-dir already exists and is populated.\"\"\"\n    with suppress(Exception):\n        if not binary_location:\n            ver = detect_b_ver.get_browser_version_from_os(\"google-chrome\")\n        else:\n            ver = detect_b_ver.get_browser_version_from_binary(\n                binary_location\n            )\n        if ver and len(ver) > 3 and int(ver.split(\".\")[0]) >= 130:\n            return True\n    return False\n\n\ndef make_dir_files_writable(dir_path):\n    # Make all files in the given directory writable.\n    for file_path in pathlib.Path(dir_path).glob(\"*\"):\n        if file_path.is_file():\n            mode = os.stat(file_path).st_mode\n            mode |= (mode & 0o444) >> 1  # copy R bits to W\n            with suppress(Exception):\n                os.chmod(file_path, mode)\n\n\ndef make_writable(file_path):\n    # Set permissions to: \"If you can read it, you can write it.\"\n    mode = os.stat(file_path).st_mode\n    mode |= (mode & 0o444) >> 1  # copy R bits to W\n    os.chmod(file_path, mode)\n\n\ndef make_executable(file_path):\n    # Set permissions to: \"If you can read it, you can execute it.\"\n    mode = os.stat(file_path).st_mode\n    mode |= (mode & 0o444) >> 2  # copy R bits to X\n    os.chmod(file_path, mode)\n\n\ndef format_exc(exception, message):\n    \"\"\"Formats an exception message to make the output cleaner.\"\"\"\n    from selenium.common.exceptions import ElementNotVisibleException\n    from selenium.common.exceptions import NoAlertPresentException\n    from selenium.common.exceptions import NoSuchAttributeException\n    from selenium.common.exceptions import NoSuchElementException\n    from selenium.common.exceptions import NoSuchFrameException\n    from selenium.common.exceptions import NoSuchWindowException\n    from seleniumbase.common.exceptions import LinkTextNotFoundException\n    from seleniumbase.common.exceptions import NoSuchFileException\n    from seleniumbase.common.exceptions import NoSuchOptionException\n    from seleniumbase.common.exceptions import TextNotVisibleException\n    from seleniumbase.common import exceptions\n\n    if exception is Exception:\n        exc = Exception\n        return exc, message\n    elif exception is ElementNotVisibleException:\n        exc = exceptions.ElementNotVisibleException\n    elif exception == \"ElementNotVisibleException\":\n        exc = exceptions.ElementNotVisibleException\n    elif exception is LinkTextNotFoundException:\n        exc = exceptions.LinkTextNotFoundException\n    elif exception == \"LinkTextNotFoundException\":\n        exc = exceptions.LinkTextNotFoundException\n    elif exception is NoSuchElementException:\n        exc = exceptions.NoSuchElementException\n    elif exception == \"NoSuchElementException\":\n        exc = exceptions.NoSuchElementException\n    elif exception is TextNotVisibleException:\n        exc = exceptions.TextNotVisibleException\n    elif exception == \"TextNotVisibleException\":\n        exc = exceptions.TextNotVisibleException\n    elif exception is NoAlertPresentException:\n        exc = exceptions.NoAlertPresentException\n    elif exception == \"NoAlertPresentException\":\n        exc = exceptions.NoAlertPresentException\n    elif exception is NoSuchAttributeException:\n        exc = exceptions.NoSuchAttributeException\n    elif exception == \"NoSuchAttributeException\":\n        exc = exceptions.NoSuchAttributeException\n    elif exception is NoSuchFrameException:\n        exc = exceptions.NoSuchFrameException\n    elif exception == \"NoSuchFrameException\":\n        exc = exceptions.NoSuchFrameException\n    elif exception is NoSuchWindowException:\n        exc = exceptions.NoSuchWindowException\n    elif exception == \"NoSuchWindowException\":\n        exc = exceptions.NoSuchWindowException\n    elif exception is NoSuchFileException:\n        exc = exceptions.NoSuchFileException\n    elif exception == \"NoSuchFileException\":\n        exc = exceptions.NoSuchFileException\n    elif exception is NoSuchOptionException:\n        exc = exceptions.NoSuchOptionException\n    elif exception == \"NoSuchOptionException\":\n        exc = exceptions.NoSuchOptionException\n    elif isinstance(exception, str):\n        exc = Exception\n        message = \"%s: %s\" % (exception, message)\n        return exc, message\n    else:\n        exc = Exception\n        return exc, message\n    message = _format_message(message)\n    try:\n        exc.message = message\n    except Exception:\n        pass\n    return exc, message\n\n\ndef _format_message(message):\n    message = \"\\n \" + message\n    return message\n\n\ndef __time_limit_exceeded(message):\n    from seleniumbase.common.exceptions import TimeLimitExceededException\n\n    raise TimeLimitExceededException(message)\n\n\ndef check_if_time_limit_exceeded():\n    if (\n        getattr(sb_config, \"time_limit\", None)\n        and not sb_config.recorder_mode\n    ):\n        time_limit = sb_config.time_limit\n        now_ms = int(time.time() * 1000)\n        if now_ms > sb_config.start_time_ms + sb_config.time_limit_ms:\n            display_time_limit = time_limit\n            plural = \"s\"\n            if float(int(time_limit)) == float(time_limit):\n                display_time_limit = int(time_limit)\n                if display_time_limit == 1:\n                    plural = \"\"\n            message = (\n                \"This test has exceeded the time limit of %s second%s!\"\n                % (display_time_limit, plural)\n            )\n            message = _format_message(message)\n            __time_limit_exceeded(message)\n"
  },
  {
    "path": "seleniumbase/fixtures/unittest_helper.py",
    "content": "import sys\nfrom contextlib import contextmanager\nfrom unittest.case import _ShouldStop, SkipTest\n\n\nclass _Outcome(object):\n    def __init__(self, result=None):\n        self.expecting_failure = False\n        self.result = result\n        self.result_supports_subtests = hasattr(result, \"addSubTest\")\n        self.success = True\n        self.skipped = []\n        self.expectedFailure = None\n        self.errors = []\n\n    @contextmanager\n    def testPartExecutor(self, test_case, isTest=False):\n        old_success = self.success\n        self.success = True\n        try:\n            yield\n        except KeyboardInterrupt:\n            raise\n        except SkipTest as e:\n            self.success = False\n            self.skipped.append((test_case, str(e)))\n        except _ShouldStop:\n            pass\n        except Exception:\n            exc_info = sys.exc_info()\n            if self.expecting_failure:\n                self.expectedFailure = exc_info\n            else:\n                self.success = False\n                self.errors.append((test_case, exc_info))\n            exc_info = None\n        else:\n            if self.result_supports_subtests and self.success:\n                self.errors.append((test_case, None))\n        finally:\n            self.success = self.success and old_success\n"
  },
  {
    "path": "seleniumbase/fixtures/words.py",
    "content": "\"\"\"Small Dictionary for Demo Mode translations\"\"\"\n\n\nclass SD:\n    def translate_in(language):\n        words = {}\n        words[\"English\"] = \"in\"\n        words[\"Chinese\"] = \"在\"\n        words[\"Dutch\"] = \"in\"\n        words[\"French\"] = \"dans\"\n        words[\"Italian\"] = \"nel\"\n        words[\"Japanese\"] = \"に\"\n        words[\"Korean\"] = \"에\"\n        words[\"Portuguese\"] = \"no\"\n        words[\"Russian\"] = \"в\"\n        words[\"Spanish\"] = \"en\"\n        return words[language]\n\n    def translate_assert(language):\n        words = {}\n        words[\"English\"] = \"ASSERT\"\n        words[\"Chinese\"] = \"断言\"\n        words[\"Dutch\"] = \"CONTROLEREN\"\n        words[\"French\"] = \"VÉRIFIER\"\n        words[\"Italian\"] = \"VERIFICARE\"\n        words[\"Japanese\"] = \"検証\"\n        words[\"Korean\"] = \"확인\"\n        words[\"Portuguese\"] = \"VERIFICAR\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ\"\n        words[\"Spanish\"] = \"VERIFICAR\"\n        return words[language]\n\n    def translate_assert_text(language):\n        words = {}\n        words[\"English\"] = \"ASSERT TEXT\"\n        words[\"Chinese\"] = \"断言文本\"\n        words[\"Dutch\"] = \"CONTROLEREN TEKST\"\n        words[\"French\"] = \"VÉRIFIER TEXTE\"\n        words[\"Italian\"] = \"VERIFICARE TESTO\"\n        words[\"Japanese\"] = \"テキストを確認する\"\n        words[\"Korean\"] = \"텍스트 확인하는\"\n        words[\"Portuguese\"] = \"VERIFICAR TEXTO\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ ТЕКСТ\"\n        words[\"Spanish\"] = \"VERIFICAR TEXTO\"\n        return words[language]\n\n    def translate_assert_exact_text(language):\n        words = {}\n        words[\"English\"] = \"ASSERT EXACT TEXT\"\n        words[\"Chinese\"] = \"确切断言文本\"\n        words[\"Dutch\"] = \"CONTROLEREN EXACTE TEKST\"\n        words[\"French\"] = \"VÉRIFIER EXACTEMENT TEXTE\"\n        words[\"Italian\"] = \"VERIFICARE TESTO ESATTO\"\n        words[\"Japanese\"] = \"正確なテキストを確認する\"\n        words[\"Korean\"] = \"정확한 텍스트를 확인하는\"\n        words[\"Portuguese\"] = \"VERIFICAR TEXTO EXATO\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ ТЕКСТ ТОЧНО\"\n        words[\"Spanish\"] = \"VERIFICAR TEXTO EXACTO\"\n        return words[language]\n\n    def translate_assert_link_text(language):\n        words = {}\n        words[\"English\"] = \"ASSERT LINK TEXT\"\n        words[\"Chinese\"] = \"断言链接文本\"\n        words[\"Dutch\"] = \"CONTROLEREN LINKTEKST\"\n        words[\"French\"] = \"VÉRIFIER TEXTE DU LIEN\"\n        words[\"Italian\"] = \"VERIFICARE TESTO DEL COLLEGAMENTO\"\n        words[\"Japanese\"] = \"リンクテキストを確認する\"\n        words[\"Korean\"] = \"링크 텍스트 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR TEXTO DO LINK\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ ССЫЛКУ\"\n        words[\"Spanish\"] = \"VERIFICAR TEXTO DEL ENLACE\"\n        return words[language]\n\n    def translate_assert_non_empty_text(language):\n        words = {}\n        words[\"English\"] = \"ASSERT NON-EMPTY TEXT\"\n        words[\"Chinese\"] = \"断言非空文本\"\n        words[\"Dutch\"] = \"CONTROLEREN NIET-LEGE TEKST\"\n        words[\"French\"] = \"VÉRIFIER TEXTE NON VIDE\"\n        words[\"Italian\"] = \"VERIFICARE TESTO NON VUOTO\"\n        words[\"Japanese\"] = \"空ではないテキストを確認する\"\n        words[\"Korean\"] = \"비어 있지 않은 텍스트 확인하는\"\n        words[\"Portuguese\"] = \"VERIFICAR TEXTO NÃO VAZIO\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ НЕПУСТОЙ ТЕКСТ\"\n        words[\"Spanish\"] = \"VERIFICAR TEXTO NO VACÍO\"\n        return words[language]\n\n    def translate_assert_attribute(language):\n        words = {}\n        words[\"English\"] = \"ASSERT ATTRIBUTE\"\n        words[\"Chinese\"] = \"断言属性\"\n        words[\"Dutch\"] = \"CONTROLEREN ATTRIBUUT\"\n        words[\"French\"] = \"VÉRIFIER ATTRIBUT\"\n        words[\"Italian\"] = \"VERIFICARE ATTRIBUTO\"\n        words[\"Japanese\"] = \"属性を確認する\"\n        words[\"Korean\"] = \"특성 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR ATRIBUTO\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ АТРИБУТ\"\n        words[\"Spanish\"] = \"VERIFICAR ATRIBUTO\"\n        return words[language]\n\n    def translate_assert_title(language):\n        words = {}\n        words[\"English\"] = \"ASSERT TITLE\"\n        words[\"Chinese\"] = \"断言标题\"\n        words[\"Dutch\"] = \"CONTROLEREN TITEL\"\n        words[\"French\"] = \"VÉRIFIER TITRE\"\n        words[\"Italian\"] = \"VERIFICARE TITOLO\"\n        words[\"Japanese\"] = \"タイトルを確認\"\n        words[\"Korean\"] = \"제목 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR TÍTULO\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ НАЗВАНИЕ\"\n        words[\"Spanish\"] = \"VERIFICAR TÍTULO\"\n        return words[language]\n\n    def translate_assert_title_contains(language):\n        words = {}\n        words[\"English\"] = \"ASSERT TITLE CONTAINS\"\n        words[\"Chinese\"] = \"断言标题包含\"\n        words[\"Dutch\"] = \"CONTROLEREN TITEL BEVAT\"\n        words[\"French\"] = \"VÉRIFIER TITRE CONTIENT\"\n        words[\"Italian\"] = \"VERIFICARE TITOLO CONTIENE\"\n        words[\"Japanese\"] = \"タイトル部分文字列を確認する\"\n        words[\"Korean\"] = \"제목 부분 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR TÍTULO CONTÉM\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ НАЗВАНИЕ СОДЕРЖИТ\"\n        words[\"Spanish\"] = \"VERIFICAR TÍTULO CONTIENE\"\n        return words[language]\n\n    def translate_assert_url(language):\n        words = {}\n        words[\"English\"] = \"ASSERT URL\"\n        words[\"Chinese\"] = \"断言 URL\"\n        words[\"Dutch\"] = \"CONTROLEREN URL\"\n        words[\"French\"] = \"VÉRIFIER URL\"\n        words[\"Italian\"] = \"VERIFICARE URL\"\n        words[\"Japanese\"] = \"URL を確認する\"\n        words[\"Korean\"] = \"URL 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR URL\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ URL\"\n        words[\"Spanish\"] = \"VERIFICAR URL\"\n        return words[language]\n\n    def translate_assert_url_contains(language):\n        words = {}\n        words[\"English\"] = \"ASSERT URL CONTAINS\"\n        words[\"Chinese\"] = \"断言 URL 包含\"\n        words[\"Dutch\"] = \"CONTROLEREN URL BEVAT\"\n        words[\"French\"] = \"VÉRIFIER URL CONTIENT\"\n        words[\"Italian\"] = \"VERIFICARE URL CONTIENE\"\n        words[\"Japanese\"] = \"URL を確認する\"\n        words[\"Korean\"] = \"URL 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR URL CONTÉM\"\n        words[\"Russian\"] = \"ПОДТВЕРДИТЬ URL СОДЕРЖИТ\"\n        words[\"Spanish\"] = \"VERIFICAR URL CONTIENE\"\n        return words[language]\n\n    def translate_assert_no_404_errors(language):\n        words = {}\n        words[\"English\"] = \"ASSERT NO 404 ERRORS\"\n        words[\"Chinese\"] = \"检查断开的链接\"\n        words[\"Dutch\"] = \"CONTROLEREN OP 404 FOUTEN\"\n        words[\"French\"] = \"AFFIRMEZ PAS D'ERREURS 404\"\n        words[\"Italian\"] = \"CONTROLLA ERRORI 404\"\n        words[\"Japanese\"] = \"リンク切れを確認する\"\n        words[\"Korean\"] = \"끊어진 링크 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR SE HÁ ERROS 404\"\n        words[\"Russian\"] = \"ПРОВЕРИТЬ ОШИБКИ 404\"\n        words[\"Spanish\"] = \"VERIFICAR SI HAY ERRORES 404\"\n        return words[language]\n\n    def translate_assert_no_js_errors(language):\n        words = {}\n        words[\"English\"] = \"ASSERT NO JS ERRORS\"\n        words[\"Chinese\"] = \"检查JS错误\"\n        words[\"Dutch\"] = \"CONTROLEREN OP JS FOUTEN\"\n        words[\"French\"] = \"AFFIRMEZ PAS D'ERREURS JS\"\n        words[\"Italian\"] = \"CONTROLLA ERRORI JS\"\n        words[\"Japanese\"] = \"JSエラーを確認する\"\n        words[\"Korean\"] = \"JS 오류 확인\"\n        words[\"Portuguese\"] = \"VERIFICAR SE HÁ ERROS JS\"\n        words[\"Russian\"] = \"ПРОВЕРИТЬ ОШИБКИ JS\"\n        words[\"Spanish\"] = \"VERIFICAR SI HAY ERRORES JS\"\n        return words[language]\n"
  },
  {
    "path": "seleniumbase/fixtures/xpath_to_css.py",
    "content": "\"\"\"Convert XPath selectors into CSS selectors\"\"\"\nimport re\n\n_sub_regexes = {\n    \"tag\": r\"([a-zA-Z][-a-zA-Z0-9]{0,40}|\\*)\",\n    \"attribute\": r\"[.a-zA-Z_:][-\\w:.]*(\\(\\))?)\",\n    \"value\": r\"\\s*[\\w/:][-/\\w\\s,:;.\\S]*\",\n}\n\n_validation_re = (\n    r\"(?P<node>\"\n    r\"(\"\n    r\"^id\\([\\\"\\']?(?P<idvalue>%(value)s)[\\\"\\']?\\)\"\n    r\"|\"\n    r\"(?P<nav>//?)(?P<tag>%(tag)s)\"\n    r\"(\\[(\"\n    r\"(?P<matched>(?P<mattr>@?%(attribute)s=[\\\"\\']\"\n    r\"(?P<mvalue>%(value)s))[\\\"\\']\"\n    r\"|\"\n    r\"(?P<contained>contains\\((?P<cattr>@?%(attribute)s,\\s*[\\\"\\']\"\n    r\"(?P<cvalue>%(value)s)[\\\"\\']\\))\"\n    r\")\\])?\"\n    r\"(\\[(?P<nth>\\d+)\\])?\"\n    r\")\"\n    r\")\" % _sub_regexes\n)\n\nprog = re.compile(_validation_re)\n\n\nclass XpathException(Exception):\n    pass\n\n\ndef _handle_brackets_in_strings(xpath):\n    # Edge Case: Brackets in strings.\n    # Example from GitHub.com -\n    # '<input type=\"text\" id=\"user[login]\">' => '//*[@id=\"user[login]\"]'\n    # Need to tell apart string-brackets from regular brackets\n    new_xpath = \"\"\n    chunks = xpath.split('\"')\n    len_chunks = len(chunks)\n    for chunk_num in range(len_chunks):\n        if chunk_num % 2 != 0:\n            chunks[chunk_num] = chunks[chunk_num].replace(\n                \"[\", \"_STR_L_bracket_\"\n            )\n            chunks[chunk_num] = chunks[chunk_num].replace(\n                \"]\", \"_STR_R_bracket_\"\n            )\n        new_xpath += chunks[chunk_num]\n        if chunk_num != len_chunks - 1:\n            new_xpath += '\"'\n    return new_xpath\n\n\ndef _filter_xpath_grouping(xpath, original):\n    \"\"\"\n    This method removes the outer parentheses for xpath grouping.\n    The xpath converter will break otherwise.\n    Example:\n    \"(//button[@type='submit'])[1]\" becomes \"//button[@type='submit'][1]\"\n    \"\"\"\n\n    # First remove the first open parentheses\n    xpath = xpath[1:]\n\n    # Next remove the last closed parentheses\n    index = xpath.rfind(\")\")\n    index_p1 = index + 1  # Make \"flake8\" and \"black\" agree\n    if index == -1:\n        raise XpathException(\n            \"\\nInvalid or unsupported XPath:\\n%s\\n\"\n            \"(Unable to convert XPath Selector to CSS Selector)\"\n            \"\" % original\n        )\n    xpath = xpath[:index] + xpath[index_p1:]\n    return xpath\n\n\ndef _get_raw_css_from_xpath(xpath, original):\n    css = \"\"\n    attr = \"\"\n    position = 0\n\n    while position < len(xpath):\n        node = prog.match(xpath[position:])\n        if node is None:\n            raise XpathException(\n                \"\\nInvalid or unsupported XPath:\\n%s\\n\"\n                \"(Unable to convert XPath Selector to CSS Selector)\"\n                \"\" % original\n            )\n        match = node.groupdict()\n\n        if position != 0:\n            nav = \" \" if match[\"nav\"] == \"//\" else \" > \"\n        else:\n            nav = \"\"\n\n        tag = \"\" if match[\"tag\"] == \"*\" else match[\"tag\"] or \"\"\n\n        if match[\"idvalue\"]:\n            attr = \"#%s\" % match[\"idvalue\"].replace(\" \", \"#\")\n        elif match[\"matched\"]:\n            if match[\"mattr\"] == \"@id\":\n                attr = \"#%s\" % match[\"mvalue\"].replace(\" \", \"#\")\n            elif match[\"mattr\"] == \"@class\":\n                attr = \".%s\" % match[\"mvalue\"].replace(\" \", \".\")\n            elif match[\"mattr\"] in [\"text()\", \".\"]:\n                attr = \":contains('%s')\" % match[\"mvalue\"]\n            elif match[\"mattr\"]:\n                attr = '[%s=\"%s\"]' % (\n                    match[\"mattr\"].replace(\"@\", \"\"),\n                    match[\"mvalue\"],\n                )\n        elif match[\"contained\"]:\n            if match[\"cattr\"].startswith(\"@\"):\n                attr = '[%s*=\"%s\"]' % (\n                    match[\"cattr\"].replace(\"@\", \"\"),\n                    match[\"cvalue\"],\n                )\n            elif match[\"cattr\"] == \"text()\":\n                attr = ':contains(\"%s\")' % match[\"cvalue\"]\n            elif match[\"cattr\"] == \".\":\n                attr = ':contains(\"%s\")' % match[\"cvalue\"]\n        else:\n            attr = \"\"\n\n        if match[\"nth\"]:\n            nth = \":nth-of-type(%s)\" % match[\"nth\"]\n        else:\n            nth = \"\"\n\n        node_css = nav + tag + attr + nth\n        css += node_css\n        position += node.end()\n    else:\n        css = css.strip()\n        return css\n\n\ndef convert_xpath_to_css(xpath):\n    original = xpath\n    xpath = xpath.replace(\" = '\", \"='\")\n\n    # **** Start of handling special xpath edge cases instantly ****\n\n    # Handle a special edge case that converts to: 'tag.class:contains(\"TEXT\")'\n    c3 = \"@class and contains(concat(' ', normalize-space(@class), ' '), ' \"\n    if c3 in xpath and xpath.count(c3) == 1 and xpath.count(\"[@\") == 1:\n        p2 = \" ') and (contains(., '\"\n        if (\n            xpath.count(p2) == 1\n            and xpath.endswith(\"'))]\")\n            and xpath.count(\"//\") == 1\n            and xpath.count(\" ') and (\") == 1\n        ):\n            s_contains = xpath.split(p2)[1].split(\"'))]\")[0]\n            s_tag = xpath.split(\"//\")[1].split(\"[@class\")[0]\n            s_class = xpath.split(c3)[1].split(\" ') and (\")[0]\n            return '%s.%s:contains(\"%s\")' % (s_tag, s_class, s_contains)\n\n    # Find instance of: //tag[@attribute='value' and (contains(., 'TEXT'))]\n    data = re.match(\n        r\"\"\"^\\s*//(\\S+)\\[@(\\S+)='(\\S+)'\\s+and\\s+\"\"\"\n        r\"\"\"\\(contains\\(\\.,\\s'(\\S+)'\\)\\)\\]\"\"\",\n        xpath,\n    )\n    if data:\n        s_tag = data.group(1)\n        s_atr = data.group(2)\n        s_val = data.group(3)\n        s_contains = data.group(4)\n        return '%s[%s=\"%s\"]:contains(\"%s\")' % (s_tag, s_atr, s_val, s_contains)\n\n    # Find instance of: //tag[@attribute1='value1' and (@attribute2='value2')]\n    data = re.match(\n        r\"\"\"^\\s*//(\\S+)\\[@(\\S+)='(\\S+)'\\s+and\\s+\"\"\"\n        r\"\"\"\\(@(\\S+)='(\\S+)'\\)\\]\"\"\",\n        xpath,\n    )\n    if data:\n        s_tag = data.group(1)\n        s_atr1 = data.group(2)\n        s_val1 = data.group(3)\n        s_atr2 = data.group(4)\n        s_val2 = data.group(5)\n        return '%s[%s=\"%s\"][%s=\"%s\"]' % (s_tag, s_atr1, s_val1, s_atr2, s_val2)\n\n    # **** End of handling special xpath edge cases instantly ****\n\n    if xpath[0] != '\"' and xpath[-1] != '\"' and xpath.count('\"') % 2 == 0:\n        xpath = _handle_brackets_in_strings(xpath)\n    xpath = xpath.replace(\"descendant-or-self::*/\", \"descORself/\")\n    if len(xpath) > 3:\n        xpath = xpath[0:3] + xpath[3:].replace(\"//\", \"/descORself/\")\n\n    if \" and contains(@\" in xpath and xpath.count(\" and contains(@\") == 1:\n        spot1 = xpath.find(\" and contains(@\")\n        spot1 = spot1 + len(\" and contains(@\")\n        spot2 = xpath.find(\",\", spot1)\n        attr = xpath[spot1:spot2]\n        swap = \" and contains(@%s, \" % attr\n        if swap in xpath:\n            swap_spot = xpath.find(swap)\n            close_paren = xpath.find(\"]\", swap_spot) - 1\n            close_paren_p1 = close_paren + 1  # Make \"flake8\" and \"black\" agree\n            if close_paren > 1:\n                xpath = xpath[:close_paren] + xpath[close_paren_p1:]\n                xpath = xpath.replace(swap, \"_STAR_=\")\n\n    if xpath.startswith(\"(\"):\n        xpath = _filter_xpath_grouping(xpath, original)\n\n    css = \"\"\n    if \"/descORself/\" in xpath and (\"@id\" in xpath or \"@class\" in xpath):\n        css_sections = []\n        xpath_sections = xpath.split(\"/descORself/\")\n        for xpath_section in xpath_sections:\n            if not xpath_section.startswith(\"//\"):\n                xpath_section = \"//\" + xpath_section\n            css_sections.append(\n                _get_raw_css_from_xpath(xpath_section, original)\n            )\n        css = \"/descORself/\".join(css_sections)\n    else:\n        css = _get_raw_css_from_xpath(xpath, original)\n\n    attribute_defs = re.findall(r\"(\\[\\w+\\=\\S+\\])\", css)\n    for attr_def in attribute_defs:\n        if (\n            attr_def.count(\"[\") == 1\n            and attr_def.count(\"]\") == 1\n            and attr_def.count(\"=\") == 1\n            and attr_def.count('\"') == 0\n            and attr_def.count(\"'\") == 0\n            and attr_def.count(\" \") == 0\n        ):\n            # Now safe to manipulate\n            q1 = attr_def.find(\"=\") + 1\n            q2 = attr_def.find(\"]\")\n            new_attr_def = attr_def[:q1] + \"'\" + attr_def[q1:q2] + \"']\"\n            css = css.replace(attr_def, new_attr_def)\n\n    # Replace the string-brackets with escaped ones\n    css = css.replace(\"_STR_L_bracket_\", \"\\\\[\")\n    css = css.replace(\"_STR_R_bracket_\", \"\\\\]\")\n\n    # Handle a lot of edge cases with conversion\n    css = css.replace(\" > descORself > \", \" \")\n    css = css.replace(\" descORself > \", \" \")\n    css = css.replace(\"/descORself/*\", \" \")\n    css = css.replace(\"/descORself/\", \" \")\n    css = css.replace(\"descORself > \", \"\")\n    css = css.replace(\"descORself/\", \" \")\n    css = css.replace(\"descORself\", \" \")\n    css = css.replace(\"_STAR_=\", \"*=\")\n    css = css.replace(\"]/\", \"] \")\n    css = css.replace(\"] *[\", \"] > [\")\n    css = css.replace(\"'\", '\"')\n    css = css.replace(\"[@\", \"[\")\n\n    return css\n"
  },
  {
    "path": "seleniumbase/js_code/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/js_code/active_css_js.py",
    "content": "###############################################################################\n# active_css_js - Return the Best CSS Selector of the Currently Active Element.\n###############################################################################\n\nget_active_element_css = r\"\"\"\nvar cssPathById = function(el) {\n    if (!(el instanceof Element)) return;\n    var path = [];\n    while (el != null && el.nodeType === Node.ELEMENT_NODE) {\n        var selector = el.nodeName.toLowerCase();\n        if (el.id) {\n            elid = el.id;\n            if (/\\s/.test(elid) || elid.includes(',') || elid.includes('.') ||\n                elid.includes('(') || elid.includes(':') || hasDigit(elid[0]))\n                return cssPathByAttribute(el, 'id');\n            selector += '#' + elid;\n            path.unshift(selector);\n            break;\n        } else {\n            var sib = el, nth = 1;\n            while (sib = sib.previousElementSibling) {\n                if (sib.nodeName.toLowerCase() == selector)\n                    nth++;\n            }\n            if (nth != 1)\n                selector += ':nth-of-type('+nth+')';\n        }\n        path.unshift(selector);\n        el = el.parentNode;\n    }\n    return path.join(' > ');\n};\nvar cssPathByAttribute = function(el, attr) {\n    if (!(el instanceof Element)) return;\n    var path = [];\n    while (el !== null && el.nodeType === Node.ELEMENT_NODE) {\n        var selector = el.nodeName.toLowerCase();\n        if (el.hasAttribute(attr) &&\n            el.getAttribute(attr).length > 0 &&\n            !el.getAttribute(attr).includes('\\n'))\n        {\n            the_attr = el.getAttribute(attr);\n            the_attr = the_attr.replaceAll('\"', '\\\\\"');\n            the_attr = the_attr.replaceAll(\"'\", \"\\\\'\");\n            selector += '[' + attr + '=\"' + the_attr + '\"]';\n            path.unshift(selector);\n            break;\n        } else {\n            var sib = el, nth = 1;\n            while (sib = sib.previousElementSibling) {\n                if (sib.nodeName.toLowerCase() == selector)\n                    nth++;\n            }\n            if (nth != 1)\n                selector += ':nth-of-type('+nth+')';\n        }\n        path.unshift(selector);\n        el = el.parentNode;\n    }\n    return path.join(' > ');\n};\nvar cssPathByClass = function(el) {\n    if (!(el instanceof Element)) return;\n    var path = [];\n    while (el !== null && el.nodeType === Node.ELEMENT_NODE) {\n        var selector = el.nodeName.toLowerCase();\n        if (el.hasAttribute('class') &&\n            el.getAttribute('class').length > 0 &&\n            !el.getAttribute('class').includes(' ') &&\n                (el.getAttribute('class').includes('-')) &&\n            document.querySelectorAll(\n                selector + '.' + el.getAttribute('class')).length == 1) {\n            selector += '.' + el.getAttribute('class');\n            path.unshift(selector);\n            break;\n        } else {\n            var sib = el, nth = 1;\n            while (sib = sib.previousElementSibling) {\n                if (sib.nodeName.toLowerCase() == selector) nth++;\n            }\n            if (nth != 1)\n                selector += ':nth-of-type('+nth+')';\n        }\n        path.unshift(selector);\n        el = el.parentNode;\n    }\n    return path.join(' > ');\n};\nvar ssOccurrences = function(string, subString, allowOverlapping) {\n    if (subString.length <= 0)\n        return (string.length + 1);\n    var n = 0;\n    var pos = 0;\n    var step = allowOverlapping ? 1 : subString.length;\n    while (true) {\n        pos = string.indexOf(subString, pos);\n        if (pos >= 0) { ++n; pos += step; }\n        else break;\n    }\n    return n;\n};\nfunction hasDigit(str) {\n    return /\\d/.test(str);\n};\nfunction isGen(str) {\n    return /[_-]\\d/.test(str) || /\\d[a-z]/.test(str);\n};\nfunction tagName(el) {\n    return el.tagName.toLowerCase();\n};\nfunction turnIntoParentAsNeeded(el) {\n    if (tagName(el) == 'span' || tagName(el) == 'i') {\n        if (tagName(el.parentElement) == 'button') {\n            el = el.parentElement;\n        }\n        else if (tagName(el.parentElement.parentElement) == 'button') {\n            el = el.parentElement.parentElement;\n        }\n    }\n    return el;\n}\nvar getBestSelector = function(el) {\n    if (!(el instanceof Element)) return;\n    el = turnIntoParentAsNeeded(el);\n    sel_by_id = cssPathById(el);\n    if (!sel_by_id.includes(' > ') && !isGen(sel_by_id)) return sel_by_id;\n    child_count_by_id = ssOccurrences(sel_by_id, ' > ');\n    selector_by_class = cssPathByClass(el);\n    tag_name = tagName(el);\n    non_id_attributes = [];\n    non_id_attributes.push('name');\n    non_id_attributes.push('data-qa');\n    non_id_attributes.push('data-tid');\n    non_id_attributes.push('data-el');\n    non_id_attributes.push('data-se');\n    non_id_attributes.push('data-name');\n    non_id_attributes.push('data-auto');\n    non_id_attributes.push('data-text');\n    non_id_attributes.push('data-test');\n    non_id_attributes.push('data-testid');\n    non_id_attributes.push('data-test-id');\n    non_id_attributes.push('data-test-selector');\n    non_id_attributes.push('data-nav');\n    non_id_attributes.push('data-sb');\n    non_id_attributes.push('data-cy');\n    non_id_attributes.push('data-action');\n    non_id_attributes.push('data-target');\n    non_id_attributes.push('data-tooltip');\n    non_id_attributes.push('alt');\n    non_id_attributes.push('title');\n    non_id_attributes.push('heading');\n    non_id_attributes.push('translate');\n    non_id_attributes.push('aria-label');\n    non_id_attributes.push('aria-describedby');\n    non_id_attributes.push('rel');\n    non_id_attributes.push('ng-model');\n    non_id_attributes.push('ng-href');\n    non_id_attributes.push('href');\n    non_id_attributes.push('label');\n    non_id_attributes.push('data-content');\n    non_id_attributes.push('data-tip');\n    non_id_attributes.push('data-for');\n    non_id_attributes.push('class');\n    non_id_attributes.push('for');\n    non_id_attributes.push('placeholder');\n    non_id_attributes.push('value');\n    non_id_attributes.push('ng-click');\n    non_id_attributes.push('ng-if');\n    non_id_attributes.push('src');\n    selector_by_attr = [];\n    all_by_attr = [];\n    num_by_attr = [];\n    child_count_by_attr = [];\n    for (var i = 0; i < non_id_attributes.length; i++) {\n        n_i_attr = non_id_attributes[i];\n        selector_by_attr[i] = null;\n        if (n_i_attr == 'class') selector_by_attr[i] = selector_by_class;\n        else selector_by_attr[i] = cssPathByAttribute(el, n_i_attr);\n        all_by_attr[i] = document.querySelectorAll(selector_by_attr[i]);\n        num_by_attr[i] = all_by_attr[i].length;\n        if (!selector_by_attr[i].includes(' > ') &&\n            ((num_by_attr[i] == 1) || (el == all_by_attr[i][0])))\n        {\n            if (n_i_attr.startsWith('aria') || n_i_attr == 'for')\n                if (hasDigit(selector_by_attr[i])) continue;\n            return selector_by_attr[i];\n        }\n        child_count_by_attr[i] = ssOccurrences(selector_by_attr[i], ' > ');\n    }\n    basic_tags = [];\n    basic_tags.push('h1');\n    basic_tags.push('h2');\n    basic_tags.push('h3');\n    basic_tags.push('canvas');\n    basic_tags.push('center');\n    basic_tags.push('input');\n    basic_tags.push('textarea');\n    for (var i = 0; i < basic_tags.length; i++) {\n        d_qsa = document.querySelectorAll(basic_tags[i]);\n        if (tag_name == basic_tags[i] && d_qsa.length == 1 && el == d_qsa[0])\n            return basic_tags[i];\n    }\n    contains_tags = [];\n    contains_tags.push('a');\n    contains_tags.push('b');\n    contains_tags.push('h1');\n    contains_tags.push('h2');\n    contains_tags.push('h3');\n    contains_tags.push('h4');\n    contains_tags.push('h5');\n    contains_tags.push('code');\n    contains_tags.push('mark');\n    contains_tags.push('button');\n    contains_tags.push('label');\n    contains_tags.push('legend');\n    contains_tags.push('li');\n    contains_tags.push('td');\n    contains_tags.push('th');\n    contains_tags.push('i');\n    contains_tags.push('small');\n    contains_tags.push('strong');\n    contains_tags.push('summary');\n    contains_tags.push('span');\n    all_by_tag = [];\n    text_content = '';\n    if (el.textContent)\n        text_content = el.textContent.trim();\n    for (var i = 0; i < contains_tags.length; i++) {\n        if (tag_name == contains_tags[i] &&\n            text_content.length >= 2 && text_content.length <= 64)\n        {\n            t_count = 0;\n            all_by_tag[i] = document.querySelectorAll(contains_tags[i]);\n            for (var j = 0; j < all_by_tag[i].length; j++) {\n                if (all_by_tag[i][j].textContent.includes(text_content))\n                    t_count += 1;\n            }\n            if (t_count === 1 && !text_content.includes('\\n')) {\n                text_content = text_content.replaceAll(\"'\", \"\\\\'\");\n                text_content = text_content.replaceAll('\"', '\\\\\"');\n                return tag_name += ':contains(\"'+text_content+'\")';\n            }\n        }\n    }\n    best_selector = sel_by_id;\n    lowest_child_count = child_count_by_id;\n    child_count_by_class = ssOccurrences(selector_by_class, ' > ');\n    if (child_count_by_class < lowest_child_count) {\n        best_selector = selector_by_class;\n        lowest_child_count = child_count_by_class;\n    }\n    for (var i = 0; i < non_id_attributes.length; i++) {\n        if (child_count_by_attr[i] < lowest_child_count &&\n            ((num_by_attr[i] == 1) || (el == all_by_attr[i][0])))\n        {\n            best_selector = selector_by_attr[i];\n            lowest_child_count = child_count_by_attr[i];\n        }\n    }\n    best_selector = best_selector.replaceAll('html > body', 'body');\n    selector = best_selector.replaceAll(' > ', ' ');\n    selector = selector.replaceAll(' div ', ' ');\n    if (document.querySelector(selector) == el)\n        best_selector = selector;\n    return best_selector;\n};\nreturn getBestSelector(document.activeElement);\n\"\"\"\n"
  },
  {
    "path": "seleniumbase/js_code/live_js.py",
    "content": "###############################################################################\n# live_js - Reload web pages when changes are detected on the server side.\n###############################################################################\n\nlive_js = r\"\"\"\n(function () {\n    var headers = {\n    \"Etag\": 1, \"Last-Modified\": 1, \"Content-Length\": 1, \"Content-Type\": 1 },\n    resources = {},\n    pendingRequests = {},\n    currentLinkElements = {},\n    oldLinkElements = {},\n    interval = 1000,\n    loaded = false,\n    active = { \"html\": 1, \"css\": 1, \"js\": 1 };\n    var Live = {\n    heartbeat: function () {\n        if (document.body) {\n            if (!loaded) Live.loadresources();\n            Live.checkForChanges();\n        }\n        setTimeout(Live.heartbeat, interval);\n    },\n    loadresources: function () {\n        function isLocal(url) {\n            var loc = document.location,\n            reg = new RegExp(\"^\\\\.|^\\/(?!\\/)|^[\\\\w]((?!://).)*$|\"\n                + loc.protocol + \"//\" + loc.host);\n            return url.match(reg);\n        }\n        var scripts = document.getElementsByTagName(\"script\"),\n        links = document.getElementsByTagName(\"link\"),\n        uris = [];\n        for (var i = 0; i < scripts.length; i++) {\n            var script = scripts[i], src = script.getAttribute(\"src\");\n            if (src && isLocal(src))\n                uris.push(src);\n            if (src && src.match(/\\blive.js#/)) {\n                for (var type in active)\n                    active[type] = src.match(\"[#,|]\" + type) != null\n            if (src.match(\"notify\"))\n                alert(\"Live.js is loaded.\");\n        }\n    }\n    if (!active.js) uris = [];\n    if (active.html) uris.push(document.location.href);\n    for (var i = 0; i < links.length && active.css; i++) {\n        var link = links[i], rel = link.getAttribute(\"rel\"\n        ),href = link.getAttribute(\"href\", 2);\n        if (href && rel && rel.match(new RegExp(\"stylesheet\", \"i\")\n        ) && isLocal(href)) {\n            uris.push(href);\n            currentLinkElements[href] = link;\n        }\n    }\n    for (var i = 0; i < uris.length; i++) {\n        var url = uris[i];\n        Live.getHead(url, function (url, info) {\n           resources[url] = info;\n        });\n    }\n    var head = document.getElementsByTagName(\"head\")[0],\n    style = document.createElement(\"style\"),\n    rule = \"transition: all .3s ease-out;\"\n    css = [\".livejs-loading * { \",\n        rule, \" -webkit-\", rule, \"-moz-\", rule, \"-o-\", rule, \"}\"].join('');\n    style.setAttribute(\"type\", \"text/css\");\n    head.appendChild(style);\n    style.styleSheet ? style.styleSheet.cssText = css :\n    style.appendChild(document.createTextNode(css));\n    loaded = true;\n},\ncheckForChanges: function () {\n    for (var url in resources) {\n        if (pendingRequests[url])\n            continue;\n        Live.getHead(url, function (url, newInfo) {\n            var oldInfo = resources[url],\n            hasChanged = false;\n            resources[url] = newInfo;\n            for (var header in oldInfo) {\n                var oldValue = oldInfo[header],\n                newValue = newInfo[header],\n                contentType = newInfo[\"Content-Type\"];\n                switch (header.toLowerCase()) {\n                    case \"etag\":\n                    if (!newValue) break;\n                    default:\n                        hasChanged = oldValue != newValue;\n                    break;\n                }\n                if (hasChanged) {\n                    Live.refreshResource(url, contentType);\n                    break;\n                }\n            }\n        });\n    }\n},\nrefreshResource: function (url, type) {\n    switch (type.toLowerCase()) {\n        case \"text/css\":\n            var link = currentLinkElements[url],\n            html = document.body.parentNode,\n            head = link.parentNode,\n            next = link.nextSibling,\n            newLink = document.createElement(\"link\");\n            html.className = html.className.replace(/\\s*livejs\\-loading/gi, ''\n            ) + ' livejs-loading';\n            newLink.setAttribute(\"type\", \"text/css\");\n            newLink.setAttribute(\"rel\", \"stylesheet\");\n            newLink.setAttribute(\"href\", url + \"?now=\" + new Date() * 1);\n            next ? head.insertBefore(newLink, next) : head.appendChild(\n                newLink);\n            currentLinkElements[url] = newLink;\n            oldLinkElements[url] = link;\n            Live.removeoldLinkElements();\n            break;\n        case \"text/html\":\n            if (url != document.location.href)\n                return;\n        case \"text/javascript\":\n        case \"application/javascript\":\n        case \"application/x-javascript\":\n            document.location.reload();\n    }\n},\nremoveoldLinkElements: function () {\n    var pending = 0;\n    for (var url in oldLinkElements) {\n        try {\n            var link = currentLinkElements[url],\n            oldLink = oldLinkElements[url],\n            html = document.body.parentNode,\n            sheet = link.sheet || link.styleSheet,\n            rules = sheet.rules || sheet.cssRules;\n            if (rules.length >= 0) {\n                oldLink.parentNode.removeChild(oldLink);\n                delete oldLinkElements[url];\n                setTimeout(function () {\n                    html.className = html.className.replace(\n                    /\\s*livejs\\-loading/gi, '');\n                }, 100);\n            }\n        } catch (e) {\n            pending++;\n        }\n        if (pending) setTimeout(Live.removeoldLinkElements, 50);\n    }\n},\ngetHead: function (url, callback) {\n    pendingRequests[url] = true;\n    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() :\n        new ActiveXObject(\"Microsoft.XmlHttp\");\n        xhr.open(\"HEAD\", url, true);\n        xhr.onreadystatechange = function () {\n            delete pendingRequests[url];\n            if (xhr.readyState == 4 && xhr.status != 304) {\n                xhr.getAllResponseHeaders();\n                var info = {};\n                for (var h in headers) {\n                    var value = xhr.getResponseHeader(h);\n                    if (h.toLowerCase() == \"etag\" && value)\n                        value = value.replace(/^W\\//, '');\n                    if (h.toLowerCase() == \"content-type\" && value)\n                        value = value.replace(/^(.*?);.*?$/i, \"$1\");\n                    info[h] = value;\n                }\n                callback(url, info);\n            }\n        }\n        xhr.send();\n}\n};\nif (document.location.protocol != \"file:\") {\n    if (!window.liveJsLoaded)\n        Live.heartbeat();\n        window.liveJsLoaded = true;\n}\n})();\n\"\"\"\n"
  },
  {
    "path": "seleniumbase/js_code/recorder_js.py",
    "content": "###############################################################################\n# recorder_js - Save browser actions to sessionStorage with good CSS selectors.\n###############################################################################\n\nrecorder_js = r\"\"\"\nvar cssPathById = function(el) {\n    if (!(el instanceof Element)) return;\n    var path = [];\n    while (el != null && el.nodeType === Node.ELEMENT_NODE) {\n        var selector = el.nodeName.toLowerCase();\n        if (el.id) {\n            elid = el.id;\n            if (/\\s/.test(elid) || elid.includes(',') || elid.includes('.') ||\n                elid.includes('(') || elid.includes(':') || hasDigit(elid[0]))\n                return cssPathByAttribute(el, 'id');\n            selector += '#' + elid;\n            path.unshift(selector);\n            break;\n        } else {\n            var sib = el, nth = 1;\n            while (sib = sib.previousElementSibling) {\n                if (sib.nodeName.toLowerCase() == selector)\n                    nth++;\n            }\n            if (nth != 1)\n                selector += ':nth-of-type('+nth+')';\n        }\n        path.unshift(selector);\n        el = el.parentNode;\n    }\n    return path.join(' > ');\n};\nvar cssPathByAttribute = function(el, attr) {\n    if (!(el instanceof Element)) return;\n    var path = [];\n    while (el !== null && el.nodeType === Node.ELEMENT_NODE) {\n        var selector = el.nodeName.toLowerCase();\n        if (el.hasAttribute(attr) &&\n            el.getAttribute(attr).length > 0 &&\n            !el.getAttribute(attr).includes('\\n'))\n        {\n            the_attr = el.getAttribute(attr);\n            the_attr = the_attr.replaceAll('\"', '\\\\\"');\n            the_attr = the_attr.replaceAll(\"'\", \"\\\\'\");\n            selector += '[' + attr + '=\"' + the_attr + '\"]';\n            path.unshift(selector);\n            break;\n        } else {\n            var sib = el, nth = 1;\n            while (sib = sib.previousElementSibling) {\n                if (sib.nodeName.toLowerCase() == selector)\n                    nth++;\n            }\n            if (nth != 1)\n                selector += ':nth-of-type('+nth+')';\n        }\n        path.unshift(selector);\n        el = el.parentNode;\n    }\n    return path.join(' > ');\n};\nvar cssPathByClass = function(el) {\n    if (!(el instanceof Element)) return;\n    var path = [];\n    while (el !== null && el.nodeType === Node.ELEMENT_NODE) {\n        var selector = el.nodeName.toLowerCase();\n        if (el.hasAttribute('class') &&\n            el.getAttribute('class').length > 0 &&\n            !el.getAttribute('class').includes(' ') &&\n                (el.getAttribute('class').includes('-')) &&\n            document.querySelectorAll(\n                selector + '.' + el.getAttribute('class')).length == 1) {\n            selector += '.' + el.getAttribute('class');\n            path.unshift(selector);\n            break;\n        } else {\n            var sib = el, nth = 1;\n            while (sib = sib.previousElementSibling) {\n                if (sib.nodeName.toLowerCase() == selector) nth++;\n            }\n            if (nth != 1)\n                selector += ':nth-of-type('+nth+')';\n        }\n        path.unshift(selector);\n        el = el.parentNode;\n    }\n    return path.join(' > ');\n};\nvar ssOccurrences = function(string, subString, allowOverlapping) {\n    if (subString.length <= 0)\n        return (string.length + 1);\n    var n = 0;\n    var pos = 0;\n    var step = allowOverlapping ? 1 : subString.length;\n    while (true) {\n        pos = string.indexOf(subString, pos);\n        if (pos >= 0) { ++n; pos += step; }\n        else break;\n    }\n    return n;\n};\nfunction hasDigit(str) {\n    return /\\d/.test(str);\n};\nfunction isGen(str) {\n    return /[_-]\\d/.test(str) || /\\d[a-z]/.test(str);\n};\nfunction tagName(el) {\n    return el.tagName.toLowerCase();\n};\nfunction turnIntoParentAsNeeded(el) {\n    if (tagName(el) == 'span' || tagName(el) == 'i') {\n        if (tagName(el.parentElement) == 'button') {\n            el = el.parentElement;\n        }\n        else if (tagName(el.parentElement.parentElement) == 'button') {\n            el = el.parentElement.parentElement;\n        }\n    }\n    return el;\n}\nvar getBestSelector = function(el) {\n    if (!(el instanceof Element)) return;\n    el = turnIntoParentAsNeeded(el);\n    sel_by_id = cssPathById(el);\n    if (!sel_by_id.includes(' > ') && !isGen(sel_by_id)) return sel_by_id;\n    child_count_by_id = ssOccurrences(sel_by_id, ' > ');\n    selector_by_class = cssPathByClass(el);\n    tag_name = tagName(el);\n    non_id_attributes = [];\n    non_id_attributes.push('name');\n    non_id_attributes.push('data-qa');\n    non_id_attributes.push('data-tid');\n    non_id_attributes.push('data-el');\n    non_id_attributes.push('data-se');\n    non_id_attributes.push('data-name');\n    non_id_attributes.push('data-auto');\n    non_id_attributes.push('data-text');\n    non_id_attributes.push('data-test');\n    non_id_attributes.push('data-testid');\n    non_id_attributes.push('data-test-id');\n    non_id_attributes.push('data-test-selector');\n    non_id_attributes.push('data-nav');\n    non_id_attributes.push('data-sb');\n    non_id_attributes.push('data-cy');\n    non_id_attributes.push('data-action');\n    non_id_attributes.push('data-target');\n    non_id_attributes.push('data-tooltip');\n    non_id_attributes.push('alt');\n    non_id_attributes.push('title');\n    non_id_attributes.push('heading');\n    non_id_attributes.push('translate');\n    non_id_attributes.push('aria-label');\n    non_id_attributes.push('aria-describedby');\n    non_id_attributes.push('rel');\n    non_id_attributes.push('ng-model');\n    non_id_attributes.push('ng-href');\n    non_id_attributes.push('href');\n    non_id_attributes.push('label');\n    non_id_attributes.push('data-content');\n    non_id_attributes.push('data-tip');\n    non_id_attributes.push('data-for');\n    non_id_attributes.push('class');\n    non_id_attributes.push('for');\n    non_id_attributes.push('placeholder');\n    non_id_attributes.push('value');\n    non_id_attributes.push('ng-click');\n    non_id_attributes.push('ng-if');\n    non_id_attributes.push('src');\n    selector_by_attr = [];\n    all_by_attr = [];\n    num_by_attr = [];\n    child_count_by_attr = [];\n    for (var i = 0; i < non_id_attributes.length; i++) {\n        n_i_attr = non_id_attributes[i];\n        selector_by_attr[i] = null;\n        if (n_i_attr == 'class') selector_by_attr[i] = selector_by_class;\n        else selector_by_attr[i] = cssPathByAttribute(el, n_i_attr);\n        all_by_attr[i] = document.querySelectorAll(selector_by_attr[i]);\n        num_by_attr[i] = all_by_attr[i].length;\n        if (!selector_by_attr[i].includes(' > ') &&\n            ((num_by_attr[i] == 1) || (el == all_by_attr[i][0])))\n        {\n            if (n_i_attr.startsWith('aria') || n_i_attr == 'for')\n                if (hasDigit(selector_by_attr[i])) continue;\n            return selector_by_attr[i];\n        }\n        child_count_by_attr[i] = ssOccurrences(selector_by_attr[i], ' > ');\n    }\n    basic_tags = [];\n    basic_tags.push('h1');\n    basic_tags.push('h2');\n    basic_tags.push('h3');\n    basic_tags.push('canvas');\n    basic_tags.push('center');\n    basic_tags.push('input');\n    basic_tags.push('textarea');\n    for (var i = 0; i < basic_tags.length; i++) {\n        d_qsa = document.querySelectorAll(basic_tags[i]);\n        if (tag_name == basic_tags[i] && d_qsa.length == 1 && el == d_qsa[0])\n            return basic_tags[i];\n    }\n    contains_tags = [];\n    contains_tags.push('a');\n    contains_tags.push('b');\n    contains_tags.push('h1');\n    contains_tags.push('h2');\n    contains_tags.push('h3');\n    contains_tags.push('h4');\n    contains_tags.push('h5');\n    contains_tags.push('code');\n    contains_tags.push('mark');\n    contains_tags.push('button');\n    contains_tags.push('label');\n    contains_tags.push('legend');\n    contains_tags.push('li');\n    contains_tags.push('td');\n    contains_tags.push('th');\n    contains_tags.push('i');\n    contains_tags.push('small');\n    contains_tags.push('strong');\n    contains_tags.push('summary');\n    contains_tags.push('span');\n    all_by_tag = [];\n    text_content = '';\n    if (el.textContent)\n        text_content = el.textContent.trim();\n    for (var i = 0; i < contains_tags.length; i++) {\n        if (tag_name == contains_tags[i] &&\n            text_content.length >= 2 && text_content.length <= 64)\n        {\n            t_count = 0;\n            all_by_tag[i] = document.querySelectorAll(contains_tags[i]);\n            for (var j = 0; j < all_by_tag[i].length; j++) {\n                if (all_by_tag[i][j].textContent.includes(text_content))\n                    t_count += 1;\n            }\n            if (t_count === 1 && !text_content.includes('\\n')) {\n                text_content = text_content.replaceAll(\"'\", \"\\\\'\");\n                text_content = text_content.replaceAll('\"', '\\\\\"');\n                return tag_name += ':contains(\"'+text_content+'\")';\n            }\n        }\n    }\n    best_selector = sel_by_id;\n    lowest_child_count = child_count_by_id;\n    child_count_by_class = ssOccurrences(selector_by_class, ' > ');\n    if (child_count_by_class < lowest_child_count) {\n        best_selector = selector_by_class;\n        lowest_child_count = child_count_by_class;\n    }\n    for (var i = 0; i < non_id_attributes.length; i++) {\n        if (child_count_by_attr[i] < lowest_child_count &&\n            ((num_by_attr[i] == 1) || (el == all_by_attr[i][0])))\n        {\n            best_selector = selector_by_attr[i];\n            lowest_child_count = child_count_by_attr[i];\n        }\n    }\n    best_selector = best_selector.replaceAll('html > body', 'body');\n    selector = best_selector.replaceAll(' > ', ' ');\n    selector = selector.replaceAll(' div ', ' ');\n    if (document.querySelector(selector) == el)\n        best_selector = selector;\n    return best_selector;\n};\n\nfunction useHref(tag_name, el) {\n    return (tag_name === 'a' && el.hasAttribute('href') &&\n            el.getAttribute('href').length > 0 && el.origin != 'null');\n};\nfunction saveRecordedActions() {\n    json_rec_act = JSON.stringify(document.recorded_actions);\n    sessionStorage.setItem('recorded_actions', json_rec_act);\n};\nfunction new_tab_on_new_origin() {\n    var AllAnchorTags = document.getElementsByTagName('a');\n    for (var i = 0; i < AllAnchorTags.length; i++) {\n        if (!AllAnchorTags[i].sbset) {\n            AllAnchorTags[i].sbset = true;\n            AllAnchorTags[i].addEventListener('click', function (event) {\n                rec_mode = sessionStorage.getItem('recorder_mode');\n                if (rec_mode !== '2' && rec_mode !== '3') {\n                    if (this.origin &&\n                        this.origin != 'null' &&\n                        this.origin != document.location.origin &&\n                        this.hasAttribute('href'))\n                    {\n                        event.preventDefault();\n                        window.open(this.href, '_blank').focus();\n                    }\n                } else { event.preventDefault(); event.stopPropagation(); }\n            }, false);\n        }\n    }\n};\nnew_tab_on_new_origin();\nvar AllInputTags = document.getElementsByTagName('input');\nvar AllButtonTags = document.getElementsByTagName('button');\nvar All_IB_Tags = [];\nAll_IB_Tags.push(...AllInputTags, ...AllButtonTags);\nfor (var i = 0; i < All_IB_Tags.length; i++) {\n    All_IB_Tags[i].addEventListener('click', function (event) {\n        rec_mode = sessionStorage.getItem('recorder_mode');\n        if (rec_mode === '2' || rec_mode === '3')\n        { event.preventDefault(); event.stopPropagation(); }\n    }, false);\n}\nvar SearchInputs = document.querySelectorAll('input[type=\"search\"]');\nfor (var i = 0; i < SearchInputs.length; i++) {\n    SearchInputs[i].addEventListener('change', function (event) {\n        new_tab_on_new_origin();\n    }, false);\n}\nvar AwayForms = document.querySelectorAll('form[action^=\"//\"]');\nfor (var i = 0; i < AwayForms.length; i++) {\n    AwayForms[i].target = '_blank';\n}\nvar reset_recorder_state = function() {\n    document.recorded_actions = [];\n    sessionStorage.setItem('pause_recorder', 'no');\n    sessionStorage.setItem('recorder_mode', '1');\n    sessionStorage.setItem('recorder_title', document.title);\n    const d_now = Date.now();\n    document.recorder_last_mouseup = d_now;\n    w_orig = window.location.origin;\n    w_href = window.location.href;\n    if (sessionStorage.getItem('recorder_activated') === 'yes') {\n        ss_ra = JSON.parse(sessionStorage.getItem('recorded_actions'));\n        document.recorded_actions = ss_ra;\n        document.recorded_actions.push(['_url_', w_orig, w_href, d_now]);\n    }\n    else {\n        sessionStorage.setItem('recorder_activated', 'yes');\n        document.recorded_actions.push(['begin', w_orig, w_href, d_now]);\n    }\n    saveRecordedActions();\n    return;\n};\nreset_recorder_state();\nvar reset_if_recorder_undefined = function() {\n    if (typeof document.recorded_actions === 'undefined')\n        reset_recorder_state();\n};\nvar set_border = function(color) {\n    document.querySelector('body').style.border = '5px solid ' + color;\n    document.querySelector('body').style.borderRadius = '10px';\n};\n\ndocument.body.addEventListener('mouseover', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const el = event.target;\n    const selector = getBestSelector(el);\n    if (!selector.startsWith('body') && !selector.includes(' div')) {\n        document.title = selector;\n    }\n});\ndocument.body.addEventListener('mouseout', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    document.title = sessionStorage.getItem('recorder_title');\n});\nwindow.addEventListener('blur', () => {\n    setTimeout(() => {\n    reset_if_recorder_undefined();\n    rec_mode = sessionStorage.getItem('recorder_mode');\n    if (rec_mode === '2' || rec_mode === '3') return;\n    const el = document.activeElement;\n    const d_now = Date.now();\n    const d_now2 = d_now + 1;\n    const doc_t = document.title;\n    skip_open = false;\n    if (tagName(el) === 'iframe' &&\n        doc_t.startsWith('iframe') &&\n        Date.now() - document.recorder_last_mouseup > 32)\n    {\n        const selector = getBestSelector(el);\n        const el_cw = el.contentWindow;\n        origin = window.location.origin;\n        if (el.hasAttribute('src') && el.getAttribute('src').length > 0) {\n            if (el.src.startsWith('data:')) return;\n            skip_open = true; window.open(el.src, \"_blank\");\n        }\n        else document.body.innerHTML = el_cw.document.body.innerHTML;\n        window.focus();\n        document.recorded_actions.push(['sw_fr', selector, origin, d_now]);\n        if (skip_open)\n            document.recorded_actions.push(['sk_fo', '', origin, d_now2]);\n        saveRecordedActions();\n    }\n    });\n}, { once: false });\ndocument.body.addEventListener('click', function (event) {\n    // do nothing\n});\ndocument.body.addEventListener('dblclick', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    document.recorded_actions.push(['dbclk', '', '', Date.now()+1]);\n    saveRecordedActions();\n});\ndocument.body.addEventListener('submit', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    ra_len = document.recorded_actions.length;\n    if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][0] === 'input' &&\n        !document.recorded_actions[ra_len-1][2].endsWith('\\n'))\n    {\n        selector = document.recorded_actions[ra_len-1][1];\n        text = document.recorded_actions[ra_len-1][2] + '\\n';\n        document.recorded_actions.pop();\n        document.recorded_actions.push(['input', selector, text, d_now]);\n        saveRecordedActions();\n    }\n});\ndocument.body.addEventListener('formdata', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    ra_len = document.recorded_actions.length;\n    if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][0] === 'input' &&\n        !document.recorded_actions[ra_len-1][2].endsWith('\\n'))\n    {\n        selector = document.recorded_actions[ra_len-1][1];\n        text = document.querySelector(selector).value + '\\n';\n        document.recorded_actions.pop();\n        document.recorded_actions.push(['input', selector, text, d_now]);\n        saveRecordedActions();\n    }\n});\ndocument.body.addEventListener('dragstart', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    const el = event.target;\n    const selector = getBestSelector(el);\n    ra_len = document.recorded_actions.length;\n    rec_mode = sessionStorage.getItem('recorder_mode');\n    if (rec_mode === '2' || rec_mode === '3') return;\n    if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][0] === 'mo_dn' &&\n        document.recorded_actions[ra_len-1][1] === selector)\n    {\n        document.recorded_actions.pop();\n    }\n    if (el.draggable === true) {\n        document.recorded_actions.push(['drags', selector, '', d_now]);\n    }\n    saveRecordedActions();\n});\ndocument.body.addEventListener('dragend', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    ra_len = document.recorded_actions.length;\n    if (ra_len > 0 && document.recorded_actions[ra_len-1][0] === 'drags')\n    {\n        document.recorded_actions.pop();\n        saveRecordedActions();\n    }\n});\ndocument.body.addEventListener('drop', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    const el = event.target;\n    const selector = getBestSelector(el);\n    ra_len = document.recorded_actions.length;\n    if (ra_len > 0 && document.recorded_actions[ra_len-1][0] === 'drags')\n    {\n        drg_s = document.recorded_actions[ra_len-1][1];\n        document.recorded_actions.pop();\n        document.recorded_actions.push(['ddrop', drg_s, selector, d_now]);\n        saveRecordedActions();\n    }\n});\ndocument.body.addEventListener('change', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    const el = event.target;\n    const selector = getBestSelector(el);\n    ra_len = document.recorded_actions.length;\n    tag_name = tagName(el);\n    e_type = el.type;\n    if (tag_name === 'select')\n    {\n        el_computed = document.querySelector(selector);\n        optxt = el_computed.options[el_computed.selectedIndex].text;\n        document.recorded_actions.push(['s_opt', selector, optxt, d_now]);\n    }\n    else if (tag_name === 'input' && e_type === 'range')\n    {\n        if (ra_len > 0 && document.recorded_actions[ra_len-1][1] === selector)\n        {\n            document.recorded_actions.pop();\n            ra_len = document.recorded_actions.length;\n        }\n        if (ra_len > 0 && document.recorded_actions[ra_len-1][1] === selector)\n        {\n            document.recorded_actions.pop();\n            ra_len = document.recorded_actions.length;\n        }\n        value = el.value;\n        document.recorded_actions.push(['set_v', selector, value, d_now]);\n    }\n    else if (tag_name === 'input' && e_type === 'file') {\n        if (ra_len > 0 && document.recorded_actions[ra_len-1][1] === selector)\n        {\n            document.recorded_actions.pop();\n            ra_len = document.recorded_actions.length;\n        }\n        value = el.value;\n        document.recorded_actions.push(['cho_f', selector, value, d_now]);\n    }\n    else if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][1] === selector &&\n        tag_name === 'input' && e_type === 'checkbox')\n    {\n        document.recorded_actions.pop();\n        ra_len = document.recorded_actions.length;\n        if (ra_len > 0 && document.recorded_actions[ra_len-1][1] === selector)\n            document.recorded_actions.pop();\n    }\n    if (tag_name === 'input' && e_type === 'checkbox' && el.checked)\n        document.recorded_actions.push(['c_box', selector, 'yes', d_now]);\n    else if (tag_name === 'input' && e_type === 'checkbox' && !el.checked)\n        document.recorded_actions.push(['c_box', selector, 'no', d_now]);\n    saveRecordedActions();\n});\ndocument.body.addEventListener('mousedown', function (event) {\n    reset_if_recorder_undefined();\n    new_tab_on_new_origin();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    el = event.target;\n    const selector = getBestSelector(el);\n    ra_len = document.recorded_actions.length;\n    rec_mode = sessionStorage.getItem('recorder_mode');\n    tag_name = tagName(el);\n    if (rec_mode === '2' || rec_mode === '3')\n    {\n        el = turnIntoParentAsNeeded(el);\n        text = el.innerText;\n        t_con = el.textContent;\n        origin = window.location.origin;\n        sel_has_contains = selector.includes(':contains(');\n        if (!text) { text = ''; }\n        text = text.trim();\n        if (el.tagName.toLowerCase() == \"input\")\n            text = el.value.trim();\n        if (!t_con) { t_con = ''; }\n        if (rec_mode === '2' || (\n            rec_mode === '3' && sel_has_contains && text === t_con.trim()))\n        {\n            document.recorded_actions.push(['as_el', selector, origin, d_now]);\n            saveRecordedActions();\n            return;\n        }\n        else if (rec_mode === '3') {\n            action = 'as_et';\n            var match = /\\r|\\n/.exec(text);\n            if (match) {\n                lines = text.split(/\\r\\n|\\r|\\n/g);\n                text = '';\n                for (var i = 0; i < lines.length; i++) {\n                    if (lines[i].length > 0) {\n                        action = 'as_te'; text = lines[i]; break;\n                    }\n                }\n            }\n            tex_sel = [text, selector];\n            document.recorded_actions.push([action, tex_sel, origin, d_now]);\n            saveRecordedActions();\n            return;\n        }\n    }\n    if (ra_len > 0 && document.recorded_actions[ra_len-1][0] === 'mo_dn')\n        document.recorded_actions.pop();\n    if (tag_name === 'select') {\n        // do nothing ('change' action)\n    }\n    else\n        document.recorded_actions.push(['mo_dn', selector, '', d_now]);\n    saveRecordedActions();\n});\ndocument.body.addEventListener('mouseup', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    document.recorder_last_mouseup = d_now;\n    const el = event.target;\n    selector = getBestSelector(el);\n    ra_len = document.recorded_actions.length;\n    tag_name = tagName(el);\n    parent_el = el.parentElement;\n    parent_tag_name = tagName(parent_el);\n    grand_el = \"\";\n    grand_tag_name = \"\";\n    origin = \"\";\n    rec_mode = sessionStorage.getItem('recorder_mode');\n    if (rec_mode === '2' || rec_mode === '3') return;\n    if (parent_el.parentElement != null) {\n        grand_el = parent_el.parentElement; grand_tag_name = tagName(grand_el);\n    }\n    if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][1] === selector &&\n        (document.recorded_actions[ra_len-1][0] === 'mo_dn' ||\n         tag_name === 'a' || parent_tag_name === 'a') && tag_name !== 'select')\n    {\n        href = '';\n        if (useHref(tag_name, el))\n        {\n            href = el.href; origin = el.origin;\n        }\n        else if (useHref(parent_tag_name, parent_el))\n        {\n            href = parent_el.href; origin = parent_el.origin;\n        }\n        else if (useHref(grand_tag_name, grand_el))\n        {\n            href = grand_el.href; origin = grand_el.origin;\n        }\n        document.recorded_actions.pop();\n        child_count = ssOccurrences(selector, ' > ');\n        if ((tag_name === \"a\" && !el.hasAttribute('onclick') &&\n             child_count > 0 && href.length > 0) ||\n            (parent_tag_name === \"a\" && href.length > 0 &&\n             child_count > 1 && !parent_el.hasAttribute('onclick')) ||\n            (grand_tag_name === \"a\" && href.length > 0 &&\n             child_count > 2 && !grand_el.hasAttribute('onclick')))\n        {\n            w_orig = window.location.origin;\n            if (origin === w_orig)\n                document.recorded_actions.push(['_url_', origin, href, d_now]);\n            else\n                document.recorded_actions.push(['begin', origin, href, d_now]);\n        }\n        else\n            document.recorded_actions.push(['click', selector, href, d_now]);\n        // hover+click\n        if (el.parentElement.classList.contains('dropdown-content') &&\n            el.parentElement.parentElement.classList.contains('dropdown'))\n        {\n            ch_s = selector;\n            pa_el = el.parentElement.parentElement;\n            pa_s = getBestSelector(pa_el);\n            if (pa_el.childElementCount >= 2 &&\n               !pa_el.firstElementChild.classList.contains('dropdown-content'))\n            {\n                pa_el = pa_el.firstElementChild;\n                pa_s = getBestSelector(pa_el);\n            }\n            document.recorded_actions.pop();\n            document.recorded_actions.push(['h_clk', pa_s, ch_s, d_now]);\n        }\n        else if (tag_name === 'canvas')\n        {\n            rect = el.getBoundingClientRect();\n            p_x = event.clientX - rect.left;\n            p_y = event.clientY - rect.top;\n            c_offset = [selector, p_x, p_y];\n            document.recorded_actions.pop();\n            document.recorded_actions.push(['canva', c_offset, href, d_now]);\n        }\n    }\n    else if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][0] === 'mo_dn' &&\n        document.recorded_actions[ra_len-1][1] === selector &&\n        tag_name === 'select')\n    { document.recorded_actions.pop(); }\n    else if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][0] === 'mo_dn')\n    {\n        // accidental drag&drop\n        document.recorded_actions.pop();\n    }\n    saveRecordedActions();\n});\ndocument.body.addEventListener('contextmenu', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const el = event.target;\n    const selector = getBestSelector(el);\n    ra_len = document.recorded_actions.length;\n    if (ra_len > 0 &&\n        document.recorded_actions[ra_len-1][0] === 'mo_dn' &&\n        document.recorded_actions[ra_len-1][1] === selector)\n    {\n        document.recorded_actions.pop();\n        saveRecordedActions();\n    }\n});\ndocument.body.addEventListener('keydown', function (event) {\n    reset_if_recorder_undefined();\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    if (document.recorded_actions.length == 0) return;\n    const el = event.target;\n    const selector = getBestSelector(el);\n    const d_now = Date.now();\n    const tag_name = tagName(el);\n    const l_key = event.key.toLowerCase();\n    if (l_key === 'enter' && (tag_name === 'button' || tag_name === 'a'))\n    {\n        href = '';\n        if (useHref(tag_name, el))\n            href = el.href;\n        document.recorded_actions.push(['click', selector, href, d_now]);\n        saveRecordedActions();\n    }\n});\ndocument.body.addEventListener('keyup', function (event) {\n    reset_if_recorder_undefined();\n    // pause+resume controls\n    pause_rec = sessionStorage.getItem('pause_recorder');\n    rec_mode = sessionStorage.getItem('recorder_mode');\n    l_key = event.key.toLowerCase();\n    no_border = 'none';\n    if (l_key === 'escape' && pause_rec === 'no' && rec_mode === '1') {\n        sessionStorage.setItem('pause_recorder', 'yes');\n        pause_rec = 'yes';\n        console.log('SeleniumBase Recorder paused');\n        document.querySelector('body').style.border = no_border;\n        document.title = sessionStorage.getItem('recorder_title');\n    }\n    else if ((event.key === '`' || event.key === '~') && pause_rec === 'yes') {\n        sessionStorage.setItem('pause_recorder', 'no');\n        pause_rec = 'no';\n        console.log('SeleniumBase Recorder resumed');\n        set_border('#F43344');\n    }\n    else if (event.key === '^' && pause_rec === 'no') {\n        sessionStorage.setItem('recorder_mode', '2');\n        set_border('#EF5BE9');\n    }\n    else if (event.key === '&' && pause_rec === 'no') {\n        sessionStorage.setItem('recorder_mode', '3');\n        set_border('#30C6C6');\n    }\n    else if (pause_rec === 'no' && l_key !== 'shift' && l_key !== 'backspace')\n    {\n        sessionStorage.setItem('recorder_mode', '1');\n        set_border('#F43344');\n    }\n    // after switching modes\n    if (sessionStorage.getItem('pause_recorder') === 'yes') return;\n    const d_now = Date.now();\n    const el = event.target;\n    const selector = getBestSelector(el);\n    skip_input = false;\n    if ((tagName(el) === 'input' &&\n        el.type !== 'checkbox' &&\n        el.type !== 'range') ||\n        tagName(el) === 'textarea')\n    {\n        ra_len = document.recorded_actions.length;\n        if (ra_len > 0 && l_key === 'enter' &&\n            document.recorded_actions[ra_len-1][0] === 'input' &&\n            document.recorded_actions[ra_len-1][1] === selector &&\n            !document.recorded_actions[ra_len-1][2].endsWith('\\n'))\n        {\n            s_text = document.recorded_actions[ra_len-1][2] + '\\n';\n            document.recorded_actions.pop();\n            document.recorded_actions.push(['input', selector, s_text, d_now]);\n            document.recorded_actions.push(['submi', selector, s_text, d_now]);\n            skip_input = true;\n        }\n        else if (ra_len > 0 &&\n            document.recorded_actions[ra_len-1][0] === 'click' &&\n            document.recorded_actions[ra_len-1][1] === selector)\n        {\n            document.recorded_actions.pop();\n        }\n        else if (ra_len > 0 &&\n            document.recorded_actions[ra_len-1][0] === 'input' &&\n            document.recorded_actions[ra_len-1][1] === selector &&\n            !document.recorded_actions[ra_len-1][2].endsWith('\\n') &&\n            l_key !== 'tab')\n        {\n            document.recorded_actions.pop();\n        }\n        else if (ra_len > 0 &&\n            document.recorded_actions[ra_len-1][0] === 'input' &&\n            document.recorded_actions[ra_len-1][1] === selector &&\n            document.recorded_actions[ra_len-1][2].endsWith('\\n'))\n        {\n            skip_input = true;\n        }\n        if (!skip_input && !el.hasAttribute('readonly') && l_key !== 'tab') {\n            document.recorded_actions.push(\n                ['input', selector, el.value, d_now]);\n        }\n    }\n    saveRecordedActions();\n});\nset_border('#F43344');\n\"\"\"\n"
  },
  {
    "path": "seleniumbase/masterqa/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n![](https://seleniumbase.github.io/cdn/img/masterqa_logo.png \"MasterQA\")\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\" /> MasterQA combines automation with manual verification steps.</h3>\n\n![](https://seleniumbase.github.io/cdn/gif/masterqa6.gif \"MasterQA\")\n\nHere's code from [basic_masterqa_test_0.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/basic_masterqa_test_0.py):\n\n```python\nfrom seleniumbase import MasterQA\n\nclass MasterQATests(MasterQA):\n    def test_masterqa(self):\n        self.open(\"https://xkcd.com/1700/\")\n        self.verify(\"Do you see a webcomic?\")\n        self.open(\"https://seleniumbase.io/demo_page\")\n        self.highlight('table')\n        self.verify(\"Do you see elements in a table?\")\n        self.open(\"https://seleniumbase.io/devices/\")\n        self.highlight(\"div.mockup-wrapper\")\n        self.verify(\"Do you see 4 computer devices?\")\n```\n\nAfter each automation checkpoint, a pop-up window will ask the user questions for each verification command.\n\nWhen the test run completes, as seen from [this longer example](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/masterqa_test_1.py), you'll reach the results page that appears after answering all the verification questions. (Failed verifications generate links to screenshots and log files.)\n\n![](https://seleniumbase.github.io/cdn/img/mqa_hybrid.png \"MasterQA\")\n\nYou may have noticed the ``Incomplete Test Runs`` row on the results page. If the value for that is not zero, it means that one of the automated steps failed. This could happen if you tell your script to perform an action on an element that doesn't exist. Now that we're mixing automation with manual QA, it's good to tell apart the failures from each. The results_table CSV file contains a spreadsheet with the details of each failure (if any) for both manual and automated steps.\n\n**How to run the example tests from scratch:**\n\n```zsh\ngit clone https://github.com/seleniumbase/SeleniumBase.git\ncd SeleniumBase\npip install .\ncd examples/master_qa\npytest basic_masterqa_test_0.py\npytest masterqa_test_1.py\n```\n\nAt the end of your test run, you'll receive a report with results, screenshots, and log files. Close the Results Page window when you're done.\n\n**Check out [masterqa_test_1.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/masterqa_test_1.py) to learn how to write your own MasterQA tests:**\n\nYou'll notice that tests are written the same way as regular [SeleniumBase](https://seleniumbase.com) tests, with the key difference being a different import: ``from seleniumbase import MasterQA`` rather than ``from seleniumbase import BaseCase``. Now your Python test class will import ``MasterQA`` instead of ``BaseCase``.\n\nTo add a manual verification step, use ``self.verify()`` in the code after each part of your test that needs a manual verification step. If you want to include a custom question, add text inside that call (in quotes). Example:\n\n```python\nself.verify()\n\nself.verify(\"Can you find the moon?\")\n```\n\n--------\n\nMasterQA is powered by [SeleniumBase](https://seleniumbase.com), the most advanced open-source automation framework on the [Planet](https://en.wikipedia.org/wiki/Earth).\n"
  },
  {
    "path": "seleniumbase/masterqa/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/masterqa/master_qa.py",
    "content": "\"\"\"Manually verify pages quickly while assisted by automation.\"\"\"\nimport os\nimport shutil\nimport sys\nimport time\nfrom selenium.common.exceptions import NoAlertPresentException\nfrom selenium.common.exceptions import WebDriverException\nfrom seleniumbase import BaseCase\nfrom seleniumbase.core.style_sheet import get_report_style\nfrom seleniumbase.config import settings\nfrom seleniumbase.fixtures import js_utils\n\n\npython3_11_or_newer = False\nif sys.version_info >= (3, 11):\n    python3_11_or_newer = True\n\n\nclass MasterQA(BaseCase):\n    def setUp(self):\n        self.check_count = 0\n        self.auto_close_results_page = False\n        super().setUp(masterqa_mode=True)\n        self.LATEST_REPORT_DIR = settings.LATEST_REPORT_DIR\n        self.ARCHIVE_DIR = settings.REPORT_ARCHIVE_DIR\n        self.RESULTS_PAGE = settings.HTML_REPORT\n        self.BAD_PAGE_LOG = settings.RESULTS_TABLE\n        self.DEFAULT_VALIDATION_TITLE = \"Manual Check\"\n        self.DEFAULT_VALIDATION_MESSAGE = (\n            settings.MASTERQA_DEFAULT_VALIDATION_MESSAGE\n        )\n        self.WAIT_TIME_BEFORE_VERIFY = (\n            settings.MASTERQA_WAIT_TIME_BEFORE_VERIFY\n        )\n        self.START_IN_FULL_SCREEN_MODE = (\n            settings.MASTERQA_START_IN_FULL_SCREEN_MODE\n        )\n        self.MAX_IDLE_TIME_BEFORE_QUIT = (\n            settings.MASTERQA_MAX_IDLE_TIME_BEFORE_QUIT\n        )\n        self.__manual_check_setup()\n        if self.headless:\n            self.auto_close_results_page = True\n        if self.START_IN_FULL_SCREEN_MODE:\n            self.maximize_window()\n\n    def verify(self, *args):\n        warn_msg = \"\\nWARNING: MasterQA skips manual checks in headless mode!\"\n        self.check_count += 1\n        if self.headless:\n            if self.check_count == 1:\n                print(warn_msg)\n            return\n        # This is where the magic happens\n        self.__manual_page_check(*args)\n\n    def auto_close_results(self):\n        \"\"\"If this method is called, the results page will automatically close\n        at the end of the test run, rather than waiting on the user to close\n        the results page manually.\n        \"\"\"\n        self.auto_close_results_page = True\n\n    def tearDown(self):\n        if self.headless and self.check_count > 0:\n            print(\n                \"WARNING: %s manual checks were skipped! (MasterQA)\"\n                % self.check_count\n            )\n        if self.__has_exception():\n            self.__add_failure(sys.exc_info()[1])\n        self.__process_manual_check_results(self.auto_close_results_page)\n        super().tearDown()\n\n    ####################\n\n    def __get_timestamp(self):\n        return str(int(time.time() * 1000))\n\n    def __manual_check_setup(self):\n        self.manual_check_count = 0\n        self.manual_check_successes = 0\n        self.incomplete_runs = 0\n        self.page_results_list = []\n        self.__clear_out_old_logs(archive_past_runs=False)\n\n    def __clear_out_old_logs(\n        self, archive_past_runs=True, get_log_folder=False\n    ):\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, self.LATEST_REPORT_DIR)\n        if not os.path.exists(file_path):\n            os.makedirs(file_path)\n\n        if archive_past_runs:\n            archive_timestamp = int(time.time())\n            archive_dir_root = os.path.join(file_path, \"..\", self.ARCHIVE_DIR)\n            if not os.path.exists(archive_dir_root):\n                os.makedirs(archive_dir_root)\n            archive_dir = os.path.join(\n                archive_dir_root, \"log_%s\" % archive_timestamp\n            )\n            shutil.move(file_path, archive_dir)\n            os.makedirs(file_path)\n            if get_log_folder:\n                return archive_dir\n        else:\n            latest_report_local = os.path.join(\".\", self.LATEST_REPORT_DIR)\n            # Just delete bad pages to make room for the latest run.\n            filelist = [\n                f\n                for f in os.listdir(latest_report_local)\n                if (f.startswith(\"failed_\"))\n                or (f == self.RESULTS_PAGE)\n                or (f.startswith(\"automation_failure\"))\n                or (f == self.BAD_PAGE_LOG)\n            ]\n            for f in filelist:\n                os.remove(os.path.join(file_path, f))\n\n    def __jq_confirm_dialog(self, question):\n        count = self.manual_check_count + 1\n        title = self.DEFAULT_VALIDATION_TITLE\n        title_content = (\n            '<center><font color=\"#7700bb\">%s #%s:'\n            '</font></center><hr><font color=\"#0066ff\">%s</font>'\n            \"\" % (title, count, question)\n        )\n        title_content = js_utils.escape_quotes_if_needed(title_content)\n        jqcd = (\n            \"\"\"jconfirm({\n                    boxWidth: '32.5%%',\n                    useBootstrap: false,\n                    containerFluid: false,\n                    animationBounce: 1,\n                    type: 'default',\n                    theme: 'bootstrap',\n                    typeAnimated: true,\n                    animation: 'scale',\n                    draggable: true,\n                    dragWindowGap: 1,\n                    container: 'body',\n                    title: '%s',\n                    content: '',\n                    buttons: {\n                        pass_button: {\n                            btnClass: 'btn-green',\n                            text: 'YES / PASS',\n                            keys: ['y', 'p', '1'],\n                            action: function(){\n                                $jqc_status = \"Success!\";\n                                jconfirm.lastButtonText = \"Success!\";\n                            }\n                        },\n                        fail_button: {\n                            btnClass: 'btn-red',\n                            text: 'NO / FAIL',\n                            keys: ['n', 'f', '2'],\n                            action: function(){\n                                $jqc_status = \"Failure!\";\n                                jconfirm.lastButtonText = \"Failure!\";\n                            }\n                        }\n                    }\n                });\"\"\"\n            % title_content\n        )\n        self.execute_script(jqcd)\n\n    def __manual_page_check(self, *args):\n        if not args:\n            instructions = self.DEFAULT_VALIDATION_MESSAGE  # self.verify()\n        else:\n            instructions = str(args[0])\n            if len(args) > 1:\n                pass\n\n        question = \"Approve?\"  # self.verify(\"\")\n        if instructions and \"?\" not in instructions:\n            question = instructions + \" <> Approve?\"\n        elif instructions and \"?\" in instructions:\n            question = instructions\n\n        wait_time_before_verify = self.WAIT_TIME_BEFORE_VERIFY\n        if self.verify_delay:\n            wait_time_before_verify = float(self.verify_delay)\n        # Allow a moment to see the full page before the dialog box pops up\n        time.sleep(wait_time_before_verify)\n\n        use_jqc = False\n        self.wait_for_ready_state_complete()\n        if js_utils.is_jquery_confirm_activated(self.driver):\n            use_jqc = True\n        else:\n            js_utils.activate_jquery_confirm(self.driver)\n            get_jqc = None\n            try:\n                get_jqc = self.execute_script(\"return jconfirm\")\n                if get_jqc is None:\n                    raise Exception(\"jconfirm did not load\")\n                use_jqc = True\n            except Exception:\n                use_jqc = False\n\n        if use_jqc:\n            # Use the jquery_confirm library for manual page checks\n            self.__jq_confirm_dialog(question)\n            time.sleep(0.02)\n            waiting_for_response = True\n            while waiting_for_response:\n                time.sleep(0.05)\n                jqc_open = self.execute_script(\n                    \"return jconfirm.instances.length\"\n                )\n                if str(jqc_open) == \"0\":\n                    break\n            time.sleep(0.1)\n            status = None\n            try:\n                status = self.execute_script(\"return $jqc_status\")\n            except Exception:\n                status = self.execute_script(\"return jconfirm.lastButtonText\")\n        else:\n            # Fallback to plain js confirm dialogs if can't load jquery_confirm\n            if self.browser == \"ie\":\n                text = self.execute_script(\n                    \"\"\"if(confirm(\"%s\")){return \"Success!\"}\n                    else{return \"Failure!\"}\"\"\"\n                    % question\n                )\n            elif self.browser == \"chrome\":\n                self.execute_script(\n                    \"\"\"if(confirm(\"%s\"))\n                    {window.master_qa_result=\"Success!\"}\n                    else{window.master_qa_result=\"Failure!\"}\"\"\"\n                    % question\n                )\n                time.sleep(0.05)\n                self.__wait_for_special_alert_absent()\n                text = self.execute_script(\"return window.master_qa_result\")\n            else:\n                try:\n                    self.execute_script(\n                        \"\"\"if(confirm(\"%s\"))\n                        {window.master_qa_result=\"Success!\"}\n                        else{window.master_qa_result=\"Failure!\"}\"\"\"\n                        % question\n                    )\n                except WebDriverException:\n                    # Fix for https://github.com/mozilla/geckodriver/issues/431\n                    pass\n                time.sleep(0.05)\n                self.__wait_for_special_alert_absent()\n                text = self.execute_script(\"return window.master_qa_result\")\n            status = text\n\n        self.manual_check_count += 1\n        try:\n            current_url = self.driver.current_url\n        except Exception:\n            current_url = self.execute_script(\"return document.URL\")\n        if \"Success!\" in str(status):\n            self.manual_check_successes += 1\n            self.page_results_list.append(\n                '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"'\n                % (\n                    self.manual_check_count,\n                    \"Success\",\n                    \"-\",\n                    current_url,\n                    self.browser,\n                    self.__get_timestamp()[:-3],\n                    instructions,\n                    \"*\",\n                )\n            )\n            return 1\n        else:\n            bad_page_name = \"failed_check_%s.png\" % self.manual_check_count\n            self.save_screenshot(bad_page_name, folder=self.LATEST_REPORT_DIR)\n            self.page_results_list.append(\n                '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"'\n                % (\n                    self.manual_check_count,\n                    \"FAILED!\",\n                    bad_page_name,\n                    current_url,\n                    self.browser,\n                    self.__get_timestamp()[:-3],\n                    instructions,\n                    \"*\",\n                )\n            )\n            return 0\n\n    def __wait_for_special_alert_absent(self):\n        timeout = self.MAX_IDLE_TIME_BEFORE_QUIT\n        for x in range(int(timeout * 20)):\n            try:\n                alert = self.driver.switch_to.alert\n                dummy_variable = alert.text  # Raises exception if no alert\n                if \"?\" not in dummy_variable:\n                    return\n                time.sleep(0.05)\n            except NoAlertPresentException:\n                return\n        self.driver.quit()\n        raise Exception(\n            \"%s seconds passed without human action! Stopping...\" % timeout\n        )\n\n    def __has_exception(self):\n        has_exception = False\n        if hasattr(sys, \"last_traceback\") and sys.last_traceback is not None:\n            has_exception = True\n        elif hasattr(self, \"_outcome\"):\n            if hasattr(self._outcome, \"errors\"):\n                if python3_11_or_newer:\n                    if (\n                        self._outcome.errors\n                        and self._outcome.errors[-1]\n                        and self._outcome.errors[-1][1]\n                    ):\n                        has_exception = True\n                else:\n                    if self._outcome.errors:\n                        has_exception = True\n        else:\n            has_exception = sys.exc_info()[1] is not None\n        return has_exception\n\n    def __add_failure(self, exception=None):\n        exc_info = None\n        if exception:\n            if hasattr(exception, \"msg\"):\n                exc_info = exception.msg\n            elif hasattr(exception, \"message\"):\n                exc_info = exception.message\n            else:\n                exc_info = \"(Unknown Exception)\"\n\n        self.incomplete_runs += 1\n        error_page = \"automation_failure_%s.png\" % self.incomplete_runs\n        self.save_screenshot(error_page, folder=self.LATEST_REPORT_DIR)\n        self.page_results_list.append(\n            '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"'\n            % (\n                \"ERR\",\n                \"ERROR!\",\n                error_page,\n                self.driver.current_url,\n                self.browser,\n                self.__get_timestamp()[:-3],\n                \"-\",\n                exc_info,\n            )\n        )\n        try:\n            # Return to the original window if another was opened\n            self.driver.switch_to_window(self.driver.window_handles[1])\n            self.driver.close()\n            self.driver.switch_to_window(self.driver.window_handles[0])\n        except Exception:\n            pass\n\n    def __add_bad_page_log_file(self):\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, self.LATEST_REPORT_DIR)\n        log_file = os.path.join(file_path, self.BAD_PAGE_LOG)\n        f = open(log_file, \"w\")\n        h_p1 = \"\"\"\"Num\",\"Result\",\"Screenshot\",\"URL\",\"Browser\",\"Epoch Time\",\"\"\"\n        h_p2 = \"\"\"\"Verification Instructions\",\"Additional Info\"\\n\"\"\"\n        page_header = h_p1 + h_p2\n        f.write(page_header)\n        for line in self.page_results_list:\n            f.write(\"%s\\n\" % line)\n        f.close()\n\n    def __add_results_page(self, html):\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, self.LATEST_REPORT_DIR)\n        results_file_name = self.RESULTS_PAGE\n        results_file = os.path.join(file_path, results_file_name)\n        f = open(results_file, \"w\")\n        f.write(html)\n        f.close()\n        return results_file\n\n    def __process_manual_check_results(self, auto_close_results_page=False):\n        perfection = True\n        failures_count = self.manual_check_count - self.manual_check_successes\n        if not self.headless:\n            print(\"\")\n        print(\"\\n*** MasterQA Manual Test Results: ***\")\n        if self.manual_check_successes == self.manual_check_count:\n            pass\n        else:\n            print(\"WARNING: Not all tests passed manual inspection!\")\n            perfection = False\n\n        if self.incomplete_runs > 0:\n            print(\"WARNING: Not all tests finished running!\")\n            perfection = False\n\n        if perfection:\n            if self.manual_check_count > 0:\n                print(\"SUCCESS: Everything checks out OKAY!\")\n            else:\n                print(\"WARNING: No manual checks were performed!\")\n        else:\n            pass\n        self.__add_bad_page_log_file()  # Includes successful results\n\n        log_string = self.__clear_out_old_logs(get_log_folder=True)\n        log_folder = log_string.split(os.sep)[-1]\n        abs_path = os.path.abspath(\".\")\n        file_path = os.path.join(abs_path, self.ARCHIVE_DIR)\n        log_path = os.path.join(file_path, log_folder)\n        web_log_path = \"file://%s\" % log_path\n\n        tf_color = \"#11BB11\"\n        if failures_count > 0:\n            tf_color = \"#EE3A3A\"\n\n        ir_color = \"#11BB11\"\n        if self.incomplete_runs > 0:\n            ir_color = \"#EE3A3A\"\n\n        summary_table = \"\"\"<div><table><thead><tr>\n              <th>TESTING SUMMARY</th>\n              <th>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>\n              </tr></thead><tbody>\n              <tr style=\"color:#00BB00\"><td>CHECKS PASSED: <td>%s</tr>\n              <tr style=\"color:%s\"     ><td>CHECKS FAILED: <td>%s</tr>\n              <tr style=\"color:#4D4DDD\"><td>TOTAL VERIFICATIONS: <td>%s</tr>\n              <tr style=\"color:%s\"     ><td>INCOMPLETE TEST RUNS: <td>%s</tr>\n              </tbody></table>\"\"\" % (\n            self.manual_check_successes,\n            tf_color,\n            failures_count,\n            self.manual_check_count,\n            ir_color,\n            self.incomplete_runs,\n        )\n\n        summary_table = (\n            \"\"\"<h1 id=\"ContextHeader\" class=\"sectionHeader\" title=\"\">\n                     %s</h1>\"\"\"\n            % summary_table\n        )\n\n        log_link_shown = os.path.join(\n            \"..\", \"%s%s\" % (\n                self.ARCHIVE_DIR, web_log_path.split(self.ARCHIVE_DIR)[1]\n            )\n        )\n        csv_link = os.path.join(web_log_path, self.BAD_PAGE_LOG)\n        csv_link_shown = \"%s\" % self.BAD_PAGE_LOG\n        log_table = \"\"\"<p><p><p><p><h2><table><tbody>\n            <tr><td>LOG FILES LINK:&nbsp;&nbsp;<td><a href=\"%s\">%s</a></tr>\n            <tr><td>RESULTS TABLE:&nbsp;&nbsp;<td><a href=\"%s\">%s</a></tr>\n            </tbody></table></h2><p><p><p><p>\"\"\" % (\n            web_log_path,\n            log_link_shown,\n            csv_link,\n            csv_link_shown,\n        )\n\n        failure_table = \"<h2><table><tbody></div>\"\n        any_screenshots = False\n        for line in self.page_results_list:\n            line = line.split(\",\")\n            if line[1] == '\"FAILED!\"' or line[1] == '\"ERROR!\"':\n                if not any_screenshots:\n                    any_screenshots = True\n                    failure_table += \"\"\"<thead><tr>\n                        <th>SCREENSHOT FILE&nbsp;&nbsp;&nbsp;&nbsp;</th>\n                        <th>LOCATION OF FAILURE</th>\n                        </tr></thead>\"\"\"\n                display_url = line[3]\n                if len(display_url) > 60:\n                    display_url = display_url[0:58] + \"...\"\n                line = (\n                    '<a href=\"%s\">%s</a>'\n                    % (\"file://\" + log_path + \"/\" + line[2], line[2])\n                    + \"\"\"\n                    &nbsp;&nbsp;&nbsp;&nbsp;<td>\n                    \"\"\"\n                    + '<a href=\"%s\">%s</a>' % (line[3], display_url)\n                )\n                line = line.replace('\"', \"\")\n                failure_table += \"<tr><td>%s</tr>\\n\" % line\n        failure_table += \"</tbody></table>\"\n        table_view = \"%s%s%s\" % (summary_table, log_table, failure_table)\n        report_html = \"<html><head>%s</head><body>%s</body></html>\" % (\n            get_report_style(),\n            table_view,\n        )\n        results_file = self.__add_results_page(report_html)\n        archived_results_file = os.path.join(log_path, self.RESULTS_PAGE)\n        shutil.copyfile(results_file, os.path.realpath(archived_results_file))\n        if self.manual_check_count > 0:\n            print(\n                \"\\n*** The manual test report is located at:\\n\" + results_file\n            )\n        self.open(\"file://%s\" % archived_results_file)\n        if auto_close_results_page:\n            # Long enough to notice the results before closing the page\n            time.sleep(1.0)\n        else:\n            # The user can decide when to close the results page\n            print(\"\\n*** Close the html report window to continue ***\")\n            try:\n                while len(self.driver.window_handles):\n                    time.sleep(0.1)\n            except Exception:\n                pass\n"
  },
  {
    "path": "seleniumbase/plugins/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/plugins/base_plugin.py",
    "content": "\"\"\"Base Plugin for SeleniumBase tests that run with pynose / nosetests\"\"\"\nimport ast\nimport sys\nimport time\nfrom contextlib import suppress\nfrom nose.plugins import Plugin\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import download_helper\nfrom seleniumbase.core import log_helper\nfrom seleniumbase.core import report_helper\nfrom seleniumbase.fixtures import constants\n\npython3_11_or_newer = False\nif sys.version_info >= (3, 11):\n    python3_11_or_newer = True\npy311_patch2 = constants.PatchPy311.PATCH = True\n\n\nclass Base(Plugin):\n    \"\"\"This plugin adds the following command-line options to pynose:\n    --env=ENV  (Set the test env. Access with \"self.env\" in tests.)\n    --account=STR  (Set account. Access with \"self.account\" in tests.)\n    --data=STRING  (Extra test data. Access with \"self.data\" in tests.)\n    --var1=STRING  (Extra test data. Access with \"self.var1\" in tests.)\n    --var2=STRING  (Extra test data. Access with \"self.var2\" in tests.)\n    --var3=STRING  (Extra test data. Access with \"self.var3\" in tests.)\n    --variables=DICT  (Extra test data. Access with \"self.variables\".)\n    --settings-file=FILE  (Override default SeleniumBase settings.)\n    --ftrace | --final-trace  (Enter Debug Mode after any test ends.)\n    --archive-logs  (Archive old log files instead of deleting them.)\n    --archive-downloads  (Archive old downloads instead of deleting.)\n    --report  (Create a fancy nosetests report after tests complete.)\n    --show-report   If self.report is turned on, then the report will\n                    display immediately after tests complete their run.\n                    Only use this when running tests locally, as this will\n                    pause the test run until the report window is closed.\n    \"\"\"\n    name = \"testing_base\"  # Usage: --with-testing_base  (Enabled by default)\n\n    def options(self, parser, env):\n        super().options(parser, env=env)\n        parser.addoption = parser.add_option  # Reuse name from pytest parser\n        parser.addoption(\n            \"--env\",\n            action=\"store\",\n            dest=\"environment\",\n            choices=(\n                constants.Environment.QA,\n                constants.Environment.RC,\n                constants.Environment.STAGING,\n                constants.Environment.DEVELOP,\n                constants.Environment.PRODUCTION,\n                constants.Environment.PERFORMANCE,\n                constants.Environment.REPLICA,\n                constants.Environment.FEDRAMP,\n                constants.Environment.OFFLINE,\n                constants.Environment.ONLINE,\n                constants.Environment.MASTER,\n                constants.Environment.REMOTE,\n                constants.Environment.LEGACY,\n                constants.Environment.LOCAL,\n                constants.Environment.ALPHA,\n                constants.Environment.BETA,\n                constants.Environment.DEMO,\n                constants.Environment.GDPR,\n                constants.Environment.MAIN,\n                constants.Environment.TEST,\n                constants.Environment.GOV,\n                constants.Environment.NEW,\n                constants.Environment.OLD,\n                constants.Environment.UAT,\n            ),\n            default=constants.Environment.TEST,\n            help=\"\"\"This option sets a test env from a list of choices.\n                    Access using \"self.env\" or \"self.environment\".\"\"\",\n        )\n        parser.addoption(\n            \"--account\",\n            dest=\"account\",\n            default=None,\n            help=\"\"\"This option sets a test account string.\n                    In tests, use \"self.account\" to get the value.\"\"\",\n        )\n        parser.addoption(\n            \"--data\",\n            dest=\"data\",\n            default=None,\n            help=\"Extra data to pass to tests from the command line.\",\n        )\n        parser.addoption(\n            \"--var1\",\n            dest=\"var1\",\n            default=None,\n            help=\"Extra data to pass to tests from the command line.\",\n        )\n        parser.addoption(\n            \"--var2\",\n            dest=\"var2\",\n            default=None,\n            help=\"Extra data to pass to tests from the command line.\",\n        )\n        parser.addoption(\n            \"--var3\",\n            dest=\"var3\",\n            default=None,\n            help=\"Extra data to pass to tests from the command line.\",\n        )\n        parser.addoption(\n            \"--variables\",\n            dest=\"variables\",\n            default=None,\n            help=\"\"\"A var dict to pass to tests from the command line.\n                    Example usage:\n                    ----------------------------------------------\n                    Option: --variables='{\"special\":123}'\n                    Access: self.variables[\"special\"]\n                    ----------------------------------------------\n                    Option: --variables='{\"color\":\"red\",\"num\":42}'\n                    Access: self.variables[\"color\"]\n                    Access: self.variables[\"num\"]\n                    ----------------------------------------------\"\"\",\n        )\n        parser.addoption(\n            \"--settings_file\",\n            \"--settings-file\",\n            \"--settings\",\n            action=\"store\",\n            dest=\"settings_file\",\n            default=None,\n            help=\"\"\"The file that stores key/value pairs for overriding\n                    values in the SeleniumBase settings.py file.\"\"\",\n        )\n        parser.addoption(\n            \"--final-debug\",\n            \"--final-trace\",\n            \"--fdebug\",\n            \"--ftrace\",\n            action=\"store_true\",\n            dest=\"final_debug\",\n            default=False,\n            help=\"\"\"Enter Debug Mode at the end of each test.\n                    To enter Debug Mode only on failures, use \"--pdb\".\n                    If using both \"--final-debug\" and \"--pdb\" together,\n                    then Debug Mode will activate twice on failures.\"\"\",\n        )\n        parser.addoption(\n            \"--log_path\",\n            \"--log-path\",\n            dest=\"log_path\",\n            default=constants.Logs.LATEST + \"/\",\n            help=\"\"\"(DEPRECATED) - This field is NOT EDITABLE anymore.\n                    Log files are saved to the \"latest_logs/\" folder.\"\"\",\n        )\n        parser.addoption(\n            \"--archive_logs\",\n            \"--archive-logs\",\n            action=\"store_true\",\n            dest=\"archive_logs\",\n            default=False,\n            help=\"Archive old log files instead of deleting them.\",\n        )\n        parser.addoption(\n            \"--archive_downloads\",\n            \"--archive-downloads\",\n            action=\"store_true\",\n            dest=\"archive_downloads\",\n            default=False,\n            help=\"Archive old downloads instead of deleting them.\",\n        )\n        parser.addoption(\n            \"--report\",\n            action=\"store_true\",\n            dest=\"report\",\n            default=False,\n            help=\"Create a fancy report at the end of the test suite.\",\n        )\n        parser.addoption(\n            \"--show_report\",\n            \"--show-report\",\n            action=\"store_true\",\n            dest=\"show_report\",\n            default=False,\n            help=\"If true when using report, will display it after tests run.\",\n        )\n        found_processes_arg = False\n        for arg in sys.argv:\n            if \"--processes=\" in arg or \"--processes\" in arg:\n                found_processes_arg = True\n        if found_processes_arg:\n            print(\"* WARNING: Don't use multi-threading with nosetests! *\")\n            parser.addoption(\n                \"--processes\",\n                dest=\"processes\",\n                default=0,\n                help=\"WARNING: Don't use multi-threading with nosetests!\",\n            )\n\n    def configure(self, options, conf):\n        super().configure(options, conf)\n        self.enabled = True  # Used if test class inherits BaseCase\n        self.options = options\n        self.report_on = options.report\n        self.show_report = options.show_report\n        self.successes = []\n        self.failures = []\n        self.start_time = float(0)\n        self.duration = float(0)\n        self.page_results_list = []\n        self.test_count = 0\n        log_path = constants.Logs.LATEST + \"/\"\n        archive_logs = options.archive_logs\n        log_helper.log_folder_setup(log_path, archive_logs)\n        download_helper.reset_downloads_folder()\n        sb_config.is_nosetest = True\n        if self.report_on:\n            report_helper.clear_out_old_report_logs(archive_past_runs=False)\n\n    def beforeTest(self, test):\n        sb_config._context_of_runner = False  # Context Manager Compatibility\n        variables = self.options.variables\n        if variables and isinstance(variables, str) and len(variables) > 0:\n            bad_input = False\n            if not variables.startswith(\"{\") or not variables.endswith(\"}\"):\n                bad_input = True\n            else:\n                try:\n                    variables = ast.literal_eval(variables)\n                    if not isinstance(variables, dict):\n                        bad_input = True\n                except Exception:\n                    bad_input = True\n            if bad_input:\n                raise Exception(\n                    '\\nExpecting a Python dictionary for \"variables\"!'\n                    \"\\nEg. --variables=\\\"{'KEY1':'VALUE', 'KEY2':123}\\\"\"\n                )\n        else:\n            variables = {}\n        test.test.test_id = test.id()\n        test.test.is_nosetest = True\n        test.test.environment = self.options.environment\n        sb_config.environment = self.options.environment\n        test.test.env = self.options.environment  # Add a shortened version\n        test.test.account = self.options.account\n        sb_config.account = self.options.account\n        test.test.data = self.options.data\n        sb_config.data = self.options.data\n        test.test.var1 = self.options.var1\n        sb_config.var1 = self.options.var1\n        test.test.var2 = self.options.var2\n        sb_config.var2 = self.options.var2\n        test.test.var3 = self.options.var3\n        sb_config.var3 = self.options.var3\n        test.test.variables = variables  # Already verified is a dictionary\n        sb_config.variables = variables\n        test.test.settings_file = self.options.settings_file\n        sb_config.settings_file = self.options.settings_file\n        test.test._final_debug = self.options.final_debug\n        test.test.log_path = self.options.log_path\n        if self.options.archive_downloads:\n            settings.ARCHIVE_EXISTING_DOWNLOADS = True\n        test.test.args = self.options\n        test.test.report_on = self.report_on\n        self.test_count += 1\n        self.start_time = float(time.time())\n\n    def finalize(self, result):\n        log_helper.archive_logs_if_set(\n            self.options.log_path, self.options.archive_logs\n        )\n        log_helper.clear_empty_logs()\n        if self.report_on:\n            report_helper.add_bad_page_log_file(self.page_results_list)\n            report_log_path = report_helper.archive_new_report_logs()\n            report_helper.build_report(\n                report_log_path,\n                self.page_results_list,\n                self.successes,\n                self.failures,\n                self.options.browser,\n                self.show_report,\n            )\n\n    def addSuccess(self, test, capt):\n        if self.report_on:\n            self.duration = str(\n                \"%.2fs\" % (float(time.time()) - float(self.start_time))\n            )\n            self.successes.append(test.id())\n            self.page_results_list.append(\n                report_helper.process_successes(\n                    test, self.test_count, self.duration\n                )\n            )\n\n    def add_fails_or_errors(self, test, err):\n        if self.report_on:\n            self.duration = str(\n                \"%.2fs\" % (float(time.time()) - float(self.start_time))\n            )\n            if test.id() == \"nose.failure.Failure.runTest\":\n                return\n            self.failures.append(test.id())\n            self.page_results_list.append(\n                report_helper.process_failures(\n                    test, self.test_count, self.duration\n                )\n            )\n        if python3_11_or_newer and py311_patch2:\n            # Handle a bug on Python 3.11 where exceptions aren't seen\n            sb_config._browser_version = None\n            with suppress(Exception):\n                test._BaseCase__set_last_page_screenshot()\n                test._BaseCase__set_last_page_url()\n                test._BaseCase__set_last_page_source()\n                sb_config._browser_version = test._get_browser_version()\n                test._log_fail_data()\n            sb_config._excinfo_tb = err\n            log_path = None\n            source = None\n            if hasattr(sb_config, \"_test_logpath\"):\n                log_path = sb_config._test_logpath\n            if hasattr(sb_config, \"_last_page_source\"):\n                source = sb_config._last_page_source\n            if log_path and source:\n                log_helper.log_page_source(log_path, None, source)\n            last_page_screenshot_png = None\n            if hasattr(sb_config, \"_last_page_screenshot_png\"):\n                last_page_screenshot_png = sb_config._last_page_screenshot_png\n            if log_path and last_page_screenshot_png:\n                log_helper.log_screenshot(\n                    log_path, None, last_page_screenshot_png\n                )\n\n    def addFailure(self, test, err, capt=None, tbinfo=None):\n        self.add_fails_or_errors(test, err)\n\n    def addError(self, test, err, capt=None):\n        \"\"\"Since Skip, Blocked, and Deprecated are all technically errors,\n        but not error states, we want to make sure that they\n        don't show up in the nose output as errors.\"\"\"\n        from seleniumbase.fixtures import errors\n\n        if (\n            err[0] == errors.BlockedTest\n            or (err[0] == errors.SkipTest)\n            or (err[0] == errors.DeprecatedTest)\n        ):\n            print(\n                err[1]\n                .__str__()\n                .split(\n                    \"\"\"-------------------- >> \"\"\"\n                    \"\"\"begin captured logging\"\"\"\n                    \"\"\" << --------------------\"\"\",\n                    1,\n                )[0]\n            )\n        else:\n            pass\n        self.add_fails_or_errors(test, err)\n\n    def handleError(self, test, err, capt=None):\n        \"\"\"After each test error, record testcase run information.\n        \"Error\" also encompasses any states other than Pass or Fail.\"\"\"\n        from nose.exc import SkipTest\n        from seleniumbase.fixtures import errors\n\n        if not hasattr(test.test, \"testcase_guid\"):\n            if err[0] == errors.BlockedTest:\n                raise SkipTest(err[1])\n            elif err[0] == errors.DeprecatedTest:\n                raise SkipTest(err[1])\n            elif err[0] == errors.SkipTest:\n                raise SkipTest(err[1])\n"
  },
  {
    "path": "seleniumbase/plugins/basic_test_info.py",
    "content": "\"\"\"Test Info Plugin for SeleniumBase tests that run with pynose / nosetests\"\"\"\nimport os\nimport time\nimport traceback\nfrom nose.plugins import Plugin\nfrom seleniumbase.config import settings\n\n\nclass BasicTestInfo(Plugin):\n    \"\"\"This plugin captures basic info when a test fails or raises an error.\"\"\"\n    name = \"basic_test_info\"  # Usage: --with-basic_test_info\n    logfile_name = settings.BASIC_INFO_NAME\n\n    def options(self, parser, env):\n        super().options(parser, env=env)\n\n    def configure(self, options, conf):\n        super().configure(options, conf)\n        if not self.enabled:\n            return\n        self.options = options\n\n    def addError(self, test, err, capt=None):\n        test_logpath = self.options.log_path + \"/\" + test.id()\n        if not os.path.exists(test_logpath):\n            os.makedirs(test_logpath)\n        file_name = \"%s/%s\" % (test_logpath, self.logfile_name)\n        basic_info_file = open(file_name, mode=\"w+\", encoding=\"utf-8\")\n        self.__log_test_error_data(basic_info_file, test, err, \"Error\")\n        basic_info_file.close()\n\n    def addFailure(self, test, err, capt=None, tbinfo=None):\n        test_logpath = self.options.log_path + \"/\" + test.id()\n        if not os.path.exists(test_logpath):\n            os.makedirs(test_logpath)\n        file_name = \"%s/%s\" % (test_logpath, self.logfile_name)\n        basic_info_file = open(file_name, mode=\"w+\", encoding=\"utf-8\")\n        self.__log_test_error_data(basic_info_file, test, err, \"Error\")\n        basic_info_file.close()\n\n    def __log_test_error_data(self, log_file, test, err, type):\n        data_to_save = []\n        data_to_save.append(\"Last Page: %s\" % test.driver.current_url)\n        data_to_save.append(\"  Browser: %s\" % self.options.browser)\n        data_to_save.append(\"Timestamp: %s\" % int(time.time()))\n        data_to_save.append(\"Server: %s \" % self.options.servername)\n        data_to_save.append(\"%s: %s\" % (type, err[0]))\n        data_to_save.append(\n            \"Traceback: \" + \"\".join(traceback.format_exception(*err))\n        )\n        log_file.writelines(\"\\r\\n\".join(data_to_save))\n"
  },
  {
    "path": "seleniumbase/plugins/db_reporting_plugin.py",
    "content": "\"\"\"DB Reporting Plugin for SeleniumBase tests that use pynose / nosetests\"\"\"\nimport time\nimport uuid\nfrom nose.plugins import Plugin\nfrom seleniumbase.fixtures import constants\n\n\nclass DBReporting(Plugin):\n    \"\"\"This plugin records test results in the Testcase Database.\"\"\"\n    name = \"db_reporting\"  # Usage: --with-db_reporting\n\n    def __init__(self):\n        Plugin.__init__(self)\n        self.execution_guid = str(uuid.uuid4())\n        self.testcase_guid = None\n        self.execution_start_time = 0\n        self.case_start_time = 0\n        self.testcase_manager = None\n        self._result_set = False\n        self._test = None\n\n    def options(self, parser, env):\n        super().options(parser, env=env)\n        parser.add_option(\n            \"--database_env\",\n            \"--database-env\",\n            action=\"store\",\n            dest=\"database_env\",\n            choices=(\n                constants.Environment.QA,\n                constants.Environment.RC,\n                constants.Environment.STAGING,\n                constants.Environment.DEVELOP,\n                constants.Environment.PRODUCTION,\n                constants.Environment.PERFORMANCE,\n                constants.Environment.REPLICA,\n                constants.Environment.FEDRAMP,\n                constants.Environment.OFFLINE,\n                constants.Environment.ONLINE,\n                constants.Environment.MASTER,\n                constants.Environment.REMOTE,\n                constants.Environment.LEGACY,\n                constants.Environment.LOCAL,\n                constants.Environment.ALPHA,\n                constants.Environment.BETA,\n                constants.Environment.DEMO,\n                constants.Environment.GDPR,\n                constants.Environment.MAIN,\n                constants.Environment.TEST,\n                constants.Environment.GOV,\n                constants.Environment.NEW,\n                constants.Environment.OLD,\n                constants.Environment.UAT,\n            ),\n            default=constants.Environment.TEST,\n            help=\"The database environment to run the tests in.\",\n        )\n\n    def configure(self, options, conf):\n        from seleniumbase.core.testcase_manager import TestcaseManager\n\n        super().configure(options, conf)\n        self.options = options\n        self.testcase_manager = TestcaseManager(self.options.database_env)\n\n    def begin(self):\n        \"\"\"At the start of the run, we want to record the test\n        execution information in the database.\"\"\"\n        import getpass\n        from seleniumbase.core.testcase_manager import ExecutionQueryPayload\n\n        exec_payload = ExecutionQueryPayload()\n        exec_payload.execution_start_time = int(time.time() * 1000)\n        self.execution_start_time = exec_payload.execution_start_time\n        exec_payload.guid = self.execution_guid\n        exec_payload.username = getpass.getuser()\n        self.testcase_manager.insert_execution_data(exec_payload)\n\n    def startTest(self, test):\n        \"\"\"At the start of the test, set testcase details.\"\"\"\n        from seleniumbase.core.application_manager import ApplicationManager\n        from seleniumbase.core.testcase_manager import TestcaseDataPayload\n\n        data_payload = TestcaseDataPayload()\n        self.testcase_guid = str(uuid.uuid4())\n        data_payload.guid = self.testcase_guid\n        data_payload.execution_guid = self.execution_guid\n        if hasattr(test, \"browser\"):\n            data_payload.browser = test.browser\n        else:\n            data_payload.browser = \"N/A\"\n        data_payload.test_address = test.id()\n        application = ApplicationManager.generate_application_string(test)\n        data_payload.env = application.split(\".\")[0]\n        data_payload.start_time = application.split(\".\")[1]\n        data_payload.state = constants.State.UNTESTED\n        self.testcase_manager.insert_testcase_data(data_payload)\n        self.case_start_time = int(time.time() * 1000)\n        # Make the testcase guid available to other plugins\n        test.testcase_guid = self.testcase_guid\n        self._test = test\n        self._test._nose_skip_reason = None\n\n    def finalize(self, result):\n        \"\"\"At the end of the test run, we want to\n        update the DB row with the total execution time.\"\"\"\n        runtime = int(time.time() * 1000) - self.execution_start_time\n        self.testcase_manager.update_execution_data(\n            self.execution_guid, runtime\n        )\n\n    def afterTest(self, test):\n        if not self._result_set:\n            err = None\n            try:\n                err = self._test._nose_skip_reason\n                if err:\n                    err = \"Skipped:   \" + str(err)\n                    err = (err, err)\n            except Exception:\n                pass\n            if not err:\n                err = \"Skipped:   (no reason given)\"\n                err = (err, err)\n            self.__insert_test_result(constants.State.SKIPPED, self._test, err)\n\n    def addSuccess(self, test, capt):\n        \"\"\"After each test success, record testcase run information.\"\"\"\n        self.__insert_test_result(constants.State.PASSED, test)\n        self._result_set = True\n\n    def addFailure(self, test, err, capt=None, tbinfo=None):\n        \"\"\"After each test failure, record testcase run information.\"\"\"\n        self.__insert_test_result(constants.State.FAILED, test, err)\n        self._result_set = True\n\n    def addError(self, test, err, capt=None):\n        \"\"\"After each test error, record testcase run information.\n        (Test errors should be treated the same as test failures.)\"\"\"\n        self.__insert_test_result(constants.State.FAILED, test, err)\n        self._result_set = True\n\n    def handleError(self, test, err, capt=None):\n        \"\"\"After each test error, record testcase run information.\n        \"Error\" also encompasses any states other than Pass or Fail.\"\"\"\n        from nose.exc import SkipTest\n        from seleniumbase.fixtures import errors\n\n        if err[0] == errors.BlockedTest:\n            self.__insert_test_result(constants.State.BLOCKED, test, err)\n            self._result_set = True\n            raise SkipTest(err[1])\n        elif err[0] == errors.DeprecatedTest:\n            self.__insert_test_result(constants.State.DEPRECATED, test, err)\n            self._result_set = True\n            raise SkipTest(err[1])\n        elif err[0] == errors.SkipTest:\n            self.__insert_test_result(constants.State.SKIPPED, test, err)\n            self._result_set = True\n            raise SkipTest(err[1])\n\n    def __insert_test_result(self, state, test, err=None):\n        from seleniumbase.core.testcase_manager import TestcaseDataPayload\n\n        data_payload = TestcaseDataPayload()\n        data_payload.runtime = int(time.time() * 1000) - self.case_start_time\n        data_payload.guid = self.testcase_guid\n        data_payload.execution_guid = self.execution_guid\n        data_payload.state = state\n        if err is not None:\n            data_payload.message = (\n                err[1]\n                .__str__()\n                .split(\n                    \"\"\"-------------------- >> \"\"\"\n                    \"\"\"begin captured logging\"\"\"\n                    \"\"\" << --------------------\"\"\",\n                    1,\n                )[0]\n            )\n        self.testcase_manager.update_testcase_data(data_payload)\n"
  },
  {
    "path": "seleniumbase/plugins/driver_manager.py",
    "content": "\"\"\"\nThe SeleniumBase Driver as a Python Context Manager or a returnable object.\n###########################################################################\n\nThe SeleniumBase Driver as a context manager:\nUsage --> ``with DriverContext() as driver:``\n\nExample -->\n\n```python\nfrom seleniumbase import DriverContext\n\nwith DriverContext(uc=True) as driver:\n    driver.get(\"https://google.com/ncr\")\n```\n\n# (The browser exits automatically after the \"with\" block ends.)\n\n###########################################################################\n# Above: The driver as a context manager. (Used with a \"with\" statement.) #\n# ----------------------------------------------------------------------- #\n# Below: The driver as a returnable object. (Used with \"return\" command.) #\n###########################################################################\n\nThe SeleniumBase Driver as a returnable object:\nUsage --> ``driver = Driver()``\n\nExample -->\n\n```python\nfrom seleniumbase import Driver\n\ndriver = Driver(uc=True)\ndriver.get(\"https://google.com/ncr\")\n```\n\n###########################################################################\n\"\"\"\nimport os\nimport sys\nfrom seleniumbase.core import sb_driver\n\n\nclass DriverContext():\n    def __init__(self, *args, **kwargs):\n        self.driver = Driver(*args, **kwargs)\n\n    def __enter__(self):\n        return self.driver\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        try:\n            if (\n                hasattr(self, \"driver\")\n                and hasattr(self.driver, \"quit\")\n                and (\n                    \"win32\" not in sys.platform\n                    or self.driver.service.process\n                )\n            ):\n                self.driver.quit()\n        except Exception:\n            pass\n        return False\n\n\ndef Driver(\n    browser=None,  # Choose from \"chrome\", \"edge\", \"firefox\", or \"safari\".\n    headless=None,  # Use the default headless mode for Chromium and Firefox.\n    headless1=None,  # Use Chromium's old headless mode. (Fast, but limited)\n    headless2=None,  # Use Chromium's new headless mode. (Has more features)\n    headed=None,  # Run tests in headed/GUI mode on Linux, where not default.\n    locale_code=None,  # Set the Language Locale Code for the web browser.\n    protocol=None,  # The Selenium Grid protocol: \"http\" or \"https\".\n    servername=None,  # The Selenium Grid server/IP used for tests.\n    port=None,  # The Selenium Grid port used by the test server.\n    proxy=None,  # Use proxy. Format: \"SERVER:PORT\" or \"USER:PASS@SERVER:PORT\".\n    proxy_bypass_list=None,  # Skip proxy when using the listed domains.\n    proxy_pac_url=None,  # Use PAC file. (Format: URL or USERNAME:PASSWORD@URL)\n    multi_proxy=None,  # Allow multiple proxies with auth when multi-threaded.\n    agent=None,  # Modify the web browser's User-Agent string.\n    cap_file=None,  # The desired capabilities to use with a Selenium Grid.\n    cap_string=None,  # The desired capabilities to use with a Selenium Grid.\n    recorder_ext=None,  # Enables the SeleniumBase Recorder Chromium extension.\n    disable_cookies=None,  # Disable Cookies on websites. (Pages might break!)\n    disable_js=None,  # Disable JavaScript on websites. (Pages might break!)\n    disable_csp=None,  # Disable the Content Security Policy of websites.\n    enable_ws=None,  # Enable Web Security on Chromium-based browsers.\n    disable_ws=None,  # Reverse of \"enable_ws\". (None and False are different)\n    enable_sync=None,  # Enable \"Chrome Sync\" on websites.\n    use_auto_ext=None,  # Use Chrome's automation extension.\n    undetectable=None,  # Use undetected-chromedriver to evade bot-detection.\n    uc_cdp_events=None,  # Capture CDP events in undetected-chromedriver mode.\n    uc_subprocess=None,  # Use undetected-chromedriver as a subprocess.\n    log_cdp_events=None,  # Capture {\"performance\": \"ALL\", \"browser\": \"ALL\"}\n    no_sandbox=None,  # (DEPRECATED) - \"--no-sandbox\" is always used now.\n    disable_gpu=None,  # (DEPRECATED) - GPU is disabled if not \"swiftshader\".\n    incognito=None,  # Enable Chromium's Incognito mode.\n    guest_mode=None,  # Enable Chromium's Guest mode.\n    dark_mode=None,  # Enable Chromium's Dark mode.\n    devtools=None,  # Open Chromium's DevTools when the browser opens.\n    remote_debug=None,  # Enable Chrome's Debugger on \"http://localhost:9222\".\n    enable_3d_apis=None,  # Enable WebGL and 3D APIs.\n    swiftshader=None,  # Chrome: --use-gl=angle / --use-angle=swiftshader-webgl\n    ad_block_on=None,  # Block some types of display ads from loading.\n    host_resolver_rules=None,  # Set host-resolver-rules, comma-separated.\n    block_images=None,  # Block images from loading during tests.\n    do_not_track=None,  # Tell websites that you don't want to be tracked.\n    chromium_arg=None,  # \"ARG=N,ARG2\" (Set Chromium args, \",\"-separated.)\n    firefox_arg=None,  # \"ARG=N,ARG2\" (Set Firefox args, comma-separated.)\n    firefox_pref=None,  # SET (Set Firefox PREFERENCE:VALUE set, \",\"-separated)\n    user_data_dir=None,  # Set the Chrome user data directory to use.\n    extension_zip=None,  # Load a Chrome Extension .zip|.crx, comma-separated.\n    extension_dir=None,  # Load a Chrome Extension directory, comma-separated.\n    disable_features=None,  # \"F1,F2\" (Disable Chrome features, \",\"-separated.)\n    binary_location=None,  # Set path of the Chromium browser binary to use.\n    driver_version=None,  # Set the chromedriver or uc_driver version to use.\n    page_load_strategy=None,  # Set Chrome PLS to \"normal\", \"eager\", or \"none\".\n    use_wire=None,  # Use selenium-wire's webdriver over selenium webdriver.\n    external_pdf=None,  # Set Chrome \"plugins.always_open_pdf_externally\":True.\n    window_position=None,  # Set the browser's starting window position: \"X,Y\"\n    window_size=None,  # Set the browser's starting window size: \"Width,Height\"\n    is_mobile=None,  # Use the mobile device emulator while running tests.\n    mobile=None,  # Shortcut / Duplicate of \"is_mobile\".\n    d_width=None,  # Set device width\n    d_height=None,  # Set device height\n    d_p_r=None,  # Set device pixel ratio\n    position=None,  # Shortcut / Duplicate of \"window_position\".\n    size=None,  # Shortcut / Duplicate of \"window_size\".\n    uc=None,  # Shortcut / Duplicate of \"undetectable\".\n    undetected=None,  # Shortcut / Duplicate of \"undetectable\".\n    uc_cdp=None,  # Shortcut / Duplicate of \"uc_cdp_events\".\n    uc_sub=None,  # Shortcut / Duplicate of \"uc_subprocess\".\n    locale=None,  # Shortcut / Duplicate of \"locale_code\".\n    log_cdp=None,  # Shortcut / Duplicate of \"log_cdp_events\".\n    ad_block=None,  # Shortcut / Duplicate of \"ad_block_on\".\n    server=None,  # Shortcut / Duplicate of \"servername\".\n    guest=None,  # Shortcut / Duplicate of \"guest_mode\".\n    wire=None,  # Shortcut / Duplicate of \"use_wire\".\n    pls=None,  # Shortcut / Duplicate of \"page_load_strategy\".\n    cft=None,  # Use \"Chrome for Testing\"\n    chs=None,  # Use \"Chrome-Headless-Shell\"\n    use_chromium=None,  # Use base \"Chromium\"\n) -> sb_driver.DriverMethods:\n    \"\"\"\n    * SeleniumBase Driver as a Python Context Manager or a returnable object. *\n\n    Example 1: (context manager format)\n    -----------------------------------\n    .. code-block:: python\n        from seleniumbase import DriverContext\n\n        with DriverContext() as driver:\n            driver.get(\"https://google.com/ncr\")\n\n    Example 2: (as a Python returnable)\n    -----------------------------------\n    .. code-block:: python\n        from seleniumbase import Driver\n\n        driver = Driver()\n        driver.get(\"https://google.com/ncr\")\n\n    Optional Parameters:\n    --------------------\n    browser (str):  Choose from \"chrome\", \"edge\", \"firefox\", or \"safari\".\n    headless (bool):  Use the default headless mode for Chromium and Firefox.\n    headless1 (bool):  Use Chromium's old headless mode. (Fast, but limited)\n    headless2 (bool):  Use Chromium's new headless mode. (Has more features)\n    headed (bool):  Run tests in headed/GUI mode on Linux, where not default.\n    locale_code (str):  Set the Language Locale Code for the web browser.\n    protocol (str):  The Selenium Grid protocol: \"http\" or \"https\".\n    servername (str):  The Selenium Grid server/IP used for tests.\n    port (int):  The Selenium Grid port used by the test server.\n    proxy (str):  Use proxy. Format: \"SERVER:PORT\" or \"USER:PASS@SERVER:PORT\".\n    proxy_bypass_list (str):  Skip proxy when using the listed domains.\n    proxy_pac_url (str):  Use PAC file. (Format: URL or USERNAME:PASSWORD@URL)\n    multi_proxy (bool):  Allow multiple proxies with auth when multi-threaded.\n    agent (str):  Modify the web browser's User-Agent string.\n    cap_file (str):  The desired capabilities to use with a Selenium Grid.\n    cap_string (str):  The desired capabilities to use with a Selenium Grid.\n    recorder_ext (bool):  Enables the SeleniumBase Recorder Chromium extension.\n    disable_cookies (bool):  Disable Cookies on websites. (Pages might break!)\n    disable_js (bool):  Disable JavaScript on websites. (Pages might break!)\n    disable_csp (bool):  Disable the Content Security Policy of websites.\n    enable_ws (bool):  Enable Web Security on Chromium-based browsers.\n    disable_ws (bool):  Reverse of \"enable_ws\". (None and False are different)\n    enable_sync (bool):  Enable \"Chrome Sync\" on websites.\n    use_auto_ext (bool):  Use Chrome's automation extension.\n    undetectable (bool):  Use undetected-chromedriver to evade bot-detection.\n    uc_cdp_events (bool):  Capture CDP events in undetected-chromedriver mode.\n    uc_subprocess (bool):  Use undetected-chromedriver as a subprocess.\n    log_cdp_events (bool):  Capture {\"performance\": \"ALL\", \"browser\": \"ALL\"}\n    no_sandbox (bool):  (DEPRECATED) - \"--no-sandbox\" is always used now.\n    disable_gpu (bool):  (DEPRECATED) - GPU is disabled if not \"swiftshader\".\n    incognito (bool):  Enable Chromium's Incognito mode.\n    guest_mode (bool):  Enable Chromium's Guest mode.\n    dark_mode (bool):  Enable Chromium's Dark mode.\n    devtools (bool):  Open Chromium's DevTools when the browser opens.\n    remote_debug (bool):  Enable Chrome's Debugger on \"http://localhost:9222\".\n    enable_3d_apis (bool):  Enable WebGL and 3D APIs.\n    swiftshader (bool):  Chrome: --use-gl=angle / --use-angle=swiftshader-webgl\n    ad_block_on (bool):  Block some types of display ads from loading.\n    host_resolver_rules (str):  Set host-resolver-rules, comma-separated.\n    block_images (bool):  Block images from loading during tests.\n    do_not_track (bool):  Tell websites that you don't want to be tracked.\n    chromium_arg (str):  \"ARG=N,ARG2\" (Set Chromium args, \",\"-separated.)\n    firefox_arg (str):  \"ARG=N,ARG2\" (Set Firefox args, comma-separated.)\n    firefox_pref (str):  SET (Set Firefox PREFERENCE:VALUE set, \",\"-separated)\n    user_data_dir (str):  Set the Chrome user data directory to use.\n    extension_zip (str):  Load a Chrome Extension .zip|.crx, comma-separated.\n    extension_dir (str):  Load a Chrome Extension directory, comma-separated.\n    disable_features (str):  \"F1,F2\" (Disable Chrome features, \",\"-separated.)\n    binary_location (str):  Set path of the Chromium browser binary to use.\n    driver_version (str):  Set the chromedriver or uc_driver version to use.\n    page_load_strategy (str):  Set Chrome PLS to \"normal\", \"eager\", or \"none\".\n    use_wire (bool):  Use selenium-wire's webdriver over selenium webdriver.\n    external_pdf (bool):  Set Chrome \"plugins.always_open_pdf_externally\":True\n    window_position (x,y):  Set the browser's starting window position: \"X,Y\"\n    window_size (w,h):  Set the browser's starting window size: \"Width,Height\"\n    is_mobile (bool):  Use the mobile device emulator while running tests.\n    mobile (bool):  Shortcut / Duplicate of \"is_mobile\".\n    d_width (int):  Set device width\n    d_height (int):  Set device height\n    d_p_r (float):  Set device pixel ratio\n    position (x,y):  Shortcut / Duplicate of \"window_position\".\n    size (w,h):  Shortcut / Duplicate of \"window_size\".\n    uc (bool):  Shortcut / Duplicate of \"undetectable\".\n    undetected (bool):  Shortcut / Duplicate of \"undetectable\".\n    uc_cdp (bool):  Shortcut / Duplicate of \"uc_cdp_events\".\n    uc_sub (bool):  Shortcut / Duplicate of \"uc_subprocess\".\n    locale (str):  Shortcut / Duplicate of \"locale_code\".\n    log_cdp (bool):  Shortcut / Duplicate of \"log_cdp_events\".\n    ad_block (bool):  Shortcut / Duplicate of \"ad_block_on\".\n    server (str):  Shortcut / Duplicate of \"servername\".\n    guest (bool):  Shortcut / Duplicate of \"guest_mode\".\n    wire (bool):  Shortcut / Duplicate of \"use_wire\".\n    pls (str):  Shortcut / Duplicate of \"page_load_strategy\".\n    \"\"\"\n    from seleniumbase import config as sb_config\n    from seleniumbase.config import settings\n    from seleniumbase.core import browser_launcher\n    from seleniumbase.core import detect_b_ver\n    from seleniumbase.fixtures import constants\n    from seleniumbase.fixtures import shared_utils\n\n    sys_argv = sys.argv\n    arg_join = \" \".join(sys_argv)\n    existing_runner = False\n    collect_only = (\"--co\" in sys_argv or \"--collect-only\" in sys_argv)\n    all_scripts = (hasattr(sb_config, \"all_scripts\") and sb_config.all_scripts)\n    if (\n        (hasattr(sb_config, \"is_behave\") and sb_config.is_behave)\n        or (hasattr(sb_config, \"is_pytest\") and sb_config.is_pytest)\n        or (hasattr(sb_config, \"is_nosetest\") and sb_config.is_nosetest)\n    ):\n        existing_runner = True\n    if (\n        existing_runner\n        and not hasattr(sb_config, \"_context_of_runner\")\n    ):\n        if hasattr(sb_config, \"is_pytest\") and sb_config.is_pytest:\n            import pytest\n            msg = \"Skipping `Driver()` script. (Use `python`, not `pytest`)\"\n            if not collect_only and not all_scripts:\n                print(\"\\n  *** %s\" % msg)\n            if collect_only or not all_scripts:\n                pytest.skip(allow_module_level=True)\n        elif hasattr(sb_config, \"is_nosetest\") and sb_config.is_nosetest:\n            raise Exception(\n                \"\\n  A Driver() script was triggered by nosetest collection!\"\n                '\\n  (Prevent that by using: ``if __name__ == \"__main__\":``)'\n            )\n    elif existing_runner:\n        sb_config._context_of_runner = True\n    sb_config._browser_shortcut = None\n    sb_config._cdp_browser = None\n    sb_config._cdp_bin_loc = None\n    browser_changes = 0\n    browser_set = None\n    browser_text = None\n    browser_list = []\n    # Check if binary-location in options\n    bin_loc_in_options = False\n    if (\n        binary_location\n        and len(str(binary_location)) > 5\n        and os.path.exists(str(binary_location))\n    ):\n        bin_loc_in_options = True\n    else:\n        for arg in sys_argv:\n            if arg in [\"--binary-location\", \"--binary_location\", \"--bl\"]:\n                bin_loc_in_options = True\n    if (\n        browser\n        and browser in constants.ChromiumSubs.chromium_subs\n        and not bin_loc_in_options\n    ):\n        bin_loc = detect_b_ver.get_binary_location(browser)\n        if bin_loc and os.path.exists(bin_loc):\n            if browser in bin_loc.lower().split(\"/\")[-1].split(\"\\\\\")[-1]:\n                sb_config._cdp_browser = browser\n                sb_config._cdp_bin_loc = bin_loc\n                binary_location = bin_loc\n                bin_loc_in_options = True\n    # As a shortcut, you can use \"--edge\" instead of \"--browser=edge\", etc,\n    # but you can only specify one default browser for tests. (Default: chrome)\n    if \"--browser=chrome\" in sys_argv or \"--browser chrome\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"chrome\"\n        browser_list.append(\"--browser=chrome\")\n    if \"--browser=edge\" in sys_argv or \"--browser edge\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"edge\"\n        browser_list.append(\"--browser=edge\")\n    if \"--browser=firefox\" in sys_argv or \"--browser firefox\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"firefox\"\n        browser_list.append(\"--browser=firefox\")\n    if \"--browser=safari\" in sys_argv or \"--browser safari\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"safari\"\n        browser_list.append(\"--browser=safari\")\n    if \"--browser=ie\" in sys_argv or \"--browser ie\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"ie\"\n        browser_list.append(\"--browser=ie\")\n    if \"--browser=remote\" in sys_argv or \"--browser remote\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"remote\"\n        browser_list.append(\"--browser=remote\")\n    if \"--browser=opera\" in sys_argv or \"--browser opera\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"opera\"\n                sb_config._browser_shortcut = \"opera\"\n                sb_config._cdp_browser = \"opera\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=opera\")\n    if \"--browser=brave\" in sys_argv or \"--browser brave\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"brave\"\n                sb_config._browser_shortcut = \"brave\"\n                sb_config._cdp_browser = \"brave\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=brave\")\n    if \"--browser=comet\" in sys_argv or \"--browser comet\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"comet\"\n                sb_config._browser_shortcut = \"comet\"\n                sb_config._cdp_browser = \"comet\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=comet\")\n    if \"--browser=atlas\" in sys_argv or \"--browser atlas\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"atlas\"\n                sb_config._browser_shortcut = \"atlas\"\n                sb_config._cdp_browser = \"atlas\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=atlas\")\n    browser_text = browser_set\n    if \"--chrome\" in sys_argv and not browser_set == \"chrome\":\n        browser_changes += 1\n        browser_text = \"chrome\"\n        browser_list.append(\"--chrome\")\n    if \"--edge\" in sys_argv and not browser_set == \"edge\":\n        browser_changes += 1\n        browser_text = \"edge\"\n        browser_list.append(\"--edge\")\n    if \"--firefox\" in sys_argv and not browser_set == \"firefox\":\n        browser_changes += 1\n        browser_text = \"firefox\"\n        browser_list.append(\"--firefox\")\n    if \"--ie\" in sys_argv and not browser_set == \"ie\":\n        browser_changes += 1\n        browser_text = \"ie\"\n        browser_list.append(\"--ie\")\n    if \"--safari\" in sys_argv and not browser_set == \"safari\":\n        browser_changes += 1\n        browser_text = \"safari\"\n        browser_list.append(\"--safari\")\n    if \"--opera\" in sys_argv and not browser_set == \"opera\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"opera\"\n                sb_config._browser_shortcut = \"opera\"\n                sb_config._cdp_browser = \"opera\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--opera\")\n    if \"--brave\" in sys_argv and not browser_set == \"brave\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"brave\"\n                sb_config._browser_shortcut = \"brave\"\n                sb_config._cdp_browser = \"brave\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--brave\")\n    if \"--comet\" in sys_argv and not browser_set == \"comet\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"comet\"\n                sb_config._browser_shortcut = \"comet\"\n                sb_config._cdp_browser = \"comet\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--comet\")\n    if \"--atlas\" in sys_argv and not browser_set == \"atlas\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"atlas\"\n                sb_config._browser_shortcut = \"atlas\"\n                sb_config._cdp_browser = \"atlas\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--atlas\")\n    if browser_changes > 1:\n        message = \"\\n\\n  TOO MANY browser types were entered!\"\n        message += \"\\n  There were %s found:\\n  >  %s\" % (\n            browser_changes,\n            \", \".join(browser_list),\n        )\n        message += \"\\n  ONLY ONE default browser is allowed!\"\n        message += \"\\n  Select a single browser & try again!\\n\"\n        if not browser:\n            raise Exception(message)\n    if browser is None:\n        if browser_text:\n            browser = browser_text\n        else:\n            browser = \"chrome\"\n    else:\n        browser = browser.lower()\n    valid_browsers = constants.ValidBrowsers.valid_browsers\n    if browser not in valid_browsers:\n        raise Exception(\n            \"Browser: {%s} is not a valid browser option. \"\n            \"Valid options = {%s}\" % (browser, valid_browsers)\n        )\n    if sb_config._browser_shortcut:\n        browser = sb_config._browser_shortcut\n    if headless is None:\n        if \"--headless\" in sys_argv:\n            headless = True\n        else:\n            headless = False\n    if headless1 is None:\n        if \"--headless1\" in sys_argv:\n            headless1 = True\n        else:\n            headless1 = False\n    if headless1:\n        headless = True\n    if headless2 is None:\n        if \"--headless2\" in sys_argv:\n            headless2 = True\n        else:\n            headless2 = False\n    if protocol is None:\n        protocol = \"http\"  # For the Selenium Grid only!\n    if server is not None and servername is None:\n        servername = server\n    if servername is None:\n        servername = \"localhost\"  # For the Selenium Grid only!\n    use_grid = False\n    if servername != \"localhost\":\n        # Use Selenium Grid (Use \"127.0.0.1\" for localhost Grid)\n        use_grid = True\n    if port is None:\n        port = \"4444\"  # For the Selenium Grid only!\n    if incognito is None:\n        if \"--incognito\" in sys_argv:\n            incognito = True\n        else:\n            incognito = False\n    if guest is not None and guest_mode is None:\n        guest_mode = guest\n    if guest_mode is None:\n        if \"--guest\" in sys_argv:\n            guest_mode = True\n        else:\n            guest_mode = False\n    if dark_mode is None:\n        if \"--dark\" in sys_argv:\n            dark_mode = True\n        else:\n            dark_mode = False\n    if devtools is None:\n        if \"--devtools\" in sys_argv:\n            devtools = True\n        else:\n            devtools = False\n    if mobile is not None and is_mobile is None:\n        is_mobile = mobile\n    if is_mobile is None:\n        if \"--mobile\" in sys_argv:\n            is_mobile = True\n        else:\n            is_mobile = False\n    test_id = \"direct_driver\"\n    proxy_string = proxy\n    if proxy_string is None and \"--proxy\" in arg_join:\n        if \"--proxy=\" in arg_join:\n            proxy_string = arg_join.split(\"--proxy=\")[1].split(\" \")[0]\n        elif \"--proxy \" in arg_join:\n            proxy_string = arg_join.split(\"--proxy \")[1].split(\" \")[0]\n        if proxy_string:\n            if proxy_string.startswith('\"') and proxy_string.endswith('\"'):\n                proxy_string = proxy_string[1:-1]\n            elif proxy_string.startswith(\"'\") and proxy_string.endswith(\"'\"):\n                proxy_string = proxy_string[1:-1]\n    c_a = chromium_arg\n    if c_a is None and \"--chromium-arg\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--chromium-arg=\"):\n                c_a = arg.split(\"--chromium-arg=\")[1]\n                break\n            elif arg == \"--chromium-arg\" and len(sys_argv) > count + 1:\n                c_a = sys_argv[count + 1]\n                if c_a.startswith(\"-\"):\n                    c_a = None\n                break\n            count += 1\n    chromium_arg = c_a\n    d_f = disable_features\n    if d_f is None and \"--disable-features\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--disable-features=\"):\n                d_f = arg.split(\"--disable-features=\")[1]\n                break\n            elif arg == \"--disable-features\" and len(sys_argv) > count + 1:\n                d_f = sys_argv[count + 1]\n                if d_f.startswith(\"-\"):\n                    d_f = None\n                break\n            count += 1\n    disable_features = d_f\n    if window_position is None and position is not None:\n        window_position = position\n    w_p = window_position\n    if w_p is None and \"--window-position\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--window-position=\"):\n                w_p = arg.split(\"--window-position=\")[1]\n                break\n            elif arg == \"--window-position\" and len(sys_argv) > count + 1:\n                w_p = sys_argv[count + 1]\n                if w_p.startswith(\"-\"):\n                    w_p = None\n                break\n            count += 1\n    window_position = w_p\n    if window_position:\n        if window_position.count(\",\") != 1:\n            message = (\n                '\\n\\n  window_position expects an \"x,y\" string!'\n                '\\n  (Your input was: \"%s\")\\n' % window_position\n            )\n            raise Exception(message)\n        window_position = window_position.replace(\" \", \"\")\n        win_x = None\n        win_y = None\n        try:\n            win_x = int(window_position.split(\",\")[0])\n            win_y = int(window_position.split(\",\")[1])\n        except Exception:\n            message = (\n                '\\n\\n  Expecting integer values for \"x,y\"!'\n                '\\n  (window_position input was: \"%s\")\\n'\n                % window_position\n            )\n            raise Exception(message)\n        settings.WINDOW_START_X = win_x\n        settings.WINDOW_START_Y = win_y\n    if window_size is None and size is not None:\n        window_size = size\n    w_s = window_size\n    if w_s is None and \"--window-size\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--window-size=\"):\n                w_s = arg.split(\"--window-size=\")[1]\n                break\n            elif arg == \"--window-size\" and len(sys_argv) > count + 1:\n                w_s = sys_argv[count + 1]\n                if w_s.startswith(\"-\"):\n                    w_s = None\n                break\n            count += 1\n    window_size = w_s\n    if window_size:\n        if window_size.count(\",\") != 1:\n            message = (\n                '\\n\\n  window_size expects a \"width,height\" string!'\n                '\\n  (Your input was: \"%s\")\\n' % window_size\n            )\n            raise Exception(message)\n        window_size = window_size.replace(\" \", \"\")\n        width = None\n        height = None\n        try:\n            width = int(window_size.split(\",\")[0])\n            height = int(window_size.split(\",\")[1])\n        except Exception:\n            message = (\n                '\\n\\n  Expecting integer values for \"width,height\"!'\n                '\\n  (window_size input was: \"%s\")\\n' % window_size\n            )\n            raise Exception(message)\n        settings.CHROME_START_WIDTH = width\n        settings.CHROME_START_HEIGHT = height\n        settings.HEADLESS_START_WIDTH = width\n        settings.HEADLESS_START_HEIGHT = height\n    if agent is None and \"--agent\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--agent=\"):\n                agent = arg.split(\"--agent=\")[1]\n                break\n            elif arg == \"--agent\" and len(sys_argv) > count + 1:\n                agent = sys_argv[count + 1]\n                if agent.startswith(\"-\"):\n                    agent = None\n                break\n            count += 1\n    user_agent = agent\n    found_bl = None\n    if hasattr(sb_config, \"_cdp_bin_loc\") and sb_config._cdp_bin_loc:\n        binary_location = sb_config._cdp_bin_loc\n    if binary_location is None and \"--binary-location\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--binary-location=\"):\n                found_bl = arg.split(\"--binary-location=\")[1]\n                break\n            elif arg == \"--binary-location\" and len(sys_argv) > count + 1:\n                found_bl = sys_argv[count + 1]\n                if found_bl.startswith(\"-\"):\n                    found_bl = None\n                break\n            count += 1\n        if found_bl:\n            binary_location = found_bl\n    if binary_location is None and \"--bl=\" in arg_join:\n        for arg in sys_argv:\n            if arg.startswith(\"--bl=\"):\n                binary_location = arg.split(\"--bl=\")[1]\n                break\n    if use_chromium and not binary_location:\n        binary_location = \"_chromium_\"\n    elif cft and not binary_location:\n        binary_location = \"cft\"\n    elif chs and not binary_location:\n        binary_location = \"chs\"\n    if \"--use-chromium\" in sys_argv and not binary_location:\n        binary_location = \"_chromium_\"\n    elif \"--cft\" in sys_argv and not binary_location:\n        binary_location = \"cft\"\n    elif \"--chs\" in sys_argv and not binary_location:\n        binary_location = \"chs\"\n    if (\n        binary_location\n        and binary_location.lower() == \"chs\"\n        and browser == \"chrome\"\n    ):\n        headless = True\n        headless1 = False\n        headless2 = False\n    recorder_mode = False\n    if recorder_ext:\n        recorder_mode = True\n    if (\n        \"--recorder\" in sys_argv\n        or \"--record\" in sys_argv\n        or \"--rec\" in sys_argv\n    ):\n        recorder_mode = True\n        recorder_ext = True\n    if (\n        undetectable\n        or undetected\n        or uc\n        or uc_cdp_events\n        or uc_cdp\n        or uc_subprocess\n        or uc_sub\n    ):\n        undetectable = True\n    if undetectable or undetected or uc:\n        uc_subprocess = True  # Use UC as a subprocess by default.\n    elif (\n        \"--undetectable\" in sys_argv\n        or \"--undetected\" in sys_argv\n        or \"--uc\" in sys_argv\n        or \"--uc-cdp-events\" in sys_argv\n        or \"--uc_cdp_events\" in sys_argv\n        or \"--uc-cdp\" in sys_argv\n        or \"--uc-subprocess\" in sys_argv\n        or \"--uc_subprocess\" in sys_argv\n        or \"--uc-sub\" in sys_argv\n    ):\n        undetectable = True\n        if uc_subprocess is None and uc_sub is None:\n            uc_subprocess = True  # Use UC as a subprocess by default.\n    else:\n        undetectable = False\n    if uc_subprocess or uc_sub:\n        uc_subprocess = True\n    elif (\n        \"--uc-subprocess\" in sys_argv\n        or \"--uc_subprocess\" in sys_argv\n        or \"--uc-sub\" in sys_argv\n    ):\n        uc_subprocess = True\n    else:\n        uc_subprocess = False\n    if uc_cdp_events or uc_cdp:\n        undetectable = True\n        uc_cdp_events = True\n    elif (\n        \"--uc-cdp-events\" in sys_argv\n        or \"--uc_cdp_events\" in sys_argv\n        or \"--uc-cdp\" in sys_argv\n        or \"--uc_cdp\" in sys_argv\n    ):\n        undetectable = True\n        uc_cdp_events = True\n    else:\n        uc_cdp_events = False\n    if (\n        undetectable\n        and browser not in [\"chrome\", \"opera\", \"brave\", \"comet\", \"atlas\"]\n    ):\n        message = (\n            '\\n  Undetected-Chromedriver Mode ONLY supports Chromium browsers!'\n            '\\n  (\"uc=True\" / \"undetectable=True\" / \"--uc\")'\n            '\\n  (Your browser choice was: \"%s\".)'\n            '\\n  (Will use \"%s\" without UC Mode.)\\n' % (browser, browser)\n        )\n        print(message)\n    if headed is None:\n        # Override the default headless mode on Linux if set.\n        if \"--gui\" in sys_argv or \"--headed\" in sys_argv:\n            headed = True\n        else:\n            headed = False\n    if (\n        shared_utils.is_linux()\n        and not headed\n        and not headless\n        and not headless2\n        and (\n            not undetectable\n            or \"DISPLAY\" not in os.environ.keys()\n            or not os.environ[\"DISPLAY\"]\n        )\n    ):\n        headless = True\n    if recorder_mode and headless:\n        headless = False\n        headless1 = False\n        headless2 = True\n    if headless2 and browser == \"firefox\":\n        headless2 = False  # Only for Chromium browsers\n        headless = True  # Firefox has regular headless\n    elif browser not in [\n        \"chrome\", \"edge\", \"opera\", \"brave\", \"comet\", \"atlas\"\n    ]:\n        headless2 = False  # Only for Chromium browsers\n    if disable_csp is None:\n        if (\n            \"--disable-csp\" in sys_argv\n            or \"--no-csp\" in sys_argv\n            or \"--dcsp\" in sys_argv\n        ):\n            disable_csp = True\n        else:\n            disable_csp = False\n    if (\n        (enable_ws is None and disable_ws is None)\n        and (\n            \"--disable-web-security\" in sys_argv\n            or \"--disable-ws\" in sys_argv\n            or \"--dws\" in sys_argv\n        )\n    ):\n        enable_ws = False\n    elif (\n        (enable_ws is None and disable_ws is None)\n        or (disable_ws is not None and not disable_ws)\n        or (enable_ws is not None and enable_ws)\n    ):\n        enable_ws = True\n    else:\n        enable_ws = False\n    if log_cdp_events is None and log_cdp is None:\n        if (\n            \"--log-cdp-events\" in sys_argv\n            or \"--log_cdp_events\" in sys_argv\n            or \"--log-cdp\" in sys_argv\n            or \"--log_cdp\" in sys_argv\n        ):\n            log_cdp_events = True\n        else:\n            log_cdp_events = False\n    elif log_cdp_events or log_cdp:\n        log_cdp_events = True\n    else:\n        log_cdp_events = False\n    if use_auto_ext is None:\n        if \"--use-auto-ext\" in sys_argv:\n            use_auto_ext = True\n        else:\n            use_auto_ext = False\n    if disable_cookies is None:\n        if \"--disable-cookies\" in sys_argv:\n            disable_cookies = True\n        else:\n            disable_cookies = False\n    if disable_js is None:\n        if \"--disable-js\" in sys_argv:\n            disable_js = True\n        else:\n            disable_js = False\n    if pls is not None and page_load_strategy is None:\n        page_load_strategy = pls\n    if not page_load_strategy and \"--pls=\" in arg_join:\n        if \"--pls=none\" in sys_argv or '--pls=\"none\"' in sys_argv:\n            page_load_strategy = \"none\"\n        elif \"--pls=eager\" in sys_argv or '--pls=\"eager\"' in sys_argv:\n            page_load_strategy = \"eager\"\n        elif \"--pls=normal\" in sys_argv or '--pls=\"normal\"' in sys_argv:\n            page_load_strategy = \"normal\"\n    if page_load_strategy is not None:\n        if page_load_strategy.lower() not in [\"normal\", \"eager\", \"none\"]:\n            raise Exception(\n                'page_load_strategy must be \"normal\", \"eager\", or \"none\"!'\n            )\n        page_load_strategy = page_load_strategy.lower()\n    elif \"--pls=normal\" in sys_argv or '--pls=\"normal\"' in sys_argv:\n        page_load_strategy = \"normal\"\n    elif \"--pls=eager\" in sys_argv or '--pls=\"eager\"' in sys_argv:\n        page_load_strategy = \"eager\"\n    elif \"--pls=none\" in sys_argv or '--pls=\"none\"' in sys_argv:\n        page_load_strategy = \"none\"\n    if block_images is None:\n        if \"--block-images\" in sys_argv or \"--block_images\" in sys_argv:\n            block_images = True\n        else:\n            block_images = False\n    if do_not_track is None:\n        if \"--do-not-track\" in sys_argv or \"--do_not_track\" in sys_argv:\n            do_not_track = True\n        else:\n            do_not_track = False\n    if use_wire is None and wire is None:\n        if \"--wire\" in sys_argv:\n            use_wire = True\n        else:\n            use_wire = False\n    elif use_wire or wire:\n        use_wire = True\n    else:\n        use_wire = False\n    if external_pdf is None:\n        if \"--external-pdf\" in sys_argv or \"--external_pdf\" in sys_argv:\n            external_pdf = True\n        else:\n            external_pdf = False\n    if remote_debug is None:\n        if \"--remote-debug\" in sys_argv or \"--remote_debug\" in sys_argv:\n            remote_debug = True\n        else:\n            remote_debug = False\n    if enable_3d_apis is None:\n        if \"--enable-3d-apis\" in sys_argv or \"--enable_3d_apis\" in sys_argv:\n            enable_3d_apis = True\n        else:\n            enable_3d_apis = False\n    if swiftshader is None:\n        if \"--swiftshader\" in sys_argv:\n            swiftshader = True\n        else:\n            swiftshader = False\n    if locale is not None and locale_code is None:\n        locale_code = locale\n    if locale_code is None:\n        if '--locale=\"' in arg_join:\n            locale_code = (\n                arg_join.split('--locale=\"')[1].split('\"')[0]\n            )\n        elif '--locale=' in arg_join:\n            locale_code = (\n                arg_join.split('--locale=')[1].split(' ')[0]\n            )\n        elif '--locale-code=\"' in arg_join:\n            locale_code = (\n                arg_join.split('--locale-code=\"')[1].split('\"')[0]\n            )\n        elif '--locale-code=' in arg_join:\n            locale_code = (\n                arg_join.split('--locale-code=')[1].split(' ')[0]\n            )\n    if ad_block is not None and ad_block_on is None:\n        ad_block_on = ad_block\n    if ad_block_on is None:\n        if \"--ad-block\" in sys_argv or \"--ad_block\" in sys_argv:\n            ad_block_on = True\n        else:\n            ad_block_on = False\n    if host_resolver_rules is None:\n        if '--host-resolver-rules=\"' in arg_join:\n            host_resolver_rules = (\n                arg_join.split('--host-resolver-rules=\"')[1].split('\"')[0]\n            )\n        elif '--host_resolver_rules=\"' in arg_join:\n            host_resolver_rules = (\n                arg_join.split(\"--host_resolver_rules=\")[1].split('\"')[0]\n            )\n    if driver_version is None and \"--driver-version\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--driver-version=\"):\n                driver_version = arg.split(\"--driver-version=\")[1]\n                break\n            elif arg == \"--driver-version\" and len(sys_argv) > count + 1:\n                driver_version = sys_argv[count + 1]\n                if driver_version.startswith(\"-\"):\n                    driver_version = None\n                break\n            count += 1\n    if driver_version is None and \"--driver_version\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--driver_version=\"):\n                driver_version = arg.split(\"--driver_version=\")[1]\n                break\n            elif arg == \"--driver_version\" and len(sys_argv) > count + 1:\n                driver_version = sys_argv[count + 1]\n                if driver_version.startswith(\"-\"):\n                    driver_version = None\n                break\n            count += 1\n    if browser in constants.ChromiumSubs.chromium_subs:\n        if not binary_location:\n            browser = \"chrome\"  # Still uses chromedriver\n            sb_config._browser_shortcut = browser\n    browser_name = browser\n\n    # Launch a web browser\n    driver = browser_launcher.get_driver(\n        browser_name=browser_name,\n        headless=headless,\n        locale_code=locale_code,\n        use_grid=use_grid,\n        protocol=protocol,\n        servername=servername,\n        port=port,\n        proxy_string=proxy_string,\n        proxy_bypass_list=proxy_bypass_list,\n        proxy_pac_url=proxy_pac_url,\n        multi_proxy=multi_proxy,\n        user_agent=user_agent,\n        cap_file=cap_file,\n        cap_string=cap_string,\n        recorder_ext=recorder_ext,\n        disable_cookies=disable_cookies,\n        disable_js=disable_js,\n        disable_csp=disable_csp,\n        enable_ws=enable_ws,\n        enable_sync=enable_sync,\n        use_auto_ext=use_auto_ext,\n        undetectable=undetectable,\n        uc_cdp_events=uc_cdp_events,\n        uc_subprocess=uc_subprocess,\n        log_cdp_events=log_cdp_events,\n        no_sandbox=no_sandbox,\n        disable_gpu=disable_gpu,\n        headless1=headless1,\n        headless2=headless2,\n        incognito=incognito,\n        guest_mode=guest_mode,\n        dark_mode=dark_mode,\n        devtools=devtools,\n        remote_debug=remote_debug,\n        enable_3d_apis=enable_3d_apis,\n        swiftshader=swiftshader,\n        ad_block_on=ad_block_on,\n        host_resolver_rules=host_resolver_rules,\n        block_images=block_images,\n        do_not_track=do_not_track,\n        chromium_arg=chromium_arg,\n        firefox_arg=firefox_arg,\n        firefox_pref=firefox_pref,\n        user_data_dir=user_data_dir,\n        extension_zip=extension_zip,\n        extension_dir=extension_dir,\n        disable_features=disable_features,\n        binary_location=binary_location,\n        driver_version=driver_version,\n        page_load_strategy=page_load_strategy,\n        use_wire=use_wire,\n        external_pdf=external_pdf,\n        test_id=test_id,\n        mobile_emulator=is_mobile,\n        device_width=d_width,\n        device_height=d_height,\n        device_pixel_ratio=d_p_r,\n        browser=browser_name,\n    )\n    return driver\n"
  },
  {
    "path": "seleniumbase/plugins/page_source.py",
    "content": "\"\"\"PageSource Plugin for SeleniumBase tests that run with pynose / nosetests\"\"\"\nimport os\nfrom nose.plugins import Plugin\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import log_helper\n\n\nclass PageSource(Plugin):\n    \"\"\"Capture the page source after a test fails.\"\"\"\n    name = \"page_source\"  # Usage: --with-page_source\n    logfile_name = settings.PAGE_SOURCE_NAME\n\n    def options(self, parser, env):\n        super().options(parser, env=env)\n\n    def configure(self, options, conf):\n        super().configure(options, conf)\n        if not self.enabled:\n            return\n        self.options = options\n\n    def addError(self, test, err, capt=None):\n        try:\n            page_source = test.driver.page_source\n        except Exception:\n            return\n        test_logpath = self.options.log_path + \"/\" + test.id()\n        if not os.path.exists(test_logpath):\n            os.makedirs(test_logpath)\n        html_file_name = os.path.join(test_logpath, self.logfile_name)\n        html_file = open(html_file_name, mode=\"w+\", encoding=\"utf-8\")\n        rendered_source = log_helper.get_html_source_with_base_href(\n            test.driver, page_source\n        )\n        html_file.write(rendered_source)\n        html_file.close()\n\n    def addFailure(self, test, err, capt=None, tbinfo=None):\n        try:\n            page_source = test.driver.page_source\n        except Exception:\n            return\n        test_logpath = self.options.log_path + \"/\" + test.id()\n        if not os.path.exists(test_logpath):\n            os.makedirs(test_logpath)\n        html_file_name = os.path.join(test_logpath, self.logfile_name)\n        html_file = open(html_file_name, mode=\"w+\", encoding=\"utf-8\")\n        rendered_source = log_helper.get_html_source_with_base_href(\n            test.driver, page_source\n        )\n        html_file.write(rendered_source)\n        html_file.close()\n"
  },
  {
    "path": "seleniumbase/plugins/pytest_plugin.py",
    "content": "\"\"\"This is the pytest configuration file for setting test options.\"\"\"\nimport colorama\nimport os\nimport pytest\nimport sys\nimport time\nfrom contextlib import suppress\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import detect_b_ver\nfrom seleniumbase.core import log_helper\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import shared_utils\n\nis_windows = shared_utils.is_windows()\npython3_11_or_newer = False\nif sys.version_info >= (3, 11):\n    python3_11_or_newer = True\npy311_patch2 = constants.PatchPy311.PATCH\nsys_argv = sys.argv\nfull_time = None\npytest_plugins = [\"pytester\"]  # Adds the \"testdir\" fixture\n\n\ndef pytest_addoption(parser):\n    \"\"\"This plugin adds the following command-line options to pytest:\n    --browser=BROWSER  (The web browser to use. Default: \"chrome\".)\n    --chrome  (Shortcut for \"--browser=chrome\". Default.)\n    --edge  (Shortcut for \"--browser=edge\".)\n    --firefox  (Shortcut for \"--browser=firefox\".)\n    --safari  (Shortcut for \"--browser=safari\".)\n    --opera  (Shortcut for \"--browser=opera\".)\n    --brave  (Shortcut for \"--browser=brave\".)\n    --comet  (Shortcut for \"--browser=comet\".)\n    --atlas  (Shortcut for \"--browser=atlas\".)\n    --use-chromium  (Shortcut for using base `Chromium`)\n    --cft  (Shortcut for using `Chrome for Testing`)\n    --chs  (Shortcut for using `Chrome-Headless-Shell`)\n    --settings-file=FILE  (Override default SeleniumBase settings.)\n    --env=ENV  (Set the test env. Access with \"self.env\" in tests.)\n    --account=STR  (Set account. Access with \"self.account\" in tests.)\n    --data=STRING  (Extra test data. Access with \"self.data\" in tests.)\n    --var1=STRING  (Extra test data. Access with \"self.var1\" in tests.)\n    --var2=STRING  (Extra test data. Access with \"self.var2\" in tests.)\n    --var3=STRING  (Extra test data. Access with \"self.var3\" in tests.)\n    --variables=DICT  (Extra test data. Access with \"self.variables\".)\n    --user-data-dir=DIR  (Set the Chrome user data directory to use.)\n    --protocol=PROTOCOL  (The Selenium Grid protocol: http|https.)\n    --server=SERVER  (The Selenium Grid server/IP used for tests.)\n    --port=PORT  (The Selenium Grid port used by the test server.)\n    --cap-file=FILE  (The web browser's desired capabilities to use.)\n    --cap-string=STRING  (The web browser's desired capabilities to use.)\n    --proxy=SERVER:PORT  (Connect to a proxy server:port as tests are running)\n    --proxy=USERNAME:PASSWORD@SERVER:PORT  (Use an authenticated proxy server)\n    --proxy-bypass-list=STRING (\";\"-separated hosts to bypass, Eg \"*.foo.com\")\n    --proxy-pac-url=URL  (Connect to a proxy server using a PAC_URL.pac file.)\n    --proxy-pac-url=USERNAME:PASSWORD@URL  (Authenticated proxy with PAC URL.)\n    --proxy-driver  (If a driver download is needed, will use: --proxy=PROXY.)\n    --multi-proxy  (Allow multiple authenticated proxies when multi-threaded.)\n    --agent=STRING  (Modify the web browser's User-Agent string.)\n    --mobile  (Use the mobile device emulator while running tests.)\n    --metrics=STRING  (Set mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\".)\n    --chromium-arg=\"ARG=N,ARG2\" (Set Chromium args, \",\"-separated, no spaces.)\n    --firefox-arg=\"ARG=N,ARG2\" (Set Firefox args, comma-separated, no spaces.)\n    --firefox-pref=SET  (Set a Firefox preference:value set, comma-separated.)\n    --extension-zip=ZIP  (Load a Chrome Extension .zip|.crx, comma-separated.)\n    --extension-dir=DIR  (Load a Chrome Extension directory, comma-separated.)\n    --disable-features=\"F1,F2\" (Disable features, comma-separated, no spaces.)\n    --binary-location=PATH  (Set path of the Chromium browser binary to use.)\n    --driver-version=VER  (Set the chromedriver or uc_driver version to use.)\n    --sjw  (Skip JS Waits for readyState to be \"complete\" or Angular to load.)\n    --wfa  (Wait for AngularJS to be done loading after specific web actions.)\n    --pls=PLS  (Set pageLoadStrategy on Chrome: \"normal\", \"eager\", or \"none\".)\n    --headless  (The default headless mode. Linux uses this mode by default.)\n    --headless1  (Use Chrome's old headless mode. Fast, but has limitations.)\n    --headless2  (Use Chrome's new headless mode, which supports extensions.)\n    --headed  (Run tests in headed/GUI mode on Linux OS, where not default.)\n    --xvfb  (Run tests using the Xvfb virtual display server on Linux OS.)\n    --xvfb-metrics=STRING  (Set Xvfb display size on Linux: \"Width,Height\".)\n    --locale=LOCALE_CODE  (Set the Language Locale Code for the web browser.)\n    --interval=SECONDS  (The autoplay interval for presentations & tour steps)\n    --start-page=URL  (The starting URL for the web browser when tests begin.)\n    --archive-logs  (Archive existing log files instead of deleting them.)\n    --archive-downloads  (Archive old downloads instead of deleting them.)\n    --time-limit=SECONDS  (Safely fail any test that exceeds the time limit.)\n    --slow  (Slow down the automation. Faster than using Demo Mode.)\n    --demo  (Slow down and visually see test actions as they occur.)\n    --demo-sleep=SECONDS  (Set the wait time after Slow & Demo Mode actions.)\n    --highlights=NUM  (Number of highlight animations for Demo Mode actions.)\n    --message-duration=SECONDS  (The time length for Messenger alerts.)\n    --check-js  (Check for JavaScript errors after page loads.)\n    --ad-block  (Block some types of display ads from loading.)\n    --host-resolver-rules=RULES  (Set host-resolver-rules, comma-separated.)\n    --block-images  (Block images from loading during tests.)\n    --do-not-track  (Indicate to websites that you don't want to be tracked.)\n    --verify-delay=SECONDS  (The delay before MasterQA verification checks.)\n    --ee / --esc-end  (Lets the user end the current test via the ESC key.)\n    --recorder  (Enables the Recorder for turning browser actions into code.)\n    --rec-behave  (Same as Recorder Mode, but also generates behave-gherkin.)\n    --rec-sleep  (If the Recorder is enabled, also records self.sleep calls.)\n    --rec-print  (If the Recorder is enabled, prints output after tests end.)\n    --disable-cookies  (Disable Cookies on websites. Pages might break!)\n    --disable-js  (Disable JavaScript on websites. Pages might break!)\n    --disable-csp  (Disable the Content Security Policy of websites.)\n    --disable-ws  (Disable Web Security on Chromium-based browsers.)\n    --enable-ws  (Enable Web Security on Chromium-based browsers.)\n    --enable-sync  (Enable \"Chrome Sync\" on websites.)\n    --uc | --undetected  (Use undetected-chromedriver to evade bot-detection.)\n    --uc-cdp-events  (Capture CDP events when running in \"--undetected\" mode.)\n    --log-cdp  (\"goog:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"})\n    --remote-debug  (Sync to Chrome Remote Debugger chrome://inspect/#devices)\n    --ftrace | --final-trace  (Debug Mode after each test. Don't use with CI!)\n    --dashboard  (Enable the SeleniumBase Dashboard. Saved at: dashboard.html)\n    --dash-title=STRING  (Set the title shown for the generated dashboard.)\n    --enable-3d-apis  (Enables WebGL and 3D APIs.)\n    --swiftshader  (Chrome \"--use-gl=angle\" / \"--use-angle=swiftshader-webgl\")\n    --incognito  (Enable Chrome's Incognito mode.)\n    --guest  (Enable Chrome's Guest mode.)\n    --dark  (Enable Chrome's Dark mode.)\n    --devtools  (Open Chrome's DevTools when the browser opens.)\n    --rs | --reuse-session  (Reuse browser session for all tests.)\n    --rcs | --reuse-class-session  (Reuse session for tests in class.)\n    --crumbs  (Delete all cookies between tests reusing a session.)\n    --disable-beforeunload  (Disable the \"beforeunload\" event on Chrome.)\n    --window-position=X,Y  (Set the browser's starting window position.)\n    --window-size=WIDTH,HEIGHT  (Set the browser's starting window size.)\n    --maximize  (Start tests with the browser window maximized.)\n    --screenshot  (Save a screenshot at the end of each test.)\n    --no-screenshot  (No screenshots saved unless tests directly ask it.)\n    --visual-baseline  (Set the visual baseline for Visual/Layout tests.)\n    --wire  (Use selenium-wire's webdriver for replacing selenium webdriver.)\n    --external-pdf  (Set Chromium \"plugins.always_open_pdf_externally\":True.)\n    --timeout-multiplier=MULTIPLIER  (Multiplies the default timeout values.)\n    --list-fail-page  (After each failing test, list the URL of the failure.)\n    \"\"\"\n    c1 = \"\"\n    c2 = \"\"\n    c3 = \"\"\n    cr = \"\"\n    if \"linux\" not in sys.platform:\n        # This will be seen when typing \"pytest --help\" on the command line.\n        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n        c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n        c3 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX\n        cr = colorama.Style.RESET_ALL\n    s_str = \"SeleniumBase\"\n    s_str = s_str.replace(\"SeleniumBase\", c1 + \"Selenium\" + c2 + \"Base\" + cr)\n    s_str = s_str + cr + \" \" + c3 + \"command-line options for pytest\" + cr\n    parser = parser.getgroup(\"SeleniumBase\", s_str)\n    parser.addoption(\n        \"--browser\",\n        action=\"store\",\n        dest=\"browser\",\n        type=str.lower,\n        choices=constants.ValidBrowsers.valid_browsers,\n        default=constants.Browser.GOOGLE_CHROME,\n        help=\"\"\"Specifies the web browser to use. Default: Chrome.\n                Examples: (--browser=edge OR --browser=firefox)\"\"\",\n    )\n    parser.addoption(\n        \"--chrome\",\n        action=\"store_true\",\n        dest=\"use_chrome\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=chrome (Default)\"\"\",\n    )\n    parser.addoption(\n        \"--edge\",\n        action=\"store_true\",\n        dest=\"use_edge\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=edge\"\"\",\n    )\n    parser.addoption(\n        \"--firefox\",\n        action=\"store_true\",\n        dest=\"use_firefox\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=firefox\"\"\",\n    )\n    parser.addoption(\n        \"--ie\",\n        action=\"store_true\",\n        dest=\"use_ie\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=ie\"\"\",\n    )\n    parser.addoption(\n        \"--safari\",\n        action=\"store_true\",\n        dest=\"use_safari\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=safari\"\"\",\n    )\n    parser.addoption(\n        \"--opera\",\n        action=\"store_true\",\n        dest=\"use_opera\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=opera\"\"\",\n    )\n    parser.addoption(\n        \"--brave\",\n        action=\"store_true\",\n        dest=\"use_brave\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=brave\"\"\",\n    )\n    parser.addoption(\n        \"--comet\",\n        action=\"store_true\",\n        dest=\"use_comet\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=comet\"\"\",\n    )\n    parser.addoption(\n        \"--atlas\",\n        action=\"store_true\",\n        dest=\"use_atlas\",\n        default=False,\n        help=\"\"\"Shortcut for --browser=atlas\"\"\",\n    )\n    parser.addoption(\n        \"--use-chromium\",\n        action=\"store_true\",\n        dest=\"use_chromium\",\n        default=False,\n        help=\"\"\"Shortcut for using base `Chromium`\"\"\",\n    )\n    parser.addoption(\n        \"--cft\",\n        action=\"store_true\",\n        dest=\"use_cft\",\n        default=False,\n        help=\"\"\"Shortcut for using `Chrome for Testing`\"\"\",\n    )\n    parser.addoption(\n        \"--chs\",\n        action=\"store_true\",\n        dest=\"use_chs\",\n        default=False,\n        help=\"\"\"Shortcut for using `Chrome-Headless-Shell`\"\"\",\n    )\n    parser.addoption(\n        \"--with-selenium\",\n        action=\"store_true\",\n        dest=\"with_selenium\",\n        default=True,\n        help=\"\"\"(DEPRECATED) Start tests with an open web browser.\n                (This is ALWAYS True now when importing BaseCase)\"\"\",\n    )\n    parser.addoption(\n        \"--env\",\n        action=\"store\",\n        dest=\"environment\",\n        type=str.lower,\n        choices=(\n            constants.Environment.QA,\n            constants.Environment.RC,\n            constants.Environment.STAGING,\n            constants.Environment.DEVELOP,\n            constants.Environment.PRODUCTION,\n            constants.Environment.PERFORMANCE,\n            constants.Environment.REPLICA,\n            constants.Environment.FEDRAMP,\n            constants.Environment.OFFLINE,\n            constants.Environment.ONLINE,\n            constants.Environment.MASTER,\n            constants.Environment.REMOTE,\n            constants.Environment.LEGACY,\n            constants.Environment.LOCAL,\n            constants.Environment.ALPHA,\n            constants.Environment.BETA,\n            constants.Environment.DEMO,\n            constants.Environment.GDPR,\n            constants.Environment.MAIN,\n            constants.Environment.TEST,\n            constants.Environment.GOV,\n            constants.Environment.NEW,\n            constants.Environment.OLD,\n            constants.Environment.UAT,\n        ),\n        default=constants.Environment.TEST,\n        help=\"\"\"This option sets a test env from a list of choices.\n                Access using \"self.env\" or \"self.environment\".\"\"\",\n    )\n    parser.addoption(\n        \"--account\",\n        dest=\"account\",\n        default=None,\n        help=\"\"\"This option sets a test account string.\n                In tests, use \"self.account\" to get the value.\"\"\",\n    )\n    parser.addoption(\n        \"--data\",\n        dest=\"data\",\n        default=None,\n        help=\"Extra data to pass to tests from the command line.\",\n    )\n    parser.addoption(\n        \"--var1\",\n        dest=\"var1\",\n        default=None,\n        help=\"Extra data to pass to tests from the command line.\",\n    )\n    parser.addoption(\n        \"--var2\",\n        dest=\"var2\",\n        default=None,\n        help=\"Extra data to pass to tests from the command line.\",\n    )\n    parser.addoption(\n        \"--var3\",\n        dest=\"var3\",\n        default=None,\n        help=\"Extra data to pass to tests from the command line.\",\n    )\n    parser.addoption(\n        \"--variables\",\n        dest=\"variables\",\n        default=None,\n        help=\"\"\"A var dict to pass to tests from the command line.\n                Example usage:\n                ----------------------------------------------\n                Option: --variables='{\"special\":123}'\n                Access: self.variables[\"special\"]  # (123)\n                ----------------------------------------------\n                Option: --variables='{\"color\":\"red\",\"num\":42}'\n                Access: self.variables[\"color\"]  # (\"red\")\n                Access: self.variables[\"num\"]  # (42)\n                ----------------------------------------------\"\"\",\n    )\n    parser.addoption(\n        \"--cap_file\",\n        \"--cap-file\",\n        dest=\"cap_file\",\n        default=None,\n        help=\"\"\"The file that stores browser desired capabilities\n                for BrowserStack, Sauce Labs, or other grids.\"\"\",\n    )\n    parser.addoption(\n        \"--cap_string\",\n        \"--cap-string\",\n        dest=\"cap_string\",\n        default=None,\n        help=\"\"\"The string that stores browser desired capabilities\n                for BrowserStack, Sauce Labs, or other grids.\n                Enclose cap-string in single quotes.\n                Enclose parameter keys in double quotes.\n                Example: --cap-string='{\"name\":\"test1\",\"v\":\"42\"}'\"\"\",\n    )\n    parser.addoption(\n        \"--settings_file\",\n        \"--settings-file\",\n        \"--settings\",\n        action=\"store\",\n        dest=\"settings_file\",\n        default=None,\n        help=\"\"\"The file that stores key/value pairs for\n                overriding values in the\n                seleniumbase/config/settings.py file.\"\"\",\n    )\n    parser.addoption(\n        \"--user_data_dir\",\n        \"--user-data-dir\",\n        dest=\"user_data_dir\",\n        default=None,\n        help=\"\"\"The Chrome User Data Directory to use. (Profile)\n                If the directory doesn't exist, it'll be created.\"\"\",\n    )\n    parser.addoption(\n        \"--with-testing_base\",\n        \"--with-testing-base\",\n        action=\"store_true\",\n        dest=\"with_testing_base\",\n        default=True,\n        help=\"\"\"(DEPRECATED) - This option is always enabled now.\n                Use for saving logs & screenshots when tests fail.\n                The following options are now active by default\n                with --with-testing_base (which is always on now):\n                --with-screen_shots ,\n                --with-basic_test_info ,\n                --with-page_source\n            \"\"\",\n    )\n    parser.addoption(\n        \"--log_path\",\n        \"--log-path\",\n        dest=\"log_path\",\n        default=constants.Logs.LATEST + \"/\",\n        help=\"\"\"(DEPRECATED) - This value is NOT EDITABLE anymore.\n                Log files are saved to the \"latest_logs/\" folder.\"\"\",\n    )\n    parser.addoption(\n        \"--archive_logs\",\n        \"--archive-logs\",\n        action=\"store_true\",\n        dest=\"archive_logs\",\n        default=False,\n        help=\"Archive old log files instead of deleting them.\",\n    )\n    parser.addoption(\n        \"--archive_downloads\",\n        \"--archive-downloads\",\n        action=\"store_true\",\n        dest=\"archive_downloads\",\n        default=False,\n        help=\"Archive old downloads instead of deleting them.\",\n    )\n    parser.addoption(\n        \"--sjw\",\n        \"--skip_js_waits\",\n        \"--skip-js-waits\",\n        action=\"store_true\",\n        dest=\"skip_js_waits\",\n        default=False,\n        help=\"\"\"Skip all calls to wait_for_ready_state_complete()\n                and wait_for_angularjs(), which are part of many\n                SeleniumBase methods for improving reliability.\"\"\",\n    )\n    parser.addoption(\n        \"--wfa\",\n        \"--wait_for_angularjs\",\n        \"--wait-for-angularjs\",\n        action=\"store_true\",\n        dest=\"wait_for_angularjs\",\n        default=False,\n        help=\"\"\"Add waiting for AngularJS. (The default setting\n                was changed to no longer wait for AngularJS to\n                finish loading as an extra JavaScript call.)\"\"\",\n    )\n    parser.addoption(\n        \"--with-db_reporting\",\n        \"--with-db-reporting\",\n        action=\"store_true\",\n        dest=\"with_db_reporting\",\n        default=False,\n        help=\"Use to record test data in the MySQL database.\",\n    )\n    parser.addoption(\n        \"--database_env\",\n        \"--database-env\",\n        action=\"store\",\n        dest=\"database_env\",\n        choices=(\n            constants.Environment.QA,\n            constants.Environment.RC,\n            constants.Environment.STAGING,\n            constants.Environment.DEVELOP,\n            constants.Environment.PRODUCTION,\n            constants.Environment.PERFORMANCE,\n            constants.Environment.REPLICA,\n            constants.Environment.FEDRAMP,\n            constants.Environment.OFFLINE,\n            constants.Environment.ONLINE,\n            constants.Environment.MASTER,\n            constants.Environment.REMOTE,\n            constants.Environment.LEGACY,\n            constants.Environment.LOCAL,\n            constants.Environment.ALPHA,\n            constants.Environment.BETA,\n            constants.Environment.DEMO,\n            constants.Environment.GDPR,\n            constants.Environment.MAIN,\n            constants.Environment.TEST,\n            constants.Environment.GOV,\n            constants.Environment.NEW,\n            constants.Environment.OLD,\n            constants.Environment.UAT,\n        ),\n        default=constants.Environment.TEST,\n        help=\"The database environment to run the tests in.\",\n    )\n    parser.addoption(\n        \"--with-s3_logging\",\n        \"--with-s3-logging\",\n        action=\"store_true\",\n        dest=\"with_s3_logging\",\n        default=False,\n        help=\"Use to save test log files in Amazon S3.\",\n    )\n    parser.addoption(\n        \"--with-screen_shots\",\n        \"--with-screen-shots\",\n        action=\"store_true\",\n        dest=\"with_screen_shots\",\n        default=False,\n        help=\"\"\"(DEPRECATED) - Screenshots are always saved now.\n                This option saves screenshots during test failures.\n                Screenshots are saved in the \"latest_logs/\" folder.\n                (Automatically on when using --with-testing_base)\"\"\",\n    )\n    parser.addoption(\n        \"--with-basic_test_info\",\n        \"--with-basic-test-info\",\n        action=\"store_true\",\n        dest=\"with_basic_test_info\",\n        default=False,\n        help=\"\"\"(DEPRECATED) - Info files are always saved now.\n                This option saves basic test info on test failures.\n                These files are saved in the \"latest_logs/\" folder.\n                (Automatically on when using --with-testing_base)\"\"\",\n    )\n    parser.addoption(\n        \"--with-page_source\",\n        \"--with-page-source\",\n        action=\"store_true\",\n        dest=\"with_page_source\",\n        default=False,\n        help=\"\"\"(DEPRECATED) - Page source is saved by default.\n                This option saves page source files on test failures.\n                (Automatically on when using --with-testing_base)\"\"\",\n    )\n    parser.addoption(\n        \"--protocol\",\n        action=\"store\",\n        dest=\"protocol\",\n        choices=(\n            constants.Protocol.HTTP,\n            constants.Protocol.HTTPS,\n        ),\n        default=constants.Protocol.HTTP,\n        help=\"\"\"Designates the Selenium Grid protocol to use.\n                Default: http.\"\"\",\n    )\n    parser.addoption(\n        \"--server\",\n        action=\"store\",\n        dest=\"servername\",\n        default=\"localhost\",\n        help=\"\"\"Designates the Selenium Grid server to use.\n                Use \"127.0.0.1\" to connect to a localhost Grid.\n                If unset or set to \"localhost\", Grid isn't used.\n                Default: \"localhost\".\"\"\",\n    )\n    parser.addoption(\n        \"--port\",\n        action=\"store\",\n        dest=\"port\",\n        default=\"4444\",\n        help=\"\"\"Designates the Selenium Grid port to use.\n                Default: 4444. (If 443, protocol becomes \"https\")\"\"\",\n    )\n    parser.addoption(\n        \"--proxy\",\n        \"--proxy-server\",\n        \"--proxy-string\",\n        action=\"store\",\n        dest=\"proxy_string\",\n        default=None,\n        help=\"\"\"Designates the proxy server:port to use.\n                Format: servername:port.  OR\n                        username:password@servername:port  OR\n                        A dict key from proxy_list.PROXY_LIST\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--proxy-bypass-list\",\n        \"--proxy_bypass_list\",\n        action=\"store\",\n        dest=\"proxy_bypass_list\",\n        default=None,\n        help=\"\"\"Designates the hosts, domains, and/or IP addresses\n                to bypass when using a proxy server with \"--proxy\".\n                Format: A \";\"-separated string.\n                Example usage:\n                    pytest\n                        --proxy=\"username:password@servername:port\"\n                        --proxy-bypass-list=\"*.foo.com;github.com\"\n                    pytest\n                        --proxy=\"servername:port\"\n                        --proxy-bypass-list=\"127.0.0.1:8080\"\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--proxy-pac-url\",\n        \"--pac-url\",\n        action=\"store\",\n        dest=\"proxy_pac_url\",\n        default=None,\n        help=\"\"\"Designates the proxy PAC URL to use.\n                Format: A URL string  OR\n                        A username:password@URL string\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--proxy-driver\",\n        \"--proxy_driver\",\n        action=\"store_true\",\n        dest=\"proxy_driver\",\n        default=False,\n        help=\"\"\"If a driver download is needed for tests,\n                uses proxy settings set via --proxy=PROXY.\"\"\",\n    )\n    parser.addoption(\n        \"--multi-proxy\",\n        \"--multi_proxy\",\n        action=\"store_true\",\n        dest=\"multi_proxy\",\n        default=False,\n        help=\"\"\"If you need to run multi-threaded tests with\n                multiple proxies that require authentication,\n                set this to allow multiple configurations.\"\"\",\n    )\n    parser.addoption(\n        \"--agent\",\n        \"--user-agent\",\n        \"--user_agent\",\n        action=\"store\",\n        dest=\"user_agent\",\n        default=None,\n        help=\"\"\"Designates the User-Agent for the browser to use.\n                Format: A string.\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--mobile\",\n        \"--mobile-emulator\",\n        \"--mobile_emulator\",\n        action=\"store_true\",\n        dest=\"mobile_emulator\",\n        default=False,\n        help=\"\"\"If this option is enabled, the mobile emulator\n                will be used while running tests.\"\"\",\n    )\n    parser.addoption(\n        \"--metrics\",\n        \"--device-metrics\",\n        \"--device_metrics\",\n        action=\"store\",\n        dest=\"device_metrics\",\n        default=None,\n        help=\"\"\"Designates the three device metrics of the mobile\n                emulator: CSS Width, CSS Height, and Pixel-Ratio.\n                Format: A comma-separated string with the 3 values.\n                Examples: \"375,734,5\" or \"412,732,3\" or \"390,715,3\"\n                Default: None. (Will use default values if None)\"\"\",\n    )\n    parser.addoption(\n        \"--chromium_arg\",\n        \"--chromium-arg\",\n        action=\"store\",\n        dest=\"chromium_arg\",\n        default=None,\n        help=\"\"\"Add a Chromium argument for Chrome/Edge browsers.\n                Format: A comma-separated list of Chromium args.\n                If an arg doesn't start with \"--\", that will be\n                added to the beginning of the arg automatically.\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--firefox_arg\",\n        \"--firefox-arg\",\n        action=\"store\",\n        dest=\"firefox_arg\",\n        default=None,\n        help=\"\"\"Add a Firefox argument for Firefox browser runs.\n                Format: A comma-separated list of Firefox args.\n                If an arg doesn't start with \"--\", that will be\n                added to the beginning of the arg automatically.\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--firefox_pref\",\n        \"--firefox-pref\",\n        action=\"store\",\n        dest=\"firefox_pref\",\n        default=None,\n        help=\"\"\"Set a Firefox preference:value combination.\n                Format: A comma-separated list of pref:value items.\n                Example usage:\n                    --firefox-pref=\"browser.formfill.enable:True\"\n                    --firefox-pref=\"pdfjs.disabled:False\"\n                    --firefox-pref=\"abc.def.xyz:42,hello.world:text\"\n                Boolean and integer values to the right of the \":\"\n                will be automatically converted into proper format.\n                If there's no \":\" in the string, then True is used.\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--extension_zip\",\n        \"--extension-zip\",\n        \"--crx\",\n        action=\"store\",\n        dest=\"extension_zip\",\n        default=None,\n        help=\"\"\"Designates the Chrome Extension ZIP file to load.\n                Format: A comma-separated list of .zip or .crx files\n                containing the Chrome extensions to load.\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--extension_dir\",\n        \"--extension-dir\",\n        action=\"store\",\n        dest=\"extension_dir\",\n        default=None,\n        help=\"\"\"Designates the Chrome Extension folder to load.\n                Format: A directory containing the Chrome extension.\n                (Can also be a comma-separated list of directories.)\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--disable_features\",\n        \"--disable-features\",\n        action=\"store\",\n        dest=\"disable_features\",\n        default=None,\n        help=\"\"\"Disable Chromium features from Chrome/Edge browsers.\n                Format: A comma-separated list of Chromium features.\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--binary_location\",\n        \"--binary-location\",\n        \"--bl\",\n        action=\"store\",\n        dest=\"binary_location\",\n        default=None,\n        help=\"\"\"Sets the path of the Chromium browser binary to use.\n                Uses the default location if not os.path.exists(PATH)\"\"\",\n    )\n    parser.addoption(\n        \"--driver_version\",\n        \"--driver-version\",\n        action=\"store\",\n        dest=\"driver_version\",\n        default=None,\n        help=\"\"\"Setting this overrides the default driver version,\n                which is set to match the detected browser version.\n                Major version only. Example: \"--driver-version=114\"\n                (Only chromedriver and uc_driver are affected.)\"\"\",\n    )\n    parser.addoption(\n        \"--pls\",\n        \"--page_load_strategy\",\n        \"--page-load-strategy\",\n        action=\"store\",\n        dest=\"page_load_strategy\",\n        type=str.lower,\n        choices=(\n            constants.PageLoadStrategy.NORMAL,\n            constants.PageLoadStrategy.EAGER,\n            constants.PageLoadStrategy.NONE,\n        ),\n        default=None,\n        help=\"\"\"This option sets Chrome's pageLoadStrategy.\n                List of choices: \"normal\", \"eager\", \"none\".\"\"\",\n    )\n    parser.addoption(\n        \"--headless\",\n        action=\"store_true\",\n        dest=\"headless\",\n        default=False,\n        help=\"\"\"Using this option activates headless mode,\n                which is required on headless machines\n                UNLESS using a virtual display with Xvfb.\n                Default: False on Mac/Windows. True on Linux.\"\"\",\n    )\n    parser.addoption(\n        \"--headless1\",\n        action=\"store_true\",\n        dest=\"headless1\",\n        default=False,\n        help=\"\"\"This option activates the old headless mode,\n                which is faster, but has limitations.\n                (May be phased out by Chrome in the future.)\"\"\",\n    )\n    parser.addoption(\n        \"--headless2\",\n        action=\"store_true\",\n        dest=\"headless2\",\n        default=False,\n        help=\"\"\"This option activates the new headless mode,\n                which supports Chromium extensions, and more,\n                but is slower than the standard headless mode.\"\"\",\n    )\n    parser.addoption(\n        \"--headed\",\n        \"--gui\",\n        action=\"store_true\",\n        dest=\"headed\",\n        default=False,\n        help=\"\"\"Using this makes Webdriver run web browsers with\n                a GUI when running tests on Linux machines.\n                (The default setting on Linux is headless.)\n                (The default setting on Mac or Windows is headed.)\"\"\",\n    )\n    parser.addoption(\n        \"--xvfb\",\n        action=\"store_true\",\n        dest=\"xvfb\",\n        default=False,\n        help=\"\"\"Using this makes tests run headlessly using Xvfb\n                instead of the browser's built-in headless mode.\n                When using \"--xvfb\", the \"--headless\" option\n                will no longer be enabled by default on Linux.\n                Default: False. (Linux-ONLY!)\"\"\",\n    )\n    parser.addoption(\n        \"--xvfb-metrics\",\n        \"--xvfb_metrics\",\n        action=\"store\",\n        dest=\"xvfb_metrics\",\n        default=None,\n        help=\"\"\"Customize the Xvfb metrics (Width,Height) on Linux.\n                Format: A comma-separated string with the 2 values.\n                Examples: \"1920,1080\" or \"1366,768\" or \"1024,768\".\n                Default: None. (None: \"1366,768\". Min: \"1024,768\".)\"\"\",\n    )\n    parser.addoption(\n        \"--locale_code\",\n        \"--locale-code\",\n        \"--locale\",\n        action=\"store\",\n        dest=\"locale_code\",\n        default=None,\n        help=\"\"\"Designates the Locale Code for the web browser.\n                A Locale is a specific version of a spoken Language.\n                The Locale alters visible text on supported websites.\n                See: https://seleniumbase.io/help_docs/locale_codes/\n                Default: None. (The web browser's default mode.)\"\"\",\n    )\n    parser.addoption(\n        \"--interval\",\n        action=\"store\",\n        dest=\"interval\",\n        default=None,\n        help=\"\"\"This globally overrides the default interval,\n                (in seconds), of features that include autoplay\n                functionality, such as tours and presentations.\n                Overrides from methods take priority over this.\n                (Headless Mode skips tours and presentations.)\"\"\",\n    )\n    parser.addoption(\n        \"--start_page\",\n        \"--start-page\",\n        \"--url\",\n        action=\"store\",\n        dest=\"start_page\",\n        default=None,\n        help=\"\"\"Designates the starting URL for the web browser\n                when each test begins.\n                Default: None.\"\"\",\n    )\n    parser.addoption(\n        \"--is_pytest\",\n        \"--is-pytest\",\n        action=\"store_true\",\n        dest=\"is_pytest\",\n        default=True,\n        help=\"\"\"This is used by the BaseCase class to tell apart\n                pytest runs from nosetest runs. (Automatic)\"\"\",\n    )\n    parser.addoption(\n        \"--all-scripts\",\n        \"--all_scripts\",\n        action=\"store_true\",\n        dest=\"all_scripts\",\n        default=False,\n        help=\"\"\"Use this to run `SB()`, `DriverContext()` and\n                `Driver()` scripts that are discovered during\n                the pytest collection phase.\"\"\",\n    )\n    parser.addoption(\n        \"--time_limit\",\n        \"--time-limit\",\n        \"--timelimit\",\n        action=\"store\",\n        dest=\"time_limit\",\n        default=None,\n        help=\"\"\"Use this to set a time limit per test, in seconds.\n                If a test runs beyond the limit, it fails.\"\"\",\n    )\n    parser.addoption(\n        \"--slow_mode\",\n        \"--slow-mode\",\n        \"--slowmo\",\n        \"--slow\",\n        action=\"store_true\",\n        dest=\"slow_mode\",\n        default=False,\n        help=\"\"\"Using this slows down the automation.\"\"\",\n    )\n    parser.addoption(\n        \"--demo_mode\",\n        \"--demo-mode\",\n        \"--demo\",\n        action=\"store_true\",\n        dest=\"demo_mode\",\n        default=False,\n        help=\"\"\"Using this slows down the automation and lets you\n                visually see what the tests are actually doing.\"\"\",\n    )\n    parser.addoption(\n        \"--demo_sleep\",\n        \"--demo-sleep\",\n        action=\"store\",\n        dest=\"demo_sleep\",\n        default=None,\n        help=\"\"\"Setting this overrides the Demo Mode sleep\n                time that happens after browser actions.\"\"\",\n    )\n    parser.addoption(\n        \"--highlights\",\n        action=\"store\",\n        dest=\"highlights\",\n        default=None,\n        help=\"\"\"Setting this overrides the default number of\n                highlight animation loops to have per call.\"\"\",\n    )\n    parser.addoption(\n        \"--message_duration\",\n        \"--message-duration\",\n        action=\"store\",\n        dest=\"message_duration\",\n        default=None,\n        help=\"\"\"Setting this overrides the default time that\n                messenger notifications remain visible when\n                reaching assert statements during Demo Mode.\"\"\",\n    )\n    parser.addoption(\n        \"--check_js\",\n        \"--check-js\",\n        action=\"store_true\",\n        dest=\"js_checking_on\",\n        default=False,\n        help=\"\"\"The option to check for JavaScript errors after\n                every page load.\"\"\",\n    )\n    parser.addoption(\n        \"--adblock\",\n        \"--ad_block\",\n        \"--ad-block\",\n        \"--block_ads\",\n        \"--block-ads\",\n        action=\"store_true\",\n        dest=\"ad_block_on\",\n        default=False,\n        help=\"\"\"Using this makes WebDriver block display ads\n                that are defined in ad_block_list.AD_BLOCK_LIST.\"\"\",\n    )\n    parser.addoption(\n        \"--host_resolver_rules\",\n        \"--host-resolver-rules\",\n        action=\"store\",\n        dest=\"host_resolver_rules\",\n        default=None,\n        help=\"\"\"Use this option to set \"host-resolver-rules\".\n                This lets you re-map traffic from any domain.\n                Eg. \"MAP www.google-analytics.com 0.0.0.0\".\n                Eg. \"MAP * ~NOTFOUND , EXCLUDE myproxy\".\n                Eg. \"MAP * 0.0.0.0 , EXCLUDE 127.0.0.1\".\n                Eg. \"MAP *.google.com myproxy\".\n                Find more examples on these pages:\n                (https://www.electronjs.org/docs/\n                 latest/api/command-line-switches)\n                (https://www.chromium.org/developers/\n                 design-documents/network-stack/socks-proxy/)\n                Use comma-separation for multiple host rules.\"\"\",\n    )\n    parser.addoption(\n        \"--block_images\",\n        \"--block-images\",\n        action=\"store_true\",\n        dest=\"block_images\",\n        default=False,\n        help=\"\"\"Using this makes WebDriver block images from\n                loading on web pages during tests.\"\"\",\n    )\n    parser.addoption(\n        \"--do_not_track\",\n        \"--do-not-track\",\n        action=\"store_true\",\n        dest=\"do_not_track\",\n        default=False,\n        help=\"\"\"Indicate to websites that you don't want to be\n                tracked. The browser will send an extra HTTP\n                header each time it requests a web page.\n                https://support.google.com/chrome/answer/2790761\"\"\",\n    )\n    parser.addoption(\n        \"--verify_delay\",\n        \"--verify-delay\",\n        action=\"store\",\n        dest=\"verify_delay\",\n        default=None,\n        help=\"\"\"Setting this overrides the default wait time\n                before each MasterQA verification pop-up.\"\"\",\n    )\n    parser.addoption(\n        \"--esc-end\",\n        \"--esc_end\",\n        \"--ee\",\n        action=\"store_true\",\n        dest=\"esc_end\",\n        default=False,\n        help=\"\"\"End the current test early via the ESC key.\n                The test will be marked as skipped.\"\"\",\n    )\n    parser.addoption(\n        \"--recorder\",\n        \"--record\",\n        \"--rec\",\n        \"--codegen\",\n        action=\"store_true\",\n        dest=\"recorder_mode\",\n        default=False,\n        help=\"\"\"Using this enables the SeleniumBase Recorder,\n                which records browser actions for converting\n                into SeleniumBase scripts.\"\"\",\n    )\n    parser.addoption(\n        \"--rec-behave\",\n        \"--rec-gherkin\",\n        action=\"store_true\",\n        dest=\"rec_behave\",\n        default=False,\n        help=\"\"\"Not only enables the SeleniumBase Recorder,\n                but also saves recorded actions into the\n                behave-gerkin format, which includes a\n                feature file, an imported steps file,\n                and the environment.py file.\"\"\",\n    )\n    parser.addoption(\n        \"--rec-sleep\",\n        \"--record-sleep\",\n        action=\"store_true\",\n        dest=\"record_sleep\",\n        default=False,\n        help=\"\"\"If Recorder Mode is enabled,\n                records sleep(seconds) calls.\"\"\",\n    )\n    parser.addoption(\n        \"--rec-print\",\n        action=\"store_true\",\n        dest=\"rec_print\",\n        default=False,\n        help=\"\"\"If Recorder Mode is enabled,\n                prints output after tests end.\"\"\",\n    )\n    parser.addoption(\n        \"--disable_js\",\n        \"--disable-js\",\n        action=\"store_true\",\n        dest=\"disable_js\",\n        default=False,\n        help=\"\"\"The option to disable JavaScript on web pages.\n                Warning: Most web pages will stop working!\"\"\",\n    )\n    parser.addoption(\n        \"--disable_cookies\",\n        \"--disable-cookies\",\n        action=\"store_true\",\n        dest=\"disable_cookies\",\n        default=False,\n        help=\"\"\"The option to disable Cookies on web pages.\n                Warning: Several pages may stop working!\"\"\",\n    )\n    parser.addoption(\n        \"--disable_csp\",\n        \"--disable-csp\",\n        \"--no_csp\",\n        \"--no-csp\",\n        \"--dcsp\",\n        action=\"store_true\",\n        dest=\"disable_csp\",\n        default=False,\n        help=\"\"\"Using this disables the Content Security Policy of\n                websites, which may interfere with some features of\n                SeleniumBase, such as loading custom JavaScript\n                libraries for various testing actions.\n                Setting this to True (--disable-csp) overrides the\n                value set in seleniumbase/config/settings.py\"\"\",\n    )\n    parser.addoption(\n        \"--disable_ws\",\n        \"--disable-ws\",\n        \"--dws\",\n        \"--disable-web-security\",\n        action=\"store_true\",\n        dest=\"disable_ws\",\n        default=False,\n        help=\"\"\"Using this disables the \"Web Security\" feature of\n                Chrome and Chromium-based browsers such as Edge.\"\"\",\n    )\n    parser.addoption(\n        \"--enable_ws\",\n        \"--enable-ws\",\n        \"--enable-web-security\",\n        action=\"store_true\",\n        dest=\"enable_ws\",\n        default=False,\n        help=\"\"\"Using this enables the \"Web Security\" feature of\n                Chrome and Chromium-based browsers such as Edge.\"\"\",\n    )\n    parser.addoption(\n        \"--enable_sync\",\n        \"--enable-sync\",\n        action=\"store_true\",\n        dest=\"enable_sync\",\n        default=False,\n        help=\"\"\"Using this enables the \"Chrome Sync\" feature.\"\"\",\n    )\n    parser.addoption(\n        \"--use_auto_ext\",\n        \"--use-auto-ext\",\n        \"--auto-ext\",\n        action=\"store_true\",\n        dest=\"use_auto_ext\",\n        default=False,\n        help=\"\"\"(DEPRECATED) - Enable the automation extension.\n                It's not required, but some commands & advanced\n                features may need it.\"\"\",\n    )\n    parser.addoption(\n        \"--undetected\",\n        \"--undetectable\",\n        \"--uc\",  # undetected-chromedriver\n        action=\"store_true\",\n        dest=\"undetectable\",\n        default=False,\n        help=\"\"\"Using this option makes chromedriver undetectable\n                to websites that use anti-bot services to block\n                automation tools from navigating them freely.\"\"\",\n    )\n    parser.addoption(\n        \"--uc_cdp_events\",\n        \"--uc-cdp-events\",\n        \"--uc-cdp\",  # For capturing CDP events during UC Mode\n        action=\"store_true\",\n        dest=\"uc_cdp_events\",\n        default=None,\n        help=\"\"\"Captures CDP events during Undetectable Mode runs.\n                Then you can add a listener to perform actions on\n                received data, such as printing it to the console:\n                    from pprint import pformat\n                    self.driver.add_cdp_listener(\n                        \"*\", lambda data: print(pformat(data))\n                    )\n                    self.open(URL)\"\"\",\n    )\n    parser.addoption(\n        \"--uc_subprocess\",\n        \"--uc-subprocess\",\n        \"--uc-sub\",  # undetected-chromedriver subprocess mode\n        action=\"store_true\",\n        dest=\"uc_subprocess\",\n        default=None,\n        help=\"\"\"(DEPRECATED) - (UC Mode always uses this now.)\n                Use undetectable-chromedriver as a subprocess,\n                which can help avoid issues that might result.\"\"\",\n    )\n    parser.addoption(\n        \"--no_sandbox\",\n        \"--no-sandbox\",\n        action=\"store_true\",\n        dest=\"no_sandbox\",\n        default=False,\n        help=\"\"\"(DEPRECATED) - \"--no-sandbox\" is always used now.\n                Using this enables the \"No Sandbox\" feature.\n                (This setting is now always enabled by default.)\"\"\",\n    )\n    parser.addoption(\n        \"--disable_gpu\",\n        \"--disable-gpu\",\n        action=\"store_true\",\n        dest=\"disable_gpu\",\n        default=False,\n        help=\"\"\"(DEPRECATED) - GPU is disabled if no swiftshader.\n                Using this enables the \"Disable GPU\" feature.\n                (GPU is disabled by default if swiftshader off.)\"\"\",\n    )\n    parser.addoption(\n        \"--log_cdp\",\n        \"--log-cdp\",\n        \"--log_cdp_events\",\n        \"--log-cdp-events\",\n        action=\"store_true\",\n        dest=\"log_cdp_events\",\n        default=None,\n        help=\"\"\"Capture CDP events. Then you can print them.\n                Eg. print(driver.get_log(\"performance\"))\"\"\",\n    )\n    parser.addoption(\n        \"--remote_debug\",\n        \"--remote-debug\",\n        \"--remote-debugger\",\n        \"--remote_debugger\",\n        action=\"store_true\",\n        dest=\"remote_debug\",\n        default=False,\n        help=\"\"\"This syncs the browser to Chromium's remote debugger.\n                To access the remote debugging interface, go to:\n                chrome://inspect/#devices while tests are running.\n                The previous URL was at: http://localhost:9222/\n                Info: chromedevtools.github.io/devtools-protocol/\"\"\",\n    )\n    parser.addoption(\n        \"--final-debug\",\n        \"--final-trace\",\n        \"--fdebug\",\n        \"--ftrace\",\n        action=\"store_true\",\n        dest=\"final_debug\",\n        default=False,\n        help=\"\"\"Enter Debug Mode at the end of each test.\n                To enter Debug Mode only on failures, use \"--pdb\".\n                If using both \"--final-debug\" and \"--pdb\" together,\n                then Debug Mode will activate twice on failures.\"\"\",\n    )\n    parser.addoption(\n        \"--dashboard\",\n        action=\"store_true\",\n        dest=\"dashboard\",\n        default=False,\n        help=\"\"\"Using this enables the SeleniumBase Dashboard.\n                To access the SeleniumBase Dashboard interface,\n                open the dashboard.html file located in the same\n                folder that the pytest command was run from.\"\"\",\n    )\n    parser.addoption(\n        \"--dash_title\",\n        \"--dash-title\",\n        dest=\"dash_title\",\n        default=None,\n        help=\"Set the title shown for the generated dashboard.\",\n    )\n    parser.addoption(\n        \"--enable_3d_apis\",\n        \"--enable-3d-apis\",\n        action=\"store_true\",\n        dest=\"enable_3d_apis\",\n        default=False,\n        help=\"\"\"Using this enables WebGL and 3D APIs.\"\"\",\n    )\n    parser.addoption(\n        \"--swiftshader\",\n        action=\"store_true\",\n        dest=\"swiftshader\",\n        default=False,\n        help=\"\"\"Using this enables the \"--use-gl=swiftshader\"\n                feature when running tests on Chrome.\"\"\",\n    )\n    parser.addoption(\n        \"--incognito\",\n        \"--incognito_mode\",\n        \"--incognito-mode\",\n        action=\"store_true\",\n        dest=\"incognito\",\n        default=False,\n        help=\"\"\"Using this enables Chrome's Incognito mode.\"\"\",\n    )\n    parser.addoption(\n        \"--guest\",\n        \"--guest_mode\",\n        \"--guest-mode\",\n        action=\"store_true\",\n        dest=\"guest_mode\",\n        default=False,\n        help=\"\"\"Using this enables Chrome's Guest mode.\"\"\",\n    )\n    parser.addoption(\n        \"--dark\",\n        \"--dark_mode\",\n        \"--dark-mode\",\n        action=\"store_true\",\n        dest=\"dark_mode\",\n        default=False,\n        help=\"\"\"Using this enables Chrome's Dark mode.\"\"\",\n    )\n    parser.addoption(\n        \"--devtools\",\n        \"--open_devtools\",\n        \"--open-devtools\",\n        action=\"store_true\",\n        dest=\"devtools\",\n        default=False,\n        help=\"\"\"Using this opens Chrome's DevTools.\"\"\",\n    )\n    parser.addoption(\n        \"--rs\",\n        \"--reuse_session\",\n        \"--reuse-session\",\n        action=\"store_true\",\n        dest=\"reuse_session\",\n        default=False,\n        help=\"\"\"The option to reuse the selenium browser window\n                session for all tests.\"\"\",\n    )\n    parser.addoption(\n        \"--rcs\",\n        \"--reuse_class_session\",\n        \"--reuse-class-session\",\n        action=\"store_true\",\n        dest=\"reuse_class_session\",\n        default=False,\n        help=\"\"\"The option to reuse the selenium browser window\n                session for all tests within the same class.\"\"\",\n    )\n    parser.addoption(\n        \"--crumbs\",\n        action=\"store_true\",\n        dest=\"crumbs\",\n        default=False,\n        help=\"\"\"The option to delete all cookies between tests\n                that reuse the same browser session. This option\n                is only useful if using \"--reuse-session\"/\"--rs\"\n                or \"--reuse-class-session\"/\"--rcs\" because tests\n                use a new clean browser if not reusing sessions.\"\"\",\n    )\n    parser.addoption(\n        \"--disable-beforeunload\",\n        \"--disable_beforeunload\",\n        action=\"store_true\",\n        dest=\"_disable_beforeunload\",\n        default=False,\n        help=\"\"\"The option to disable the \"beforeunload\" event\n                on Chromium browsers (Chrome or Edge).\n                This is already the default Firefox option.\"\"\",\n    )\n    parser.addoption(\n        \"--window-position\",\n        \"--window_position\",\n        action=\"store\",\n        dest=\"window_position\",\n        default=None,\n        help=\"\"\"The option to set the starting window x,y position\n                Format: A comma-separated string with the 2 values.\n                Example: \"55,66\"\n                Default: None. (Will use default values if None)\"\"\",\n    )\n    parser.addoption(\n        \"--window-size\",\n        \"--window_size\",\n        action=\"store\",\n        dest=\"window_size\",\n        default=None,\n        help=\"\"\"The option to set the default window \"width,height\".\n                Format: A comma-separated string with the 2 values.\n                Example: \"1200,800\"\n                Default: None. (Will use default values if None)\"\"\",\n    )\n    parser.addoption(\n        \"--maximize_window\",\n        \"--maximize-window\",\n        \"--maximize\",\n        \"--fullscreen\",\n        action=\"store_true\",\n        dest=\"maximize_option\",\n        default=False,\n        help=\"\"\"The option to start with a maximized browser window.\n                (Overrides the \"window-size\" option if used.)\"\"\",\n    )\n    parser.addoption(\n        \"--screenshot\",\n        \"--save_screenshot\",\n        \"--save-screenshot\",\n        \"--ss\",\n        action=\"store_true\",\n        dest=\"save_screenshot\",\n        default=False,\n        help=\"\"\"Save a screenshot at the end of every test.\n                By default, this is only done for failures.\n                Will be saved in the \"latest_logs/\" folder.\"\"\",\n    )\n    parser.addoption(\n        \"--no-screenshot\",\n        \"--no_screenshot\",\n        \"--ns\",\n        action=\"store_true\",\n        dest=\"no_screenshot\",\n        default=False,\n        help=\"\"\"No screenshots saved unless tests directly ask it.\n                This changes default behavior where screenshots are\n                saved for test failures and pytest-html reports.\"\"\",\n    )\n    parser.addoption(\n        \"--visual_baseline\",\n        \"--visual-baseline\",\n        action=\"store_true\",\n        dest=\"visual_baseline\",\n        default=False,\n        help=\"\"\"Setting this resets the visual baseline for\n                Automated Visual Testing with SeleniumBase.\n                When a test calls self.check_window(), it will\n                rebuild its files in the visual_baseline folder.\"\"\",\n    )\n    parser.addoption(\n        \"--wire\",\n        action=\"store_true\",\n        dest=\"use_wire\",\n        default=False,\n        help=\"\"\"Use selenium-wire's webdriver for selenium webdriver.\"\"\",\n    )\n    parser.addoption(\n        \"--external_pdf\",\n        \"--external-pdf\",\n        action=\"store_true\",\n        dest=\"external_pdf\",\n        default=False,\n        help=\"\"\"This option sets the following on Chromium:\n                \"plugins.always_open_pdf_externally\": True,\n                which causes opened PDF URLs to download immediately,\n                instead of being displayed in the browser window.\"\"\",\n    )\n    parser.addoption(\n        \"--timeout_multiplier\",\n        \"--timeout-multiplier\",\n        action=\"store\",\n        dest=\"timeout_multiplier\",\n        default=None,\n        help=\"\"\"Setting this overrides the default timeout\n                by the multiplier when waiting for page elements.\n                Unused when tests override the default value.\"\"\",\n    )\n    parser.addoption(\n        \"--lfp\",\n        \"--list-fail-page\",\n        \"--list-fail-pages\",\n        action=\"store_true\",\n        dest=\"fail_page\",\n        default=False,\n        help=\"\"\"(For debugging) After each failing test, list the URL\n                where the failure occurred.\n                Useful when you don't have access to the latest_logs/\n                folder, such as when running tests in GitHub Actions.\"\"\",\n    )\n\n    arg_join = \" \".join(sys_argv)\n    sb_config._browser_shortcut = None\n    sb_config._cdp_browser = None\n    sb_config._cdp_bin_loc = None\n    sb_config._vd_list = []\n    # Check if binary-location in options\n    bin_loc_in_options = False\n    for arg in sys_argv:\n        if arg in [\"--binary-location\", \"--binary_location\", \"--bl\"]:\n            bin_loc_in_options = True\n    # SeleniumBase does not support pytest-timeout due to hanging browsers.\n    for arg in sys_argv:\n        if \"--timeout=\" in arg:\n            raise Exception(\n                \"\\n  Don't use --timeout=s from pytest-timeout! \"\n                \"\\n  It's not thread-safe for WebDriver processes! \"\n                \"\\n  Use --time-limit=s from SeleniumBase instead!\\n\"\n            )\n\n    # Dashboard Mode does not support tests using forked subprocesses.\n    if \"--forked\" in sys_argv and \"--dashboard\" in sys_argv:\n        raise Exception(\n            \"\\n  Dashboard Mode does NOT support forked subprocesses!\"\n            '\\n  (*** DO NOT combine \"--forked\" with \"--dashboard\"! ***)\\n'\n        )\n\n    # Reuse-Session Mode does not support tests using forked subprocesses.\n    if \"--forked\" in sys_argv and (\n        \"--rs\" in sys_argv or \"--reuse-session\" in sys_argv\n    ):\n        raise Exception(\n            \"\\n  Reuse-Session Mode does NOT support forked subprocesses!\"\n            '\\n  (DO NOT combine \"--forked\" with \"--rs\"/\"--reuse-session\"!)\\n'\n        )\n\n    # Recorder Mode does not support multi-threaded / multi-process runs.\n    if (\n        \"--recorder\" in sys_argv\n        or \"--record\" in sys_argv\n        or \"--rec\" in sys_argv\n    ):\n        if (\"-n\" in sys_argv) or (\" -n=\" in arg_join) or (\"-c\" in sys_argv):\n            raise Exception(\n                \"\\n  Recorder Mode does NOT support multi-process mode (-n)!\"\n                '\\n  (DO NOT combine \"--recorder\" with \"-n NUM_PROCESSES\"!)\\n'\n            )\n\n    using_recorder = False\n    if (\n        \"--recorder\" in sys_argv\n        or \"--record\" in sys_argv\n        or \"--rec\" in sys_argv\n    ):\n        using_recorder = True\n\n    # As a shortcut, you can use \"--edge\" instead of \"--browser=edge\", etc,\n    # but you can only specify one default browser for tests. (Default: chrome)\n    browser_changes = 0\n    browser_set = None\n    browser_text = None\n    browser_list = []\n    if \"--browser=chrome\" in sys_argv or \"--browser chrome\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"chrome\"\n        browser_list.append(\"--browser=chrome\")\n    if \"--browser=edge\" in sys_argv or \"--browser edge\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"edge\"\n        browser_list.append(\"--browser=edge\")\n    if \"--browser=firefox\" in sys_argv or \"--browser firefox\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"firefox\"\n        browser_list.append(\"--browser=firefox\")\n    if \"--browser=safari\" in sys_argv or \"--browser safari\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"safari\"\n        browser_list.append(\"--browser=safari\")\n    if \"--browser=ie\" in sys_argv or \"--browser ie\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"ie\"\n        browser_list.append(\"--browser=ie\")\n    if \"--browser=remote\" in sys_argv or \"--browser remote\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"remote\"\n        browser_list.append(\"--browser=remote\")\n    if \"--browser=opera\" in sys_argv or \"--browser opera\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"opera\"\n                sb_config._browser_shortcut = \"opera\"\n                sb_config._cdp_browser = \"opera\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=opera\")\n    if \"--browser=brave\" in sys_argv or \"--browser brave\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"brave\"\n                sb_config._browser_shortcut = \"brave\"\n                sb_config._cdp_browser = \"brave\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=brave\")\n    if \"--browser=comet\" in sys_argv or \"--browser comet\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"comet\"\n                sb_config._browser_shortcut = \"comet\"\n                sb_config._cdp_browser = \"comet\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=comet\")\n    if \"--browser=atlas\" in sys_argv or \"--browser atlas\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"atlas\"\n                sb_config._browser_shortcut = \"atlas\"\n                sb_config._cdp_browser = \"atlas\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=atlas\")\n    browser_text = browser_set\n    if \"--chrome\" in sys_argv and not browser_set == \"chrome\":\n        browser_changes += 1\n        browser_text = \"chrome\"\n        sb_config._browser_shortcut = \"chrome\"\n        browser_list.append(\"--chrome\")\n    if \"--edge\" in sys_argv and not browser_set == \"edge\":\n        browser_changes += 1\n        browser_text = \"edge\"\n        sb_config._browser_shortcut = \"edge\"\n        browser_list.append(\"--edge\")\n    if \"--firefox\" in sys_argv and not browser_set == \"firefox\":\n        browser_changes += 1\n        browser_text = \"firefox\"\n        sb_config._browser_shortcut = \"firefox\"\n        browser_list.append(\"--firefox\")\n    if \"--ie\" in sys_argv and not browser_set == \"ie\":\n        browser_changes += 1\n        browser_text = \"ie\"\n        sb_config._browser_shortcut = \"ie\"\n        browser_list.append(\"--ie\")\n    if \"--safari\" in sys_argv and not browser_set == \"safari\":\n        browser_changes += 1\n        browser_text = \"safari\"\n        sb_config._browser_shortcut = \"safari\"\n        browser_list.append(\"--safari\")\n    if \"--opera\" in sys_argv and not browser_set == \"opera\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"opera\"\n                sb_config._browser_shortcut = \"opera\"\n                sb_config._cdp_browser = \"opera\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--opera\")\n    if \"--brave\" in sys_argv and not browser_set == \"brave\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"brave\"\n                sb_config._browser_shortcut = \"brave\"\n                sb_config._cdp_browser = \"brave\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--brave\")\n    if \"--comet\" in sys_argv and not browser_set == \"comet\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"comet\"\n                sb_config._browser_shortcut = \"comet\"\n                sb_config._cdp_browser = \"comet\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--comet\")\n    if \"--atlas\" in sys_argv and not browser_set == \"atlas\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"atlas\"\n                sb_config._browser_shortcut = \"atlas\"\n                sb_config._cdp_browser = \"atlas\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--atlas\")\n    if browser_changes > 1:\n        message = \"\\n  TOO MANY browser types were entered!\"\n        message += \"\\n  There were %s found:\\n  >  %s\" % (\n            browser_changes,\n            \", \".join(browser_list),\n        )\n        message += \"\\n  ONLY ONE default browser is allowed!\"\n        message += \"\\n  Select a single browser & try again!\\n\"\n        raise Exception(message)\n    if (\n        using_recorder\n        and browser_changes == 1\n        and browser_text not in [\n            \"chrome\", \"edge\", \"opera\", \"brave\", \"comet\", \"atlas\", \"chromium\"\n        ]\n    ):\n        message = (\n            \"\\n  Recorder Mode ONLY supports Chromium browsers!\"\n            '\\n  (Your browser choice was: \"%s\")\\n' % browser_list[0]\n        )\n        raise Exception(message)\n    undetectable = False\n    if (\n        \"--undetected\" in sys_argv\n        or \"--undetectable\" in sys_argv\n        or \"--uc\" in sys_argv\n        or \"--uc-cdp-events\" in sys_argv\n        or \"--uc_cdp_events\" in sys_argv\n        or \"--uc-cdp\" in sys_argv\n        or \"--uc-subprocess\" in sys_argv\n        or \"--uc_subprocess\" in sys_argv\n        or \"--uc-sub\" in sys_argv\n    ):\n        undetectable = True\n    if (\n        browser_changes == 1\n        and browser_text not in [\n            \"chrome\", \"opera\", \"brave\", \"comet\", \"atlas\", \"chromium\"\n        ]\n        and undetectable\n    ):\n        message = (\n            '\\n  Undetected-Chromedriver Mode ONLY supports Chrome!'\n            '\\n  (\"--uc\" / \"--undetected\" / \"--undetectable\")'\n            '\\n  (Your browser choice was: \"%s\")\\n' % browser_list[0]\n        )\n        raise Exception(message)\n    if undetectable and \"--wire\" in sys_argv:\n        raise Exception(\n            \"\\n  SeleniumBase doesn't support mixing --uc with --wire mode!\"\n            \"\\n  If you need both, override get_new_driver() from BaseCase:\"\n            \"\\n  https://seleniumbase.io/help_docs/syntax_formats/#sb_sf_09\\n\"\n        )\n\n\ndef pytest_configure(config):\n    \"\"\"This runs after command-line options have been parsed.\"\"\"\n    sb_config.item_count = 0\n    sb_config.item_count_passed = 0\n    sb_config.item_count_failed = 0\n    sb_config.item_count_skipped = 0\n    sb_config.item_count_untested = 0\n    sb_config.is_pytest = True\n    sb_config.is_behave = False\n    sb_config.is_nosetest = False\n    sb_config.is_context_manager = False\n    sb_config.pytest_config = config\n    sb_config.browser = config.getoption(\"browser\")\n    if sb_config._browser_shortcut:\n        sb_config.browser = sb_config._browser_shortcut\n    elif sys_argv == [\"-c\"]:  # Multithreading messes with args\n        if config.getoption(\"use_opera\"):\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.browser = \"opera\"\n        elif config.getoption(\"use_brave\"):\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.browser = \"brave\"\n        elif config.getoption(\"use_comet\"):\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.browser = \"comet\"\n        elif config.getoption(\"use_atlas\"):\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.browser = \"atlas\"\n    sb_config.account = config.getoption(\"account\")\n    sb_config.data = config.getoption(\"data\")\n    sb_config.var1 = config.getoption(\"var1\")\n    sb_config.var2 = config.getoption(\"var2\")\n    sb_config.var3 = config.getoption(\"var3\")\n    sb_config.variables = config.getoption(\"variables\")\n    sb_config.environment = config.getoption(\"environment\")\n    sb_config.with_selenium = config.getoption(\"with_selenium\")\n    sb_config.user_agent = config.getoption(\"user_agent\")\n    sb_config.mobile_emulator = config.getoption(\"mobile_emulator\")\n    sb_config.device_metrics = config.getoption(\"device_metrics\")\n    sb_config.headless = config.getoption(\"headless\")\n    sb_config.headless1 = config.getoption(\"headless1\")\n    if sb_config.headless1:\n        sb_config.headless = True\n    sb_config.headless2 = config.getoption(\"headless2\")\n    if sb_config.headless2 and sb_config.browser == \"firefox\":\n        sb_config.headless2 = False  # Only for Chromium browsers\n        sb_config.headless = True  # Firefox has regular headless\n    elif (\n        sb_config.browser not in [\n            \"chrome\", \"edge\", \"opera\", \"brave\", \"comet\", \"atlas\", \"chromium\"\n        ]\n    ):\n        sb_config.headless2 = False  # Only for Chromium browsers\n    sb_config.headed = config.getoption(\"headed\")\n    sb_config.xvfb = config.getoption(\"xvfb\")\n    sb_config.xvfb_metrics = config.getoption(\"xvfb_metrics\")\n    sb_config.locale_code = config.getoption(\"locale_code\")\n    sb_config.interval = config.getoption(\"interval\")\n    sb_config.start_page = config.getoption(\"start_page\")\n    sb_config.chromium_arg = config.getoption(\"chromium_arg\")\n    sb_config.firefox_arg = config.getoption(\"firefox_arg\")\n    sb_config.firefox_pref = config.getoption(\"firefox_pref\")\n    sb_config.extension_zip = config.getoption(\"extension_zip\")\n    sb_config.extension_dir = config.getoption(\"extension_dir\")\n    sb_config.disable_features = config.getoption(\"disable_features\")\n    sb_config.binary_location = config.getoption(\"binary_location\")\n    if getattr(sb_config, \"_cdp_bin_loc\", None):\n        sb_config.binary_location = sb_config._cdp_bin_loc\n    elif not sb_config.binary_location:\n        if (\n            config.getoption(\"use_opera\")\n            or sb_config._browser_shortcut == \"opera\"\n        ):\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.binary_location = bin_loc\n        elif (\n            config.getoption(\"use_brave\")\n            or sb_config._browser_shortcut == \"brave\"\n        ):\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.binary_location = bin_loc\n        elif (\n            config.getoption(\"use_comet\")\n            or sb_config._browser_shortcut == \"comet\"\n        ):\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.binary_location = bin_loc\n        elif (\n            config.getoption(\"use_atlas\")\n            or sb_config._browser_shortcut == \"atlas\"\n        ):\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if bin_loc and os.path.exists(bin_loc):\n                sb_config.binary_location = bin_loc\n    if config.getoption(\"use_chromium\") and not sb_config.binary_location:\n        sb_config.binary_location = \"_chromium_\"\n    elif config.getoption(\"use_cft\") and not sb_config.binary_location:\n        sb_config.binary_location = \"cft\"\n    elif config.getoption(\"use_chs\") and not sb_config.binary_location:\n        sb_config.binary_location = \"chs\"\n    if (\n        sb_config.binary_location\n        and sb_config.binary_location.lower() == \"chs\"\n        and sb_config.browser == \"chrome\"\n    ):\n        sb_config.headless = True\n        sb_config.headless1 = False\n        sb_config.headless2 = False\n    if sb_config.browser in constants.ChromiumSubs.chromium_subs:\n        if not sb_config.binary_location:\n            sb_config.browser = \"chrome\"  # Still uses chromedriver\n            sb_config._browser_shortcut = sb_config.browser\n    sb_config.driver_version = config.getoption(\"driver_version\")\n    sb_config.page_load_strategy = config.getoption(\"page_load_strategy\")\n    sb_config.with_testing_base = config.getoption(\"with_testing_base\")\n    sb_config.with_db_reporting = config.getoption(\"with_db_reporting\")\n    sb_config.with_s3_logging = config.getoption(\"with_s3_logging\")\n    sb_config.with_screen_shots = config.getoption(\"with_screen_shots\")\n    sb_config.with_basic_test_info = config.getoption(\"with_basic_test_info\")\n    sb_config.with_page_source = config.getoption(\"with_page_source\")\n    sb_config.protocol = config.getoption(\"protocol\")\n    sb_config.servername = config.getoption(\"servername\")\n    sb_config.port = config.getoption(\"port\")\n    if sb_config.servername != \"localhost\":\n        # Using Selenium Grid\n        # (Set --server=\"127.0.0.1\" for localhost Grid)\n        if str(sb_config.port) == \"443\":\n            sb_config.protocol = \"https\"\n    sb_config.proxy_string = config.getoption(\"proxy_string\")\n    sb_config.proxy_bypass_list = config.getoption(\"proxy_bypass_list\")\n    sb_config.proxy_pac_url = config.getoption(\"proxy_pac_url\")\n    sb_config.proxy_driver = config.getoption(\"proxy_driver\")\n    sb_config.multi_proxy = config.getoption(\"multi_proxy\")\n    sb_config.cap_file = config.getoption(\"cap_file\")\n    sb_config.cap_string = config.getoption(\"cap_string\")\n    sb_config.settings_file = config.getoption(\"settings_file\")\n    sb_config.user_data_dir = config.getoption(\"user_data_dir\")\n    sb_config.database_env = config.getoption(\"database_env\")\n    sb_config.log_path = constants.Logs.LATEST + \"/\"\n    sb_config.archive_logs = config.getoption(\"archive_logs\")\n    if config.getoption(\"archive_downloads\"):\n        settings.ARCHIVE_EXISTING_DOWNLOADS = True\n    if config.getoption(\"skip_js_waits\"):\n        settings.SKIP_JS_WAITS = True\n    if config.getoption(\"wait_for_angularjs\"):\n        settings.WAIT_FOR_ANGULARJS = True\n    sb_config.all_scripts = config.getoption(\"all_scripts\")\n    sb_config._time_limit = config.getoption(\"time_limit\")\n    sb_config.time_limit = config.getoption(\"time_limit\")\n    sb_config.slow_mode = config.getoption(\"slow_mode\")\n    sb_config.demo_mode = config.getoption(\"demo_mode\")\n    sb_config.demo_sleep = config.getoption(\"demo_sleep\")\n    sb_config.highlights = config.getoption(\"highlights\")\n    sb_config.message_duration = config.getoption(\"message_duration\")\n    sb_config.js_checking_on = config.getoption(\"js_checking_on\")\n    sb_config.ad_block_on = config.getoption(\"ad_block_on\")\n    sb_config.host_resolver_rules = config.getoption(\"host_resolver_rules\")\n    sb_config.block_images = config.getoption(\"block_images\")\n    sb_config.do_not_track = config.getoption(\"do_not_track\")\n    sb_config.verify_delay = config.getoption(\"verify_delay\")\n    sb_config.esc_end = config.getoption(\"esc_end\")\n    sb_config.recorder_mode = config.getoption(\"recorder_mode\")\n    sb_config.recorder_ext = config.getoption(\"recorder_mode\")  # Again\n    sb_config.rec_behave = config.getoption(\"rec_behave\")\n    sb_config.rec_print = config.getoption(\"rec_print\")\n    sb_config.record_sleep = config.getoption(\"record_sleep\")\n    if sb_config.rec_print and not sb_config.recorder_mode:\n        sb_config.recorder_mode = True\n        sb_config.recorder_ext = True\n    elif sb_config.rec_behave and not sb_config.recorder_mode:\n        sb_config.recorder_mode = True\n        sb_config.recorder_ext = True\n    elif sb_config.record_sleep and not sb_config.recorder_mode:\n        sb_config.recorder_mode = True\n        sb_config.recorder_ext = True\n    sb_config.disable_cookies = config.getoption(\"disable_cookies\")\n    sb_config.disable_js = config.getoption(\"disable_js\")\n    sb_config.disable_csp = config.getoption(\"disable_csp\")\n    sb_config.disable_ws = config.getoption(\"disable_ws\")\n    sb_config.enable_ws = config.getoption(\"enable_ws\")\n    if not sb_config.disable_ws:\n        sb_config.enable_ws = True\n    sb_config.enable_sync = config.getoption(\"enable_sync\")\n    sb_config.use_auto_ext = config.getoption(\"use_auto_ext\")\n    sb_config.undetectable = config.getoption(\"undetectable\")\n    sb_config.uc_cdp_events = config.getoption(\"uc_cdp_events\")\n    if sb_config.uc_cdp_events and not sb_config.undetectable:\n        sb_config.undetectable = True\n    sb_config.uc_subprocess = config.getoption(\"uc_subprocess\")\n    if sb_config.uc_subprocess and not sb_config.undetectable:\n        sb_config.undetectable = True\n    sb_config.no_sandbox = config.getoption(\"no_sandbox\")\n    sb_config.disable_gpu = config.getoption(\"disable_gpu\")\n    sb_config.log_cdp_events = config.getoption(\"log_cdp_events\")\n    sb_config.remote_debug = config.getoption(\"remote_debug\")\n    sb_config.final_debug = config.getoption(\"final_debug\")\n    sb_config.dashboard = config.getoption(\"dashboard\")\n    sb_config.dash_title = config.getoption(\"dash_title\")\n    sb_config.enable_3d_apis = config.getoption(\"enable_3d_apis\")\n    sb_config.swiftshader = config.getoption(\"swiftshader\")\n    sb_config.incognito = config.getoption(\"incognito\")\n    sb_config.guest_mode = config.getoption(\"guest_mode\")\n    sb_config.dark_mode = config.getoption(\"dark_mode\")\n    sb_config.devtools = config.getoption(\"devtools\")\n    sb_config.reuse_session = config.getoption(\"reuse_session\")\n    sb_config.reuse_class_session = config.getoption(\"reuse_class_session\")\n    if sb_config.reuse_class_session:\n        sb_config.reuse_session = True\n    sb_config.shared_driver = None  # The default driver for session reuse\n    sb_config.crumbs = config.getoption(\"crumbs\")\n    sb_config._disable_beforeunload = config.getoption(\"_disable_beforeunload\")\n    sb_config.window_position = config.getoption(\"window_position\")\n    sb_config.window_size = config.getoption(\"window_size\")\n    sb_config.maximize_option = config.getoption(\"maximize_option\")\n    sb_config.save_screenshot = config.getoption(\"save_screenshot\")\n    sb_config.no_screenshot = config.getoption(\"no_screenshot\")\n    sb_config.visual_baseline = config.getoption(\"visual_baseline\")\n    sb_config.use_wire = config.getoption(\"use_wire\")\n    sb_config.external_pdf = config.getoption(\"external_pdf\")\n    sb_config.timeout_multiplier = config.getoption(\"timeout_multiplier\")\n    sb_config.list_fp = config.getoption(\"fail_page\")\n    sb_config._is_timeout_changed = False\n    sb_config._has_logs = False\n    sb_config._fail_page = None\n    sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT\n    sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT\n    sb_config.pytest_html_report = config.getoption(\"htmlpath\")  # --html=FILE\n    sb_config._sb_class = None  # (Used with the sb fixture for \"--rcs\")\n    sb_config._sb_node = {}  # sb node dictionary (Used with the sb fixture)\n    # Dashboard-specific variables\n    sb_config._results = {}  # SBase Dashboard test results\n    sb_config._duration = {}  # SBase Dashboard test duration\n    sb_config._display_id = {}  # SBase Dashboard display ID\n    sb_config._d_t_log_path = {}  # SBase Dashboard test log path\n    sb_config._dash_html = None  # SBase Dashboard HTML copy\n    sb_config._test_id = None  # SBase Dashboard test id\n    sb_config._latest_display_id = None  # The latest SBase display id\n    sb_config._dashboard_initialized = False  # Becomes True after init\n    sb_config._has_exception = False  # This becomes True if any test fails\n    sb_config._multithreaded = False  # This becomes True if multithreading\n    sb_config._only_unittest = True  # If any test uses BaseCase, becomes False\n    sb_config._sbase_detected = False  # Becomes True during SeleniumBase tests\n    sb_config._extra_dash_entries = []  # Dashboard entries for non-SBase tests\n    sb_config._using_html_report = False  # Becomes True when using html report\n    sb_config._dash_is_html_report = False  # Dashboard becomes the html report\n    sb_config._saved_dashboard_pie = None  # Copy of pie chart for html report\n    sb_config._dash_final_summary = None  # Dash status to add to html report\n    sb_config._html_report_name = None  # The name of the pytest html report\n    sb_config._html_report_copy = None  # The copy of the pytest html report\n\n    arg_join = \" \".join(sys_argv)\n    if (\n        \"-n\" in sys_argv\n        or \" -n=\" in arg_join\n        or \" -n\" in arg_join\n        or \"-c\" in sys_argv\n        or \"-n=\" in config.getini(\"addopts\")\n        or \"-n \" in config.getini(\"addopts\")\n        or \"-n\" in config.getini(\"addopts\")\n    ):\n        sb_config._multithreaded = True\n    if (\n        \"--html\" in sys_argv\n        or \" --html=\" in arg_join\n        or \"--html=\" in config.getini(\"addopts\")\n        or \"--html \" in config.getini(\"addopts\")\n    ):\n        sb_config._using_html_report = True\n        sb_config._html_report_name = config.getoption(\"htmlpath\")\n        if sb_config.dashboard:\n            if sb_config._html_report_name == \"dashboard.html\":\n                sb_config._dash_is_html_report = True\n        sb_config._html_report_copy = \"last_report.html\"\n\n    # Recorder Mode does not support multi-threaded / multi-process runs.\n    if sb_config.recorder_mode and sb_config._multithreaded:\n        # At this point, the user likely put a \"-n NUM\" in the pytest.ini file.\n        # Since raising an exception in pytest_configure raises INTERNALERROR,\n        # print a message here instead and cancel Recorder Mode.\n        print(\n            \"\\n  Recorder Mode does NOT support multi-process mode (-n)!\"\n            '\\n  (DO NOT combine \"--recorder\" with \"-n NUM_PROCESSES\"!)'\n            \"\\n  (The Recorder WILL BE DISABLED during this run!)\\n\"\n        )\n        sb_config.recorder_mode = False\n        sb_config.recorder_ext = False\n\n    if sb_config.xvfb and \"linux\" not in sys.platform:\n        # The Xvfb virtual display server is for Linux OS Only!\n        sb_config.xvfb = False\n    if (\n        \"linux\" in sys.platform\n        and not sb_config.headed\n        and not sb_config.headless\n        and not sb_config.headless2\n        and not sb_config.xvfb\n    ):\n        if not sb_config.undetectable:\n            print(\n                \"(Linux uses --headless by default. \"\n                \"To override, use --headed / --gui. \"\n                \"For Xvfb mode instead, use --xvfb. \"\n                \"Or you can hide this info by using \"\n                \"--headless / --headless2 / --uc.)\"\n            )\n            sb_config.headless = True\n        else:\n            sb_config.xvfb = True\n\n    # Recorder Mode can still optimize scripts in --headless2 mode.\n    if sb_config.recorder_mode and sb_config.headless:\n        sb_config.headless = False\n        sb_config.headless1 = False\n        sb_config.headless2 = True\n\n    if not sb_config.headless and not sb_config.headless2:\n        sb_config.headed = True\n\n    if config.getoption(\"use_chrome\"):\n        sb_config.browser = \"chrome\"\n    elif config.getoption(\"use_edge\"):\n        sb_config.browser = \"edge\"\n    elif config.getoption(\"use_firefox\"):\n        sb_config.browser = \"firefox\"\n    elif config.getoption(\"use_ie\"):\n        sb_config.browser = \"ie\"\n    elif config.getoption(\"use_safari\"):\n        sb_config.browser = \"safari\"\n    else:\n        pass  # Use the browser specified by \"--browser=BROWSER\"\n\n    if sb_config.browser == \"safari\" and sb_config.headless:\n        sb_config.headless = False  # Safari doesn't support headless mode\n        sb_config.headless1 = False\n\n    if sb_config.dash_title:\n        constants.Dashboard.TITLE = sb_config.dash_title.replace(\"_\", \" \")\n\n    if sb_config.save_screenshot and sb_config.no_screenshot:\n        sb_config.save_screenshot = False  # \"no_screenshot\" has priority\n\n    if (\n        \"-v\" in sys_argv and not sb_config._multithreaded\n        or (\n            hasattr(config, \"invocation_params\")\n            and \"-v\" in config.invocation_params.args\n            and (\n                \"-n=1\" in config.invocation_params.args\n                or \"-n1\" in config.invocation_params.args\n                or \"-n 1\" in \" \".join(config.invocation_params.args)\n            )\n        )\n    ):\n        sb_config.list_fp = True  # List the fail pages in console output\n\n    if (\n        sb_config._multithreaded\n        and \"--co\" not in sys_argv\n        and \"--collect-only\" not in sys_argv\n    ):\n        from seleniumbase.core import download_helper\n        from seleniumbase.core import proxy_helper\n\n        log_helper.log_folder_setup(\n            constants.Logs.LATEST + \"/\", sb_config.archive_logs\n        )\n        download_helper.reset_downloads_folder()\n        proxy_helper.remove_proxy_zip_if_present()\n\n\ndef pytest_sessionstart(session):\n    pass\n\n\ndef _get_test_ids_(the_item):\n    test_id = the_item.nodeid\n    if not test_id:\n        test_id = \"unidentified_TestCase\"\n    display_id = test_id\n    r\"\"\"\n    # Now using the nodeid for both the test_id and display_id.\n    # (This only impacts tests using The Dashboard.)\n    # If there are any issues, we'll revert back to the old code.\n    test_id = the_item.nodeid.split(\"/\")[-1].replace(\" \", \"_\")\n    if \"[\" in test_id:\n        test_id_intro = test_id.split(\"[\")[0]\n        parameter = test_id.split(\"[\")[1]\n        parameter = re.sub(re.compile(r\"\\W\"), \"\", parameter)\n        test_id = test_id_intro + \"__\" + parameter\n    display_id = test_id\n    test_id = test_id.replace(\"/\", \".\").replace(\"\\\\\", \".\")\n    test_id = test_id.replace(\"::\", \".\").replace(\".py\", \"\")\n    \"\"\"\n    return test_id, display_id\n\n\ndef _create_dashboard_assets_():\n    from seleniumbase.js_code.live_js import live_js\n    from seleniumbase.core.style_sheet import get_pytest_style\n\n    abs_path = os.path.abspath(\".\")\n    assets_folder = os.path.join(abs_path, \"assets\")\n    if not os.path.exists(assets_folder):\n        with suppress(Exception):\n            os.makedirs(assets_folder, exist_ok=True)\n    pytest_style_css = os.path.join(assets_folder, \"pytest_style.css\")\n    add_pytest_style_css = True\n    if os.path.exists(pytest_style_css):\n        existing_pytest_style = None\n        with open(pytest_style_css, mode=\"r\") as f:\n            existing_pytest_style = f.read()\n        if existing_pytest_style == get_pytest_style():\n            add_pytest_style_css = False\n    if add_pytest_style_css:\n        out_file = open(pytest_style_css, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(get_pytest_style())\n        out_file.close()\n    live_js_file = os.path.join(assets_folder, \"live.js\")\n    add_live_js_file = True\n    if os.path.exists(live_js_file):\n        existing_live_js = None\n        with open(live_js_file, mode=\"r\") as f:\n            existing_live_js = f.read()\n        if existing_live_js == live_js:\n            add_live_js_file = False\n    if add_live_js_file:\n        out_file = open(live_js_file, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(live_js)\n        out_file.close()\n\n\ndef pytest_itemcollected(item):\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    sb_config.item_count += 1\n    if sb_config.dashboard:\n        test_id, display_id = _get_test_ids_(item)\n        sb_config._results[test_id] = \"Untested\"\n        sb_config._duration[test_id] = \"-\"\n        sb_config._display_id[test_id] = display_id\n        sb_config._d_t_log_path[test_id] = None\n\n\ndef pytest_deselected(items):\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    if sb_config.dashboard:\n        sb_config.item_count -= len(items)\n        for item in items:\n            test_id, display_id = _get_test_ids_(item)\n            if test_id in sb_config._results.keys():\n                sb_config._results.pop(test_id)\n\n\ndef pytest_collection_finish(session):\n    \"\"\"This runs after item collection is finalized.\n    https://docs.pytest.org/en/stable/reference.html \"\"\"\n    sb_config._context_of_runner = False  # Context Manager Compatibility\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    if len(session.items) > 0 and not sb_config._multithreaded:\n        from seleniumbase.core import download_helper\n        from seleniumbase.core import proxy_helper\n\n        log_helper.log_folder_setup(\n            constants.Logs.LATEST + \"/\", sb_config.archive_logs\n        )\n        download_helper.reset_downloads_folder()\n        proxy_helper.remove_proxy_zip_if_present()\n    if sb_config.dashboard and len(session.items) > 0:\n        _create_dashboard_assets_()\n        # Print the Dashboard path if at least one test runs.\n        sb_config.item_count_untested = sb_config.item_count\n        dash_path = os.path.join(os.getcwd(), \"dashboard.html\")\n        dash_url = \"file://\" + dash_path.replace(\"\\\\\", \"/\")\n        star_len = len(\"Dashboard: \") + len(dash_url)\n        with suppress(Exception):\n            terminal_size = os.get_terminal_size().columns\n            if terminal_size > 30 and star_len > terminal_size:\n                star_len = terminal_size\n        stars = \"*\" * star_len\n        c1 = \"\"\n        cr = \"\"\n        if \"linux\" not in sys.platform:\n            c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n            cr = colorama.Style.RESET_ALL\n        if sb_config._multithreaded:\n            if (\n                hasattr(session.config, \"workerinput\")\n                and session.config.workerinput[\"workerid\"] == \"gw0\"\n            ):\n                sys.stderr.write(\n                    \"\\nDashboard: %s%s%s\\n%s\\n\" % (c1, dash_url, cr, stars)\n                )\n        else:\n            print(\"Dashboard: %s%s%s\\n%s\" % (c1, dash_url, cr, stars))\n\n\ndef pytest_runtest_setup(item):\n    \"\"\"This runs before every test with pytest.\"\"\"\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    if sb_config.dashboard:\n        sb_config._sbase_detected = False\n        sb_config._pdb_failure = False\n    sb_config._fail_page = None\n    test_id, display_id = _get_test_ids_(item)\n    sb_config._test_id = test_id\n    sb_config._latest_display_id = display_id\n\n\ndef pytest_runtest_teardown(item):\n    \"\"\"This runs after every test with pytest.\n    Make sure that webdriver and headless displays have exited.\n    (Has zero effect on tests using --reuse-session / --rs)\"\"\"\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    with suppress(Exception):\n        if hasattr(item, \"_testcase\") or hasattr(sb_config, \"_sb_pdb_driver\"):\n            if hasattr(item, \"_testcase\"):\n                self = item._testcase\n                with suppress(Exception):\n                    if (\n                        getattr(self, \"driver\", None)\n                        and \"--pdb\" not in sys_argv\n                    ):\n                        if not (is_windows or self.driver.service.process):\n                            self.driver.quit()\n            elif getattr(sb_config, \"_sb_pdb_driver\", None):\n                with suppress(Exception):\n                    if (\n                        not is_windows\n                        or sb_config._sb_pdb_driver.service.process\n                    ):\n                        sb_config._sb_pdb_driver.quit()\n                        sb_config._sb_pdb_driver = None\n        with suppress(Exception):\n            if (\n                getattr(self, \"_xvfb_display\", None)\n                and hasattr(self._xvfb_display, \"stop\")\n                and not getattr(sb_config, \"reuse_session\", None)\n            ):\n                self.headless_active = False\n                sb_config.headless_active = False\n                self._xvfb_display.stop()\n                self._xvfb_display = None\n            if (\n                getattr(sb_config, \"_virtual_display\", None)\n                and hasattr(sb_config._virtual_display, \"stop\")\n                and not getattr(sb_config, \"reuse_session\", None)\n            ):\n                sb_config._virtual_display.stop()\n                sb_config._virtual_display = None\n    if (\n        (\n            sb_config._has_exception\n            or (python3_11_or_newer and py311_patch2)\n            or \"--pdb\" in sys_argv\n        )\n        and sb_config.list_fp\n        and sb_config._fail_page\n    ):\n        if (\n            \"-s\" in sys_argv\n            or \"--capture=no\" in sys_argv\n            or \"--capture=tee-sys\" in sys_argv\n            or (\n                hasattr(sb_config.pytest_config, \"invocation_params\")\n                and (\n                    \"-s\" in sb_config.pytest_config.invocation_params.args\n                    or \"--capture=no\" in (\n                        sb_config.pytest_config.invocation_params.args\n                    )\n                    or \"--capture=tee-sys\" in (\n                        sb_config.pytest_config.invocation_params.args\n                    )\n                )\n            )\n        ):\n            print(\"\\n=> Fail Page: %s\" % sb_config._fail_page)\n        else:\n            sys.stdout.write(\"\\n=> Fail Page: %s\\n\" % sb_config._fail_page)\n\n\ndef pytest_html_duration_format(duration):\n    return \"%.2f\" % duration\n\n\ndef pytest_sessionfinish(session):\n    pass\n\n\ndef pytest_terminal_summary(terminalreporter):\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    if (\n        not hasattr(terminalreporter, \"stats\")\n        or not hasattr(terminalreporter.stats, \"keys\")\n    ):\n        return\n    if len(terminalreporter.stats.keys()) == 0:\n        return\n    if not sb_config._multithreaded and not sb_config._sbase_detected:\n        return\n    latest_logs_dir = os.path.join(os.getcwd(), constants.Logs.LATEST) + os.sep\n    if (\n        \"failed\" in terminalreporter.stats.keys()\n        and os.path.exists(latest_logs_dir)\n        and os.listdir(latest_logs_dir)\n    ):\n        sb_config._has_exception = True\n    if sb_config._multithreaded:\n        if os.path.exists(latest_logs_dir) and os.listdir(latest_logs_dir):\n            sb_config._has_exception = True\n        if sb_config.dashboard:\n            abs_path = os.path.abspath(\".\")\n            dash_lock = constants.Dashboard.LOCKFILE\n            dash_lock_path = os.path.join(abs_path, dash_lock)\n            if os.path.exists(dash_lock_path):\n                sb_config._only_unittest = False\n    if sb_config._has_exception and (\n        sb_config.dashboard and not sb_config._only_unittest\n    ):\n        # Print link a second time because the first one may be off-screen\n        dashboard_file = os.path.join(os.getcwd(), \"dashboard.html\")\n        dashboard_url = \"file://\" + dashboard_file.replace(\"\\\\\", \"/\")\n        terminalreporter.write_sep(\"-\", \"Dashboard = %s\" % dashboard_url)\n    if (\n        sb_config._has_exception\n        or sb_config.save_screenshot\n        or sb_config._has_logs\n    ):\n        # Log files are generated during test failures and Screenshot Mode\n        terminalreporter.write_sep(\n            \"-\", \"Latest Logs dir: %s\" % latest_logs_dir\n        )\n\n\ndef _perform_pytest_unconfigure_(config):\n    from seleniumbase.core import proxy_helper\n\n    reporter = config.pluginmanager.get_plugin(\"terminalreporter\")\n    start_time = None\n    if hasattr(reporter, \"_sessionstarttime\"):\n        start_time = reporter._sessionstarttime  # (pytest < 8.4.0)\n    else:\n        start_time = reporter._session_start.time  # (pytest >= 8.4.0)\n    duration = time.time() - start_time\n    if not getattr(sb_config, \"multi_proxy\", None):\n        proxy_helper.remove_proxy_zip_if_present()\n    if getattr(sb_config, \"reuse_session\", None):\n        # Close the shared browser session\n        if sb_config.shared_driver:\n            try:\n                if (\n                    not is_windows\n                    or sb_config.browser == \"ie\"\n                    or sb_config.shared_driver.service.process\n                ):\n                    sb_config.shared_driver.quit()\n            except AttributeError:\n                pass\n            except Exception:\n                pass\n        sb_config.shared_driver = None\n        with suppress(Exception):\n            if (\n                getattr(sb_config, \"_virtual_display\", None)\n                and hasattr(sb_config._virtual_display, \"stop\")\n            ):\n                sb_config._virtual_display.stop()\n                sb_config._virtual_display = None\n                sb_config.headless_active = False\n            if getattr(sb_config, \"_vd_list\", None):\n                if isinstance(sb_config._vd_list, list):\n                    for display in sb_config._vd_list:\n                        if display:\n                            with suppress(Exception):\n                                display.stop()\n    if hasattr(sb_config, \"log_path\") and sb_config.item_count > 0:\n        log_helper.archive_logs_if_set(\n            constants.Logs.LATEST + \"/\", sb_config.archive_logs\n        )\n        if os.path.exists(\"./assets/\"):  # Used by pytest-html reports\n            with suppress(Exception):\n                shared_utils.make_dir_files_writable(\"./assets/\")\n    log_helper.clear_empty_logs()\n    # Dashboard post-processing: Disable time-based refresh and stamp complete\n    if not getattr(sb_config, \"dashboard\", None):\n        html_report_path = None\n        the_html_r = None\n        abs_path = os.path.abspath(\".\")\n        if sb_config._html_report_name:\n            html_report_path = os.path.join(\n                abs_path, sb_config._html_report_name\n            )\n        if sb_config._html_report_copy:\n            html_report_path_copy = os.path.join(\n                abs_path, sb_config._html_report_copy\n            )\n        if (\n            sb_config._using_html_report\n            and html_report_path\n            and os.path.exists(html_report_path)\n        ):\n            with open(html_report_path, mode=\"r\", encoding=\"utf-8\") as f:\n                the_html_r = f.read()\n            assets_chunk = \"if (assets.length === 1) {\"\n            remove_media = \"container.classList.remove('media-container')\"\n            rm_n_left = '<div class=\"media-container__nav--left\"><</div>'\n            rm_n_right = '<div class=\"media-container__nav--right\">></div>'\n            the_html_r = the_html_r.replace(\n                assets_chunk,\n                \"%s %s\" % (assets_chunk, remove_media),\n            )\n            the_html_r = the_html_r.replace(rm_n_left, \"\")\n            the_html_r = the_html_r.replace(rm_n_right, \"\")\n            the_html_r = the_html_r.replace(\"<ul>$\", \"$\")\n            the_html_r = the_html_r.replace(\"}<ul>\", \"}\")\n            the_html_r = the_html_r.replace(\n                \"<li>${val}</li>\", \"${val},&nbsp;&nbsp;\"\n            )\n            the_html_r = the_html_r.replace(\n                \"<div>${value}</div>\", \"<span>${value}</span\"\n            )\n            ph_link = '<a href=\"https://pypi.python.org/pypi/pytest-html\">'\n            sb_link = (\n                '<a href=\"https://github.com/seleniumbase/SeleniumBase\">'\n                'SeleniumBase</a>'\n            )\n            the_html_r = the_html_r.replace(\n                ph_link, \"%s and %s\" % (sb_link, ph_link)\n            )\n            the_html_r = the_html_r.replace(\n                \"findAll('.collapsible\", \"//findAll('.collapsible\"\n            )\n            the_html_r = the_html_r.replace(\n                \"mediaName.innerText\", \"//mediaName.innerText\"\n            )\n            the_html_r = the_html_r.replace(\n                \"counter.innerText\", \"//counter.innerText\"\n            )\n            run_count = '<p class=\"run-count\">'\n            run_c_loc = the_html_r.find(run_count)\n            rc_loc = the_html_r.find(\" took \", run_c_loc)\n            end_rc_loc = the_html_r.find(\".</p>\", rc_loc)\n            run_time = \"%.2f\" % duration\n            new_time = \" ran in %s seconds\" % run_time\n            the_html_r = (\n                the_html_r[:rc_loc] + new_time + the_html_r[end_rc_loc:]\n            )\n            with open(html_report_path, mode=\"w\", encoding=\"utf-8\") as f:\n                f.write(the_html_r)  # Finalize the HTML report\n            with suppress(Exception):\n                shared_utils.make_writable(html_report_path)\n            with open(html_report_path_copy, mode=\"w\", encoding=\"utf-8\") as f:\n                f.write(the_html_r)  # Finalize the HTML report copy\n            with suppress(Exception):\n                shared_utils.make_writable(html_report_path_copy)\n            assets_style = \"./assets/style.css\"\n            if os.path.exists(assets_style):\n                html_style = None\n                with open(assets_style, mode=\"r\", encoding=\"utf-8\") as f:\n                    html_style = f.read()\n                if html_style:\n                    html_style = html_style.replace(\"top: -50px;\", \"top: 2px;\")\n                    html_style = html_style.replace(\"+ 50px)\", \"+ 40px)\")\n                    html_style = html_style.replace(\"ht: 240px;\", \"ht: 228px;\")\n                    html_style = html_style.replace(\n                        \"- 80px);\", \"- 80px);\\n  margin-bottom: -42px;\"\n                    )\n                    html_style = html_style.replace(\".collapsible\", \".oldc\")\n                    html_style = html_style.replace(\" (hide details)\", \"\")\n                    html_style = html_style.replace(\" (show details)\", \"\")\n                with open(assets_style, mode=\"w\", encoding=\"utf-8\") as f:\n                    f.write(html_style)\n                with suppress(Exception):\n                    shared_utils.make_writable(assets_style)\n        # Done with \"pytest_unconfigure\" unless using the Dashboard\n        return\n    stamp = \"\"\n    if sb_config._dash_is_html_report:\n        # (If the Dashboard URL is the same as the HTML Report URL:)\n        # Have the html report refresh back to a dashboard on update\n        stamp += (\n            '\\n<script type=\"text/javascript\" src=\"%s\">'\n            \"</script>\" % constants.Dashboard.LIVE_JS\n        )\n    stamp += \"\\n<!--Test Run Complete-->\"\n    find_it = constants.Dashboard.META_REFRESH_HTML\n    swap_with = \"\"  # Stop refreshing the page after the run is done\n    find_it_2 = \"Awaiting results... (Refresh the page for updates)\"\n    swap_with_2 = (\n        \"Test Run ENDED: Some results UNREPORTED due to skipped tearDown()\"\n    )\n    find_it_3 = '<td class=\"col-result\">Untested</td>'\n    swap_with_3 = '<td class=\"col-result\">Unreported</td>'\n    # These use caching to prevent extra method calls\n    DASH_PIE_PNG_1 = constants.Dashboard.get_dash_pie_1()\n    DASH_PIE_PNG_2 = constants.Dashboard.get_dash_pie_2()\n    DASH_PIE_PNG_3 = constants.Dashboard.get_dash_pie_3()\n    find_it_4 = 'href=\"%s\"' % DASH_PIE_PNG_1\n    swap_with_4 = 'href=\"%s\"' % DASH_PIE_PNG_2\n    try:\n        abs_path = os.path.abspath(\".\")\n        dashboard_path = os.path.join(abs_path, \"dashboard.html\")\n        # Part 1: Finalizing the dashboard / integrating html report\n        if os.path.exists(dashboard_path):\n            the_html_d = None\n            with open(dashboard_path, mode=\"r\", encoding=\"utf-8\") as f:\n                the_html_d = f.read()\n            if sb_config._multithreaded and \"-c\" in sys_argv:\n                # Threads have \"-c\" in sys.argv, except for the last\n                raise Exception('Break out of \"try\" block.')\n            if sb_config._multithreaded:\n                dash_pie_loc = constants.Dashboard.DASH_PIE\n                pie_path = os.path.join(abs_path, dash_pie_loc)\n                if os.path.exists(pie_path):\n                    import json\n\n                    with open(pie_path, mode=\"r\") as f:\n                        dash_pie = f.read().strip()\n                    sb_config._saved_dashboard_pie = json.loads(dash_pie)\n            # If the test run doesn't complete by itself, stop refresh\n            the_html_d = the_html_d.replace(find_it, swap_with)\n            the_html_d = the_html_d.replace(find_it_2, swap_with_2)\n            the_html_d = the_html_d.replace(find_it_3, swap_with_3)\n            the_html_d = the_html_d.replace(find_it_4, swap_with_4)\n            the_html_d += stamp\n            if sb_config._dash_is_html_report and (\n                sb_config._saved_dashboard_pie\n            ):\n                the_html_d = the_html_d.replace(\n                    \"<h1>dashboard.html</h1>\",\n                    sb_config._saved_dashboard_pie,\n                )\n                the_html_d = the_html_d.replace(\n                    \"</head>\",\n                    '</head><link rel=\"shortcut icon\" '\n                    'href=\"%s\">' % DASH_PIE_PNG_3,\n                )\n                the_html_d = the_html_d.replace(\"<html>\", '<html lang=\"en\">')\n                the_html_d = the_html_d.replace(\n                    \"<head>\",\n                    '<head><meta http-equiv=\"Content-Type\" '\n                    'content=\"text/html, charset=utf-8;\">'\n                    '<meta name=\"viewport\" content=\"shrink-to-fit=no\">',\n                )\n                if sb_config._dash_final_summary:\n                    the_html_d += sb_config._dash_final_summary\n                time.sleep(0.1)  # Add time for \"livejs\" to detect changes\n                with open(dashboard_path, mode=\"w\", encoding=\"utf-8\") as f:\n                    f.write(the_html_d)  # Finalize the dashboard\n                time.sleep(0.1)  # Add time for \"livejs\" to detect changes\n                the_html_d = the_html_d.replace(\n                    \"</head>\", \"</head><!-- Dashboard Report Done -->\"\n                )\n            with open(dashboard_path, mode=\"w\", encoding=\"utf-8\") as f:\n                f.write(the_html_d)  # Finalize the dashboard\n            with suppress(Exception):\n                shared_utils.make_writable(dashboard_path)\n            assets_style = \"./assets/style.css\"\n            if os.path.exists(assets_style):\n                html_style = None\n                with open(assets_style, mode=\"r\", encoding=\"utf-8\") as f:\n                    html_style = f.read()\n                if html_style:\n                    html_style = html_style.replace(\"top: -50px;\", \"top: 2px;\")\n                    html_style = html_style.replace(\"+ 50px)\", \"+ 40px)\")\n                    html_style = html_style.replace(\"ht: 240px;\", \"ht: 228px;\")\n                    html_style = html_style.replace(\n                        \"- 80px);\", \"- 80px);\\n  margin-bottom: -42px;\"\n                    )\n                    html_style = html_style.replace(\".collapsible\", \".oldc\")\n                    html_style = html_style.replace(\" (hide details)\", \"\")\n                    html_style = html_style.replace(\" (show details)\", \"\")\n                with open(assets_style, mode=\"w\", encoding=\"utf-8\") as f:\n                    f.write(html_style)\n                with suppress(Exception):\n                    shared_utils.make_writable(assets_style)\n            # Part 2: Appending a pytest html report with dashboard data\n            html_report_path = None\n            if sb_config._html_report_name:\n                html_report_path = os.path.join(\n                    abs_path, sb_config._html_report_name\n                )\n            if sb_config._html_report_copy:\n                html_report_path_copy = os.path.join(\n                    abs_path, sb_config._html_report_copy\n                )\n            if (\n                sb_config._using_html_report\n                and html_report_path\n                and os.path.exists(html_report_path)\n                and not sb_config._dash_is_html_report\n            ):\n                # Add the dashboard pie to the pytest html report\n                the_html_r = None\n                with open(html_report_path, mode=\"r\", encoding=\"utf-8\") as f:\n                    the_html_r = f.read()\n                if sb_config._saved_dashboard_pie:\n                    h_r_name = sb_config._html_report_name\n                    if \"/\" in h_r_name and h_r_name.endswith(\".html\"):\n                        h_r_name = h_r_name.split(\"/\")[-1]\n                    elif \"\\\\\" in h_r_name and h_r_name.endswith(\".html\"):\n                        h_r_name = h_r_name.split(\"\\\\\")[-1]\n                    the_html_r = the_html_r.replace(\n                        '<h1 id=\"title\">%s</h1>' % h_r_name,\n                        sb_config._saved_dashboard_pie,\n                    )\n                    the_html_r = the_html_r.replace(\n                        \"</head>\",\n                        '</head><link rel=\"shortcut icon\" href='\n                        '\"%s\">' % DASH_PIE_PNG_3,\n                    )\n                    if sb_config._dash_final_summary:\n                        the_html_r += sb_config._dash_final_summary\n                assets_chunk = \"if (assets.length === 1) {\"\n                remove_media = \"container.classList.remove('media-container')\"\n                rm_n_left = '<div class=\"media-container__nav--left\"><</div>'\n                rm_n_right = '<div class=\"media-container__nav--right\">></div>'\n                the_html_r = the_html_r.replace(\n                    assets_chunk,\n                    \"%s %s\" % (assets_chunk, remove_media),\n                )\n                the_html_r = the_html_r.replace(rm_n_left, \"\")\n                the_html_r = the_html_r.replace(rm_n_right, \"\")\n                the_html_r = the_html_r.replace(\"<ul>$\", \"$\")\n                the_html_r = the_html_r.replace(\"}<ul>\", \"}\")\n                the_html_r = the_html_r.replace(\n                    \"<li>${val}</li>\", \"${val},&nbsp;&nbsp;\"\n                )\n                the_html_r = the_html_r.replace(\n                    \"<div>${value}</div>\", \"<span>${value}</span\"\n                )\n                ph_link = '<a href=\"https://pypi.python.org/pypi/pytest-html\">'\n                sb_link = (\n                    '<a href=\"https://github.com/seleniumbase/SeleniumBase\">'\n                    'SeleniumBase</a>'\n                )\n                the_html_r = the_html_r.replace(\n                    ph_link, \"%s and %s\" % (sb_link, ph_link)\n                )\n                the_html_r = the_html_r.replace(\n                    \"findAll('.collapsible\", \"//findAll('.collapsible\"\n                )\n                the_html_r = the_html_r.replace(\n                    \"mediaName.innerText\", \"//mediaName.innerText\"\n                )\n                the_html_r = the_html_r.replace(\n                    \"counter.innerText\", \"//counter.innerText\"\n                )\n                run_count = '<p class=\"run-count\">'\n                run_c_loc = the_html_r.find(run_count)\n                rc_loc = the_html_r.find(\" took \", run_c_loc)\n                end_rc_loc = the_html_r.find(\".</p>\", rc_loc)\n                run_time = \"%.2f\" % duration\n                new_time = \" ran in %s seconds\" % run_time\n                the_html_r = (\n                    the_html_r[:rc_loc] + new_time + the_html_r[end_rc_loc:]\n                )\n                with open(html_report_path, mode=\"w\", encoding=\"utf-8\") as f:\n                    f.write(the_html_r)  # Finalize the HTML report\n                with suppress(Exception):\n                    shared_utils.make_writable(html_report_path)\n                with open(\n                    html_report_path_copy, mode=\"w\", encoding=\"utf-8\"\n                ) as f:\n                    f.write(the_html_r)  # Finalize the HTML report copy\n                with suppress(Exception):\n                    shared_utils.make_writable(html_report_path_copy)\n    except KeyboardInterrupt:\n        pass\n    except Exception:\n        pass\n\n\ndef pytest_unconfigure(config):\n    \"\"\"This runs after all tests have completed with pytest.\"\"\"\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    reporter = config.pluginmanager.get_plugin(\"terminalreporter\")\n    if (\n        not hasattr(reporter, \"_sessionstarttime\")\n        and (\n            not hasattr(reporter, \"_session_start\")\n            or (\n                hasattr(reporter, \"_session_start\")\n                and not hasattr(reporter._session_start, \"time\")\n            )\n        )\n    ):\n        return\n    if getattr(sb_config, \"_multithreaded\", None):\n        import fasteners\n\n        dash_lock = fasteners.InterProcessLock(constants.Dashboard.LOCKFILE)\n        if getattr(sb_config, \"dashboard\", None):\n            # Multi-threaded tests with the Dashboard\n            abs_path = os.path.abspath(\".\")\n            dash_lock_file = constants.Dashboard.LOCKFILE\n            dash_lock_path = os.path.join(abs_path, dash_lock_file)\n            if os.path.exists(dash_lock_path):\n                sb_config._only_unittest = False\n                dashboard_path = os.path.join(abs_path, \"dashboard.html\")\n                with dash_lock:\n                    if (\n                        sb_config._dash_html\n                        and config.getoption(\"htmlpath\") == \"dashboard.html\"\n                    ):\n                        # Dash is HTML Report (Multithreaded)\n                        sb_config._dash_is_html_report = True\n                        with open(\n                            dashboard_path, mode=\"w\", encoding=\"utf-8\"\n                        ) as f:\n                            f.write(sb_config._dash_html)\n                    # Dashboard Multithreaded\n                    _perform_pytest_unconfigure_(config)\n                    return\n            else:\n                # Dash Lock is missing\n                _perform_pytest_unconfigure_(config)\n                return\n        with dash_lock:\n            # Multi-threaded tests\n            _perform_pytest_unconfigure_(config)\n            return\n    else:\n        # Single-threaded tests\n        _perform_pytest_unconfigure_(config)\n        return\n\n\n@pytest.fixture()\ndef sb(request):\n    \"\"\"SeleniumBase as a pytest fixture.\n    Usage example: \"def test_one(sb):\"\n    You may need to use this for tests that use other pytest fixtures.\"\"\"\n    from seleniumbase import BaseCase\n    from seleniumbase.core import session_helper\n\n    class BaseClass(BaseCase):\n        def setUp(self):\n            super().setUp()\n\n        def tearDown(self):\n            self.save_teardown_screenshot()\n            super().tearDown()\n\n        def base_method(self):\n            pass\n\n    if request.cls:\n        if sb_config.reuse_class_session:\n            the_class = str(request.cls).split(\".\")[-1].split(\"'\")[0]\n            if the_class != sb_config._sb_class:\n                session_helper.end_reused_class_session_as_needed()\n                sb_config._sb_class = the_class\n        request.cls.sb = BaseClass(\"base_method\")\n        request.cls.sb.setUp()\n        request.cls.sb._needs_tearDown = True\n        request.cls.sb._using_sb_fixture = True\n        request.cls.sb._using_sb_fixture_class = True\n        sb_config._sb_node[request.node.nodeid] = request.cls.sb\n        yield request.cls.sb\n        if request.cls.sb._needs_tearDown:\n            request.cls.sb.tearDown()\n            request.cls.sb._needs_tearDown = False\n    else:\n        sb = BaseClass(\"base_method\")\n        sb.setUp()\n        sb._needs_tearDown = True\n        sb._using_sb_fixture = True\n        sb._using_sb_fixture_no_class = True\n        sb_config._sb_node[request.node.nodeid] = sb\n        yield sb\n        if sb._needs_tearDown:\n            sb.tearDown()\n            sb._needs_tearDown = False\n\n\n@pytest.mark.hookwrapper\ndef pytest_runtest_makereport(item, call):\n    if \"--co\" in sys_argv or \"--collect-only\" in sys_argv:\n        return\n    pytest_html = item.config.pluginmanager.getplugin(\"html\")\n    outcome = yield\n    report = outcome.get_result()\n    if sb_config._multithreaded:\n        sb_config._using_html_report = True  # For Dashboard use\n    if (\n        pytest_html\n        and report.when == \"call\"\n        and hasattr(sb_config, \"dashboard\")\n    ):\n        if sb_config.dashboard and not sb_config._sbase_detected:\n            test_id, display_id = _get_test_ids_(item)\n            r_outcome = report.outcome\n            if len(r_outcome) > 1:\n                r_outcome = r_outcome[0].upper() + r_outcome[1:]\n            sb_config._results[test_id] = r_outcome\n            sb_config._duration[test_id] = \"*****\"\n            sb_config._display_id[test_id] = display_id\n            sb_config._d_t_log_path[test_id] = \"\"\n            if test_id not in sb_config._extra_dash_entries:\n                sb_config._extra_dash_entries.append(test_id)\n        elif (\n            sb_config._sbase_detected\n            and ((python3_11_or_newer and py311_patch2) or \"--pdb\" in sys_argv)\n            and (report.outcome == \"failed\" or \"AssertionError\" in str(call))\n            and not sb_config._has_exception\n        ):\n            # Handle a bug on Python 3.11 where exceptions aren't seen\n            log_path = \"\"\n            sb_config._has_logs = True\n            if hasattr(sb_config, \"_test_logpath\"):\n                log_path = sb_config._test_logpath\n            if sb_config.dashboard:\n                sb_config._process_dashboard_entry(True)\n            if hasattr(sb_config, \"_add_pytest_html_extra\"):\n                sb_config._add_pytest_html_extra()\n            if hasattr(sb_config, \"_visual_baseline_copies\"):\n                sb_config._process_v_baseline_logs()\n            sb_config._excinfo_value = call.excinfo.value\n            sb_config._excinfo_tb = call.excinfo.tb\n            if \"pytest_plugin.BaseClass.base_method\" not in log_path:\n                source = None\n                if hasattr(sb_config, \"_last_page_source\"):\n                    source = sb_config._last_page_source\n                if log_path and source:\n                    log_helper.log_page_source(log_path, None, source)\n                last_page_screenshot_png = None\n                if hasattr(sb_config, \"_last_page_screenshot_png\"):\n                    last_page_screenshot_png = (\n                        sb_config._last_page_screenshot_png\n                    )\n                if log_path and last_page_screenshot_png:\n                    log_helper.log_screenshot(\n                        log_path, None, last_page_screenshot_png\n                    )\n                if log_path:\n                    sb_config._log_fail_data()\n        with suppress(Exception):\n            extra_report = None\n            if hasattr(item, \"_testcase\"):\n                extra_report = item._testcase._html_report_extra\n            elif hasattr(item.instance, \"sb\") or (\n                item.nodeid in sb_config._sb_node\n            ):\n                if not hasattr(item.instance, \"sb\"):\n                    sb_node = sb_config._sb_node[item.nodeid]\n                else:\n                    sb_node = item.instance.sb\n                test_id = item.nodeid\n                if not test_id:\n                    test_id = \"unidentified_TestCase\"\n                r\"\"\"\n                # Now using the nodeid for both the test_id and display_id.\n                # (This only impacts tests using The Dashboard.)\n                # If there are any issues, we'll revert back to the old code.\n                test_id = test_id.split(\"/\")[-1].replace(\" \", \"_\")\n                if \"[\" in test_id:\n                    test_id_intro = test_id.split(\"[\")[0]\n                    parameter = test_id.split(\"[\")[1]\n                    parameter = re.sub(re.compile(r\"\\W\"), \"\", parameter)\n                    test_id = test_id_intro + \"__\" + parameter\n                test_id = test_id.replace(\"/\", \".\").replace(\"\\\\\", \".\")\n                test_id = test_id.replace(\"::\", \".\").replace(\".py\", \"\")\n                \"\"\"\n                sb_node._sb_test_identifier = test_id\n                if sb_node._needs_tearDown:\n                    sb_node.tearDown()\n                    sb_node._needs_tearDown = False\n                extra_report = sb_node._html_report_extra\n            else:\n                return\n            extra = getattr(report, \"extra\", [])\n            if len(extra_report) > 1 and extra_report[1][\"content\"]:\n                report.extras = extra + extra_report\n            if sb_config._dash_is_html_report:\n                # If the Dashboard URL is the same as the HTML Report URL,\n                # have the html report refresh back to a dashboard on update.\n                refresh_updates = (\n                    '<script type=\"text/javascript\" src=\"%s\">'\n                    \"</script>\" % constants.Dashboard.LIVE_JS\n                )\n                report.extras.append(pytest_html.extras.html(refresh_updates))\n"
  },
  {
    "path": "seleniumbase/plugins/s3_logging_plugin.py",
    "content": "\"\"\"S3 Logging Plugin for SeleniumBase tests that run with pynose / nosetests\"\"\"\nimport uuid\nimport os\nfrom nose.plugins import Plugin\n\n\nclass S3Logging(Plugin):\n    \"\"\"The plugin for uploading test logs to the S3 bucket specified.\"\"\"\n    name = \"s3_logging\"  # Usage: --with-s3-logging\n\n    def configure(self, options, conf):\n        \"\"\"Get the options.\"\"\"\n        super().configure(options, conf)\n        self.options = options\n        self.test_id = None\n\n    def save_data_to_logs(self, data, file_name):\n        from seleniumbase.fixtures import page_utils\n\n        test_logpath = os.path.join(self.options.log_path, self.test_id)\n        file_name = str(file_name)\n        destination_folder = test_logpath\n        page_utils._save_data_as(data, destination_folder, file_name)\n\n    def afterTest(self, test):\n        \"\"\"Upload logs to the S3 bucket after tests complete.\"\"\"\n        from seleniumbase.core.s3_manager import S3LoggingBucket\n\n        self.test_id = test.test.id()\n        s3_bucket = S3LoggingBucket()\n        guid = str(uuid.uuid4().hex)\n        path = os.path.join(self.options.log_path, self.test_id)\n        uploaded_files = []\n        for logfile in os.listdir(path):\n            logfile_name = \"%s/%s/%s\" % (\n                guid,\n                self.test_id,\n                logfile.split(path)[-1],\n            )\n            s3_bucket.upload_file(logfile_name, os.path.join(path, logfile))\n            uploaded_files.append(logfile_name)\n        s3_bucket.save_uploaded_file_names(uploaded_files)\n        index_file = s3_bucket.upload_index_file(\n            test.id(), guid, path, self.save_data_to_logs\n        )\n        print(\"\\n*** Log files uploaded: ***\\n%s\\n\" % index_file)\n\n        # If the SB database plugin is also being used,\n        # attach a link to the logs index database row.\n        if hasattr(test.test, \"testcase_guid\"):\n            from seleniumbase.core.testcase_manager import (\n                TestcaseDataPayload,\n                TestcaseManager,\n            )\n\n            self.testcase_manager = TestcaseManager(self.options.database_env)\n            data_payload = TestcaseDataPayload()\n            data_payload.guid = test.test.testcase_guid\n            data_payload.log_url = index_file\n            self.testcase_manager.update_testcase_log_url(data_payload)\n"
  },
  {
    "path": "seleniumbase/plugins/sb_manager.py",
    "content": "\"\"\"\nSeleniumBase as a Python Context Manager.\n#########################################\n\nThe SeleniumBase SB Context Manager:\nUsage --> ``with SB() as sb:``\n\nExample -->\n\n```python\nfrom seleniumbase import SB\n\nwith SB(uc=True, test=True) as sb:\n    url = \"https://google.com/ncr\"\n    sb.activate_cdp_mode(url)\n    sb.type('[name=\"q\"]', \"SeleniumBase GitHub page\")\n    sb.click('[value=\"Google Search\"]')\n    sb.sleep(2)\n    print(sb.get_page_title())\n```\n\n# (The browser exits automatically after the \"with\" block ends.)\n\n#########################################\n\"\"\"\nfrom contextlib import contextmanager, suppress\nfrom typing import Any, Generator\nfrom seleniumbase import BaseCase\n\n\n@contextmanager  # Usage: -> ``with SB() as sb:``\ndef SB(\n    test=None,  # Test Mode: Output, Logging, Continue on failure unless \"rtf\".\n    rtf=None,  # Shortcut / Duplicate of \"raise_test_failure\".\n    raise_test_failure=None,  # If \"test\" mode, raise Exception on 1st failure.\n    browser=None,  # Choose from \"chrome\", \"edge\", \"firefox\", or \"safari\".\n    headless=None,  # Use the default headless mode for Chromium and Firefox.\n    headless1=None,  # Use Chromium's old headless mode. (Fast, but limited)\n    headless2=None,  # Use Chromium's new headless mode. (Has more features)\n    locale_code=None,  # Set the Language Locale Code for the web browser.\n    protocol=None,  # The Selenium Grid protocol: \"http\" or \"https\".\n    servername=None,  # The Selenium Grid server/IP used for tests.\n    port=None,  # The Selenium Grid port used by the test server.\n    proxy=None,  # Use proxy. Format: \"SERVER:PORT\" or \"USER:PASS@SERVER:PORT\".\n    proxy_bypass_list=None,  # Skip proxy when using the listed domains.\n    proxy_pac_url=None,  # Use PAC file. (Format: URL or USERNAME:PASSWORD@URL)\n    multi_proxy=None,  # Allow multiple proxies with auth when multi-threaded.\n    agent=None,  # Modify the web browser's User-Agent string.\n    cap_file=None,  # The desired capabilities to use with a Selenium Grid.\n    cap_string=None,  # The desired capabilities to use with a Selenium Grid.\n    recorder_ext=None,  # Enables the SeleniumBase Recorder Chromium extension.\n    disable_cookies=None,  # Disable Cookies on websites. (Pages might break!)\n    disable_js=None,  # Disable JavaScript on websites. (Pages might break!)\n    disable_csp=None,  # Disable the Content Security Policy of websites.\n    enable_ws=None,  # Enable Web Security on Chromium-based browsers.\n    enable_sync=None,  # Enable \"Chrome Sync\" on websites.\n    use_auto_ext=None,  # Use Chrome's automation extension.\n    undetectable=None,  # Use undetected-chromedriver to evade bot-detection.\n    uc_cdp_events=None,  # Capture CDP events in undetected-chromedriver mode.\n    uc_subprocess=None,  # Use undetected-chromedriver as a subprocess.\n    log_cdp_events=None,  # Capture {\"performance\": \"ALL\", \"browser\": \"ALL\"}\n    incognito=None,  # Enable Chromium's Incognito mode.\n    guest_mode=None,  # Enable Chromium's Guest mode.\n    dark_mode=None,  # Enable Chromium's Dark mode.\n    devtools=None,  # Open Chromium's DevTools when the browser opens.\n    remote_debug=None,  # Enable Chrome's Debugger on \"http://localhost:9222\".\n    enable_3d_apis=None,  # Enable WebGL and 3D APIs.\n    swiftshader=None,  # Chrome: --use-gl=angle / --use-angle=swiftshader-webgl\n    ad_block_on=None,  # Block some types of display ads from loading.\n    host_resolver_rules=None,  # Set host-resolver-rules, comma-separated.\n    block_images=None,  # Block images from loading during tests.\n    do_not_track=None,  # Tell websites that you don't want to be tracked.\n    chromium_arg=None,  # \"ARG=N,ARG2\" (Set Chromium args, \",\"-separated.)\n    firefox_arg=None,  # \"ARG=N,ARG2\" (Set Firefox args, comma-separated.)\n    firefox_pref=None,  # SET (Set Firefox PREFERENCE:VALUE set, \",\"-separated)\n    user_data_dir=None,  # Set the Chrome user data directory to use.\n    extension_zip=None,  # Load a Chrome Extension .zip|.crx, comma-separated.\n    extension_dir=None,  # Load a Chrome Extension directory, comma-separated.\n    disable_features=None,  # \"F1,F2\" (Disable Chrome features, \",\"-separated.)\n    binary_location=None,  # Set path of the Chromium browser binary to use.\n    driver_version=None,  # Set the chromedriver or uc_driver version to use.\n    skip_js_waits=None,  # Skip JS Waits (readyState==\"complete\" and Angular).\n    wait_for_angularjs=None,  # Wait for AngularJS to load after some actions.\n    use_wire=None,  # Use selenium-wire's webdriver over selenium webdriver.\n    external_pdf=None,  # Set Chrome \"plugins.always_open_pdf_externally\":True.\n    window_position=None,  # Set the browser's starting window position: \"X,Y\"\n    window_size=None,  # Set the browser's starting window size: \"Width,Height\"\n    is_mobile=None,  # Use the mobile device emulator while running tests.\n    mobile=None,  # Shortcut / Duplicate of \"is_mobile\".\n    device_metrics=None,  # Set mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\"\n    xvfb=None,  # Run tests using the Xvfb virtual display server on Linux OS.\n    xvfb_metrics=None,  # Set Xvfb display size on Linux: \"Width,Height\".\n    start_page=None,  # The starting URL for the web browser when tests begin.\n    rec_print=None,  # If Recorder is enabled, prints output after tests end.\n    rec_behave=None,  # Like Recorder Mode, but also generates behave-gherkin.\n    record_sleep=None,  # If Recorder enabled, also records self.sleep calls.\n    data=None,  # Extra test data. Access with \"self.data\" in tests.\n    var1=None,  # Extra test data. Access with \"self.var1\" in tests.\n    var2=None,  # Extra test data. Access with \"self.var2\" in tests.\n    var3=None,  # Extra test data. Access with \"self.var3\" in tests.\n    variables=None,  # DICT (Extra test data. Access with \"self.variables\")\n    account=None,  # Set account. Access with \"self.account\" in tests.\n    environment=None,  # Set the test env. Access with \"self.env\" in tests.\n    headed=None,  # Run tests in headed/GUI mode on Linux, where not default.\n    maximize=None,  # Start tests with the browser window maximized.\n    disable_ws=None,  # Reverse of \"enable_ws\". (None and False are different)\n    disable_beforeunload=None,  # Disable the \"beforeunload\" event on Chromium.\n    settings_file=None,  # A file for overriding default SeleniumBase settings.\n    position=None,  # Shortcut / Duplicate of \"window_position\".\n    size=None,  # Shortcut / Duplicate of \"window_size\".\n    uc=None,  # Shortcut / Duplicate of \"undetectable\".\n    undetected=None,  # Shortcut / Duplicate of \"undetectable\".\n    uc_cdp=None,  # Shortcut / Duplicate of \"uc_cdp_events\".\n    uc_sub=None,  # Shortcut / Duplicate of \"uc_subprocess\".\n    locale=None,  # Shortcut / Duplicate of \"locale_code\".\n    log_cdp=None,  # Shortcut / Duplicate of \"log_cdp_events\".\n    ad_block=None,  # Shortcut / Duplicate of \"ad_block_on\".\n    server=None,  # Shortcut / Duplicate of \"servername\".\n    guest=None,  # Shortcut / Duplicate of \"guest_mode\".\n    wire=None,  # Shortcut / Duplicate of \"use_wire\".\n    pls=None,  # Shortcut / Duplicate of \"page_load_strategy\".\n    sjw=None,  # Shortcut / Duplicate of \"skip_js_waits\".\n    wfa=None,  # Shortcut / Duplicate of \"wait_for_angularjs\".\n    cft=None,  # Use \"Chrome for Testing\"\n    chs=None,  # Use \"Chrome-Headless-Shell\"\n    use_chromium=None,  # Use base \"Chromium\"\n    save_screenshot=None,  # Save a screenshot at the end of each test.\n    no_screenshot=None,  # No screenshots saved unless tests directly ask it.\n    page_load_strategy=None,  # Set Chrome PLS to \"normal\", \"eager\", or \"none\".\n    timeout_multiplier=None,  # Multiplies the default timeout values.\n    js_checking_on=None,  # Check for JavaScript errors after page loads.\n    slow=None,  # Slow down the automation. Faster than using Demo Mode.\n    demo=None,  # Slow down and visually see test actions as they occur.\n    demo_sleep=None,  # SECONDS (Set wait time after Slow & Demo Mode actions.)\n    message_duration=None,  # SECONDS (The time length for Messenger alerts.)\n    highlights=None,  # Number of highlight animations for Demo Mode actions.\n    interval=None,  # SECONDS (Autoplay interval for SB Slides & Tour steps.)\n    time_limit=None,  # SECONDS (Safely fail tests that exceed the time limit.)\n) -> Generator[BaseCase, Any, None]:\n    \"\"\"\n    * SeleniumBase as a Python Context Manager *\n\n    Example:\n    --------\n    .. code-block:: python\n        from seleniumbase import SB\n\n        with SB(uc=True, test=True) as sb:\n            url = \"https://google.com/ncr\"\n            sb.activate_cdp_mode(url)\n            sb.type('[name=\"q\"]', \"SeleniumBase GitHub page\")\n            sb.click('[value=\"Google Search\"]')\n            sb.sleep(2)\n            print(sb.get_page_title())\n\n    Optional Parameters:\n    --------------------\n    test (bool):  Test Mode: Output, Logging, Continue on failure unless \"rtf\".\n    rtf (bool):  Shortcut / Duplicate of \"raise_test_failure\".\n    raise_test_failure (bool):  If \"test\" mode, raise Exception on 1st failure.\n    browser (str):  Choose from \"chrome\", \"edge\", \"firefox\", or \"safari\".\n    headless (bool):  Use the default headless mode for Chromium and Firefox.\n    headless1 (bool):  Use Chromium's old headless mode. (Fast, but limited)\n    headless2 (bool):  Use Chromium's new headless mode. (Has more features)\n    locale_code (str):  Set the Language Locale Code for the web browser.\n    protocol (str):  The Selenium Grid protocol: \"http\" or \"https\".\n    servername (str):  The Selenium Grid server/IP used for tests.\n    port (int):  The Selenium Grid port used by the test server.\n    proxy (str):  Use proxy. Format: \"SERVER:PORT\" or \"USER:PASS@SERVER:PORT\".\n    proxy_bypass_list (str):  Skip proxy when using the listed domains.\n    proxy_pac_url (str):  Use PAC file. (Format: URL or USERNAME:PASSWORD@URL)\n    multi_proxy (bool):  # Allow multiple proxies with auth when multithreaded.\n    agent (str):  Modify the web browser's User-Agent string.\n    cap_file (str):  The desired capabilities to use with a Selenium Grid.\n    cap_string (str):  The desired capabilities to use with a Selenium Grid.\n    recorder_ext (bool):  Enables the SeleniumBase Recorder Chromium extension.\n    disable_cookies (bool):  Disable Cookies on websites. (Pages might break!)\n    disable_js (bool):  Disable JavaScript on websites. (Pages might break!)\n    disable_csp (bool):  Disable the Content Security Policy of websites.\n    enable_ws (bool):  Enable Web Security on Chromium-based browsers.\n    enable_sync (bool):  Enable \"Chrome Sync\" on websites.\n    use_auto_ext (bool):  Use Chrome's automation extension.\n    undetectable (bool):  Use undetected-chromedriver to evade bot-detection.\n    uc_cdp_events (bool):  Capture CDP events in undetected-chromedriver mode.\n    uc_subprocess (bool):  Use undetected-chromedriver as a subprocess.\n    log_cdp_events (bool):  Capture {\"performance\": \"ALL\", \"browser\": \"ALL\"}\n    incognito (bool):  Enable Chromium's Incognito mode.\n    guest_mode (bool):  Enable Chromium's Guest mode.\n    dark_mode (bool):  Enable Chromium's Dark mode.\n    devtools (bool):  Open Chromium's DevTools when the browser opens.\n    remote_debug (bool):  Enable Chrome's Debugger on \"http://localhost:9222\".\n    enable_3d_apis (bool):  Enable WebGL and 3D APIs.\n    swiftshader (bool):  Chrome: --use-gl=angle / --use-angle=swiftshader-webgl\n    ad_block_on (bool):  Block some types of display ads from loading.\n    host_resolver_rules (str):  Set host-resolver-rules, comma-separated.\n    block_images (bool):  Block images from loading during tests.\n    do_not_track (bool):  Tell websites that you don't want to be tracked.\n    chromium_arg (str):  \"ARG=N,ARG2\" (Set Chromium args, \",\"-separated.)\n    firefox_arg (str):  \"ARG=N,ARG2\" (Set Firefox args, comma-separated.)\n    firefox_pref (str):  SET (Set Firefox PREFERENCE:VALUE set, \",\"-separated)\n    user_data_dir (str):  Set the Chrome user data directory to use.\n    extension_zip (str):  Load a Chrome Extension .zip|.crx, comma-separated.\n    extension_dir (str):  Load a Chrome Extension directory, comma-separated.\n    disable_features (str):  \"F1,F2\" (Disable Chrome features, \",\"-separated.)\n    binary_location (str):  Set path of the Chromium browser binary to use.\n    driver_version (str):  Set the chromedriver or uc_driver version to use.\n    skip_js_waits (bool):  Skip JS Waits (readyState==\"complete\" and Angular).\n    wait_for_angularjs (bool):  Wait for AngularJS to load after some actions.\n    use_wire (bool):  Use selenium-wire's webdriver over selenium webdriver.\n    external_pdf (bool):  Set Chrome \"plugins.always_open_pdf_externally\":True.\n    window_position (x,y):  Set the browser's starting window position: \"X,Y\"\n    window_size (w,h):  Set the browser's starting window size: \"Width,Height\"\n    is_mobile (bool):  Use the mobile device emulator while running tests.\n    mobile (bool):  Shortcut / Duplicate of \"is_mobile\".\n    device_metrics (w,h,pr):  Mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\"\n    xvfb (bool):  Run tests using the Xvfb virtual display server on Linux OS.\n    xvfb_metrics (w,h):  Set Xvfb display size on Linux: \"Width,Height\".\n    start_page (str):  The starting URL for the web browser when tests begin.\n    rec_print (bool):  If Recorder is enabled, prints output after tests end.\n    rec_behave (bool):  Like Recorder Mode, but also generates behave-gherkin.\n    record_sleep (bool):  If Recorder enabled, also records self.sleep calls.\n    data (str):  Extra test data. Access with \"self.data\" in tests.\n    var1 (str):  Extra test data. Access with \"self.var1\" in tests.\n    var2 (str):  Extra test data. Access with \"self.var2\" in tests.\n    var3 (str):  Extra test data. Access with \"self.var3\" in tests.\n    variables (dict):  Extra test data. Access with \"self.variables\".\n    account (str):  Set account. Access with \"self.account\" in tests.\n    environment (str):  Set the test env. Access with \"self.env\" in tests.\n    headed (bool):  Run tests in headed/GUI mode on Linux, where not default.\n    maximize (bool):  Start tests with the browser window maximized.\n    disable_ws (bool):  Reverse of \"enable_ws\". (None and False are different)\n    disable_beforeunload (bool):  Disable the \"beforeunload\" event on Chromium.\n    settings_file (str):  A file for overriding default SeleniumBase settings.\n    position (x,y):  Shortcut / Duplicate of \"window_position\".\n    size (w,h):  Shortcut / Duplicate of \"window_size\".\n    uc (bool):  Shortcut / Duplicate of \"undetectable\".\n    undetected (bool):  Shortcut / Duplicate of \"undetectable\".\n    uc_cdp (bool):  Shortcut / Duplicate of \"uc_cdp_events\".\n    uc_sub (bool):  Shortcut / Duplicate of \"uc_subprocess\".\n    locale (str):  Shortcut / Duplicate of \"locale_code\".\n    log_cdp (bool):  Shortcut / Duplicate of \"log_cdp_events\".\n    ad_block (bool):  Shortcut / Duplicate of \"ad_block_on\".\n    server (str):  Shortcut / Duplicate of \"servername\".\n    guest (bool):  Shortcut / Duplicate of \"guest_mode\".\n    wire (bool):  Shortcut / Duplicate of \"use_wire\".\n    pls (str):  Shortcut / Duplicate of \"page_load_strategy\".\n    sjw (bool):  Shortcut / Duplicate of \"skip_js_waits\".\n    wfa (bool):  Shortcut / Duplicate of \"wait_for_angularjs\".\n    save_screenshot (bool):  Save a screenshot at the end of each test.\n    no_screenshot (bool):  No screenshots saved unless tests directly ask it.\n    page_load_strategy (str):  Set Chrome PLS to \"normal\", \"eager\", or \"none\".\n    timeout_multiplier (float):  Multiplies the default timeout values.\n    js_checking_on (bool):  Check for JavaScript errors after page loads.\n    slow (bool):  Slow down the automation. Faster than using Demo Mode.\n    demo (bool):  Slow down and visually see test actions as they occur.\n    demo_sleep (float):  SECONDS (Set wait time after Slow & Demo Mode actions)\n    message_duration (float):  SECONDS (The time length for Messenger alerts.)\n    highlights (int):  Number of highlight animations for Demo Mode actions.\n    interval (float):  SECONDS (Autoplay interval for SB Slides & Tour steps.)\n    time_limit (float):  SECONDS (Safely fail tests that exceed the time limit)\n    \"\"\"\n    import colorama\n    import gc\n    import os\n    import sys\n    import time\n    import traceback\n    from seleniumbase import config as sb_config\n    from seleniumbase.config import settings\n    from seleniumbase.core import detect_b_ver\n    from seleniumbase.fixtures import constants\n    from seleniumbase.fixtures import shared_utils\n\n    sb_config_backup = sb_config\n    sb_config._do_sb_post_mortem = False\n    sys_argv = sys.argv\n    arg_join = \" \".join(sys_argv)\n    archive_logs = False\n    existing_runner = False\n    collect_only = (\"--co\" in sys_argv or \"--collect-only\" in sys_argv)\n    all_scripts = (hasattr(sb_config, \"all_scripts\") and sb_config.all_scripts)\n    do_log_folder_setup = False  # The first \"test=True\" run does it\n    inner_test = False\n    if (\n        (hasattr(sb_config, \"is_behave\") and sb_config.is_behave)\n        or (hasattr(sb_config, \"is_pytest\") and sb_config.is_pytest)\n        or (hasattr(sb_config, \"is_nosetest\") and sb_config.is_nosetest)\n    ):\n        existing_runner = True\n        if test:\n            inner_test = True\n        test = False  # Already using a test runner. Skip extra test steps.\n    elif test is None and \"--test\" in sys_argv:\n        test = True\n    if existing_runner and not hasattr(sb_config, \"_context_of_runner\"):\n        if hasattr(sb_config, \"is_pytest\") and sb_config.is_pytest:\n            import pytest\n            msg = \"Skipping `SB()` script. (Use `python`, not `pytest`)\"\n            if not collect_only and not all_scripts:\n                print(\"\\n  *** %s\" % msg)\n            if collect_only or not all_scripts:\n                pytest.skip(allow_module_level=True)\n        elif hasattr(sb_config, \"is_nosetest\") and sb_config.is_nosetest:\n            raise Exception(\n                \"\\n  SB Manager script was triggered by nosetest collection!\"\n                '\\n  (Prevent that by using: ``if __name__ == \"__main__\":``)'\n            )\n    elif existing_runner:\n        sb_config._context_of_runner = True\n    if (\n        not existing_runner\n        and not hasattr(sb_config, \"_has_older_context\")\n        and test\n    ):\n        # This is the first \"test\" from context manager scripts run.\n        sb_config._has_older_context = True\n        do_log_folder_setup = True\n    else:\n        if test:\n            pass  # Not the first \"test\" of context manager scripts.\n        else:\n            pass  # Not in \"test\" mode. (No special output/logging.)\n    with_testing_base = False\n    if test:\n        with_testing_base = True\n    if (\n        raise_test_failure\n        or rtf\n        or \"--raise-test-failure\" in sys_argv\n        or \"--raise_test_failure\" in sys_argv\n        or \"--rtf\" in sys_argv\n        or \"-x\" in sys_argv  # Carry-over from \"pytest\"\n        or \"--exitfirst\" in sys_argv  # Carry-over from \"pytest\"\n    ):\n        raise_test_failure = True  # Exit on first error or failed test.\n    else:\n        raise_test_failure = False\n    sb_config._browser_shortcut = None\n    sb_config._cdp_browser = None\n    sb_config._cdp_bin_loc = None\n    browser_changes = 0\n    browser_set = None\n    browser_text = None\n    browser_list = []\n    # Check if binary-location in options\n    bin_loc_in_options = False\n    if (\n        binary_location\n        and len(str(binary_location)) > 5\n        and os.path.exists(str(binary_location))\n    ):\n        bin_loc_in_options = True\n    else:\n        for arg in sys_argv:\n            if arg in [\"--binary-location\", \"--binary_location\", \"--bl\"]:\n                bin_loc_in_options = True\n    if (\n        browser\n        and browser in constants.ChromiumSubs.chromium_subs\n        and not bin_loc_in_options\n    ):\n        bin_loc = detect_b_ver.get_binary_location(browser)\n        if bin_loc and os.path.exists(bin_loc):\n            if browser in bin_loc.lower().split(\"/\")[-1].split(\"\\\\\")[-1]:\n                sb_config._cdp_browser = browser\n                sb_config._cdp_bin_loc = bin_loc\n                binary_location = bin_loc\n                bin_loc_in_options = True\n    # As a shortcut, you can use \"--edge\" instead of \"--browser=edge\", etc,\n    # but you can only specify one default browser for tests. (Default: chrome)\n    if \"--browser=chrome\" in sys_argv or \"--browser chrome\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"chrome\"\n        browser_list.append(\"--browser=chrome\")\n    if \"--browser=edge\" in sys_argv or \"--browser edge\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"edge\"\n        browser_list.append(\"--browser=edge\")\n    if \"--browser=firefox\" in sys_argv or \"--browser firefox\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"firefox\"\n        browser_list.append(\"--browser=firefox\")\n    if \"--browser=safari\" in sys_argv or \"--browser safari\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"safari\"\n        browser_list.append(\"--browser=safari\")\n    if \"--browser=ie\" in sys_argv or \"--browser ie\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"ie\"\n        browser_list.append(\"--browser=ie\")\n    if \"--browser=remote\" in sys_argv or \"--browser remote\" in sys_argv:\n        browser_changes += 1\n        browser_set = \"remote\"\n        browser_list.append(\"--browser=remote\")\n    if \"--browser=opera\" in sys_argv or \"--browser opera\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"opera\"\n                sb_config._browser_shortcut = \"opera\"\n                sb_config._cdp_browser = \"opera\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=opera\")\n    if \"--browser=brave\" in sys_argv or \"--browser brave\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"brave\"\n                sb_config._browser_shortcut = \"brave\"\n                sb_config._cdp_browser = \"brave\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=brave\")\n    if \"--browser=comet\" in sys_argv or \"--browser comet\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"comet\"\n                sb_config._browser_shortcut = \"comet\"\n                sb_config._cdp_browser = \"comet\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=comet\")\n    if \"--browser=atlas\" in sys_argv or \"--browser atlas\" in sys_argv:\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_set = \"atlas\"\n                sb_config._browser_shortcut = \"atlas\"\n                sb_config._cdp_browser = \"atlas\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--browser=atlas\")\n    browser_text = browser_set\n    if \"--chrome\" in sys_argv and not browser_set == \"chrome\":\n        browser_changes += 1\n        browser_text = \"chrome\"\n        sb_config._browser_shortcut = \"chrome\"\n        browser_list.append(\"--chrome\")\n    if \"--edge\" in sys_argv and not browser_set == \"edge\":\n        browser_changes += 1\n        browser_text = \"edge\"\n        sb_config._browser_shortcut = \"edge\"\n        browser_list.append(\"--edge\")\n    if \"--firefox\" in sys_argv and not browser_set == \"firefox\":\n        browser_changes += 1\n        browser_text = \"firefox\"\n        sb_config._browser_shortcut = \"firefox\"\n        browser_list.append(\"--firefox\")\n    if \"--ie\" in sys_argv and not browser_set == \"ie\":\n        browser_changes += 1\n        browser_text = \"ie\"\n        sb_config._browser_shortcut = \"ie\"\n        browser_list.append(\"--ie\")\n    if \"--safari\" in sys_argv and not browser_set == \"safari\":\n        browser_changes += 1\n        browser_text = \"safari\"\n        sb_config._browser_shortcut = \"safari\"\n        browser_list.append(\"--safari\")\n    if \"--opera\" in sys_argv and not browser_set == \"opera\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"opera\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"opera\"\n                sb_config._browser_shortcut = \"opera\"\n                sb_config._cdp_browser = \"opera\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--opera\")\n    if \"--brave\" in sys_argv and not browser_set == \"brave\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"brave\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"brave\"\n                sb_config._browser_shortcut = \"brave\"\n                sb_config._cdp_browser = \"brave\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--brave\")\n    if \"--comet\" in sys_argv and not browser_set == \"comet\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"comet\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"comet\"\n                sb_config._browser_shortcut = \"comet\"\n                sb_config._cdp_browser = \"comet\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--comet\")\n    if \"--atlas\" in sys_argv and not browser_set == \"atlas\":\n        if not bin_loc_in_options:\n            bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n            if os.path.exists(bin_loc):\n                browser_changes += 1\n                browser_text = \"atlas\"\n                sb_config._browser_shortcut = \"atlas\"\n                sb_config._cdp_browser = \"atlas\"\n                sb_config._cdp_bin_loc = bin_loc\n                browser_list.append(\"--atlas\")\n    if browser_changes > 1:\n        message = \"\\n\\n  TOO MANY browser types were entered!\"\n        message += \"\\n  There were %s found:\\n  >  %s\" % (\n            browser_changes,\n            \", \".join(browser_list),\n        )\n        message += \"\\n  ONLY ONE default browser is allowed!\"\n        message += \"\\n  Select a single browser & try again!\\n\"\n        if not browser:\n            raise Exception(message)\n    if browser is None:\n        if browser_text:\n            browser = browser_text\n        else:\n            browser = \"chrome\"\n    else:\n        browser = browser.lower()\n    valid_browsers = constants.ValidBrowsers.valid_browsers\n    if browser not in valid_browsers:\n        raise Exception(\n            \"Browser: {%s} is not a valid browser option. \"\n            \"Valid options = {%s}\" % (browser, valid_browsers)\n        )\n    if headless is None:\n        if \"--headless\" in sys_argv:\n            headless = True\n        else:\n            headless = False\n    if headless1 is None:\n        if \"--headless1\" in sys_argv:\n            headless1 = True\n        else:\n            headless1 = False\n    if headless1:\n        headless = True\n    if headless2 is None:\n        if \"--headless2\" in sys_argv:\n            headless2 = True\n        else:\n            headless2 = False\n    if protocol is None:\n        protocol = \"http\"  # For the Selenium Grid only!\n    if server is not None and servername is None:\n        servername = server\n    if servername is None:\n        servername = \"localhost\"  # For the Selenium Grid only!\n    if port is None:\n        port = \"4444\"  # For the Selenium Grid only!\n    if not environment:\n        environment = \"test\"\n    if incognito is None:\n        if \"--incognito\" in sys_argv:\n            incognito = True\n        else:\n            incognito = False\n    if guest is not None and guest_mode is None:\n        guest_mode = guest\n    if guest_mode is None:\n        if \"--guest\" in sys_argv:\n            guest_mode = True\n        else:\n            guest_mode = False\n    if dark_mode is None:\n        if \"--dark\" in sys_argv:\n            dark_mode = True\n        else:\n            dark_mode = False\n    if devtools is None:\n        if \"--devtools\" in sys_argv:\n            devtools = True\n        else:\n            devtools = False\n    if mobile is not None and is_mobile is None:\n        is_mobile = mobile\n    if is_mobile is None:\n        if \"--mobile\" in sys_argv:\n            is_mobile = True\n        else:\n            is_mobile = False\n    if is_mobile:\n        sb_config.mobile_emulator = True\n    proxy_string = proxy\n    if proxy_string is None and \"--proxy\" in arg_join:\n        if \"--proxy=\" in arg_join:\n            proxy_string = arg_join.split(\"--proxy=\")[1].split(\" \")[0]\n        elif \"--proxy \" in arg_join:\n            proxy_string = arg_join.split(\"--proxy \")[1].split(\" \")[0]\n        if proxy_string:\n            if proxy_string.startswith('\"') and proxy_string.endswith('\"'):\n                proxy_string = proxy_string[1:-1]\n            elif proxy_string.startswith(\"'\") and proxy_string.endswith(\"'\"):\n                proxy_string = proxy_string[1:-1]\n    c_a = chromium_arg\n    if c_a is None and \"--chromium-arg\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--chromium-arg=\"):\n                c_a = arg.split(\"--chromium-arg=\")[1]\n                break\n            elif arg == \"--chromium-arg\" and len(sys_argv) > count + 1:\n                c_a = sys_argv[count + 1]\n                if c_a.startswith(\"-\"):\n                    c_a = None\n                break\n            count += 1\n    chromium_arg = c_a\n    d_f = disable_features\n    if d_f is None and \"--disable-features\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--disable-features=\"):\n                d_f = arg.split(\"--disable-features=\")[1]\n                break\n            elif arg == \"--disable-features\" and len(sys_argv) > count + 1:\n                d_f = sys_argv[count + 1]\n                if d_f.startswith(\"-\"):\n                    d_f = None\n                break\n            count += 1\n    disable_features = d_f\n    if window_position is None and position is not None:\n        window_position = position\n    w_p = window_position\n    if (\n        w_p is None\n        and (\"--window-position\" in arg_join or \"--position\" in arg_join)\n    ):\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--window-position=\"):\n                w_p = arg.split(\"--window-position=\")[1]\n                break\n            elif arg == \"--window-position\" and len(sys_argv) > count + 1:\n                w_p = sys_argv[count + 1]\n                if w_p.startswith(\"-\"):\n                    w_p = None\n                break\n            count += 1\n    window_position = w_p\n    if window_size is None and size is not None:\n        window_size = size\n    w_s = window_size\n    if w_s is None and \"--window-size\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--window-size=\"):\n                w_s = arg.split(\"--window-size=\")[1]\n                break\n            elif arg == \"--window-size\" and len(sys_argv) > count + 1:\n                w_s = sys_argv[count + 1]\n                if w_s.startswith(\"-\"):\n                    w_s = None\n                break\n            count += 1\n    window_size = w_s\n    x_m = xvfb_metrics\n    if x_m is None and \"--xvfb-metrics\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--xvfb-metrics=\"):\n                x_m = arg.split(\"--xvfb-metrics=\")[1]\n                break\n            elif arg == \"--xvfb-metrics\" and len(sys_argv) > count + 1:\n                x_m = sys_argv[count + 1]\n                if x_m.startswith(\"-\"):\n                    x_m = None\n                break\n            count += 1\n    xvfb_metrics = x_m\n    if agent is None and \"--agent\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--agent=\"):\n                agent = arg.split(\"--agent=\")[1]\n                break\n            elif arg == \"--agent\" and len(sys_argv) > count + 1:\n                agent = sys_argv[count + 1]\n                if agent.startswith(\"-\"):\n                    agent = None\n                break\n            count += 1\n    user_agent = agent\n    found_bl = None\n    if binary_location is None and \"--binary-location\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--binary-location=\"):\n                found_bl = arg.split(\"--binary-location=\")[1]\n                break\n            elif arg == \"--binary-location\" and len(sys_argv) > count + 1:\n                found_bl = sys_argv[count + 1]\n                if found_bl.startswith(\"-\"):\n                    found_bl = None\n                break\n            count += 1\n        if found_bl:\n            binary_location = found_bl\n    if binary_location is None and \"--bl=\" in arg_join:\n        for arg in sys_argv:\n            if arg.startswith(\"--bl=\"):\n                binary_location = arg.split(\"--bl=\")[1]\n                break\n    if use_chromium and not binary_location:\n        binary_location = \"_chromium_\"\n    elif cft and not binary_location:\n        binary_location = \"cft\"\n    elif chs and not binary_location:\n        binary_location = \"chs\"\n    if \"--use-chromium\" in sys_argv and not binary_location:\n        binary_location = \"_chromium_\"\n    elif \"--cft\" in sys_argv and not binary_location:\n        binary_location = \"cft\"\n    elif \"--chs\" in sys_argv and not binary_location:\n        binary_location = \"chs\"\n    if (\n        binary_location\n        and binary_location.lower() == \"chs\"\n        and browser == \"chrome\"\n    ):\n        headless = True\n        headless1 = False\n        headless2 = False\n    recorder_mode = False\n    if recorder_ext:\n        recorder_mode = True\n    if (\n        \"--recorder\" in sys_argv\n        or \"--record\" in sys_argv\n        or \"--rec\" in sys_argv\n    ):\n        recorder_mode = True\n        recorder_ext = True\n    if rec_print is None:\n        if \"--rec-print\" in sys_argv:\n            rec_print = True\n        else:\n            rec_print = False\n    if rec_behave is None:\n        if \"--rec-behave\" in sys_argv:\n            rec_behave = True\n        else:\n            rec_behave = False\n    if record_sleep is None:\n        if \"--rec-sleep\" in sys_argv or \"--record-sleep\" in sys_argv:\n            record_sleep = True\n        else:\n            record_sleep = False\n    if xvfb is None:\n        if \"--xvfb\" in sys_argv:\n            xvfb = True\n        else:\n            xvfb = False\n    if not shared_utils.is_linux():\n        # The Xvfb virtual display server is for Linux OS Only!\n        xvfb = False\n    if (\n        undetectable\n        or undetected\n        or uc\n        or uc_cdp_events\n        or uc_cdp\n        or uc_subprocess\n        or uc_sub\n    ):\n        undetectable = True\n    if undetectable or undetected or uc:\n        uc_subprocess = True  # Use UC as a subprocess by default.\n    elif (\n        \"--undetectable\" in sys_argv\n        or \"--undetected\" in sys_argv\n        or \"--uc\" in sys_argv\n        or \"--uc-cdp-events\" in sys_argv\n        or \"--uc_cdp_events\" in sys_argv\n        or \"--uc-cdp\" in sys_argv\n        or \"--uc-subprocess\" in sys_argv\n        or \"--uc_subprocess\" in sys_argv\n        or \"--uc-sub\" in sys_argv\n    ):\n        undetectable = True\n        if uc_subprocess is None and uc_sub is None:\n            uc_subprocess = True  # Use UC as a subprocess by default.\n    else:\n        undetectable = False\n    if uc_subprocess or uc_sub:\n        uc_subprocess = True\n    elif (\n        \"--uc-subprocess\" in sys_argv\n        or \"--uc_subprocess\" in sys_argv\n        or \"--uc-sub\" in sys_argv\n    ):\n        uc_subprocess = True\n    else:\n        uc_subprocess = False\n    if uc_cdp_events or uc_cdp:\n        undetectable = True\n        uc_cdp_events = True\n    elif (\n        \"--uc-cdp-events\" in sys_argv\n        or \"--uc_cdp_events\" in sys_argv\n        or \"--uc-cdp\" in sys_argv\n        or \"--uc_cdp\" in sys_argv\n    ):\n        undetectable = True\n        uc_cdp_events = True\n    else:\n        uc_cdp_events = False\n    if (\n        undetectable\n        and browser not in [\"chrome\", \"opera\", \"brave\", \"comet\", \"atlas\"]\n    ):\n        message = (\n            '\\n  Undetected-Chromedriver Mode ONLY supports Chromium browsers!'\n            '\\n  (\"uc=True\" / \"undetectable=True\" / \"--uc\")'\n            '\\n  (Your browser choice was: \"%s\".)'\n            '\\n  (Will use \"%s\" without UC Mode.)\\n' % (browser, browser)\n        )\n        print(message)\n    if headed is None:\n        # Override the default headless mode on Linux if set.\n        if \"--gui\" in sys_argv or \"--headed\" in sys_argv:\n            headed = True\n        else:\n            headed = False\n    if (\n        shared_utils.is_linux()\n        and not headed\n        and not headless\n        and not headless2\n        and not xvfb\n    ):\n        if not undetectable:\n            headless = True\n        else:\n            xvfb = True\n    if headless2 and browser == \"firefox\":\n        headless2 = False  # Only for Chromium browsers\n        headless = True  # Firefox has regular headless\n    elif browser not in [\"chrome\", \"edge\", \"opera\", \"brave\", \"comet\", \"atlas\"]:\n        headless2 = False  # Only for Chromium browsers\n    if not headless and not headless2:\n        headed = True\n    if rec_print and not recorder_mode:\n        recorder_mode = True\n        recorder_ext = True\n    elif rec_behave and not recorder_mode:\n        recorder_mode = True\n        recorder_ext = True\n    elif record_sleep and not recorder_mode:\n        recorder_mode = True\n        recorder_ext = True\n    if recorder_mode and headless:\n        headless = False\n        headless1 = False\n        headless2 = True\n    sb_config.proxy_driver = False\n    if \"--proxy-driver\" in sys_argv or \"--proxy_driver\" in sys_argv:\n        sb_config.proxy_driver = True\n    if variables and isinstance(variables, str) and len(variables) > 0:\n        import ast\n        bad_input = False\n        if (\n            not variables.startswith(\"{\")\n            or not variables.endswith(\"}\")\n        ):\n            bad_input = True\n        else:\n            try:\n                variables = ast.literal_eval(variables)\n                if not isinstance(variables, dict):\n                    bad_input = True\n            except Exception:\n                bad_input = True\n        if bad_input:\n            raise Exception(\n                '\\nExpecting a Python dictionary for \"variables\"!'\n                \"\\nEg. --variables=\\\"{'KEY1':'VALUE', 'KEY2':123}\\\"\"\n            )\n    elif not isinstance(variables, dict):\n        variables = {}\n    if disable_csp is None:\n        if (\n            \"--disable-csp\" in sys_argv\n            or \"--no-csp\" in sys_argv\n            or \"--dcsp\" in sys_argv\n        ):\n            disable_csp = True\n        else:\n            disable_csp = False\n    if (\n        (enable_ws is None and disable_ws is None)\n        and (\n            \"--disable-web-security\" in sys_argv\n            or \"--disable-ws\" in sys_argv\n            or \"--dws\" in sys_argv\n        )\n    ):\n        enable_ws = False\n        disable_ws = True\n    elif (\n        (enable_ws is None and disable_ws is None)\n        or (disable_ws is not None and not disable_ws)\n        or (enable_ws is not None and enable_ws)\n    ):\n        enable_ws = True\n        disable_ws = False\n    else:\n        enable_ws = False\n        disable_ws = True\n    if log_cdp_events is None and log_cdp is None:\n        if (\n            \"--log-cdp-events\" in sys_argv\n            or \"--log_cdp_events\" in sys_argv\n            or \"--log-cdp\" in sys_argv\n            or \"--log_cdp\" in sys_argv\n        ):\n            log_cdp_events = True\n        else:\n            log_cdp_events = False\n    elif log_cdp_events or log_cdp:\n        log_cdp_events = True\n    else:\n        log_cdp_events = False\n    if use_auto_ext is None:\n        if \"--use-auto-ext\" in sys_argv:\n            use_auto_ext = True\n        else:\n            use_auto_ext = False\n    if disable_cookies is None:\n        if \"--disable-cookies\" in sys_argv:\n            disable_cookies = True\n        else:\n            disable_cookies = False\n    if disable_js is None:\n        if \"--disable-js\" in sys_argv:\n            disable_js = True\n        else:\n            disable_js = False\n    maximize_option = False\n    if maximize or \"--maximize\" in sys_argv:\n        maximize_option = True\n    _disable_beforeunload = False\n    if disable_beforeunload:\n        _disable_beforeunload = True\n    if pls is not None and page_load_strategy is None:\n        page_load_strategy = pls\n    if not page_load_strategy and \"--pls=\" in arg_join:\n        if \"--pls=none\" in sys_argv or '--pls=\"none\"' in sys_argv:\n            page_load_strategy = \"none\"\n        elif \"--pls=eager\" in sys_argv or '--pls=\"eager\"' in sys_argv:\n            page_load_strategy = \"eager\"\n        elif \"--pls=normal\" in sys_argv or '--pls=\"normal\"' in sys_argv:\n            page_load_strategy = \"normal\"\n    if page_load_strategy is not None:\n        if page_load_strategy.lower() not in [\"normal\", \"eager\", \"none\"]:\n            raise Exception(\n                'page_load_strategy must be \"normal\", \"eager\", or \"none\"!'\n            )\n        page_load_strategy = page_load_strategy.lower()\n    elif \"--pls=normal\" in sys_argv or '--pls=\"normal\"' in sys_argv:\n        page_load_strategy = \"normal\"\n    elif \"--pls=eager\" in sys_argv or '--pls=\"eager\"' in sys_argv:\n        page_load_strategy = \"eager\"\n    elif \"--pls=none\" in sys_argv or '--pls=\"none\"' in sys_argv:\n        page_load_strategy = \"none\"\n    if sjw is not None and skip_js_waits is None:\n        skip_js_waits = sjw\n    if skip_js_waits is None:\n        if (\n            \"--sjw\" in sys_argv\n            or \"--skip_js_waits\" in sys_argv\n            or \"--skip-js-waits\" in sys_argv\n        ):\n            settings.SKIP_JS_WAITS = True\n    elif skip_js_waits:\n        settings.SKIP_JS_WAITS = skip_js_waits\n    if wfa is not None and wait_for_angularjs is None:\n        wait_for_angularjs = wfa\n    if wait_for_angularjs is None:\n        if (\n            \"--wfa\" in sys_argv\n            or \"--wait_for_angularjs\" in sys_argv\n            or \"--wait-for-angularjs\" in sys_argv\n        ):\n            settings.WAIT_FOR_ANGULARJS = True\n    elif wait_for_angularjs:\n        settings.WAIT_FOR_ANGULARJS = wait_for_angularjs\n    if save_screenshot is None:\n        if (\n            \"--screenshot\" in sys_argv\n            or \"--save-screenshot\" in sys_argv\n            or \"--ss\" in sys_argv\n        ):\n            save_screenshot = True\n        else:\n            save_screenshot = False\n    if no_screenshot is None:\n        if \"--no-screenshot\" in sys_argv or \"--ns\" in sys_argv:\n            no_screenshot = True\n        else:\n            no_screenshot = False\n    if save_screenshot and no_screenshot:\n        save_screenshot = False  # \"no_screenshot\" has priority\n    if browser == \"safari\" and headless:\n        headless = False  # Safari doesn't support headless mode\n        headless1 = False\n    if js_checking_on is None:\n        if \"--check-js\" in sys_argv:\n            js_checking_on = True\n        else:\n            js_checking_on = False\n    slow_mode = False\n    if slow:\n        slow_mode = True\n    elif \"--slow\" in sys_argv:\n        slow_mode = True\n    demo_mode = False\n    if demo:\n        demo_mode = True\n    elif \"--demo\" in sys_argv:\n        demo_mode = True\n    if block_images is None:\n        if \"--block-images\" in sys_argv or \"--block_images\" in sys_argv:\n            block_images = True\n        else:\n            block_images = False\n    if do_not_track is None:\n        if \"--do-not-track\" in sys_argv or \"--do_not_track\" in sys_argv:\n            do_not_track = True\n        else:\n            do_not_track = False\n    if use_wire is None and wire is None:\n        if \"--wire\" in sys_argv:\n            use_wire = True\n        else:\n            use_wire = False\n    elif use_wire or wire:\n        use_wire = True\n    else:\n        use_wire = False\n    if external_pdf is None:\n        if \"--external-pdf\" in sys_argv or \"--external_pdf\" in sys_argv:\n            external_pdf = True\n        else:\n            external_pdf = False\n    if remote_debug is None:\n        if \"--remote-debug\" in sys_argv or \"--remote_debug\" in sys_argv:\n            remote_debug = True\n        else:\n            remote_debug = False\n    if enable_3d_apis is None:\n        if \"--enable-3d-apis\" in sys_argv or \"--enable_3d_apis\" in sys_argv:\n            enable_3d_apis = True\n        else:\n            enable_3d_apis = False\n    if swiftshader is None:\n        if \"--swiftshader\" in sys_argv:\n            swiftshader = True\n        else:\n            swiftshader = False\n    if locale is not None and locale_code is None:\n        locale_code = locale\n    if locale_code is None:\n        if '--locale=\"' in arg_join:\n            locale_code = (\n                arg_join.split('--locale=\"')[1].split('\"')[0]\n            )\n        elif '--locale=' in arg_join:\n            locale_code = (\n                arg_join.split('--locale=')[1].split(' ')[0]\n            )\n        elif '--locale-code=\"' in arg_join:\n            locale_code = (\n                arg_join.split('--locale-code=\"')[1].split('\"')[0]\n            )\n        elif '--locale-code=' in arg_join:\n            locale_code = (\n                arg_join.split('--locale-code=')[1].split(' ')[0]\n            )\n    if ad_block is not None and ad_block_on is None:\n        ad_block_on = ad_block\n    if ad_block_on is None:\n        if \"--ad-block\" in sys_argv or \"--ad_block\" in sys_argv:\n            ad_block_on = True\n        else:\n            ad_block_on = False\n    if host_resolver_rules is None:\n        if '--host-resolver-rules=\"' in arg_join:\n            host_resolver_rules = (\n                arg_join.split('--host-resolver-rules=\"')[1].split('\"')[0]\n            )\n        elif '--host_resolver_rules=\"' in arg_join:\n            host_resolver_rules = (\n                arg_join.split(\"--host_resolver_rules=\")[1].split('\"')[0]\n            )\n    if driver_version is None and \"--driver-version\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--driver-version=\"):\n                driver_version = arg.split(\"--driver-version=\")[1]\n                break\n            elif arg == \"--driver-version\" and len(sys_argv) > count + 1:\n                driver_version = sys_argv[count + 1]\n                if driver_version.startswith(\"-\"):\n                    driver_version = None\n                break\n            count += 1\n    if driver_version is None and \"--driver_version\" in arg_join:\n        count = 0\n        for arg in sys_argv:\n            if arg.startswith(\"--driver_version=\"):\n                driver_version = arg.split(\"--driver_version=\")[1]\n                break\n            elif arg == \"--driver_version\" and len(sys_argv) > count + 1:\n                driver_version = sys_argv[count + 1]\n                if driver_version.startswith(\"-\"):\n                    driver_version = None\n                break\n            count += 1\n    if highlights is not None:\n        try:\n            highlights = int(highlights)\n        except Exception:\n            raise Exception('\"highlights\" must be an integer!')\n    if interval is not None:\n        try:\n            interval = float(interval)\n        except Exception:\n            raise Exception('\"interval\" must be numeric!')\n    if time_limit is not None:\n        try:\n            time_limit = float(time_limit)\n        except Exception:\n            raise Exception('\"time_limit\" must be numeric!')\n\n    sb_config.with_testing_base = with_testing_base\n    sb_config.browser = browser\n    if sb_config._browser_shortcut:\n        sb_config.browser = sb_config._browser_shortcut\n    if not hasattr(sb_config, \"is_behave\"):\n        sb_config.is_behave = False\n    if not hasattr(sb_config, \"is_pytest\"):\n        sb_config.is_pytest = False\n    if not hasattr(sb_config, \"is_nosetest\"):\n        sb_config.is_nosetest = False\n    sb_config.is_context_manager = True\n    sb_config.headless = headless\n    sb_config.headless1 = headless1\n    sb_config.headless2 = headless2\n    sb_config.headed = headed\n    sb_config.xvfb = xvfb\n    sb_config.xvfb_metrics = xvfb_metrics\n    sb_config.start_page = start_page\n    sb_config.locale_code = locale_code\n    sb_config.protocol = protocol\n    sb_config.servername = servername\n    sb_config.port = port\n    sb_config.data = data\n    sb_config.var1 = var1\n    sb_config.var2 = var2\n    sb_config.var3 = var3\n    sb_config.variables = variables\n    sb_config.account = account\n    sb_config.environment = environment\n    sb_config.env = environment\n    sb_config.user_agent = user_agent\n    sb_config.incognito = incognito\n    sb_config.guest_mode = guest_mode\n    sb_config.dark_mode = dark_mode\n    sb_config.devtools = devtools\n    sb_config.mobile_emulator = is_mobile\n    sb_config.device_metrics = device_metrics\n    sb_config.extension_zip = extension_zip\n    sb_config.extension_dir = extension_dir\n    sb_config.database_env = \"test\"\n    sb_config.log_path = constants.Logs.LATEST\n    sb_config.archive_logs = archive_logs\n    sb_config.disable_csp = disable_csp\n    sb_config.disable_ws = disable_ws\n    sb_config.enable_ws = enable_ws\n    sb_config.enable_sync = enable_sync\n    sb_config.use_auto_ext = use_auto_ext\n    sb_config.undetectable = undetectable\n    sb_config.uc_cdp_events = uc_cdp_events\n    sb_config.uc_subprocess = uc_subprocess\n    sb_config.log_cdp_events = log_cdp_events\n    sb_config.no_sandbox = None\n    sb_config.disable_gpu = None\n    sb_config.disable_cookies = disable_cookies\n    sb_config.disable_js = disable_js\n    sb_config._multithreaded = False\n    sb_config.reuse_session = False\n    sb_config.crumbs = False\n    sb_config.final_debug = False\n    sb_config.visual_baseline = False\n    sb_config.window_position = window_position\n    sb_config.window_size = window_size\n    sb_config.maximize_option = maximize_option\n    sb_config._disable_beforeunload = _disable_beforeunload\n    sb_config.save_screenshot = save_screenshot\n    sb_config.no_screenshot = no_screenshot\n    sb_config.binary_location = binary_location\n    if hasattr(sb_config, \"_cdp_bin_loc\") and sb_config._cdp_bin_loc:\n        sb_config.binary_location = sb_config._cdp_bin_loc\n    sb_config.driver_version = driver_version\n    sb_config.page_load_strategy = page_load_strategy\n    sb_config.timeout_multiplier = timeout_multiplier\n    sb_config.pytest_html_report = None\n    sb_config.with_db_reporting = False\n    sb_config.with_s3_logging = False\n    sb_config.js_checking_on = js_checking_on\n    sb_config.recorder_mode = recorder_mode\n    sb_config.recorder_ext = recorder_ext\n    sb_config.record_sleep = record_sleep\n    sb_config.rec_behave = rec_behave\n    sb_config.rec_print = rec_print\n    sb_config.report_on = False\n    sb_config.slow_mode = slow_mode\n    sb_config.demo_mode = demo_mode\n    sb_config._time_limit = time_limit\n    sb_config.demo_sleep = demo_sleep\n    sb_config.dashboard = False\n    sb_config._dashboard_initialized = False\n    sb_config.message_duration = message_duration\n    sb_config.host_resolver_rules = host_resolver_rules\n    sb_config.block_images = block_images\n    sb_config.do_not_track = do_not_track\n    sb_config.use_wire = use_wire\n    sb_config.external_pdf = external_pdf\n    sb_config.remote_debug = remote_debug\n    sb_config.settings_file = settings_file\n    sb_config.user_data_dir = user_data_dir\n    sb_config.chromium_arg = chromium_arg\n    sb_config.firefox_arg = firefox_arg\n    sb_config.firefox_pref = firefox_pref\n    sb_config.disable_features = disable_features\n    sb_config.proxy_string = proxy_string\n    sb_config.proxy_bypass_list = proxy_bypass_list\n    sb_config.proxy_pac_url = proxy_pac_url\n    sb_config.multi_proxy = multi_proxy\n    sb_config.enable_3d_apis = enable_3d_apis\n    sb_config.swiftshader = swiftshader\n    sb_config.ad_block_on = ad_block_on\n    sb_config.highlights = highlights\n    sb_config.interval = interval\n    sb_config.cap_file = cap_file\n    sb_config.cap_string = cap_string\n    if sb_config.browser in constants.ChromiumSubs.chromium_subs:\n        if not sb_config.binary_location:\n            sb_config.browser = \"chrome\"  # Still uses chromedriver\n            sb_config._browser_shortcut = sb_config.browser\n\n    sb = BaseCase()\n    sb.with_testing_base = sb_config.with_testing_base\n    sb.browser = sb_config.browser\n    sb.is_behave = False\n    sb.is_pytest = False\n    sb.is_nosetest = False\n    sb.is_context_manager = sb_config.is_context_manager\n    sb.headless = sb_config.headless\n    sb.headless1 = sb_config.headless1\n    sb.headless2 = sb_config.headless2\n    sb.headed = sb_config.headed\n    sb.xvfb = sb_config.xvfb\n    sb.xvfb_metrics = sb_config.xvfb_metrics\n    sb.start_page = sb_config.start_page\n    sb.locale_code = sb_config.locale_code\n    sb.protocol = sb_config.protocol\n    sb.servername = sb_config.servername\n    sb.port = sb_config.port\n    sb.data = sb_config.data\n    sb.var1 = sb_config.var1\n    sb.var2 = sb_config.var2\n    sb.var3 = sb_config.var3\n    sb.variables = sb_config.variables\n    sb.account = sb_config.account\n    sb.environment = sb_config.environment\n    sb.env = sb_config.env\n    sb.user_agent = sb_config.user_agent\n    sb.incognito = sb_config.incognito\n    sb.guest_mode = sb_config.guest_mode\n    sb.dark_mode = sb_config.dark_mode\n    sb.devtools = sb_config.devtools\n    sb.binary_location = sb_config.binary_location\n    sb.driver_version = sb_config.driver_version\n    sb.mobile_emulator = sb_config.mobile_emulator\n    sb.device_metrics = sb_config.device_metrics\n    sb.extension_zip = sb_config.extension_zip\n    sb.extension_dir = sb_config.extension_dir\n    sb.database_env = sb_config.database_env\n    sb.log_path = sb_config.log_path\n    sb.archive_logs = sb_config.archive_logs\n    sb.disable_csp = sb_config.disable_csp\n    sb.disable_ws = sb_config.disable_ws\n    sb.enable_ws = sb_config.enable_ws\n    sb.enable_sync = sb_config.enable_sync\n    sb.use_auto_ext = sb_config.use_auto_ext\n    sb.undetectable = sb_config.undetectable\n    sb.uc_cdp_events = sb_config.uc_cdp_events\n    sb.uc_subprocess = sb_config.uc_subprocess\n    sb.log_cdp_events = sb_config.log_cdp_events\n    sb.no_sandbox = sb_config.no_sandbox\n    sb.disable_gpu = sb_config.disable_gpu\n    sb.disable_cookies = sb_config.disable_cookies\n    sb.disable_js = sb_config.disable_js\n    sb._multithreaded = sb_config._multithreaded\n    sb._reuse_session = sb_config.reuse_session\n    sb._crumbs = sb_config.crumbs\n    sb._final_debug = sb_config.final_debug\n    sb.visual_baseline = sb_config.visual_baseline\n    sb.window_position = sb_config.window_position\n    sb.window_size = sb_config.window_size\n    sb.maximize_option = sb_config.maximize_option\n    sb._disable_beforeunload = sb_config._disable_beforeunload\n    sb.save_screenshot_after_test = sb_config.save_screenshot\n    sb.no_screenshot_after_test = sb_config.no_screenshot\n    sb.page_load_strategy = sb_config.page_load_strategy\n    sb.timeout_multiplier = sb_config.timeout_multiplier\n    sb.pytest_html_report = sb_config.pytest_html_report\n    sb.with_db_reporting = sb_config.with_db_reporting\n    sb.with_s3_logging = sb_config.with_s3_logging\n    sb.js_checking_on = sb_config.js_checking_on\n    sb.recorder_mode = sb_config.recorder_mode\n    sb.recorder_ext = sb_config.recorder_ext\n    sb.record_sleep = sb_config.record_sleep\n    sb.rec_behave = sb_config.rec_behave\n    sb.rec_print = sb_config.rec_print\n    sb.report_on = sb_config.report_on\n    sb.slow_mode = sb_config.slow_mode\n    sb.demo_mode = sb_config.demo_mode\n    sb.time_limit = sb_config._time_limit\n    sb.demo_sleep = sb_config.demo_sleep\n    sb.dashboard = sb_config.dashboard\n    sb._dash_initialized = sb_config._dashboard_initialized\n    sb.message_duration = sb_config.message_duration\n    sb.host_resolver_rules = sb_config.host_resolver_rules\n    sb.block_images = sb_config.block_images\n    sb.do_not_track = sb_config.do_not_track\n    sb.use_wire = sb_config.use_wire\n    sb.external_pdf = sb_config.external_pdf\n    sb.remote_debug = sb_config.remote_debug\n    sb.settings_file = sb_config.settings_file\n    sb.user_data_dir = sb_config.user_data_dir\n    sb.chromium_arg = sb_config.chromium_arg\n    sb.firefox_arg = sb_config.firefox_arg\n    sb.firefox_pref = sb_config.firefox_pref\n    sb.disable_features = sb_config.disable_features\n    sb.proxy_string = sb_config.proxy_string\n    sb.proxy_bypass_list = sb_config.proxy_bypass_list\n    sb.proxy_pac_url = sb_config.proxy_pac_url\n    sb.multi_proxy = sb_config.multi_proxy\n    sb.enable_3d_apis = sb_config.enable_3d_apis\n    sb._swiftshader = sb_config.swiftshader\n    sb.ad_block_on = sb_config.ad_block_on\n    sb.highlights = sb_config.highlights\n    sb.interval = sb_config.interval\n    sb.cap_file = sb_config.cap_file\n    sb.cap_string = sb_config.cap_string\n    sb._has_failure = False  # This may change\n\n    with suppress(Exception):\n        stack_base = traceback.format_stack()[0].split(\"with SB(\")[0]\n        stack_base = stack_base.split(os.sep)[-1]\n        test_base = stack_base.split(\", in \")[0]\n        filename = test_base.split('\"')[0]\n        methodname = \".line_\" + test_base.split(\", line \")[-1]\n        context_id = filename.split(\".\")[0] + methodname\n        sb._manager_saved_id = context_id\n\n    if hasattr(sb_config, \"headless_active\"):\n        sb.headless_active = sb_config.headless_active\n    else:\n        sb.headless_active = False\n    test_name = None\n    terminal_width = shared_utils.get_terminal_width()\n    if test:\n        c1 = colorama.Fore.GREEN\n        b1 = colorama.Style.BRIGHT\n        cr = colorama.Style.RESET_ALL\n        stack_base = traceback.format_stack()[0].split(\"with SB(\")[0]\n        stack_base = stack_base.split(os.sep)[-1]\n        test_name = stack_base.split(\", in \")[0].replace('\", line ', \":\")\n        test_name += \":SB\"\n        start_text = \"=== {%s} starts ===\" % test_name\n        remaining_spaces = terminal_width - len(start_text)\n        left_space = \"\"\n        right_space = \"\"\n        if remaining_spaces > 0:\n            left_spaces = int(remaining_spaces / 2)\n            left_space = left_spaces * \"=\"\n            right_spaces = remaining_spaces - left_spaces\n            right_space = right_spaces * \"=\"\n        if not test_name.startswith(\"runpy.py:\"):\n            print(\"%s%s%s%s%s\" % (b1, left_space, start_text, right_space, cr))\n    if do_log_folder_setup:\n        from seleniumbase.core import log_helper\n        from seleniumbase.core import download_helper\n        from seleniumbase.core import proxy_helper\n\n        log_helper.log_folder_setup(constants.Logs.LATEST + os.sep)\n        log_helper.clear_empty_logs()\n        download_helper.reset_downloads_folder()\n        if not sb_config.multi_proxy:\n            proxy_helper.remove_proxy_zip_if_present()\n    start_time = time.time()\n    sb.setUp()\n    test_passed = True  # This can change later\n    teardown_exception = None\n    if \"--trace\" in sys_argv:\n        import pdb\n\n        pdb.set_trace()  # Debug Mode\n        # Type \"s\" and press [Enter] to step into \"yield sb\".\n    try:\n        yield sb\n    except Exception as e:\n        sb._has_failure = True\n        exception = e\n        test_passed = False\n        if (test or inner_test) and not test_name:\n            print(e)\n            return\n        elif not test_name:\n            raise\n        else:\n            the_traceback = traceback.format_exc().strip()\n            try:\n                p2 = the_traceback.split(', in ')[1].split('\", line ')[0]\n                filename = p2.split(os.sep)[-1]\n                sb.cm_filename = filename\n            except Exception:\n                sb.cm_filename = None\n        # Tests will raise an exception if raise_test_failure is True\n    finally:\n        if sb._has_failure and \"--pdb\" in sys_argv:\n            sb_config._do_sb_post_mortem = True\n        elif (\n            \"--final-debug\" in sys_argv\n            or \"--final-trace\" in sys_argv\n            or \"--fdebug\" in sys_argv\n            or \"--ftrace\" in sys_argv\n        ):\n            sb_config._do_sb_final_trace = True\n        try:\n            sb.tearDown()\n        except Exception as t_e:\n            teardown_exception = t_e\n            print(traceback.format_exc().strip())\n            if test and not test_passed:\n                print(\"********** ERROR: The test AND the tearDown() FAILED!\")\n        if (\n            hasattr(sb_config, \"_virtual_display\")\n            and sb_config._virtual_display\n            and hasattr(sb_config._virtual_display, \"stop\")\n        ):\n            try:\n                sb_config._virtual_display.stop()\n                sb_config._virtual_display = None\n                sb_config.headless_active = False\n            except AttributeError:\n                pass\n            except Exception:\n                pass\n        end_time = time.time()\n        run_time = end_time - start_time\n        sb_config = sb_config_backup\n        if test:\n            sb_config._has_older_context = True\n        if test_name:\n            result = \"passed\"\n            if test and not test_passed:\n                result = \"failed\"\n                c1 = colorama.Fore.RED\n            end_text = (\n                \"=== {%s} %s in %.2fs ===\"\n                % (test_name, result, run_time)\n            )\n            remaining_spaces = terminal_width - len(end_text)\n            end_text = (\n                \"=== %s%s{%s} %s%s%s in %.2fs ===\"\n                % (b1, c1, test_name, result, cr, c1, run_time)\n            )\n            left_space = \"\"\n            right_space = \"\"\n            if remaining_spaces > 0:\n                left_spaces = int(remaining_spaces / 2)\n                left_space = left_spaces * \"=\"\n                right_spaces = remaining_spaces - left_spaces\n                right_space = right_spaces * \"=\"\n            if test and not test_passed:\n                print(the_traceback)\n            if not test_name.startswith(\"runpy.py:\"):\n                print(\n                    \"%s%s%s%s%s\"\n                    % (c1, left_space, end_text, right_space, cr)\n                )\n        if undetectable and hasattr(sb, \"_drivers_browser_map\"):\n            import asyncio\n            for driver in sb._drivers_browser_map.keys():\n                if (\n                    hasattr(driver, \"cdp\")\n                    and driver.cdp\n                    and hasattr(driver.cdp, \"loop\")\n                ):\n                    asyncio.set_event_loop(driver.cdp.loop)\n                    tasks = [tab.aclose() for tab in driver.cdp.get_tabs()]\n                    tasks.append(driver.cdp.driver.connection.aclose())\n                    driver.cdp.loop.run_until_complete(asyncio.gather(*tasks))\n                    driver.cdp.loop.close()\n        gc.collect()\n    if test and test_name and not test_passed and raise_test_failure:\n        raise exception\n    elif (\n        teardown_exception\n        and (\n            not test\n            or (test_passed and raise_test_failure)\n        )\n    ):\n        raise teardown_exception\n"
  },
  {
    "path": "seleniumbase/plugins/screen_shots.py",
    "content": "\"\"\"Screenshot Plugin for SeleniumBase tests that run with pynose / nosetests\"\"\"\nimport os\nfrom nose.plugins import Plugin\nfrom seleniumbase.config import settings\n\n\nclass ScreenShots(Plugin):\n    \"\"\"This plugin takes a screenshot when a test fails.\"\"\"\n    name = \"screen_shots\"\n    logfile_name = settings.SCREENSHOT_NAME\n\n    def options(self, parser, env):\n        super().options(parser, env=env)\n\n    def configure(self, options, conf):\n        super().configure(options, conf)\n        if not self.enabled:\n            return\n        self.options = options\n\n    def add_screenshot(self, test, err, capt=None, tbinfo=None):\n        test_logpath = self.options.log_path + \"/\" + test.id()\n        if not os.path.exists(test_logpath):\n            os.makedirs(test_logpath)\n        screenshot_file = \"%s/%s\" % (test_logpath, self.logfile_name)\n        test.driver.get_screenshot_as_file(screenshot_file)\n\n    def addError(self, test, err, capt=None):\n        self.add_screenshot(test, err, capt=capt)\n\n    def addFailure(self, test, err, capt=None, tbinfo=None):\n        self.add_screenshot(test, err, capt=capt, tbinfo=tbinfo)\n"
  },
  {
    "path": "seleniumbase/plugins/selenium_plugin.py",
    "content": "\"\"\"Selenium Plugin for SeleniumBase tests that run with pynose / nosetests\"\"\"\nimport os\nimport sys\nfrom contextlib import suppress\nfrom nose.plugins import Plugin\nfrom seleniumbase import config as sb_config\nfrom seleniumbase.config import settings\nfrom seleniumbase.core import detect_b_ver\nfrom seleniumbase.core import proxy_helper\nfrom seleniumbase.fixtures import constants\nfrom seleniumbase.fixtures import shared_utils\n\n\nclass SeleniumBrowser(Plugin):\n    \"\"\"This plugin adds the following command-line options to pynose:\n    --browser=BROWSER  (The web browser to use. Default: \"chrome\".)\n    --chrome  (Shortcut for \"--browser=chrome\". Default.)\n    --edge  (Shortcut for \"--browser=edge\".)\n    --firefox  (Shortcut for \"--browser=firefox\".)\n    --safari  (Shortcut for \"--browser=safari\".)\n    --opera  (Shortcut for \"--browser=opera\".)\n    --brave  (Shortcut for \"--browser=brave\".)\n    --comet  (Shortcut for \"--browser=comet\".)\n    --atlas  (Shortcut for \"--browser=atlas\".)\n    --use-chromium  (Shortcut for using base `Chromium`)\n    --cft  (Shortcut for using `Chrome for Testing`)\n    --chs  (Shortcut for using `Chrome-Headless-Shell`)\n    --user-data-dir=DIR  (Set the Chrome user data directory to use.)\n    --protocol=PROTOCOL  (The Selenium Grid protocol: http|https.)\n    --server=SERVER  (The Selenium Grid server/IP used for tests.)\n    --port=PORT  (The Selenium Grid port used by the test server.)\n    --cap-file=FILE  (The web browser's desired capabilities to use.)\n    --cap-string=STRING  (The web browser's desired capabilities to use.)\n    --proxy=SERVER:PORT  (Connect to a proxy server:port as tests are running)\n    --proxy=USERNAME:PASSWORD@SERVER:PORT  (Use an authenticated proxy server)\n    --proxy-bypass-list=STRING (\";\"-separated hosts to bypass, Eg \"*.foo.com\")\n    --proxy-pac-url=URL  (Connect to a proxy server using a PAC_URL.pac file.)\n    --proxy-pac-url=USERNAME:PASSWORD@URL  (Authenticated proxy with PAC URL.)\n    --proxy-driver  (If a driver download is needed, will use: --proxy=PROXY.)\n    --multi-proxy  (Allow multiple authenticated proxies when multi-threaded.)\n    --agent=STRING  (Modify the web browser's User-Agent string.)\n    --mobile  (Use the mobile device emulator while running tests.)\n    --metrics=STRING  (Set mobile metrics: \"CSSWidth,CSSHeight,PixelRatio\".)\n    --chromium-arg=\"ARG=N,ARG2\" (Set Chromium args, \",\"-separated, no spaces.)\n    --firefox-arg=\"ARG=N,ARG2\" (Set Firefox args, comma-separated, no spaces.)\n    --firefox-pref=SET  (Set a Firefox preference:value set, comma-separated.)\n    --extension-zip=ZIP  (Load a Chrome Extension .zip|.crx, comma-separated.)\n    --extension-dir=DIR  (Load a Chrome Extension directory, comma-separated.)\n    --disable-features=\"F1,F2\" (Disable features, comma-separated, no spaces.)\n    --binary-location=PATH  (Set path of the Chromium browser binary to use.)\n    --driver-version=VER  (Set the chromedriver or uc_driver version to use.)\n    --sjw  (Skip JS Waits for readyState to be \"complete\" or Angular to load.)\n    --wfa  (Wait for AngularJS to be done loading after specific web actions.)\n    --pls=PLS  (Set pageLoadStrategy on Chrome: \"normal\", \"eager\", or \"none\".)\n    --headless  (The default headless mode. Linux uses this mode by default.)\n    --headless1  (Use Chrome's old headless mode. Fast, but has limitations.)\n    --headless2  (Use Chrome's new headless mode, which supports extensions.)\n    --headed  (Run tests in headed/GUI mode on Linux OS, where not default.)\n    --xvfb  (Run tests using the Xvfb virtual display server on Linux OS.)\n    --xvfb-metrics=STRING  (Set Xvfb display size on Linux: \"Width,Height\".)\n    --locale=LOCALE_CODE  (Set the Language Locale Code for the web browser.)\n    --interval=SECONDS  (The autoplay interval for presentations & tour steps)\n    --start-page=URL  (The starting URL for the web browser when tests begin.)\n    --time-limit=SECONDS  (Safely fail any test that exceeds the time limit.)\n    --slow  (Slow down the automation. Faster than using Demo Mode.)\n    --demo  (Slow down and visually see test actions as they occur.)\n    --demo-sleep=SECONDS  (Set the wait time after Slow & Demo Mode actions.)\n    --highlights=NUM  (Number of highlight animations for Demo Mode actions.)\n    --message-duration=SECONDS  (The time length for Messenger alerts.)\n    --check-js  (Check for JavaScript errors after page loads.)\n    --ad-block  (Block some types of display ads from loading.)\n    --host-resolver-rules=RULES  (Set host-resolver-rules, comma-separated.)\n    --block-images  (Block images from loading during tests.)\n    --do-not-track  (Indicate to websites that you don't want to be tracked.)\n    --verify-delay=SECONDS  (The delay before MasterQA verification checks.)\n    --ee / --esc-end  (Lets the user end the current test via the ESC key.)\n    --recorder  (Enables the Recorder for turning browser actions into code.)\n    --rec-behave  (Same as Recorder Mode, but also generates behave-gherkin.)\n    --rec-sleep  (If the Recorder is enabled, also records self.sleep calls.)\n    --rec-print  (If the Recorder is enabled, prints output after tests end.)\n    --disable-cookies  (Disable Cookies on websites. Pages might break!)\n    --disable-js  (Disable JavaScript on websites. Pages might break!)\n    --disable-csp  (Disable the Content Security Policy of websites.)\n    --disable-ws  (Disable Web Security on Chromium-based browsers.)\n    --enable-ws  (Enable Web Security on Chromium-based browsers.)\n    --enable-sync  (Enable \"Chrome Sync\" on websites.)\n    --uc | --undetected  (Use undetected-chromedriver to evade bot-detection.)\n    --uc-cdp-events  (Capture CDP events when running in \"--undetected\" mode.)\n    --log-cdp  (\"goog:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"})\n    --remote-debug  (Sync to Chrome Remote Debugger chrome://inspect/#devices)\n    --enable-3d-apis  (Enables WebGL and 3D APIs.)\n    --swiftshader  (Chrome \"--use-gl=angle\" / \"--use-angle=swiftshader-webgl\")\n    --incognito  (Enable Chrome's Incognito mode.)\n    --guest  (Enable Chrome's Guest mode.)\n    --dark  (Enable Chrome's Dark mode.)\n    --devtools  (Open Chrome's DevTools when the browser opens.)\n    --disable-beforeunload  (Disable the \"beforeunload\" event on Chrome.)\n    --window-position=X,Y  (Set the browser's starting window position.)\n    --window-size=WIDTH,HEIGHT  (Set the browser's starting window size.)\n    --maximize  (Start tests with the browser window maximized.)\n    --screenshot  (Save a screenshot at the end of each test.)\n    --visual-baseline  (Set the visual baseline for Visual/Layout tests.)\n    --wire  (Use selenium-wire's webdriver for replacing selenium webdriver.)\n    --external-pdf (Set Chromium \"plugins.always_open_pdf_externally\": True.)\n    --timeout-multiplier=MULTIPLIER  (Multiplies the default timeout values.)\n    \"\"\"\n    name = \"selenium\"  # Usage: --with-selenium\n\n    def options(self, parser, env):\n        super().options(parser, env=env)\n        parser.addoption = parser.add_option  # Reuse name from pytest parser\n        parser.addoption(\n            \"--browser\",\n            action=\"store\",\n            dest=\"browser\",\n            choices=constants.ValidBrowsers.valid_browsers,\n            default=constants.Browser.GOOGLE_CHROME,\n            help=\"\"\"Specifies the web browser to use. Default: Chrome.\n                    Examples: (--browser=edge OR --browser=firefox)\"\"\",\n        )\n        parser.addoption(\n            \"--chrome\",\n            action=\"store_true\",\n            dest=\"use_chrome\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=chrome (Default)\"\"\",\n        )\n        parser.addoption(\n            \"--edge\",\n            action=\"store_true\",\n            dest=\"use_edge\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=edge\"\"\",\n        )\n        parser.addoption(\n            \"--firefox\",\n            action=\"store_true\",\n            dest=\"use_firefox\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=firefox\"\"\",\n        )\n        parser.addoption(\n            \"--ie\",\n            action=\"store_true\",\n            dest=\"use_ie\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=ie\"\"\",\n        )\n        parser.addoption(\n            \"--safari\",\n            action=\"store_true\",\n            dest=\"use_safari\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=safari\"\"\",\n        )\n        parser.addoption(\n            \"--opera\",\n            action=\"store_true\",\n            dest=\"use_opera\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=opera\"\"\",\n        )\n        parser.addoption(\n            \"--brave\",\n            action=\"store_true\",\n            dest=\"use_brave\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=brave\"\"\",\n        )\n        parser.addoption(\n            \"--comet\",\n            action=\"store_true\",\n            dest=\"use_comet\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=comet\"\"\",\n        )\n        parser.addoption(\n            \"--atlas\",\n            action=\"store_true\",\n            dest=\"use_atlas\",\n            default=False,\n            help=\"\"\"Shortcut for --browser=atlas\"\"\",\n        )\n        parser.addoption(\n            \"--use-chromium\",\n            action=\"store_true\",\n            dest=\"use_chromium\",\n            default=False,\n            help=\"\"\"Shortcut for using base `Chromium`\"\"\",\n        )\n        parser.addoption(\n            \"--cft\",\n            action=\"store_true\",\n            dest=\"use_cft\",\n            default=False,\n            help=\"\"\"Shortcut for using `Chrome for Testing`\"\"\",\n        )\n        parser.addoption(\n            \"--chs\",\n            action=\"store_true\",\n            dest=\"use_chs\",\n            default=False,\n            help=\"\"\"Shortcut for using `Chrome-Headless-Shell`\"\"\",\n        )\n        parser.addoption(\n            \"--cap_file\",\n            \"--cap-file\",\n            action=\"store\",\n            dest=\"cap_file\",\n            default=None,\n            help=\"\"\"The file that stores browser desired capabilities\n                    for BrowserStack, Sauce Labs, or other grids.\"\"\",\n        )\n        parser.addoption(\n            \"--cap_string\",\n            \"--cap-string\",\n            dest=\"cap_string\",\n            default=None,\n            help=\"\"\"The string that stores browser desired capabilities\n                    for BrowserStack, Sauce Labs, or other grids.\n                    Enclose cap-string in single quotes.\n                    Enclose parameter keys in double quotes.\n                    Example: --cap-string='{\"name\":\"test1\",\"v\":\"42\"}'\"\"\",\n        )\n        parser.addoption(\n            \"--user_data_dir\",\n            \"--user-data-dir\",\n            action=\"store\",\n            dest=\"user_data_dir\",\n            default=None,\n            help=\"\"\"The Chrome User Data Directory to use. (Chrome Profile)\n                    If the directory doesn't exist, it'll be created.\"\"\",\n        )\n        parser.addoption(\n            \"--sjw\",\n            \"--skip_js_waits\",\n            \"--skip-js-waits\",\n            action=\"store_true\",\n            dest=\"skip_js_waits\",\n            default=False,\n            help=\"\"\"Skip all calls to wait_for_ready_state_complete()\n                    and wait_for_angularjs(), which are part of many\n                    SeleniumBase methods for improving reliability.\"\"\",\n        )\n        parser.addoption(\n            \"--wfa\",\n            \"--wait_for_angularjs\",\n            \"--wait-for-angularjs\",\n            action=\"store_true\",\n            dest=\"wait_for_angularjs\",\n            default=False,\n            help=\"\"\"Add waiting for AngularJS. (The default setting\n                    was changed to no longer wait for AngularJS to\n                    finish loading as an extra JavaScript call.)\"\"\",\n        )\n        parser.addoption(\n            \"--protocol\",\n            action=\"store\",\n            dest=\"protocol\",\n            choices=(\n                constants.Protocol.HTTP,\n                constants.Protocol.HTTPS,\n            ),\n            default=constants.Protocol.HTTP,\n            help=\"\"\"Designates the Selenium Grid protocol to use.\n                    Default: http.\"\"\",\n        )\n        parser.addoption(\n            \"--server\",\n            action=\"store\",\n            dest=\"servername\",\n            default=\"localhost\",\n            help=\"\"\"Designates the Selenium Grid server to use.\n                    Use \"127.0.0.1\" to connect to a localhost Grid.\n                    If unset or set to \"localhost\", Grid isn't used.\n                    Default: \"localhost\".\"\"\",\n        )\n        parser.addoption(\n            \"--port\",\n            action=\"store\",\n            dest=\"port\",\n            default=\"4444\",\n            help=\"\"\"Designates the Selenium Grid port to use.\n                    Default: 4444. (If 443, protocol becomes \"https\")\"\"\",\n        )\n        parser.addoption(\n            \"--proxy\",\n            \"--proxy-server\",\n            \"--proxy-string\",\n            action=\"store\",\n            dest=\"proxy_string\",\n            default=None,\n            help=\"\"\"Designates the proxy server:port to use.\n                    Format: servername:port.  OR\n                            username:password@servername:port  OR\n                            A dict key from proxy_list.PROXY_LIST\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--proxy-bypass-list\",\n            \"--proxy_bypass_list\",\n            action=\"store\",\n            dest=\"proxy_bypass_list\",\n            default=None,\n            help=\"\"\"Designates the hosts, domains, and/or IP addresses\n                    to bypass when using a proxy server with \"--proxy\".\n                    Format: A \";\"-separated string.\n                    Example usage:\n                        pytest\n                            --proxy=\"username:password@servername:port\"\n                            --proxy-bypass-list=\"*.foo.com;github.com\"\n                        pytest\n                            --proxy=\"servername:port\"\n                            --proxy-bypass-list=\"127.0.0.1:8080\"\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--proxy-pac-url\",\n            \"--pac-url\",\n            action=\"store\",\n            dest=\"proxy_pac_url\",\n            default=None,\n            help=\"\"\"Designates the proxy PAC URL to use.\n                    Format: A URL string  OR\n                            A username:password@URL string\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--proxy-driver\",\n            \"--proxy_driver\",\n            action=\"store_true\",\n            dest=\"proxy_driver\",\n            default=False,\n            help=\"\"\"If a driver download is needed for tests,\n                    uses proxy settings set via --proxy=PROXY.\"\"\",\n        )\n        parser.addoption(\n            \"--multi-proxy\",\n            \"--multi_proxy\",\n            action=\"store_true\",\n            dest=\"multi_proxy\",\n            default=False,\n            help=\"\"\"If you need to run multi-threaded tests with\n                    multiple proxies that require authentication,\n                    set this to allow multiple configurations.\"\"\",\n        )\n        parser.addoption(\n            \"--agent\",\n            \"--user-agent\",\n            \"--user_agent\",\n            action=\"store\",\n            dest=\"user_agent\",\n            default=None,\n            help=\"\"\"Designates the User-Agent for the browser to use.\n                    Format: A string.\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--mobile\",\n            \"--mobile-emulator\",\n            \"--mobile_emulator\",\n            action=\"store_true\",\n            dest=\"mobile_emulator\",\n            default=False,\n            help=\"\"\"If this option is enabled, the mobile emulator\n                    will be used while running tests.\"\"\",\n        )\n        parser.addoption(\n            \"--metrics\",\n            \"--device-metrics\",\n            \"--device_metrics\",\n            action=\"store\",\n            dest=\"device_metrics\",\n            default=None,\n            help=\"\"\"Designates the three device metrics of the mobile\n                    emulator: CSS Width, CSS Height, and Pixel-Ratio.\n                    Format: A comma-separated string with the 3 values.\n                    Examples: \"375,734,5\" or \"412,732,3\" or \"390,715,3\"\n                    Default: None. (Will use default values if None)\"\"\",\n        )\n        parser.addoption(\n            \"--chromium_arg\",\n            \"--chromium-arg\",\n            action=\"store\",\n            dest=\"chromium_arg\",\n            default=None,\n            help=\"\"\"Add a Chromium argument for Chrome/Edge browsers.\n                    Format: A comma-separated list of Chromium args.\n                    If an arg doesn't start with \"--\", that will be\n                    added to the beginning of the arg automatically.\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--firefox_arg\",\n            \"--firefox-arg\",\n            action=\"store\",\n            dest=\"firefox_arg\",\n            default=None,\n            help=\"\"\"Add a Firefox argument for Firefox browser runs.\n                    Format: A comma-separated list of Firefox args.\n                    If an arg doesn't start with \"--\", that will be\n                    added to the beginning of the arg automatically.\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--firefox_pref\",\n            \"--firefox-pref\",\n            action=\"store\",\n            dest=\"firefox_pref\",\n            default=None,\n            help=\"\"\"Set a Firefox preference:value combination.\n                    Format: A comma-separated list of pref:value items.\n                    Example usage:\n                        --firefox-pref=\"browser.formfill.enable:True\"\n                        --firefox-pref=\"pdfjs.disabled:False\"\n                        --firefox-pref=\"abc.def.xyz:42,hello.world:text\"\n                    Boolean and integer values to the right of the \":\"\n                    will be automatically converted into proper format.\n                    If there's no \":\" in the string, then True is used.\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--extension_zip\",\n            \"--extension-zip\",\n            \"--crx\",\n            action=\"store\",\n            dest=\"extension_zip\",\n            default=None,\n            help=\"\"\"Designates the Chrome Extension ZIP file to load.\n                    Format: A comma-separated list of .zip or .crx files\n                    containing the Chrome extensions to load.\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--extension_dir\",\n            \"--extension-dir\",\n            action=\"store\",\n            dest=\"extension_dir\",\n            default=None,\n            help=\"\"\"Designates the Chrome Extension folder to load.\n                    Format: A directory containing the Chrome extension.\n                    (Can also be a comma-separated list of directories.)\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--disable_features\",\n            \"--disable-features\",\n            action=\"store\",\n            dest=\"disable_features\",\n            default=None,\n            help=\"\"\"Disable Chromium features from Chrome/Edge browsers.\n                    Format: A comma-separated list of Chromium features.\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--binary_location\",\n            \"--binary-location\",\n            \"--bl\",\n            action=\"store\",\n            dest=\"binary_location\",\n            default=None,\n            help=\"\"\"Sets the path of the Chromium browser binary to use.\n                    Uses the default location if not os.path.exists(PATH)\"\"\",\n        )\n        parser.addoption(\n            \"--driver_version\",\n            \"--driver-version\",\n            action=\"store\",\n            dest=\"driver_version\",\n            default=None,\n            help=\"\"\"Setting this overrides the default driver version,\n                    which is set to match the detected browser version.\n                    Major version only. Example: \"--driver-version=114\"\n                    (Only chromedriver and uc_driver are affected.)\"\"\",\n        )\n        parser.addoption(\n            \"--pls\",\n            \"--page_load_strategy\",\n            \"--page-load-strategy\",\n            action=\"store\",\n            dest=\"page_load_strategy\",\n            choices=(\n                constants.PageLoadStrategy.NORMAL,\n                constants.PageLoadStrategy.EAGER,\n                constants.PageLoadStrategy.NONE,\n            ),\n            default=None,\n            help=\"\"\"This option sets Chrome's pageLoadStrategy.\n                    List of choices: \"normal\", \"eager\", \"none\".\"\"\",\n        )\n        parser.addoption(\n            \"--headless\",\n            action=\"store_true\",\n            dest=\"headless\",\n            default=False,\n            help=\"\"\"Using this option activates headless mode,\n                which is required on headless machines\n                UNLESS using a virtual display with Xvfb.\n                Default: False on Mac/Windows. True on Linux.\"\"\",\n        )\n        parser.addoption(\n            \"--headless1\",\n            action=\"store_true\",\n            dest=\"headless1\",\n            default=False,\n            help=\"\"\"This option activates the old headless mode,\n                    which is faster, but has limitations.\n                    (May be phased out by Chrome in the future.)\"\"\",\n        )\n        parser.addoption(\n            \"--headless2\",\n            action=\"store_true\",\n            dest=\"headless2\",\n            default=False,\n            help=\"\"\"This option activates the new headless mode,\n                    which supports Chromium extensions, and more,\n                    but is slower than the standard headless mode.\"\"\",\n        )\n        parser.addoption(\n            \"--headed\",\n            \"--gui\",\n            action=\"store_true\",\n            dest=\"headed\",\n            default=False,\n            help=\"\"\"Using this makes Webdriver run web browsers with\n                    a GUI when running tests on Linux machines.\n                    (The default setting on Linux is headless.)\n                    (The default setting on Mac or Windows is headed.)\"\"\",\n        )\n        parser.addoption(\n            \"--xvfb\",\n            action=\"store_true\",\n            dest=\"xvfb\",\n            default=False,\n            help=\"\"\"Using this makes tests run headlessly using Xvfb\n                    instead of the browser's built-in headless mode.\n                    When using \"--xvfb\", the \"--headless\" option\n                    will no longer be enabled by default on Linux.\n                    Default: False. (Linux-ONLY!)\"\"\",\n        )\n        parser.addoption(\n            \"--xvfb-metrics\",\n            \"--xvfb_metrics\",\n            action=\"store\",\n            dest=\"xvfb_metrics\",\n            default=None,\n            help=\"\"\"Customize the Xvfb metrics (Width,Height) on Linux.\n                    Format: A comma-separated string with the 2 values.\n                    Examples: \"1920,1080\" or \"1366,768\" or \"1024,768\".\n                    Default: None. (None: \"1366,768\". Min: \"1024,768\".)\"\"\",\n        )\n        parser.addoption(\n            \"--locale_code\",\n            \"--locale-code\",\n            \"--locale\",\n            action=\"store\",\n            dest=\"locale_code\",\n            default=None,\n            help=\"\"\"Designates the Locale Code for the web browser.\n                    A Locale is a specific version of a spoken Language.\n                    The Locale alters visible text on supported websites.\n                    See: https://seleniumbase.io/help_docs/locale_codes/\n                    Default: None. (The web browser's default mode.)\"\"\",\n        )\n        parser.addoption(\n            \"--interval\",\n            action=\"store\",\n            dest=\"interval\",\n            default=None,\n            help=\"\"\"This globally overrides the default interval,\n                    (in seconds), of features that include autoplay\n                    functionality, such as tours and presentations.\n                    Overrides from methods take priority over this.\n                    (Headless Mode skips tours and presentations.)\"\"\",\n        )\n        parser.addoption(\n            \"--start_page\",\n            \"--start-page\",\n            \"--url\",\n            action=\"store\",\n            dest=\"start_page\",\n            default=None,\n            help=\"\"\"Designates the starting URL for the web browser\n                    when each test begins.\n                    Default: None.\"\"\",\n        )\n        parser.addoption(\n            \"--time_limit\",\n            \"--time-limit\",\n            \"--timelimit\",\n            action=\"store\",\n            dest=\"time_limit\",\n            default=None,\n            help=\"\"\"Use this to set a time limit per test, in seconds.\n                    If a test runs beyond the limit, it fails.\"\"\",\n        )\n        parser.addoption(\n            \"--slow_mode\",\n            \"--slow-mode\",\n            \"--slowmo\",\n            \"--slow\",\n            action=\"store_true\",\n            dest=\"slow_mode\",\n            default=False,\n            help=\"\"\"Using this slows down the automation.\"\"\",\n        )\n        parser.addoption(\n            \"--demo_mode\",\n            \"--demo-mode\",\n            \"--demo\",\n            action=\"store_true\",\n            dest=\"demo_mode\",\n            default=False,\n            help=\"\"\"Using this slows down the automation and lets you\n                    visually see what the tests are actually doing.\"\"\",\n        )\n        parser.addoption(\n            \"--demo_sleep\",\n            \"--demo-sleep\",\n            action=\"store\",\n            dest=\"demo_sleep\",\n            default=None,\n            help=\"\"\"Setting this overrides the Demo Mode sleep\n                    time that happens after browser actions.\"\"\",\n        )\n        parser.addoption(\n            \"--highlights\",\n            action=\"store\",\n            dest=\"highlights\",\n            default=None,\n            help=\"\"\"Setting this overrides the default number of\n                    highlight animation loops to have per call.\"\"\",\n        )\n        parser.addoption(\n            \"--message_duration\",\n            \"--message-duration\",\n            action=\"store\",\n            dest=\"message_duration\",\n            default=None,\n            help=\"\"\"Setting this overrides the default time that\n                    messenger notifications remain visible when\n                    reaching assert statements during Demo Mode.\"\"\",\n        )\n        parser.addoption(\n            \"--check_js\",\n            \"--check-js\",\n            action=\"store_true\",\n            dest=\"js_checking_on\",\n            default=False,\n            help=\"\"\"The option to check for JavaScript errors after\n                    every page load.\"\"\",\n        )\n        parser.addoption(\n            \"--adblock\",\n            \"--ad_block\",\n            \"--ad-block\",\n            \"--block_ads\",\n            \"--block-ads\",\n            action=\"store_true\",\n            dest=\"ad_block_on\",\n            default=False,\n            help=\"\"\"Using this makes WebDriver block display ads\n                    that are defined in ad_block_list.AD_BLOCK_LIST.\"\"\",\n        )\n        parser.addoption(\n            \"--host_resolver_rules\",\n            \"--host-resolver-rules\",\n            action=\"store\",\n            dest=\"host_resolver_rules\",\n            default=None,\n            help=\"\"\"Use this option to set \"host-resolver-rules\".\n                    This lets you re-map traffic from any domain.\n                    Eg. \"MAP www.google-analytics.com 0.0.0.0\".\n                    Eg. \"MAP * ~NOTFOUND , EXCLUDE myproxy\".\n                    Eg. \"MAP * 0.0.0.0 , EXCLUDE 127.0.0.1\".\n                    Eg. \"MAP *.google.com myproxy\".\n                    Find more examples on these pages:\n                    (https://www.electronjs.org/docs/\n                     latest/api/command-line-switches)\n                    (https://www.chromium.org/developers/\n                     design-documents/network-stack/socks-proxy/)\n                    Use comma-separation for multiple host rules.\"\"\",\n        )\n        parser.addoption(\n            \"--block_images\",\n            \"--block-images\",\n            action=\"store_true\",\n            dest=\"block_images\",\n            default=False,\n            help=\"\"\"Using this makes WebDriver block images from\n                    loading on web pages during tests.\"\"\",\n        )\n        parser.addoption(\n            \"--do_not_track\",\n            \"--do-not-track\",\n            action=\"store_true\",\n            dest=\"do_not_track\",\n            default=False,\n            help=\"\"\"Indicate to websites that you don't want to be\n                    tracked. The browser will send an extra HTTP\n                    header each time it requests a web page.\n                    https://support.google.com/chrome/answer/2790761\"\"\",\n        )\n        parser.addoption(\n            \"--verify_delay\",\n            \"--verify-delay\",\n            action=\"store\",\n            dest=\"verify_delay\",\n            default=None,\n            help=\"\"\"Setting this overrides the default wait time\n                    before each MasterQA verification pop-up.\"\"\",\n        )\n        parser.addoption(\n            \"--esc-end\",\n            \"--esc_end\",\n            \"--ee\",\n            action=\"store_true\",\n            dest=\"esc_end\",\n            default=False,\n            help=\"\"\"End the current test early via the ESC key.\n                    The test will be marked as skipped.\"\"\",\n        )\n        parser.addoption(\n            \"--recorder\",\n            \"--record\",\n            \"--rec\",\n            \"--codegen\",\n            action=\"store_true\",\n            dest=\"recorder_mode\",\n            default=False,\n            help=\"\"\"Using this enables the SeleniumBase Recorder,\n                    which records browser actions for converting\n                    into SeleniumBase scripts.\"\"\",\n        )\n        parser.addoption(\n            \"--rec-behave\",\n            \"--rec-gherkin\",\n            action=\"store_true\",\n            dest=\"rec_behave\",\n            default=False,\n            help=\"\"\"Not only enables the SeleniumBase Recorder,\n                    but also saves recorded actions into the\n                    behave-gerkin format, which includes a\n                    feature file, an imported steps file,\n                    and the environment.py file.\"\"\",\n        )\n        parser.addoption(\n            \"--rec-sleep\",\n            \"--record-sleep\",\n            action=\"store_true\",\n            dest=\"record_sleep\",\n            default=False,\n            help=\"\"\"If Recorder Mode is enabled,\n                    records sleep(seconds) calls.\"\"\",\n        )\n        parser.addoption(\n            \"--rec-print\",\n            action=\"store_true\",\n            dest=\"rec_print\",\n            default=False,\n            help=\"\"\"If Recorder Mode is enabled,\n                    prints output after tests end.\"\"\",\n        )\n        parser.addoption(\n            \"--disable_js\",\n            \"--disable-js\",\n            action=\"store_true\",\n            dest=\"disable_js\",\n            default=False,\n            help=\"\"\"The option to disable JavaScript on web pages.\n                    Warning: Most web pages will stop working!\"\"\",\n        )\n        parser.addoption(\n            \"--disable_cookies\",\n            \"--disable-cookies\",\n            action=\"store_true\",\n            dest=\"disable_cookies\",\n            default=False,\n            help=\"\"\"The option to disable Cookies on web pages.\n                    Warning: Several pages may stop working!\"\"\",\n        )\n        parser.addoption(\n            \"--disable_csp\",\n            \"--disable-csp\",\n            \"--no_csp\",\n            \"--no-csp\",\n            \"--dcsp\",\n            action=\"store_true\",\n            dest=\"disable_csp\",\n            default=False,\n            help=\"\"\"Using this disables the Content Security Policy of\n                    websites, which may interfere with some features of\n                    SeleniumBase, such as loading custom JavaScript\n                    libraries for various testing actions.\n                    Setting this to True (--disable-csp) overrides the\n                    value set in seleniumbase/config/settings.py\"\"\",\n        )\n        parser.addoption(\n            \"--disable_ws\",\n            \"--disable-ws\",\n            \"--dws\",\n            \"--disable-web-security\",\n            action=\"store_true\",\n            dest=\"disable_ws\",\n            default=False,\n            help=\"\"\"Using this disables the \"Web Security\" feature of\n                    Chrome and Chromium-based browsers such as Edge.\"\"\",\n        )\n        parser.addoption(\n            \"--enable_ws\",\n            \"--enable-ws\",\n            \"--enable-web-security\",\n            action=\"store_true\",\n            dest=\"enable_ws\",\n            default=False,\n            help=\"\"\"Using this enables the \"Web Security\" feature of\n                    Chrome and Chromium-based browsers such as Edge.\"\"\",\n        )\n        parser.addoption(\n            \"--enable_sync\",\n            \"--enable-sync\",\n            action=\"store_true\",\n            dest=\"enable_sync\",\n            default=False,\n            help=\"\"\"Using this enables the \"Chrome Sync\" feature.\"\"\",\n        )\n        parser.addoption(\n            \"--use_auto_ext\",\n            \"--use-auto-ext\",\n            \"--auto-ext\",\n            action=\"store_true\",\n            dest=\"use_auto_ext\",\n            default=False,\n            help=\"\"\"(DEPRECATED) - Enable the automation extension.\n                    It's not required, but some commands & advanced\n                    features may need it.\"\"\",\n        )\n        parser.addoption(\n            \"--undetected\",\n            \"--undetectable\",\n            \"--uc\",  # undetected-chromedriver\n            action=\"store_true\",\n            dest=\"undetectable\",\n            default=False,\n            help=\"\"\"Using this option makes chromedriver undetectable\n                    to websites that use anti-bot services to block\n                    automation tools from navigating them freely.\"\"\",\n        )\n        parser.addoption(\n            \"--uc_cdp_events\",\n            \"--uc-cdp-events\",\n            \"--uc-cdp\",  # For capturing CDP events during UC Mode\n            action=\"store_true\",\n            dest=\"uc_cdp_events\",\n            default=None,\n            help=\"\"\"Captures CDP events during Undetectable Mode runs.\n                    Then you can add a listener to perform actions on\n                    received data, such as printing it to the console:\n                        from pprint import pformat\n                        self.driver.add_cdp_listener(\n                            \"*\", lambda data: print(pformat(data))\n                        )\n                        self.open(URL)\"\"\",\n        )\n        parser.addoption(\n            \"--uc_subprocess\",\n            \"--uc-subprocess\",\n            \"--uc-sub\",  # undetected-chromedriver subprocess mode\n            action=\"store_true\",\n            dest=\"uc_subprocess\",\n            default=None,\n            help=\"\"\"(DEPRECATED) - (UC Mode always uses this now.)\n                    Use undetectable-chromedriver as a subprocess,\n                    which can help avoid issues that might result.\"\"\",\n        )\n        parser.addoption(\n            \"--no_sandbox\",\n            \"--no-sandbox\",\n            action=\"store_true\",\n            dest=\"no_sandbox\",\n            default=False,\n            help=\"\"\"(DEPRECATED) - \"--no-sandbox\" is always used now.\n                    Using this enables the \"No Sandbox\" feature.\n                    (This setting is now always enabled by default.)\"\"\",\n        )\n        parser.addoption(\n            \"--disable_gpu\",\n            \"--disable-gpu\",\n            action=\"store_true\",\n            dest=\"disable_gpu\",\n            default=False,\n            help=\"\"\"(DEPRECATED) - GPU is disabled if no swiftshader.\n                    Using this enables the \"Disable GPU\" feature.\n                    (GPU is disabled by default if swiftshader off.)\"\"\",\n        )\n        parser.addoption(\n            \"--log_cdp\",\n            \"--log-cdp\",\n            \"--log_cdp_events\",\n            \"--log-cdp-events\",\n            action=\"store_true\",\n            dest=\"log_cdp_events\",\n            default=None,\n            help=\"\"\"Capture CDP events. Then you can print them.\n                    Eg. print(driver.get_log(\"performance\"))\"\"\",\n        )\n        parser.addoption(\n            \"--remote_debug\",\n            \"--remote-debug\",\n            \"--remote-debugger\",\n            \"--remote_debugger\",\n            action=\"store_true\",\n            dest=\"remote_debug\",\n            default=False,\n            help=\"\"\"This syncs the browser to Chromium's remote debugger.\n                    To access the remote debugging interface, go to:\n                    chrome://inspect/#devices while tests are running.\n                    The previous URL was at: http://localhost:9222/\n                    Info: chromedevtools.github.io/devtools-protocol/\"\"\",\n        )\n        parser.addoption(\n            \"--enable_3d_apis\",\n            \"--enable-3d-apis\",\n            action=\"store_true\",\n            dest=\"enable_3d_apis\",\n            default=False,\n            help=\"\"\"Using this enables WebGL and 3D APIs.\"\"\",\n        )\n        parser.addoption(\n            \"--swiftshader\",\n            action=\"store_true\",\n            dest=\"swiftshader\",\n            default=False,\n            help=\"\"\"Using this enables the \"--use-gl=swiftshader\"\n                    feature when running tests on Chrome.\"\"\",\n        )\n        parser.addoption(\n            \"--incognito\",\n            \"--incognito_mode\",\n            \"--incognito-mode\",\n            action=\"store_true\",\n            dest=\"incognito\",\n            default=False,\n            help=\"\"\"Using this enables Chrome's Incognito mode.\"\"\",\n        )\n        parser.addoption(\n            \"--guest\",\n            \"--guest_mode\",\n            \"--guest-mode\",\n            action=\"store_true\",\n            dest=\"guest_mode\",\n            default=False,\n            help=\"\"\"Using this enables Chrome's Guest mode.\"\"\",\n        )\n        parser.addoption(\n            \"--dark\",\n            \"--dark_mode\",\n            \"--dark-mode\",\n            action=\"store_true\",\n            dest=\"dark_mode\",\n            default=False,\n            help=\"\"\"Using this enables Chrome's Dark mode.\"\"\",\n        )\n        parser.addoption(\n            \"--devtools\",\n            \"--open_devtools\",\n            \"--open-devtools\",\n            action=\"store_true\",\n            dest=\"devtools\",\n            default=False,\n            help=\"\"\"Using this opens Chrome's DevTools.\"\"\",\n        )\n        parser.addoption(\n            \"--disable-beforeunload\",\n            \"--disable_beforeunload\",\n            action=\"store_true\",\n            dest=\"_disable_beforeunload\",\n            default=False,\n            help=\"\"\"The option to disable the \"beforeunload\" event\n                    on Chromium browsers (Chrome or Edge).\n                    This is already the default Firefox option.\"\"\",\n        )\n        parser.addoption(\n            \"--window-position\",\n            \"--window_position\",\n            action=\"store\",\n            dest=\"window_position\",\n            default=None,\n            help=\"\"\"The option to set the starting window x,y position.\n                    Format: A comma-separated string with the 2 values.\n                    Example: \"55,66\"\n                    Default: None. (Will use default values if None)\"\"\",\n        )\n        parser.addoption(\n            \"--window-size\",\n            \"--window_size\",\n            action=\"store\",\n            dest=\"window_size\",\n            default=None,\n            help=\"\"\"The option to set the default window \"width,height\".\n                    Format: A comma-separated string with the 2 values.\n                    Example: \"1200,800\"\n                    Default: None. (Will use default values if None)\"\"\",\n        )\n        parser.addoption(\n            \"--maximize_window\",\n            \"--maximize-window\",\n            \"--maximize\",\n            \"--fullscreen\",\n            action=\"store_true\",\n            dest=\"maximize_option\",\n            default=False,\n            help=\"\"\"The option to start with a maximized browser window.\n                    (Overrides the \"window-size\" option if used.)\"\"\",\n        )\n        parser.addoption(\n            \"--screenshot\",\n            \"--save_screenshot\",\n            \"--save-screenshot\",\n            \"--ss\",\n            action=\"store_true\",\n            dest=\"save_screenshot\",\n            default=False,\n            help=\"\"\"Save a screenshot at the end of every test.\n                    By default, this is only done for failures.\n                    Will be saved in the \"latest_logs/\" folder.\"\"\",\n        )\n        parser.addoption(\n            \"--no-screenshot\",\n            \"--no_screenshot\",\n            \"--ns\",\n            action=\"store_true\",\n            dest=\"no_screenshot\",\n            default=False,\n            help=\"\"\"No screenshots saved unless tests directly ask it.\n                    This changes default behavior where screenshots are\n                    saved for test failures and pytest-html reports.\"\"\",\n        )\n        parser.addoption(\n            \"--visual_baseline\",\n            \"--visual-baseline\",\n            action=\"store_true\",\n            dest=\"visual_baseline\",\n            default=False,\n            help=\"\"\"Setting this resets the visual baseline for\n                    Automated Visual Testing with SeleniumBase.\n                    When a test calls self.check_window(), it will\n                    rebuild its files in the visual_baseline folder.\"\"\",\n        )\n        parser.addoption(\n            \"--wire\",\n            action=\"store_true\",\n            dest=\"use_wire\",\n            default=False,\n            help=\"\"\"Use selenium-wire's webdriver for selenium webdriver.\"\"\",\n        )\n        parser.addoption(\n            \"--external_pdf\",\n            \"--external-pdf\",\n            action=\"store_true\",\n            dest=\"external_pdf\",\n            default=False,\n            help=\"\"\"This option sets the following on Chromium:\n                    \"plugins.always_open_pdf_externally\": True,\n                    which causes opened PDF URLs to download immediately,\n                    instead of being displayed in the browser window.\"\"\",\n        )\n        parser.addoption(\n            \"--timeout_multiplier\",\n            \"--timeout-multiplier\",\n            action=\"store\",\n            dest=\"timeout_multiplier\",\n            default=None,\n            help=\"\"\"Setting this overrides the default timeout\n                    by the multiplier when waiting for page elements.\n                    Unused when tests override the default value.\"\"\",\n        )\n\n    def configure(self, options, conf):\n        super().configure(options, conf)\n        self.enabled = True  # Used if test class inherits BaseCase\n        self.options = options\n        self.headless_active = False  # Default setting\n        sb_config.headless_active = False\n        sb_config.is_nosetest = True\n        proxy_helper.remove_proxy_zip_if_present()\n\n    def beforeTest(self, test):\n        browser = self.options.browser\n        test.test.browser = browser\n        test.test.headless = None\n        test.test.headless1 = None\n        test.test.headless2 = None\n        # As a shortcut, you can use \"--edge\" instead of \"--browser=edge\", etc,\n        # but you can only specify one default browser. (Default: chrome)\n        sb_config._browser_shortcut = None\n        sys_argv = sys.argv\n        browser_changes = 0\n        browser_set = None\n        browser_text = None\n        browser_list = []\n        # Check if binary-location in options\n        bin_loc_in_options = False\n        for arg in sys_argv:\n            if arg in [\"--binary-location\", \"--binary_location\", \"--bl\"]:\n                bin_loc_in_options = True\n        if \"--browser=chrome\" in sys_argv or \"--browser chrome\" in sys_argv:\n            browser_changes += 1\n            browser_set = \"chrome\"\n            browser_list.append(\"--browser=chrome\")\n        if \"--browser=edge\" in sys_argv or \"--browser edge\" in sys_argv:\n            browser_changes += 1\n            browser_set = \"edge\"\n            browser_list.append(\"--browser=edge\")\n        if \"--browser=firefox\" in sys_argv or \"--browser firefox\" in sys_argv:\n            browser_changes += 1\n            browser_set = \"firefox\"\n            browser_list.append(\"--browser=firefox\")\n        if \"--browser=safari\" in sys_argv or \"--browser safari\" in sys_argv:\n            browser_changes += 1\n            browser_set = \"safari\"\n            browser_list.append(\"--browser=safari\")\n        if \"--browser=ie\" in sys_argv or \"--browser ie\" in sys_argv:\n            browser_changes += 1\n            browser_set = \"ie\"\n            browser_list.append(\"--browser=ie\")\n        if \"--browser=remote\" in sys_argv or \"--browser remote\" in sys_argv:\n            browser_changes += 1\n            browser_set = \"remote\"\n            browser_list.append(\"--browser=remote\")\n        if \"--browser=opera\" in sys_argv or \"--browser opera\" in sys_argv:\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"opera\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_set = \"opera\"\n                    sb_config._browser_shortcut = \"opera\"\n                    sb_config._cdp_browser = \"opera\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--browser=opera\")\n        if \"--browser=brave\" in sys_argv or \"--browser brave\" in sys_argv:\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"brave\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_set = \"brave\"\n                    sb_config._browser_shortcut = \"brave\"\n                    sb_config._cdp_browser = \"brave\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--browser=brave\")\n        if \"--browser=comet\" in sys_argv or \"--browser comet\" in sys_argv:\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"comet\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_set = \"comet\"\n                    sb_config._browser_shortcut = \"comet\"\n                    sb_config._cdp_browser = \"comet\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--browser=comet\")\n        if \"--browser=atlas\" in sys_argv or \"--browser atlas\" in sys_argv:\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_set = \"atlas\"\n                    sb_config._browser_shortcut = \"atlas\"\n                    sb_config._cdp_browser = \"atlas\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--browser=atlas\")\n        browser_text = browser_set\n        if \"--chrome\" in sys_argv and not browser_set == \"chrome\":\n            browser_changes += 1\n            browser_text = \"chrome\"\n            sb_config._browser_shortcut = \"chrome\"\n            browser_list.append(\"--chrome\")\n        if \"--edge\" in sys_argv and not browser_set == \"edge\":\n            browser_changes += 1\n            browser_text = \"edge\"\n            sb_config._browser_shortcut = \"edge\"\n            browser_list.append(\"--edge\")\n        if \"--firefox\" in sys_argv and not browser_set == \"firefox\":\n            browser_changes += 1\n            browser_text = \"firefox\"\n            sb_config._browser_shortcut = \"firefox\"\n            browser_list.append(\"--firefox\")\n        if \"--ie\" in sys_argv and not browser_set == \"ie\":\n            browser_changes += 1\n            browser_text = \"ie\"\n            sb_config._browser_shortcut = \"ie\"\n            browser_list.append(\"--ie\")\n        if \"--safari\" in sys_argv and not browser_set == \"safari\":\n            browser_changes += 1\n            browser_text = \"safari\"\n            sb_config._browser_shortcut = \"safari\"\n            browser_list.append(\"--safari\")\n        if \"--opera\" in sys_argv and not browser_set == \"opera\":\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"opera\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_text = \"opera\"\n                    sb_config._browser_shortcut = \"opera\"\n                    sb_config._cdp_browser = \"opera\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--opera\")\n        if \"--brave\" in sys_argv and not browser_set == \"brave\":\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"brave\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_text = \"brave\"\n                    sb_config._browser_shortcut = \"brave\"\n                    sb_config._cdp_browser = \"brave\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--brave\")\n        if \"--comet\" in sys_argv and not browser_set == \"comet\":\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"comet\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_text = \"comet\"\n                    sb_config._browser_shortcut = \"comet\"\n                    sb_config._cdp_browser = \"comet\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--comet\")\n        if \"--atlas\" in sys_argv and not browser_set == \"atlas\":\n            if not bin_loc_in_options:\n                bin_loc = detect_b_ver.get_binary_location(\"atlas\")\n                if os.path.exists(bin_loc):\n                    browser_changes += 1\n                    browser_text = \"atlas\"\n                    sb_config._browser_shortcut = \"atlas\"\n                    sb_config._cdp_browser = \"atlas\"\n                    sb_config._cdp_bin_loc = bin_loc\n                    browser_list.append(\"--atlas\")\n        if browser_changes > 1:\n            message = \"\\n\\n  TOO MANY browser types were entered!\"\n            message += \"\\n  There were %s found:\\n  >  %s\" % (\n                browser_changes,\n                \", \".join(browser_list),\n            )\n            message += \"\\n  ONLY ONE default browser is allowed!\"\n            message += \"\\n  Select a single browser & try again!\\n\"\n            raise Exception(message)\n        if browser_text:\n            browser = browser_text\n        if self.options.recorder_mode and browser not in [\n            \"chrome\", \"edge\", \"opera\", \"brave\", \"comet\", \"atlas\", \"chromium\"\n        ]:\n            message = (\n                \"\\n\\n  Recorder Mode ONLY supports Chromium browsers!\"\n                '\\n  (Your browser choice was: \"%s\")\\n' % browser\n            )\n            raise Exception(message)\n        window_position = self.options.window_position\n        if window_position:\n            if window_position.count(\",\") != 1:\n                message = (\n                    '\\n\\n  window_position expects an \"x,y\" string!'\n                    '\\n  (Your input was: \"%s\")\\n' % window_position\n                )\n                raise Exception(message)\n            window_position = window_position.replace(\" \", \"\")\n            win_x = None\n            win_y = None\n            try:\n                win_x = int(window_position.split(\",\")[0])\n                win_y = int(window_position.split(\",\")[1])\n            except Exception:\n                message = (\n                    '\\n\\n  Expecting integer values for \"x,y\"!'\n                    '\\n  (window_position input was: \"%s\")\\n'\n                    % window_position\n                )\n                raise Exception(message)\n            settings.WINDOW_START_X = win_x\n            settings.WINDOW_START_Y = win_y\n        window_size = self.options.window_size\n        if window_size:\n            if window_size.count(\",\") != 1:\n                message = (\n                    '\\n\\n  window_size expects a \"width,height\" string!'\n                    '\\n  (Your input was: \"%s\")\\n' % window_size\n                )\n                raise Exception(message)\n            window_size = window_size.replace(\" \", \"\")\n            width = None\n            height = None\n            try:\n                width = int(window_size.split(\",\")[0])\n                height = int(window_size.split(\",\")[1])\n            except Exception:\n                message = (\n                    '\\n\\n  Expecting integer values for \"width,height\"!'\n                    '\\n  (window_size input was: \"%s\")\\n' % window_size\n                )\n                raise Exception(message)\n            settings.CHROME_START_WIDTH = width\n            settings.CHROME_START_HEIGHT = height\n            settings.HEADLESS_START_WIDTH = width\n            settings.HEADLESS_START_HEIGHT = height\n        test.test.is_nosetest = True\n        test.test.is_behave = False\n        test.test.is_pytest = False\n        test.test.is_context_manager = False\n        sb_config.is_nosetest = True\n        sb_config.is_behave = False\n        sb_config.is_pytest = False\n        sb_config.is_context_manager = False\n        test.test.browser = self.options.browser\n        if sb_config._browser_shortcut:\n            self.options.browser = sb_config._browser_shortcut\n            test.test.browser = sb_config._browser_shortcut\n        test.test.cap_file = self.options.cap_file\n        test.test.cap_string = self.options.cap_string\n        test.test.headless = self.options.headless\n        test.test.headless1 = self.options.headless1\n        if test.test.headless1:\n            test.test.headless = True\n        test.test.headless2 = self.options.headless2\n        if test.test.headless and test.test.browser == \"safari\":\n            test.test.headless = False  # Safari doesn't use headless\n            test.test.headless1 = False\n        if test.test.headless2 and test.test.browser == \"firefox\":\n            test.test.headless2 = False  # Only for Chromium browsers\n            test.test.headless = True  # Firefox has regular headless\n            self.options.headless2 = False\n            self.options.headless = True\n        elif test.test.browser not in [\"chrome\", \"edge\"]:\n            test.test.headless2 = False  # Only for Chromium browsers\n            self.options.headless2 = False\n        test.test.headed = self.options.headed\n        test.test.xvfb = self.options.xvfb\n        test.test.xvfb_metrics = self.options.xvfb_metrics\n        test.test.locale_code = self.options.locale_code\n        test.test.interval = self.options.interval\n        test.test.start_page = self.options.start_page\n        if self.options.skip_js_waits:\n            settings.SKIP_JS_WAITS = True\n        if self.options.wait_for_angularjs:\n            settings.WAIT_FOR_ANGULARJS = True\n        test.test.protocol = self.options.protocol\n        test.test.servername = self.options.servername\n        test.test.port = self.options.port\n        test.test.user_data_dir = self.options.user_data_dir\n        test.test.extension_zip = self.options.extension_zip\n        test.test.extension_dir = self.options.extension_dir\n        test.test.disable_features = self.options.disable_features\n        test.test.binary_location = self.options.binary_location\n        if getattr(sb_config, \"_cdp_bin_loc\", None):\n            test.test.binary_location = sb_config._cdp_bin_loc\n        if self.options.use_chromium and not test.test.binary_location:\n            test.test.binary_location = \"_chromium_\"\n        elif self.options.use_cft and not test.test.binary_location:\n            test.test.binary_location = \"cft\"\n        elif self.options.use_chs and not test.test.binary_location:\n            test.test.binary_location = \"chs\"\n        sb_config.binary_location = test.test.binary_location\n        if (\n            test.test.binary_location\n            and test.test.binary_location.lower() == \"chs\"\n            and test.test.browser == \"chrome\"\n        ):\n            test.test.headless = True\n            test.test.headless1 = False\n            test.test.headless2 = False\n        if test.test.browser in constants.ChromiumSubs.chromium_subs:\n            if not sb_config.binary_location:\n                test.test.browser = \"chrome\"  # Still uses chromedriver\n                sb_config._browser_shortcut = test.test.browser\n        test.test.driver_version = self.options.driver_version\n        test.test.page_load_strategy = self.options.page_load_strategy\n        test.test.chromium_arg = self.options.chromium_arg\n        test.test.firefox_arg = self.options.firefox_arg\n        test.test.firefox_pref = self.options.firefox_pref\n        test.test.proxy_string = self.options.proxy_string\n        test.test.proxy_bypass_list = self.options.proxy_bypass_list\n        test.test.proxy_pac_url = self.options.proxy_pac_url\n        test.test.multi_proxy = self.options.multi_proxy\n        test.test.user_agent = self.options.user_agent\n        test.test.mobile_emulator = self.options.mobile_emulator\n        test.test.device_metrics = self.options.device_metrics\n        test.test.time_limit = self.options.time_limit\n        test.test.slow_mode = self.options.slow_mode\n        test.test.demo_mode = self.options.demo_mode\n        test.test.demo_sleep = self.options.demo_sleep\n        test.test.highlights = self.options.highlights\n        test.test.message_duration = self.options.message_duration\n        test.test.js_checking_on = self.options.js_checking_on\n        test.test.ad_block_on = self.options.ad_block_on\n        test.test.host_resolver_rules = self.options.host_resolver_rules\n        test.test.block_images = self.options.block_images\n        test.test.do_not_track = self.options.do_not_track\n        test.test.verify_delay = self.options.verify_delay  # MasterQA\n        test.test.esc_end = self.options.esc_end\n        test.test.recorder_mode = self.options.recorder_mode\n        test.test.recorder_ext = self.options.recorder_mode  # Again\n        test.test.rec_behave = self.options.rec_behave\n        test.test.rec_print = self.options.rec_print\n        test.test.record_sleep = self.options.record_sleep\n        if self.options.rec_print:\n            test.test.recorder_mode = True\n            test.test.recorder_ext = True\n        elif self.options.rec_behave:\n            test.test.recorder_mode = True\n            test.test.recorder_ext = True\n        elif self.options.record_sleep:\n            test.test.recorder_mode = True\n            test.test.recorder_ext = True\n        test.test.disable_cookies = self.options.disable_cookies\n        test.test.disable_js = self.options.disable_js\n        test.test.disable_csp = self.options.disable_csp\n        test.test.disable_ws = self.options.disable_ws\n        test.test.enable_ws = self.options.enable_ws\n        if not self.options.disable_ws:\n            test.test.enable_ws = True\n        test.test.enable_sync = self.options.enable_sync\n        test.test.use_auto_ext = self.options.use_auto_ext\n        test.test.undetectable = self.options.undetectable\n        test.test.uc_cdp_events = self.options.uc_cdp_events\n        test.test.log_cdp_events = self.options.log_cdp_events\n        if test.test.uc_cdp_events and not test.test.undetectable:\n            test.test.undetectable = True\n        test.test.uc_subprocess = self.options.uc_subprocess\n        if test.test.uc_subprocess and not test.test.undetectable:\n            test.test.undetectable = True\n        test.test.no_sandbox = self.options.no_sandbox\n        test.test.disable_gpu = self.options.disable_gpu\n        test.test.remote_debug = self.options.remote_debug\n        test.test.enable_3d_apis = self.options.enable_3d_apis\n        test.test._swiftshader = self.options.swiftshader\n        test.test.incognito = self.options.incognito\n        test.test.guest_mode = self.options.guest_mode\n        test.test.dark_mode = self.options.dark_mode\n        test.test.devtools = self.options.devtools\n        test.test._disable_beforeunload = self.options._disable_beforeunload\n        test.test.window_position = self.options.window_position\n        test.test.window_size = self.options.window_size\n        test.test.maximize_option = self.options.maximize_option\n        if self.options.save_screenshot and self.options.no_screenshot:\n            self.options.save_screenshot = False  # no_screenshot has priority\n        test.test.save_screenshot_after_test = self.options.save_screenshot\n        test.test.no_screenshot_after_test = self.options.no_screenshot\n        test.test.visual_baseline = self.options.visual_baseline\n        test.test.use_wire = self.options.use_wire\n        test.test.external_pdf = self.options.external_pdf\n        test.test.timeout_multiplier = self.options.timeout_multiplier\n        test.test.dashboard = False\n        test.test._multithreaded = False\n        test.test._reuse_session = False\n        sb_config.recorder_mode = test.test.recorder_mode\n        sb_config.no_screenshot = test.test.no_screenshot_after_test\n        if test.test.servername != \"localhost\":\n            # Using Selenium Grid\n            # (Set --server=\"127.0.0.1\" for localhost Grid)\n            if str(self.options.port) == \"443\":\n                test.test.protocol = \"https\"\n        if (\n            shared_utils.is_linux()\n            and not self.options.headed\n            and not self.options.headless\n            and not self.options.headless2\n            and not self.options.xvfb\n        ):\n            if not self.options.undetectable:\n                print(\n                    \"(Linux uses --headless by default. \"\n                    \"To override, use --headed / --gui. \"\n                    \"For Xvfb mode instead, use --xvfb. \"\n                    \"Or you can hide this info by using \"\n                    \"--headless / --headless2 / --uc.)\"\n                )\n                self.options.headless = True\n                test.test.headless = True\n            else:\n                self.options.xvfb = True\n                test.test.xvfb = True\n        if self.options.use_wire and self.options.undetectable:\n            print(\n                \"\\n\"\n                \"SeleniumBase doesn't support mixing --uc with --wire mode.\\n\"\n                \"If you need both, override get_new_driver() from BaseCase:\\n\"\n                \"https://seleniumbase.io/help_docs/syntax_formats/#sb_sf_09\\n\"\n                \"(Only UC Mode without Wire Mode will be used for this run)\\n\"\n            )\n            self.options.use_wire = False\n            test.test.use_wire = False\n        # Recorder Mode can still optimize scripts in --headless2 mode.\n        if self.options.recorder_mode and self.options.headless:\n            self.options.headless = False\n            self.options.headless1 = False\n            self.options.headless2 = True\n            test.test.headless = False\n            test.test.headless1 = False\n            test.test.headless2 = True\n        if not self.options.headless and not self.options.headless2:\n            self.options.headed = True\n            test.test.headed = True\n        sb_config._virtual_display = None\n        sb_config.headless_active = False\n        self.headless_active = False\n        if (\n            shared_utils.is_linux()\n            and (not self.options.headed or self.options.xvfb)\n        ):\n            width = settings.HEADLESS_START_WIDTH\n            height = settings.HEADLESS_START_HEIGHT\n            with suppress(Exception):\n                from sbvirtualdisplay import Display\n\n                self._xvfb_display = Display(visible=0, size=(width, height))\n                self._xvfb_display.start()\n                sb_config._virtual_display = self._xvfb_display\n                self.headless_active = True\n                sb_config.headless_active = True\n        sb_config._is_timeout_changed = False\n        sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT\n        sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT\n        sb_config._context_of_runner = False  # Context Manager Compatibility\n        sb_config.mobile_emulator = self.options.mobile_emulator\n        sb_config.proxy_driver = self.options.proxy_driver\n        sb_config.multi_proxy = self.options.multi_proxy\n        # The driver will be received later\n        self.driver = None\n        test.test.driver = self.driver\n\n    def finalize(self, result):\n        \"\"\"This runs after all tests have completed with nosetests.\"\"\"\n        if not getattr(sb_config, \"multi_proxy\", None):\n            proxy_helper.remove_proxy_zip_if_present()\n\n    def afterTest(self, test):\n        try:\n            # If the browser window is still open, close it now.\n            if (\n                not shared_utils.is_windows()\n                or test.test.browser == \"ie\"\n                or self.driver.service.process\n            ):\n                self.driver.quit()\n        except AttributeError:\n            pass\n        except Exception:\n            pass\n        with suppress(Exception):\n            if (\n                getattr(self, \"_xvfb_display\", None)\n                and hasattr(self._xvfb_display, \"stop\")\n            ):\n                self.headless_active = False\n                sb_config.headless_active = False\n                self._xvfb_display.stop()\n                self._xvfb_display = None\n            if (\n                getattr(sb_config, \"_virtual_display\", None)\n                and hasattr(sb_config._virtual_display, \"stop\")\n            ):\n                sb_config._virtual_display.stop()\n                sb_config._virtual_display = None\n"
  },
  {
    "path": "seleniumbase/resources/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n## [<img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"32\">](https://github.com/seleniumbase/SeleniumBase/) Resource Files\n\nSeleniumBase uses JavaScript libraries for bonus features such as the Website Tour Maker, Presentation Maker, Chart Maker, Demo Mode, HTML Inspector, and more. In general, SeleniumBase retrieves these resources via CDN link.\n\n**favicon.ico** - This file is used by [style_sheet.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/core/style_sheet.py) for the favicon icon. Currently, SeleniumBase uses the version at [https://raw.githubusercontent.com/seleniumbase/SeleniumBase/master/seleniumbase/resources/favicon.ico](https://raw.githubusercontent.com/seleniumbase/SeleniumBase/master/seleniumbase/resources/favicon.ico).\n\n--------\n\nThe remaining resources have been moved into [github.com/seleniumbase/resource-files](https://github.com/seleniumbase/resource-files) in order to reduce the size of SeleniumBase:\n\n**messenger/** - Files in this folder are used for creating JavaScript notifications during test runs in Demo Mode.\n\n**jquery_confirm/** - Files in this folder are used for creating JavaScript confirmation prompts during test runs when using MasterQA.\n\n**html_inspector/** - Files in this folder are used for the HTML Inspector, which validates website pages.\n\n**reveal/** - Files in this folder are used for the HTML Presentation Maker.\n\n**prettify/** - Files in this folder are used to assist the HTML Presentation Maker.\n\n**shepherd/** - Files in this folder are used for creating website tours using the Shepherd JavaScript library.\n\n**bootstrap_tour/** - Files in this folder are used for creating website tours using the Bootstrap Tour JavaScript library.\n\n**introjs/** - Files in this folder are used for creating website tours using the IntroJS JavaScript library.\n\n**driverjs/** - Files in this folder are used for creating website tours using the DriverJS JavaScript library.\n\n**hopscotch/** - Files in this folder are used for creating website tours using the Hopscotch JavaScript library.\n"
  },
  {
    "path": "seleniumbase/resources/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/translate/__init__.py",
    "content": "from seleniumbase.translate import chinese  # noqa\nfrom seleniumbase.translate import dutch  # noqa\nfrom seleniumbase.translate import french  # noqa\nfrom seleniumbase.translate import italian  # noqa\nfrom seleniumbase.translate import japanese  # noqa\nfrom seleniumbase.translate import korean  # noqa\nfrom seleniumbase.translate import portuguese  # noqa\nfrom seleniumbase.translate import russian  # noqa\nfrom seleniumbase.translate import spanish  # noqa\n"
  },
  {
    "path": "seleniumbase/translate/chinese.py",
    "content": "# Chinese / 中文 - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass 硒测试用例(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Chinese\"\n\n    def 开启(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def 开启网址(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def 单击(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def 双击(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def 上下文点击(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def 慢单击(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def 如果可见请单击(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def JS如果存在请单击(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def 单击链接文本(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def 鼠标点击偏移(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def 更新文本(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def 输入文本(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def 添加文本(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def 获取文本(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def 断言文本(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def 确切断言文本(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def 断言链接文本(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def 断言非空文本(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def 断言文本不可见(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def 断言元素(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def 断言元素可见(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def 断言元素不可见(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def 断言元素存在(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def 断言元素不存在(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def 断言属性(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def 断言URL(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def 断言URL包含(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def 断言标题(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def 断言标题包含(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def 获取标题(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def 断言为真(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def 断言为假(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def 断言等于(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def 断言不等于(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def 刷新页面(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def 获取当前网址(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def 获取页面源代码(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def 回去(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def 向前(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def 文本是否显示(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def 确切文本是否显示(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def 元素是否可见(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def 元素是否启用(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def 元素是否存在(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def 等待文本(self, *args, **kwargs):\n        # wait_for_text(text, selector=\"html\")\n        return self.wait_for_text(*args, **kwargs)\n\n    def 等待元素(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def 等待元素可见(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def 等待元素不可见(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def 等待元素存在(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def 等待元素不存在(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def 等待属性(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def 等待页面加载完成(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def 睡(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def 等待(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def 提交(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def 清除(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def 专注于(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def JS单击(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def JS更新文本(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def JS输入文本(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def JQUERY单击(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def JQUERY更新文本(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def JQUERY输入文本(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def 检查HTML(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def 保存截图(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def 保存截图到日志(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def 选择文件(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def 执行脚本(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def 安全执行脚本(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def 加载JQUERY(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def 加载RECORDER(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def 开启如果不网址(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def 阻止广告(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def 跳过(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def 检查断开的链接(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def 检查JS错误(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def 切换到帧(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def 切换到默认内容(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def 切换到父框架(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def 打开新窗口(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def 切换到窗口(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def 切换到默认窗口(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def 切换到最新的窗口(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def 最大化窗口(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def 亮点(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def 亮点单击(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def 滚动到(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def 滚动到顶部(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def 滚动到底部(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def 鼠标悬停并单击(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def 鼠标悬停(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def 是否被选中(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def 按向上箭头(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def 按向下箭头(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def 按向左箭头(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def 按向右箭头(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def 单击可见元素(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def 按文本选择选项(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def 按索引选择选项(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def 按值选择选项(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def 创建演示文稿(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def 添加幻灯片(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def 保存演示文稿(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def 开始演示文稿(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def 创建饼图(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def 创建条形图(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def 创建柱形图(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def 创建折线图(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def 创建面积图(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def 将系列添加到图表(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def 添加数据点(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def 保存图表(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def 显示图表(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def 提取图表(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def 创建游览(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def 创建SHEPHERD游览(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def 创建BOOTSTRAP游览(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def 创建DRIVERJS游览(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def 创建HOPSCOTCH游览(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def 创建INTROJS游览(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def 添加游览步骤(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def 播放游览(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def 导出游览(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def 获取PDF文本(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def 断言PDF文本(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def 下载文件(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def 下载的文件是否存在(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def 获取下载的文件路径(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def 检查下载的文件(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def 删除下载的文件(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def 失败(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def 获取(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def 访问(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def 访问网址(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def 获取元素(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def 查找元素(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def 删除第一个元素(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def 删除所有元素(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def 查找文本(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def 设置文本(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def 获取属性(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def 设置属性(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def 设置所有属性(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def 写文本(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def 设置消息主题(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def 显示讯息(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def 打印(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def 推迟断言元素(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def 推迟断言文本(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def 处理推迟断言(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def 接受警报(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def 解除警报(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def 切换到警报(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def 拖放(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def 设置HTML(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def 加载HTML文件(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def 打开HTML文件(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def 删除所有COOKIE(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def 获取用户代理(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def 获取语言代码(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_中文(MasterQA, 硒测试用例):\n    def 校验(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"手动检查\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"页面是否看起来不错？\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/dutch.py",
    "content": "# Dutch / Nederlands - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass Testgeval(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Dutch\"\n\n    def openen(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def url_openen(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def klik(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def dubbelklik(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def contextklik(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def klik_langzaam(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def klik_indien_zichtbaar(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def js_klik_indien_aanwezig(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def klik_linktekst(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def klik_op_locatie(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def tekst_bijwerken(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def typ(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def tekst_toevoegen(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def tekst_ophalen(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def controleren_tekst(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def controleren_exacte_tekst(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def controleren_linktekst(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def controleren_niet_lege_tekst(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def controleren_tekst_niet_zichtbaar(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def controleren_element(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def controleren_element_zichtbaar(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def controleren_element_niet_zichtbaar(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def controleren_element_aanwezig(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def controleren_element_afwezig(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def controleren_attribuut(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def controleren_url(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def controleren_url_bevat(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def controleren_titel(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def controleren_titel_bevat(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def titel_ophalen(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def controleren_ware(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def controleren_valse(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def controleren_gelijk(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def controleren_niet_gelijk(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def ververs_pagina(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def huidige_url_ophalen(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def broncode_ophalen(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def terug(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def vooruit(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def tekst_zichtbaar(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def exacte_tekst_zichtbaar(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def element_zichtbaar(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def element_ingeschakeld(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def element_aanwezig(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def wachten_op_tekst(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def wachten_op_element(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def wachten_op_element_zichtbaar(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def wachten_op_element_niet_zichtbaar(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def wachten_op_element_aanwezig(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def wachten_op_element_afwezig(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def wachten_op_attribuut(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def wacht_tot_de_pagina_is_geladen(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def slapen(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def wachten(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def verzenden(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def wissen(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def focussen(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def js_klik(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def js_tekst_bijwerken(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def js_typ(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def jquery_klik(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def jquery_tekst_bijwerken(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def jquery_typ(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def html_inspecteren(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def bewaar_screenshot(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def bewaar_screenshot_om_te_loggen(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def selecteer_bestand(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def script_uitvoeren(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def script_veilig_uitvoeren(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def activeer_jquery(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def activeer_recorder(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def openen_zo_niet_url(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def blokkeer_advertenties(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def overslaan(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def controleren_op_gebroken_links(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def controleren_op_js_fouten(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def overschakelen_naar_frame(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def overschakelen_naar_standaardcontent(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def overschakelen_naar_bovenliggend_frame(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def nieuw_venster_openen(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def overschakelen_naar_venster(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def overschakelen_naar_standaardvenster(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def overschakelen_naar_nieuwste_venster(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def venster_maximaliseren(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def markeren(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def markeren_klik(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def scrollen_naar(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def naar_boven_scrollen(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def naar_beneden_scrollen(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def zweven_en_klik(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def zweven(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def is_het_geselecteerd(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def druk_op_pijl_omhoog(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def druk_op_pijl_omlaag(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def druk_op_pijl_links(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def druk_op_pijl_rechts(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def klik_zichtbare_elementen(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def optie_selecteren_op_tekst(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def optie_selecteren_op_index(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def optie_selecteren_op_waarde(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def maak_een_presentatie(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def een_dia_toevoegen(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def de_presentatie_opslaan(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def de_presentatie_starten(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def maak_een_cirkeldiagram(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def maak_een_staafdiagram(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def maak_een_kolomdiagram(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def maak_een_lijndiagram(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def maak_een_vlakdiagram(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def reeksen_toevoegen_aan_grafiek(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def gegevenspunt_toevoegen(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def grafiek_opslaan(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def grafiek_weergeven(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def grafiek_uitpakken(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def maak_een_tour(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def maak_een_shepherd_tour(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def maak_een_bootstrap_tour(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def maak_een_driverjs_tour(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def maak_een_hopscotch_tour(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def maak_een_introjs_tour(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def toevoegen_tour_stap(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def speel_de_tour(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def de_tour_exporteren(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def pdf_tekst_ophalen(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def controleren_pdf_tekst(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def bestand_downloaden(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def gedownloade_bestand_aanwezig(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def pad_gedownloade_bestand_ophalen(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def controleren_gedownloade_bestand(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def verwijder_gedownloade_bestand(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def mislukken(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def ophalen(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def bezoek(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def bezoek_url(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def element_ophalen(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def vind_element(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def verwijder_element(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def verwijder_elementen(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def vind_tekst(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def tekst_instellen(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def attribuut_ophalen(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def attribuut_instellen(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def attributen_instellen(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def schrijven(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def thema_van_bericht_instellen(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def bericht_weergeven(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def afdrukken(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def uitgestelde_controleren_element(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def uitgestelde_controleren_tekst(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def verwerken_uitgestelde_controleren(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def waarschuwing_accepteren(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def waarschuwing_wegsturen(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def overschakelen_naar_waarschuwing(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def slepen_en_neerzetten(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def html_instellen(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def html_bestand_laden(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def html_bestand_openen(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def alle_cookies_verwijderen(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def gebruikersagent_ophalen(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def taalcode_ophalen(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_Nederlands(MasterQA, Testgeval):\n    def controleren(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"Handmatige controle\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"Ziet de pagina er goed uit?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/french.py",
    "content": "# French / Français - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass CasDeBase(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"French\"\n\n    def ouvrir(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def ouvrir_url(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def cliquer(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def double_cliquer(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def contextuel_cliquer(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def cliquer_lentement(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def cliquer_si_affiché(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def js_cliquer_si_présent(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def cliquer_texte_du_lien(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def cliquer_emplacement(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def modifier_texte(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def taper(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def ajouter_texte(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def obtenir_texte(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def vérifier_texte(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def vérifier_texte_exactement(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def vérifier_texte_du_lien(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def vérifier_texte_non_vide(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def vérifier_texte_pas_affiché(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def vérifier_élément(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def vérifier_élément_affiché(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def vérifier_élément_pas_affiché(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def vérifier_élément_présent(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def vérifier_élément_pas_présent(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def vérifier_attribut(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def vérifier_url(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def vérifier_url_contient(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def vérifier_titre(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def vérifier_titre_contient(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def obtenir_titre(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def vérifier_vrai(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def vérifier_faux(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def vérifier_égal(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def vérifier_non_égal(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def rafraîchir_la_page(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def obtenir_url_actuelle(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def obtenir_html_de_la_page(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def retour(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def en_avant(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def est_texte_affiché(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def est_texte_exactement_affiché(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def est_un_élément_affiché(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def est_un_élément_activé(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def est_un_élément_présent(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def attendre_le_texte(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def attendre_un_élément(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def attendre_un_élément_affiché(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def attendre_un_élément_pas_affiché(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def attendre_un_élément_présent(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def attendre_un_élément_pas_présent(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def attendre_un_attribut(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def attendre_que_la_page_se_charge(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def dormir(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def attendre(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def soumettre(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def effacer(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def concentrer(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def js_cliquer(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def js_modifier_texte(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def js_taper(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def jquery_cliquer(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def jquery_modifier_texte(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def jquery_taper(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def vérifier_html(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def enregistrer_capture_d_écran(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def enregistrer_capture_d_écran_aux_logs(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def sélectionner_fichier(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def exécuter_script(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def exécuter_script_sans_risque(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def activer_jquery(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def activer_recorder(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def ouvrir_si_non_url(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def annonces_de_bloc(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def sauter(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def vérifier_les_liens_rompus(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def vérifier_les_erreurs_js(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def passer_au_cadre(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def passer_au_contenu_par_défaut(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def passer_au_cadre_parent(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def ouvrir_une_nouvelle_fenêtre(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def passer_à_fenêtre(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def passer_à_fenêtre_par_défaut(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def passer_à_fenêtre_dernière(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def maximiser_fenêtre(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def illuminer(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def illuminer_cliquer(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def déménager_à(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def faites_défiler_vers_le_haut(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def faites_défiler_vers_le_bas(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def passer_la_souris_et_cliquer(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def survol_de_la_souris(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def est_il_sélectionné(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def appuyer_sur_flèche_haut(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def appuyer_sur_flèche_bas(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def appuyer_sur_flèche_gauche(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def appuyer_sur_flèche_droite(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def cliquer_éléments_visibles(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def sélectionner_option_par_texte(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def sélectionner_option_par_index(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def sélectionner_option_par_valeur(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def créer_une_présentation(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def ajouter_une_diapositive(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def enregistrer_la_présentation(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def démarrer_la_présentation(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def créer_un_graphique_à_secteurs(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def créer_un_graphique_à_barres(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def créer_un_graphique_à_colonnes(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def créer_un_graphique_linéaire(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def créer_un_graphique_en_aires(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def ajouter_séries_au_graphique(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def ajouter_un_point_de_données(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def enregistrer_le_graphique(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def afficher_le_graphique(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def extraire_le_graphique(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def créer_une_visite(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def créer_une_visite_shepherd(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def créer_une_visite_bootstrap(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def créer_une_visite_driverjs(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def créer_une_visite_hopscotch(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def créer_une_visite_introjs(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def ajouter_étape_à_la_visite(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def jouer_la_visite(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def exporter_la_visite(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def obtenir_texte_pdf(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def vérifier_texte_pdf(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def télécharger_fichier(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def est_un_fichier_téléchargé_présent(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def obtenir_chemin_du_fichier_téléchargé(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def vérifier_fichier_téléchargé(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def supprimer_fichier_téléchargé(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def échouer(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def obtenir(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def visiter(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def visiter_url(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def obtenir_élément(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def trouver_élément(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def supprimer_élément(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def supprimer_éléments(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def trouver_texte(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def définir_texte(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def obtenir_attribut(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def définir_attribut(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def définir_attributs(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def écriver(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def définir_thème_du_message(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def afficher_message(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def imprimer(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def reporté_vérifier_élément(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def reporté_vérifier_texte(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def effectuer_vérifications_reportées(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def accepter_alerte(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def rejeter_alerte(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def passer_à_alerte(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def glisser_et_déposer(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def définir_html(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def charger_html_fichier(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def ouvrir_html_fichier(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def supprimer_tous_les_cookies(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def obtenir_agent_utilisateur(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def obtenir_code_de_langue(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_Français(MasterQA, CasDeBase):\n    def vérifier(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"Vérification manuelle\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"La page est-elle bonne?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/italian.py",
    "content": "# Italian / Italiano - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass CasoDiProva(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Italian\"\n\n    def apri(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def apri_url(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def fare_clic(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def doppio_clic(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def clic_contestuale(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def clic_lentamente(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def clic_se_visto(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def js_clic_se_presente(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def clic_testo_del_collegamento(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def clic_su_posizione(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def aggiornare_testo(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def digitare(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def aggiungi_testo(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def ottenere_testo(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def verificare_testo(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def verificare_testo_esatto(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def verificare_testo_del_collegamento(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def verificare_testo_non_vuoto(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def verificare_testo_non_visto(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def verificare_elemento(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def verificare_elemento_visto(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def verificare_elemento_non_visto(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def verificare_elemento_presente(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def verificare_elemento_assente(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def verificare_attributo(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def verificare_url(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def verificare_url_contiene(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def verificare_titolo(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def verificare_titolo_contiene(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def ottenere_titolo(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def verificare_vero(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def verificare_falso(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def verificare_uguale(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def verificare_non_uguale(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def aggiorna_la_pagina(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def ottenere_url_corrente(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def ottenere_la_pagina_html(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def indietro(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def avanti(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def è_testo_visto(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def è_testo_esatto_visto(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def è_elemento_visto(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def è_elemento_abilitato(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def è_elemento_presente(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def attendere_il_testo(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def attendere_un_elemento(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def attendere_un_elemento_visto(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def attendere_un_elemento_non_visto(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def attendere_un_elemento_presente(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def attendere_un_elemento_assente(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def attendere_un_attributo(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def attendere_il_caricamento_della_pagina(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def dormire(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def attendere(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def inviare(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def cancellare(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def focalizzare(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def js_fare_clic(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def js_aggiornare_testo(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def js_digitare(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def jquery_fare_clic(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def jquery_aggiornare_testo(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def jquery_digitare(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def controlla_html(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def salva_screenshot(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def salva_screenshot_nei_logs(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def seleziona_file(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def eseguire_script(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def eseguire_script_sicuro(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def attiva_jquery(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def attiva_recorder(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def apri_se_non_url(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def bloccare_gli_annunci(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def saltare(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def verificare_i_collegamenti(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def controlla_errori_js(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def passa_alla_cornice(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def passa_al_contenuto_predefinito(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def passa_alla_cornice_principale(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def apri_una_nuova_finestra(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def passa_alla_finestra(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def passa_alla_finestra_predefinita(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def passa_alla_finestra_ultimo(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def ingrandisci_finestra(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def illuminare(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def illuminare_clic(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def scorrere_fino_a(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def scorri_verso_alto(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def scorri_verso_il_basso(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def passare_il_mouse_e_fare_clic(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def passaggio_del_mouse(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def è_selezionato(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def premere_la_freccia_su(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def premere_la_freccia_giù(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def premere_la_freccia_sinistra(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def premere_la_freccia_destra(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def clic_sugli_elementi_visibili(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def selezionare_opzione_per_testo(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def selezionare_opzione_per_indice(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def selezionare_opzione_per_valore(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def creare_una_presentazione(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def aggiungere_una_diapositiva(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def salva_la_presentazione(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def avviare_la_presentazione(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def creare_un_grafico_a_torta(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def creare_un_grafico_a_barre(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def creare_un_grafico_a_colonne(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def creare_un_grafico_a_linee(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def creare_un_grafico_ad_area(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def aggiungere_serie_al_grafico(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def aggiungi_punto_dati(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def salva_il_grafico(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def mostra_il_grafico(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def estrarre_il_grafico(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def creare_un_tour(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def creare_un_tour_shepherd(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def creare_un_tour_bootstrap(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def creare_un_tour_driverjs(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def creare_un_tour_hopscotch(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def creare_un_tour_introjs(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def aggiungere_passo_al_tour(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def riprodurre_il_tour(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def esportare_il_tour(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def ottenere_testo_pdf(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def verificare_testo_pdf(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def scaricare_file(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def è_file_scaricato_presente(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def ottenere_percorso_del_file_scaricato(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def verificare_file_scaricato(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def eliminare_file_scaricato(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def fallire(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def ottenere(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def visita(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def visita_url(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def ottenere_elemento(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def trovare_elemento(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def rimuovere_elemento(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def rimuovere_elementi(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def trovare_testo(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def impostare_testo(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def ottenere_attributo(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def imposta_attributo(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def impostare_gli_attributi(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def scrivere(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def impostare_tema_del_messaggio(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def visualizza_messaggio(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def stampare(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def differita_verificare_elemento(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def differita_verificare_testo(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def elaborare_differita_verificari(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def accetta_avviso(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def elimina_avviso(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def passa_al_avviso(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def trascinare_e_rilasciare(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def impostare_html(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def caricare_html_file(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def apri_html_file(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def elimina_tutti_i_cookie(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def ottenere_agente_utente(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def ottenere_codice_lingua(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_Italiano(MasterQA, CasoDiProva):\n    def verificare(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"Controllo manuale\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"La pagina ha un bell'aspetto?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/japanese.py",
    "content": "# Japanese / 日本語 - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass セレニウムテストケース(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Japanese\"\n\n    def を開く(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def URLを開く(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def クリックして(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def ダブルクリックして(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def コンテキストクリック(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def ゆっくりクリックして(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def 表示されている場合はクリック(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def 存在する場合はJSクリック(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def リンクテキストをクリックします(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def オフセットでクリック(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def テキストを更新(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def 入力(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def テキストを追加(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def テキストを取得(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def テキストを確認する(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def 正確なテキストを確認する(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def リンクテキストを確認する(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def 空ではないテキストを確認する(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def テキが表示されていないことを確認します(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def 要素を確認する(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def 要素が表示されていることを確認(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def 要素が表示されていないことを確認します(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def 要素が存在することを確認します(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def 要素が存在しないことを確認します(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def 属性を確認する(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def URLを確認する(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def URL部分文字列を確認する(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def タイトルを確認(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def タイトル部分文字列を確認する(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def タイトルを取得する(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def 検証が正しい(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def 検証は偽です(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def 検証が等しい(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def 検証が等しくない(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def ページを更新する(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def 現在のURLを取得(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def ページのソースコードを取得する(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def 戻る(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def 進む(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def テキストが表示されています(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def 正確なテキストが表示されています(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def 要素は表示されますか(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def 要素が有効かどうか(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def 要素が存在するかどうか(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def テキストを待つ(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def 要素を待つ(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def 要素が表示されるのを待ちます(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def 要素が表示されなくなるまで待ちます(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def 要素が存在するのを待つ(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def 要素が存在しないのを待つ(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def 属性を待つ(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def ページがロードされるのを待ちます(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def 眠る(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def 待つ(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def を提出す(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def クリアする(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def 集中する(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def JSクリックして(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def JSテキストを更新(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def JS入力(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def JQUERYクリックして(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def JQUERYテキストを更新(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def JQUERY入力(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def HTMLをチェック(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def スクリーンショットを保存(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def ログにスクリーンショットを保存(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def ファイルを選択(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def スクリプトを実行する(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def スクリプトを安全に実行する(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def JQUERYを読み込む(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def RECORDERを読み込む(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def URLでない場合は開く(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def ブロック広告(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def スキップ(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def リンク切れを確認する(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def JSエラーを確認する(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def フレームに切り替えます(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def デフォルトのコンテンツに切り替える(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def 親フレームに切り替えます(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def 新しいウィンドウを開く(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def ウィンドウに切り替え(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def デフォルトのウィンドウに切り替える(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def 最新のウィンドウに切り替えます(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def ウィンドウを最大化する(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def ハイライト(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def ハイライトしてクリックして(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def スクロールして(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def 一番上までスクロール(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def 一番下までスクロール(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def マウスオーバーしてクリック(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def マウスオーバー(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def 選択されていることを(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def 上矢印を押します(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def 下矢印を押します(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def 左矢印を押します(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def 右矢印を押します(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def 表示要素をクリックします(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def テキストでオプションを選択(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def インデックスでオプションを選択(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def 値でオプションを選択(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def プレゼンテーションを作成する(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def スライドを追加する(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def プレゼンテーションを保存する(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def プレゼンテーションを開始する(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def 円グラフを作成する(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def 棒グラフを作成する(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def 縦棒グラフを作成する(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def 折れ線グラフを作成する(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def 面グラフを作成する(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def グラフに系列を追加する(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def データポイントを追加する(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def グラフを保存する(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def グラフを表示する(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def グラフを抽出する(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def ツアーを作成する(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def SHEPHERDツアーを作成する(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def BOOTSTRAPツアーを作成する(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def DRIVERJSツアーを作成する(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def HOPSCOTCHツアーを作成する(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def INTROJSツアーを作成する(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def ツアーステップを追加する(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def ツアーを再生する(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def ツアーをエクスポートする(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def PDFテキストを取得(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def PDFテキストを確認する(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def ファイルをダウンロード(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def ダウンロードしたファイルが存在するかどうか(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def ダウンロードしたファイルパスを取得する(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def ダウンロードしたファイルを確認する(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def ダウンロードしたファイルを削除する(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def 失敗(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def を取得する(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def を訪問(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def URLを訪問(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def 要素を取得する(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def 要素を見つける(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def 最初の要素を削除(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def すべての要素を削除(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def テキストを見つける(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def テキストを設定する(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def 属性を取得する(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def 属性を設定する(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def すべての属性を設定(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def 書く(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def メッセージのスタイルを設定する(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def メッセージを表示する(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def 印刷(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def を延期する要素を確認する(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def を延期するテキストを確認する(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def 遅延アサーションの処理(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def アラートを受け入れる(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def アラートを却下(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def アラートに切り替え(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def ドラッグアンドドロップ(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def HTML設定する(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def HTMLファイルを読み込む(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def HTMLファイルを開く(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def すべてのクッキーを削除する(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def ユーザーエージェントの取得(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def 言語コードを取得する(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_日本語(MasterQA, セレニウムテストケース):\n    def を確認する(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"手動チェック\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"ページは見栄えが良いですか?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/korean.py",
    "content": "# Korean / 한국어 - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass 셀레늄_테스트_케이스(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Korean\"\n\n    def 열기(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def URL_열기(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def 클릭(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def 더블_클릭(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def 컨텍스트_클릭(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def 천천히_클릭(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def 보이는_경우_클릭(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def JS_존재하는지_경우_클릭(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def 링크_텍스트를_클릭합니다(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def 위치를_클릭(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def 텍스트를_업데이트(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def 입력(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def 텍스트를_추가(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def 텍스트를_검색(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def 텍스트_확인(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def 정확한_텍스트를_확인하는(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def 링크_텍스트_확인(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def 비어_있지_않은_텍스트_확인하는(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def 텍스트_보이지_않는지_확인(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def 요소_확인(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def 요소가_보이는지_확인(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def 요소가_보이지_않는지_확인(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def 요소가_존재하는지_확인(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def 요소가_존재하지_않는지_확인(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def 특성_확인(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def URL_확인(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def URL_부분_확인(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def 제목_확인(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def 제목_부분_확인(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def 제목_검색(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def 올바른지_확인(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def 거짓인지_확인(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def 동일한지_확인(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def 동일하지_않다고_어설션(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def 페이지_새로_고침(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def 현재의_URL을_가져(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def 페이지의_소스_코드를_얻을(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def 뒤로(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def 앞으로(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def 텍스트가_표시됩니다(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def 정확한_텍스트가_표시됩니다(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def 요소가_표시됩니다(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def 요소가_활성화돼(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def 요소가_있습니다(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def 텍스트가_나타날_때까지_기다립니다(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def 요소가_나타날_때까지_기다립니다(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def 요소가_표시_될_때까지_기다립니다(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def 요소가_사라질_때까지_기다리십시오(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def 요소가_존재할_때까지_기다립니다(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def 요소가_나타날_때까지_기다리십시오(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def 특성_때까지_기다립니다(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def 페이지가_로드될_때까지_기다립니다(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def 잠을(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def 기다림(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def 제출(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def 지우려면(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def 집중하다(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def JS_클릭(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def JS_텍스트를_업데이트(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def JS_입력(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def JQUERY_클릭(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def JQUERY_텍스트를_업데이트(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def JQUERY_입력(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def HTML_확인(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def 스크린_샷_저장(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def 로그에_스크린_샷_저장(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def 파일을_선택(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def 스크립트를_실행하려면(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def 스크립트를_안전하게_실행(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def JQUERY_로드(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def RECORDER_로드(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def URL_이_아닌_경우_열기(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def 광고_차단(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def 건너뛸(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def 끊어진_링크_확인(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def JS_오류_확인(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def 프레임으로_전환(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def 기본_콘텐츠로_전환(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def 상위_프레임으로_전환(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def 새_창_열기(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def 창으로_전환(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def 기본_창으로_전환(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def 최신_창으로_전환(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def 창_최대화(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def 강조(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def 강조_클릭(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def 요소로_스크롤(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def 맨_위로_스크롤(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def 하단으로_스크롤(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def 마우스오버_및_클릭(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def 마우스오버(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def 선택되어_있는지(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def 위쪽_화살표를_누릅니다(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def 아래쪽_화살표를_누르십시오(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def 왼쪽_화살표를_누르십시오(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def 오른쪽_화살표를_누르십시오(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def 페이지_요소를_클릭_합니다(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def 텍스트로_옵션_선택(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def 인덱스별로_옵션_선택(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def 값별로_옵션_선택(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def 프레젠테이션_만들기(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def 슬라이드_추가(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def 프레젠테이션_저장(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def 프레젠테이션_시작(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def 원형_차트_만들기(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def 막대_차트_만들기(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def 열_차트_만들기(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def 선_차트_만들기(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def 영역_차트_만들기(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def 차트에_시리즈_추가(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def 데이터_포인트_추가(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def 차트_저장(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def 차트_표시(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def 차트_추출(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def 가이드_투어_만들기(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def 가이드_SHEPHERD_투어_만들기(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def 가이드_BOOTSTRAP_투어_만들기(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def 가이드_DRIVERJS_투어_만들기(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def 가이드_HOPSCOTCH_투어_만들기(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def 가이드_INTROJS_투어_만들기(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def 둘러보기_단계_추가(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def 가이드_투어를하다(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def 가이드_투어_내보내기(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def PDF_텍스트를_검색(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def PDF_텍스트_확인(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def 파일_다운로드(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def 다운로드한_파일이_있습니다(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def 다운로드한_파일_경로_가져_오기(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def 다운로드한_파일_확인(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def 다운로드한_파일_삭제(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def 실패(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def 받기(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def 방문(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def 방문_URL(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def 요소_검색(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def 요소를_찾을(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def 첫_번째_요소_제거(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def 모든_요소_제거(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def 텍스트_찾기(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def 텍스트_설정(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def 특성_검색(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def 특성_설정(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def 모든_특성_설정(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def 쓰다(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def 메시지_테마_설정(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def 메시지를_표시(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def 인쇄(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def 연기된_요소_확인(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def 연기된_텍스트_확인(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def 연기된_검증_처리(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def 경고를_수락(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def 경고를_거부(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def 경고로_전환(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def 드래그_앤_드롭(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def HTML_설정(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def HTML_파일_로드(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def HTML_파일_열기(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def 모든_쿠키_삭제(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def 사용자_에이전트_가져_오기(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def 언어_코드를_얻을(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_한국어(MasterQA, 셀레늄_테스트_케이스):\n    def 확인(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"수동 검사\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"페이지가 잘 보이나요?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/master_dict.py",
    "content": "\"\"\"\nMaster Dictionary\n\nTranslations\n0: English\n1: Chinese\n2: Dutch\n3: French\n4: Italian\n5: Japanese\n6: Korean\n7: Portuguese\n8: Russian\n9: Spanish\n\"\"\"\n\n\nclass MD_F:\n    # Master Dictionary Functions\n\n    def get_languages_list():\n        languages = []\n        languages.append(\"English\")\n        languages.append(\"Chinese\")\n        languages.append(\"Dutch\")\n        languages.append(\"French\")\n        languages.append(\"Italian\")\n        languages.append(\"Japanese\")\n        languages.append(\"Korean\")\n        languages.append(\"Portuguese\")\n        languages.append(\"Russian\")\n        languages.append(\"Spanish\")\n        return languages\n\n    def get_parent_classes_list():\n        parent_classes = []\n        parent_classes.append(\"BaseCase\")\n        parent_classes.append(\"硒测试用例\")\n        parent_classes.append(\"Testgeval\")\n        parent_classes.append(\"CasDeBase\")\n        parent_classes.append(\"CasoDiProva\")\n        parent_classes.append(\"セレニウムテストケース\")\n        parent_classes.append(\"셀레늄_테스트_케이스\")\n        parent_classes.append(\"CasoDeTeste\")\n        parent_classes.append(\"ТестНаСелен\")\n        parent_classes.append(\"CasoDePrueba\")\n        return parent_classes\n\n    def get_masterqa_parent_classes_list():\n        parent_classes = []\n        parent_classes.append(\"MasterQA\")\n        parent_classes.append(\"MasterQA_中文\")\n        parent_classes.append(\"MasterQA_Nederlands\")\n        parent_classes.append(\"MasterQA_Français\")\n        parent_classes.append(\"MasterQA_Italiano\")\n        parent_classes.append(\"MasterQA_日本語\")\n        parent_classes.append(\"MasterQA_한국어\")\n        parent_classes.append(\"MasterQA_Português\")\n        parent_classes.append(\"MasterQA_Русский\")\n        parent_classes.append(\"MasterQA_Español\")\n        return parent_classes\n\n    def get_parent_class_lang(parent_class):\n        parent_class_lang = {}\n        parent_class_lang[\"BaseCase\"] = \"English\"\n        parent_class_lang[\"硒测试用例\"] = \"Chinese\"\n        parent_class_lang[\"Testgeval\"] = \"Dutch\"\n        parent_class_lang[\"CasDeBase\"] = \"French\"\n        parent_class_lang[\"CasoDiProva\"] = \"Italian\"\n        parent_class_lang[\"セレニウムテストケース\"] = \"Japanese\"\n        parent_class_lang[\"셀레늄_테스트_케이스\"] = \"Korean\"\n        parent_class_lang[\"CasoDeTeste\"] = \"Portuguese\"\n        parent_class_lang[\"ТестНаСелен\"] = \"Russian\"\n        parent_class_lang[\"CasoDePrueba\"] = \"Spanish\"\n        if parent_class not in parent_class_lang.keys():\n            raise Exception(\n                \"Invalid parent_class {%s} not in {%s}!\"\n                % (parent_class, parent_class_lang.keys())\n            )\n        return parent_class_lang[parent_class]\n\n    def get_mqa_par_class_lang(parent_class):\n        parent_class_lang = {}\n        parent_class_lang[\"MasterQA\"] = \"English\"\n        parent_class_lang[\"MasterQA_中文\"] = \"Chinese\"\n        parent_class_lang[\"MasterQA_Nederlands\"] = \"Dutch\"\n        parent_class_lang[\"MasterQA_Français\"] = \"French\"\n        parent_class_lang[\"MasterQA_Italiano\"] = \"Italian\"\n        parent_class_lang[\"MasterQA_日本語\"] = \"Japanese\"\n        parent_class_lang[\"MasterQA_한국어\"] = \"Korean\"\n        parent_class_lang[\"MasterQA_Português\"] = \"Portuguese\"\n        parent_class_lang[\"MasterQA_Русский\"] = \"Russian\"\n        parent_class_lang[\"MasterQA_Español\"] = \"Spanish\"\n        if parent_class not in parent_class_lang.keys():\n            raise Exception(\n                \"Invalid parent_class {%s} not in {%s}!\"\n                % (parent_class, parent_class_lang.keys())\n            )\n        return parent_class_lang[parent_class]\n\n    def get_lang_parent_class(language):\n        lang_parent_class = {}\n        lang_parent_class[\"English\"] = \"BaseCase\"\n        lang_parent_class[\"Chinese\"] = \"硒测试用例\"\n        lang_parent_class[\"Dutch\"] = \"Testgeval\"\n        lang_parent_class[\"French\"] = \"CasDeBase\"\n        lang_parent_class[\"Italian\"] = \"CasoDiProva\"\n        lang_parent_class[\"Japanese\"] = \"セレニウムテストケース\"\n        lang_parent_class[\"Korean\"] = \"셀레늄_테스트_케이스\"\n        lang_parent_class[\"Portuguese\"] = \"CasoDeTeste\"\n        lang_parent_class[\"Russian\"] = \"ТестНаСелен\"\n        lang_parent_class[\"Spanish\"] = \"CasoDePrueba\"\n        if language not in lang_parent_class.keys():\n            raise Exception(\n                \"Invalid language {%s} not in {%s}!\"\n                % (language, lang_parent_class.keys())\n            )\n        return lang_parent_class[language]\n\n    def get_mqa_lang_par_class(language):\n        lang_parent_class = {}\n        lang_parent_class[\"English\"] = \"MasterQA\"\n        lang_parent_class[\"Chinese\"] = \"MasterQA_中文\"\n        lang_parent_class[\"Dutch\"] = \"MasterQA_Nederlands\"\n        lang_parent_class[\"French\"] = \"MasterQA_Français\"\n        lang_parent_class[\"Italian\"] = \"MasterQA_Italiano\"\n        lang_parent_class[\"Japanese\"] = \"MasterQA_日本語\"\n        lang_parent_class[\"Korean\"] = \"MasterQA_한국어\"\n        lang_parent_class[\"Portuguese\"] = \"MasterQA_Português\"\n        lang_parent_class[\"Russian\"] = \"MasterQA_Русский\"\n        lang_parent_class[\"Spanish\"] = \"MasterQA_Español\"\n        if language not in lang_parent_class.keys():\n            raise Exception(\n                \"Invalid language {%s} not in {%s}!\"\n                % (language, lang_parent_class.keys())\n            )\n        return lang_parent_class[language]\n\n    def get_import_line(language):\n        import_line = {}\n        # - The Default Import Line:\n        import_line[\"English\"] = \"from seleniumbase import BaseCase\"\n        # - Translated Import Lines:\n        import_line[\n            \"Chinese\"\n        ] = \"from seleniumbase.translate.chinese import 硒测试用例\"\n        import_line[\n            \"Dutch\"\n        ] = \"from seleniumbase.translate.dutch import Testgeval\"\n        import_line[\n            \"French\"\n        ] = \"from seleniumbase.translate.french import CasDeBase\"\n        import_line[\n            \"Italian\"\n        ] = \"from seleniumbase.translate.italian import CasoDiProva\"\n        import_line[\n            \"Japanese\"\n        ] = \"from seleniumbase.translate.japanese import セレニウムテストケース\"\n        import_line[\n            \"Korean\"\n        ] = \"from seleniumbase.translate.korean import 셀레늄_테스트_케이스\"\n        import_line[\n            \"Portuguese\"\n        ] = \"from seleniumbase.translate.portuguese import CasoDeTeste\"\n        import_line[\n            \"Russian\"\n        ] = \"from seleniumbase.translate.russian import ТестНаСелен\"\n        import_line[\n            \"Spanish\"\n        ] = \"from seleniumbase.translate.spanish import CasoDePrueba\"\n        if language not in import_line.keys():\n            raise Exception(\n                \"Invalid language {%s} not in {%s}!\"\n                % (language, import_line.keys())\n            )\n        return import_line[language]\n\n    def get_mqa_im_line(language):\n        import_line = {}\n        # - The Default Import Line:\n        import_line[\"English\"] = \"from seleniumbase import MasterQA\"\n        # - Translated Import Lines:\n        import_line[\n            \"Chinese\"\n        ] = \"from seleniumbase.translate.chinese import MasterQA_中文\"\n        import_line[\n            \"Dutch\"\n        ] = \"from seleniumbase.translate.dutch import MasterQA_Nederlands\"\n        import_line[\n            \"French\"\n        ] = \"from seleniumbase.translate.french import MasterQA_Français\"\n        import_line[\n            \"Italian\"\n        ] = \"from seleniumbase.translate.italian import MasterQA_Italiano\"\n        import_line[\n            \"Japanese\"\n        ] = \"from seleniumbase.translate.japanese import MasterQA_日本語\"\n        import_line[\n            \"Korean\"\n        ] = \"from seleniumbase.translate.korean import MasterQA_한국어\"\n        import_line[\n            \"Portuguese\"\n        ] = \"from seleniumbase.translate.portuguese import MasterQA_Português\"\n        import_line[\n            \"Russian\"\n        ] = \"from seleniumbase.translate.russian import MasterQA_Русский\"\n        import_line[\n            \"Spanish\"\n        ] = \"from seleniumbase.translate.spanish import MasterQA_Español\"\n        if language not in import_line.keys():\n            raise Exception(\n                \"Invalid language {%s} not in {%s}!\"\n                % (language, import_line.keys())\n            )\n        return import_line[language]\n\n    def get_locale_code(language):\n        locale_codes = {}\n        locale_codes[\"English\"] = \"en\"\n        locale_codes[\"Chinese\"] = \"zh\"\n        locale_codes[\"Dutch\"] = \"nl\"\n        locale_codes[\"French\"] = \"fr\"\n        locale_codes[\"Italian\"] = \"it\"\n        locale_codes[\"Japanese\"] = \"ja\"\n        locale_codes[\"Korean\"] = \"ko\"\n        locale_codes[\"Portuguese\"] = \"pt\"\n        locale_codes[\"Russian\"] = \"ru\"\n        locale_codes[\"Spanish\"] = \"es\"\n        if language not in locale_codes.keys():\n            raise Exception(\n                \"Invalid language {%s} not in {%s}!\"\n                % (language, locale_codes.keys())\n            )\n        return locale_codes[language]\n\n    def get_locale_list():\n        locale_list = []\n        locale_list.append(\"en\")\n        locale_list.append(\"zh\")\n        locale_list.append(\"nl\")\n        locale_list.append(\"fr\")\n        locale_list.append(\"it\")\n        locale_list.append(\"ja\")\n        locale_list.append(\"ko\")\n        locale_list.append(\"pt\")\n        locale_list.append(\"ru\")\n        locale_list.append(\"es\")\n        return locale_list\n\n\nclass MD_L_Codes:\n    # Master Dictionary Language Codes\n    lang = {}\n    lang[\"English\"] = 0\n    lang[\"Chinese\"] = 1\n    lang[\"Dutch\"] = 2\n    lang[\"French\"] = 3\n    lang[\"Italian\"] = 4\n    lang[\"Japanese\"] = 5\n    lang[\"Korean\"] = 6\n    lang[\"Portuguese\"] = 7\n    lang[\"Russian\"] = 8\n    lang[\"Spanish\"] = 9\n\n\nclass MD:\n    # Master Dictionary\n    md = {}\n    num_langs = len(MD_L_Codes.lang)\n\n    md[\"open\"] = [\"*\"] * num_langs\n    md[\"open\"][0] = \"open\"\n    md[\"open\"][1] = \"开启\"\n    md[\"open\"][2] = \"openen\"\n    md[\"open\"][3] = \"ouvrir\"\n    md[\"open\"][4] = \"apri\"\n    md[\"open\"][5] = \"を開く\"\n    md[\"open\"][6] = \"열기\"\n    md[\"open\"][7] = \"abrir\"\n    md[\"open\"][8] = \"открыть\"\n    md[\"open\"][9] = \"abrir\"\n\n    md[\"open_url\"] = [\"*\"] * num_langs\n    md[\"open_url\"][0] = \"open_url\"\n    md[\"open_url\"][1] = \"开启网址\"\n    md[\"open_url\"][2] = \"url_openen\"\n    md[\"open_url\"][3] = \"ouvrir_url\"\n    md[\"open_url\"][4] = \"apri_url\"\n    md[\"open_url\"][5] = \"URLを開く\"\n    md[\"open_url\"][6] = \"URL_열기\"\n    md[\"open_url\"][7] = \"abrir_url\"\n    md[\"open_url\"][8] = \"открыть_URL\"\n    md[\"open_url\"][9] = \"abrir_url\"\n\n    md[\"click\"] = [\"*\"] * num_langs\n    md[\"click\"][0] = \"click\"\n    md[\"click\"][1] = \"单击\"\n    md[\"click\"][2] = \"klik\"\n    md[\"click\"][3] = \"cliquer\"\n    md[\"click\"][4] = \"fare_clic\"\n    md[\"click\"][5] = \"クリックして\"\n    md[\"click\"][6] = \"클릭\"\n    md[\"click\"][7] = \"clique\"\n    md[\"click\"][8] = \"нажмите\"\n    md[\"click\"][9] = \"haga_clic\"\n\n    md[\"double_click\"] = [\"*\"] * num_langs\n    md[\"double_click\"][0] = \"double_click\"\n    md[\"double_click\"][1] = \"双击\"\n    md[\"double_click\"][2] = \"dubbelklik\"\n    md[\"double_click\"][3] = \"double_cliquer\"\n    md[\"double_click\"][4] = \"doppio_clic\"\n    md[\"double_click\"][5] = \"ダブルクリックして\"\n    md[\"double_click\"][6] = \"더블_클릭\"\n    md[\"double_click\"][7] = \"clique_duas_vezes\"\n    md[\"double_click\"][8] = \"дважды_нажмите\"\n    md[\"double_click\"][9] = \"doble_clic\"\n\n    md[\"context_click\"] = [\"*\"] * num_langs\n    md[\"context_click\"][0] = \"context_click\"\n    md[\"context_click\"][1] = \"上下文点击\"\n    md[\"context_click\"][2] = \"contextklik\"\n    md[\"context_click\"][3] = \"contextuel_cliquer\"\n    md[\"context_click\"][4] = \"clic_contestuale\"\n    md[\"context_click\"][5] = \"コンテキストクリック\"\n    md[\"context_click\"][6] = \"컨텍스트_클릭\"\n    md[\"context_click\"][7] = \"clique_de_contexto\"\n    md[\"context_click\"][8] = \"контекстный_щелчок\"\n    md[\"context_click\"][9] = \"clic_de_contexto\"\n\n    md[\"slow_click\"] = [\"*\"] * num_langs\n    md[\"slow_click\"][0] = \"slow_click\"\n    md[\"slow_click\"][1] = \"慢单击\"\n    md[\"slow_click\"][2] = \"klik_langzaam\"\n    md[\"slow_click\"][3] = \"cliquer_lentement\"\n    md[\"slow_click\"][4] = \"clic_lentamente\"\n    md[\"slow_click\"][5] = \"ゆっくりクリックして\"\n    md[\"slow_click\"][6] = \"천천히_클릭\"\n    md[\"slow_click\"][7] = \"clique_devagar\"\n    md[\"slow_click\"][8] = \"нажмите_медленно\"\n    md[\"slow_click\"][9] = \"clic_lentamente\"\n\n    md[\"click_if_visible\"] = [\"*\"] * num_langs\n    md[\"click_if_visible\"][0] = \"click_if_visible\"\n    md[\"click_if_visible\"][1] = \"如果可见请单击\"\n    md[\"click_if_visible\"][2] = \"klik_indien_zichtbaar\"\n    md[\"click_if_visible\"][3] = \"cliquer_si_affiché\"\n    md[\"click_if_visible\"][4] = \"clic_se_visto\"\n    md[\"click_if_visible\"][5] = \"表示されている場合はクリック\"\n    md[\"click_if_visible\"][6] = \"보이는_경우_클릭\"\n    md[\"click_if_visible\"][7] = \"clique_se_está_visível\"\n    md[\"click_if_visible\"][8] = \"нажмите_если_виден\"\n    md[\"click_if_visible\"][9] = \"clic_si_está_muestra\"\n\n    md[\"js_click_if_present\"] = [\"*\"] * num_langs\n    md[\"js_click_if_present\"][0] = \"js_click_if_present\"\n    md[\"js_click_if_present\"][1] = \"JS如果存在请单击\"\n    md[\"js_click_if_present\"][2] = \"js_klik_indien_aanwezig\"\n    md[\"js_click_if_present\"][3] = \"js_cliquer_si_présent\"\n    md[\"js_click_if_present\"][4] = \"js_clic_se_presente\"\n    md[\"js_click_if_present\"][5] = \"存在する場合はJSクリック\"\n    md[\"js_click_if_present\"][6] = \"JS_존재하는지_경우_클릭\"\n    md[\"js_click_if_present\"][7] = \"js_clique_se_está_presente\"\n    md[\"js_click_if_present\"][8] = \"JS_нажмите_если_присутствует\"\n    md[\"js_click_if_present\"][9] = \"js_clic_si_está_presente\"\n\n    md[\"click_link\"] = [\"*\"] * num_langs\n    md[\"click_link\"][0] = \"click_link\"\n    md[\"click_link\"][1] = \"单击链接文本\"\n    md[\"click_link\"][2] = \"klik_linktekst\"\n    md[\"click_link\"][3] = \"cliquer_texte_du_lien\"\n    md[\"click_link\"][4] = \"clic_testo_del_collegamento\"\n    md[\"click_link\"][5] = \"リンクテキストをクリックします\"\n    md[\"click_link\"][6] = \"링크_텍스트를_클릭합니다\"\n    md[\"click_link\"][7] = \"clique_texto_do_link\"\n    md[\"click_link\"][8] = \"нажмите_ссылку\"\n    md[\"click_link\"][9] = \"clic_texto_del_enlace\"\n\n    md[\"click_with_offset\"] = [\"*\"] * num_langs\n    md[\"click_with_offset\"][0] = \"click_with_offset\"\n    md[\"click_with_offset\"][1] = \"鼠标点击偏移\"\n    md[\"click_with_offset\"][2] = \"klik_op_locatie\"\n    md[\"click_with_offset\"][3] = \"cliquer_emplacement\"\n    md[\"click_with_offset\"][4] = \"clic_su_posizione\"\n    md[\"click_with_offset\"][5] = \"オフセットでクリック\"\n    md[\"click_with_offset\"][6] = \"위치를_클릭\"\n    md[\"click_with_offset\"][7] = \"clique_com_deslocamento\"\n    md[\"click_with_offset\"][8] = \"нажмите_на_местоположение\"\n    md[\"click_with_offset\"][9] = \"clic_con_desplazamiento\"\n\n    md[\"update_text\"] = [\"*\"] * num_langs\n    md[\"update_text\"][0] = \"update_text\"\n    md[\"update_text\"][1] = \"更新文本\"\n    md[\"update_text\"][2] = \"tekst_bijwerken\"\n    md[\"update_text\"][3] = \"modifier_texte\"\n    md[\"update_text\"][4] = \"aggiornare_testo\"\n    md[\"update_text\"][5] = \"テキストを更新\"\n    md[\"update_text\"][6] = \"텍스트를_업데이트\"\n    md[\"update_text\"][7] = \"atualizar_texto\"\n    md[\"update_text\"][8] = \"обновить_текст\"\n    md[\"update_text\"][9] = \"actualizar_texto\"\n\n    md[\"add_text\"] = [\"*\"] * num_langs\n    md[\"add_text\"][0] = \"add_text\"\n    md[\"add_text\"][1] = \"添加文本\"\n    md[\"add_text\"][2] = \"tekst_toevoegen\"\n    md[\"add_text\"][3] = \"ajouter_texte\"\n    md[\"add_text\"][4] = \"aggiungi_testo\"\n    md[\"add_text\"][5] = \"テキストを追加\"\n    md[\"add_text\"][6] = \"텍스트를_추가\"\n    md[\"add_text\"][7] = \"adicionar_texto\"\n    md[\"add_text\"][8] = \"добавить_текст\"\n    md[\"add_text\"][9] = \"agregar_texto\"\n\n    md[\"get_text\"] = [\"*\"] * num_langs\n    md[\"get_text\"][0] = \"get_text\"\n    md[\"get_text\"][1] = \"获取文本\"\n    md[\"get_text\"][2] = \"tekst_ophalen\"\n    md[\"get_text\"][3] = \"obtenir_texte\"\n    md[\"get_text\"][4] = \"ottenere_testo\"\n    md[\"get_text\"][5] = \"テキストを取得\"\n    md[\"get_text\"][6] = \"텍스트를_검색\"\n    md[\"get_text\"][7] = \"obter_texto\"\n    md[\"get_text\"][8] = \"получить_текст\"\n    md[\"get_text\"][9] = \"obtener_texto\"\n\n    md[\"assert_text\"] = [\"*\"] * num_langs\n    md[\"assert_text\"][0] = \"assert_text\"\n    md[\"assert_text\"][1] = \"断言文本\"\n    md[\"assert_text\"][2] = \"controleren_tekst\"\n    md[\"assert_text\"][3] = \"vérifier_texte\"\n    md[\"assert_text\"][4] = \"verificare_testo\"\n    md[\"assert_text\"][5] = \"テキストを確認する\"\n    md[\"assert_text\"][6] = \"텍스트_확인\"\n    md[\"assert_text\"][7] = \"verificar_texto\"\n    md[\"assert_text\"][8] = \"подтвердить_текст\"\n    md[\"assert_text\"][9] = \"verificar_texto\"\n\n    md[\"assert_exact_text\"] = [\"*\"] * num_langs\n    md[\"assert_exact_text\"][0] = \"assert_exact_text\"\n    md[\"assert_exact_text\"][1] = \"确切断言文本\"\n    md[\"assert_exact_text\"][2] = \"controleren_exacte_tekst\"\n    md[\"assert_exact_text\"][3] = \"vérifier_texte_exactement\"\n    md[\"assert_exact_text\"][4] = \"verificare_testo_esatto\"\n    md[\"assert_exact_text\"][5] = \"正確なテキストを確認する\"\n    md[\"assert_exact_text\"][6] = \"정확한_텍스트를_확인하는\"\n    md[\"assert_exact_text\"][7] = \"verificar_texto_exato\"\n    md[\"assert_exact_text\"][8] = \"подтвердить_текст_точно\"\n    md[\"assert_exact_text\"][9] = \"verificar_texto_exacto\"\n\n    md[\"assert_link_text\"] = [\"*\"] * num_langs\n    md[\"assert_link_text\"][0] = \"assert_link_text\"\n    md[\"assert_link_text\"][1] = \"断言链接文本\"\n    md[\"assert_link_text\"][2] = \"controleren_linktekst\"\n    md[\"assert_link_text\"][3] = \"vérifier_texte_du_lien\"\n    md[\"assert_link_text\"][4] = \"verificare_testo_del_collegamento\"\n    md[\"assert_link_text\"][5] = \"リンクテキストを確認する\"\n    md[\"assert_link_text\"][6] = \"링크_텍스트_확인\"\n    md[\"assert_link_text\"][7] = \"verificar_texto_do_link\"\n    md[\"assert_link_text\"][8] = \"подтвердить_ссылку\"\n    md[\"assert_link_text\"][9] = \"verificar_texto_del_enlace\"\n\n    md[\"assert_non_empty_text\"] = [\"*\"] * num_langs\n    md[\"assert_non_empty_text\"][0] = \"assert_non_empty_text\"\n    md[\"assert_non_empty_text\"][1] = \"断言非空文本\"\n    md[\"assert_non_empty_text\"][2] = \"controleren_niet_lege_tekst\"\n    md[\"assert_non_empty_text\"][3] = \"vérifier_texte_non_vide\"\n    md[\"assert_non_empty_text\"][4] = \"verificare_testo_non_vuoto\"\n    md[\"assert_non_empty_text\"][5] = \"空ではないテキストを確認する\"\n    md[\"assert_non_empty_text\"][6] = \"비어_있지_않은_텍스트_확인하는\"\n    md[\"assert_non_empty_text\"][7] = \"verificar_texto_não_vazio\"\n    md[\"assert_non_empty_text\"][8] = \"подтвердить_непустой_текст\"\n    md[\"assert_non_empty_text\"][9] = \"verificar_texto_no_vacío\"\n\n    md[\"assert_text_not_visible\"] = [\"*\"] * num_langs\n    md[\"assert_text_not_visible\"][0] = \"assert_text_not_visible\"\n    md[\"assert_text_not_visible\"][1] = \"断言文本不可见\"\n    md[\"assert_text_not_visible\"][2] = \"controleren_tekst_niet_zichtbaar\"\n    md[\"assert_text_not_visible\"][3] = \"vérifier_texte_pas_affiché\"\n    md[\"assert_text_not_visible\"][4] = \"verificare_testo_non_visto\"\n    md[\"assert_text_not_visible\"][5] = \"テキが表示されていないことを確認します\"\n    md[\"assert_text_not_visible\"][6] = \"텍스트_보이지_않는지_확인\"\n    md[\"assert_text_not_visible\"][7] = \"verificar_texto_não_visível\"\n    md[\"assert_text_not_visible\"][8] = \"подтвердить_текст_не_виден\"\n    md[\"assert_text_not_visible\"][9] = \"verificar_texto_no_se_muestra\"\n\n    md[\"assert_element\"] = [\"*\"] * num_langs\n    md[\"assert_element\"][0] = \"assert_element\"\n    md[\"assert_element\"][1] = \"断言元素\"\n    md[\"assert_element\"][2] = \"controleren_element\"\n    md[\"assert_element\"][3] = \"vérifier_élément\"\n    md[\"assert_element\"][4] = \"verificare_elemento\"\n    md[\"assert_element\"][5] = \"要素を確認する\"\n    md[\"assert_element\"][6] = \"요소_확인\"\n    md[\"assert_element\"][7] = \"verificar_elemento\"\n    md[\"assert_element\"][8] = \"подтвердить_элемент\"\n    md[\"assert_element\"][9] = \"verificar_elemento\"\n\n    md[\"assert_element_visible\"] = [\"*\"] * num_langs\n    md[\"assert_element_visible\"][0] = \"assert_element_visible\"\n    md[\"assert_element_visible\"][1] = \"断言元素可见\"\n    md[\"assert_element_visible\"][2] = \"controleren_element_zichtbaar\"\n    md[\"assert_element_visible\"][3] = \"vérifier_élément_affiché\"\n    md[\"assert_element_visible\"][4] = \"verificare_elemento_visto\"\n    md[\"assert_element_visible\"][5] = \"要素が表示されていることを確認\"\n    md[\"assert_element_visible\"][6] = \"요소가_보이는지_확인\"\n    md[\"assert_element_visible\"][7] = \"verificar_elemento_visível\"\n    md[\"assert_element_visible\"][8] = \"подтвердить_элемент_виден\"\n    md[\"assert_element_visible\"][9] = \"verificar_elemento_se_muestra\"\n\n    md[\"assert_element_not_visible\"] = [\"*\"] * num_langs\n    md[\"assert_element_not_visible\"][0] = \"assert_element_not_visible\"\n    md[\"assert_element_not_visible\"][1] = \"断言元素不可见\"\n    md[\"assert_element_not_visible\"][2] = \"controleren_element_niet_zichtbaar\"\n    md[\"assert_element_not_visible\"][3] = \"vérifier_élément_pas_affiché\"\n    md[\"assert_element_not_visible\"][4] = \"verificare_elemento_non_visto\"\n    md[\"assert_element_not_visible\"][5] = \"要素が表示されていないことを確認します\"\n    md[\"assert_element_not_visible\"][6] = \"요소가_보이지_않는지_확인\"\n    md[\"assert_element_not_visible\"][7] = \"verificar_elemento_não_visível\"\n    md[\"assert_element_not_visible\"][8] = \"подтвердить_элемент_не_виден\"\n    md[\"assert_element_not_visible\"][9] = \"verificar_elemento_no_se_muestra\"\n\n    md[\"assert_element_present\"] = [\"*\"] * num_langs\n    md[\"assert_element_present\"][0] = \"assert_element_present\"\n    md[\"assert_element_present\"][1] = \"断言元素存在\"\n    md[\"assert_element_present\"][2] = \"controleren_element_aanwezig\"\n    md[\"assert_element_present\"][3] = \"vérifier_élément_présent\"\n    md[\"assert_element_present\"][4] = \"verificare_elemento_presente\"\n    md[\"assert_element_present\"][5] = \"要素が存在することを確認します\"\n    md[\"assert_element_present\"][6] = \"요소가_존재하는지_확인\"\n    md[\"assert_element_present\"][7] = \"verificar_elemento_presente\"\n    md[\"assert_element_present\"][8] = \"подтвердить_элемент_присутствует\"\n    md[\"assert_element_present\"][9] = \"verificar_elemento_presente\"\n\n    md[\"assert_element_absent\"] = [\"*\"] * num_langs\n    md[\"assert_element_absent\"][0] = \"assert_element_absent\"\n    md[\"assert_element_absent\"][1] = \"断言元素不存在\"\n    md[\"assert_element_absent\"][2] = \"controleren_element_afwezig\"\n    md[\"assert_element_absent\"][3] = \"vérifier_élément_pas_présent\"\n    md[\"assert_element_absent\"][4] = \"verificare_elemento_assente\"\n    md[\"assert_element_absent\"][5] = \"要素が存在しないことを確認します\"\n    md[\"assert_element_absent\"][6] = \"요소가_존재하지_않는지_확인\"\n    md[\"assert_element_absent\"][7] = \"verificar_elemento_ausente\"\n    md[\"assert_element_absent\"][8] = \"подтвердить_элемент_отсутствует\"\n    md[\"assert_element_absent\"][9] = \"verificar_elemento_ausente\"\n\n    md[\"assert_attribute\"] = [\"*\"] * num_langs\n    md[\"assert_attribute\"][0] = \"assert_attribute\"\n    md[\"assert_attribute\"][1] = \"断言属性\"\n    md[\"assert_attribute\"][2] = \"controleren_attribuut\"\n    md[\"assert_attribute\"][3] = \"vérifier_attribut\"\n    md[\"assert_attribute\"][4] = \"verificare_attributo\"\n    md[\"assert_attribute\"][5] = \"属性を確認する\"\n    md[\"assert_attribute\"][6] = \"특성_확인\"\n    md[\"assert_attribute\"][7] = \"verificar_atributo\"\n    md[\"assert_attribute\"][8] = \"подтвердить_атрибут\"\n    md[\"assert_attribute\"][9] = \"verificar_atributo\"\n\n    md[\"assert_url\"] = [\"*\"] * num_langs\n    md[\"assert_url\"][0] = \"assert_url\"\n    md[\"assert_url\"][1] = \"断言URL\"\n    md[\"assert_url\"][2] = \"controleren_url\"\n    md[\"assert_url\"][3] = \"vérifier_url\"\n    md[\"assert_url\"][4] = \"verificare_url\"\n    md[\"assert_url\"][5] = \"URLを確認する\"\n    md[\"assert_url\"][6] = \"URL_확인\"\n    md[\"assert_url\"][7] = \"verificar_url\"\n    md[\"assert_url\"][8] = \"подтвердить_URL\"\n    md[\"assert_url\"][9] = \"verificar_url\"\n\n    md[\"assert_url_contains\"] = [\"*\"] * num_langs\n    md[\"assert_url_contains\"][0] = \"assert_url_contains\"\n    md[\"assert_url_contains\"][1] = \"断言URL包含\"\n    md[\"assert_url_contains\"][2] = \"controleren_url_bevat\"\n    md[\"assert_url_contains\"][3] = \"vérifier_url_contient\"\n    md[\"assert_url_contains\"][4] = \"verificare_url_contiene\"\n    md[\"assert_url_contains\"][5] = \"URL部分文字列を確認する\"\n    md[\"assert_url_contains\"][6] = \"URL_부분_확인\"\n    md[\"assert_url_contains\"][7] = \"verificar_url_contém\"\n    md[\"assert_url_contains\"][8] = \"подтвердить_URL_содержит\"\n    md[\"assert_url_contains\"][9] = \"verificar_url_contiene\"\n\n    md[\"assert_title\"] = [\"*\"] * num_langs\n    md[\"assert_title\"][0] = \"assert_title\"\n    md[\"assert_title\"][1] = \"断言标题\"\n    md[\"assert_title\"][2] = \"controleren_titel\"\n    md[\"assert_title\"][3] = \"vérifier_titre\"\n    md[\"assert_title\"][4] = \"verificare_titolo\"\n    md[\"assert_title\"][5] = \"タイトルを確認\"\n    md[\"assert_title\"][6] = \"제목_확인\"\n    md[\"assert_title\"][7] = \"verificar_título\"\n    md[\"assert_title\"][8] = \"подтвердить_название\"\n    md[\"assert_title\"][9] = \"verificar_título\"\n\n    md[\"assert_title_contains\"] = [\"*\"] * num_langs\n    md[\"assert_title_contains\"][0] = \"assert_title_contains\"\n    md[\"assert_title_contains\"][1] = \"断言标题包含\"\n    md[\"assert_title_contains\"][2] = \"controleren_titel_bevat\"\n    md[\"assert_title_contains\"][3] = \"vérifier_titre_contient\"\n    md[\"assert_title_contains\"][4] = \"verificare_titolo_contiene\"\n    md[\"assert_title_contains\"][5] = \"タイトル部分文字列を確認する\"\n    md[\"assert_title_contains\"][6] = \"제목_부분_확인\"\n    md[\"assert_title_contains\"][7] = \"verificar_título_contém\"\n    md[\"assert_title_contains\"][8] = \"подтвердить_название_содержит\"\n    md[\"assert_title_contains\"][9] = \"verificar_título_contiene\"\n\n    md[\"get_title\"] = [\"*\"] * num_langs\n    md[\"get_title\"][0] = \"get_title\"\n    md[\"get_title\"][1] = \"获取标题\"\n    md[\"get_title\"][2] = \"titel_ophalen\"\n    md[\"get_title\"][3] = \"obtenir_titre\"\n    md[\"get_title\"][4] = \"ottenere_titolo\"\n    md[\"get_title\"][5] = \"タイトルを取得する\"\n    md[\"get_title\"][6] = \"제목_검색\"\n    md[\"get_title\"][7] = \"obter_título\"\n    md[\"get_title\"][8] = \"получить_название\"\n    md[\"get_title\"][9] = \"obtener_título\"\n\n    md[\"assert_true\"] = [\"*\"] * num_langs\n    md[\"assert_true\"][0] = \"assert_true\"\n    md[\"assert_true\"][1] = \"断言为真\"\n    md[\"assert_true\"][2] = \"controleren_ware\"\n    md[\"assert_true\"][3] = \"vérifier_vrai\"\n    md[\"assert_true\"][4] = \"verificare_vero\"\n    md[\"assert_true\"][5] = \"検証が正しい\"\n    md[\"assert_true\"][6] = \"올바른지_확인\"\n    md[\"assert_true\"][7] = \"verificar_verdade\"\n    md[\"assert_true\"][8] = \"подтвердить_правду\"\n    md[\"assert_true\"][9] = \"verificar_verdad\"\n\n    md[\"assert_false\"] = [\"*\"] * num_langs\n    md[\"assert_false\"][0] = \"assert_false\"\n    md[\"assert_false\"][1] = \"断言为假\"\n    md[\"assert_false\"][2] = \"controleren_valse\"\n    md[\"assert_false\"][3] = \"vérifier_faux\"\n    md[\"assert_false\"][4] = \"verificare_falso\"\n    md[\"assert_false\"][5] = \"検証は偽です\"\n    md[\"assert_false\"][6] = \"거짓인지_확인\"\n    md[\"assert_false\"][7] = \"verificar_falso\"\n    md[\"assert_false\"][8] = \"подтвердить_ложные\"\n    md[\"assert_false\"][9] = \"verificar_falso\"\n\n    md[\"assert_equal\"] = [\"*\"] * num_langs\n    md[\"assert_equal\"][0] = \"assert_equal\"\n    md[\"assert_equal\"][1] = \"断言等于\"\n    md[\"assert_equal\"][2] = \"controleren_gelijk\"\n    md[\"assert_equal\"][3] = \"vérifier_égal\"\n    md[\"assert_equal\"][4] = \"verificare_uguale\"\n    md[\"assert_equal\"][5] = \"検証が等しい\"\n    md[\"assert_equal\"][6] = \"동일한지_확인\"\n    md[\"assert_equal\"][7] = \"verificar_igual\"\n    md[\"assert_equal\"][8] = \"подтвердить_одинаковый\"\n    md[\"assert_equal\"][9] = \"verificar_igual\"\n\n    md[\"assert_not_equal\"] = [\"*\"] * num_langs\n    md[\"assert_not_equal\"][0] = \"assert_not_equal\"\n    md[\"assert_not_equal\"][1] = \"断言不等于\"\n    md[\"assert_not_equal\"][2] = \"controleren_niet_gelijk\"\n    md[\"assert_not_equal\"][3] = \"vérifier_non_égal\"\n    md[\"assert_not_equal\"][4] = \"verificare_non_uguale\"\n    md[\"assert_not_equal\"][5] = \"検証が等しくない\"\n    md[\"assert_not_equal\"][6] = \"동일하지_않다고_어설션\"\n    md[\"assert_not_equal\"][7] = \"verificar_não_igual\"\n    md[\"assert_not_equal\"][8] = \"подтвердить_не_одинаковый\"\n    md[\"assert_not_equal\"][9] = \"verificar_diferente\"\n\n    md[\"refresh_page\"] = [\"*\"] * num_langs\n    md[\"refresh_page\"][0] = \"refresh_page\"\n    md[\"refresh_page\"][1] = \"刷新页面\"\n    md[\"refresh_page\"][2] = \"ververs_pagina\"\n    md[\"refresh_page\"][3] = \"rafraîchir_la_page\"\n    md[\"refresh_page\"][4] = \"aggiorna_la_pagina\"\n    md[\"refresh_page\"][5] = \"ページを更新する\"\n    md[\"refresh_page\"][6] = \"페이지_새로_고침\"\n    md[\"refresh_page\"][7] = \"atualizar_a_página\"\n    md[\"refresh_page\"][8] = \"обновить_страницу\"\n    md[\"refresh_page\"][9] = \"actualizar_la_página\"\n\n    md[\"get_current_url\"] = [\"*\"] * num_langs\n    md[\"get_current_url\"][0] = \"get_current_url\"\n    md[\"get_current_url\"][1] = \"获取当前网址\"\n    md[\"get_current_url\"][2] = \"huidige_url_ophalen\"\n    md[\"get_current_url\"][3] = \"obtenir_url_actuelle\"\n    md[\"get_current_url\"][4] = \"ottenere_url_corrente\"\n    md[\"get_current_url\"][5] = \"現在のURLを取得\"\n    md[\"get_current_url\"][6] = \"현재의_URL을_가져\"\n    md[\"get_current_url\"][7] = \"obter_url_atual\"\n    md[\"get_current_url\"][8] = \"получить_текущий_URL\"\n    md[\"get_current_url\"][9] = \"obtener_url_actual\"\n\n    md[\"get_page_source\"] = [\"*\"] * num_langs\n    md[\"get_page_source\"][0] = \"get_page_source\"\n    md[\"get_page_source\"][1] = \"获取页面源代码\"\n    md[\"get_page_source\"][2] = \"broncode_ophalen\"\n    md[\"get_page_source\"][3] = \"obtenir_html_de_la_page\"\n    md[\"get_page_source\"][4] = \"ottenere_la_pagina_html\"\n    md[\"get_page_source\"][5] = \"ページのソースコードを取得する\"\n    md[\"get_page_source\"][6] = \"페이지의_소스_코드를_얻을\"\n    md[\"get_page_source\"][7] = \"obter_a_página_html\"\n    md[\"get_page_source\"][8] = \"получить_источник_страницы\"\n    md[\"get_page_source\"][9] = \"obtener_html_de_la_página\"\n\n    md[\"go_back\"] = [\"*\"] * num_langs\n    md[\"go_back\"][0] = \"go_back\"\n    md[\"go_back\"][1] = \"回去\"\n    md[\"go_back\"][2] = \"terug\"\n    md[\"go_back\"][3] = \"retour\"\n    md[\"go_back\"][4] = \"indietro\"\n    md[\"go_back\"][5] = \"戻る\"\n    md[\"go_back\"][6] = \"뒤로\"\n    md[\"go_back\"][7] = \"voltar\"\n    md[\"go_back\"][8] = \"назад\"\n    md[\"go_back\"][9] = \"volver\"\n\n    md[\"go_forward\"] = [\"*\"] * num_langs\n    md[\"go_forward\"][0] = \"go_forward\"\n    md[\"go_forward\"][1] = \"向前\"\n    md[\"go_forward\"][2] = \"vooruit\"\n    md[\"go_forward\"][3] = \"en_avant\"\n    md[\"go_forward\"][4] = \"avanti\"\n    md[\"go_forward\"][5] = \"進む\"\n    md[\"go_forward\"][6] = \"앞으로\"\n    md[\"go_forward\"][7] = \"avançar\"\n    md[\"go_forward\"][8] = \"вперед\"\n    md[\"go_forward\"][9] = \"adelante\"\n\n    md[\"is_text_visible\"] = [\"*\"] * num_langs\n    md[\"is_text_visible\"][0] = \"is_text_visible\"\n    md[\"is_text_visible\"][1] = \"文本是否显示\"\n    md[\"is_text_visible\"][2] = \"tekst_zichtbaar\"\n    md[\"is_text_visible\"][3] = \"est_texte_affiché\"\n    md[\"is_text_visible\"][4] = \"è_testo_visto\"\n    md[\"is_text_visible\"][5] = \"テキストが表示されています\"\n    md[\"is_text_visible\"][6] = \"텍스트가_표시됩니다\"\n    md[\"is_text_visible\"][7] = \"o_texto_está_visível\"\n    md[\"is_text_visible\"][8] = \"текст_виден\"\n    md[\"is_text_visible\"][9] = \"se_muestra_el_texto\"\n\n    md[\"is_exact_text_visible\"] = [\"*\"] * num_langs\n    md[\"is_exact_text_visible\"][0] = \"is_exact_text_visible\"\n    md[\"is_exact_text_visible\"][1] = \"确切文本是否显示\"\n    md[\"is_exact_text_visible\"][2] = \"exacte_tekst_zichtbaar\"\n    md[\"is_exact_text_visible\"][3] = \"est_texte_exactement_affiché\"\n    md[\"is_exact_text_visible\"][4] = \"è_testo_esatto_visto\"\n    md[\"is_exact_text_visible\"][5] = \"正確なテキストが表示されています\"\n    md[\"is_exact_text_visible\"][6] = \"정확한_텍스트가_표시됩니다\"\n    md[\"is_exact_text_visible\"][7] = \"o_texto_exato_está_visível\"\n    md[\"is_exact_text_visible\"][8] = \"точный_текст_виден\"\n    md[\"is_exact_text_visible\"][9] = \"se_muestra_el_texto_exacto\"\n\n    md[\"is_element_visible\"] = [\"*\"] * num_langs\n    md[\"is_element_visible\"][0] = \"is_element_visible\"\n    md[\"is_element_visible\"][1] = \"元素是否可见\"\n    md[\"is_element_visible\"][2] = \"element_zichtbaar\"\n    md[\"is_element_visible\"][3] = \"est_un_élément_affiché\"\n    md[\"is_element_visible\"][4] = \"è_elemento_visto\"\n    md[\"is_element_visible\"][5] = \"要素は表示されますか\"\n    md[\"is_element_visible\"][6] = \"요소가_표시됩니다\"\n    md[\"is_element_visible\"][7] = \"o_elemento_está_visível\"\n    md[\"is_element_visible\"][8] = \"элемент_виден\"\n    md[\"is_element_visible\"][9] = \"se_muestra_el_elemento\"\n\n    md[\"is_element_enabled\"] = [\"*\"] * num_langs\n    md[\"is_element_enabled\"][0] = \"is_element_enabled\"\n    md[\"is_element_enabled\"][1] = \"元素是否启用\"\n    md[\"is_element_enabled\"][2] = \"element_ingeschakeld\"\n    md[\"is_element_enabled\"][3] = \"est_un_élément_activé\"\n    md[\"is_element_enabled\"][4] = \"è_elemento_abilitato\"\n    md[\"is_element_enabled\"][5] = \"要素が有効かどうか\"\n    md[\"is_element_enabled\"][6] = \"요소가_활성화돼\"\n    md[\"is_element_enabled\"][7] = \"o_elemento_está_habilitado\"\n    md[\"is_element_enabled\"][8] = \"элемент_включен\"\n    md[\"is_element_enabled\"][9] = \"está_habilitado_el_elemento\"\n\n    md[\"is_element_present\"] = [\"*\"] * num_langs\n    md[\"is_element_present\"][0] = \"is_element_present\"\n    md[\"is_element_present\"][1] = \"元素是否存在\"\n    md[\"is_element_present\"][2] = \"element_aanwezig\"\n    md[\"is_element_present\"][3] = \"est_un_élément_présent\"\n    md[\"is_element_present\"][4] = \"è_elemento_presente\"\n    md[\"is_element_present\"][5] = \"要素が存在するかどうか\"\n    md[\"is_element_present\"][6] = \"요소가_있습니다\"\n    md[\"is_element_present\"][7] = \"o_elemento_está_presente\"\n    md[\"is_element_present\"][8] = \"элемент_присутствует\"\n    md[\"is_element_present\"][9] = \"está_presente_el_elemento\"\n\n    md[\"wait_for_text\"] = [\"*\"] * num_langs\n    md[\"wait_for_text\"][0] = \"wait_for_text\"\n    md[\"wait_for_text\"][1] = \"等待文本\"\n    md[\"wait_for_text\"][2] = \"wachten_op_tekst\"\n    md[\"wait_for_text\"][3] = \"attendre_le_texte\"\n    md[\"wait_for_text\"][4] = \"attendere_il_testo\"\n    md[\"wait_for_text\"][5] = \"テキストを待つ\"\n    md[\"wait_for_text\"][6] = \"텍스트가_나타날_때까지_기다립니다\"\n    md[\"wait_for_text\"][7] = \"aguardar_o_texto\"\n    md[\"wait_for_text\"][8] = \"ждать_текста\"\n    md[\"wait_for_text\"][9] = \"espera_el_texto\"\n\n    md[\"wait_for_element\"] = [\"*\"] * num_langs\n    md[\"wait_for_element\"][0] = \"wait_for_element\"\n    md[\"wait_for_element\"][1] = \"等待元素\"\n    md[\"wait_for_element\"][2] = \"wachten_op_element\"\n    md[\"wait_for_element\"][3] = \"attendre_un_élément\"\n    md[\"wait_for_element\"][4] = \"attendere_un_elemento\"\n    md[\"wait_for_element\"][5] = \"要素を待つ\"\n    md[\"wait_for_element\"][6] = \"요소가_나타날_때까지_기다립니다\"\n    md[\"wait_for_element\"][7] = \"aguardar_o_elemento\"\n    md[\"wait_for_element\"][8] = \"ждать_элемента\"\n    md[\"wait_for_element\"][9] = \"espera_el_elemento\"\n\n    md[\"wait_for_element_visible\"] = [\"*\"] * num_langs\n    md[\"wait_for_element_visible\"][0] = \"wait_for_element_visible\"\n    md[\"wait_for_element_visible\"][1] = \"等待元素可见\"\n    md[\"wait_for_element_visible\"][2] = \"wachten_op_element_zichtbaar\"\n    md[\"wait_for_element_visible\"][3] = \"attendre_un_élément_affiché\"\n    md[\"wait_for_element_visible\"][4] = \"attendere_un_elemento_visto\"\n    md[\"wait_for_element_visible\"][5] = \"要素が表示されるのを待ちます\"\n    md[\"wait_for_element_visible\"][6] = \"요소가_표시_될_때까지_기다립니다\"\n    md[\"wait_for_element_visible\"][7] = \"aguardar_o_elemento_visível\"\n    md[\"wait_for_element_visible\"][8] = \"ждать_элемента_виден\"\n    md[\"wait_for_element_visible\"][9] = \"espera_el_elemento_se_muestra\"\n\n    md[\"wait_for_element_not_visible\"] = [\"*\"] * num_langs\n    md[\"wait_for_element_not_visible\"][0] = \"wait_for_element_not_visible\"\n    md[\"wait_for_element_not_visible\"][1] = \"等待元素不可见\"\n    md[\"wait_for_element_not_visible\"][2] = \"wachten_op_element_niet_zichtbaar\"\n    md[\"wait_for_element_not_visible\"][3] = \"attendre_un_élément_pas_affiché\"\n    md[\"wait_for_element_not_visible\"][4] = \"attendere_un_elemento_non_visto\"\n    md[\"wait_for_element_not_visible\"][5] = \"要素が表示されなくなるまで待ちます\"\n    md[\"wait_for_element_not_visible\"][6] = \"요소가_사라질_때까지_기다리십시오\"\n    md[\"wait_for_element_not_visible\"][7] = \"aguardar_o_elemento_não_visível\"\n    md[\"wait_for_element_not_visible\"][8] = \"ждать_элемента_не_виден\"\n    md[\"wait_for_element_not_visible\"][9] = \"espera_el_elemento_no_se_muestra\"\n\n    md[\"wait_for_element_present\"] = [\"*\"] * num_langs\n    md[\"wait_for_element_present\"][0] = \"wait_for_element_present\"\n    md[\"wait_for_element_present\"][1] = \"等待元素存在\"\n    md[\"wait_for_element_present\"][2] = \"wachten_op_element_aanwezig\"\n    md[\"wait_for_element_present\"][3] = \"attendre_un_élément_présent\"\n    md[\"wait_for_element_present\"][4] = \"attendere_un_elemento_presente\"\n    md[\"wait_for_element_present\"][5] = \"要素が存在するのを待つ\"\n    md[\"wait_for_element_present\"][6] = \"요소가_존재할_때까지_기다립니다\"\n    md[\"wait_for_element_present\"][7] = \"aguardar_o_elemento_presente\"\n    md[\"wait_for_element_present\"][8] = \"ждать_элемента_присутствует\"\n    md[\"wait_for_element_present\"][9] = \"espera_el_elemento_presente\"\n\n    md[\"wait_for_element_absent\"] = [\"*\"] * num_langs\n    md[\"wait_for_element_absent\"][0] = \"wait_for_element_absent\"\n    md[\"wait_for_element_absent\"][1] = \"等待元素不存在\"\n    md[\"wait_for_element_absent\"][2] = \"wachten_op_element_afwezig\"\n    md[\"wait_for_element_absent\"][3] = \"attendre_un_élément_pas_présent\"\n    md[\"wait_for_element_absent\"][4] = \"attendere_un_elemento_assente\"\n    md[\"wait_for_element_absent\"][5] = \"要素が存在しないのを待つ\"\n    md[\"wait_for_element_absent\"][6] = \"요소가_나타날_때까지_기다리십시오\"\n    md[\"wait_for_element_absent\"][7] = \"aguardar_o_elemento_ausente\"\n    md[\"wait_for_element_absent\"][8] = \"ждать_элемента_отсутствует\"\n    md[\"wait_for_element_absent\"][9] = \"espera_el_elemento_ausente\"\n\n    md[\"wait_for_attribute\"] = [\"*\"] * num_langs\n    md[\"wait_for_attribute\"][0] = \"wait_for_attribute\"\n    md[\"wait_for_attribute\"][1] = \"等待属性\"\n    md[\"wait_for_attribute\"][2] = \"wachten_op_attribuut\"\n    md[\"wait_for_attribute\"][3] = \"attendre_un_attribut\"\n    md[\"wait_for_attribute\"][4] = \"attendere_un_attributo\"\n    md[\"wait_for_attribute\"][5] = \"属性を待つ\"\n    md[\"wait_for_attribute\"][6] = \"특성_때까지_기다립니다\"\n    md[\"wait_for_attribute\"][7] = \"aguardar_o_atributo\"\n    md[\"wait_for_attribute\"][8] = \"ждать_атрибут\"\n    md[\"wait_for_attribute\"][9] = \"espera_el_atributo\"\n\n    md[\"wait_for_ready_state_complete\"] = [\"*\"] * num_langs\n    md[\"wait_for_ready_state_complete\"][0] = \"wait_for_ready_state_complete\"\n    md[\"wait_for_ready_state_complete\"][1] = \"等待页面加载完成\"\n    md[\"wait_for_ready_state_complete\"][2] = \"wacht_tot_de_pagina_is_geladen\"\n    md[\"wait_for_ready_state_complete\"][3] = \"attendre_que_la_page_se_charge\"\n    wfrsc_it = \"attendere_il_caricamento_della_pagina\"\n    md[\"wait_for_ready_state_complete\"][4] = wfrsc_it\n    md[\"wait_for_ready_state_complete\"][5] = \"ページがロードされるのを待ちます\"\n    md[\"wait_for_ready_state_complete\"][6] = \"페이지가_로드될_때까지_기다립니다\"\n    md[\"wait_for_ready_state_complete\"][7] = \"aguardar_a_página_carregar\"\n    md[\"wait_for_ready_state_complete\"][8] = \"ждать_загрузки_страницы\"\n    md[\"wait_for_ready_state_complete\"][9] = \"esperar_a_que_cargue_la_página\"\n\n    md[\"sleep\"] = [\"*\"] * num_langs\n    md[\"sleep\"][0] = \"sleep\"\n    md[\"sleep\"][1] = \"睡\"\n    md[\"sleep\"][2] = \"slapen\"\n    md[\"sleep\"][3] = \"dormir\"\n    md[\"sleep\"][4] = \"dormire\"\n    md[\"sleep\"][5] = \"眠る\"\n    md[\"sleep\"][6] = \"잠을\"\n    md[\"sleep\"][7] = \"dormir\"\n    md[\"sleep\"][8] = \"спать\"\n    md[\"sleep\"][9] = \"dormir\"\n\n    md[\"wait\"] = [\"*\"] * num_langs\n    md[\"wait\"][0] = \"wait\"\n    md[\"wait\"][1] = \"等待\"\n    md[\"wait\"][2] = \"wachten\"\n    md[\"wait\"][3] = \"attendre\"\n    md[\"wait\"][4] = \"attendere\"\n    md[\"wait\"][5] = \"待つ\"\n    md[\"wait\"][6] = \"기다림\"\n    md[\"wait\"][7] = \"aguardar\"\n    md[\"wait\"][8] = \"ждать\"\n    md[\"wait\"][9] = \"espera\"\n\n    md[\"submit\"] = [\"*\"] * num_langs\n    md[\"submit\"][0] = \"submit\"\n    md[\"submit\"][1] = \"提交\"\n    md[\"submit\"][2] = \"verzenden\"\n    md[\"submit\"][3] = \"soumettre\"\n    md[\"submit\"][4] = \"inviare\"\n    md[\"submit\"][5] = \"を提出す\"\n    md[\"submit\"][6] = \"제출\"\n    md[\"submit\"][7] = \"enviar\"\n    md[\"submit\"][8] = \"отправить\"\n    md[\"submit\"][9] = \"enviar\"\n\n    md[\"clear\"] = [\"*\"] * num_langs\n    md[\"clear\"][0] = \"clear\"\n    md[\"clear\"][1] = \"清除\"\n    md[\"clear\"][2] = \"wissen\"\n    md[\"clear\"][3] = \"effacer\"\n    md[\"clear\"][4] = \"cancellare\"\n    md[\"clear\"][5] = \"クリアする\"\n    md[\"clear\"][6] = \"지우려면\"\n    md[\"clear\"][7] = \"limpar\"\n    md[\"clear\"][8] = \"очистить\"\n    md[\"clear\"][9] = \"despejar\"\n\n    md[\"focus\"] = [\"*\"] * num_langs\n    md[\"focus\"][0] = \"focus\"\n    md[\"focus\"][1] = \"专注于\"\n    md[\"focus\"][2] = \"focussen\"\n    md[\"focus\"][3] = \"concentrer\"\n    md[\"focus\"][4] = \"focalizzare\"\n    md[\"focus\"][5] = \"集中する\"\n    md[\"focus\"][6] = \"집중하다\"\n    md[\"focus\"][7] = \"focar\"\n    md[\"focus\"][8] = \"сосредоточиться\"\n    md[\"focus\"][9] = \"centrarse\"\n\n    md[\"js_click\"] = [\"*\"] * num_langs\n    md[\"js_click\"][0] = \"js_click\"\n    md[\"js_click\"][1] = \"JS单击\"\n    md[\"js_click\"][2] = \"js_klik\"\n    md[\"js_click\"][3] = \"js_cliquer\"\n    md[\"js_click\"][4] = \"js_fare_clic\"\n    md[\"js_click\"][5] = \"JSクリックして\"\n    md[\"js_click\"][6] = \"JS_클릭\"\n    md[\"js_click\"][7] = \"js_clique\"\n    md[\"js_click\"][8] = \"JS_нажмите\"\n    md[\"js_click\"][9] = \"js_haga_clic\"\n\n    md[\"js_update_text\"] = [\"*\"] * num_langs\n    md[\"js_update_text\"][0] = \"js_update_text\"\n    md[\"js_update_text\"][1] = \"JS更新文本\"\n    md[\"js_update_text\"][2] = \"js_tekst_bijwerken\"\n    md[\"js_update_text\"][3] = \"js_modifier_texte\"\n    md[\"js_update_text\"][4] = \"js_aggiornare_testo\"\n    md[\"js_update_text\"][5] = \"JSテキストを更新\"\n    md[\"js_update_text\"][6] = \"JS_텍스트를_업데이트\"\n    md[\"js_update_text\"][7] = \"js_atualizar_texto\"\n    md[\"js_update_text\"][8] = \"JS_обновить_текст\"\n    md[\"js_update_text\"][9] = \"js_actualizar_texto\"\n\n    md[\"js_type\"] = [\"*\"] * num_langs\n    md[\"js_type\"][0] = \"js_type\"\n    md[\"js_type\"][1] = \"JS输入文本\"\n    md[\"js_type\"][2] = \"js_typ\"\n    md[\"js_type\"][3] = \"js_taper\"\n    md[\"js_type\"][4] = \"js_digitare\"\n    md[\"js_type\"][5] = \"JS入力\"\n    md[\"js_type\"][6] = \"JS_입력\"\n    md[\"js_type\"][7] = \"js_digitar\"\n    md[\"js_type\"][8] = \"JS_введите\"\n    md[\"js_type\"][9] = \"js_escriba\"\n\n    md[\"jquery_click\"] = [\"*\"] * num_langs\n    md[\"jquery_click\"][0] = \"jquery_click\"\n    md[\"jquery_click\"][1] = \"JQUERY单击\"\n    md[\"jquery_click\"][2] = \"jquery_klik\"\n    md[\"jquery_click\"][3] = \"jquery_cliquer\"\n    md[\"jquery_click\"][4] = \"jquery_fare_clic\"\n    md[\"jquery_click\"][5] = \"JQUERYクリックして\"\n    md[\"jquery_click\"][6] = \"JQUERY_클릭\"\n    md[\"jquery_click\"][7] = \"jquery_clique\"\n    md[\"jquery_click\"][8] = \"JQUERY_нажмите\"\n    md[\"jquery_click\"][9] = \"jquery_haga_clic\"\n\n    md[\"jquery_update_text\"] = [\"*\"] * num_langs\n    md[\"jquery_update_text\"][0] = \"jquery_update_text\"\n    md[\"jquery_update_text\"][1] = \"JQUERY更新文本\"\n    md[\"jquery_update_text\"][2] = \"jquery_tekst_bijwerken\"\n    md[\"jquery_update_text\"][3] = \"jquery_modifier_texte\"\n    md[\"jquery_update_text\"][4] = \"jquery_aggiornare_testo\"\n    md[\"jquery_update_text\"][5] = \"JQUERYテキストを更新\"\n    md[\"jquery_update_text\"][6] = \"JQUERY_텍스트를_업데이트\"\n    md[\"jquery_update_text\"][7] = \"jquery_atualizar_texto\"\n    md[\"jquery_update_text\"][8] = \"JQUERY_обновить_текст\"\n    md[\"jquery_update_text\"][9] = \"jquery_actualizar_texto\"\n\n    md[\"jquery_type\"] = [\"*\"] * num_langs\n    md[\"jquery_type\"][0] = \"jquery_type\"\n    md[\"jquery_type\"][1] = \"JQUERY输入文本\"\n    md[\"jquery_type\"][2] = \"jquery_typ\"\n    md[\"jquery_type\"][3] = \"jquery_taper\"\n    md[\"jquery_type\"][4] = \"jquery_digitare\"\n    md[\"jquery_type\"][5] = \"JQUERY入力\"\n    md[\"jquery_type\"][6] = \"JQUERY_입력\"\n    md[\"jquery_type\"][7] = \"jquery_digitar\"\n    md[\"jquery_type\"][8] = \"JQUERY_введите\"\n    md[\"jquery_type\"][9] = \"jquery_escriba\"\n\n    md[\"inspect_html\"] = [\"*\"] * num_langs\n    md[\"inspect_html\"][0] = \"inspect_html\"\n    md[\"inspect_html\"][1] = \"检查HTML\"\n    md[\"inspect_html\"][2] = \"html_inspecteren\"\n    md[\"inspect_html\"][3] = \"vérifier_html\"\n    md[\"inspect_html\"][4] = \"controlla_html\"\n    md[\"inspect_html\"][5] = \"HTMLをチェック\"\n    md[\"inspect_html\"][6] = \"HTML_확인\"\n    md[\"inspect_html\"][7] = \"verificar_html\"\n    md[\"inspect_html\"][8] = \"проверить_HTML\"\n    md[\"inspect_html\"][9] = \"comprobar_html\"\n\n    md[\"save_screenshot\"] = [\"*\"] * num_langs\n    md[\"save_screenshot\"][0] = \"save_screenshot\"\n    md[\"save_screenshot\"][1] = \"保存截图\"\n    md[\"save_screenshot\"][2] = \"bewaar_screenshot\"\n    md[\"save_screenshot\"][3] = \"enregistrer_capture_d_écran\"\n    md[\"save_screenshot\"][4] = \"salva_screenshot\"\n    md[\"save_screenshot\"][5] = \"スクリーンショットを保存\"\n    md[\"save_screenshot\"][6] = \"스크린_샷_저장\"\n    md[\"save_screenshot\"][7] = \"salvar_captura_de_tela\"\n    md[\"save_screenshot\"][8] = \"сохранить_скриншот\"\n    md[\"save_screenshot\"][9] = \"guardar_captura_de_pantalla\"\n\n    md[\"save_screenshot_to_logs\"] = [\"*\"] * num_langs\n    md[\"save_screenshot_to_logs\"][0] = \"save_screenshot_to_logs\"\n    md[\"save_screenshot_to_logs\"][1] = \"保存截图到日志\"\n    md[\"save_screenshot_to_logs\"][2] = \"bewaar_screenshot_om_te_loggen\"\n    md[\"save_screenshot_to_logs\"][3] = \"enregistrer_capture_d_écran_aux_logs\"\n    md[\"save_screenshot_to_logs\"][4] = \"salva_screenshot_nei_logs\"\n    md[\"save_screenshot_to_logs\"][5] = \"ログにスクリーンショットを保存\"\n    md[\"save_screenshot_to_logs\"][6] = \"로그에_스크린_샷_저장\"\n    md[\"save_screenshot_to_logs\"][7] = \"salvar_captura_de_tela_para_logs\"\n    md[\"save_screenshot_to_logs\"][8] = \"сохранить_скриншот_в_логи\"\n    md[\"save_screenshot_to_logs\"][9] = \"guardar_captura_de_pantalla_para_logs\"\n\n    md[\"choose_file\"] = [\"*\"] * num_langs\n    md[\"choose_file\"][0] = \"choose_file\"\n    md[\"choose_file\"][1] = \"选择文件\"\n    md[\"choose_file\"][2] = \"selecteer_bestand\"\n    md[\"choose_file\"][3] = \"sélectionner_fichier\"\n    md[\"choose_file\"][4] = \"seleziona_file\"\n    md[\"choose_file\"][5] = \"ファイルを選択\"\n    md[\"choose_file\"][6] = \"파일을_선택\"\n    md[\"choose_file\"][7] = \"selecionar_arquivo\"\n    md[\"choose_file\"][8] = \"выберите_файл\"\n    md[\"choose_file\"][9] = \"seleccionar_archivo\"\n\n    md[\"execute_script\"] = [\"*\"] * num_langs\n    md[\"execute_script\"][0] = \"execute_script\"\n    md[\"execute_script\"][1] = \"执行脚本\"\n    md[\"execute_script\"][2] = \"script_uitvoeren\"\n    md[\"execute_script\"][3] = \"exécuter_script\"\n    md[\"execute_script\"][4] = \"eseguire_script\"\n    md[\"execute_script\"][5] = \"スクリプトを実行する\"\n    md[\"execute_script\"][6] = \"스크립트를_실행하려면\"\n    md[\"execute_script\"][7] = \"executar_script\"\n    md[\"execute_script\"][8] = \"выполнение_скрипта\"\n    md[\"execute_script\"][9] = \"ejecutar_script\"\n\n    md[\"safe_execute_script\"] = [\"*\"] * num_langs\n    md[\"safe_execute_script\"][0] = \"safe_execute_script\"\n    md[\"safe_execute_script\"][1] = \"安全执行脚本\"\n    md[\"safe_execute_script\"][2] = \"script_veilig_uitvoeren\"\n    md[\"safe_execute_script\"][3] = \"exécuter_script_sans_risque\"\n    md[\"safe_execute_script\"][4] = \"eseguire_script_sicuro\"\n    md[\"safe_execute_script\"][5] = \"スクリプトを安全に実行する\"\n    md[\"safe_execute_script\"][6] = \"스크립트를_안전하게_실행\"\n    md[\"safe_execute_script\"][7] = \"executar_script_com_segurança\"\n    md[\"safe_execute_script\"][8] = \"безопасное_выполнение_скрипта\"\n    md[\"safe_execute_script\"][9] = \"ejecutar_script_de_forma_segura\"\n\n    md[\"activate_jquery\"] = [\"*\"] * num_langs\n    md[\"activate_jquery\"][0] = \"activate_jquery\"\n    md[\"activate_jquery\"][1] = \"加载JQUERY\"\n    md[\"activate_jquery\"][2] = \"activeer_jquery\"\n    md[\"activate_jquery\"][3] = \"activer_jquery\"\n    md[\"activate_jquery\"][4] = \"attiva_jquery\"\n    md[\"activate_jquery\"][5] = \"JQUERYを読み込む\"\n    md[\"activate_jquery\"][6] = \"JQUERY_로드\"\n    md[\"activate_jquery\"][7] = \"ativar_jquery\"\n    md[\"activate_jquery\"][8] = \"активировать_JQUERY\"\n    md[\"activate_jquery\"][9] = \"activar_jquery\"\n\n    md[\"activate_recorder\"] = [\"*\"] * num_langs\n    md[\"activate_recorder\"][0] = \"activate_recorder\"\n    md[\"activate_recorder\"][1] = \"加载RECORDER\"\n    md[\"activate_recorder\"][2] = \"activeer_recorder\"\n    md[\"activate_recorder\"][3] = \"activer_recorder\"\n    md[\"activate_recorder\"][4] = \"attiva_recorder\"\n    md[\"activate_recorder\"][5] = \"RECORDERを読み込む\"\n    md[\"activate_recorder\"][6] = \"RECORDER_로드\"\n    md[\"activate_recorder\"][7] = \"ativar_recorder\"\n    md[\"activate_recorder\"][8] = \"активировать_RECORDER\"\n    md[\"activate_recorder\"][9] = \"activar_recorder\"\n\n    md[\"open_if_not_url\"] = [\"*\"] * num_langs\n    md[\"open_if_not_url\"][0] = \"open_if_not_url\"\n    md[\"open_if_not_url\"][1] = \"开启如果不网址\"\n    md[\"open_if_not_url\"][2] = \"openen_zo_niet_url\"\n    md[\"open_if_not_url\"][3] = \"ouvrir_si_non_url\"\n    md[\"open_if_not_url\"][4] = \"apri_se_non_url\"\n    md[\"open_if_not_url\"][5] = \"URLでない場合は開く\"\n    md[\"open_if_not_url\"][6] = \"URL_이_아닌_경우_열기\"\n    md[\"open_if_not_url\"][7] = \"abrir_se_não_url\"\n    md[\"open_if_not_url\"][8] = \"открыть_если_не_URL\"\n    md[\"open_if_not_url\"][9] = \"abrir_que_no_url\"\n\n    md[\"ad_block\"] = [\"*\"] * num_langs\n    md[\"ad_block\"][0] = \"ad_block\"\n    md[\"ad_block\"][1] = \"阻止广告\"\n    md[\"ad_block\"][2] = \"blokkeer_advertenties\"\n    md[\"ad_block\"][3] = \"annonces_de_bloc\"\n    md[\"ad_block\"][4] = \"bloccare_gli_annunci\"\n    md[\"ad_block\"][5] = \"ブロック広告\"\n    md[\"ad_block\"][6] = \"광고_차단\"\n    md[\"ad_block\"][7] = \"bloquear_anúncios\"\n    md[\"ad_block\"][8] = \"блокировать_рекламу\"\n    md[\"ad_block\"][9] = \"bloquear_anuncios\"\n\n    md[\"skip\"] = [\"*\"] * num_langs\n    md[\"skip\"][0] = \"skip\"\n    md[\"skip\"][1] = \"跳过\"\n    md[\"skip\"][2] = \"overslaan\"\n    md[\"skip\"][3] = \"sauter\"\n    md[\"skip\"][4] = \"saltare\"\n    md[\"skip\"][5] = \"スキップ\"\n    md[\"skip\"][6] = \"건너뛸\"\n    md[\"skip\"][7] = \"saltar\"\n    md[\"skip\"][8] = \"пропускать\"\n    md[\"skip\"][9] = \"saltar\"\n\n    md[\"assert_no_404_errors\"] = [\"*\"] * num_langs\n    md[\"assert_no_404_errors\"][0] = \"assert_no_404_errors\"\n    md[\"assert_no_404_errors\"][1] = \"检查断开的链接\"\n    md[\"assert_no_404_errors\"][2] = \"controleren_op_gebroken_links\"\n    md[\"assert_no_404_errors\"][3] = \"vérifier_les_liens_rompus\"\n    md[\"assert_no_404_errors\"][4] = \"verificare_i_collegamenti\"\n    md[\"assert_no_404_errors\"][5] = \"リンク切れを確認する\"\n    md[\"assert_no_404_errors\"][6] = \"끊어진_링크_확인\"\n    md[\"assert_no_404_errors\"][7] = \"verificar_se_há_links_quebrados\"\n    md[\"assert_no_404_errors\"][8] = \"проверить_ошибки_404\"\n    md[\"assert_no_404_errors\"][9] = \"verificar_si_hay_enlaces_rotos\"\n\n    md[\"assert_no_js_errors\"] = [\"*\"] * num_langs\n    md[\"assert_no_js_errors\"][0] = \"assert_no_js_errors\"\n    md[\"assert_no_js_errors\"][1] = \"检查JS错误\"\n    md[\"assert_no_js_errors\"][2] = \"controleren_op_js_fouten\"\n    md[\"assert_no_js_errors\"][3] = \"vérifier_les_erreurs_js\"\n    md[\"assert_no_js_errors\"][4] = \"controlla_errori_js\"\n    md[\"assert_no_js_errors\"][5] = \"JSエラーを確認する\"\n    md[\"assert_no_js_errors\"][6] = \"JS_오류_확인\"\n    md[\"assert_no_js_errors\"][7] = \"verificar_se_há_erros_js\"\n    md[\"assert_no_js_errors\"][8] = \"проверить_ошибки_JS\"\n    md[\"assert_no_js_errors\"][9] = \"verificar_si_hay_errores_js\"\n\n    md[\"switch_to_frame\"] = [\"*\"] * num_langs\n    md[\"switch_to_frame\"][0] = \"switch_to_frame\"\n    md[\"switch_to_frame\"][1] = \"切换到帧\"\n    md[\"switch_to_frame\"][2] = \"overschakelen_naar_frame\"\n    md[\"switch_to_frame\"][3] = \"passer_au_cadre\"\n    md[\"switch_to_frame\"][4] = \"passa_alla_cornice\"\n    md[\"switch_to_frame\"][5] = \"フレームに切り替えます\"\n    md[\"switch_to_frame\"][6] = \"프레임으로_전환\"\n    md[\"switch_to_frame\"][7] = \"mudar_para_o_quadro\"\n    md[\"switch_to_frame\"][8] = \"переключиться_на_кадр\"\n    md[\"switch_to_frame\"][9] = \"cambiar_al_marco\"\n\n    md[\"switch_to_default_content\"] = [\"*\"] * num_langs\n    md[\"switch_to_default_content\"][0] = \"switch_to_default_content\"\n    md[\"switch_to_default_content\"][1] = \"切换到默认内容\"\n    md[\"switch_to_default_content\"][2] = \"overschakelen_naar_standaardcontent\"\n    md[\"switch_to_default_content\"][3] = \"passer_au_contenu_par_défaut\"\n    md[\"switch_to_default_content\"][4] = \"passa_al_contenuto_predefinito\"\n    md[\"switch_to_default_content\"][5] = \"デフォルトのコンテンツに切り替える\"\n    md[\"switch_to_default_content\"][6] = \"기본_콘텐츠로_전환\"\n    md[\"switch_to_default_content\"][7] = \"mudar_para_o_conteúdo_padrão\"\n    stdc_ru = \"переключиться_на_содержимое_по_умолчанию\"\n    md[\"switch_to_default_content\"][8] = stdc_ru\n    md[\"switch_to_default_content\"][9] = \"cambiar_al_contenido_predeterminado\"\n\n    md[\"switch_to_parent_frame\"] = [\"*\"] * num_langs\n    md[\"switch_to_parent_frame\"][0] = \"switch_to_parent_frame\"\n    md[\"switch_to_parent_frame\"][1] = \"切换到父框架\"\n    md[\"switch_to_parent_frame\"][2] = \"overschakelen_naar_bovenliggend_frame\"\n    md[\"switch_to_parent_frame\"][3] = \"passer_au_cadre_parent\"\n    md[\"switch_to_parent_frame\"][4] = \"passa_alla_cornice_principale\"\n    md[\"switch_to_parent_frame\"][5] = \"親フレームに切り替えます\"\n    md[\"switch_to_parent_frame\"][6] = \"상위_프레임으로_전환\"\n    md[\"switch_to_parent_frame\"][7] = \"mudar_para_o_quadro_pai\"\n    md[\"switch_to_parent_frame\"][8] = \"переключиться_на_родительский_кадр\"\n    md[\"switch_to_parent_frame\"][9] = \"cambiar_al_marco_principal\"\n\n    md[\"open_new_window\"] = [\"*\"] * num_langs\n    md[\"open_new_window\"][0] = \"open_new_window\"\n    md[\"open_new_window\"][1] = \"打开新窗口\"\n    md[\"open_new_window\"][2] = \"nieuw_venster_openen\"\n    md[\"open_new_window\"][3] = \"ouvrir_une_nouvelle_fenêtre\"\n    md[\"open_new_window\"][4] = \"apri_una_nuova_finestra\"\n    md[\"open_new_window\"][5] = \"新しいウィンドウを開く\"\n    md[\"open_new_window\"][6] = \"새_창_열기\"\n    md[\"open_new_window\"][7] = \"abrir_nova_janela\"\n    md[\"open_new_window\"][8] = \"открыть_новое_окно\"\n    md[\"open_new_window\"][9] = \"abrir_una_nueva_ventana\"\n\n    md[\"switch_to_window\"] = [\"*\"] * num_langs\n    md[\"switch_to_window\"][0] = \"switch_to_window\"\n    md[\"switch_to_window\"][1] = \"切换到窗口\"\n    md[\"switch_to_window\"][2] = \"overschakelen_naar_venster\"\n    md[\"switch_to_window\"][3] = \"passer_à_fenêtre\"\n    md[\"switch_to_window\"][4] = \"passa_alla_finestra\"\n    md[\"switch_to_window\"][5] = \"ウィンドウに切り替え\"\n    md[\"switch_to_window\"][6] = \"창으로_전환\"\n    md[\"switch_to_window\"][7] = \"mudar_para_janela\"\n    md[\"switch_to_window\"][8] = \"переключиться_на_окно\"\n    md[\"switch_to_window\"][9] = \"cambiar_a_ventana\"\n\n    md[\"switch_to_default_window\"] = [\"*\"] * num_langs\n    md[\"switch_to_default_window\"][0] = \"switch_to_default_window\"\n    md[\"switch_to_default_window\"][1] = \"切换到默认窗口\"\n    md[\"switch_to_default_window\"][2] = \"overschakelen_naar_standaardvenster\"\n    md[\"switch_to_default_window\"][3] = \"passer_à_fenêtre_par_défaut\"\n    md[\"switch_to_default_window\"][4] = \"passa_alla_finestra_predefinita\"\n    md[\"switch_to_default_window\"][5] = \"デフォルトのウィンドウに切り替える\"\n    md[\"switch_to_default_window\"][6] = \"기본_창으로_전환\"\n    md[\"switch_to_default_window\"][7] = \"mudar_para_a_janela_padrão\"\n    md[\"switch_to_default_window\"][8] = \"переключиться_на_окно_по_умолчанию\"\n    md[\"switch_to_default_window\"][9] = \"cambiar_a_ventana_predeterminada\"\n\n    md[\"switch_to_newest_window\"] = [\"*\"] * num_langs\n    md[\"switch_to_newest_window\"][0] = \"switch_to_newest_window\"\n    md[\"switch_to_newest_window\"][1] = \"切换到最新的窗口\"\n    md[\"switch_to_newest_window\"][2] = \"overschakelen_naar_nieuwste_venster\"\n    md[\"switch_to_newest_window\"][3] = \"passer_à_fenêtre_dernière\"\n    md[\"switch_to_newest_window\"][4] = \"passa_alla_finestra_ultimo\"\n    md[\"switch_to_newest_window\"][5] = \"最新のウィンドウに切り替えます\"\n    md[\"switch_to_newest_window\"][6] = \"최신_창으로_전환\"\n    md[\"switch_to_newest_window\"][7] = \"mudar_para_a_janela_última\"\n    md[\"switch_to_newest_window\"][8] = \"переключиться_на_последнее_окно\"\n    md[\"switch_to_newest_window\"][9] = \"cambiar_a_ventana_última\"\n\n    md[\"maximize_window\"] = [\"*\"] * num_langs\n    md[\"maximize_window\"][0] = \"maximize_window\"\n    md[\"maximize_window\"][1] = \"最大化窗口\"\n    md[\"maximize_window\"][2] = \"venster_maximaliseren\"\n    md[\"maximize_window\"][3] = \"maximiser_fenêtre\"\n    md[\"maximize_window\"][4] = \"ingrandisci_finestra\"\n    md[\"maximize_window\"][5] = \"ウィンドウを最大化する\"\n    md[\"maximize_window\"][6] = \"창_최대화\"\n    md[\"maximize_window\"][7] = \"maximizar_janela\"\n    md[\"maximize_window\"][8] = \"максимальное_окно\"\n    md[\"maximize_window\"][9] = \"maximizar_ventana\"\n\n    md[\"highlight\"] = [\"*\"] * num_langs\n    md[\"highlight\"][0] = \"highlight\"\n    md[\"highlight\"][1] = \"亮点\"\n    md[\"highlight\"][2] = \"markeren\"\n    md[\"highlight\"][3] = \"illuminer\"\n    md[\"highlight\"][4] = \"illuminare\"\n    md[\"highlight\"][5] = \"ハイライト\"\n    md[\"highlight\"][6] = \"강조\"\n    md[\"highlight\"][7] = \"destaque\"\n    md[\"highlight\"][8] = \"осветить\"\n    md[\"highlight\"][9] = \"resalte\"\n\n    md[\"highlight_click\"] = [\"*\"] * num_langs\n    md[\"highlight_click\"][0] = \"highlight_click\"\n    md[\"highlight_click\"][1] = \"亮点单击\"\n    md[\"highlight_click\"][2] = \"markeren_klik\"\n    md[\"highlight_click\"][3] = \"illuminer_cliquer\"\n    md[\"highlight_click\"][4] = \"illuminare_clic\"\n    md[\"highlight_click\"][5] = \"ハイライトしてクリックして\"\n    md[\"highlight_click\"][6] = \"강조_클릭\"\n    md[\"highlight_click\"][7] = \"destaque_clique\"\n    md[\"highlight_click\"][8] = \"осветить_нажмите\"\n    md[\"highlight_click\"][9] = \"resalte_clic\"\n\n    md[\"scroll_to\"] = [\"*\"] * num_langs\n    md[\"scroll_to\"][0] = \"scroll_to\"\n    md[\"scroll_to\"][1] = \"滚动到\"\n    md[\"scroll_to\"][2] = \"scrollen_naar\"\n    md[\"scroll_to\"][3] = \"déménager_à\"\n    md[\"scroll_to\"][4] = \"scorrere_fino_a\"\n    md[\"scroll_to\"][5] = \"スクロールして\"\n    md[\"scroll_to\"][6] = \"요소로_스크롤\"\n    md[\"scroll_to\"][7] = \"rolar_para\"\n    md[\"scroll_to\"][8] = \"прокрутить_к\"\n    md[\"scroll_to\"][9] = \"desplazarse_a\"\n\n    md[\"scroll_to_top\"] = [\"*\"] * num_langs\n    md[\"scroll_to_top\"][0] = \"scroll_to_top\"\n    md[\"scroll_to_top\"][1] = \"滚动到顶部\"\n    md[\"scroll_to_top\"][2] = \"naar_boven_scrollen\"\n    md[\"scroll_to_top\"][3] = \"faites_défiler_vers_le_haut\"\n    md[\"scroll_to_top\"][4] = \"scorri_verso_alto\"\n    md[\"scroll_to_top\"][5] = \"一番上までスクロール\"\n    md[\"scroll_to_top\"][6] = \"맨_위로_스크롤\"\n    md[\"scroll_to_top\"][7] = \"rolar_para_o_topo\"\n    md[\"scroll_to_top\"][8] = \"пролистать_наверх\"\n    md[\"scroll_to_top\"][9] = \"desplazarse_hasta_la_parte_superior\"\n\n    md[\"scroll_to_bottom\"] = [\"*\"] * num_langs\n    md[\"scroll_to_bottom\"][0] = \"scroll_to_bottom\"\n    md[\"scroll_to_bottom\"][1] = \"滚动到底部\"\n    md[\"scroll_to_bottom\"][2] = \"naar_beneden_scrollen\"\n    md[\"scroll_to_bottom\"][3] = \"faites_défiler_vers_le_bas\"\n    md[\"scroll_to_bottom\"][4] = \"scorri_verso_il_basso\"\n    md[\"scroll_to_bottom\"][5] = \"一番下までスクロール\"\n    md[\"scroll_to_bottom\"][6] = \"하단으로_스크롤\"\n    md[\"scroll_to_bottom\"][7] = \"rolar_para_o_fundo\"\n    md[\"scroll_to_bottom\"][8] = \"прокрутить_вниз\"\n    md[\"scroll_to_bottom\"][9] = \"desplazarse_hasta_la_parte_inferior\"\n\n    md[\"hover_and_click\"] = [\"*\"] * num_langs\n    md[\"hover_and_click\"][0] = \"hover_and_click\"\n    md[\"hover_and_click\"][1] = \"鼠标悬停并单击\"\n    md[\"hover_and_click\"][2] = \"zweven_en_klik\"\n    md[\"hover_and_click\"][3] = \"passer_la_souris_et_cliquer\"\n    md[\"hover_and_click\"][4] = \"passare_il_mouse_e_fare_clic\"\n    md[\"hover_and_click\"][5] = \"マウスオーバーしてクリック\"\n    md[\"hover_and_click\"][6] = \"마우스오버_및_클릭\"\n    md[\"hover_and_click\"][7] = \"passe_o_mouse_e_clique\"\n    md[\"hover_and_click\"][8] = \"наведите_и_нажмите\"\n    md[\"hover_and_click\"][9] = \"pasar_el_ratón_y_hacer_clic\"\n\n    md[\"hover\"] = [\"*\"] * num_langs\n    md[\"hover\"][0] = \"hover\"\n    md[\"hover\"][1] = \"鼠标悬停\"\n    md[\"hover\"][2] = \"zweven\"\n    md[\"hover\"][3] = \"survol_de_la_souris\"\n    md[\"hover\"][4] = \"passaggio_del_mouse\"\n    md[\"hover\"][5] = \"マウスオーバー\"\n    md[\"hover\"][6] = \"마우스오버\"\n    md[\"hover\"][7] = \"passe_o_mouse\"\n    md[\"hover\"][8] = \"наведение_мыши\"\n    md[\"hover\"][9] = \"pasar_el_ratón\"\n\n    md[\"is_selected\"] = [\"*\"] * num_langs\n    md[\"is_selected\"][0] = \"is_selected\"\n    md[\"is_selected\"][1] = \"是否被选中\"\n    md[\"is_selected\"][2] = \"is_het_geselecteerd\"\n    md[\"is_selected\"][3] = \"est_il_sélectionné\"\n    md[\"is_selected\"][4] = \"è_selezionato\"\n    md[\"is_selected\"][5] = \"選択されていることを\"\n    md[\"is_selected\"][6] = \"선택되어_있는지\"\n    md[\"is_selected\"][7] = \"é_selecionado\"\n    md[\"is_selected\"][8] = \"выбран\"\n    md[\"is_selected\"][9] = \"está_seleccionado\"\n\n    md[\"press_up_arrow\"] = [\"*\"] * num_langs\n    md[\"press_up_arrow\"][0] = \"press_up_arrow\"\n    md[\"press_up_arrow\"][1] = \"按向上箭头\"\n    md[\"press_up_arrow\"][2] = \"druk_op_pijl_omhoog\"\n    md[\"press_up_arrow\"][3] = \"appuyer_sur_flèche_haut\"\n    md[\"press_up_arrow\"][4] = \"premere_la_freccia_su\"\n    md[\"press_up_arrow\"][5] = \"上矢印を押します\"\n    md[\"press_up_arrow\"][6] = \"위쪽_화살표를_누릅니다\"\n    md[\"press_up_arrow\"][7] = \"pressione_a_seta_para_cima\"\n    md[\"press_up_arrow\"][8] = \"нажмите_стрелку_вверх\"\n    md[\"press_up_arrow\"][9] = \"presione_la_flecha_hacia_arriba\"\n\n    md[\"press_down_arrow\"] = [\"*\"] * num_langs\n    md[\"press_down_arrow\"][0] = \"press_down_arrow\"\n    md[\"press_down_arrow\"][1] = \"按向下箭头\"\n    md[\"press_down_arrow\"][2] = \"druk_op_pijl_omlaag\"\n    md[\"press_down_arrow\"][3] = \"appuyer_sur_flèche_bas\"\n    md[\"press_down_arrow\"][4] = \"premere_la_freccia_giù\"\n    md[\"press_down_arrow\"][5] = \"下矢印を押します\"\n    md[\"press_down_arrow\"][6] = \"아래쪽_화살표를_누르십시오\"\n    md[\"press_down_arrow\"][7] = \"pressione_a_seta_para_baixo\"\n    md[\"press_down_arrow\"][8] = \"нажмите_стрелку_вниз\"\n    md[\"press_down_arrow\"][9] = \"presione_la_flecha_hacia_abajo\"\n\n    md[\"press_left_arrow\"] = [\"*\"] * num_langs\n    md[\"press_left_arrow\"][0] = \"press_left_arrow\"\n    md[\"press_left_arrow\"][1] = \"按向左箭头\"\n    md[\"press_left_arrow\"][2] = \"druk_op_pijl_links\"\n    md[\"press_left_arrow\"][3] = \"appuyer_sur_flèche_gauche\"\n    md[\"press_left_arrow\"][4] = \"premere_la_freccia_sinistra\"\n    md[\"press_left_arrow\"][5] = \"左矢印を押します\"\n    md[\"press_left_arrow\"][6] = \"왼쪽_화살표를_누르십시오\"\n    md[\"press_left_arrow\"][7] = \"pressione_a_seta_esquerda\"\n    md[\"press_left_arrow\"][8] = \"нажмите_стрелку_влево\"\n    md[\"press_left_arrow\"][9] = \"presione_la_flecha_izquierda\"\n\n    md[\"press_right_arrow\"] = [\"*\"] * num_langs\n    md[\"press_right_arrow\"][0] = \"press_right_arrow\"\n    md[\"press_right_arrow\"][1] = \"按向右箭头\"\n    md[\"press_right_arrow\"][2] = \"druk_op_pijl_rechts\"\n    md[\"press_right_arrow\"][3] = \"appuyer_sur_flèche_droite\"\n    md[\"press_right_arrow\"][4] = \"premere_la_freccia_destra\"\n    md[\"press_right_arrow\"][5] = \"右矢印を押します\"\n    md[\"press_right_arrow\"][6] = \"오른쪽_화살표를_누르십시오\"\n    md[\"press_right_arrow\"][7] = \"pressione_a_seta_direita\"\n    md[\"press_right_arrow\"][8] = \"нажмите_стрелку_вправо\"\n    md[\"press_right_arrow\"][9] = \"presione_la_flecha_derecha\"\n\n    md[\"click_visible_elements\"] = [\"*\"] * num_langs\n    md[\"click_visible_elements\"][0] = \"click_visible_elements\"\n    md[\"click_visible_elements\"][1] = \"单击可见元素\"\n    md[\"click_visible_elements\"][2] = \"klik_zichtbare_elementen\"\n    md[\"click_visible_elements\"][3] = \"cliquer_éléments_visibles\"\n    md[\"click_visible_elements\"][4] = \"clic_sugli_elementi_visibili\"\n    md[\"click_visible_elements\"][5] = \"表示要素をクリックします\"\n    md[\"click_visible_elements\"][6] = \"페이지_요소를_클릭_합니다\"\n    md[\"click_visible_elements\"][7] = \"clique_nos_elementos_visíveis\"\n    md[\"click_visible_elements\"][8] = \"нажмите_видимые_элементы\"\n    md[\"click_visible_elements\"][9] = \"clic_en_elementos_visibles\"\n\n    md[\"select_option_by_text\"] = [\"*\"] * num_langs\n    md[\"select_option_by_text\"][0] = \"select_option_by_text\"\n    md[\"select_option_by_text\"][1] = \"按文本选择选项\"\n    md[\"select_option_by_text\"][2] = \"optie_selecteren_op_tekst\"\n    md[\"select_option_by_text\"][3] = \"sélectionner_option_par_texte\"\n    md[\"select_option_by_text\"][4] = \"selezionare_opzione_per_testo\"\n    md[\"select_option_by_text\"][5] = \"テキストでオプションを選択\"\n    md[\"select_option_by_text\"][6] = \"텍스트로_옵션_선택\"\n    md[\"select_option_by_text\"][7] = \"selecionar_opção_por_texto\"\n    md[\"select_option_by_text\"][8] = \"выбрать_опцию_по_тексту\"\n    md[\"select_option_by_text\"][9] = \"seleccionar_opción_por_texto\"\n\n    md[\"select_option_by_index\"] = [\"*\"] * num_langs\n    md[\"select_option_by_index\"][0] = \"select_option_by_index\"\n    md[\"select_option_by_index\"][1] = \"按索引选择选项\"\n    md[\"select_option_by_index\"][2] = \"optie_selecteren_op_index\"\n    md[\"select_option_by_index\"][3] = \"sélectionner_option_par_index\"\n    md[\"select_option_by_index\"][4] = \"selezionare_opzione_per_indice\"\n    md[\"select_option_by_index\"][5] = \"インデックスでオプションを選択\"\n    md[\"select_option_by_index\"][6] = \"인덱스별로_옵션_선택\"\n    md[\"select_option_by_index\"][7] = \"selecionar_opção_por_índice\"\n    md[\"select_option_by_index\"][8] = \"выбрать_опцию_по_индексу\"\n    md[\"select_option_by_index\"][9] = \"seleccionar_opción_por_índice\"\n\n    md[\"select_option_by_value\"] = [\"*\"] * num_langs\n    md[\"select_option_by_value\"][0] = \"select_option_by_value\"\n    md[\"select_option_by_value\"][1] = \"按值选择选项\"\n    md[\"select_option_by_value\"][2] = \"optie_selecteren_op_waarde\"\n    md[\"select_option_by_value\"][3] = \"sélectionner_option_par_valeur\"\n    md[\"select_option_by_value\"][4] = \"selezionare_opzione_per_valore\"\n    md[\"select_option_by_value\"][5] = \"値でオプションを選択\"\n    md[\"select_option_by_value\"][6] = \"값별로_옵션_선택\"\n    md[\"select_option_by_value\"][7] = \"selecionar_opção_por_valor\"\n    md[\"select_option_by_value\"][8] = \"выбрать_опцию_по_значению\"\n    md[\"select_option_by_value\"][9] = \"seleccionar_opción_por_valor\"\n\n    md[\"create_presentation\"] = [\"*\"] * num_langs\n    md[\"create_presentation\"][0] = \"create_presentation\"\n    md[\"create_presentation\"][1] = \"创建演示文稿\"\n    md[\"create_presentation\"][2] = \"maak_een_presentatie\"\n    md[\"create_presentation\"][3] = \"créer_une_présentation\"\n    md[\"create_presentation\"][4] = \"creare_una_presentazione\"\n    md[\"create_presentation\"][5] = \"プレゼンテーションを作成する\"\n    md[\"create_presentation\"][6] = \"프레젠테이션_만들기\"\n    md[\"create_presentation\"][7] = \"criar_uma_apresentação\"\n    md[\"create_presentation\"][8] = \"создать_презентацию\"\n    md[\"create_presentation\"][9] = \"crear_una_presentación\"\n\n    md[\"add_slide\"] = [\"*\"] * num_langs\n    md[\"add_slide\"][0] = \"add_slide\"\n    md[\"add_slide\"][1] = \"添加幻灯片\"\n    md[\"add_slide\"][2] = \"een_dia_toevoegen\"\n    md[\"add_slide\"][3] = \"ajouter_une_diapositive\"\n    md[\"add_slide\"][4] = \"aggiungere_una_diapositiva\"\n    md[\"add_slide\"][5] = \"スライドを追加する\"\n    md[\"add_slide\"][6] = \"슬라이드_추가\"\n    md[\"add_slide\"][7] = \"adicionar_um_slide\"\n    md[\"add_slide\"][8] = \"добавить_слайд\"\n    md[\"add_slide\"][9] = \"agregar_una_diapositiva\"\n\n    md[\"save_presentation\"] = [\"*\"] * num_langs\n    md[\"save_presentation\"][0] = \"save_presentation\"\n    md[\"save_presentation\"][1] = \"保存演示文稿\"\n    md[\"save_presentation\"][2] = \"de_presentatie_opslaan\"\n    md[\"save_presentation\"][3] = \"enregistrer_la_présentation\"\n    md[\"save_presentation\"][4] = \"salva_la_presentazione\"\n    md[\"save_presentation\"][5] = \"プレゼンテーションを保存する\"\n    md[\"save_presentation\"][6] = \"프레젠테이션_저장\"\n    md[\"save_presentation\"][7] = \"salvar_apresentação\"\n    md[\"save_presentation\"][8] = \"сохранить_презентацию\"\n    md[\"save_presentation\"][9] = \"guardar_presentación\"\n\n    md[\"begin_presentation\"] = [\"*\"] * num_langs\n    md[\"begin_presentation\"][0] = \"begin_presentation\"\n    md[\"begin_presentation\"][1] = \"开始演示文稿\"\n    md[\"begin_presentation\"][2] = \"de_presentatie_starten\"\n    md[\"begin_presentation\"][3] = \"démarrer_la_présentation\"\n    md[\"begin_presentation\"][4] = \"avviare_la_presentazione\"\n    md[\"begin_presentation\"][5] = \"プレゼンテーションを開始する\"\n    md[\"begin_presentation\"][6] = \"프레젠테이션_시작\"\n    md[\"begin_presentation\"][7] = \"iniciar_apresentação\"\n    md[\"begin_presentation\"][8] = \"начать_презентацию\"\n    md[\"begin_presentation\"][9] = \"iniciar_presentación\"\n\n    md[\"create_pie_chart\"] = [\"*\"] * num_langs\n    md[\"create_pie_chart\"][0] = \"create_pie_chart\"\n    md[\"create_pie_chart\"][1] = \"创建饼图\"\n    md[\"create_pie_chart\"][2] = \"maak_een_cirkeldiagram\"\n    md[\"create_pie_chart\"][3] = \"créer_un_graphique_à_secteurs\"\n    md[\"create_pie_chart\"][4] = \"creare_un_grafico_a_torta\"\n    md[\"create_pie_chart\"][5] = \"円グラフを作成する\"\n    md[\"create_pie_chart\"][6] = \"원형_차트_만들기\"\n    md[\"create_pie_chart\"][7] = \"criar_um_gráfico_de_pizza\"\n    md[\"create_pie_chart\"][8] = \"создать_круговую_диаграмму\"\n    md[\"create_pie_chart\"][9] = \"crear_un_gráfico_circular\"\n\n    md[\"create_bar_chart\"] = [\"*\"] * num_langs\n    md[\"create_bar_chart\"][0] = \"create_bar_chart\"\n    md[\"create_bar_chart\"][1] = \"创建条形图\"\n    md[\"create_bar_chart\"][2] = \"maak_een_staafdiagram\"\n    md[\"create_bar_chart\"][3] = \"créer_un_graphique_à_barres\"\n    md[\"create_bar_chart\"][4] = \"creare_un_grafico_a_barre\"\n    md[\"create_bar_chart\"][5] = \"棒グラフを作成する\"\n    md[\"create_bar_chart\"][6] = \"막대_차트_만들기\"\n    md[\"create_bar_chart\"][7] = \"criar_um_gráfico_de_barras\"\n    md[\"create_bar_chart\"][8] = \"создать_бар_диаграмму\"\n    md[\"create_bar_chart\"][9] = \"crear_un_gráfico_de_barras\"\n\n    md[\"create_column_chart\"] = [\"*\"] * num_langs\n    md[\"create_column_chart\"][0] = \"create_column_chart\"\n    md[\"create_column_chart\"][1] = \"创建柱形图\"\n    md[\"create_column_chart\"][2] = \"maak_een_kolomdiagram\"\n    md[\"create_column_chart\"][3] = \"créer_un_graphique_à_colonnes\"\n    md[\"create_column_chart\"][4] = \"creare_un_grafico_a_colonne\"\n    md[\"create_column_chart\"][5] = \"縦棒グラフを作成する\"\n    md[\"create_column_chart\"][6] = \"열_차트_만들기\"\n    md[\"create_column_chart\"][7] = \"criar_um_gráfico_de_colunas\"\n    md[\"create_column_chart\"][8] = \"создать_столбчатую_диаграмму\"\n    md[\"create_column_chart\"][9] = \"crear_un_gráfico_de_columnas\"\n\n    md[\"create_line_chart\"] = [\"*\"] * num_langs\n    md[\"create_line_chart\"][0] = \"create_line_chart\"\n    md[\"create_line_chart\"][1] = \"创建折线图\"\n    md[\"create_line_chart\"][2] = \"maak_een_lijndiagram\"\n    md[\"create_line_chart\"][3] = \"créer_un_graphique_linéaire\"\n    md[\"create_line_chart\"][4] = \"creare_un_grafico_a_linee\"\n    md[\"create_line_chart\"][5] = \"折れ線グラフを作成する\"\n    md[\"create_line_chart\"][6] = \"선_차트_만들기\"\n    md[\"create_line_chart\"][7] = \"criar_um_gráfico_de_linhas\"\n    md[\"create_line_chart\"][8] = \"создать_линейную_диаграмму\"\n    md[\"create_line_chart\"][9] = \"crear_un_gráfico_de_líneas\"\n\n    md[\"create_area_chart\"] = [\"*\"] * num_langs\n    md[\"create_area_chart\"][0] = \"create_area_chart\"\n    md[\"create_area_chart\"][1] = \"创建面积图\"\n    md[\"create_area_chart\"][2] = \"maak_een_vlakdiagram\"\n    md[\"create_area_chart\"][3] = \"créer_un_graphique_en_aires\"\n    md[\"create_area_chart\"][4] = \"creare_un_grafico_ad_area\"\n    md[\"create_area_chart\"][5] = \"面グラフを作成する\"\n    md[\"create_area_chart\"][6] = \"영역_차트_만들기\"\n    md[\"create_area_chart\"][7] = \"criar_um_gráfico_de_área\"\n    md[\"create_area_chart\"][8] = \"создать_диаграмму_области\"\n    md[\"create_area_chart\"][9] = \"crear_un_gráfico_de_área\"\n\n    md[\"add_series_to_chart\"] = [\"*\"] * num_langs\n    md[\"add_series_to_chart\"][0] = \"add_series_to_chart\"\n    md[\"add_series_to_chart\"][1] = \"将系列添加到图表\"\n    md[\"add_series_to_chart\"][2] = \"reeksen_toevoegen_aan_grafiek\"\n    md[\"add_series_to_chart\"][3] = \"ajouter_séries_au_graphique\"\n    md[\"add_series_to_chart\"][4] = \"aggiungere_serie_al_grafico\"\n    md[\"add_series_to_chart\"][5] = \"グラフに系列を追加する\"\n    md[\"add_series_to_chart\"][6] = \"차트에_시리즈_추가\"\n    md[\"add_series_to_chart\"][7] = \"adicionar_séries_ao_gráfico\"\n    md[\"add_series_to_chart\"][8] = \"добавить_серии_в_диаграмму\"\n    md[\"add_series_to_chart\"][9] = \"agregar_series_al_gráfico\"\n\n    md[\"add_data_point\"] = [\"*\"] * num_langs\n    md[\"add_data_point\"][0] = \"add_data_point\"\n    md[\"add_data_point\"][1] = \"添加数据点\"\n    md[\"add_data_point\"][2] = \"gegevenspunt_toevoegen\"\n    md[\"add_data_point\"][3] = \"ajouter_un_point_de_données\"\n    md[\"add_data_point\"][4] = \"aggiungi_punto_dati\"\n    md[\"add_data_point\"][5] = \"データポイントを追加する\"\n    md[\"add_data_point\"][6] = \"데이터_포인트_추가\"\n    md[\"add_data_point\"][7] = \"adicionar_ponto_de_dados\"\n    md[\"add_data_point\"][8] = \"добавить_точку_данных\"\n    md[\"add_data_point\"][9] = \"agregar_punto_de_datos\"\n\n    md[\"save_chart\"] = [\"*\"] * num_langs\n    md[\"save_chart\"][0] = \"save_chart\"\n    md[\"save_chart\"][1] = \"保存图表\"\n    md[\"save_chart\"][2] = \"grafiek_opslaan\"\n    md[\"save_chart\"][3] = \"enregistrer_le_graphique\"\n    md[\"save_chart\"][4] = \"salva_il_grafico\"\n    md[\"save_chart\"][5] = \"グラフを保存する\"\n    md[\"save_chart\"][6] = \"차트_저장\"\n    md[\"save_chart\"][7] = \"salvar_gráfico\"\n    md[\"save_chart\"][8] = \"сохранить_диаграмму\"\n    md[\"save_chart\"][9] = \"guardar_gráfico\"\n\n    md[\"display_chart\"] = [\"*\"] * num_langs\n    md[\"display_chart\"][0] = \"display_chart\"\n    md[\"display_chart\"][1] = \"显示图表\"\n    md[\"display_chart\"][2] = \"grafiek_weergeven\"\n    md[\"display_chart\"][3] = \"afficher_le_graphique\"\n    md[\"display_chart\"][4] = \"mostra_il_grafico\"\n    md[\"display_chart\"][5] = \"グラフを表示する\"\n    md[\"display_chart\"][6] = \"차트_표시\"\n    md[\"display_chart\"][7] = \"exibir_gráfico\"\n    md[\"display_chart\"][8] = \"отображать_диаграмму\"\n    md[\"display_chart\"][9] = \"muestra_gráfico\"\n\n    md[\"extract_chart\"] = [\"*\"] * num_langs\n    md[\"extract_chart\"][0] = \"extract_chart\"\n    md[\"extract_chart\"][1] = \"提取图表\"\n    md[\"extract_chart\"][2] = \"grafiek_uitpakken\"\n    md[\"extract_chart\"][3] = \"extraire_le_graphique\"\n    md[\"extract_chart\"][4] = \"estrarre_il_grafico\"\n    md[\"extract_chart\"][5] = \"グラフを抽出する\"\n    md[\"extract_chart\"][6] = \"차트_추출\"\n    md[\"extract_chart\"][7] = \"extrair_gráfico\"\n    md[\"extract_chart\"][8] = \"извлекать_диаграмму\"\n    md[\"extract_chart\"][9] = \"extracto_gráfico\"\n\n    md[\"create_tour\"] = [\"*\"] * num_langs\n    md[\"create_tour\"][0] = \"create_tour\"\n    md[\"create_tour\"][1] = \"创建游览\"\n    md[\"create_tour\"][2] = \"maak_een_tour\"\n    md[\"create_tour\"][3] = \"créer_une_visite\"\n    md[\"create_tour\"][4] = \"creare_un_tour\"\n    md[\"create_tour\"][5] = \"ツアーを作成する\"\n    md[\"create_tour\"][6] = \"가이드_투어_만들기\"\n    md[\"create_tour\"][7] = \"criar_um_tour\"\n    md[\"create_tour\"][8] = \"создать_тур\"\n    md[\"create_tour\"][9] = \"crear_una_gira\"\n\n    md[\"create_shepherd_tour\"] = [\"*\"] * num_langs\n    md[\"create_shepherd_tour\"][0] = \"create_shepherd_tour\"\n    md[\"create_shepherd_tour\"][1] = \"创建SHEPHERD游览\"\n    md[\"create_shepherd_tour\"][2] = \"maak_een_shepherd_tour\"\n    md[\"create_shepherd_tour\"][3] = \"créer_une_visite_shepherd\"\n    md[\"create_shepherd_tour\"][4] = \"creare_un_tour_shepherd\"\n    md[\"create_shepherd_tour\"][5] = \"SHEPHERDツアーを作成する\"\n    md[\"create_shepherd_tour\"][6] = \"가이드_SHEPHERD_투어_만들기\"\n    md[\"create_shepherd_tour\"][7] = \"criar_um_tour_shepherd\"\n    md[\"create_shepherd_tour\"][8] = \"создать_SHEPHERD_тур\"\n    md[\"create_shepherd_tour\"][9] = \"crear_una_gira_shepherd\"\n\n    md[\"create_bootstrap_tour\"] = [\"*\"] * num_langs\n    md[\"create_bootstrap_tour\"][0] = \"create_bootstrap_tour\"\n    md[\"create_bootstrap_tour\"][1] = \"创建BOOTSTRAP游览\"\n    md[\"create_bootstrap_tour\"][2] = \"maak_een_bootstrap_tour\"\n    md[\"create_bootstrap_tour\"][3] = \"créer_une_visite_bootstrap\"\n    md[\"create_bootstrap_tour\"][4] = \"creare_un_tour_bootstrap\"\n    md[\"create_bootstrap_tour\"][5] = \"BOOTSTRAPツアーを作成する\"\n    md[\"create_bootstrap_tour\"][6] = \"가이드_BOOTSTRAP_투어_만들기\"\n    md[\"create_bootstrap_tour\"][7] = \"criar_um_tour_bootstrap\"\n    md[\"create_bootstrap_tour\"][8] = \"создать_BOOTSTRAP_тур\"\n    md[\"create_bootstrap_tour\"][9] = \"crear_una_gira_bootstrap\"\n\n    md[\"create_driverjs_tour\"] = [\"*\"] * num_langs\n    md[\"create_driverjs_tour\"][0] = \"create_driverjs_tour\"\n    md[\"create_driverjs_tour\"][1] = \"创建DRIVERJS游览\"\n    md[\"create_driverjs_tour\"][2] = \"maak_een_driverjs_tour\"\n    md[\"create_driverjs_tour\"][3] = \"créer_une_visite_driverjs\"\n    md[\"create_driverjs_tour\"][4] = \"creare_un_tour_driverjs\"\n    md[\"create_driverjs_tour\"][5] = \"DRIVERJSツアーを作成する\"\n    md[\"create_driverjs_tour\"][6] = \"가이드_DRIVERJS_투어_만들기\"\n    md[\"create_driverjs_tour\"][7] = \"criar_um_tour_driverjs\"\n    md[\"create_driverjs_tour\"][8] = \"создать_DRIVERJS_тур\"\n    md[\"create_driverjs_tour\"][9] = \"crear_una_gira_driverjs\"\n\n    md[\"create_hopscotch_tour\"] = [\"*\"] * num_langs\n    md[\"create_hopscotch_tour\"][0] = \"create_hopscotch_tour\"\n    md[\"create_hopscotch_tour\"][1] = \"创建HOPSCOTCH游览\"\n    md[\"create_hopscotch_tour\"][2] = \"maak_een_hopscotch_tour\"\n    md[\"create_hopscotch_tour\"][3] = \"créer_une_visite_hopscotch\"\n    md[\"create_hopscotch_tour\"][4] = \"creare_un_tour_hopscotch\"\n    md[\"create_hopscotch_tour\"][5] = \"HOPSCOTCHツアーを作成する\"\n    md[\"create_hopscotch_tour\"][6] = \"가이드_HOPSCOTCH_투어_만들기\"\n    md[\"create_hopscotch_tour\"][7] = \"criar_um_tour_hopscotch\"\n    md[\"create_hopscotch_tour\"][8] = \"создать_HOPSCOTCH_тур\"\n    md[\"create_hopscotch_tour\"][9] = \"crear_una_gira_hopscotch\"\n\n    md[\"create_introjs_tour\"] = [\"*\"] * num_langs\n    md[\"create_introjs_tour\"][0] = \"create_introjs_tour\"\n    md[\"create_introjs_tour\"][1] = \"创建INTROJS游览\"\n    md[\"create_introjs_tour\"][2] = \"maak_een_introjs_tour\"\n    md[\"create_introjs_tour\"][3] = \"créer_une_visite_introjs\"\n    md[\"create_introjs_tour\"][4] = \"creare_un_tour_introjs\"\n    md[\"create_introjs_tour\"][5] = \"INTROJSツアーを作成する\"\n    md[\"create_introjs_tour\"][6] = \"가이드_INTROJS_투어_만들기\"\n    md[\"create_introjs_tour\"][7] = \"criar_um_tour_introjs\"\n    md[\"create_introjs_tour\"][8] = \"создать_INTROJS_тур\"\n    md[\"create_introjs_tour\"][9] = \"crear_una_gira_introjs\"\n\n    md[\"add_tour_step\"] = [\"*\"] * num_langs\n    md[\"add_tour_step\"][0] = \"add_tour_step\"\n    md[\"add_tour_step\"][1] = \"添加游览步骤\"\n    md[\"add_tour_step\"][2] = \"toevoegen_tour_stap\"\n    md[\"add_tour_step\"][3] = \"ajouter_étape_à_la_visite\"\n    md[\"add_tour_step\"][4] = \"aggiungere_passo_al_tour\"\n    md[\"add_tour_step\"][5] = \"ツアーステップを追加する\"\n    md[\"add_tour_step\"][6] = \"둘러보기_단계_추가\"\n    md[\"add_tour_step\"][7] = \"adicionar_passo_para_o_tour\"\n    md[\"add_tour_step\"][8] = \"добавить_шаг_в_тур\"\n    md[\"add_tour_step\"][9] = \"agregar_paso_a_la_gira\"\n\n    md[\"play_tour\"] = [\"*\"] * num_langs\n    md[\"play_tour\"][0] = \"play_tour\"\n    md[\"play_tour\"][1] = \"播放游览\"\n    md[\"play_tour\"][2] = \"speel_de_tour\"\n    md[\"play_tour\"][3] = \"jouer_la_visite\"\n    md[\"play_tour\"][4] = \"riprodurre_il_tour\"\n    md[\"play_tour\"][5] = \"ツアーを再生する\"\n    md[\"play_tour\"][6] = \"가이드_투어를하다\"\n    md[\"play_tour\"][7] = \"jogar_o_tour\"\n    md[\"play_tour\"][8] = \"играть_тур\"\n    md[\"play_tour\"][9] = \"reproducir_la_gira\"\n\n    md[\"export_tour\"] = [\"*\"] * num_langs\n    md[\"export_tour\"][0] = \"export_tour\"\n    md[\"export_tour\"][1] = \"导出游览\"\n    md[\"export_tour\"][2] = \"de_tour_exporteren\"\n    md[\"export_tour\"][3] = \"exporter_la_visite\"\n    md[\"export_tour\"][4] = \"esportare_il_tour\"\n    md[\"export_tour\"][5] = \"ツアーをエクスポートする\"\n    md[\"export_tour\"][6] = \"가이드_투어_내보내기\"\n    md[\"export_tour\"][7] = \"exportar_o_tour\"\n    md[\"export_tour\"][8] = \"экспортировать_тур\"\n    md[\"export_tour\"][9] = \"exportar_la_gira\"\n\n    md[\"get_pdf_text\"] = [\"*\"] * num_langs\n    md[\"get_pdf_text\"][0] = \"get_pdf_text\"\n    md[\"get_pdf_text\"][1] = \"获取PDF文本\"\n    md[\"get_pdf_text\"][2] = \"pdf_tekst_ophalen\"\n    md[\"get_pdf_text\"][3] = \"obtenir_texte_pdf\"\n    md[\"get_pdf_text\"][4] = \"ottenere_testo_pdf\"\n    md[\"get_pdf_text\"][5] = \"PDFテキストを取得\"\n    md[\"get_pdf_text\"][6] = \"PDF_텍스트를_검색\"\n    md[\"get_pdf_text\"][7] = \"obter_texto_pdf\"\n    md[\"get_pdf_text\"][8] = \"получить_текст_PDF\"\n    md[\"get_pdf_text\"][9] = \"obtener_texto_pdf\"\n\n    md[\"assert_pdf_text\"] = [\"*\"] * num_langs\n    md[\"assert_pdf_text\"][0] = \"assert_pdf_text\"\n    md[\"assert_pdf_text\"][1] = \"断言PDF文本\"\n    md[\"assert_pdf_text\"][2] = \"controleren_pdf_tekst\"\n    md[\"assert_pdf_text\"][3] = \"vérifier_texte_pdf\"\n    md[\"assert_pdf_text\"][4] = \"verificare_testo_pdf\"\n    md[\"assert_pdf_text\"][5] = \"PDFテキストを確認する\"\n    md[\"assert_pdf_text\"][6] = \"PDF_텍스트_확인\"\n    md[\"assert_pdf_text\"][7] = \"verificar_texto_pdf\"\n    md[\"assert_pdf_text\"][8] = \"подтвердить_текст_PDF\"\n    md[\"assert_pdf_text\"][9] = \"verificar_texto_pdf\"\n\n    md[\"download_file\"] = [\"*\"] * num_langs\n    md[\"download_file\"][0] = \"download_file\"\n    md[\"download_file\"][1] = \"下载文件\"\n    md[\"download_file\"][2] = \"bestand_downloaden\"\n    md[\"download_file\"][3] = \"télécharger_fichier\"\n    md[\"download_file\"][4] = \"scaricare_file\"\n    md[\"download_file\"][5] = \"ファイルをダウンロード\"\n    md[\"download_file\"][6] = \"파일_다운로드\"\n    md[\"download_file\"][7] = \"baixar_arquivo\"\n    md[\"download_file\"][8] = \"скачать_файл\"\n    md[\"download_file\"][9] = \"descargar_archivo\"\n\n    md[\"is_downloaded_file_present\"] = [\"*\"] * num_langs\n    md[\"is_downloaded_file_present\"][0] = \"is_downloaded_file_present\"\n    md[\"is_downloaded_file_present\"][1] = \"下载的文件是否存在\"\n    md[\"is_downloaded_file_present\"][2] = \"gedownloade_bestand_aanwezig\"\n    md[\"is_downloaded_file_present\"][3] = \"est_un_fichier_téléchargé_présent\"\n    md[\"is_downloaded_file_present\"][4] = \"è_file_scaricato_presente\"\n    md[\"is_downloaded_file_present\"][5] = \"ダウンロードしたファイルが存在するかどうか\"\n    md[\"is_downloaded_file_present\"][6] = \"다운로드한_파일이_있습니다\"\n    md[\"is_downloaded_file_present\"][7] = \"o_arquivo_baixado_está_presente\"\n    md[\"is_downloaded_file_present\"][8] = \"загруженный_файл_присутствует\"\n    md[\"is_downloaded_file_present\"][9] = \"está_presente_el_archivo_descargado\"\n\n    md[\"get_path_of_downloaded_file\"] = [\"*\"] * num_langs\n    md[\"get_path_of_downloaded_file\"][0] = \"get_path_of_downloaded_file\"\n    md[\"get_path_of_downloaded_file\"][1] = \"获取下载的文件路径\"\n    md[\"get_path_of_downloaded_file\"][2] = \"pad_gedownloade_bestand_ophalen\"\n    gpodf_fr = \"obtenir_chemin_du_fichier_téléchargé\"\n    md[\"get_path_of_downloaded_file\"][3] = gpodf_fr\n    gpodf_it = \"ottenere_percorso_del_file_scaricato\"\n    md[\"get_path_of_downloaded_file\"][4] = gpodf_it\n    md[\"get_path_of_downloaded_file\"][5] = \"ダウンロードしたファイルパスを取得する\"\n    md[\"get_path_of_downloaded_file\"][6] = \"다운로드한_파일_경로_가져_오기\"\n    md[\"get_path_of_downloaded_file\"][7] = \"obter_caminho_do_arquivo_baixado\"\n    md[\"get_path_of_downloaded_file\"][8] = \"получить_путь_к_загруженному_файлу\"\n    gpodf_es = \"obtener_ruta_del_archivo_descargado\"\n    md[\"get_path_of_downloaded_file\"][9] = gpodf_es\n\n    md[\"assert_downloaded_file\"] = [\"*\"] * num_langs\n    md[\"assert_downloaded_file\"][0] = \"assert_downloaded_file\"\n    md[\"assert_downloaded_file\"][1] = \"检查下载的文件\"\n    md[\"assert_downloaded_file\"][2] = \"controleren_gedownloade_bestand\"\n    md[\"assert_downloaded_file\"][3] = \"vérifier_fichier_téléchargé\"\n    md[\"assert_downloaded_file\"][4] = \"verificare_file_scaricato\"\n    md[\"assert_downloaded_file\"][5] = \"ダウンロードしたファイルを確認する\"\n    md[\"assert_downloaded_file\"][6] = \"다운로드한_파일_확인\"\n    md[\"assert_downloaded_file\"][7] = \"verificar_arquivo_baixado\"\n    md[\"assert_downloaded_file\"][8] = \"подтвердить_загруженный_файл\"\n    md[\"assert_downloaded_file\"][9] = \"verificar_archivo_descargado\"\n\n    md[\"delete_downloaded_file\"] = [\"*\"] * num_langs\n    md[\"delete_downloaded_file\"][0] = \"delete_downloaded_file\"\n    md[\"delete_downloaded_file\"][1] = \"删除下载的文件\"\n    md[\"delete_downloaded_file\"][2] = \"verwijder_gedownloade_bestand\"\n    md[\"delete_downloaded_file\"][3] = \"supprimer_fichier_téléchargé\"\n    md[\"delete_downloaded_file\"][4] = \"eliminare_file_scaricato\"\n    md[\"delete_downloaded_file\"][5] = \"ダウンロードしたファイルを削除する\"\n    md[\"delete_downloaded_file\"][6] = \"다운로드한_파일_삭제\"\n    md[\"delete_downloaded_file\"][7] = \"exclua_arquivo_baixado\"\n    md[\"delete_downloaded_file\"][8] = \"удалить_загруженный_файл\"\n    md[\"delete_downloaded_file\"][9] = \"eliminar_archivo_descargado\"\n\n    md[\"fail\"] = [\"*\"] * num_langs\n    md[\"fail\"][0] = \"fail\"\n    md[\"fail\"][1] = \"失败\"\n    md[\"fail\"][2] = \"mislukken\"\n    md[\"fail\"][3] = \"échouer\"\n    md[\"fail\"][4] = \"fallire\"\n    md[\"fail\"][5] = \"失敗\"\n    md[\"fail\"][6] = \"실패\"\n    md[\"fail\"][7] = \"falhar\"\n    md[\"fail\"][8] = \"провалить\"\n    md[\"fail\"][9] = \"fallar\"\n\n    md[\"get\"] = [\"*\"] * num_langs\n    md[\"get\"][0] = \"get\"\n    md[\"get\"][1] = \"获取\"\n    md[\"get\"][2] = \"ophalen\"\n    md[\"get\"][3] = \"obtenir\"\n    md[\"get\"][4] = \"ottenere\"\n    md[\"get\"][5] = \"を取得する\"\n    md[\"get\"][6] = \"받기\"\n    md[\"get\"][7] = \"obter\"\n    md[\"get\"][8] = \"получить\"\n    md[\"get\"][9] = \"obtener\"\n\n    md[\"visit\"] = [\"*\"] * num_langs\n    md[\"visit\"][0] = \"visit\"\n    md[\"visit\"][1] = \"访问\"\n    md[\"visit\"][2] = \"bezoek\"\n    md[\"visit\"][3] = \"visiter\"\n    md[\"visit\"][4] = \"visita\"\n    md[\"visit\"][5] = \"を訪問\"\n    md[\"visit\"][6] = \"방문\"\n    md[\"visit\"][7] = \"visitar\"\n    md[\"visit\"][8] = \"посетить\"\n    md[\"visit\"][9] = \"visita\"\n\n    md[\"visit_url\"] = [\"*\"] * num_langs\n    md[\"visit_url\"][0] = \"visit_url\"\n    md[\"visit_url\"][1] = \"访问网址\"\n    md[\"visit_url\"][2] = \"bezoek_url\"\n    md[\"visit_url\"][3] = \"visiter_url\"\n    md[\"visit_url\"][4] = \"visita_url\"\n    md[\"visit_url\"][5] = \"URLを訪問\"\n    md[\"visit_url\"][6] = \"방문_URL\"\n    md[\"visit_url\"][7] = \"visitar_url\"\n    md[\"visit_url\"][8] = \"посетить_URL\"\n    md[\"visit_url\"][9] = \"visita_url\"\n\n    md[\"get_element\"] = [\"*\"] * num_langs\n    md[\"get_element\"][0] = \"get_element\"\n    md[\"get_element\"][1] = \"获取元素\"\n    md[\"get_element\"][2] = \"element_ophalen\"\n    md[\"get_element\"][3] = \"obtenir_élément\"\n    md[\"get_element\"][4] = \"ottenere_elemento\"\n    md[\"get_element\"][5] = \"要素を取得する\"\n    md[\"get_element\"][6] = \"요소_검색\"\n    md[\"get_element\"][7] = \"obter_elemento\"\n    md[\"get_element\"][8] = \"получить_элемент\"\n    md[\"get_element\"][9] = \"obtener_elemento\"\n\n    md[\"find_element\"] = [\"*\"] * num_langs\n    md[\"find_element\"][0] = \"find_element\"\n    md[\"find_element\"][1] = \"查找元素\"\n    md[\"find_element\"][2] = \"vind_element\"\n    md[\"find_element\"][3] = \"trouver_élément\"\n    md[\"find_element\"][4] = \"trovare_elemento\"\n    md[\"find_element\"][5] = \"要素を見つける\"\n    md[\"find_element\"][6] = \"요소를_찾을\"\n    md[\"find_element\"][7] = \"encontrar_elemento\"\n    md[\"find_element\"][8] = \"найти_элемент\"\n    md[\"find_element\"][9] = \"encontrar_elemento\"\n\n    md[\"remove_element\"] = [\"*\"] * num_langs\n    md[\"remove_element\"][0] = \"remove_element\"\n    md[\"remove_element\"][1] = \"删除第一个元素\"\n    md[\"remove_element\"][2] = \"verwijder_element\"\n    md[\"remove_element\"][3] = \"supprimer_élément\"\n    md[\"remove_element\"][4] = \"rimuovere_elemento\"\n    md[\"remove_element\"][5] = \"最初の要素を削除\"\n    md[\"remove_element\"][6] = \"첫_번째_요소_제거\"\n    md[\"remove_element\"][7] = \"remover_elemento\"\n    md[\"remove_element\"][8] = \"удалить_элемент\"\n    md[\"remove_element\"][9] = \"eliminar_elemento\"\n\n    md[\"remove_elements\"] = [\"*\"] * num_langs\n    md[\"remove_elements\"][0] = \"remove_elements\"\n    md[\"remove_elements\"][1] = \"删除所有元素\"\n    md[\"remove_elements\"][2] = \"verwijder_elementen\"\n    md[\"remove_elements\"][3] = \"supprimer_éléments\"\n    md[\"remove_elements\"][4] = \"rimuovere_elementi\"\n    md[\"remove_elements\"][5] = \"すべての要素を削除\"\n    md[\"remove_elements\"][6] = \"모든_요소_제거\"\n    md[\"remove_elements\"][7] = \"remover_elementos\"\n    md[\"remove_elements\"][8] = \"удалить_элементы\"\n    md[\"remove_elements\"][9] = \"eliminar_elementos\"\n\n    md[\"find_text\"] = [\"*\"] * num_langs\n    md[\"find_text\"][0] = \"find_text\"\n    md[\"find_text\"][1] = \"查找文本\"\n    md[\"find_text\"][2] = \"vind_tekst\"\n    md[\"find_text\"][3] = \"trouver_texte\"\n    md[\"find_text\"][4] = \"trovare_testo\"\n    md[\"find_text\"][5] = \"テキストを見つける\"\n    md[\"find_text\"][6] = \"텍스트_찾기\"\n    md[\"find_text\"][7] = \"encontrar_texto\"\n    md[\"find_text\"][8] = \"найти_текст\"\n    md[\"find_text\"][9] = \"encontrar_texto\"\n\n    md[\"set_text\"] = [\"*\"] * num_langs\n    md[\"set_text\"][0] = \"set_text\"\n    md[\"set_text\"][1] = \"设置文本\"\n    md[\"set_text\"][2] = \"tekst_instellen\"\n    md[\"set_text\"][3] = \"définir_texte\"\n    md[\"set_text\"][4] = \"impostare_testo\"\n    md[\"set_text\"][5] = \"テキストを設定する\"\n    md[\"set_text\"][6] = \"텍스트_설정\"\n    md[\"set_text\"][7] = \"definir_texto\"\n    md[\"set_text\"][8] = \"набор_текст\"\n    md[\"set_text\"][9] = \"establecer_texto\"\n\n    md[\"get_attribute\"] = [\"*\"] * num_langs\n    md[\"get_attribute\"][0] = \"get_attribute\"\n    md[\"get_attribute\"][1] = \"获取属性\"\n    md[\"get_attribute\"][2] = \"attribuut_ophalen\"\n    md[\"get_attribute\"][3] = \"obtenir_attribut\"\n    md[\"get_attribute\"][4] = \"ottenere_attributo\"\n    md[\"get_attribute\"][5] = \"属性を取得する\"\n    md[\"get_attribute\"][6] = \"특성_검색\"\n    md[\"get_attribute\"][7] = \"obter_atributo\"\n    md[\"get_attribute\"][8] = \"получить_атрибут\"\n    md[\"get_attribute\"][9] = \"obtener_atributo\"\n\n    md[\"set_attribute\"] = [\"*\"] * num_langs\n    md[\"set_attribute\"][0] = \"set_attribute\"\n    md[\"set_attribute\"][1] = \"设置属性\"\n    md[\"set_attribute\"][2] = \"attribuut_instellen\"\n    md[\"set_attribute\"][3] = \"définir_attribut\"\n    md[\"set_attribute\"][4] = \"imposta_attributo\"\n    md[\"set_attribute\"][5] = \"属性を設定する\"\n    md[\"set_attribute\"][6] = \"특성_설정\"\n    md[\"set_attribute\"][7] = \"definir_atributo\"\n    md[\"set_attribute\"][8] = \"набор_атрибута\"\n    md[\"set_attribute\"][9] = \"establecer_atributo\"\n\n    md[\"set_attributes\"] = [\"*\"] * num_langs\n    md[\"set_attributes\"][0] = \"set_attributes\"\n    md[\"set_attributes\"][1] = \"设置所有属性\"\n    md[\"set_attributes\"][2] = \"attributen_instellen\"\n    md[\"set_attributes\"][3] = \"définir_attributs\"\n    md[\"set_attributes\"][4] = \"impostare_gli_attributi\"\n    md[\"set_attributes\"][5] = \"すべての属性を設定\"\n    md[\"set_attributes\"][6] = \"모든_특성_설정\"\n    md[\"set_attributes\"][7] = \"definir_atributos\"\n    md[\"set_attributes\"][8] = \"набор_атрибутов\"\n    md[\"set_attributes\"][9] = \"establecer_atributos\"\n\n    md[\"set_content\"] = [\"*\"] * num_langs\n    md[\"set_content\"][0] = \"set_content\"\n    md[\"set_content\"][1] = \"设置HTML\"\n    md[\"set_content\"][2] = \"html_instellen\"\n    md[\"set_content\"][3] = \"définir_html\"\n    md[\"set_content\"][4] = \"impostare_html\"\n    md[\"set_content\"][5] = \"HTML設定する\"\n    md[\"set_content\"][6] = \"HTML_설정\"\n    md[\"set_content\"][7] = \"definir_html\"\n    md[\"set_content\"][8] = \"набор_HTML\"\n    md[\"set_content\"][9] = \"establecer_html\"\n\n    md[\"type\"] = [\"*\"] * num_langs\n    md[\"type\"][0] = \"type\"\n    md[\"type\"][1] = \"输入文本\"\n    md[\"type\"][2] = \"typ\"\n    md[\"type\"][3] = \"taper\"\n    md[\"type\"][4] = \"digitare\"\n    md[\"type\"][5] = \"入力\"\n    md[\"type\"][6] = \"입력\"\n    md[\"type\"][7] = \"digitar\"\n    md[\"type\"][8] = \"введите\"\n    md[\"type\"][9] = \"escriba\"\n\n    md[\"write\"] = [\"*\"] * num_langs\n    md[\"write\"][0] = \"write\"\n    md[\"write\"][1] = \"写文本\"\n    md[\"write\"][2] = \"schrijven\"\n    md[\"write\"][3] = \"écriver\"\n    md[\"write\"][4] = \"scrivere\"\n    md[\"write\"][5] = \"書く\"\n    md[\"write\"][6] = \"쓰다\"\n    md[\"write\"][7] = \"escreva\"\n    md[\"write\"][8] = \"написать\"\n    md[\"write\"][9] = \"escribir\"\n\n    md[\"set_messenger_theme\"] = [\"*\"] * num_langs\n    md[\"set_messenger_theme\"][0] = \"set_messenger_theme\"\n    md[\"set_messenger_theme\"][1] = \"设置消息主题\"\n    md[\"set_messenger_theme\"][2] = \"thema_van_bericht_instellen\"\n    md[\"set_messenger_theme\"][3] = \"définir_thème_du_message\"\n    md[\"set_messenger_theme\"][4] = \"impostare_tema_del_messaggio\"\n    md[\"set_messenger_theme\"][5] = \"メッセージのスタイルを設定する\"\n    md[\"set_messenger_theme\"][6] = \"메시지_테마_설정\"\n    md[\"set_messenger_theme\"][7] = \"definir_tema_da_mensagem\"\n    md[\"set_messenger_theme\"][8] = \"набор_тему_сообщения\"\n    md[\"set_messenger_theme\"][9] = \"establecer_tema_del_mensaje\"\n\n    md[\"post_message\"] = [\"*\"] * num_langs\n    md[\"post_message\"][0] = \"post_message\"\n    md[\"post_message\"][1] = \"显示讯息\"\n    md[\"post_message\"][2] = \"bericht_weergeven\"\n    md[\"post_message\"][3] = \"afficher_message\"\n    md[\"post_message\"][4] = \"visualizza_messaggio\"\n    md[\"post_message\"][5] = \"メッセージを表示する\"\n    md[\"post_message\"][6] = \"메시지를_표시\"\n    md[\"post_message\"][7] = \"exibir_mensagem\"\n    md[\"post_message\"][8] = \"показать_сообщение\"\n    md[\"post_message\"][9] = \"mostrar_mensaje\"\n\n    md[\"_print\"] = [\"*\"] * num_langs\n    md[\"_print\"][0] = \"_print\"\n    md[\"_print\"][1] = \"打印\"\n    md[\"_print\"][2] = \"afdrukken\"\n    md[\"_print\"][3] = \"imprimer\"\n    md[\"_print\"][4] = \"stampare\"\n    md[\"_print\"][5] = \"印刷\"\n    md[\"_print\"][6] = \"인쇄\"\n    md[\"_print\"][7] = \"imprimir\"\n    md[\"_print\"][8] = \"печатать\"\n    md[\"_print\"][9] = \"imprimir\"\n\n    md[\"deferred_assert_element\"] = [\"*\"] * num_langs\n    md[\"deferred_assert_element\"][0] = \"deferred_assert_element\"\n    md[\"deferred_assert_element\"][1] = \"推迟断言元素\"\n    md[\"deferred_assert_element\"][2] = \"uitgestelde_controleren_element\"\n    md[\"deferred_assert_element\"][3] = \"reporté_vérifier_élément\"\n    md[\"deferred_assert_element\"][4] = \"differita_verificare_elemento\"\n    md[\"deferred_assert_element\"][5] = \"を延期する要素を確認する\"\n    md[\"deferred_assert_element\"][6] = \"연기된_요소_확인\"\n    md[\"deferred_assert_element\"][7] = \"adiada_verificar_elemento\"\n    md[\"deferred_assert_element\"][8] = \"отложенный_подтвердить_элемент\"\n    md[\"deferred_assert_element\"][9] = \"diferido_verificar_elemento\"\n\n    md[\"deferred_assert_text\"] = [\"*\"] * num_langs\n    md[\"deferred_assert_text\"][0] = \"deferred_assert_text\"\n    md[\"deferred_assert_text\"][1] = \"推迟断言文本\"\n    md[\"deferred_assert_text\"][2] = \"uitgestelde_controleren_tekst\"\n    md[\"deferred_assert_text\"][3] = \"reporté_vérifier_texte\"\n    md[\"deferred_assert_text\"][4] = \"differita_verificare_testo\"\n    md[\"deferred_assert_text\"][5] = \"を延期するテキストを確認する\"\n    md[\"deferred_assert_text\"][6] = \"연기된_텍스트_확인\"\n    md[\"deferred_assert_text\"][7] = \"adiada_verificar_texto\"\n    md[\"deferred_assert_text\"][8] = \"отложенный_подтвердить_текст\"\n    md[\"deferred_assert_text\"][9] = \"diferido_verificar_texto\"\n\n    md[\"process_deferred_asserts\"] = [\"*\"] * num_langs\n    md[\"process_deferred_asserts\"][0] = \"process_deferred_asserts\"\n    md[\"process_deferred_asserts\"][1] = \"处理推迟断言\"\n    md[\"process_deferred_asserts\"][2] = \"verwerken_uitgestelde_controleren\"\n    md[\"process_deferred_asserts\"][3] = \"effectuer_vérifications_reportées\"\n    md[\"process_deferred_asserts\"][4] = \"elaborare_differita_verificari\"\n    md[\"process_deferred_asserts\"][5] = \"遅延アサーションの処理\"\n    md[\"process_deferred_asserts\"][6] = \"연기된_검증_처리\"\n    md[\"process_deferred_asserts\"][7] = \"processar_verificações_adiada\"\n    md[\"process_deferred_asserts\"][8] = \"обработки_отложенных_подтверждений\"\n    md[\"process_deferred_asserts\"][9] = \"procesar_verificaciones_diferidas\"\n\n    md[\"accept_alert\"] = [\"*\"] * num_langs\n    md[\"accept_alert\"][0] = \"accept_alert\"\n    md[\"accept_alert\"][1] = \"接受警报\"\n    md[\"accept_alert\"][2] = \"waarschuwing_accepteren\"\n    md[\"accept_alert\"][3] = \"accepter_alerte\"\n    md[\"accept_alert\"][4] = \"accetta_avviso\"\n    md[\"accept_alert\"][5] = \"アラートを受け入れる\"\n    md[\"accept_alert\"][6] = \"경고를_수락\"\n    md[\"accept_alert\"][7] = \"aceitar_alerta\"\n    md[\"accept_alert\"][8] = \"принять_оповещение\"\n    md[\"accept_alert\"][9] = \"aceptar_alerta\"\n\n    md[\"dismiss_alert\"] = [\"*\"] * num_langs\n    md[\"dismiss_alert\"][0] = \"dismiss_alert\"\n    md[\"dismiss_alert\"][1] = \"解除警报\"\n    md[\"dismiss_alert\"][2] = \"waarschuwing_wegsturen\"\n    md[\"dismiss_alert\"][3] = \"rejeter_alerte\"\n    md[\"dismiss_alert\"][4] = \"elimina_avviso\"\n    md[\"dismiss_alert\"][5] = \"アラートを却下\"\n    md[\"dismiss_alert\"][6] = \"경고를_거부\"\n    md[\"dismiss_alert\"][7] = \"demitir_alerta\"\n    md[\"dismiss_alert\"][8] = \"увольнять_оповещение\"\n    md[\"dismiss_alert\"][9] = \"descartar_alerta\"\n\n    md[\"switch_to_alert\"] = [\"*\"] * num_langs\n    md[\"switch_to_alert\"][0] = \"switch_to_alert\"\n    md[\"switch_to_alert\"][1] = \"切换到警报\"\n    md[\"switch_to_alert\"][2] = \"overschakelen_naar_waarschuwing\"\n    md[\"switch_to_alert\"][3] = \"passer_à_alerte\"\n    md[\"switch_to_alert\"][4] = \"passa_al_avviso\"\n    md[\"switch_to_alert\"][5] = \"アラートに切り替え\"\n    md[\"switch_to_alert\"][6] = \"경고로_전환\"\n    md[\"switch_to_alert\"][7] = \"mudar_para_alerta\"\n    md[\"switch_to_alert\"][8] = \"переключиться_на_оповещение\"\n    md[\"switch_to_alert\"][9] = \"cambiar_a_alerta\"\n\n    md[\"drag_and_drop\"] = [\"*\"] * num_langs\n    md[\"drag_and_drop\"][0] = \"drag_and_drop\"\n    md[\"drag_and_drop\"][1] = \"拖放\"\n    md[\"drag_and_drop\"][2] = \"slepen_en_neerzetten\"\n    md[\"drag_and_drop\"][3] = \"glisser_et_déposer\"\n    md[\"drag_and_drop\"][4] = \"trascinare_e_rilasciare\"\n    md[\"drag_and_drop\"][5] = \"ドラッグアンドドロップ\"\n    md[\"drag_and_drop\"][6] = \"드래그_앤_드롭\"\n    md[\"drag_and_drop\"][7] = \"arrastar_e_soltar\"\n    md[\"drag_and_drop\"][8] = \"перетащить_и_падение\"\n    md[\"drag_and_drop\"][9] = \"arrastrar_y_soltar\"\n\n    md[\"load_html_file\"] = [\"*\"] * num_langs\n    md[\"load_html_file\"][0] = \"load_html_file\"\n    md[\"load_html_file\"][1] = \"加载HTML文件\"\n    md[\"load_html_file\"][2] = \"html_bestand_laden\"\n    md[\"load_html_file\"][3] = \"charger_html_fichier\"\n    md[\"load_html_file\"][4] = \"caricare_html_file\"\n    md[\"load_html_file\"][5] = \"HTMLファイルを読み込む\"\n    md[\"load_html_file\"][6] = \"HTML_파일_로드\"\n    md[\"load_html_file\"][7] = \"carregar_arquivo_html\"\n    md[\"load_html_file\"][8] = \"загрузить_HTML_файл\"\n    md[\"load_html_file\"][9] = \"cargar_archivo_html\"\n\n    md[\"open_html_file\"] = [\"*\"] * num_langs\n    md[\"open_html_file\"][0] = \"open_html_file\"\n    md[\"open_html_file\"][1] = \"打开HTML文件\"\n    md[\"open_html_file\"][2] = \"html_bestand_openen\"\n    md[\"open_html_file\"][3] = \"ouvrir_html_fichier\"\n    md[\"open_html_file\"][4] = \"apri_html_file\"\n    md[\"open_html_file\"][5] = \"HTMLファイルを開く\"\n    md[\"open_html_file\"][6] = \"HTML_파일_열기\"\n    md[\"open_html_file\"][7] = \"abrir_arquivo_html\"\n    md[\"open_html_file\"][8] = \"открыть_HTML_файл\"\n    md[\"open_html_file\"][9] = \"abrir_archivo_html\"\n\n    md[\"delete_all_cookies\"] = [\"*\"] * num_langs\n    md[\"delete_all_cookies\"][0] = \"delete_all_cookies\"\n    md[\"delete_all_cookies\"][1] = \"删除所有COOKIE\"\n    md[\"delete_all_cookies\"][2] = \"alle_cookies_verwijderen\"\n    md[\"delete_all_cookies\"][3] = \"supprimer_tous_les_cookies\"\n    md[\"delete_all_cookies\"][4] = \"elimina_tutti_i_cookie\"\n    md[\"delete_all_cookies\"][5] = \"すべてのクッキーを削除する\"\n    md[\"delete_all_cookies\"][6] = \"모든_쿠키_삭제\"\n    md[\"delete_all_cookies\"][7] = \"excluir_todos_os_cookies\"\n    md[\"delete_all_cookies\"][8] = \"удалить_все_куки\"\n    md[\"delete_all_cookies\"][9] = \"eliminar_todas_las_cookies\"\n\n    md[\"get_user_agent\"] = [\"*\"] * num_langs\n    md[\"get_user_agent\"][0] = \"get_user_agent\"\n    md[\"get_user_agent\"][1] = \"获取用户代理\"\n    md[\"get_user_agent\"][2] = \"gebruikersagent_ophalen\"\n    md[\"get_user_agent\"][3] = \"obtenir_agent_utilisateur\"\n    md[\"get_user_agent\"][4] = \"ottenere_agente_utente\"\n    md[\"get_user_agent\"][5] = \"ユーザーエージェントの取得\"\n    md[\"get_user_agent\"][6] = \"사용자_에이전트_가져_오기\"\n    md[\"get_user_agent\"][7] = \"obter_agente_do_usuário\"\n    md[\"get_user_agent\"][8] = \"получить_агента_пользователя\"\n    md[\"get_user_agent\"][9] = \"obtener_agente_de_usuario\"\n\n    md[\"get_locale_code\"] = [\"*\"] * num_langs\n    md[\"get_locale_code\"][0] = \"get_locale_code\"\n    md[\"get_locale_code\"][1] = \"获取语言代码\"\n    md[\"get_locale_code\"][2] = \"taalcode_ophalen\"\n    md[\"get_locale_code\"][3] = \"obtenir_code_de_langue\"\n    md[\"get_locale_code\"][4] = \"ottenere_codice_lingua\"\n    md[\"get_locale_code\"][5] = \"言語コードを取得する\"\n    md[\"get_locale_code\"][6] = \"언어_코드를_얻을\"\n    md[\"get_locale_code\"][7] = \"obter_código_de_idioma\"\n    md[\"get_locale_code\"][8] = \"получить_код_языка\"\n    md[\"get_locale_code\"][9] = \"obtener_código_de_idioma\"\n\n    ################\n    # Duplicates\n\n    # \"input\" -> duplicate of \"type\"\n    md[\"input\"] = [\"*\"] * num_langs\n    md[\"input\"][0] = \"input\"\n    md[\"input\"][1] = \"输入文本\"\n    md[\"input\"][2] = \"typ\"\n    md[\"input\"][3] = \"taper\"\n    md[\"input\"][4] = \"digitare\"\n    md[\"input\"][5] = \"入力\"\n    md[\"input\"][6] = \"입력\"\n    md[\"input\"][7] = \"digitar\"\n    md[\"input\"][8] = \"введите\"\n    md[\"input\"][9] = \"escriba\"\n\n    # \"fill\" -> duplicate of \"type\"\n    md[\"fill\"] = [\"*\"] * num_langs\n    md[\"fill\"][0] = \"fill\"\n    md[\"fill\"][1] = \"输入文本\"\n    md[\"fill\"][2] = \"typ\"\n    md[\"fill\"][3] = \"taper\"\n    md[\"fill\"][4] = \"digitare\"\n    md[\"fill\"][5] = \"入力\"\n    md[\"fill\"][6] = \"입력\"\n    md[\"fill\"][7] = \"digitar\"\n    md[\"fill\"][8] = \"введите\"\n    md[\"fill\"][9] = \"escriba\"\n\n    # \"goto\" -> duplicate of \"visit\"\n    md[\"goto\"] = [\"*\"] * num_langs\n    md[\"goto\"][0] = \"goto\"\n    md[\"goto\"][1] = \"访问\"\n    md[\"goto\"][2] = \"bezoek\"\n    md[\"goto\"][3] = \"visiter\"\n    md[\"goto\"][4] = \"visita\"\n    md[\"goto\"][5] = \"を訪問\"\n    md[\"goto\"][6] = \"방문\"\n    md[\"goto\"][7] = \"visitar\"\n    md[\"goto\"][8] = \"посетить\"\n    md[\"goto\"][9] = \"visita\"\n\n    # \"go_to\" -> duplicate of \"visit\"\n    md[\"go_to\"] = [\"*\"] * num_langs\n    md[\"go_to\"][0] = \"go_to\"\n    md[\"go_to\"][1] = \"访问\"\n    md[\"go_to\"][2] = \"bezoek\"\n    md[\"go_to\"][3] = \"visiter\"\n    md[\"go_to\"][4] = \"visita\"\n    md[\"go_to\"][5] = \"を訪問\"\n    md[\"go_to\"][6] = \"방문\"\n    md[\"go_to\"][7] = \"visitar\"\n    md[\"go_to\"][8] = \"посетить\"\n    md[\"go_to\"][9] = \"visita\"\n\n    # \"refresh\" -> duplicate of \"refresh_page\"\n    md[\"refresh\"] = [\"*\"] * num_langs\n    md[\"refresh\"][0] = \"refresh\"\n    md[\"refresh\"][1] = \"刷新页面\"\n    md[\"refresh\"][2] = \"ververs_pagina\"\n    md[\"refresh\"][3] = \"rafraîchir_la_page\"\n    md[\"refresh\"][4] = \"aggiorna_la_pagina\"\n    md[\"refresh\"][5] = \"ページを更新する\"\n    md[\"refresh\"][6] = \"페이지_새로_고침\"\n    md[\"refresh\"][7] = \"atualizar_a_página\"\n    md[\"refresh\"][8] = \"обновить_страницу\"\n    md[\"refresh\"][9] = \"actualizar_la_página\"\n\n    # \"reload\" -> duplicate of \"refresh_page\"\n    md[\"reload\"] = [\"*\"] * num_langs\n    md[\"reload\"][0] = \"reload\"\n    md[\"reload\"][1] = \"刷新页面\"\n    md[\"reload\"][2] = \"ververs_pagina\"\n    md[\"reload\"][3] = \"rafraîchir_la_page\"\n    md[\"reload\"][4] = \"aggiorna_la_pagina\"\n    md[\"reload\"][5] = \"ページを更新する\"\n    md[\"reload\"][6] = \"페이지_새로_고침\"\n    md[\"reload\"][7] = \"atualizar_a_página\"\n    md[\"reload\"][8] = \"обновить_страницу\"\n    md[\"reload\"][9] = \"actualizar_la_página\"\n\n    # \"reload_page\" -> duplicate of \"refresh_page\"\n    md[\"reload_page\"] = [\"*\"] * num_langs\n    md[\"reload_page\"][0] = \"reload_page\"\n    md[\"reload_page\"][1] = \"刷新页面\"\n    md[\"reload_page\"][2] = \"ververs_pagina\"\n    md[\"reload_page\"][3] = \"rafraîchir_la_page\"\n    md[\"reload_page\"][4] = \"aggiorna_la_pagina\"\n    md[\"reload_page\"][5] = \"ページを更新する\"\n    md[\"reload_page\"][6] = \"페이지_새로_고침\"\n    md[\"reload_page\"][7] = \"atualizar_a_página\"\n    md[\"reload_page\"][8] = \"обновить_страницу\"\n    md[\"reload_page\"][9] = \"actualizar_la_página\"\n\n    # \"open_new_tab\" -> duplicate of \"open_new_window\"\n    md[\"open_new_tab\"] = [\"*\"] * num_langs\n    md[\"open_new_tab\"][0] = \"open_new_tab\"\n    md[\"open_new_tab\"][1] = \"打开新窗口\"\n    md[\"open_new_tab\"][2] = \"nieuw_venster_openen\"\n    md[\"open_new_tab\"][3] = \"ouvrir_une_nouvelle_fenêtre\"\n    md[\"open_new_tab\"][4] = \"apri_una_nuova_finestra\"\n    md[\"open_new_tab\"][5] = \"新しいウィンドウを開く\"\n    md[\"open_new_tab\"][6] = \"새_창_열기\"\n    md[\"open_new_tab\"][7] = \"abrir_nova_janela\"\n    md[\"open_new_tab\"][8] = \"открыть_новое_окно\"\n    md[\"open_new_tab\"][9] = \"abrir_una_nueva_ventana\"\n\n    # \"switch_to_newest_tab\" -> duplicate of \"switch_to_newest_window\"\n    md[\"switch_to_newest_tab\"] = [\"*\"] * num_langs\n    md[\"switch_to_newest_tab\"][0] = \"switch_to_newest_tab\"\n    md[\"switch_to_newest_tab\"][1] = \"切换到最新的窗口\"\n    md[\"switch_to_newest_tab\"][2] = \"overschakelen_naar_nieuwste_venster\"\n    md[\"switch_to_newest_tab\"][3] = \"passer_à_fenêtre_dernière\"\n    md[\"switch_to_newest_tab\"][4] = \"passa_alla_finestra_ultimo\"\n    md[\"switch_to_newest_tab\"][5] = \"最新のウィンドウに切り替えます\"\n    md[\"switch_to_newest_tab\"][6] = \"최신_창으로_전환\"\n    md[\"switch_to_newest_tab\"][7] = \"mudar_para_a_janela_última\"\n    md[\"switch_to_newest_tab\"][8] = \"переключиться_на_последнее_окно\"\n    md[\"switch_to_newest_tab\"][9] = \"cambiar_a_ventana_última\"\n\n    # \"get_page_title\" -> duplicate of \"get_title\"\n    md[\"get_page_title\"] = [\"*\"] * num_langs\n    md[\"get_page_title\"][0] = \"get_page_title\"\n    md[\"get_page_title\"][1] = \"获取标题\"\n    md[\"get_page_title\"][2] = \"titel_ophalen\"\n    md[\"get_page_title\"][3] = \"obtenir_le_titre\"\n    md[\"get_page_title\"][4] = \"ottenere_il_titolo\"\n    md[\"get_page_title\"][5] = \"タイトルを取得する\"\n    md[\"get_page_title\"][6] = \"제목_검색\"\n    md[\"get_page_title\"][7] = \"obter_título\"\n    md[\"get_page_title\"][8] = \"получить_название\"\n    md[\"get_page_title\"][9] = \"obtener_título\"\n\n    # \"click_link_text\" -> duplicate of \"click_link\"\n    md[\"click_link_text\"] = [\"*\"] * num_langs\n    md[\"click_link_text\"][0] = \"click_link_text\"\n    md[\"click_link_text\"][1] = \"单击链接文本\"\n    md[\"click_link_text\"][2] = \"klik_linktekst\"\n    md[\"click_link_text\"][3] = \"cliquer_texte_du_lien\"\n    md[\"click_link_text\"][4] = \"clic_testo_del_collegamento\"\n    md[\"click_link_text\"][5] = \"リンクテキストをクリックします\"\n    md[\"click_link_text\"][6] = \"링크_텍스트를_클릭합니다\"\n    md[\"click_link_text\"][7] = \"clique_texto_do_link\"\n    md[\"click_link_text\"][8] = \"нажмите_ссылку\"\n    md[\"click_link_text\"][9] = \"clic_texto_del_enlace\"\n\n    # \"right_click\" -> duplicate of \"context_click\"\n    md[\"right_click\"] = [\"*\"] * num_langs\n    md[\"right_click\"][0] = \"right_click\"\n    md[\"right_click\"][1] = \"上下文点击\"\n    md[\"right_click\"][2] = \"contextklik\"\n    md[\"right_click\"][3] = \"contextuel_cliquer\"\n    md[\"right_click\"][4] = \"clic_contestuale\"\n    md[\"right_click\"][5] = \"コンテキストクリック\"\n    md[\"right_click\"][6] = \"컨텍스트_클릭\"\n    md[\"right_click\"][7] = \"clique_de_contexto\"\n    md[\"right_click\"][8] = \"контекстный_щелчок\"\n    md[\"right_click\"][9] = \"clic_de_contexto\"\n\n    # \"send_keys\" -> duplicate of \"add_text\"\n    md[\"send_keys\"] = [\"*\"] * num_langs\n    md[\"send_keys\"][0] = \"send_keys\"\n    md[\"send_keys\"][1] = \"添加文本\"\n    md[\"send_keys\"][2] = \"tekst_toevoegen\"\n    md[\"send_keys\"][3] = \"ajouter_texte\"\n    md[\"send_keys\"][4] = \"aggiungi_testo\"\n    md[\"send_keys\"][5] = \"テキストを追加\"\n    md[\"send_keys\"][6] = \"텍스트를_추가\"\n    md[\"send_keys\"][7] = \"adicionar_texto\"\n    md[\"send_keys\"][8] = \"добавить_текст\"\n    md[\"send_keys\"][9] = \"agregar_texto\"\n\n    # \"load_html_string\" -> duplicate of \"set_content\"\n    md[\"load_html_string\"] = [\"*\"] * num_langs\n    md[\"load_html_string\"][0] = \"load_html_string\"\n    md[\"load_html_string\"][1] = \"设置HTML\"\n    md[\"load_html_string\"][2] = \"html_instellen\"\n    md[\"load_html_string\"][3] = \"définir_html\"\n    md[\"load_html_string\"][4] = \"impostare_html\"\n    md[\"load_html_string\"][5] = \"HTML設定する\"\n    md[\"load_html_string\"][6] = \"HTML_설정\"\n    md[\"load_html_string\"][7] = \"definir_html\"\n    md[\"load_html_string\"][8] = \"набор_HTML\"\n    md[\"load_html_string\"][9] = \"establecer_html\"\n\n    # \"set_attribute_all\" -> duplicate of \"set_attributes\"\n    md[\"set_attribute_all\"] = [\"*\"] * num_langs\n    md[\"set_attribute_all\"][0] = \"set_attribute_all\"\n    md[\"set_attribute_all\"][1] = \"设置所有属性\"\n    md[\"set_attribute_all\"][2] = \"attributen_instellen\"\n    md[\"set_attribute_all\"][3] = \"définir_attributs\"\n    md[\"set_attribute_all\"][4] = \"impostare_gli_attributi\"\n    md[\"set_attribute_all\"][5] = \"すべての属性を設定\"\n    md[\"set_attribute_all\"][6] = \"모든_특성_설정\"\n    md[\"set_attribute_all\"][7] = \"definir_atributos\"\n    md[\"set_attribute_all\"][8] = \"набор_атрибутов\"\n    md[\"set_attribute_all\"][9] = \"establecer_atributos\"\n\n    # \"is_checked\" -> duplicate of \"is_selected\"\n    md[\"is_checked\"] = [\"*\"] * num_langs\n    md[\"is_checked\"][0] = \"is_checked\"\n    md[\"is_checked\"][1] = \"是否被选中\"\n    md[\"is_checked\"][2] = \"is_het_geselecteerd\"\n    md[\"is_checked\"][3] = \"est_il_sélectionné\"\n    md[\"is_checked\"][4] = \"è_selezionato\"\n    md[\"is_checked\"][5] = \"選択されていることを\"\n    md[\"is_checked\"][6] = \"선택되어_있는지\"\n    md[\"is_checked\"][7] = \"é_selecionado\"\n    md[\"is_checked\"][8] = \"выбран\"\n    md[\"is_checked\"][9] = \"está_seleccionado\"\n\n    # \"wait_for_text_visible\" -> duplicate of \"wait_for_text\"\n    md[\"wait_for_text_visible\"] = [\"*\"] * num_langs\n    md[\"wait_for_text_visible\"][0] = \"wait_for_text_visible\"\n    md[\"wait_for_text_visible\"][1] = \"等待文本\"\n    md[\"wait_for_text_visible\"][2] = \"wachten_op_tekst\"\n    md[\"wait_for_text_visible\"][3] = \"attendre_le_texte\"\n    md[\"wait_for_text_visible\"][4] = \"attendere_il_testo\"\n    md[\"wait_for_text_visible\"][5] = \"テキストを待つ\"\n    md[\"wait_for_text_visible\"][6] = \"텍스트가_나타날_때까지_기다립니다\"\n    md[\"wait_for_text_visible\"][7] = \"aguardar_o_texto\"\n    md[\"wait_for_text_visible\"][8] = \"ждать_текста\"\n    md[\"wait_for_text_visible\"][9] = \"espera_el_texto\"\n\n    # \"assert_text_visible\" -> duplicate of \"assert_text\"\n    md[\"assert_text_visible\"] = [\"*\"] * num_langs\n    md[\"assert_text_visible\"][0] = \"assert_text_visible\"\n    md[\"assert_text_visible\"][1] = \"断言文本\"\n    md[\"assert_text_visible\"][2] = \"controleren_tekst\"\n    md[\"assert_text_visible\"][3] = \"vérifier_texte\"\n    md[\"assert_text_visible\"][4] = \"verificare_testo\"\n    md[\"assert_text_visible\"][5] = \"テキストを確認する\"\n    md[\"assert_text_visible\"][6] = \"텍스트_확인\"\n    md[\"assert_text_visible\"][7] = \"verificar_texto\"\n    md[\"assert_text_visible\"][8] = \"подтвердить_текст\"\n    md[\"assert_text_visible\"][9] = \"verificar_texto\"\n\n    # \"assert_link\" -> duplicate of \"assert_link_text\"\n    md[\"assert_link\"] = [\"*\"] * num_langs\n    md[\"assert_link\"][0] = \"assert_link\"\n    md[\"assert_link\"][1] = \"断言链接文本\"\n    md[\"assert_link\"][2] = \"controleren_linktekst\"\n    md[\"assert_link\"][3] = \"vérifier_texte_du_lien\"\n    md[\"assert_link\"][4] = \"verificare_testo_del_collegamento\"\n    md[\"assert_link\"][5] = \"リンクテキストを確認する\"\n    md[\"assert_link\"][6] = \"링크_텍스트_확인\"\n    md[\"assert_link\"][7] = \"verificar_texto_do_link\"\n    md[\"assert_link\"][8] = \"подтвердить_ссылку\"\n    md[\"assert_link\"][9] = \"verificar_texto_del_enlace\"\n\n    # \"assert_no_broken_links\" -> duplicate of \"assert_no_404_errors\"\n    md[\"assert_no_broken_links\"] = [\"*\"] * num_langs\n    md[\"assert_no_broken_links\"][0] = \"assert_no_broken_links\"\n    md[\"assert_no_broken_links\"][1] = \"检查断开的链接\"\n    md[\"assert_no_broken_links\"][2] = \"controleren_op_gebroken_links\"\n    md[\"assert_no_broken_links\"][3] = \"vérifier_les_liens_rompus\"\n    md[\"assert_no_broken_links\"][4] = \"verificare_i_collegamenti\"\n    md[\"assert_no_broken_links\"][5] = \"リンク切れを確認する\"\n    md[\"assert_no_broken_links\"][6] = \"끊어진_링크_확인\"\n    md[\"assert_no_broken_links\"][7] = \"verificar_se_há_links_quebrados\"\n    md[\"assert_no_broken_links\"][8] = \"проверить_ошибки_404\"\n    md[\"assert_no_broken_links\"][9] = \"verificar_si_hay_enlaces_rotos\"\n\n    # \"block_ads\" -> duplicate of \"ad_block\"\n    md[\"block_ads\"] = [\"*\"] * num_langs\n    md[\"block_ads\"][0] = \"block_ads\"\n    md[\"block_ads\"][1] = \"阻止广告\"\n    md[\"block_ads\"][2] = \"blokkeer_advertenties\"\n    md[\"block_ads\"][3] = \"annonces_de_bloc\"\n    md[\"block_ads\"][4] = \"bloccare_gli_annunci\"\n    md[\"block_ads\"][5] = \"ブロック広告\"\n    md[\"block_ads\"][6] = \"광고_차단\"\n    md[\"block_ads\"][7] = \"bloquear_anúncios\"\n    md[\"block_ads\"][8] = \"блокировать_рекламу\"\n    md[\"block_ads\"][9] = \"bloquear_anuncios\"\n\n    # \"scroll_to_element\" -> duplicate of \"scroll_to\"\n    md[\"scroll_to_element\"] = [\"*\"] * num_langs\n    md[\"scroll_to_element\"][0] = \"scroll_to_element\"\n    md[\"scroll_to_element\"][1] = \"滚动到\"\n    md[\"scroll_to_element\"][2] = \"scrollen_naar\"\n    md[\"scroll_to_element\"][3] = \"déménager_à\"\n    md[\"scroll_to_element\"][4] = \"scorrere_fino_a\"\n    md[\"scroll_to_element\"][5] = \"スクロールして\"\n    md[\"scroll_to_element\"][6] = \"요소로_스크롤\"\n    md[\"scroll_to_element\"][7] = \"rolar_para\"\n    md[\"scroll_to_element\"][8] = \"прокрутить_к\"\n    md[\"scroll_to_element\"][9] = \"desplazarse_a\"\n\n    # \"start_tour\" -> duplicate of \"play_tour\"\n    md[\"start_tour\"] = [\"*\"] * num_langs\n    md[\"start_tour\"][0] = \"start_tour\"\n    md[\"start_tour\"][1] = \"播放游览\"\n    md[\"start_tour\"][2] = \"speel_de_tour\"\n    md[\"start_tour\"][3] = \"jouer_la_visite\"\n    md[\"start_tour\"][4] = \"riprodurre_il_tour\"\n    md[\"start_tour\"][5] = \"ツアーを再生する\"\n    md[\"start_tour\"][6] = \"가이드_투어를하다\"\n    md[\"start_tour\"][7] = \"jogar_o_tour\"\n    md[\"start_tour\"][8] = \"играть_тур\"\n    md[\"start_tour\"][9] = \"reproducir_la_gira\"\n\n    # \"delete_downloaded_file_if_present\" -> double of \"delete_downloaded_file\"\n    md[\"delete_downloaded_file_if_present\"] = [\"*\"] * num_langs\n    ddfip_en = \"delete_downloaded_file_if_present\"\n    md[\"delete_downloaded_file_if_present\"][0] = ddfip_en\n    md[\"delete_downloaded_file_if_present\"][1] = \"删除下载的文件\"\n    ddfip_nl = \"verwijder_gedownloade_bestand\"\n    md[\"delete_downloaded_file_if_present\"][2] = ddfip_nl\n    md[\"delete_downloaded_file_if_present\"][3] = \"supprimer_fichier_téléchargé\"\n    md[\"delete_downloaded_file_if_present\"][4] = \"eliminare_file_scaricato\"\n    md[\"delete_downloaded_file_if_present\"][5] = \"ダウンロードしたファイルを削除する\"\n    md[\"delete_downloaded_file_if_present\"][6] = \"다운로드한_파일_삭제\"\n    md[\"delete_downloaded_file_if_present\"][7] = \"exclua_arquivo_baixado\"\n    md[\"delete_downloaded_file_if_present\"][8] = \"удалить_загруженный_файл\"\n    md[\"delete_downloaded_file_if_present\"][9] = \"eliminar_archivo_descargado\"\n\n    # \"wait_for_and_accept_alert\" -> duplicate of \"accept_alert\"\n    md[\"wait_for_and_accept_alert\"] = [\"*\"] * num_langs\n    md[\"wait_for_and_accept_alert\"][0] = \"wait_for_and_accept_alert\"\n    md[\"wait_for_and_accept_alert\"][1] = \"接受警报\"\n    md[\"wait_for_and_accept_alert\"][2] = \"waarschuwing_accepteren\"\n    md[\"wait_for_and_accept_alert\"][3] = \"accepter_alerte\"\n    md[\"wait_for_and_accept_alert\"][4] = \"accetta_avviso\"\n    md[\"wait_for_and_accept_alert\"][5] = \"アラートを受け入れる\"\n    md[\"wait_for_and_accept_alert\"][6] = \"경고를_수락\"\n    md[\"wait_for_and_accept_alert\"][7] = \"aceitar_alerta\"\n    md[\"wait_for_and_accept_alert\"][8] = \"принять_оповещение\"\n    md[\"wait_for_and_accept_alert\"][9] = \"aceptar_alerta\"\n\n    # \"wait_for_and_dismiss_alert\" -> duplicate of \"dismiss_alert\"\n    md[\"wait_for_and_dismiss_alert\"] = [\"*\"] * num_langs\n    md[\"wait_for_and_dismiss_alert\"][0] = \"wait_for_and_dismiss_alert\"\n    md[\"wait_for_and_dismiss_alert\"][1] = \"解除警报\"\n    md[\"wait_for_and_dismiss_alert\"][2] = \"waarschuwing_wegsturen\"\n    md[\"wait_for_and_dismiss_alert\"][3] = \"rejeter_alerte\"\n    md[\"wait_for_and_dismiss_alert\"][4] = \"elimina_avviso\"\n    md[\"wait_for_and_dismiss_alert\"][5] = \"アラートを却下\"\n    md[\"wait_for_and_dismiss_alert\"][6] = \"경고를_거부\"\n    md[\"wait_for_and_dismiss_alert\"][7] = \"demitir_alerta\"\n    md[\"wait_for_and_dismiss_alert\"][8] = \"увольнять_оповещение\"\n    md[\"wait_for_and_dismiss_alert\"][9] = \"descartar_alerta\"\n\n    # \"wait_for_and_switch_to_alert\" -> duplicate of \"switch_to_alert\"\n    md[\"wait_for_and_switch_to_alert\"] = [\"*\"] * num_langs\n    md[\"wait_for_and_switch_to_alert\"][0] = \"wait_for_and_switch_to_alert\"\n    md[\"wait_for_and_switch_to_alert\"][1] = \"切换到警报\"\n    md[\"wait_for_and_switch_to_alert\"][2] = \"overschakelen_naar_waarschuwing\"\n    md[\"wait_for_and_switch_to_alert\"][3] = \"passer_à_alerte\"\n    md[\"wait_for_and_switch_to_alert\"][4] = \"passa_al_avviso\"\n    md[\"wait_for_and_switch_to_alert\"][5] = \"アラートに切り替え\"\n    md[\"wait_for_and_switch_to_alert\"][6] = \"경고로_전환\"\n    md[\"wait_for_and_switch_to_alert\"][7] = \"mudar_para_alerta\"\n    md[\"wait_for_and_switch_to_alert\"][8] = \"переключиться_на_оповещение\"\n    md[\"wait_for_and_switch_to_alert\"][9] = \"cambiar_a_alerta\"\n\n    ################\n    # MasterQA Only!\n\n    md[\"verify\"] = [\"*\"] * num_langs\n    md[\"verify\"][0] = \"verify\"\n    md[\"verify\"][1] = \"校验\"\n    md[\"verify\"][2] = \"controleren\"\n    md[\"verify\"][3] = \"vérifier\"\n    md[\"verify\"][4] = \"verificare\"\n    md[\"verify\"][5] = \"を確認する\"\n    md[\"verify\"][6] = \"확인\"\n    md[\"verify\"][7] = \"verificar\"\n    md[\"verify\"][8] = \"подтвердить\"\n    md[\"verify\"][9] = \"verificar\"\n"
  },
  {
    "path": "seleniumbase/translate/portuguese.py",
    "content": "# Portuguese / Português - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass CasoDeTeste(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Portuguese\"\n\n    def abrir(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def abrir_url(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def clique(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def clique_duas_vezes(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def clique_de_contexto(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def clique_devagar(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def clique_se_está_visível(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def js_clique_se_está_presente(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def clique_texto_do_link(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def clique_com_deslocamento(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def atualizar_texto(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def digitar(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def adicionar_texto(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def obter_texto(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def verificar_texto(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def verificar_texto_exato(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def verificar_texto_do_link(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def verificar_texto_não_vazio(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def verificar_texto_não_visível(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def verificar_elemento(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def verificar_elemento_visível(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def verificar_elemento_não_visível(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def verificar_elemento_presente(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def verificar_elemento_ausente(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def verificar_atributo(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def verificar_url(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def verificar_url_contém(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def verificar_título(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def verificar_título_contém(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def obter_título(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def verificar_verdade(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def verificar_falso(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def verificar_igual(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def verificar_não_igual(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def atualizar_a_página(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def obter_url_atual(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def obter_a_página_html(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def voltar(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def avançar(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def o_texto_está_visível(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def o_texto_exato_está_visível(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def o_elemento_está_visível(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def o_elemento_está_habilitado(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def o_elemento_está_presente(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def aguardar_o_texto(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def aguardar_o_elemento(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def aguardar_o_elemento_visível(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def aguardar_o_elemento_não_visível(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def aguardar_o_elemento_presente(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def aguardar_o_elemento_ausente(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def aguardar_o_atributo(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def aguardar_a_página_carregar(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def dormir(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def aguardar(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def enviar(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def limpar(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def focar(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def js_clique(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def js_atualizar_texto(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def js_digitar(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def jquery_clique(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def jquery_atualizar_texto(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def jquery_digitar(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def verificar_html(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def salvar_captura_de_tela(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def salvar_captura_de_tela_para_logs(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def selecionar_arquivo(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def executar_script(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def executar_script_com_segurança(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def ativar_jquery(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def ativar_recorder(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def abrir_se_não_url(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def bloquear_anúncios(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def saltar(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def verificar_se_há_links_quebrados(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def verificar_se_há_erros_js(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def mudar_para_o_quadro(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def mudar_para_o_conteúdo_padrão(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def mudar_para_o_quadro_pai(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def abrir_nova_janela(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def mudar_para_janela(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def mudar_para_a_janela_padrão(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def mudar_para_a_janela_última(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def maximizar_janela(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def destaque(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def destaque_clique(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def rolar_para(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def rolar_para_o_topo(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def rolar_para_o_fundo(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def passe_o_mouse_e_clique(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def passe_o_mouse(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def é_selecionado(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def pressione_a_seta_para_cima(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def pressione_a_seta_para_baixo(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def pressione_a_seta_esquerda(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def pressione_a_seta_direita(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def clique_nos_elementos_visíveis(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def selecionar_opção_por_texto(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def selecionar_opção_por_índice(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def selecionar_opção_por_valor(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def criar_uma_apresentação(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def adicionar_um_slide(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def salvar_apresentação(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def iniciar_apresentação(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def criar_um_gráfico_de_pizza(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def criar_um_gráfico_de_barras(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def criar_um_gráfico_de_colunas(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def criar_um_gráfico_de_linhas(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def criar_um_gráfico_de_área(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def adicionar_séries_ao_gráfico(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def adicionar_ponto_de_dados(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def salvar_gráfico(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def exibir_gráfico(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def extrair_gráfico(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def criar_um_tour(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def criar_um_tour_shepherd(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def criar_um_tour_bootstrap(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def criar_um_tour_driverjs(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def criar_um_tour_hopscotch(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def criar_um_tour_introjs(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def adicionar_passo_para_o_tour(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def jogar_o_tour(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def exportar_o_tour(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def obter_texto_pdf(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def verificar_texto_pdf(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def baixar_arquivo(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def o_arquivo_baixado_está_presente(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def obter_caminho_do_arquivo_baixado(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def verificar_arquivo_baixado(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def exclua_arquivo_baixado(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def falhar(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def obter(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def visitar(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def visitar_url(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def obter_elemento(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def encontrar_elemento(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def remover_elemento(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def remover_elementos(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def encontrar_texto(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def definir_texto(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def obter_atributo(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def definir_atributo(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def definir_atributos(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def escreva(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def definir_tema_da_mensagem(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def exibir_mensagem(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def imprimir(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def adiada_verificar_elemento(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def adiada_verificar_texto(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def processar_verificações_adiada(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def aceitar_alerta(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def demitir_alerta(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def mudar_para_alerta(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def arrastar_e_soltar(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def definir_html(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def carregar_arquivo_html(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def abrir_arquivo_html(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def excluir_todos_os_cookies(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def obter_agente_do_usuário(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def obter_código_de_idioma(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_Português(MasterQA, CasoDeTeste):\n    def verificar(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"Verificação manual\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"A página parece boa?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/russian.py",
    "content": "# Russian / Русский - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass ТестНаСелен(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Russian\"\n\n    def открыть(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def открыть_URL(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def нажмите(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def дважды_нажмите(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def контекстный_щелчок(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def нажмите_медленно(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def нажмите_если_виден(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def JS_нажмите_если_присутствует(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def нажмите_ссылку(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def нажмите_на_местоположение(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def обновить_текст(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def введите(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def добавить_текст(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def получить_текст(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def подтвердить_текст(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def подтвердить_текст_точно(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def подтвердить_ссылку(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def подтвердить_непустой_текст(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def подтвердить_текст_не_виден(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def подтвердить_элемент(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def подтвердить_элемент_виден(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def подтвердить_элемент_не_виден(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def подтвердить_элемент_присутствует(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def подтвердить_элемент_отсутствует(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def подтвердить_атрибут(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def подтвердить_URL(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def подтвердить_URL_содержит(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def подтвердить_название(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def подтвердить_название_содержит(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def получить_название(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def подтвердить_правду(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def подтвердить_ложные(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def подтвердить_одинаковый(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def подтвердить_не_одинаковый(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def обновить_страницу(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def получить_текущий_URL(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def получить_источник_страницы(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def назад(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def вперед(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def текст_виден(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def точный_текст_виден(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def элемент_виден(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def элемент_включен(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def элемент_присутствует(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def ждать_текста(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def ждать_элемента(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def ждать_элемента_виден(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def ждать_элемента_не_виден(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def ждать_элемента_присутствует(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def ждать_элемента_отсутствует(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def ждать_атрибут(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def ждать_загрузки_страницы(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def спать(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def ждать(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def отправить(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def очистить(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def сосредоточиться(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def JS_нажмите(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def JS_обновить_текст(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def JS_введите(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def JQUERY_нажмите(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def JQUERY_обновить_текст(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def JQUERY_введите(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def проверить_HTML(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def сохранить_скриншот(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def сохранить_скриншот_в_логи(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def выберите_файл(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def выполнение_скрипта(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def безопасное_выполнение_скрипта(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def активировать_JQUERY(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def активировать_RECORDER(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def открыть_если_не_URL(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def блокировать_рекламу(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def пропускать(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def проверить_ошибки_404(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def проверить_ошибки_JS(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def переключиться_на_кадр(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def переключиться_на_содержимое_по_умолчанию(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def переключиться_на_родительский_кадр(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def открыть_новое_окно(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def переключиться_на_окно(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def переключиться_на_окно_по_умолчанию(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def переключиться_на_последнее_окно(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def максимальное_окно(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def осветить(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def осветить_нажмите(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def прокрутить_к(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def пролистать_наверх(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def прокрутить_вниз(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def наведите_и_нажмите(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def наведение_мыши(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def выбран(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def нажмите_стрелку_вверх(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def нажмите_стрелку_вниз(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def нажмите_стрелку_влево(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def нажмите_стрелку_вправо(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def нажмите_видимые_элементы(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def выбрать_опцию_по_тексту(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def выбрать_опцию_по_индексу(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def выбрать_опцию_по_значению(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def создать_презентацию(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def добавить_слайд(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def сохранить_презентацию(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def начать_презентацию(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def создать_круговую_диаграмму(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def создать_бар_диаграмму(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def создать_столбчатую_диаграмму(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def создать_линейную_диаграмму(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def создать_диаграмму_области(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def добавить_серии_в_диаграмму(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def добавить_точку_данных(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def сохранить_диаграмму(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def отображать_диаграмму(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def извлекать_диаграмму(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def создать_тур(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def создать_SHEPHERD_тур(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def создать_BOOTSTRAP_тур(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def создать_DRIVERJS_тур(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def создать_HOPSCOTCH_тур(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def создать_INTROJS_тур(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def добавить_шаг_в_тур(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def играть_тур(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def экспортировать_тур(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def получить_текст_PDF(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def подтвердить_текст_PDF(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def скачать_файл(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def загруженный_файл_присутствует(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def получить_путь_к_загруженному_файлу(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def подтвердить_загруженный_файл(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def удалить_загруженный_файл(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def провалить(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def получить(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def посетить(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def посетить_URL(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def получить_элемент(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def найти_элемент(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def удалить_элемент(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def удалить_элементы(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def найти_текст(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def набор_текст(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def получить_атрибут(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def набор_атрибута(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def набор_атрибутов(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def написать(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def набор_тему_сообщения(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def показать_сообщение(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def печатать(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def отложенный_подтвердить_элемент(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def отложенный_подтвердить_текст(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def обработки_отложенных_подтверждений(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def принять_оповещение(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def увольнять_оповещение(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def переключиться_на_оповещение(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def перетащить_и_падение(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def набор_HTML(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def загрузить_HTML_файл(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def открыть_HTML_файл(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def удалить_все_куки(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def получить_агента_пользователя(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def получить_код_языка(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_Русский(MasterQA, ТестНаСелен):\n    def подтвердить(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"Ручная проверка\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"Страница хорошо выглядит?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/spanish.py",
    "content": "# Spanish / Español - Translations\nfrom seleniumbase import BaseCase\nfrom seleniumbase import MasterQA\n\n\nclass CasoDePrueba(BaseCase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._language = \"Spanish\"\n\n    def abrir(self, *args, **kwargs):\n        # open(url)\n        return self.open(*args, **kwargs)\n\n    def abrir_url(self, *args, **kwargs):\n        # open_url(url)\n        return self.open_url(*args, **kwargs)\n\n    def haga_clic(self, *args, **kwargs):\n        # click(selector)\n        return self.click(*args, **kwargs)\n\n    def doble_clic(self, *args, **kwargs):\n        # double_click(selector)\n        return self.double_click(*args, **kwargs)\n\n    def clic_de_contexto(self, *args, **kwargs):\n        # context_click(selector)\n        return self.context_click(*args, **kwargs)\n\n    def clic_lentamente(self, *args, **kwargs):\n        # slow_click(selector)\n        return self.slow_click(*args, **kwargs)\n\n    def clic_si_está_muestra(self, *args, **kwargs):\n        # click_if_visible(selector, by=By.CSS_SELECTOR)\n        return self.click_if_visible(*args, **kwargs)\n\n    def js_clic_si_está_presente(self, *args, **kwargs):\n        # js_click_if_present(selector, by=By.CSS_SELECTOR)\n        return self.js_click_if_present(*args, **kwargs)\n\n    def clic_texto_del_enlace(self, *args, **kwargs):\n        # click_link_text(link_text)\n        return self.click_link_text(*args, **kwargs)\n\n    def clic_con_desplazamiento(self, *args, **kwargs):\n        # click_with_offset(selector, x, y, by=By.CSS_SELECTOR,\n        #                   mark=None, timeout=None, center=None)\n        return self.click_with_offset(*args, **kwargs)\n\n    def actualizar_texto(self, *args, **kwargs):\n        # update_text(selector, text)\n        return self.update_text(*args, **kwargs)\n\n    def escriba(self, *args, **kwargs):\n        # type(selector, text)  # Same as update_text()\n        return self.type(*args, **kwargs)\n\n    def agregar_texto(self, *args, **kwargs):\n        # add_text(selector, text)\n        return self.add_text(*args, **kwargs)\n\n    def obtener_texto(self, *args, **kwargs):\n        # get_text(selector, text)\n        return self.get_text(*args, **kwargs)\n\n    def verificar_texto(self, *args, **kwargs):\n        # assert_text(text, selector)\n        return self.assert_text(*args, **kwargs)\n\n    def verificar_texto_exacto(self, *args, **kwargs):\n        # assert_exact_text(text, selector)\n        return self.assert_exact_text(*args, **kwargs)\n\n    def verificar_texto_del_enlace(self, *args, **kwargs):\n        # assert_link_text(link_text)\n        return self.assert_link_text(*args, **kwargs)\n\n    def verificar_texto_no_vacío(self, *args, **kwargs):\n        # assert_non_empty_text(selector)\n        return self.assert_non_empty_text(*args, **kwargs)\n\n    def verificar_texto_no_se_muestra(self, *args, **kwargs):\n        # assert_text_not_visible(text, selector)\n        return self.assert_text_not_visible(*args, **kwargs)\n\n    def verificar_elemento(self, *args, **kwargs):\n        # assert_element(selector)\n        return self.assert_element(*args, **kwargs)\n\n    def verificar_elemento_se_muestra(self, *args, **kwargs):\n        # assert_element_visible(selector)  # Same as self.assert_element()\n        return self.assert_element_visible(*args, **kwargs)\n\n    def verificar_elemento_no_se_muestra(self, *args, **kwargs):\n        # assert_element_not_visible(selector)\n        return self.assert_element_not_visible(*args, **kwargs)\n\n    def verificar_elemento_presente(self, *args, **kwargs):\n        # assert_element_present(selector)\n        return self.assert_element_present(*args, **kwargs)\n\n    def verificar_elemento_ausente(self, *args, **kwargs):\n        # assert_element_absent(selector)\n        return self.assert_element_absent(*args, **kwargs)\n\n    def verificar_atributo(self, *args, **kwargs):\n        # assert_attribute(selector, attribute, value)\n        return self.assert_attribute(*args, **kwargs)\n\n    def verificar_url(self, *args, **kwargs):\n        # assert_url(url)\n        return self.assert_url(*args, **kwargs)\n\n    def verificar_url_contiene(self, *args, **kwargs):\n        # assert_url_contains(substring)\n        return self.assert_url_contains(*args, **kwargs)\n\n    def verificar_título(self, *args, **kwargs):\n        # assert_title(title)\n        return self.assert_title(*args, **kwargs)\n\n    def verificar_título_contiene(self, *args, **kwargs):\n        # assert_title_contains(substring)\n        return self.assert_title_contains(*args, **kwargs)\n\n    def obtener_título(self, *args, **kwargs):\n        # get_title()\n        return self.get_title(*args, **kwargs)\n\n    def verificar_verdad(self, *args, **kwargs):\n        # assert_true(expr)\n        return self.assert_true(*args, **kwargs)\n\n    def verificar_falso(self, *args, **kwargs):\n        # assert_false(expr)\n        return self.assert_false(*args, **kwargs)\n\n    def verificar_igual(self, *args, **kwargs):\n        # assert_equal(first, second)\n        return self.assert_equal(*args, **kwargs)\n\n    def verificar_diferente(self, *args, **kwargs):\n        # assert_not_equal(first, second)\n        return self.assert_not_equal(*args, **kwargs)\n\n    def actualizar_la_página(self, *args, **kwargs):\n        # refresh_page()\n        return self.refresh_page(*args, **kwargs)\n\n    def obtener_url_actual(self, *args, **kwargs):\n        # get_current_url()\n        return self.get_current_url(*args, **kwargs)\n\n    def obtener_html_de_la_página(self, *args, **kwargs):\n        # get_page_source()\n        return self.get_page_source(*args, **kwargs)\n\n    def volver(self, *args, **kwargs):\n        # go_back()\n        return self.go_back(*args, **kwargs)\n\n    def adelante(self, *args, **kwargs):\n        # go_forward()\n        return self.go_forward(*args, **kwargs)\n\n    def se_muestra_el_texto(self, *args, **kwargs):\n        # is_text_visible(text, selector=\"html\")\n        return self.is_text_visible(*args, **kwargs)\n\n    def se_muestra_el_texto_exacto(self, *args, **kwargs):\n        # is_exact_text_visible(text, selector=\"html\")\n        return self.is_exact_text_visible(*args, **kwargs)\n\n    def se_muestra_el_elemento(self, *args, **kwargs):\n        # is_element_visible(selector)\n        return self.is_element_visible(*args, **kwargs)\n\n    def está_habilitado_el_elemento(self, *args, **kwargs):\n        # is_element_enabled(selector)\n        return self.is_element_enabled(*args, **kwargs)\n\n    def está_presente_el_elemento(self, *args, **kwargs):\n        # is_element_present(selector)\n        return self.is_element_present(*args, **kwargs)\n\n    def espera_el_texto(self, *args, **kwargs):\n        # wait_for_text(text, selector)\n        return self.wait_for_text(*args, **kwargs)\n\n    def espera_el_elemento(self, *args, **kwargs):\n        # wait_for_element(selector)\n        return self.wait_for_element(*args, **kwargs)\n\n    def espera_el_elemento_se_muestra(self, *args, **kwargs):\n        # wait_for_element_visible(selector)  # Same as wait_for_element()\n        return self.wait_for_element_visible(*args, **kwargs)\n\n    def espera_el_elemento_no_se_muestra(self, *args, **kwargs):\n        # wait_for_element_not_visible(selector)\n        return self.wait_for_element_not_visible(*args, **kwargs)\n\n    def espera_el_elemento_presente(self, *args, **kwargs):\n        # wait_for_element_present(selector)\n        return self.wait_for_element_present(*args, **kwargs)\n\n    def espera_el_elemento_ausente(self, *args, **kwargs):\n        # wait_for_element_absent(selector)\n        return self.wait_for_element_absent(*args, **kwargs)\n\n    def espera_el_atributo(self, *args, **kwargs):\n        # wait_for_attribute(selector, attribute, value)\n        return self.wait_for_attribute(*args, **kwargs)\n\n    def esperar_a_que_cargue_la_página(self, *args, **kwargs):\n        # wait_for_ready_state_complete()\n        return self.wait_for_ready_state_complete(*args, **kwargs)\n\n    def dormir(self, *args, **kwargs):\n        # sleep(seconds)\n        return self.sleep(*args, **kwargs)\n\n    def espera(self, *args, **kwargs):\n        # wait(seconds)  # Same as sleep(seconds)\n        return self.wait(*args, **kwargs)\n\n    def enviar(self, *args, **kwargs):\n        # submit(selector)\n        return self.submit(*args, **kwargs)\n\n    def despejar(self, *args, **kwargs):\n        # clear(selector)\n        return self.clear(*args, **kwargs)\n\n    def centrarse(self, *args, **kwargs):\n        # focus(selector)\n        return self.focus(*args, **kwargs)\n\n    def js_haga_clic(self, *args, **kwargs):\n        # js_click(selector)\n        return self.js_click(*args, **kwargs)\n\n    def js_actualizar_texto(self, *args, **kwargs):\n        # js_update_text(selector, text)\n        return self.js_update_text(*args, **kwargs)\n\n    def js_escriba(self, *args, **kwargs):\n        # js_type(selector, text)\n        return self.js_type(*args, **kwargs)\n\n    def jquery_haga_clic(self, *args, **kwargs):\n        # jquery_click(selector)\n        return self.jquery_click(*args, **kwargs)\n\n    def jquery_actualizar_texto(self, *args, **kwargs):\n        # jquery_update_text(selector, text)\n        return self.jquery_update_text(*args, **kwargs)\n\n    def jquery_escriba(self, *args, **kwargs):\n        # jquery_type(selector, text)\n        return self.jquery_type(*args, **kwargs)\n\n    def comprobar_html(self, *args, **kwargs):\n        # inspect_html()\n        return self.inspect_html(*args, **kwargs)\n\n    def guardar_captura_de_pantalla(self, *args, **kwargs):\n        # save_screenshot(name)\n        return self.save_screenshot(*args, **kwargs)\n\n    def guardar_captura_de_pantalla_para_logs(self, *args, **kwargs):\n        # save_screenshot_to_logs(name)\n        return self.save_screenshot_to_logs(*args, **kwargs)\n\n    def seleccionar_archivo(self, *args, **kwargs):\n        # choose_file(selector, file_path)\n        return self.choose_file(*args, **kwargs)\n\n    def ejecutar_script(self, *args, **kwargs):\n        # execute_script(script)\n        return self.execute_script(*args, **kwargs)\n\n    def ejecutar_script_de_forma_segura(self, *args, **kwargs):\n        # safe_execute_script(script)\n        return self.safe_execute_script(*args, **kwargs)\n\n    def activar_jquery(self, *args, **kwargs):\n        # activate_jquery()\n        return self.activate_jquery(*args, **kwargs)\n\n    def activar_recorder(self, *args, **kwargs):\n        # activate_recorder()\n        return self.activate_recorder(*args, **kwargs)\n\n    def abrir_que_no_url(self, *args, **kwargs):\n        # open_if_not_url(url)\n        return self.open_if_not_url(*args, **kwargs)\n\n    def bloquear_anuncios(self, *args, **kwargs):\n        # ad_block()\n        return self.ad_block(*args, **kwargs)\n\n    def saltar(self, *args, **kwargs):\n        # skip(reason=\"\")\n        return self.skip(*args, **kwargs)\n\n    def verificar_si_hay_enlaces_rotos(self, *args, **kwargs):\n        # assert_no_404_errors()\n        return self.assert_no_404_errors(*args, **kwargs)\n\n    def verificar_si_hay_errores_js(self, *args, **kwargs):\n        # assert_no_js_errors()\n        return self.assert_no_js_errors(*args, **kwargs)\n\n    def cambiar_al_marco(self, *args, **kwargs):\n        # switch_to_frame(frame)\n        return self.switch_to_frame(*args, **kwargs)\n\n    def cambiar_al_contenido_predeterminado(self, *args, **kwargs):\n        # switch_to_default_content()\n        return self.switch_to_default_content(*args, **kwargs)\n\n    def cambiar_al_marco_principal(self, *args, **kwargs):\n        # switch_to_parent_frame()\n        return self.switch_to_parent_frame(*args, **kwargs)\n\n    def abrir_una_nueva_ventana(self, *args, **kwargs):\n        # open_new_window()\n        return self.open_new_window(*args, **kwargs)\n\n    def cambiar_a_ventana(self, *args, **kwargs):\n        # switch_to_window(window)\n        return self.switch_to_window(*args, **kwargs)\n\n    def cambiar_a_ventana_predeterminada(self, *args, **kwargs):\n        # switch_to_default_window()\n        return self.switch_to_default_window(*args, **kwargs)\n\n    def cambiar_a_ventana_última(self, *args, **kwargs):\n        # switch_to_newest_window()\n        return self.switch_to_newest_window(*args, **kwargs)\n\n    def maximizar_ventana(self, *args, **kwargs):\n        # maximize_window()\n        return self.maximize_window(*args, **kwargs)\n\n    def resalte(self, *args, **kwargs):\n        # highlight(selector)\n        return self.highlight(*args, **kwargs)\n\n    def resalte_clic(self, *args, **kwargs):\n        # highlight_click(selector)\n        return self.highlight_click(*args, **kwargs)\n\n    def desplazarse_a(self, *args, **kwargs):\n        # scroll_to(selector)\n        return self.scroll_to(*args, **kwargs)\n\n    def desplazarse_hasta_la_parte_superior(self, *args, **kwargs):\n        # scroll_to_top()\n        return self.scroll_to_top(*args, **kwargs)\n\n    def desplazarse_hasta_la_parte_inferior(self, *args, **kwargs):\n        # scroll_to_bottom()\n        return self.scroll_to_bottom(*args, **kwargs)\n\n    def pasar_el_ratón_y_hacer_clic(self, *args, **kwargs):\n        # hover_and_click(hover_selector, click_selector)\n        return self.hover_and_click(*args, **kwargs)\n\n    def pasar_el_ratón(self, *args, **kwargs):\n        # hover(selector)\n        return self.hover(*args, **kwargs)\n\n    def está_seleccionado(self, *args, **kwargs):\n        # is_selected(selector)\n        return self.is_selected(*args, **kwargs)\n\n    def presione_la_flecha_hacia_arriba(self, *args, **kwargs):\n        # press_up_arrow(selector=\"html\", times=1)\n        return self.press_up_arrow(*args, **kwargs)\n\n    def presione_la_flecha_hacia_abajo(self, *args, **kwargs):\n        # press_down_arrow(selector=\"html\", times=1)\n        return self.press_down_arrow(*args, **kwargs)\n\n    def presione_la_flecha_izquierda(self, *args, **kwargs):\n        # press_left_arrow(selector=\"html\", times=1)\n        return self.press_left_arrow(*args, **kwargs)\n\n    def presione_la_flecha_derecha(self, *args, **kwargs):\n        # press_right_arrow(selector=\"html\", times=1)\n        return self.press_right_arrow(*args, **kwargs)\n\n    def clic_en_elementos_visibles(self, *args, **kwargs):\n        # click_visible_elements(selector)\n        return self.click_visible_elements(*args, **kwargs)\n\n    def seleccionar_opción_por_texto(self, *args, **kwargs):\n        # select_option_by_text(dropdown_selector, option)\n        return self.select_option_by_text(*args, **kwargs)\n\n    def seleccionar_opción_por_índice(self, *args, **kwargs):\n        # select_option_by_index(dropdown_selector, option)\n        return self.select_option_by_index(*args, **kwargs)\n\n    def seleccionar_opción_por_valor(self, *args, **kwargs):\n        # select_option_by_value(dropdown_selector, option)\n        return self.select_option_by_value(*args, **kwargs)\n\n    def crear_una_presentación(self, *args, **kwargs):\n        # create_presentation(name=None, theme=\"default\", transition=\"default\")\n        return self.create_presentation(*args, **kwargs)\n\n    def agregar_una_diapositiva(self, *args, **kwargs):\n        # add_slide(content=None, image=None, code=None, iframe=None,\n        #           content2=None, notes=None, transition=None, name=None)\n        return self.add_slide(*args, **kwargs)\n\n    def guardar_presentación(self, *args, **kwargs):\n        # save_presentation(name=None, filename=None,\n        #                   show_notes=False, interval=0)\n        return self.save_presentation(*args, **kwargs)\n\n    def iniciar_presentación(self, *args, **kwargs):\n        # begin_presentation(name=None, filename=None,\n        #                    show_notes=False, interval=0)\n        return self.begin_presentation(*args, **kwargs)\n\n    def crear_un_gráfico_circular(self, *args, **kwargs):\n        # create_pie_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_pie_chart(*args, **kwargs)\n\n    def crear_un_gráfico_de_barras(self, *args, **kwargs):\n        # create_bar_chart(chart_name=None, title=None, subtitle=None,\n        #                  data_name=None, unit=None, libs=True)\n        return self.create_bar_chart(*args, **kwargs)\n\n    def crear_un_gráfico_de_columnas(self, *args, **kwargs):\n        # create_column_chart(chart_name=None, title=None, subtitle=None,\n        #                     data_name=None, unit=None, libs=True)\n        return self.create_column_chart(*args, **kwargs)\n\n    def crear_un_gráfico_de_líneas(self, *args, **kwargs):\n        # create_line_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_line_chart(*args, **kwargs)\n\n    def crear_un_gráfico_de_área(self, *args, **kwargs):\n        # create_area_chart(chart_name=None, title=None, subtitle=None,\n        #                   data_name=None, unit=None, zero=False, libs=True)\n        return self.create_area_chart(*args, **kwargs)\n\n    def agregar_series_al_gráfico(self, *args, **kwargs):\n        # add_series_to_chart(data_name=None, chart_name=None)\n        return self.add_series_to_chart(*args, **kwargs)\n\n    def agregar_punto_de_datos(self, *args, **kwargs):\n        # add_data_point(label, value, color=None, chart_name=None)\n        return self.add_data_point(*args, **kwargs)\n\n    def guardar_gráfico(self, *args, **kwargs):\n        # save_chart(chart_name=None, filename=None)\n        return self.save_chart(*args, **kwargs)\n\n    def muestra_gráfico(self, *args, **kwargs):\n        # display_chart(chart_name=None, filename=None, interval=0)\n        return self.display_chart(*args, **kwargs)\n\n    def extracto_gráfico(self, *args, **kwargs):\n        # extract_chart(chart_name=None)\n        return self.extract_chart(*args, **kwargs)\n\n    def crear_una_gira(self, *args, **kwargs):\n        # create_tour(name=None, theme=None)\n        return self.create_tour(*args, **kwargs)\n\n    def crear_una_gira_shepherd(self, *args, **kwargs):\n        # create_shepherd_tour(name=None, theme=None)\n        return self.create_shepherd_tour(*args, **kwargs)\n\n    def crear_una_gira_bootstrap(self, *args, **kwargs):\n        # create_bootstrap_tour(name=None, theme=None)\n        return self.create_bootstrap_tour(*args, **kwargs)\n\n    def crear_una_gira_driverjs(self, *args, **kwargs):\n        # create_driverjs_tour(name=None, theme=None)\n        return self.create_driverjs_tour(*args, **kwargs)\n\n    def crear_una_gira_hopscotch(self, *args, **kwargs):\n        # create_hopscotch_tour(name=None, theme=None)\n        return self.create_hopscotch_tour(*args, **kwargs)\n\n    def crear_una_gira_introjs(self, *args, **kwargs):\n        # create_introjs_tour(name=None, theme=None)\n        return self.create_introjs_tour(*args, **kwargs)\n\n    def agregar_paso_a_la_gira(self, *args, **kwargs):\n        # add_tour_step(message, selector=None, name=None,\n        #               title=None, theme=None, alignment=None)\n        return self.add_tour_step(*args, **kwargs)\n\n    def reproducir_la_gira(self, *args, **kwargs):\n        # play_tour(name=None)\n        return self.play_tour(*args, **kwargs)\n\n    def exportar_la_gira(self, *args, **kwargs):\n        # export_tour(name=None, filename=\"my_tour.js\", url=None)\n        return self.export_tour(*args, **kwargs)\n\n    def obtener_texto_pdf(self, *args, **kwargs):\n        # get_pdf_text(pdf, page=None, maxpages=None, password=None,\n        #              codec='utf-8', wrap=False, nav=False, override=False)\n        return self.get_pdf_text(*args, **kwargs)\n\n    def verificar_texto_pdf(self, *args, **kwargs):\n        # assert_pdf_text(pdf, text, page=None, maxpages=None, password=None,\n        #                 codec='utf-8', wrap=True, nav=False, override=False)\n        return self.assert_pdf_text(*args, **kwargs)\n\n    def descargar_archivo(self, *args, **kwargs):\n        # download_file(file)\n        return self.download_file(*args, **kwargs)\n\n    def está_presente_el_archivo_descargado(self, *args, **kwargs):\n        # is_downloaded_file_present(file)\n        return self.is_downloaded_file_present(*args, **kwargs)\n\n    def obtener_ruta_del_archivo_descargado(self, *args, **kwargs):\n        # get_path_of_downloaded_file(file)\n        return self.get_path_of_downloaded_file(*args, **kwargs)\n\n    def verificar_archivo_descargado(self, *args, **kwargs):\n        # assert_downloaded_file(file)\n        return self.assert_downloaded_file(*args, **kwargs)\n\n    def eliminar_archivo_descargado(self, *args, **kwargs):\n        # delete_downloaded_file(file)\n        return self.delete_downloaded_file(*args, **kwargs)\n\n    def fallar(self, *args, **kwargs):\n        # fail(msg=None)  # Inherited from \"unittest\"\n        return self.fail(*args, **kwargs)\n\n    def obtener(self, *args, **kwargs):\n        # get(url)  # Same as open(url)\n        return self.get(*args, **kwargs)\n\n    def visita(self, *args, **kwargs):\n        # visit(url)  # Same as open(url)\n        return self.visit(*args, **kwargs)\n\n    def visita_url(self, *args, **kwargs):\n        # visit_url(url)  # Same as open(url)\n        return self.visit_url(*args, **kwargs)\n\n    def obtener_elemento(self, *args, **kwargs):\n        # get_element(selector)  # Element can be hidden\n        return self.get_element(*args, **kwargs)\n\n    def encontrar_elemento(self, *args, **kwargs):\n        # find_element(selector)  # Element must be visible\n        return self.find_element(*args, **kwargs)\n\n    def eliminar_elemento(self, *args, **kwargs):\n        # remove_element(selector)\n        return self.remove_element(*args, **kwargs)\n\n    def eliminar_elementos(self, *args, **kwargs):\n        # remove_elements(selector)\n        return self.remove_elements(*args, **kwargs)\n\n    def encontrar_texto(self, *args, **kwargs):\n        # find_text(text, selector=\"html\")  # Same as wait_for_text\n        return self.find_text(*args, **kwargs)\n\n    def establecer_texto(self, *args, **kwargs):\n        # set_text(selector, text)\n        return self.set_text(*args, **kwargs)\n\n    def obtener_atributo(self, *args, **kwargs):\n        # get_attribute(selector, attribute)\n        return self.get_attribute(*args, **kwargs)\n\n    def establecer_atributo(self, *args, **kwargs):\n        # set_attribute(selector, attribute, value)\n        return self.set_attribute(*args, **kwargs)\n\n    def establecer_atributos(self, *args, **kwargs):\n        # set_attributes(selector, attribute, value)\n        return self.set_attributes(*args, **kwargs)\n\n    def escribir(self, *args, **kwargs):\n        # write(selector, text)  # Same as update_text()\n        return self.write(*args, **kwargs)\n\n    def establecer_tema_del_mensaje(self, *args, **kwargs):\n        # set_messenger_theme(theme=\"default\", location=\"default\")\n        return self.set_messenger_theme(*args, **kwargs)\n\n    def mostrar_mensaje(self, *args, **kwargs):\n        # post_message(message, duration=None, pause=True, style=\"info\")\n        return self.post_message(*args, **kwargs)\n\n    def imprimir(self, *args, **kwargs):\n        # _print(msg)  # Same as Python print()\n        return self._print(*args, **kwargs)\n\n    def diferido_verificar_elemento(self, *args, **kwargs):\n        # deferred_assert_element(selector)\n        return self.deferred_assert_element(*args, **kwargs)\n\n    def diferido_verificar_texto(self, *args, **kwargs):\n        # deferred_assert_text(text, selector=\"html\")\n        return self.deferred_assert_text(*args, **kwargs)\n\n    def procesar_verificaciones_diferidas(self, *args, **kwargs):\n        # process_deferred_asserts(print_only=False)\n        return self.process_deferred_asserts(*args, **kwargs)\n\n    def aceptar_alerta(self, *args, **kwargs):\n        # accept_alert(timeout=None)\n        return self.accept_alert(*args, **kwargs)\n\n    def descartar_alerta(self, *args, **kwargs):\n        # dismiss_alert(timeout=None)\n        return self.dismiss_alert(*args, **kwargs)\n\n    def cambiar_a_alerta(self, *args, **kwargs):\n        # switch_to_alert(timeout=None)\n        return self.switch_to_alert(*args, **kwargs)\n\n    def arrastrar_y_soltar(self, *args, **kwargs):\n        # drag_and_drop(drag_selector, drop_selector)\n        return self.drag_and_drop(*args, **kwargs)\n\n    def establecer_html(self, *args, **kwargs):\n        # set_content(html_string, new_page=False)\n        return self.set_content(*args, **kwargs)\n\n    def cargar_archivo_html(self, *args, **kwargs):\n        # load_html_file(html_file, new_page=True)\n        return self.load_html_file(*args, **kwargs)\n\n    def abrir_archivo_html(self, *args, **kwargs):\n        # open_html_file(html_file)\n        return self.open_html_file(*args, **kwargs)\n\n    def eliminar_todas_las_cookies(self, *args, **kwargs):\n        # delete_all_cookies()\n        return self.delete_all_cookies(*args, **kwargs)\n\n    def obtener_agente_de_usuario(self, *args, **kwargs):\n        # get_user_agent()\n        return self.get_user_agent(*args, **kwargs)\n\n    def obtener_código_de_idioma(self, *args, **kwargs):\n        # get_locale_code()\n        return self.get_locale_code(*args, **kwargs)\n\n\nclass MasterQA_Español(MasterQA, CasoDePrueba):\n    def verificar(self, *args, **kwargs):\n        # \"Manual Check\"\n        self.DEFAULT_VALIDATION_TITLE = \"Comprobación manual\"\n        # \"Does the page look good?\"\n        self.DEFAULT_VALIDATION_MESSAGE = \"¿Se ve bien la página?\"\n        # verify(QUESTION)\n        return self.verify(*args, **kwargs)\n"
  },
  {
    "path": "seleniumbase/translate/translator.py",
    "content": "\"\"\"\nTranslates a SeleniumBase Python file into a different language\n\nUsage:\n        seleniumbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\n        OR:    sbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\nLanguages:\n        --en / --English    |    --zh / --Chinese\n        --nl / --Dutch      |    --fr / --French\n        --it / --Italian    |    --ja / --Japanese\n        --ko / --Korean     |    --pt / --Portuguese\n        --ru / --Russian    |    --es / --Spanish\nActions:\n        -p / --print  (Print translation output to the screen)\n        -o / --overwrite  (Overwrite the file being translated)\n        -c / --copy  (Copy the translation to a new .py file)\nOptions:\n        -n  (include line Numbers when using the Print action)\nOutput:\n        Translates a SeleniumBase Python file into the language\n        specified. Method calls and \"import\" lines get swapped.\n        Both a language and an action must be specified.\n        The \"-p\" action can be paired with one other action.\n        When running with \"-c\" (or \"--copy\"), the new file name\n        will be the original name appended with an underscore\n        plus the 2-letter language code of the new language.\n        (Example: Translating \"test_1.py\" into Japanese with\n        \"-c\" will create a new file called \"test_1_ja.py\".)\n\"\"\"\nimport colorama\nimport os\nimport re\nimport sys\nfrom seleniumbase.translate import master_dict\n\nMD_F = master_dict.MD_F\nMD_L_Codes = master_dict.MD_L_Codes\nMD = master_dict.MD\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** translate **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"         seleniumbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\\n\"\n    exp += \"         OR:    sbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\\n\"\n    exp += \"  Languages:\\n\"\n    exp += \"         --en / --English    |    --zh / --Chinese\\n\"\n    exp += \"         --nl / --Dutch      |    --fr / --French\\n\"\n    exp += \"         --it / --Italian    |    --ja / --Japanese\\n\"\n    exp += \"         --ko / --Korean     |    --pt / --Portuguese\\n\"\n    exp += \"         --ru / --Russian    |    --es / --Spanish\\n\"\n    exp += \"  Actions:\\n\"\n    exp += \"         -p / --print  (Print translation output to the screen)\\n\"\n    exp += \"         -o / --overwrite  (Overwrite the file being translated)\\n\"\n    exp += \"         -c / --copy  (Copy the translation to a new .py file)\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"         -n  (include line Numbers when using the Print action)\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"         Translates a SeleniumBase Python file into the language\\n\"\n    exp += '         specified. Method calls and \"import\" lines get swapped.\\n'\n    exp += \"         Both a language and an action must be specified.\\n\"\n    exp += '         The \"-p\" action can be paired with one other action.\\n'\n    exp += '         When running with \"-c\" (or \"--copy\"), the new file name\\n'\n    exp += \"         will be the original name appended with an underscore\\n\"\n    exp += \"         plus the 2-letter language code of the new language.\\n\"\n    exp += '         (Example: Translating \"test_1.py\" into Japanese with\\n'\n    exp += '          \"-c\" will create a new file called \"test_1_ja.py\".)\\n'\n    if not msg:\n        raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n    else:\n        raise Exception(\"INVALID RUN COMMAND!\\n%s\\n\\n%s\" % (msg, exp))\n\n\ndef sc_ranges():\n    # Get the ranges of special double-width characters.\n    special_char_ranges = [\n        {\"from\": ord(\"\\u4e00\"), \"to\": ord(\"\\u9FFF\")},\n        {\"from\": ord(\"\\u3040\"), \"to\": ord(\"\\u30ff\")},\n        {\"from\": ord(\"\\uac00\"), \"to\": ord(\"\\ud7a3\")},\n        {\"from\": ord(\"\\uff01\"), \"to\": ord(\"\\uff60\")},\n    ]\n    return special_char_ranges\n\n\ndef is_cjk(char):\n    # Returns True if the special character is Chinese, Japanese, or Korean.\n    sc = any(\n        [range[\"from\"] <= ord(char) <= range[\"to\"] for range in sc_ranges()]\n    )\n    return sc\n\n\ndef get_width(line):\n    # Return the true width of the line. Not the same as line length.\n    # Chinese/Japanese/Korean characters take up double width visually.\n    line_length = len(line)\n    for char in line:\n        if is_cjk(char):\n            line_length += 1\n    return line_length\n\n\ndef process_test_file(code_lines, new_lang):\n    detected_lang = None\n    changed = False\n    found_bc = False  # Found BaseCase or a translation\n    seleniumbase_lines = []\n    lang_codes = MD_L_Codes.lang\n    nl_code = lang_codes[new_lang]  # new_lang language code\n    dl_code = None  # detected_lang language code\n    md = MD.md  # Master Dictionary\n\n    for line in code_lines:\n        line = line.rstrip()\n\n        # Find imports that determine the language\n        if line.lstrip().startswith(\"from seleniumbase\") and \"import\" in line:\n            added_line = False\n            for lang in MD_F.get_languages_list():\n                data = re.match(\n                    r\"^\\s*\" + MD_F.get_import_line(lang) + r\"([\\S\\s]*)$\", line\n                )\n                if data:\n                    comments = \"%s\" % data.group(1)\n                    new_line = None\n                    detected_lang = lang\n                    dl_code = lang_codes[detected_lang]\n                    if detected_lang != new_lang:\n                        changed = True\n                        new_line = MD_F.get_import_line(new_lang) + comments\n                    else:\n                        found_bc = True\n                        new_line = line\n                    if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                        new_line = new_line[0 : -len(\"  # noqa\")]\n                    seleniumbase_lines.append(new_line)\n                    added_line = True\n                    break\n                data = re.match(\n                    r\"^\\s*\" + MD_F.get_mqa_im_line(lang) + r\"([\\S\\s]*)$\", line\n                )\n                if data:\n                    comments = \"%s\" % data.group(1)\n                    new_line = None\n                    detected_lang = lang\n                    dl_code = lang_codes[detected_lang]\n                    if detected_lang != new_lang:\n                        changed = True\n                        new_line = MD_F.get_mqa_im_line(new_lang) + comments\n                    else:\n                        found_bc = True\n                        new_line = line\n                    if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                        new_line = new_line[0 : -len(\"  # noqa\")]\n                    seleniumbase_lines.append(new_line)\n                    added_line = True\n                    break\n            if not added_line:\n                # Probably a language missing from the translator.\n                # Add the import line as it is and move on.\n                seleniumbase_lines.append(line)\n            continue\n\n        # Find class definitions that determine the language\n        if line.lstrip().startswith(\"class \") and \":\" in line:\n            added_line = False\n            data = re.match(\n                r\"\"\"^(\\s*)class\\s+([\\S]+)\\(([\\S]+)\\):([\\S\\s]*)$\"\"\", line\n            )\n            if data:\n                whitespace = data.group(1)\n                name = \"%s\" % data.group(2)\n                parent_class = \"%s\" % data.group(3)\n                comments = \"%s\" % data.group(4)\n                if parent_class in MD_F.get_parent_classes_list():\n                    detected_lang = MD_F.get_parent_class_lang(parent_class)\n                    dl_code = lang_codes[detected_lang]\n                    if detected_lang != new_lang:\n                        changed = True\n                        new_parent = MD_F.get_lang_parent_class(new_lang)\n                        new_line = \"%sclass %s(%s):%s\" \"\" % (\n                            whitespace,\n                            name,\n                            new_parent,\n                            comments,\n                        )\n                    else:\n                        found_bc = True\n                        new_line = line\n                    if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                        new_line = new_line[0 : -len(\"  # noqa\")]\n                    seleniumbase_lines.append(new_line)\n                    added_line = True\n                    continue\n                elif parent_class in MD_F.get_masterqa_parent_classes_list():\n                    detected_lang = MD_F.get_mqa_par_class_lang(parent_class)\n                    dl_code = lang_codes[detected_lang]\n                    if detected_lang != new_lang:\n                        changed = True\n                        new_parent = MD_F.get_mqa_lang_par_class(new_lang)\n                        new_line = \"%sclass %s(%s):%s\" \"\" % (\n                            whitespace,\n                            name,\n                            new_parent,\n                            comments,\n                        )\n                    else:\n                        found_bc = True\n                        new_line = line\n                    if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                        new_line = new_line[0 : -len(\"  # noqa\")]\n                    seleniumbase_lines.append(new_line)\n                    added_line = True\n                    continue\n            if not added_line:\n                # Probably a language missing from the translator.\n                # Add the class definition line as it is and move on.\n                seleniumbase_lines.append(line)\n            continue\n\n        if (\n            \".main(__name__, __file__)\" in line\n            and detected_lang\n            and new_lang\n            and (detected_lang != new_lang)\n        ):\n            old_basecase = MD_F.get_lang_parent_class(detected_lang)\n            new_basecase = MD_F.get_lang_parent_class(new_lang)\n            if old_basecase in line:\n                new_line = line.replace(old_basecase, new_basecase)\n                seleniumbase_lines.append(new_line)\n                continue\n\n        if (\n            \"self.\" in line\n            and \"(\" in line\n            and detected_lang\n            and (detected_lang != new_lang)\n        ):\n            found_swap = False\n            replace_count = line.count(\"self.\")  # Total possible replacements\n            for key in md.keys():\n                original = \"self.\" + md[key][dl_code] + \"(\"\n                if original in line:\n                    replacement = \"self.\" + md[key][nl_code] + \"(\"\n                    new_line = line.replace(original, replacement)\n                    found_swap = True\n                    replace_count -= 1\n                    if replace_count == 0:\n                        break  # Done making replacements\n                    else:\n                        # There might be another method to replace in the line.\n                        # Example: self.assert_true(\"Name\" in self.get_title())\n                        line = new_line\n                        continue\n\n            if found_swap:\n                if new_line.endswith(\"  # noqa\"):  # Remove flake8 skip\n                    new_line = new_line[0 : -len(\"  # noqa\")]\n                seleniumbase_lines.append(new_line)\n                continue\n\n        seleniumbase_lines.append(line)\n\n    return seleniumbase_lines, changed, detected_lang, found_bc\n\n\ndef main():\n    c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX\n    c2 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX\n    c3 = colorama.Fore.RED + colorama.Back.LIGHTGREEN_EX\n    c4 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX\n    c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX\n    c6 = colorama.Fore.RED + colorama.Back.LIGHTCYAN_EX\n    c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA\n    cr = colorama.Style.RESET_ALL\n    new_lang = None\n    overwrite = False\n    copy = False\n    print_only = False\n    help_me = False\n    invalid_cmd = None\n    line_numbers = False\n    word_wrap = True  # Always use word wrap now\n\n    expected_arg = \"A SeleniumBase Python file\"\n    command_args = sys.argv[2:]\n    seleniumbase_file = command_args[0]\n    if not seleniumbase_file.endswith(\".py\"):\n        seleniumbase_file = (\n            c7 + \">>\" + c5 + \" \" + seleniumbase_file + \" \" + c7 + \"<<\" + cr\n        )\n        bad_file_error = (\n            \"\\n`%s` is not a Python file!\\n\\n\"\n            \"Expecting: [%s]\" % (seleniumbase_file, expected_arg)\n        )\n        bad_file_error = bad_file_error.replace(\n            \"is not a Python file!\", c3 + \"is not a Python file!\" + cr\n        )\n        bad_file_error = bad_file_error.replace(\n            expected_arg, c4 + expected_arg + cr\n        )\n        bad_file_error = bad_file_error.replace(\n            \"Expecting:\", c3 + \"Expecting:\" + cr\n        )\n        print(bad_file_error)\n        help_me = True\n\n    if len(command_args) >= 2 and not help_me:\n        options = command_args[1:]\n        for option in options:\n            option = option.lower()\n            if option == \"help\" or option == \"--help\":\n                help_me = True\n            elif option == \"-o\" or option == \"--overwrite\":\n                overwrite = True\n            elif option == \"-c\" or option == \"--copy\":\n                copy = True\n            elif option == \"-p\" or option == \"--print\":\n                print_only = True\n            elif option == \"-n\":\n                line_numbers = True\n            elif option == \"--en\" or option == \"--english\":\n                new_lang = \"English\"\n            elif option == \"--zh\" or option == \"--chinese\":\n                new_lang = \"Chinese\"\n            elif option == \"--nl\" or option == \"--dutch\":\n                new_lang = \"Dutch\"\n            elif option == \"--fr\" or option == \"--french\":\n                new_lang = \"French\"\n            elif option == \"--it\" or option == \"--italian\":\n                new_lang = \"Italian\"\n            elif option == \"--ja\" or option == \"--japanese\":\n                new_lang = \"Japanese\"\n            elif option == \"--ko\" or option == \"--korean\":\n                new_lang = \"Korean\"\n            elif option == \"--pt\" or option == \"--portuguese\":\n                new_lang = \"Portuguese\"\n            elif option == \"--ru\" or option == \"--russian\":\n                new_lang = \"Russian\"\n            elif option == \"--es\" or option == \"--spanish\":\n                new_lang = \"Spanish\"\n            else:\n                invalid_cmd = \"\\n===> INVALID OPTION: >> %s <<\\n\" % option\n                invalid_cmd = invalid_cmd.replace(\">> \", \">>\" + c5 + \" \")\n                invalid_cmd = invalid_cmd.replace(\" <<\", \" \" + cr + \"<<\")\n                invalid_cmd = invalid_cmd.replace(\">>\", c7 + \">>\" + cr)\n                invalid_cmd = invalid_cmd.replace(\"<<\", c7 + \"<<\" + cr)\n                help_me = True\n                break\n    else:\n        help_me = True\n\n    specify_lang = (\n        \"\\n>* You must specify a language to translate to! *<\\n\"\n        \"\\n\"\n        \">    ********  Language Options:  ********    <\\n\"\n        \"   --en / --English    |    --zh / --Chinese\\n\"\n        \"   --nl / --Dutch      |    --fr / --French\\n\"\n        \"   --it / --Italian    |    --ja / --Japanese\\n\"\n        \"   --ko / --Korean     |    --pt / --Portuguese\\n\"\n        \"   --ru / --Russian    |    --es / --Spanish\\n\"\n    )\n    specify_action = (\n        \"\\n>* You must specify an action type! *<\\n\"\n        \"\\n\"\n        \"> *** Action Options: *** <\\n\"\n        \"      -p / --print\\n\"\n        \"      -o / --overwrite\\n\"\n        \"      -c / --copy\\n\"\n    )\n    example_run = (\n        \"\\n> *** Examples: *** <\\n\"\n        \"Translate test_1.py into Chinese and only print the output:\\n\"\n        \" >$ sbase translate test_1.py --zh -p\\n\"\n        \"Translate test_2.py into Portuguese and overwrite the file:\\n\"\n        \" >$ sbase translate test_2.py --pt -o\\n\"\n        \"Translate test_3.py into Dutch and make a copy of the file:\\n\"\n        \" >$ sbase translate test_3.py --nl -c\\n\"\n    )\n    usage = (\n        \"\\n> *** Usage: *** <\\n\"\n        \" >$ sbase translate [SB_FILE.py] [LANGUAGE] [ACTION]\\n\"\n    )\n    specify_lang = specify_lang.replace(\">*\", c5 + \">*\")\n    specify_lang = specify_lang.replace(\"*<\", \"*<\" + cr)\n    specify_lang = specify_lang.replace(\n        \"Language Options:\", c4 + \"Language Options:\" + cr\n    )\n    specify_lang = specify_lang.replace(\n        \">    ********  \", c3 + \">    ********  \" + cr\n    )\n    specify_lang = specify_lang.replace(\n        \"  ********    <\", c3 + \"  ********    <\" + cr\n    )\n    specify_lang = specify_lang.replace(\"--en\", c2 + \"--en\" + cr)\n    specify_lang = specify_lang.replace(\"--zh\", c2 + \"--zh\" + cr)\n    specify_lang = specify_lang.replace(\"--nl\", c2 + \"--nl\" + cr)\n    specify_lang = specify_lang.replace(\"--fr\", c2 + \"--fr\" + cr)\n    specify_lang = specify_lang.replace(\"--it\", c2 + \"--it\" + cr)\n    specify_lang = specify_lang.replace(\"--ja\", c2 + \"--ja\" + cr)\n    specify_lang = specify_lang.replace(\"--ko\", c2 + \"--ko\" + cr)\n    specify_lang = specify_lang.replace(\"--pt\", c2 + \"--pt\" + cr)\n    specify_lang = specify_lang.replace(\"--ru\", c2 + \"--ru\" + cr)\n    specify_lang = specify_lang.replace(\"--es\", c2 + \"--es\" + cr)\n    specify_lang = specify_lang.replace(\"--English\", c2 + \"--English\" + cr)\n    specify_lang = specify_lang.replace(\"--Chinese\", c2 + \"--Chinese\" + cr)\n    specify_lang = specify_lang.replace(\"--Dutch\", c2 + \"--Dutch\" + cr)\n    specify_lang = specify_lang.replace(\"--French\", c2 + \"--French\" + cr)\n    specify_lang = specify_lang.replace(\"--Italian\", c2 + \"--Italian\" + cr)\n    specify_lang = specify_lang.replace(\"--Japanese\", c2 + \"--Japanese\" + cr)\n    specify_lang = specify_lang.replace(\"--Korean\", c2 + \"--Korean\" + cr)\n    specify_lang = specify_lang.replace(\n        \"--Portuguese\", c2 + \"--Portuguese\" + cr\n    )\n    specify_lang = specify_lang.replace(\"--Russian\", c2 + \"--Russian\" + cr)\n    specify_lang = specify_lang.replace(\"--Spanish\", c2 + \"--Spanish\" + cr)\n    specify_action = specify_action.replace(\">*\", c6 + \">*\")\n    specify_action = specify_action.replace(\"*<\", \"*<\" + cr)\n    specify_action = specify_action.replace(\n        \"Action Options:\", c4 + \"Action Options:\" + cr\n    )\n    specify_action = specify_action.replace(\"> *** \", c3 + \"> *** \" + cr)\n    specify_action = specify_action.replace(\" *** <\", c3 + \" *** <\" + cr)\n    specify_action = specify_action.replace(\" -p\", \" \" + c1 + \"-p\" + cr)\n    specify_action = specify_action.replace(\" -o\", \" \" + c1 + \"-o\" + cr)\n    specify_action = specify_action.replace(\" -c\", \" \" + c1 + \"-c\" + cr)\n    specify_action = specify_action.replace(\n        \" --print\", \" \" + c1 + \"--print\" + cr\n    )\n    specify_action = specify_action.replace(\n        \" --overwrite\", \" \" + c1 + \"--overwrite\" + cr\n    )\n    specify_action = specify_action.replace(\n        \" --copy\", \" \" + c1 + \"--copy\" + cr\n    )\n    example_run = example_run.replace(\"Examples:\", c4 + \"Examples:\" + cr)\n    example_run = example_run.replace(\"> *** \", c3 + \"> *** \" + cr)\n    example_run = example_run.replace(\" *** <\", c3 + \" *** <\" + cr)\n    example_run = example_run.replace(\" -p\", \" \" + c1 + \"-p\" + cr)\n    example_run = example_run.replace(\" -o\", \" \" + c1 + \"-o\" + cr)\n    example_run = example_run.replace(\" -c\", \" \" + c1 + \"-c\" + cr)\n    example_run = example_run.replace(\"Chinese\", c2 + \"Chinese\" + cr)\n    example_run = example_run.replace(\"Portuguese\", c2 + \"Portuguese\" + cr)\n    example_run = example_run.replace(\"Dutch\", c2 + \"Dutch\" + cr)\n    example_run = example_run.replace(\" --zh\", \" \" + c2 + \"--zh\" + cr)\n    example_run = example_run.replace(\" --pt\", \" \" + c2 + \"--pt\" + cr)\n    example_run = example_run.replace(\" --nl\", \" \" + c2 + \"--nl\" + cr)\n    example_run = example_run.replace(\"sbase\", c4 + \"sbase\" + cr)\n    usage = usage.replace(\"Usage:\", c4 + \"Usage:\" + cr)\n    usage = usage.replace(\"> *** \", c3 + \"> *** \" + cr)\n    usage = usage.replace(\" *** <\", c3 + \" *** <\" + cr)\n    usage = usage.replace(\"SB_FILE.py\", c4 + \"SB_FILE.py\" + cr)\n    usage = usage.replace(\"LANGUAGE\", c2 + \"LANGUAGE\" + cr)\n    usage = usage.replace(\"ACTION\", c1 + \"ACTION\" + cr)\n\n    if help_me:\n        message = \"\"\n        if invalid_cmd:\n            message += invalid_cmd\n        message += specify_lang + specify_action + example_run + usage\n        print(\"\")\n        raise Exception(message)\n    if not overwrite and not copy and not print_only:\n        message = specify_action + example_run + usage\n        if not new_lang:\n            message = specify_lang + specify_action + example_run + usage\n        print(\"\")\n        raise Exception(message)\n    if not new_lang:\n        print(\"\")\n        raise Exception(specify_lang + example_run + usage)\n    if overwrite and copy:\n        part_1 = (\n            \"\\n* You can choose either {-o / --overwrite} \"\n            \"OR {-c / --copy}, BUT * NOT BOTH *!\\n\"\n        )\n        part_1 = part_1.replace(\"-o \", c1 + \"-o\" + cr + \" \")\n        part_1 = part_1.replace(\"--overwrite\", c1 + \"--overwrite\" + cr)\n        part_1 = part_1.replace(\"-c \", c1 + \"-c\" + cr + \" \")\n        part_1 = part_1.replace(\"--copy\", c1 + \"--copy\" + cr)\n        part_1 = part_1.replace(\"* NOT BOTH *\", c6 + \"* NOT BOTH *\" + cr)\n        message = part_1 + example_run + usage\n        print(\"\")\n        raise Exception(message)\n\n    with open(seleniumbase_file, mode=\"r\", encoding=\"utf-8\") as f:\n        all_code = f.read()\n    if \"def test_\" not in all_code and \"from seleniumbase\" not in all_code:\n        print(\"\")\n        raise Exception(\n            \"\\n\\n`%s` is not a valid SeleniumBase test file!\\n\"\n            \"\\nExpecting: [%s]\\n\" % (seleniumbase_file, expected_arg)\n        )\n    all_code = all_code.replace(\"\\t\", \"    \")\n    code_lines = all_code.split(\"\\n\")\n\n    sb_lines, changed, d_l, found_bc = process_test_file(code_lines, new_lang)\n    seleniumbase_lines = sb_lines\n    detected_lang = d_l\n    found_basecase = found_bc\n\n    if not changed and found_basecase:\n        print(\"\")\n        msg1 = \" [[[[%s]]]] was already in [[[%s]]]!\\n\\n\" \"\" % (\n            seleniumbase_file,\n            new_lang,\n        )\n        msg1 = msg1.replace(\"[[[[\", \"\" + c3).replace(\"]]]]\", cr + \"\")\n        msg1 = msg1.replace(\"[[[\", \"\" + c5).replace(\"]]]\", cr + \"\")\n        msg2 = None\n        if print_only:\n            msg2 = \"*> ***  No changes to display!  *** <*\"\n        elif overwrite:\n            msg2 = \"*> ***  No changes were made!  *** <*\"\n        else:  # \"copy\" action\n            msg2 = \"*> ***  No action was taken!  *** <*\"\n        msg2 = msg2.replace(\"*>\", \" \" + c6).replace(\"<*\", cr + \"\\n\")\n        print(msg1 + msg2)\n        return\n\n    if not changed and not found_basecase:\n        print(\"\")\n        filename = c3 + seleniumbase_file + cr\n        from_sb = c5 + \"from seleniumbase\" + cr\n        msg0 = \" * In order to translate the script,\\n\"\n        msg1 = ' %s requires \"%s...\"\\n' % (filename, from_sb)\n        msg2 = \" and a BaseCase import in a supported language!\\n\\n\"\n        msg3 = None\n        if print_only:\n            msg3 = \"*> ***  No changes to display!  *** <*\"\n        elif overwrite:\n            msg3 = \"*> ***  No changes were made!  *** <*\"\n        else:  # \"copy\" action\n            msg3 = \"*> ***  No action was taken!  *** <*\"\n        msg3 = msg3.replace(\"*>\", \" \" + c6).replace(\"<*\", cr + \"\\n\")\n        print(msg0 + msg1 + msg2 + msg3)\n        return\n\n    save_line = (\n        \" [[[[%s]]]] was translated to [[[%s]]]! \"\n        \"(Previous: %s)\\n\"\n        \"\" % (seleniumbase_file, new_lang, detected_lang)\n    )\n    save_line = save_line.replace(\"[[[[\", \"\" + c4)\n    save_line = save_line.replace(\"]]]]\", cr + \"\")\n    save_line = save_line.replace(\"[[[\", \"\" + c2)\n    save_line = save_line.replace(\"]]]\", cr + \"\")\n\n    if print_only:\n        from rich.console import Console\n        from rich.syntax import Syntax\n\n        console_width = None  # width of console output when running script\n        used_width = None  # code_width and few spaces on right for padding\n        magic_console = None\n        magic_syntax = None\n        try:\n            console_width = os.popen(\"stty size\", \"r\").read().split()[1]\n            if console_width:\n                console_width = int(console_width)\n        except Exception:\n            console_width = None\n\n        python_code = \"\\n\".join(seleniumbase_lines)\n        code_width = 1\n\n        w = 0  # line number whitespace\n        if line_numbers:\n            w = 4\n            num_lines = len(code_lines)\n            if num_lines >= 10:\n                w = 5\n            if num_lines >= 100:\n                w = 6\n            if num_lines >= 1000:\n                w = 7\n            if num_lines >= 10000:\n                w = 8\n\n        new_sb_lines = []\n        for line in seleniumbase_lines:\n            if line.endswith(\"  # noqa\") and line.count(\"  # noqa\") == 1:\n                line = line.replace(\"  # noqa\", \"\")\n            line_length2 = len(line)  # Normal Python string length used\n            line_length = get_width(line)  # Special characters count 2X\n            if line_length > code_width:\n                code_width = line_length\n\n            if console_width:\n                # If line is larger than console_width, try to optimize it.\n                # Smart Python word wrap to be used with valid indentation.\n                if line_length + w > console_width:  # 5 is line number ws\n                    if line.strip().startswith(\"#\"):\n                        new_sb_lines.append(line)\n                        continue\n                    elif (\n                        line.count(\"  # \") == 1\n                        and get_width(line.split(\"  # \")[0]) + w\n                        <= console_width\n                    ):\n                        # Line is short enough once comment is removed\n                        line = line.split(\"  # \")[0]\n                        new_sb_lines.append(line)\n                        continue\n                    elif (\n                        line.count(\" # \") == 1\n                        and get_width(line.split(\" # \")[0]) + w\n                        <= console_width\n                    ):\n                        # L-Length good if removing bad flake8 comment\n                        line = line.split(\"  # \")[0]\n                        new_sb_lines.append(line)\n                        continue\n                    if line.startswith(\"from\") and \" import \" in line:\n                        line1 = line.split(\" import \")[0] + \" \\\\\"\n                        line2 = \"    import \" + line.split(\" import \")[1]\n                        new_sb_lines.append(line1)\n                        new_sb_lines.append(line2)\n                        continue\n                    if (\n                        line.count(\"(\") >= 1\n                        and line.count(\"(\") == line.count(\")\")\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        first_paren = line.find(\"(\")\n                        line1 = line[:first_paren + 1]\n                        line2 = new_ws + line[first_paren + 1:]\n                        if (\"):\") not in line2:\n                            new_sb_lines.append(line1)\n                            if get_width(line2) + w > console_width:\n                                if line2.count('\", \"') == 1:\n                                    line2a = line2.split('\", \"')[0] + '\",'\n                                    line2b = (\n                                        new_ws\n                                        + '\"'\n                                        + (line2.split('\", \"')[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"', '\") == 1:\n                                    line2a = line2.split(\"', '\")[0] + \"',\"\n                                    line2b = (\n                                        new_ws\n                                        + \"'\"\n                                        + (line2.split(\"', '\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"://\") == 1 and (\n                                    line2.count('\")') == 1\n                                ):\n                                    line2a = line2.split(\"://\")[0] + '://\"'\n                                    line2b = (\n                                        new_ws\n                                        + '\"'\n                                        + (line2.split(\"://\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    if get_width(line2b) + w > (\n                                        console_width\n                                    ):\n                                        if line2b.count(\"/\") > 0:\n                                            slash_one = line2b.find(\"/\")\n                                            slash_one_p1 = slash_one + 1\n                                            line2b1 = (\n                                                line2b[:slash_one_p1] + '\"'\n                                            )\n                                            line2b2 = (\n                                                new_ws\n                                                + '\"'\n                                                + (line2b[slash_one_p1:])\n                                            )\n                                            new_sb_lines.append(line2b1)\n                                            if line2b2.count(\")  # \") == 1:\n                                                line2b2 = (\n                                                    line2b2.split(\")  # \")[\n                                                        0\n                                                    ]\n                                                    + \")\"\n                                                )\n                                            new_sb_lines.append(line2b2)\n                                            continue\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"://\") == 1 and (\n                                    line2.count(\"')\") == 1\n                                ):\n                                    line2a = line2.split(\"://\")[0] + \"://'\"\n                                    line2b = (\n                                        new_ws\n                                        + \"'\"\n                                        + (line2.split(\"://\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    if get_width(line2b) + w > (\n                                        console_width\n                                    ):\n                                        if line2b.count(\"/\") > 0:\n                                            slash_one = line2b.find(\"/\")\n                                            slash_one_p1 = slash_one + 1\n                                            line2b1 = (\n                                                line2b[:slash_one_p1] + \"'\"\n                                            )\n                                            line2b2 = (\n                                                new_ws\n                                                + \"'\"\n                                                + (line2b[slash_one_p1:])\n                                            )\n                                            new_sb_lines.append(line2b1)\n                                            if line2b2.count(\")  # \") == 1:\n                                                line2b2 = (\n                                                    line2b2.split(\")  # \")[\n                                                        0\n                                                    ]\n                                                    + \")\"\n                                                )\n                                            new_sb_lines.append(line2b2)\n                                            continue\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\", \") == 1:\n                                    line2a = line2.split(\", \")[0] + \",\"\n                                    line2b = new_ws + line2.split(\", \")[1]\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count('=\"') == 1 and (\n                                    line2.lstrip().startswith(\"'\")\n                                ):\n                                    line2a = line2.split('=\"')[0] + \"='\"\n                                    line2b = (\n                                        new_ws\n                                        + \"'\\\"\"\n                                        + (line2.split('=\"')[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                                elif line2.count(\"='\") == 1 and (\n                                    line2.lstrip().startswith('\"')\n                                ):\n                                    line2a = line2.split(\"='\")[0] + '=\"'\n                                    line2b = (\n                                        new_ws\n                                        + \"\\\"'\"\n                                        + (line2.split(\"='\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                            new_sb_lines.append(line2)\n                        elif get_width(line2) + 4 + w <= console_width:\n                            line2 = \"    \" + line2\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                        else:\n                            new_sb_lines.append(line)\n                        continue\n                    if line.count('(\"') == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split('(\"')[0] + \"(\"\n                        line2 = new_ws + '\"' + line.split('(\"')[1]\n                        if (\"):\") not in line2:\n                            new_sb_lines.append(line1)\n                            if get_width(line2) + w > console_width:\n                                if line2.count('\" in self.') == 1:\n                                    line2a = (\n                                        line2.split('\" in self.')[0]\n                                        + '\" in'\n                                    )\n                                    line2b = (\n                                        new_ws\n                                        + \"self.\"\n                                        + (line2.split('\" in self.')[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                            new_sb_lines.append(line2)\n                        elif get_width(line2) + 4 + w <= console_width:\n                            line2 = \"    \" + line2\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                        else:\n                            new_sb_lines.append(line)\n                        continue\n                    if line.count(\"('\") == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"('\")[0] + \"(\"\n                        line2 = new_ws + \"'\" + line.split(\"('\")[1]\n                        if (\"):\") not in line2:\n                            new_sb_lines.append(line1)\n                            if get_width(line2) + w > console_width:\n                                if line2.count(\"' in self.\") == 1:\n                                    line2a = (\n                                        line2.split(\"' in self.\")[0]\n                                        + \"' in\"\n                                    )\n                                    line2b = (\n                                        new_ws\n                                        + \"self.\"\n                                        + (line2.split(\"' in self.\")[1])\n                                    )\n                                    new_sb_lines.append(line2a)\n                                    new_sb_lines.append(line2b)\n                                    continue\n                            new_sb_lines.append(line2)\n                        elif get_width(line2) + 4 + w <= console_width:\n                            line2 = \"    \" + line2\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                        else:\n                            new_sb_lines.append(line)\n                        continue\n                    if line.count('= \"') == 1 and line.count(\"://\") == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"://\")[0] + '://\" \\\\'\n                        line2 = new_ws + '\"' + line.split(\"://\")[1]\n                        new_sb_lines.append(line1)\n                        if get_width(line2) + w > console_width:\n                            if line2.count(\"/\") > 0:\n                                slash_one = line2.find(\"/\")\n                                slash_one_p1 = slash_one + 1\n                                line2a = line2[:slash_one_p1] + '\" \\\\'\n                                line2b = (\n                                    new_ws + '\"' + line2[slash_one_p1:]\n                                )\n                                new_sb_lines.append(line2a)\n                                new_sb_lines.append(line2b)\n                                continue\n                        new_sb_lines.append(line2)\n                        continue\n                    if line.count(\"= '\") == 1 and line.count(\"://\") == 1:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"://\")[0] + \"://' \\\\\"\n                        line2 = new_ws + \"'\" + line.split(\"://\")[1]\n                        new_sb_lines.append(line1)\n                        if get_width(line2) + w > console_width:\n                            if line2.count(\"/\") > 0:\n                                slash_one = line2.find(\"/\")\n                                slash_one_p1 = slash_one + 1\n                                line2a = line2[:slash_one_p1] + \"' \\\\\"\n                                line2b = (\n                                    new_ws + \"'\" + line2[slash_one_p1:]\n                                )\n                                new_sb_lines.append(line2a)\n                                new_sb_lines.append(line2b)\n                                continue\n                        new_sb_lines.append(line2)\n                        continue\n                    if line.count(\"(self.\") == 1 and (\"):\") not in line:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\"(self.\")[0] + \"(\"\n                        line2 = new_ws + \"self.\" + line.split(\"(self.\")[1]\n                        if get_width(line1) + w <= console_width:\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" == \") == 1 and not (\n                        line.endswith(\":\") or (\":  #\") in line\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\" == \")[0] + \" == (\"\n                        line2 = new_ws + line.split(\" == \")[1] + \")\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" == \") == 1 and line.endswith(\":\"):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"        \"\n                        line1 = line.split(\" == \")[0] + \" == (\"\n                        line2 = new_ws + line.split(\" == \")[1][:-1] + \"):\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if (\n                        line.count(\" == \") == 1\n                        and (line.count(\":  #\") == 1)\n                        and (line.find(\" == \") < line.find(\":  #\"))\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"        \"\n                        comments = \"  #\" + line.split(\":  #\")[1]\n                        line0 = line.split(\":  #\")[0] + \":\"\n                        line1 = line0.split(\" == \")[0] + \" == (\"\n                        line2 = new_ws + line0.split(\" == \")[1][:-1] + \"):\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            if (\n                                get_width(line2 + comments) + w\n                                <= console_width\n                            ):\n                                new_sb_lines.append(line2 + comments)\n                            else:\n                                new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" % \") == 1 and (\"):\") not in line:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\" % \")[0] + \" \\\\\"\n                        line2 = new_ws + \"% \" + line.split(\" % \")[1]\n                        if get_width(line1) + w <= console_width:\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                    if line.count(\" = \") == 1 and (\"  # \") not in line:\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"    \"\n                        line1 = line.split(\" = \")[0] + \" = (\"\n                        line2 = new_ws + line.split(\" = \")[1] + \")\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            new_sb_lines.append(line2)\n                            continue\n                        elif get_width(line1) + w <= console_width:\n                            if line2.count(\" % \") == 1 and not (\n                                line2.endswith(\":\")\n                            ):\n                                whitespace = line_length2 - len(\n                                    line2.lstrip()\n                                )\n                                line2a = line2.split(\" % \")[0] + \" \\\\\"\n                                line2b = (\n                                    new_ws + \"% \" + line2.split(\" % \")[1]\n                                )\n                                if get_width(line2a) + w <= console_width:\n                                    if (\n                                        get_width(line2b) + w\n                                        <= console_width\n                                    ):\n                                        new_sb_lines.append(line1)\n                                        new_sb_lines.append(line2a)\n                                        new_sb_lines.append(line2b)\n                                        continue\n                    if (\n                        line.count(\" = \") == 1\n                        and (line.count(\"  # \") == 1)\n                        and (line.find(\" = \") < line.find(\"  # \"))\n                    ):\n                        whitespace = line_length2 - len(line.lstrip())\n                        new_ws = line[0:whitespace] + \"        \"\n                        comments = \"  # \" + line.split(\"  # \")[1]\n                        line0 = line.split(\"  # \")[0]\n                        line1 = line0.split(\" = \")[0] + \" = (\"\n                        line2 = new_ws + line0.split(\" = \")[1] + \")\"\n                        if get_width(line1) + w <= console_width and (\n                            get_width(line2) + w <= console_width\n                        ):\n                            new_sb_lines.append(line1)\n                            if (\n                                get_width(line2 + comments) + w\n                                <= console_width\n                            ):\n                                new_sb_lines.append(line2 + comments)\n                            else:\n                                new_sb_lines.append(line2)\n                            continue\n                new_sb_lines.append(line)\n\n        if new_sb_lines:\n            seleniumbase_lines = new_sb_lines\n            python_code = \"\\n\".join(seleniumbase_lines)\n\n        extra_r_spaces = 2\n        if console_width and (code_width + extra_r_spaces < console_width):\n            used_width = code_width + extra_r_spaces\n\n        magic_syntax = Syntax(\n            python_code,\n            \"python\",\n            theme=\"monokai\",\n            line_numbers=line_numbers,\n            code_width=used_width,\n            word_wrap=word_wrap,\n        )\n        magic_console = Console()\n\n        print(\"\")\n        print(save_line)\n        print(\" \" + c1 + \" ***  Here are the results:  >>> \" + cr)\n        # ----------------------------------------\n        dash_length = 62  # May change\n        if used_width and used_width + w < console_width:\n            dash_length = used_width + w\n        elif console_width:\n            dash_length = console_width\n        dashes = \"-\" * dash_length\n        print(dashes)\n        print_success = False\n        if magic_syntax:\n            try:\n                magic_console.print(magic_syntax)\n                print_success = True\n            except Exception:\n                pass\n        if not magic_syntax or not print_success:\n            for line in seleniumbase_lines:\n                print(line)\n        print(dashes)\n        # ----------------------------------------\n\n    new_file_name = None\n    if copy:\n        base_file_name = seleniumbase_file.split(\".py\")[0]\n        new_locale = MD_F.get_locale_code(new_lang)\n        new_ext = \"_\" + new_locale + \".py\"\n        for locale in MD_F.get_locale_list():\n            ext = \"_\" + locale + \".py\"\n            if seleniumbase_file.endswith(ext):\n                base_file_name = seleniumbase_file.split(ext)[0]\n                break\n        new_file_name = base_file_name + new_ext\n    elif overwrite:\n        new_file_name = seleniumbase_file\n    else:\n        pass  # Print-only run already done\n\n    if not print_only:\n        print(\"\")\n        print(save_line)\n    else:\n        pass  # Print-only run already done\n\n    if new_file_name:\n        out_file = open(new_file_name, mode=\"w+\", encoding=\"utf-8\")\n        out_file.writelines(\"\\r\\n\".join(seleniumbase_lines))\n        out_file.close()\n        results_saved = (\n            \"The translation was saved to: [[[%s]]]\\n\" \"\" % new_file_name\n        )\n        results_saved = results_saved.replace(\"[[[\", \"\" + c1)\n        results_saved = results_saved.replace(\"]]]\", cr + \"\")\n        print(results_saved)\n\n\nif __name__ == \"__main__\":\n    invalid_run_command()\n"
  },
  {
    "path": "seleniumbase/undetected/__init__.py",
    "content": "import logging\r\nimport os\r\nimport re\r\nimport requests\r\nimport subprocess\r\nimport sys\r\nimport time\r\nfrom filelock import FileLock\r\nimport selenium.webdriver.chrome.service\r\nimport selenium.webdriver.chrome.webdriver\r\nimport selenium.webdriver.common.service\r\nimport selenium.webdriver.remote.command\r\nfrom contextlib import suppress\r\nfrom .cdp import CDP\r\nfrom .cdp import PageElement\r\nfrom .dprocess import start_detached\r\nfrom .options import ChromeOptions\r\nfrom .reactor import Reactor\r\nfrom .webelement import WebElement\r\n\r\n__all__ = (\r\n    \"Chrome\",\r\n    \"ChromeOptions\",\r\n    \"Patcher\",\r\n    \"Reactor\",\r\n    \"CDP\",\r\n    \"find_chrome_executable\",\r\n)\r\nIS_MAC = \"darwin\" in sys.platform\r\nIS_POSIX = sys.platform.startswith((\"darwin\", \"cygwin\", \"linux\"))\r\nlogger = logging.getLogger(\"uc\")\r\nlogger.setLevel(logging.getLogger().getEffectiveLevel())\r\n\r\n\r\nclass Chrome(selenium.webdriver.chrome.webdriver.WebDriver):\r\n    \"\"\"Controls chromedriver to drive a browser.\r\n    The driver gets downloaded automatically.\"\"\"\r\n    _instances = set()\r\n    session_id = None\r\n    debug = False\r\n\r\n    def __init__(\r\n        self,\r\n        options=None,\r\n        user_data_dir=None,\r\n        driver_executable_path=None,\r\n        browser_executable_path=None,\r\n        port=0,\r\n        enable_cdp_events=False,\r\n        log_level=0,\r\n        headless=False,\r\n        patch_driver=True,\r\n        version_main=None,\r\n        patcher_force_close=False,\r\n        suppress_welcome=True,\r\n        use_subprocess=True,\r\n        debug=False,\r\n        **kw,\r\n    ):\r\n        \"\"\"\r\n        Starts the Chrome service and creates a new instance of chromedriver.\r\n\r\n        Parameters\r\n        ----------\r\n\r\n        options: (default: None)\r\n            Takes an instance of ChromeOptions to customize browser behavior.\r\n\r\n        user_data_dir:\r\n            None (default) Create a temp profile directory for the browser.\r\n            If user_data_dir is a path to a valid Chrome profile directory,\r\n            use it and turn off the automatic removal mechanism at exit.\r\n\r\n        driver_executable_path:\r\n            None (default) Downloads and patches the new binary.\r\n\r\n        browser_executable_path:\r\n            None (default) Use find_chrome_executable().\r\n            (If not specified, make sure Chrome is on the PATH.)\r\n\r\n        port: (default: 0)\r\n            Port you would like the service to run.\r\n            If left as 0, a free port will be found.\r\n\r\n        enable_cdp_events: (default: False)\r\n            This enables the handling of wire messages.\r\n            When enabled, you can subscribe to CDP events by using:\r\n\r\n                driver.add_cdp_listener(\"Network.dataReceived\", yourcallback)\r\n                # yourcallback: callable that accepts exactly 1 dict parameter.\r\n\r\n        log_level: (default: adapts to python global log level)\r\n\r\n        headless: (default: False)\r\n            Use headless mode.\r\n            (Already handled by seleniumbase/core/browser_launcher.py)\r\n\r\n        patch_driver: (default: True)\r\n            Patches uc_driver to be undetectable if not already patched.\r\n\r\n        version_main: (default: None)\r\n            Overrides the browser version for older versions of Chrome.\r\n            Eg: version_main=96\r\n            (Useful when you have a newer driver, but an older browser.)\r\n\r\n        patcher_force_close: (default: False)\r\n            Instructs patcher to access the chromedriver binary.\r\n            If the file is locked, it will force shutdown all instances.\r\n            Setting this is not recommended, unless you know the implications.\r\n\r\n        suppress_welcome: (default: True)\r\n            Suppress the Chrome welcome screen that appears on first-time runs.\r\n\r\n        use_subprocess: (default: True)\r\n            Subprocess chromedriver/python: Don't make Chrome a parent process.\r\n        \"\"\"\r\n        self.debug = debug\r\n        self.patcher = None\r\n        import fasteners\r\n        from seleniumbase.fixtures import constants\r\n        from seleniumbase.fixtures import shared_utils\r\n        if patch_driver:\r\n            uc_lock = fasteners.InterProcessLock(\r\n                constants.MultiBrowser.DRIVER_FIXING_LOCK\r\n            )\r\n            with uc_lock:\r\n                from .patcher import Patcher\r\n                self.patcher = Patcher(\r\n                    executable_path=driver_executable_path,\r\n                    force=patcher_force_close,\r\n                    version_main=version_main,\r\n                )\r\n                self.patcher.auto()\r\n        if not options:\r\n            options = ChromeOptions()\r\n        try:\r\n            if hasattr(options, \"_session\") and options._session is not None:\r\n                # Prevent reuse of options.\r\n                # (Probably a port overlap. Quit existing driver and continue.)\r\n                logger.debug(\"You cannot reuse the ChromeOptions object\")\r\n                with suppress(Exception):\r\n                    options._session.quit()\r\n        except AttributeError:\r\n            pass\r\n        options._session = self\r\n        debug_host = \"127.0.0.1\"\r\n        debug_port = 9222\r\n        special_port_free = False  # If the port isn't free, don't use 9222\r\n        try:\r\n            with requests.Session() as session:\r\n                res = session.get(\r\n                    \"http://127.0.0.1:9222\",\r\n                    headers={\"Connection\": \"close\"},\r\n                    timeout=2,\r\n                )\r\n                if res.status_code != 200:\r\n                    raise Exception(\"The port is free! It will be used!\")\r\n        except Exception:\r\n            # Use port 9222, which outputs to chrome://inspect/#devices\r\n            special_port_free = True\r\n        sys_argv = sys.argv\r\n        arg_join = \" \".join(sys_argv)\r\n        from seleniumbase import config as sb_config\r\n        if (\r\n            ((\"-n\" in sys.argv) or (\" -n=\" in arg_join) or (\"-c\" in sys.argv))\r\n            or getattr(sb_config, \"multi_proxy\", None)\r\n            or not special_port_free\r\n        ):\r\n            debug_port = selenium.webdriver.common.service.utils.free_port()\r\n        if hasattr(options, \"_remote_debugging_port\"):\r\n            # The user chooses the port. Errors happen if the port is taken.\r\n            debug_port = options._remote_debugging_port\r\n        if not options.debugger_address:\r\n            options.debugger_address = \"%s:%d\" % (debug_host, debug_port)\r\n        if enable_cdp_events:\r\n            options.set_capability(\r\n                \"goog:loggingPrefs\", {\"performance\": \"ALL\", \"browser\": \"ALL\"}\r\n            )\r\n        options.add_argument(\"--remote-debugging-host=%s\" % debug_host)\r\n        options.add_argument(\"--remote-debugging-port=%s\" % debug_port)\r\n        if user_data_dir:\r\n            user_data_dir = os.path.abspath(user_data_dir)\r\n            options.add_argument(\"--user-data-dir=%s\" % user_data_dir)\r\n        language, keep_user_data_dir = None, bool(user_data_dir)\r\n        # See if a custom user profile is specified in options\r\n        for arg in options.arguments:\r\n            if \"lang\" in arg:\r\n                m = re.search(\"(?:--)?lang(?:[ =])?(.*)\", arg)\r\n                try:\r\n                    language = m[1]\r\n                except IndexError:\r\n                    language = \"en-US,en;q=0.9\"\r\n            if \"user-data-dir\" in arg:\r\n                m = re.search(\"(?:--)?user-data-dir(?:[ =])?(.*)\", arg)\r\n                try:\r\n                    user_data_dir = m[1]\r\n                    keep_user_data_dir = True\r\n                except IndexError:\r\n                    pass\r\n        if not user_data_dir:\r\n            if getattr(options, \"user_data_dir\", None):\r\n                options.add_argument(\r\n                    \"--user-data-dir=%s\" % options.user_data_dir\r\n                )\r\n                keep_user_data_dir = True\r\n            else:\r\n                import tempfile\r\n                user_data_dir = os.path.normpath(tempfile.mkdtemp())\r\n                keep_user_data_dir = False\r\n                arg = \"--user-data-dir=%s\" % user_data_dir\r\n                # Create a temporary folder for the user-data profile.\r\n                options.add_argument(arg)\r\n        if not language:\r\n            with suppress(Exception):\r\n                import locale\r\n                language = locale.getlocale()[0].replace(\"_\", \"-\")\r\n            if (\r\n                not language\r\n                or \"English\" in language\r\n                or \"United States\" in language\r\n            ):\r\n                language = \"en-US\"\r\n        options.add_argument(\"--lang=%s\" % language)\r\n        if not options.binary_location:\r\n            binary_location = (\r\n                browser_executable_path or find_chrome_executable()\r\n            )\r\n            if binary_location:\r\n                options.binary_location = binary_location\r\n            else:\r\n                # Improve the default error message in this situation.\r\n                # Setting options.binary_location to None results in:\r\n                #    \"TypeError: Binary Location Must be a String\"\r\n                raise Exception(\"Chrome not found! Install it first!\")\r\n        self._delay = constants.UC.RECONNECT_TIME\r\n        self.user_data_dir = user_data_dir\r\n        self.keep_user_data_dir = keep_user_data_dir\r\n        if suppress_welcome:\r\n            options.arguments.extend(\r\n                [\r\n                    \"--no-default-browser-check\",\r\n                    \"--no-first-run\",\r\n                    \"--no-service-autorun\",\r\n                    \"--password-store=basic\",\r\n                    \"--profile-directory=Default\",\r\n                ]\r\n            )\r\n        options.add_argument(\r\n            \"--log-level=%d\" % log_level\r\n            or divmod(logging.getLogger().getEffectiveLevel(), 10)[0]\r\n        )\r\n        if hasattr(options, 'handle_prefs'):\r\n            options.handle_prefs(user_data_dir)\r\n        with suppress(Exception):\r\n            import json\r\n            with open(\r\n                os.path.join(\r\n                    os.path.abspath(user_data_dir),\r\n                    \"Default\",\r\n                    \"Preferences\",\r\n                ),\r\n                encoding=\"utf-8\",\r\n                mode=\"r+\",\r\n                errors=\"ignore\",\r\n            ) as fs:\r\n                config = json.load(fs)\r\n                if (\r\n                    \"exit_type\" not in config[\"profile\"].keys()\r\n                    or config[\"profile\"][\"exit_type\"] is not None\r\n                ):\r\n                    config[\"profile\"][\"exit_type\"] = None\r\n                fs.seek(0, 0)\r\n                fs.truncate()\r\n                json.dump(config, fs)\r\n        creationflags = 0\r\n        if \"win32\" in sys.platform:\r\n            creationflags = subprocess.CREATE_NO_WINDOW\r\n        self.options = options\r\n        uc_lock = fasteners.InterProcessLock(\r\n            constants.MultiBrowser.DRIVER_FIXING_LOCK\r\n        )\r\n        with uc_lock:\r\n            if not use_subprocess:\r\n                self.browser_pid = start_detached(\r\n                    options.binary_location, *options.arguments\r\n                )\r\n            else:\r\n                gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\r\n                with gui_lock:\r\n                    shared_utils.make_writable(\r\n                        constants.MultiBrowser.PYAUTOGUILOCK\r\n                    )\r\n                    browser = subprocess.Popen(\r\n                        [options.binary_location, *options.arguments],\r\n                        stdin=subprocess.PIPE,\r\n                        stdout=subprocess.PIPE,\r\n                        stderr=subprocess.PIPE,\r\n                        close_fds=IS_POSIX,\r\n                        creationflags=creationflags,\r\n                    )\r\n                    self.browser_pid = browser.pid\r\n            service_ = None\r\n            log_output = subprocess.PIPE\r\n            if patch_driver:\r\n                service_ = selenium.webdriver.chrome.service.Service(\r\n                    executable_path=self.patcher.executable_path,\r\n                    service_args=[\"--disable-build-check\"],\r\n                    port=port,\r\n                    log_output=log_output,\r\n                )\r\n            else:\r\n                service_ = selenium.webdriver.chrome.service.Service(\r\n                    executable_path=driver_executable_path,\r\n                    service_args=[\"--disable-build-check\"],\r\n                    port=port,\r\n                    log_output=log_output,\r\n                )\r\n            if hasattr(service_, \"creationflags\"):\r\n                setattr(service_, \"creationflags\", creationflags)\r\n            if hasattr(service_, \"creation_flags\"):\r\n                setattr(service_, \"creation_flags\", creationflags)\r\n            try:\r\n                super().__init__(options=options, service=service_)\r\n            except OSError as e:\r\n                if IS_MAC and \"Bad CPU type in executable\" in str(e):\r\n                    print(str(e))\r\n                    message = (\r\n                        \"Missing a macOS dependency:\\n\"\r\n                        \"Your Mac needs Rosetta 2 to use UC Mode!\\n\"\r\n                        'Run: \"softwareupdate --install-rosetta\"\\n'\r\n                        \"Info: \"\r\n                        \"https://apple.stackexchange.com/a/408379/607628\"\r\n                    )\r\n                    raise Exception(message)\r\n                else:\r\n                    raise\r\n            self.reactor = None\r\n            if enable_cdp_events:\r\n                if logging.getLogger().getEffectiveLevel() == logging.DEBUG:\r\n                    logging.getLogger(\r\n                        \"selenium.webdriver.remote.remote_connection\"\r\n                    ).setLevel(20)\r\n                reactor = Reactor(self)\r\n                reactor.start()\r\n                self.reactor = reactor\r\n            self._web_element_cls = WebElement\r\n\r\n    def __getattribute__(self, item):\r\n        if not super().__getattribute__(\"debug\"):\r\n            return super().__getattribute__(item)\r\n        else:\r\n            import inspect\r\n            original = super().__getattribute__(item)\r\n            if inspect.ismethod(original) and not inspect.isclass(original):\r\n                def newfunc(*args, **kwargs):\r\n                    return original(*args, **kwargs)\r\n                return newfunc\r\n            return original\r\n\r\n    def __dir__(self):\r\n        return object.__dir__(self)\r\n\r\n    def _get_cdc_props(self):\r\n        cdc_props = []\r\n        with suppress(Exception):\r\n            cdc_props = self.execute_script(\r\n                \"\"\"\r\n                let objectToInspect = window,\r\n                    result = [];\r\n                while(objectToInspect !== null)\r\n                { result = result.concat(\r\n                    Object.getOwnPropertyNames(objectToInspect)\r\n                  );\r\n                  objectToInspect = Object.getPrototypeOf(objectToInspect); }\r\n                return result.filter(i => i.match(/^[a-z]{3}_[a-z]{22}_.*/i))\r\n                \"\"\"\r\n            )\r\n        return cdc_props\r\n\r\n    def _hook_remove_cdc_props(self, cdc_props):\r\n        if len(cdc_props) < 1:\r\n            return\r\n        cdc_props_js_array = \"[\" + \", \".join(\r\n            '\"' + p + '\"' for p in cdc_props\r\n        ) + \"]\"\r\n        self.execute_cdp_cmd(\r\n            \"Page.addScriptToEvaluateOnNewDocument\",\r\n            {\r\n                \"source\": cdc_props_js_array + (\r\n                    \".forEach(p => delete window[p]);\"\r\n                )\r\n            },\r\n        )\r\n\r\n    def remove_cdc_props_as_needed(self):\r\n        cdc_props = self._get_cdc_props()\r\n        if len(cdc_props) > 0:\r\n            self._hook_remove_cdc_props(cdc_props)\r\n            time.sleep(0.05)\r\n\r\n    def get(self, url):\r\n        self.remove_cdc_props_as_needed()\r\n        return super().get(url)\r\n\r\n    def add_cdp_listener(self, event_name, callback):\r\n        if (\r\n            getattr(self, \"reactor\", None)\r\n            and isinstance(self.reactor, Reactor)\r\n        ):\r\n            self.reactor.add_event_handler(event_name, callback)\r\n            return self.reactor.handlers\r\n        return False\r\n\r\n    def clear_cdp_listeners(self):\r\n        if (\r\n            getattr(self, \"reactor\", None)\r\n            and isinstance(self.reactor, Reactor)\r\n        ):\r\n            self.reactor.handlers.clear()\r\n\r\n    def window_new(self, url=None):\r\n        self.execute(\r\n            selenium.webdriver.remote.command.Command.NEW_WINDOW,\r\n            {\"type\": \"window\"},\r\n        )\r\n        if url:\r\n            self.remove_cdc_props_as_needed()\r\n            return super().get(url)\r\n        return None\r\n\r\n    def tab_new(self, url):\r\n        \"\"\"Open url in a new tab.\"\"\"\r\n        if not hasattr(self, \"cdp\"):\r\n            self.cdp = CDP(self.options)\r\n        self.cdp.tab_new(str(url))\r\n\r\n    def tab_list(self):\r\n        if not hasattr(self, \"cdp\"):\r\n            self.cdp = CDP(self.options)\r\n\r\n        retval = self.get(self.cdp.endpoints[\"list\"])\r\n        return [PageElement(o) for o in retval]\r\n\r\n    def reconnect(self, timeout=0.1):\r\n        \"\"\"This can be useful when sites use heavy detection methods:\r\n        - Stops the chromedriver service that runs in the background.\r\n        - Starts the chromedriver service that runs in the background.\r\n        - Recreates the session.\"\"\"\r\n        if hasattr(self, \"service\"):\r\n            with suppress(Exception):\r\n                if self.service.is_connectable():\r\n                    self.stop_client()\r\n                    try:\r\n                        self.service.send_remote_shutdown_command()\r\n                    except TypeError:\r\n                        pass\r\n                    finally:\r\n                        with suppress(Exception):\r\n                            self.service._terminate_process()\r\n            if isinstance(timeout, str):\r\n                if timeout.lower() == \"breakpoint\":\r\n                    breakpoint()  # To continue:\r\n                    pass  # Type \"c\" & press ENTER!\r\n            else:\r\n                time.sleep(timeout)\r\n            with suppress(Exception):\r\n                self.service.start()\r\n        with suppress(Exception):\r\n            self.start_session()\r\n            time.sleep(0.0075)\r\n        with suppress(Exception):\r\n            for window_handle in self.window_handles:\r\n                self.switch_to.window(window_handle)\r\n                if self.current_url.startswith(\"chrome-extension://\"):\r\n                    # https://issues.chromium.org/issues/396611138\r\n                    # (Remove the Linux conditional when resolved)\r\n                    # (So that close() is always called)\r\n                    if \"linux\" in sys.platform:\r\n                        self.close()\r\n                    if self.service.is_connectable():\r\n                        self.stop_client()\r\n                        try:\r\n                            self.service.send_remote_shutdown_command()\r\n                        except TypeError:\r\n                            pass\r\n                        finally:\r\n                            with suppress(Exception):\r\n                                self.service._terminate_process()\r\n                    self.service.start()\r\n                    self.start_session()\r\n                    time.sleep(0.003)\r\n        with suppress(Exception):\r\n            self.switch_to.window(self.window_handles[-1])\r\n        self._is_connected = True\r\n\r\n    def disconnect(self):\r\n        \"\"\"Stops the chromedriver service that runs in the background.\r\n        To use driver methods again, you MUST call driver.connect()\"\"\"\r\n        if hasattr(self, \"service\"):\r\n            with suppress(Exception):\r\n                if self.service.is_connectable():\r\n                    self.stop_client()\r\n                    time.sleep(0.003)\r\n                    try:\r\n                        self.service.send_remote_shutdown_command()\r\n                    except TypeError:\r\n                        pass\r\n                    finally:\r\n                        with suppress(Exception):\r\n                            self.service._terminate_process()\r\n        self._is_connected = False\r\n\r\n    def connect(self):\r\n        \"\"\"Starts the chromedriver service that runs in the background\r\n        and recreates the session.\"\"\"\r\n        if hasattr(self, \"service\"):\r\n            with suppress(Exception):\r\n                self.service.start()\r\n        with suppress(Exception):\r\n            self.start_session()\r\n            time.sleep(0.0075)\r\n        with suppress(Exception):\r\n            for window_handle in self.window_handles:\r\n                self.switch_to.window(window_handle)\r\n                current_url = None\r\n                if hasattr(self, \"cdp\") and hasattr(self.cdp, \"driver\"):\r\n                    with suppress(Exception):\r\n                        current_url = self.cdp.get_current_url()\r\n                if not current_url:\r\n                    current_url = self.current_url\r\n                if current_url.startswith(\"chrome-extension://\"):\r\n                    # https://issues.chromium.org/issues/396611138\r\n                    # (Remove the Linux conditional when resolved)\r\n                    # (So that close() is always called)\r\n                    if \"linux\" in sys.platform:\r\n                        self.close()\r\n                    if self.service.is_connectable():\r\n                        self.stop_client()\r\n                        try:\r\n                            self.service.send_remote_shutdown_command()\r\n                        except TypeError:\r\n                            pass\r\n                        finally:\r\n                            with suppress(Exception):\r\n                                self.service._terminate_process()\r\n                    self.service.start()\r\n                    self.start_session()\r\n                    time.sleep(0.003)\r\n        with suppress(Exception):\r\n            self.switch_to.window(self.window_handles[-1])\r\n        self._is_connected = True\r\n\r\n    def start_session(self, capabilities=None):\r\n        if not capabilities:\r\n            capabilities = self.options.to_capabilities()\r\n        super().start_session(capabilities)\r\n\r\n    def quit(self):\r\n        try:\r\n            logger.debug(\"Terminating the UC browser\")\r\n            os.kill(self.browser_pid, 15)\r\n            if \"linux\" in sys.platform:\r\n                os.waitpid(self.browser_pid, 0)\r\n                time.sleep(0.02)\r\n            else:\r\n                time.sleep(0.04)\r\n        except (AttributeError, ChildProcessError, RuntimeError, OSError):\r\n            time.sleep(0.05)\r\n        except TimeoutError as e:\r\n            logger.debug(e, exc_info=True)\r\n        except Exception:\r\n            pass\r\n        with suppress(Exception):\r\n            self.stop_client()\r\n        with suppress(Exception):\r\n            if hasattr(self, \"command_executor\") and self.command_executor:\r\n                self.command_executor.close()\r\n\r\n        # Remove instance reference to allow garbage collection\r\n        Chrome._instances.discard(self)\r\n\r\n        if hasattr(self, \"service\") and getattr(self.service, \"process\", None):\r\n            logger.debug(\"Stopping webdriver service\")\r\n            with suppress(Exception):\r\n                try:\r\n                    self.service.send_remote_shutdown_command()\r\n                except TypeError:\r\n                    pass\r\n                finally:\r\n                    with suppress(Exception):\r\n                        self.service._terminate_process()\r\n        if (\r\n            hasattr(self, \"reactor\")\r\n            and self.reactor\r\n            and hasattr(self.reactor, \"event\")\r\n        ):\r\n            logger.debug(\"Shutting down Reactor\")\r\n            with suppress(Exception):\r\n                self.reactor.event.set()\r\n                self.reactor.join(timeout=2)\r\n            self.reactor = None\r\n        if (\r\n            hasattr(self, \"keep_user_data_dir\")\r\n            and hasattr(self, \"user_data_dir\")\r\n            and not self.keep_user_data_dir\r\n        ):\r\n            import shutil\r\n            for _ in range(5):\r\n                try:\r\n                    shutil.rmtree(self.user_data_dir, ignore_errors=False)\r\n                except FileNotFoundError:\r\n                    pass\r\n                except (RuntimeError, OSError, PermissionError) as e:\r\n                    logger.debug(\r\n                        \"When removing the temp profile, a %s occured: \"\r\n                        \"%s\\nRetrying...\"\r\n                        % (e.__class__.__name__, e)\r\n                    )\r\n                else:\r\n                    logger.debug(\r\n                        \"Successfully removed %s\" % self.user_data_dir\r\n                    )\r\n                    break\r\n                time.sleep(0.1)\r\n        # Dereference Patcher so that it can start cleaning up as well.\r\n        # This must come last, otherwise it will throw \"in use\" errors.\r\n        self.patcher = None\r\n\r\n    def __del__(self):\r\n        with suppress(Exception):\r\n            if \"win32\" in sys.platform:\r\n                self.stop_client()\r\n                self.command_executor.close()\r\n            else:\r\n                super().quit()\r\n        with suppress(Exception):\r\n            self.quit()\r\n\r\n    def __enter__(self):\r\n        return self\r\n\r\n    def __exit__(self, exc_type, exc_val, exc_tb):\r\n        self.reconnect(timeout=self._delay)\r\n\r\n    def __hash__(self):\r\n        return hash(self.options.debugger_address)\r\n\r\n\r\ndef find_chrome_executable():\r\n    from seleniumbase.core import detect_b_ver\r\n    binary_location = detect_b_ver.get_binary_location(\"google-chrome\", True)\r\n    if os.path.exists(binary_location) and os.access(binary_location, os.X_OK):\r\n        return os.path.normpath(binary_location)\r\n\r\n    candidates = set()\r\n    if IS_POSIX:\r\n        for item in os.environ.get(\"PATH\").split(os.pathsep):\r\n            for subitem in (\r\n                \"google-chrome\",\r\n                \"google-chrome-stable\",\r\n                \"google-chrome-beta\",\r\n                \"google-chrome-dev\",\r\n                \"google-chrome-unstable\",\r\n                \"chrome\",\r\n                \"chromium\",\r\n                \"chromium-browser\",\r\n            ):\r\n                candidates.add(os.sep.join((item, subitem)))\r\n        if \"darwin\" in sys.platform:\r\n            gc = \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\"\r\n            candidates.update(\r\n                [\r\n                    gc, \"/Applications/Chromium.app/Contents/MacOS/Chromium\"\r\n                ]\r\n            )\r\n    else:\r\n        for item in map(\r\n            os.environ.get,\r\n            (\r\n                \"PROGRAMFILES\",\r\n                \"PROGRAMFILES(X86)\",\r\n                \"LOCALAPPDATA\",\r\n                \"PROGRAMW6432\",\r\n            ),\r\n        ):\r\n            for subitem in (\r\n                \"Google/Chrome/Application\",\r\n                \"Google/Chrome Beta/Application\",\r\n                \"Google/Chrome Canary/Application\",\r\n            ):\r\n                candidates.add(os.sep.join((item, subitem, \"chrome.exe\")))\r\n    for candidate in candidates:\r\n        if os.path.exists(candidate) and os.access(candidate, os.X_OK):\r\n            return os.path.normpath(candidate)\r\n    return None  # Browser not found!\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp.py",
    "content": "import json\r\nimport logging\r\nimport requests\r\nimport websockets\r\n\r\nlog = logging.getLogger(__name__)\r\n\r\n\r\nclass CDPObject(dict):\r\n    def __init__(self, *a, **k):\r\n        super().__init__(*a, **k)\r\n        self.__dict__ = self\r\n        for k in self.__dict__:\r\n            if isinstance(self.__dict__[k], dict):\r\n                self.__dict__[k] = CDPObject(self.__dict__[k])\r\n            elif isinstance(self.__dict__[k], list):\r\n                for i in range(len(self.__dict__[k])):\r\n                    if isinstance(self.__dict__[k][i], dict):\r\n                        self.__dict__[k][i] = CDPObject(self)\r\n\r\n    def __repr__(self):\r\n        tpl = \"%s(\\n\\t{{}}\\n\\t)\" % self.__class__.__name__\r\n        return tpl.format(\r\n            \"\\n  \".join(\"%s = %s\" % (k, v) for k, v in self.items())\r\n        )\r\n\r\n\r\nclass PageElement(CDPObject):\r\n    pass\r\n\r\n\r\nclass CDP:\r\n    log = logging.getLogger(\"CDP\")\r\n\r\n    endpoints = CDPObject(\r\n        {\r\n            \"json\": \"/json\",\r\n            \"protocol\": \"/json/protocol\",\r\n            \"list\": \"/json/list\",\r\n            \"new\": \"/json/new?{url}\",\r\n            \"activate\": \"/json/activate/{id}\",\r\n            \"close\": \"/json/close/{id}\",\r\n        }\r\n    )\r\n\r\n    def __init__(self, options):\r\n        self.server_addr = \"http://{0}:{1}\".format(\r\n            *options.debugger_address.split(\":\")\r\n        )\r\n        self._reqid = 0\r\n        self._session = requests.Session()\r\n        self._last_resp = None\r\n        self._last_json = None\r\n        with requests.Session() as session:\r\n            resp = session.get(\r\n                self.server_addr + self.endpoints.json,\r\n                headers={\"Connection\": \"close\"},\r\n                timeout=2,\r\n            )\r\n            self.sessionId = resp.json()[0][\"id\"]\r\n            self.wsurl = resp.json()[0][\"webSocketDebuggerUrl\"]\r\n\r\n    def tab_activate(self, id=None):\r\n        if not id:\r\n            active_tab = self.tab_list()[0]\r\n            id = active_tab.id\r\n            self.wsurl = active_tab.webSocketDebuggerUrl\r\n        with requests.Session() as session:\r\n            resp = session.post(\r\n                self.server_addr + self.endpoints[\"activate\"].format(id=id),\r\n                headers={\"Connection\": \"close\"},\r\n                timeout=2,\r\n            )\r\n            return resp.json()\r\n\r\n    def tab_list(self):\r\n        with requests.Session() as session:\r\n            resp = session.get(\r\n                self.server_addr + self.endpoints[\"list\"],\r\n                headers={\"Connection\": \"close\"},\r\n                timeout=2,\r\n            )\r\n            retval = resp.json()\r\n            return [PageElement(o) for o in retval]\r\n\r\n    def tab_new(self, url):\r\n        with requests.Session() as session:\r\n            resp = session.post(\r\n                self.server_addr + self.endpoints[\"new\"].format(url=url),\r\n                headers={\"Connection\": \"close\"},\r\n                timeout=2,\r\n            )\r\n            return resp.json()\r\n\r\n    def tab_close_last_opened(self):\r\n        sessions = self.tab_list()\r\n        opentabs = [s for s in sessions if s[\"type\"] == \"page\"]\r\n        with requests.Session() as session:\r\n            endp_close = self.endpoints[\"close\"]\r\n            resp = session.post(\r\n                self.server_addr + endp_close.format(id=opentabs[-1][\"id\"]),\r\n                headers={\"Connection\": \"close\"},\r\n                timeout=2,\r\n            )\r\n            return resp.json()\r\n\r\n    async def send(self, method, params):\r\n        self._reqid += 1\r\n        async with websockets.connect(self.wsurl) as ws:\r\n            await ws.send(\r\n                json.dumps(\r\n                    {\"method\": method, \"params\": params, \"id\": self._reqid}\r\n                )\r\n            )\r\n            self._last_resp = await ws.recv()\r\n            self._last_json = json.loads(self._last_resp)\r\n            self.log.info(self._last_json)\r\n\r\n    def get(self, uri):\r\n        from urllib.parse import unquote\r\n\r\n        uri = unquote(uri, errors=\"strict\")\r\n        with requests.Session() as session:\r\n            resp = session.get(\r\n                self.server_addr + uri,\r\n                headers={\"Connection\": \"close\"},\r\n                timeout=2,\r\n            )\r\n            try:\r\n                self._last_resp = resp\r\n                self._last_json = resp.json()\r\n            except Exception:\r\n                return\r\n            else:\r\n                return self._last_json\r\n\r\n    def post(self, uri, data=None):\r\n        from urllib.parse import unquote\r\n\r\n        uri = unquote(uri, errors=\"strict\")\r\n        if not data:\r\n            data = {}\r\n        with requests.Session() as session:\r\n            resp = session.post(\r\n                self.server_addr + uri,\r\n                json=data,\r\n                headers={\"Connection\": \"close\"},\r\n                timeout=2,\r\n            )\r\n            try:\r\n                self._last_resp = resp\r\n                self._last_json = resp.json()\r\n            except Exception:\r\n                return self._last_resp\r\n\r\n    @property\r\n    def last_json(self):\r\n        return self._last_json\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/__init__.py",
    "content": "from seleniumbase.undetected.cdp_driver import cdp_util  # noqa\nfrom seleniumbase.undetected.cdp_driver.cdp_util import start_async  # noqa\nfrom seleniumbase.undetected.cdp_driver.cdp_util import start_sync  # noqa\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/_contradict.py",
    "content": "import warnings as _warnings\r\nfrom collections.abc import Mapping as _Mapping, Sequence as _Sequence\r\nimport logging\r\n\r\n__logger__ = logging.getLogger(__name__)\r\n__all__ = [\"cdict\", \"ContraDict\"]\r\n\r\n\r\ndef cdict(*args, **kwargs):\r\n    \"\"\"Factory function\"\"\"\r\n    return ContraDict(*args, **kwargs)\r\n\r\n\r\nclass ContraDict(dict):\r\n    \"\"\"\r\n    Directly inherited from dict.\r\n    Accessible by attribute. o.x == o['x']\r\n    This works also for all corner cases.\r\n    Native json.dumps and json.loads work with it.\r\n\r\n    Names like \"keys\", \"update\", \"values\" etc won't overwrite the methods,\r\n    but will just be available using dict lookup notation obj['items']\r\n    instead of obj.items.\r\n\r\n    All key names are converted to snake_case.\r\n    Hyphen's (-), dot's (.) or whitespaces are replaced by underscore (_).\r\n    Autocomplete works even if the objects comes from a list.\r\n    Recursive action. Dict assignments will be converted too.\r\n    \"\"\"\r\n    __module__ = None\r\n\r\n    def __init__(self, *args, **kwargs):\r\n        super().__init__()\r\n        # silent = kwargs.pop(\"silent\", False)\r\n        _ = dict(*args, **kwargs)\r\n\r\n        super().__setattr__(\"__dict__\", self)\r\n        for k, v in _.items():\r\n            _check_key(k, self, False, True)\r\n            super().__setitem__(k, _wrap(self.__class__, v))\r\n\r\n    def __setitem__(self, key, value):\r\n        super().__setitem__(key, _wrap(self.__class__, value))\r\n\r\n    def __setattr__(self, key, value):\r\n        super().__setitem__(key, _wrap(self.__class__, value))\r\n\r\n    def __getattribute__(self, attribute):\r\n        if attribute in self:\r\n            return self[attribute]\r\n        if not _check_key(attribute, self, True, silent=True):\r\n            return getattr(super(), attribute)\r\n        return object.__getattribute__(self, attribute)\r\n\r\n\r\ndef _wrap(cls, v):\r\n    if isinstance(v, _Mapping):\r\n        v = cls(v)\r\n    elif isinstance(v, _Sequence) and not isinstance(\r\n        v, (str, bytes, bytearray, set, tuple)\r\n    ):\r\n        v = list([_wrap(cls, x) for x in v])\r\n    return v\r\n\r\n\r\n_warning_names = (\r\n    \"items\",\r\n    \"keys\",\r\n    \"values\",\r\n    \"update\",\r\n    \"clear\",\r\n    \"copy\",\r\n    \"fromkeys\",\r\n    \"get\",\r\n    \"items\",\r\n    \"keys\",\r\n    \"pop\",\r\n    \"popitem\",\r\n    \"setdefault\",\r\n    \"update\",\r\n    \"values\",\r\n    \"class\",\r\n)\r\n\r\n_warning_names_message = \"\"\"\\n\\\r\n    While creating a ContraDict object, a key offending key name '{0}'\r\n    has been found, which might behave unexpected.\r\n    You will only be able to look it up using key,\r\n    eg. myobject['{0}']. myobject.{0} will not work with that name.\"\"\"\r\n\r\n\r\ndef _check_key(\r\n    key: str, mapping: _Mapping, boolean: bool = False, silent=True\r\n):\r\n    \"\"\"Checks `key` and warns if needed.\r\n    :param key:\r\n    :param boolean: return True or False instead of passthrough\r\n    \"\"\"\r\n    e = None\r\n    if not isinstance(key, (str,)):\r\n        if boolean:\r\n            return True\r\n        return key\r\n    if key.lower() in _warning_names or any(_ in key for _ in (\"-\", \".\")):\r\n        if not silent:\r\n            _warnings.warn(_warning_names_message.format(key))\r\n        e = True\r\n    if not boolean:\r\n        return key\r\n    return not e\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/browser.py",
    "content": "\"\"\"CDP-Driver is based on NoDriver\"\"\"\r\nfrom __future__ import annotations\r\nimport asyncio\r\nimport atexit\r\nimport fasteners\r\nimport http.cookiejar\r\nimport json\r\nimport logging\r\nimport os\r\nimport pathlib\r\nimport pickle\r\nimport re\r\nimport shutil\r\nimport time\r\nimport urllib.parse\r\nimport urllib.request\r\nimport warnings\r\nfrom collections import defaultdict\r\nfrom contextlib import suppress\r\nfrom seleniumbase import config as sb_config\r\nfrom seleniumbase.fixtures import constants\r\nfrom seleniumbase.fixtures import shared_utils\r\nfrom typing import List, Optional, Set, Tuple, Union\r\nimport mycdp as cdp\r\nfrom . import cdp_util as util\r\nfrom . import tab\r\nfrom ._contradict import ContraDict\r\nfrom .config import PathLike, Config, is_posix\r\nfrom .connection import Connection\r\n\r\nlogger = logging.getLogger(__name__)\r\n\r\n\r\ndef get_registered_instances():\r\n    return __registered__instances__\r\n\r\n\r\ndef deconstruct_browser():\r\n    for _ in __registered__instances__:\r\n        if not _.stopped:\r\n            _.stop(deconstruct=True)\r\n        max_attempts = 5\r\n        for attempt in range(max_attempts):\r\n            try:\r\n                if _.config and not _.config.uses_custom_data_dir:\r\n                    if os.path.exists(_.config.user_data_dir):\r\n                        shutil.rmtree(\r\n                            _.config.user_data_dir, ignore_errors=False\r\n                        )\r\n                    if not os.path.exists(_.config.user_data_dir):\r\n                        break\r\n                    else:\r\n                        time.sleep(0.12)\r\n            except FileNotFoundError:\r\n                break\r\n            except (PermissionError, OSError) as e:\r\n                if attempt == max_attempts - 1:\r\n                    logger.debug(\r\n                        \"Problem removing data dir %s\\n\"\r\n                        \"Consider checking whether it's there \"\r\n                        \"and remove it by hand\\nerror: %s\"\r\n                        % (_.config.user_data_dir, e)\r\n                    )\r\n                    break\r\n                time.sleep(0.12)\r\n                continue\r\n        logging.debug(\"Temp profile %s was removed.\" % _.config.user_data_dir)\r\n\r\n\r\nclass Browser:\r\n    \"\"\"\r\n    The Browser object is the \"root\" of the hierarchy\r\n    and contains a reference to the browser parent process.\r\n    There should usually be only 1 instance of this.\r\n    All opened tabs, extra browser screens,\r\n    and resources will not cause a new Browser process,\r\n    but rather create additional :class:`Tab` objects.\r\n    So, besides starting your instance and first/additional tabs,\r\n    you don't actively use it a lot under normal conditions.\r\n    Tab objects will represent and control:\r\n     - tabs (as you know them)\r\n     - browser windows (new window)\r\n     - iframe\r\n     - background processes\r\n    Note:\r\n    The Browser object is not instantiated by __init__\r\n    but using the asynchronous :meth:`Browser.create` method.\r\n    Note:\r\n    In Chromium based browsers, there is a parent process which keeps\r\n    running all the time, even if there are no visible browser windows.\r\n    Sometimes it's stubborn to close it, so make sure that after using\r\n    this library, the browser is correctly and fully closed/exited/killed.\r\n    \"\"\"\r\n    _process: asyncio.subprocess.Process\r\n    _process_pid: int\r\n    _http: HTTPApi = None\r\n    _cookies: CookieJar = None\r\n    config: Config\r\n    connection: Connection\r\n\r\n    @classmethod\r\n    async def create(\r\n        cls,\r\n        config: Config = None,\r\n        *,\r\n        user_data_dir: PathLike = None,\r\n        headless: bool = False,\r\n        incognito: bool = False,\r\n        guest: bool = False,\r\n        browser_executable_path: PathLike = None,\r\n        browser_args: List[str] = None,\r\n        sandbox: bool = True,\r\n        host: str = None,\r\n        port: int = None,\r\n        **kwargs,\r\n    ) -> Browser:\r\n        \"\"\"Entry point for creating an instance.\"\"\"\r\n        if not config:\r\n            config = Config(\r\n                user_data_dir=user_data_dir,\r\n                headless=headless,\r\n                incognito=incognito,\r\n                guest=guest,\r\n                browser_executable_path=browser_executable_path,\r\n                browser_args=browser_args or [],\r\n                sandbox=sandbox,\r\n                host=host,\r\n                port=port,\r\n                **kwargs,\r\n            )\r\n        try:\r\n            instance = cls(config)\r\n            await instance.start()\r\n        except Exception:\r\n            time.sleep(0.15)\r\n            instance = cls(config)\r\n            await instance.start()\r\n        return instance\r\n\r\n    def __init__(self, config: Config, **kwargs):\r\n        \"\"\"\r\n        Constructor. To create a instance, use :py:meth:`Browser.create(...)`\r\n        :param config:\r\n        \"\"\"\r\n        try:\r\n            asyncio.get_running_loop()\r\n        except RuntimeError:\r\n            raise RuntimeError(\r\n                \"{0} objects of this class are created \"\r\n                \"using await {0}.create()\".format(\r\n                    self.__class__.__name__\r\n                )\r\n            )\r\n        self.config = config\r\n        self.targets: List = []\r\n        self.info = None\r\n        self._target = None\r\n        self._process = None\r\n        self._process_pid = None\r\n        self._keep_user_data_dir = None\r\n        self._is_updating = asyncio.Event()\r\n        self.connection: Connection = None\r\n        logger.debug(\"Session object initialized: %s\" % vars(self))\r\n\r\n    @property\r\n    def websocket_url(self):\r\n        return self.info.webSocketDebuggerUrl\r\n\r\n    @property\r\n    def main_tab(self) -> tab.Tab:\r\n        \"\"\"Returns the target which was launched with the browser.\"\"\"\r\n        return sorted(\r\n            self.targets, key=lambda x: x.type_ == \"page\", reverse=True\r\n        )[0]\r\n\r\n    @property\r\n    def tabs(self) -> List[tab.Tab]:\r\n        \"\"\"Returns the current targets which are of type \"page\".\"\"\"\r\n        tabs = filter(lambda item: item.type_ == \"page\", self.targets)\r\n        return list(tabs)\r\n\r\n    @property\r\n    def cookies(self) -> CookieJar:\r\n        if not self._cookies:\r\n            self._cookies = CookieJar(self)\r\n        return self._cookies\r\n\r\n    @property\r\n    def stopped(self):\r\n        if self._process and self._process.returncode is None:\r\n            return False\r\n        return True\r\n\r\n    async def wait(self, time: Union[float, int] = 1) -> Browser:\r\n        \"\"\"Wait for <time> seconds. Important to use,\r\n        especially in between page navigation.\r\n        :param time:\r\n        \"\"\"\r\n        return await asyncio.sleep(time, result=self)\r\n\r\n    sleep = wait\r\n    \"\"\"Alias for wait\"\"\"\r\n    async def _handle_target_update(\r\n        self,\r\n        event: Union[\r\n            cdp.target.TargetInfoChanged,\r\n            cdp.target.TargetDestroyed,\r\n            cdp.target.TargetCreated,\r\n            cdp.target.TargetCrashed,\r\n        ],\r\n    ):\r\n        \"\"\"This is an internal handler which updates the targets\r\n        when Chrome emits the corresponding event.\"\"\"\r\n        if isinstance(event, cdp.target.TargetInfoChanged):\r\n            target_info = event.target_info\r\n            current_tab = next(\r\n                filter(\r\n                    lambda item: item.target_id == target_info.target_id,\r\n                    self.targets,\r\n                )\r\n            )\r\n            current_target = current_tab.target\r\n            if logger.getEffectiveLevel() <= 10:\r\n                changes = util.compare_target_info(\r\n                    current_target, target_info\r\n                )\r\n                changes_string = \"\"\r\n                for change in changes:\r\n                    key, old, new = change\r\n                    changes_string += f\"\\n{key}: {old} => {new}\\n\"\r\n                logger.debug(\r\n                    \"Target #%d has changed: %s\"\r\n                    % (self.targets.index(current_tab), changes_string)\r\n                )\r\n                current_tab.target = target_info\r\n        elif isinstance(event, cdp.target.TargetCreated):\r\n            target_info: cdp.target.TargetInfo = event.target_info\r\n            websocket_url = (\r\n                f\"ws://{self.config.host}:{self.config.port}\"\r\n                f\"/devtools/{target_info.type_ or 'page'}\"\r\n                f\"/{target_info.target_id}\"\r\n            )\r\n            async with tab.Tab(\r\n                websocket_url=websocket_url,\r\n                target=target_info,\r\n                browser=self\r\n            ) as new_target:\r\n                self.targets.append(new_target)\r\n                logger.debug(\r\n                    \"Target #%d created => %s\"\r\n                    % (len(self.targets), new_target)\r\n                )\r\n        elif isinstance(event, cdp.target.TargetDestroyed):\r\n            current_tab = next(\r\n                filter(\r\n                    lambda item: item.target_id == event.target_id,\r\n                    self.targets,\r\n                )\r\n            )\r\n            logger.debug(\r\n                \"Target removed. id # %d => %s\"\r\n                % (self.targets.index(current_tab), current_tab)\r\n            )\r\n            self.targets.remove(current_tab)\r\n\r\n    def get_rd_host(self):\r\n        return self.config.host\r\n\r\n    def get_rd_port(self):\r\n        return self.config.port\r\n\r\n    def get_rd_url(self):\r\n        \"\"\"Returns the remote-debugging URL, which is used for\r\n        allowing the Playwright integration to launch stealthy.\r\n        Also sets an environment variable to hide this warning:\r\n        Deprecation: \"url.parse() behavior is not standardized\".\r\n        (github.com/microsoft/playwright-python/issues/3016)\"\"\"\r\n        os.environ[\"NODE_NO_WARNINGS\"] = \"1\"\r\n        host = self.config.host\r\n        port = self.config.port\r\n        return f\"http://{host}:{port}\"\r\n\r\n    def get_endpoint_url(self):\r\n        return self.get_rd_url()\r\n\r\n    def get_port(self):\r\n        return self.get_rd_port()\r\n\r\n    async def set_auth(self, username, password, tab):\r\n        async def auth_challenge_handler(event: cdp.fetch.AuthRequired):\r\n            await tab.send(\r\n                cdp.fetch.continue_with_auth(\r\n                    request_id=event.request_id,\r\n                    auth_challenge_response=cdp.fetch.AuthChallengeResponse(\r\n                        response=\"ProvideCredentials\",\r\n                        username=username,\r\n                        password=password,\r\n                    ),\r\n                )\r\n            )\r\n\r\n        async def req_paused(event: cdp.fetch.RequestPaused):\r\n            await tab.send(\r\n                cdp.fetch.continue_request(request_id=event.request_id)\r\n            )\r\n\r\n        tab.add_handler(\r\n            cdp.fetch.RequestPaused,\r\n            lambda event: asyncio.create_task(req_paused(event)),\r\n        )\r\n\r\n        tab.add_handler(\r\n            cdp.fetch.AuthRequired,\r\n            lambda event: asyncio.create_task(auth_challenge_handler(event)),\r\n        )\r\n\r\n        await tab.send(cdp.fetch.enable(handle_auth_requests=True))\r\n\r\n    async def get(\r\n        self,\r\n        url=\"about:blank\",\r\n        new_tab: bool = False,\r\n        new_window: bool = False,\r\n        **kwargs,\r\n    ) -> tab.Tab:\r\n        \"\"\"Top level get. Utilizes the first tab to retrieve given url.\r\n        Convenience function known from selenium.\r\n        This function detects when DOM events have fired during navigation.\r\n        :param url: The URL to navigate to\r\n        :param new_tab: Open new tab\r\n        :param new_window: Open new window\r\n        :return: Page\r\n        \"\"\"\r\n        await asyncio.sleep(0.005)\r\n        if url and \":\" not in url:\r\n            url = \"https://\" + url\r\n        if new_tab or new_window:\r\n            # Create new target using the browser session.\r\n            target_id = await self.connection.send(\r\n                cdp.target.create_target(\r\n                    url, new_window=new_window, enable_begin_frame_control=True\r\n                )\r\n            )\r\n            connection: tab.Tab = next(\r\n                filter(\r\n                    lambda item: (\r\n                        item.type_ == \"page\" and item.target_id == target_id\r\n                    ),\r\n                    self.targets,\r\n                )\r\n            )\r\n            connection.browser = self\r\n        else:\r\n            try:\r\n                # Most recently opened tab\r\n                connection = self.targets[-1]\r\n                await connection.sleep(0.005)\r\n            except Exception:\r\n                # First tab from browser.tabs\r\n                connection: tab.Tab = next(\r\n                    filter(lambda item: item.type_ == \"page\", self.targets)\r\n                )\r\n                await connection.sleep(0.005)\r\n        _cdp_timezone = None\r\n        _cdp_user_agent = \"\"\r\n        _cdp_locale = None\r\n        _cdp_platform = None\r\n        _cdp_disable_csp = None\r\n        _cdp_geolocation = None\r\n        _cdp_mobile_mode = None\r\n        _cdp_recorder = None\r\n        _cdp_ad_block = None\r\n        if getattr(sb_config, \"_cdp_timezone\", None):\r\n            _cdp_timezone = sb_config._cdp_timezone\r\n        if getattr(sb_config, \"_cdp_user_agent\", None):\r\n            _cdp_user_agent = sb_config._cdp_user_agent\r\n        if getattr(sb_config, \"_cdp_locale\", None):\r\n            _cdp_locale = sb_config._cdp_locale\r\n        if getattr(sb_config, \"_cdp_platform\", None):\r\n            _cdp_platform = sb_config._cdp_platform\r\n        if getattr(sb_config, \"_cdp_geolocation\", None):\r\n            _cdp_geolocation = sb_config._cdp_geolocation\r\n        if getattr(sb_config, \"_cdp_mobile_mode\", None):\r\n            _cdp_mobile_mode = sb_config._cdp_mobile_mode\r\n        if getattr(sb_config, \"ad_block_on\", None):\r\n            _cdp_ad_block = sb_config.ad_block_on\r\n        if getattr(sb_config, \"disable_csp\", None):\r\n            _cdp_disable_csp = sb_config.disable_csp\r\n        if \"timezone\" in kwargs:\r\n            _cdp_timezone = kwargs[\"timezone\"]\r\n        elif \"tzone\" in kwargs:\r\n            _cdp_timezone = kwargs[\"tzone\"]\r\n        if \"user_agent\" in kwargs:\r\n            _cdp_user_agent = kwargs[\"user_agent\"]\r\n        elif \"agent\" in kwargs:\r\n            _cdp_user_agent = kwargs[\"agent\"]\r\n        if \"locale\" in kwargs:\r\n            _cdp_locale = kwargs[\"locale\"]\r\n        elif \"lang\" in kwargs:\r\n            _cdp_locale = kwargs[\"lang\"]\r\n        elif \"locale_code\" in kwargs:\r\n            _cdp_locale = kwargs[\"locale_code\"]\r\n        if \"platform\" in kwargs:\r\n            _cdp_platform = kwargs[\"platform\"]\r\n        elif \"plat\" in kwargs:\r\n            _cdp_platform = kwargs[\"plat\"]\r\n        if \"disable_csp\" in kwargs:\r\n            _cdp_disable_csp = kwargs[\"disable_csp\"]\r\n        if \"geolocation\" in kwargs:\r\n            _cdp_geolocation = kwargs[\"geolocation\"]\r\n        elif \"geoloc\" in kwargs:\r\n            _cdp_geolocation = kwargs[\"geoloc\"]\r\n        if \"ad_block\" in kwargs:\r\n            _cdp_ad_block = kwargs[\"ad_block\"]\r\n        if \"mobile\" in kwargs:\r\n            _cdp_mobile_mode = kwargs[\"mobile\"]\r\n        if \"recorder\" in kwargs:\r\n            _cdp_recorder = kwargs[\"recorder\"]\r\n        await connection.sleep(0.01)\r\n        await connection.send(cdp.network.enable())\r\n        await connection.sleep(0.01)\r\n        if _cdp_timezone:\r\n            await connection.set_timezone(_cdp_timezone)\r\n        if _cdp_locale:\r\n            await connection.set_locale(_cdp_locale)\r\n        if _cdp_user_agent or _cdp_locale or _cdp_platform:\r\n            await connection.set_user_agent(\r\n                user_agent=_cdp_user_agent,\r\n                accept_language=_cdp_locale,\r\n                platform=_cdp_platform,\r\n            )\r\n        if _cdp_ad_block:\r\n            await connection.send(cdp.network.set_blocked_urls(\r\n                urls=[\r\n                    \"*.cloudflareinsights.com*\",\r\n                    \"*.googlesyndication.com*\",\r\n                    \"*.googletagmanager.com*\",\r\n                    \"*.google-analytics.com*\",\r\n                    \"*.amazon-adsystem.com*\",\r\n                    \"*.adsafeprotected.com*\",\r\n                    \"*.ads.linkedin.com*\",\r\n                    \"*.casalemedia.com*\",\r\n                    \"*.doubleclick.net*\",\r\n                    \"*.admanmedia.com*\",\r\n                    \"*.quantserve.com*\",\r\n                    \"*.fastclick.net*\",\r\n                    \"*.snigelweb.com*\",\r\n                    \"*.bidswitch.net*\",\r\n                    \"*.360yield.com*\",\r\n                    \"*.adthrive.com*\",\r\n                    \"*.pubmatic.com*\",\r\n                    \"*.id5-sync.com*\",\r\n                    \"*.moatads.com*\",\r\n                    \"*.dotomi.com*\",\r\n                    \"*.adsrvr.org*\",\r\n                    \"*.atmtd.com*\",\r\n                    \"*.liadm.com*\",\r\n                    \"*.loopme.me*\",\r\n                    \"*.adnxs.com*\",\r\n                    \"*.openx.net*\",\r\n                    \"*.tapad.com*\",\r\n                    \"*.3lift.com*\",\r\n                    \"*.turn.com*\",\r\n                    \"*.2mdn.net*\",\r\n                    \"*.cpx.to*\",\r\n                    \"*.ad.gt*\",\r\n                ]\r\n            ))\r\n        if _cdp_geolocation:\r\n            await connection.set_geolocation(_cdp_geolocation)\r\n        if _cdp_disable_csp:\r\n            await connection.send(cdp.page.set_bypass_csp(enabled=True))\r\n        if _cdp_mobile_mode:\r\n            await connection.send(\r\n                cdp.emulation.set_device_metrics_override(\r\n                    width=412, height=732, device_scale_factor=3, mobile=True\r\n                )\r\n            )\r\n        # (The code below is for the Chrome 142 extension fix)\r\n        if (\r\n            getattr(sb_config, \"_cdp_proxy\", None)\r\n            and \"@\" in sb_config._cdp_proxy\r\n            and \"auth\" not in kwargs\r\n        ):\r\n            username_and_password = sb_config._cdp_proxy.split(\"@\")[0]\r\n            proxy_user = username_and_password.split(\":\")[0]\r\n            proxy_pass = username_and_password.split(\":\")[1]\r\n            if (\r\n                hasattr(self.main_tab, \"_last_auth\")\r\n                and self.main_tab._last_auth == username_and_password\r\n            ):\r\n                pass  # Auth was already set\r\n            else:\r\n                self.main_tab._last_auth = username_and_password\r\n                await self.set_auth(proxy_user, proxy_pass, self.tabs[0])\r\n                time.sleep(0.25)\r\n        if \"auth\" in kwargs and kwargs[\"auth\"] and \":\" in kwargs[\"auth\"]:\r\n            username_and_password = kwargs[\"auth\"]\r\n            proxy_user = username_and_password.split(\":\")[0]\r\n            proxy_pass = username_and_password.split(\":\")[1]\r\n            if (\r\n                hasattr(self.main_tab, \"_last_auth\")\r\n                and self.main_tab._last_auth == username_and_password\r\n            ):\r\n                pass  # Auth was already set\r\n            else:\r\n                self.main_tab._last_auth = username_and_password\r\n                await self.set_auth(proxy_user, proxy_pass, self.tabs[0])\r\n                time.sleep(0.25)\r\n        await connection.sleep(0.15)\r\n        frame_id, loader_id, *_ = await connection.send(\r\n            cdp.page.navigate(url)\r\n        )\r\n        major_browser_version = None\r\n        try:\r\n            major_browser_version = (\r\n                int(self.info[\"Browser\"].split(\"/\")[-1].split(\".\")[0])\r\n            )\r\n        except Exception:\r\n            pass\r\n        if (\r\n            _cdp_recorder\r\n            and (\r\n                not hasattr(sb_config, \"browser\")\r\n                or (\r\n                    sb_config.browser == \"chrome\"\r\n                    and (\r\n                        not major_browser_version\r\n                        or major_browser_version >= 142\r\n                    )\r\n                )\r\n            )\r\n        ):\r\n            # (The code below is for the Chrome 142 extension fix)\r\n            from seleniumbase.js_code.recorder_js import recorder_js\r\n            recorder_code = (\r\n                \"\"\"window.onload = function() { %s };\"\"\" % recorder_js\r\n            )\r\n            await connection.send(\r\n                cdp.page.add_script_to_evaluate_on_new_document(recorder_code)\r\n            )\r\n            await connection.sleep(0.25)\r\n            await self.wait(0.05)\r\n            await connection.send(cdp.runtime.evaluate(recorder_js))\r\n        # Update the frame_id on the tab\r\n        connection.frame_id = frame_id\r\n        connection.browser = self\r\n        # Give settings time to take effect\r\n        await connection.sleep(0.25)\r\n        await self.wait(0.05)\r\n        return connection\r\n\r\n    async def start(self=None) -> Browser:\r\n        \"\"\"Launches the actual browser.\"\"\"\r\n        if not self:\r\n            warnings.warn(\r\n                \"Use ``await Browser.create()`` to create a new instance!\"\r\n            )\r\n            return\r\n        if self._process or self._process_pid:\r\n            if self._process.returncode is not None:\r\n                return await self.create(config=self.config)\r\n            warnings.warn(\r\n                \"Ignored! This call has no effect when already running!\"\r\n            )\r\n            return\r\n        # self.config.update(kwargs)\r\n        connect_existing = False\r\n        if self.config.host is not None and self.config.port is not None:\r\n            connect_existing = True\r\n        else:\r\n            self.config.host = \"127.0.0.1\"\r\n            self.config.port = util.free_port()\r\n        if not connect_existing:\r\n            logger.debug(\r\n                \"BROWSER EXECUTABLE PATH: %s\"\r\n                % self.config.browser_executable_path,\r\n            )\r\n            if not pathlib.Path(self.config.browser_executable_path).exists():\r\n                raise FileNotFoundError(\r\n                    (\r\n                        \"\"\"\r\n                    ---------------------------------------\r\n                    Could not determine browser executable.\r\n                    ---------------------------------------\r\n                    Browser must be installed in the default location / path!\r\n                    If you are sure about the browser executable,\r\n                    set it using `browser_executable_path='{}` parameter.\"\"\"\r\n                    ).format(\r\n                        \"/path/to/your/browser/executable\"\r\n                        if is_posix\r\n                        else \"c:/path/to/your/browser.exe\"\r\n                    )\r\n                )\r\n        if getattr(self.config, \"_extensions\", None):\r\n            self.config.add_argument(\r\n                \"--load-extension=%s\"\r\n                % \",\".join(str(_) for _ in self.config._extensions)\r\n            )\r\n        exe = self.config.browser_executable_path\r\n        params = self.config()\r\n        logger.debug(\r\n            \"Starting\\n\\texecutable :%s\\n\\narguments:\\n%s\",\r\n            exe,\r\n            \"\\n\\t\".join(params),\r\n        )\r\n        if not connect_existing:\r\n            self._process: asyncio.subprocess.Process = (\r\n                await asyncio.create_subprocess_exec(\r\n                    exe,\r\n                    *params,\r\n                    stdin=asyncio.subprocess.PIPE,\r\n                    stdout=asyncio.subprocess.PIPE,\r\n                    stderr=asyncio.subprocess.PIPE,\r\n                    close_fds=is_posix,\r\n                )\r\n            )\r\n            await asyncio.sleep(0.05)\r\n            self._process_pid = self._process.pid\r\n        await asyncio.sleep(0.05)\r\n        self._http = HTTPApi((self.config.host, self.config.port))\r\n        await asyncio.sleep(0.05)\r\n        get_registered_instances().add(self)\r\n        await asyncio.sleep(0.15)\r\n        max_attempts = 20\r\n        for attempt in range(max_attempts):\r\n            try:\r\n                self.info = ContraDict(\r\n                    await self._http.get(\"version\"), silent=True\r\n                )\r\n            except (Exception,):\r\n                if attempt == max_attempts - 1:\r\n                    logger.debug(\"Could not start\", exc_info=True)\r\n                else:\r\n                    await self.sleep(0.2)\r\n            else:\r\n                break\r\n        if not self.info:\r\n            chromium = \"Chromium\"\r\n            if getattr(sb_config, \"_cdp_browser\", None):\r\n                chromium = sb_config._cdp_browser\r\n                chromium = chromium[0].upper() + chromium[1:]\r\n            message = \"Failed to connect to the browser\"\r\n            if not exe or not os.path.exists(exe):\r\n                message = (\r\n                    \"%s executable not found. Is it installed?\" % chromium\r\n                )\r\n            dash_len = len(message)\r\n            dashes = \"-\" * dash_len\r\n            raise Exception(\r\n                \"\"\"\r\n                %s\r\n                %s\r\n                %s\r\n                \"\"\" % (dashes, message, dashes)\r\n            )\r\n        await asyncio.sleep(0.03)\r\n        self.connection = Connection(\r\n            self.info.webSocketDebuggerUrl, browser=self\r\n        )\r\n        await asyncio.sleep(0.03)\r\n        if self.config.autodiscover_targets:\r\n            logger.debug(\"Enabling autodiscover targets\")\r\n            self.connection.handlers[cdp.target.TargetInfoChanged] = [\r\n                self._handle_target_update\r\n            ]\r\n            self.connection.handlers[cdp.target.TargetCreated] = [\r\n                self._handle_target_update\r\n            ]\r\n            self.connection.handlers[cdp.target.TargetDestroyed] = [\r\n                self._handle_target_update\r\n            ]\r\n            self.connection.handlers[cdp.target.TargetCrashed] = [\r\n                self._handle_target_update\r\n            ]\r\n            await self.connection.send(\r\n                cdp.target.set_discover_targets(discover=True)\r\n            )\r\n        await self\r\n        # self.connection.handlers[cdp.inspector.Detached] = [self.stop]\r\n        # return self\r\n\r\n    async def grant_permissions(\r\n        self,\r\n        permissions: List[str] | str,\r\n        origin: Optional[str] = None,\r\n    ):\r\n        \"\"\"Grant specific permissions to the current window.\r\n        Applies to all origins if no origin is specified.\"\"\"\r\n        if isinstance(permissions, str):\r\n            permissions = [permissions]\r\n        await self.connection.send(\r\n            cdp.browser.grant_permissions(permissions, origin)\r\n        )\r\n\r\n    async def grant_all_permissions(self):\r\n        \"\"\"\r\n        Grant permissions for:\r\n            audioCapture\r\n            backgroundSync\r\n            backgroundFetch\r\n            clipboardReadWrite\r\n            clipboardSanitizedWrite\r\n            displayCapture\r\n            durableStorage\r\n            geolocation\r\n            idleDetection\r\n            localFonts\r\n            midi\r\n            midiSysex\r\n            nfc\r\n            notifications\r\n            paymentHandler\r\n            periodicBackgroundSync\r\n            sensors\r\n            storageAccess\r\n            topLevelStorageAccess\r\n            videoCapture\r\n            wakeLockScreen\r\n            wakeLockSystem\r\n            windowManagement\r\n        \"\"\"\r\n        permissions = [\r\n            \"audioCapture\",\r\n            \"backgroundSync\",\r\n            \"backgroundFetch\",\r\n            \"clipboardReadWrite\",\r\n            \"clipboardSanitizedWrite\",\r\n            \"displayCapture\",\r\n            \"durableStorage\",\r\n            \"geolocation\",\r\n            \"idleDetection\",\r\n            \"localFonts\",\r\n            \"midi\",\r\n            \"midiSysex\",\r\n            \"nfc\",\r\n            \"notifications\",\r\n            \"paymentHandler\",\r\n            \"periodicBackgroundSync\",\r\n            \"sensors\",\r\n            \"storageAccess\",\r\n            \"topLevelStorageAccess\",\r\n            \"videoCapture\",\r\n            \"wakeLockScreen\",\r\n            \"wakeLockSystem\",\r\n            \"windowManagement\",\r\n        ]\r\n        await self.connection.send(cdp.browser.grant_permissions(permissions))\r\n\r\n    async def reset_permissions(self):\r\n        \"\"\"Reset permissions for all origins on the current window.\"\"\"\r\n        await self.connection.send(cdp.browser.reset_permissions())\r\n\r\n    async def tile_windows(self, windows=None, max_columns: int = 0):\r\n        import math\r\n        try:\r\n            import mss\r\n        except Exception:\r\n            pip_find_lock = fasteners.InterProcessLock(\r\n                constants.PipInstall.FINDLOCK\r\n            )\r\n            with pip_find_lock:  # Prevent issues with multiple processes\r\n                shared_utils.pip_install(\"mss\")\r\n            import mss\r\n        m = mss.mss()\r\n        screen, screen_width, screen_height = 3 * (None,)\r\n        if m.monitors and len(m.monitors) >= 1:\r\n            screen = m.monitors[0]\r\n            screen_width = screen[\"width\"]\r\n            screen_height = screen[\"height\"]\r\n        if not screen or not screen_width or not screen_height:\r\n            warnings.warn(\"No monitors detected!\")\r\n            return\r\n        await self\r\n        distinct_windows = defaultdict(list)\r\n        if windows:\r\n            tabs = windows\r\n        else:\r\n            tabs = self.tabs\r\n        for _tab in tabs:\r\n            window_id, bounds = await _tab.get_window()\r\n            distinct_windows[window_id].append(_tab)\r\n        num_windows = len(distinct_windows)\r\n        req_cols = max_columns or int(num_windows * (19 / 6))\r\n        req_rows = int(num_windows / req_cols)\r\n        while req_cols * req_rows < num_windows:\r\n            req_rows += 1\r\n        box_w = math.floor((screen_width / req_cols) - 1)\r\n        box_h = math.floor(screen_height / req_rows)\r\n        distinct_windows_iter = iter(distinct_windows.values())\r\n        grid = []\r\n        for x in range(req_cols):\r\n            for y in range(req_rows):\r\n                try:\r\n                    tabs = next(distinct_windows_iter)\r\n                except StopIteration:\r\n                    continue\r\n                if not tabs:\r\n                    continue\r\n                tab = tabs[0]\r\n                try:\r\n                    pos = [x * box_w, y * box_h, box_w, box_h]\r\n                    grid.append(pos)\r\n                    await tab.set_window_size(*pos)\r\n                except Exception:\r\n                    logger.info(\r\n                        \"Could not set window size. Exception => \",\r\n                        exc_info=True,\r\n                    )\r\n                    continue\r\n        return grid\r\n\r\n    async def _get_targets(self) -> List[cdp.target.TargetInfo]:\r\n        info = await self.connection.send(\r\n            cdp.target.get_targets(), _is_update=True\r\n        )\r\n        return info\r\n\r\n    async def update_targets(self):\r\n        targets: List[cdp.target.TargetInfo]\r\n        targets = await self._get_targets()\r\n        for t in targets:\r\n            for existing_tab in self.targets:\r\n                existing_target = existing_tab.target\r\n                if existing_target.target_id == t.target_id:\r\n                    existing_tab.target.__dict__.update(t.__dict__)\r\n                    break\r\n            else:\r\n                self.targets.append(\r\n                    Connection(\r\n                        (\r\n                            f\"ws://{self.config.host}:{self.config.port}\"\r\n                            f\"/devtools/page\"  # All types are \"page\"\r\n                            f\"/{t.target_id}\"\r\n                        ),\r\n                        target=t,\r\n                        browser=self,\r\n                    )\r\n                )\r\n        await asyncio.sleep(0)\r\n\r\n    async def __aenter__(self):\r\n        return self\r\n\r\n    async def __aexit__(self, exc_type, exc_val, exc_tb):\r\n        if exc_type and exc_val:\r\n            raise exc_type(exc_val)\r\n\r\n    def __iter__(self):\r\n        self._i = self.tabs.index(self.main_tab)\r\n        return self\r\n\r\n    def __reversed__(self):\r\n        return reversed(list(self.tabs))\r\n\r\n    def __next__(self):\r\n        try:\r\n            return self.tabs[self._i]\r\n        except IndexError:\r\n            del self._i\r\n            raise StopIteration\r\n        except AttributeError:\r\n            del self._i\r\n            raise StopIteration\r\n        finally:\r\n            if hasattr(self, \"_i\"):\r\n                if self._i != len(self.tabs):\r\n                    self._i += 1\r\n                else:\r\n                    del self._i\r\n\r\n    def stop(self, deconstruct=False):\r\n        if (\r\n            not hasattr(sb_config, \"_closed_connection_ids\")\r\n            or not isinstance(sb_config._closed_connection_ids, list)\r\n        ):\r\n            sb_config._closed_connection_ids = []\r\n        connection_id = None\r\n        with suppress(Exception):\r\n            connection_id = self.connection.websocket.id.hex\r\n        close_success = False\r\n        try:\r\n            if self.connection:\r\n                asyncio.get_event_loop().create_task(self.connection.aclose())\r\n                logger.debug(\r\n                    \"Closed connection using get_event_loop().create_task()\"\r\n                )\r\n        except RuntimeError:\r\n            if self.connection:\r\n                try:\r\n                    asyncio.run(self.connection.aclose())\r\n                    logger.debug(\"Closed the connection using asyncio.run()\")\r\n                except Exception:\r\n                    pass\r\n        for _ in range(3):\r\n            try:\r\n                if connection_id not in sb_config._closed_connection_ids:\r\n                    self._process.terminate()\r\n                    logger.debug(\r\n                        \"Terminated browser with pid %d successfully.\"\r\n                        % self._process.pid\r\n                    )\r\n                    if connection_id:\r\n                        sb_config._closed_connection_ids.append(connection_id)\r\n                        close_success = True\r\n                    break\r\n            except (Exception,):\r\n                try:\r\n                    self._process.kill()\r\n                    logger.debug(\r\n                        \"Killed browser with pid %d successfully.\"\r\n                        % self._process.pid\r\n                    )\r\n                    break\r\n                except (Exception,):\r\n                    try:\r\n                        if hasattr(self, \"browser_process_pid\"):\r\n                            os.kill(self._process_pid, 15)\r\n                            logger.debug(\r\n                                \"Killed browser with pid %d \"\r\n                                \"using signal 15 successfully.\"\r\n                                % self._process.pid\r\n                            )\r\n                            break\r\n                    except (TypeError,):\r\n                        logger.info(\"TypeError\", exc_info=True)\r\n                        pass\r\n                    except (PermissionError,):\r\n                        logger.info(\r\n                            \"Browser already stopped, \"\r\n                            \"or no permission to kill. Skip.\"\r\n                        )\r\n                        pass\r\n                    except (ProcessLookupError,):\r\n                        logger.info(\"ProcessLookupError\")\r\n                        pass\r\n                    except (Exception,):\r\n                        raise\r\n            self._process = None\r\n            self._process_pid = None\r\n        if (\r\n            hasattr(sb_config, \"_xvfb_users\")\r\n            and isinstance(sb_config._xvfb_users, int)\r\n            and close_success\r\n            and hasattr(sb_config, \"_virtual_display\")\r\n            and sb_config._virtual_display\r\n        ):\r\n            sb_config._xvfb_users -= 1\r\n            if sb_config._xvfb_users < 0:\r\n                sb_config._xvfb_users = 0\r\n        if (\r\n            shared_utils.is_linux()\r\n            and (\r\n                hasattr(sb_config, \"_virtual_display\")\r\n                and sb_config._virtual_display\r\n                and hasattr(sb_config._virtual_display, \"stop\")\r\n            )\r\n            and sb_config._xvfb_users == 0\r\n        ):\r\n            try:\r\n                sb_config._virtual_display.stop()\r\n                sb_config._virtual_display = None\r\n                sb_config.headless_active = False\r\n            except AttributeError:\r\n                pass\r\n            except Exception:\r\n                pass\r\n        if (\r\n            deconstruct\r\n            and connection_id\r\n            and connection_id in sb_config._closed_connection_ids\r\n        ):\r\n            sb_config._closed_connection_ids.remove(connection_id)\r\n\r\n    def quit(self):\r\n        self.stop()\r\n\r\n    def __await__(self):\r\n        # return ( asyncio.sleep(0)).__await__()\r\n        return self.update_targets().__await__()\r\n\r\n    def __del__(self):\r\n        pass\r\n\r\n\r\n__registered__instances__: Set[Browser] = set()\r\n\r\n\r\nclass CookieJar:\r\n    def __init__(self, browser: Browser):\r\n        self._browser = browser\r\n\r\n    async def get_all(\r\n        self, requests_cookie_format: bool = False\r\n    ) -> List[Union[cdp.network.Cookie, \"http.cookiejar.Cookie\"]]:\r\n        \"\"\"\r\n        Get all cookies.\r\n        :param requests_cookie_format: when True,\r\n         returns python http.cookiejar.Cookie objects,\r\n         compatible with requests library and many others.\r\n        :type requests_cookie_format: bool\r\n        \"\"\"\r\n        connection = None\r\n        for _tab in self._browser.tabs:\r\n            if getattr(_tab, \"closed\", None):\r\n                continue\r\n            connection = _tab\r\n            break\r\n        else:\r\n            connection = self._browser.connection\r\n        cookies = await connection.send(cdp.network.get_cookies())\r\n        if requests_cookie_format:\r\n            import requests.cookies\r\n\r\n            return [\r\n                requests.cookies.create_cookie(\r\n                    name=c.name,\r\n                    value=c.value,\r\n                    domain=c.domain,\r\n                    path=c.path,\r\n                    expires=c.expires,\r\n                    secure=c.secure,\r\n                )\r\n                for c in cookies\r\n            ]\r\n        return cookies\r\n\r\n    async def set_all(self, cookies: List[cdp.network.CookieParam]):\r\n        \"\"\"\r\n        Set cookies.\r\n        :param cookies: List of cookies\r\n        \"\"\"\r\n        connection = None\r\n        for _tab in self._browser.tabs:\r\n            if getattr(_tab, \"closed\", None):\r\n                continue\r\n            connection = _tab\r\n            break\r\n        else:\r\n            connection = self._browser.connection\r\n        await connection.send(cdp.network.set_cookies(cookies))\r\n\r\n    async def save(self, file: PathLike = \".session.dat\", pattern: str = \".*\"):\r\n        \"\"\"\r\n        Save all cookies (or a subset, controlled by `pattern`)\r\n        to a file to be restored later.\r\n        :param file:\r\n        :param pattern: regex style pattern string.\r\n                any cookie that has a  domain, key or value field\r\n                which matches the pattern will be included.\r\n            default = \".*\"  (all)\r\n            Eg: the pattern \"(cf|.com|nowsecure)\" will include cookies which:\r\n                - Have a string \"cf\" (cloudflare)\r\n                - Have \".com\" in them, in either domain, key or value field.\r\n                - Contain \"nowsecure\"\r\n        :type pattern: str\r\n        \"\"\"\r\n        pattern = re.compile(pattern)\r\n        save_path = pathlib.Path(file).resolve()\r\n        connection = None\r\n        for _tab in self._browser.tabs:\r\n            if getattr(_tab, \"closed\", None):\r\n                continue\r\n            connection = _tab\r\n            break\r\n        else:\r\n            connection = self._browser.connection\r\n        cookies = await connection.send(cdp.network.get_cookies())\r\n        # if not connection:\r\n        #     return\r\n        # if not connection.websocket:\r\n        #     return\r\n        # if connection.websocket.closed:\r\n        #     return\r\n        cookies = await self.get_all(requests_cookie_format=False)\r\n        included_cookies = []\r\n        for cookie in cookies:\r\n            for match in pattern.finditer(str(cookie.__dict__)):\r\n                logger.debug(\r\n                    \"Saved cookie for matching pattern '%s' => (%s: %s)\"\r\n                    % (pattern.pattern, cookie.name, cookie.value)\r\n                )\r\n                included_cookies.append(cookie)\r\n                break\r\n        pickle.dump(cookies, save_path.open(\"w+b\"))\r\n\r\n    async def load(self, file: PathLike = \".session.dat\", pattern: str = \".*\"):\r\n        \"\"\"\r\n        Load all cookies (or a subset, controlled by `pattern`)\r\n        from a file created by :py:meth:`~save_cookies`.\r\n        :param file:\r\n        :param pattern: Regex style pattern string.\r\n               Any cookie that has a  domain, key,\r\n               or value field which matches the pattern will be included.\r\n            Default = \".*\"  (all)\r\n            Eg: the pattern \"(cf|.com|nowsecure)\" will include cookies which:\r\n                - Have a string \"cf\" (cloudflare)\r\n                - Have \".com\" in them, in either domain, key or value field.\r\n                - Contain \"nowsecure\"\r\n        :type pattern: str\r\n        \"\"\"\r\n        pattern = re.compile(pattern)\r\n        save_path = pathlib.Path(file).resolve()\r\n        cookies = pickle.load(save_path.open(\"r+b\"))\r\n        included_cookies = []\r\n        connection = None\r\n        for _tab in self._browser.tabs:\r\n            if getattr(_tab, \"closed\", None):\r\n                continue\r\n            connection = _tab\r\n            break\r\n        else:\r\n            connection = self._browser.connection\r\n        for cookie in cookies:\r\n            for match in pattern.finditer(str(cookie.__dict__)):\r\n                included_cookies.append(cookie)\r\n                logger.debug(\r\n                    \"Loaded cookie for matching pattern '%s' => (%s: %s)\"\r\n                    % (pattern.pattern, cookie.name, cookie.value)\r\n                )\r\n                break\r\n        await connection.send(cdp.network.set_cookies(included_cookies))\r\n\r\n    async def clear(self):\r\n        \"\"\"\r\n        Clear current cookies.\r\n        Note: This includes all open tabs/windows for this browser.\r\n        \"\"\"\r\n        connection = None\r\n        for _tab in self._browser.tabs:\r\n            if getattr(_tab, \"closed\", None):\r\n                continue\r\n            connection = _tab\r\n            break\r\n        else:\r\n            connection = self._browser.connection\r\n        cookies = await connection.send(cdp.network.get_cookies())\r\n        if cookies:\r\n            await connection.send(cdp.storage.clear_cookies())\r\n\r\n\r\nclass HTTPApi:\r\n    def __init__(self, addr: Tuple[str, int]):\r\n        self.host, self.port = addr\r\n        self.api = \"http://%s:%d\" % (self.host, self.port)\r\n\r\n    @classmethod\r\n    def from_target(cls, target):\r\n        ws_url = urllib.parse.urlparse(target.websocket_url)\r\n        inst = cls((ws_url.hostname, ws_url.port))\r\n        return inst\r\n\r\n    async def get(self, endpoint: str):\r\n        return await self._request(endpoint)\r\n\r\n    async def post(self, endpoint, data):\r\n        return await self._request(endpoint, data)\r\n\r\n    async def _request(self, endpoint, method: str = \"GET\", data: dict = None):\r\n        url = urllib.parse.urljoin(\r\n            self.api, f\"json/{endpoint}\" if endpoint else \"/json\"\r\n        )\r\n        if data and method.lower() == \"get\":\r\n            raise ValueError(\"GET requests cannot contain data\")\r\n        if not url:\r\n            url = self.api + endpoint\r\n        request = urllib.request.Request(url)\r\n        request.method = method\r\n        request.data = None\r\n        if data:\r\n            request.data = json.dumps(data).encode(\"utf-8\")\r\n\r\n        response = await asyncio.get_running_loop().run_in_executor(\r\n            None, lambda: urllib.request.urlopen(request, timeout=10)\r\n        )\r\n        return json.loads(response.read())\r\n\r\n\r\natexit.register(deconstruct_browser)\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/cdp_util.py",
    "content": "\"\"\"CDP-Driver is based on NoDriver\"\"\"\r\nfrom __future__ import annotations\r\nimport asyncio\r\nimport fasteners\r\nimport logging\r\nimport os\r\nimport sys\r\nimport time\r\nimport types\r\nimport typing\r\nfrom contextlib import suppress\r\nfrom seleniumbase import config as sb_config\r\nfrom seleniumbase import extensions\r\nfrom seleniumbase.config import settings\r\nfrom seleniumbase.core import detect_b_ver\r\nfrom seleniumbase.core import download_helper\r\nfrom seleniumbase.core import proxy_helper\r\nfrom seleniumbase.fixtures import constants\r\nfrom seleniumbase.fixtures import shared_utils\r\nfrom typing import Optional, List, Union, Callable\r\nfrom .element import Element\r\nfrom .browser import Browser\r\nfrom .browser import PathLike\r\nfrom .config import Config\r\nfrom .tab import Tab\r\nimport mycdp as cdp\r\n\r\nlogger = logging.getLogger(__name__)\r\nIS_LINUX = shared_utils.is_linux()\r\nDOWNLOADS_FOLDER = download_helper.get_downloads_folder()\r\nPROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK\r\nEXTENSIONS_DIR = os.path.dirname(os.path.realpath(extensions.__file__))\r\nAD_BLOCK_ZIP_PATH = os.path.join(EXTENSIONS_DIR, \"ad_block.zip\")\r\nT = typing.TypeVar(\"T\")\r\n\r\n\r\ndef __activate_standard_virtual_display():\r\n    from sbvirtualdisplay import Display\r\n    width = settings.HEADLESS_START_WIDTH\r\n    height = settings.HEADLESS_START_HEIGHT\r\n    with suppress(Exception):\r\n        _xvfb_display = Display(visible=0, size=(width, height))\r\n        _xvfb_display.start()\r\n        time.sleep(0.03)\r\n        sb_config._virtual_display = _xvfb_display\r\n        sb_config.headless_active = True\r\n\r\n\r\ndef __activate_virtual_display_as_needed(\r\n    headless, headed, xvfb, xvfb_metrics, override_display=False\r\n):\r\n    \"\"\"This is only needed on Linux.\"\"\"\r\n    reset_virtual_display = False\r\n    if IS_LINUX and (not headed or xvfb):\r\n        if (\r\n            not hasattr(sb_config, \"_closed_connection_ids\")\r\n            or not isinstance(sb_config._closed_connection_ids, list)\r\n        ):\r\n            sb_config._closed_connection_ids = []\r\n        if (\r\n            not hasattr(sb_config, \"_xvfb_users\")\r\n            or not isinstance(sb_config._xvfb_users, int)\r\n        ):\r\n            reset_virtual_display = True\r\n            sb_config._xvfb_users = 0\r\n        sb_config._xvfb_users += 1\r\n    if (\r\n        IS_LINUX\r\n        and (not headed or xvfb)\r\n        and (\r\n            not hasattr(sb_config, \"_virtual_display\")\r\n            or not sb_config._virtual_display\r\n            or reset_virtual_display\r\n            or override_display\r\n        )\r\n    ):\r\n        from sbvirtualdisplay import Display\r\n        pip_find_lock = fasteners.InterProcessLock(\r\n            constants.PipInstall.FINDLOCK\r\n        )\r\n        with pip_find_lock:  # Prevent issues with multiple processes\r\n            if not headless:\r\n                import Xlib.display\r\n                try:\r\n                    _xvfb_width = None\r\n                    _xvfb_height = None\r\n                    if xvfb_metrics:\r\n                        with suppress(Exception):\r\n                            metrics_string = xvfb_metrics\r\n                            metrics_string = metrics_string.replace(\" \", \"\")\r\n                            metrics_list = metrics_string.split(\",\")[0:2]\r\n                            _xvfb_width = int(metrics_list[0])\r\n                            _xvfb_height = int(metrics_list[1])\r\n                            # The minimum width,height is: 1024,768\r\n                            if _xvfb_width < 1024:\r\n                                _xvfb_width = 1024\r\n                            sb_config._xvfb_width = _xvfb_width\r\n                            if _xvfb_height < 768:\r\n                                _xvfb_height = 768\r\n                            sb_config._xvfb_height = _xvfb_height\r\n                            xvfb = True\r\n                    if not _xvfb_width:\r\n                        _xvfb_width = 1366\r\n                    if not _xvfb_height:\r\n                        _xvfb_height = 768\r\n                    _xvfb_display = Display(\r\n                        visible=True,\r\n                        size=(_xvfb_width, _xvfb_height),\r\n                        backend=\"xvfb\",\r\n                        use_xauth=True,\r\n                    )\r\n                    time.sleep(0.05)\r\n                    if \"--debug-display\" in sys.argv:\r\n                        print(\r\n                            \"Starting VDisplay from cdp_util: (%s, %s)\"\r\n                            % (_xvfb_width, _xvfb_height)\r\n                        )\r\n                    try:\r\n                        _xvfb_display.start()\r\n                    except Exception:\r\n                        time.sleep(0.03)\r\n                        _xvfb_display.start()\r\n                    time.sleep(0.03)\r\n                    if \"DISPLAY\" not in os.environ.keys():\r\n                        time.sleep(0.03)\r\n                        _xvfb_display.start()\r\n                        time.sleep(0.08)\r\n                        if \"DISPLAY\" not in os.environ.keys():\r\n                            print(\r\n                                \"\\n  X11 display failed! Is Xvfb installed? \"\r\n                                \"\\n  Try this: `sudo apt install -y xvfb`\"\r\n                            )\r\n                            __activate_standard_virtual_display()\r\n                    else:\r\n                        sb_config._virtual_display = _xvfb_display\r\n                        sb_config.headless_active = True\r\n                except Exception as e:\r\n                    if hasattr(e, \"msg\"):\r\n                        print(\"\\n\" + str(e.msg))\r\n                    else:\r\n                        print(e)\r\n                    print(\"\\nX11 display failed! Will use regular xvfb!\")\r\n                    __activate_standard_virtual_display()\r\n                    return\r\n                pyautogui_is_installed = False\r\n                try:\r\n                    import pyautogui\r\n                    with suppress(Exception):\r\n                        use_pyautogui_ver = constants.PyAutoGUI.VER\r\n                        u_pv = shared_utils.make_version_tuple(\r\n                            use_pyautogui_ver\r\n                        )\r\n                        pv = shared_utils.make_version_tuple(\r\n                            pyautogui.__version__\r\n                        )\r\n                        if pv < u_pv:\r\n                            del pyautogui  # To get newer ver\r\n                            shared_utils.pip_install(\r\n                                \"pyautogui\", version=\"Latest\"\r\n                            )\r\n                            import pyautogui\r\n                    pyautogui_is_installed = True\r\n                except Exception:\r\n                    message = (\r\n                        \"PyAutoGUI is required for CDP Mode on Linux! \"\r\n                        \"Installing now...\"\r\n                    )\r\n                    print(\"\\n\" + message)\r\n                    shared_utils.pip_install(\"pyautogui\", version=\"Latest\")\r\n                    import pyautogui\r\n                    pyautogui_is_installed = True\r\n                if (\r\n                    pyautogui_is_installed\r\n                    and hasattr(pyautogui, \"_pyautogui_x11\")\r\n                ):\r\n                    try:\r\n                        pyautogui._pyautogui_x11._display = (\r\n                            Xlib.display.Display(os.environ['DISPLAY'])\r\n                        )\r\n                        sb_config._pyautogui_x11_display = (\r\n                            pyautogui._pyautogui_x11._display\r\n                        )\r\n                    except Exception as e:\r\n                        if hasattr(e, \"msg\"):\r\n                            print(\"\\n\" + str(e.msg))\r\n                        else:\r\n                            print(e)\r\n            else:\r\n                __activate_standard_virtual_display()\r\n\r\n\r\ndef __set_proxy_filenames():\r\n    DOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER\r\n    for num in range(1000):\r\n        PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, \"proxy_ext_dir_%s\" % num)\r\n        if os.path.exists(PROXY_DIR_PATH):\r\n            continue\r\n        proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH\r\n        return\r\n    # Exceeded upper bound. Use Defaults:\r\n    PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, \"proxy_ext_dir\")\r\n    proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH\r\n\r\n\r\ndef __add_chrome_ext_dir(extension_dir, dir_path):\r\n    # Add dir_path to the existing extension_dir\r\n    option_exists = False\r\n    if extension_dir:\r\n        option_exists = True\r\n        extension_dir = \"%s,%s\" % (\r\n            extension_dir, os.path.realpath(dir_path)\r\n        )\r\n    if not option_exists:\r\n        extension_dir = os.path.realpath(dir_path)\r\n    return extension_dir\r\n\r\n\r\ndef __unzip_to_new_folder(zip_file, folder):\r\n    proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)\r\n    with proxy_dir_lock:\r\n        with suppress(Exception):\r\n            shared_utils.make_writable(PROXY_DIR_LOCK)\r\n        if not os.path.exists(folder):\r\n            import zipfile\r\n            zip_ref = zipfile.ZipFile(zip_file, \"r\")\r\n            os.makedirs(folder)\r\n            zip_ref.extractall(folder)\r\n            zip_ref.close()\r\n\r\n\r\ndef __add_chrome_proxy_extension(\r\n    extension_dir,\r\n    proxy_string,\r\n    proxy_user,\r\n    proxy_pass,\r\n    proxy_scheme=\"http\",\r\n    proxy_bypass_list=None,\r\n    multi_proxy=False,\r\n):\r\n    \"\"\"Implementation of https://stackoverflow.com/a/35293284/7058266\r\n    for https://stackoverflow.com/q/12848327/7058266\r\n    (Run Selenium on a proxy server that requires authentication.)\"\"\"\r\n    args = \" \".join(sys.argv)\r\n    bypass_list = proxy_bypass_list\r\n    if (\r\n        not (\"-n\" in sys.argv or \" -n=\" in args or args == \"-c\")\r\n        and not multi_proxy\r\n    ):\r\n        # Single-threaded\r\n        proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)\r\n        with proxy_dir_lock:\r\n            proxy_helper.create_proxy_ext(\r\n                proxy_string,\r\n                proxy_user,\r\n                proxy_pass,\r\n                proxy_scheme,\r\n                bypass_list,\r\n                zip_it=False,\r\n            )\r\n            proxy_dir_path = proxy_helper.PROXY_DIR_PATH\r\n            extension_dir = __add_chrome_ext_dir(\r\n                extension_dir, proxy_dir_path\r\n            )\r\n    else:\r\n        # Multi-threaded\r\n        proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)\r\n        with proxy_dir_lock:\r\n            with suppress(Exception):\r\n                shared_utils.make_writable(PROXY_DIR_LOCK)\r\n            if multi_proxy:\r\n                __set_proxy_filenames()\r\n            if not os.path.exists(proxy_helper.PROXY_DIR_PATH):\r\n                proxy_helper.create_proxy_ext(\r\n                    proxy_string,\r\n                    proxy_user,\r\n                    proxy_pass,\r\n                    proxy_scheme,\r\n                    bypass_list,\r\n                    zip_it=False,\r\n                )\r\n            extension_dir = __add_chrome_ext_dir(\r\n                extension_dir, proxy_helper.PROXY_DIR_PATH\r\n            )\r\n    return extension_dir\r\n\r\n\r\nasync def start(\r\n    config: Optional[Config] = None,\r\n    *,\r\n    user_data_dir: Optional[PathLike] = None,\r\n    headless: Optional[bool] = None,\r\n    incognito: Optional[bool] = None,\r\n    guest: Optional[bool] = None,\r\n    browser_executable_path: Optional[PathLike] = None,\r\n    browser_args: Optional[List[str]] = None,\r\n    xvfb_metrics: Optional[List[str]] = None,  # \"Width,Height\" for Linux\r\n    ad_block: Optional[bool] = None,\r\n    sandbox: Optional[bool] = True,\r\n    lang: Optional[str] = None,  # Set the Language Locale Code\r\n    host: Optional[str] = None,  # Chrome remote-debugging-host\r\n    port: Optional[int] = None,  # Chrome remote-debugging-port\r\n    xvfb: Optional[int] = None,  # Use a special virtual display on Linux\r\n    headed: Optional[bool] = None,  # Override default Xvfb mode on Linux\r\n    expert: Optional[bool] = None,  # Open up closed Shadow-root elements\r\n    agent: Optional[str] = None,  # Set the user-agent string\r\n    proxy: Optional[str] = None,  # \"host:port\" or \"user:pass@host:port\"\r\n    tzone: Optional[str] = None,  # Eg \"America/New_York\", \"Asia/Kolkata\"\r\n    geoloc: Optional[list | tuple] = None,  # Eg (48.87645, 2.26340)\r\n    mobile: Optional[bool] = None,  # Use Mobile Mode with default args\r\n    disable_csp: Optional[str] = None,  # Disable content security policy\r\n    extension_dir: Optional[str] = None,  # Chrome extension directory\r\n    use_chromium: Optional[str] = None,  # Use the base Chromium browser\r\n    **kwargs: Optional[dict],\r\n) -> Browser:\r\n    \"\"\"\r\n    Helper function to launch a browser. It accepts several keyword parameters.\r\n    Conveniently, you can just call it bare (no parameters) to quickly launch\r\n    an instance with best practice defaults.\r\n    Note: Due to a Chrome-130 bug, use start_async or start_sync instead.\r\n     (Calling this method directly could lead to an unresponsive browser)\r\n    Note: New args are expected: Use kwargs only!\r\n    Note: This should be called ``await start()``\r\n    :param user_data_dir:\r\n    :type user_data_dir: PathLike\r\n    :param headless:\r\n    :type headless: bool\r\n    :param browser_executable_path:\r\n    :type browser_executable_path: PathLike\r\n    :param browser_args:\r\n     [\"--some-chromeparam=somevalue\", \"some-other-param=someval\"]\r\n    :type browser_args: List[str]\r\n    :param sandbox: Default True, but when set to False it adds --no-sandbox\r\n     to the params, also when using linux under a root user,\r\n     it adds False automatically (else Chrome won't start).\r\n    :type sandbox: bool\r\n    :param lang: language string\r\n    :type lang: str\r\n    :param port: If you connect to an existing debuggable session,\r\n     you can specify the port here.\r\n     If both host and port are provided,\r\n     then a local Chrome browser will not be started!\r\n    :type port: int\r\n    :param host: If you connect to an existing debuggable session,\r\n     you can specify the host here.\r\n     If both host and port are provided,\r\n     then a local Chrome browser will not be started!\r\n    :type host: str\r\n    :param expert: When set to True, \"expert\" mode is enabled.\r\n     This means adding: --disable-web-security --disable-site-isolation-trials,\r\n     as well as some scripts and patching useful for debugging.\r\n     (For example, ensuring shadow-root is always in \"open\" mode.)\r\n    :type expert: bool\r\n    \"\"\"\r\n    sys_argv = sys.argv\r\n    arg_join = \" \".join(sys_argv)\r\n    if headless is None:\r\n        if \"--headless\" in sys_argv:\r\n            headless = True\r\n        else:\r\n            headless = False\r\n    if headed is None:\r\n        if \"--gui\" in sys_argv or \"--headed\" in sys_argv:\r\n            headed = True\r\n        else:\r\n            headed = False\r\n    if xvfb is None:\r\n        if \"--xvfb\" in sys_argv:\r\n            xvfb = True\r\n        else:\r\n            xvfb = False\r\n    if not hasattr(sb_config, \"xvfb\"):\r\n        sb_config.xvfb = xvfb\r\n    if incognito is None:\r\n        if \"--incognito\" in sys_argv:\r\n            incognito = True\r\n        else:\r\n            incognito = False\r\n    if guest is None:\r\n        if \"--guest\" in sys_argv:\r\n            guest = True\r\n        else:\r\n            guest = False\r\n    if mobile is None:\r\n        if \"--mobile\" in sys_argv:\r\n            mobile = True\r\n        else:\r\n            mobile = False\r\n    if mobile:\r\n        sb_config._cdp_mobile_mode = True\r\n    else:\r\n        sb_config._cdp_mobile_mode = False\r\n    if ad_block is None:\r\n        if \"--ad-block\" in sys_argv or \"--ad_block\" in sys_argv:\r\n            ad_block = True\r\n        else:\r\n            ad_block = False\r\n    if disable_csp is None:\r\n        if \"--disable-csp\" in sys_argv or \"--disable_csp\" in sys_argv:\r\n            disable_csp = True\r\n        else:\r\n            disable_csp = False\r\n    if xvfb_metrics is None and \"--xvfb-metrics\" in arg_join:\r\n        x_m = xvfb_metrics\r\n        count = 0\r\n        for arg in sys_argv:\r\n            if arg.startswith(\"--xvfb-metrics=\"):\r\n                x_m = arg.split(\"--xvfb-metrics=\")[1]\r\n                break\r\n            elif arg == \"--xvfb-metrics\" and len(sys_argv) > count + 1:\r\n                x_m = sys_argv[count + 1]\r\n                if x_m.startswith(\"-\"):\r\n                    x_m = None\r\n                break\r\n            count += 1\r\n        if x_m:\r\n            if x_m.startswith('\"') and x_m.endswith('\"'):\r\n                x_m = x_m[1:-1]\r\n            elif x_m.startswith(\"'\") and x_m.endswith(\"'\"):\r\n                x_m = x_m[1:-1]\r\n        xvfb_metrics = x_m\r\n    if agent is None and \"user_agent\" not in kwargs and \"--agent\" in arg_join:\r\n        count = 0\r\n        for arg in sys_argv:\r\n            if arg.startswith(\"--agent=\"):\r\n                agent = arg.split(\"--agent=\")[1]\r\n                break\r\n            elif arg == \"--agent\" and len(sys_argv) > count + 1:\r\n                agent = sys_argv[count + 1]\r\n                if agent.startswith(\"-\"):\r\n                    agent = None\r\n                break\r\n            count += 1\r\n        if agent:\r\n            if agent.startswith('\"') and agent.endswith('\"'):\r\n                agent = agent[1:-1]\r\n            elif agent.startswith(\"'\") and agent.endswith(\"'\"):\r\n                agent = agent[1:-1]\r\n    if (\r\n        geoloc is None\r\n        and \"geolocation\" not in kwargs\r\n        and \"--geolocation\" in arg_join\r\n    ):\r\n        count = 0\r\n        for arg in sys_argv:\r\n            if arg.startswith(\"--geolocation=\"):\r\n                geoloc = arg.split(\"--geolocation=\")[1]\r\n                break\r\n            elif arg == \"--geolocation\" and len(sys_argv) > count + 1:\r\n                geoloc = sys_argv[count + 1]\r\n                if geoloc.startswith(\"-\"):\r\n                    geoloc = None\r\n                break\r\n            count += 1\r\n        if geoloc:\r\n            if geoloc.startswith('\"') and geoloc.endswith('\"'):\r\n                geoloc = geoloc[1:-1]\r\n            elif geoloc.startswith(\"'\") and geoloc.endswith(\"'\"):\r\n                geoloc = geoloc[1:-1]\r\n            if geoloc:\r\n                import ast\r\n                geoloc = ast.literal_eval(geoloc)\r\n    if not lang and \"locale\" not in kwargs and \"locale_code\" not in kwargs:\r\n        if \"--locale\" in arg_join:\r\n            count = 0\r\n            for arg in sys_argv:\r\n                if arg.startswith(\"--locale=\"):\r\n                    lang = arg.split(\"--locale=\")[1]\r\n                    break\r\n                elif arg == \"--locale\" and len(sys_argv) > count + 1:\r\n                    lang = sys_argv[count + 1]\r\n                    if lang.startswith(\"-\"):\r\n                        lang = None\r\n                    break\r\n                count += 1\r\n        elif \"--lang\" in arg_join:\r\n            count = 0\r\n            for arg in sys_argv:\r\n                if arg.startswith(\"--lang=\"):\r\n                    lang = arg.split(\"--lang=\")[1]\r\n                    break\r\n                elif arg == \"--lang\" and len(sys_argv) > count + 1:\r\n                    lang = sys_argv[count + 1]\r\n                    if lang.startswith(\"-\"):\r\n                        lang = None\r\n                    break\r\n                count += 1\r\n        if lang:\r\n            if lang.startswith('\"') and lang.endswith('\"'):\r\n                lang = lang[1:-1]\r\n            elif lang.startswith(\"'\") and lang.endswith(\"'\"):\r\n                lang = lang[1:-1]\r\n    if not browser_executable_path and \"binary_location\" not in kwargs:\r\n        bin_loc = None\r\n        if \"--binary-location\" in arg_join or \"--binary_location\" in arg_join:\r\n            bin_loc_cmd = \"--binary-location\"\r\n            if \"--binary_location\" in arg_join:\r\n                bin_loc_cmd = \"--binary_location\"\r\n            count = 0\r\n            bin_loc = None\r\n            for arg in sys_argv:\r\n                if arg.startswith(\"%s=\" % bin_loc_cmd):\r\n                    bin_loc = arg.split(\"%s=\" % bin_loc_cmd)[1]\r\n                    break\r\n                elif arg == bin_loc_cmd and len(sys_argv) > count + 1:\r\n                    bin_loc = sys_argv[count + 1]\r\n                    if bin_loc.startswith(\"-\"):\r\n                        bin_loc = None\r\n                    break\r\n                count += 1\r\n        elif \"--bl=\" in arg_join:\r\n            count = 0\r\n            bin_loc = None\r\n            for arg in sys_argv:\r\n                if arg.startswith(\"--bl=\"):\r\n                    bin_loc = arg.split(\"--bl=\")[1]\r\n                    break\r\n                count += 1\r\n        if bin_loc:\r\n            if bin_loc.startswith('\"') and bin_loc.endswith('\"'):\r\n                bin_loc = bin_loc[1:-1]\r\n            elif bin_loc.startswith(\"'\") and bin_loc.endswith(\"'\"):\r\n                bin_loc = bin_loc[1:-1]\r\n            if bin_loc and not os.path.exists(bin_loc):\r\n                print(\"  No browser executable at PATH {%s}! \" % bin_loc)\r\n                print(\"  Using default Chrome browser instead!\")\r\n                bin_loc = None\r\n            browser_executable_path = bin_loc\r\n        elif use_chromium or \"--use-chromium\" in arg_join:\r\n            browser_executable_path = \"_chromium_\"\r\n    if proxy is None and \"--proxy\" in arg_join:\r\n        proxy_string = None\r\n        if \"--proxy=\" in arg_join:\r\n            proxy_string = arg_join.split(\"--proxy=\")[1].split(\" \")[0]\r\n        elif \"--proxy \" in arg_join:\r\n            proxy_string = arg_join.split(\"--proxy \")[1].split(\" \")[0]\r\n        if proxy_string:\r\n            if proxy_string.startswith('\"') and proxy_string.endswith('\"'):\r\n                proxy_string = proxy_string[1:-1]\r\n            elif proxy_string.startswith(\"'\") and proxy_string.endswith(\"'\"):\r\n                proxy_string = proxy_string[1:-1]\r\n            proxy = proxy_string\r\n    if tzone is None and \"timezone\" not in kwargs and \"--timezone\" in arg_join:\r\n        tz_string = None\r\n        if \"--timezone=\" in arg_join:\r\n            tz_string = arg_join.split(\"--timezone=\")[1].split(\" \")[0]\r\n        elif \"--timezone \" in arg_join:\r\n            tz_string = arg_join.split(\"--timezone \")[1].split(\" \")[0]\r\n        if tz_string:\r\n            if tz_string.startswith('\"') and tz_string.endswith('\"'):\r\n                tz_string = proxy_string[1:-1]\r\n            elif tz_string.startswith(\"'\") and tz_string.endswith(\"'\"):\r\n                tz_string = proxy_string[1:-1]\r\n            tzone = tz_string\r\n    platform_var = None\r\n    if (\r\n        \"platform\" not in kwargs\r\n        and \"plat\" not in kwargs\r\n        and \"--platform\" in arg_join\r\n    ):\r\n        count = 0\r\n        for arg in sys_argv:\r\n            if arg.startswith(\"--platform=\"):\r\n                platform_var = arg.split(\"--platform=\")[1]\r\n                break\r\n            elif arg == \"--platform\" and len(sys_argv) > count + 1:\r\n                platform_var = sys_argv[count + 1]\r\n                if platform_var.startswith(\"-\"):\r\n                    platform_var = None\r\n                break\r\n            count += 1\r\n        if platform_var:\r\n            if platform_var.startswith('\"') and platform_var.endswith('\"'):\r\n                platform_var = platform_var[1:-1]\r\n            elif platform_var.startswith(\"'\") and platform_var.endswith(\"'\"):\r\n                platform_var = platform_var[1:-1]\r\n    if IS_LINUX and not headless and not headed and not xvfb:\r\n        xvfb = True  # The default setting on Linux\r\n    if port and not host:\r\n        host = \"127.0.0.1\"  # Assume localhost\r\n    if not host or not port:\r\n        # The browser hasn't been launched yet. (May need a virtual display)\r\n        __activate_virtual_display_as_needed(\r\n            headless, headed, xvfb, xvfb_metrics\r\n        )\r\n    if proxy and \"@\" in str(proxy):\r\n        user_with_pass = proxy.split(\"@\")[0]\r\n        if \":\" in user_with_pass:\r\n            proxy_user = user_with_pass.split(\":\")[0]\r\n            proxy_pass = user_with_pass.split(\":\")[1]\r\n            proxy_string = proxy.split(\"@\")[1]\r\n            proxy_string, proxy_scheme = proxy_helper.validate_proxy_string(\r\n                proxy_string, keep_scheme=True\r\n            )\r\n            extension_dir = __add_chrome_proxy_extension(\r\n                extension_dir,\r\n                proxy_string,\r\n                proxy_user,\r\n                proxy_pass,\r\n                proxy_scheme,\r\n            )\r\n    if ad_block:\r\n        sb_config.ad_block_on = True\r\n        incognito = False\r\n        guest = False\r\n        ad_block_zip = AD_BLOCK_ZIP_PATH\r\n        ad_block_dir = os.path.join(DOWNLOADS_FOLDER, \"ad_block\")\r\n        __unzip_to_new_folder(ad_block_zip, ad_block_dir)\r\n        extension_dir = __add_chrome_ext_dir(extension_dir, ad_block_dir)\r\n    if disable_csp:\r\n        sb_config.disable_csp = True\r\n    if \"binary_location\" in kwargs and not browser_executable_path:\r\n        browser_executable_path = kwargs[\"binary_location\"]\r\n    if not user_data_dir and \"--user-data-dir\" in arg_join:\r\n        udd_string = None\r\n        if \"--user-data-dir=\" in arg_join:\r\n            udd_string = arg_join.split(\"--user-data-dir=\")[1].split(\" \")[0]\r\n        elif \"--user-data-dir \" in arg_join:\r\n            udd_string = arg_join.split(\"--user-data-dir \")[1].split(\" \")[0]\r\n        if udd_string:\r\n            if udd_string.startswith('\"') and udd_string.endswith('\"'):\r\n                udd_string = udd_string[1:-1]\r\n            elif udd_string.startswith(\"'\") and udd_string.endswith(\"'\"):\r\n                udd_string = udd_string[1:-1]\r\n            user_data_dir = udd_string\r\n    if user_data_dir:\r\n        user_data_dir = os.path.abspath(user_data_dir)\r\n    if not browser_executable_path:\r\n        browser = None\r\n        if \"browser\" in kwargs:\r\n            browser = kwargs[\"browser\"]\r\n        if not browser and \"--browser\" in arg_join:\r\n            br_string = None\r\n            if \"--browser=\" in arg_join:\r\n                br_string = arg_join.split(\"--browser=\")[1].split(\" \")[0]\r\n            elif \"--browser \" in arg_join:\r\n                br_string = arg_join.split(\"--browser \")[1].split(\" \")[0]\r\n            if br_string:\r\n                if br_string.startswith('\"') and br_string.endswith('\"'):\r\n                    br_string = br_string[1:-1]\r\n                elif br_string.startswith(\"'\") and br_string.endswith(\"'\"):\r\n                    br_string = br_string[1:-1]\r\n                browser = br_string\r\n        if not browser:\r\n            if \"--edge\" in sys_argv:\r\n                browser = \"edge\"\r\n            elif \"--opera\" in sys_argv:\r\n                browser = \"opera\"\r\n            elif \"--brave\" in sys_argv:\r\n                browser = \"brave\"\r\n            elif \"--comet\" in sys_argv:\r\n                browser = \"comet\"\r\n            elif \"--atlas\" in sys_argv:\r\n                browser = \"atlas\"\r\n            else:\r\n                browser = \"chrome\"\r\n        sb_config._cdp_browser = browser\r\n        if browser == \"comet\" or browser == \"atlas\":\r\n            incognito = False\r\n            guest = False\r\n        with suppress(Exception):\r\n            browser_binary = detect_b_ver.get_binary_location(browser)\r\n            if browser_binary and os.path.exists(browser_binary):\r\n                browser_executable_path = browser_binary\r\n    else:\r\n        bin_loc = str(browser_executable_path).lower()\r\n        if bin_loc.endswith(\"opera\") or bin_loc.endswith(\"opera.exe\"):\r\n            sb_config._cdp_browser = \"opera\"\r\n        elif bin_loc.endswith(\"edge\") or bin_loc.endswith(\"edge.exe\"):\r\n            sb_config._cdp_browser = \"edge\"\r\n        elif bin_loc.endswith(\"brave\") or bin_loc.endswith(\"brave.exe\"):\r\n            sb_config._cdp_browser = \"brave\"\r\n        elif bin_loc.endswith(\"comet\") or bin_loc.endswith(\"comet.exe\"):\r\n            sb_config._cdp_browser = \"comet\"\r\n        elif bin_loc.endswith(\"atlas\") or bin_loc.endswith(\"atlas.exe\"):\r\n            sb_config._cdp_browser = \"atlas\"\r\n        else:\r\n            sb_config._cdp_browser = \"chrome\"\r\n    sb_config.incognito = incognito\r\n    sb_config.guest_mode = guest\r\n    if not config:\r\n        config = Config(\r\n            user_data_dir,\r\n            headless,\r\n            incognito,\r\n            guest,\r\n            browser_executable_path,\r\n            browser_args,\r\n            sandbox,\r\n            lang,\r\n            host=host,\r\n            port=port,\r\n            expert=expert,\r\n            proxy=proxy,\r\n            extension_dir=extension_dir,\r\n            **kwargs,\r\n        )\r\n    driver = None\r\n    try:\r\n        driver = await Browser.create(config)\r\n    except Exception:\r\n        time.sleep(0.12)\r\n        if not host or not port:\r\n            __activate_virtual_display_as_needed(\r\n                headless, headed, xvfb, xvfb_metrics, override_display=True\r\n            )\r\n            time.sleep(0.05)\r\n        driver = await Browser.create(config)\r\n    if proxy:\r\n        sb_config._cdp_proxy = proxy\r\n        if \"@\" in str(proxy):\r\n            time.sleep(0.15)\r\n    if lang:\r\n        sb_config._cdp_locale = lang\r\n    elif \"locale\" in kwargs:\r\n        sb_config._cdp_locale = kwargs[\"locale\"]\r\n    elif \"locale_code\" in kwargs:\r\n        sb_config._cdp_locale = kwargs[\"locale_code\"]\r\n    if tzone:\r\n        sb_config._cdp_timezone = tzone\r\n    elif \"timezone\" in kwargs:\r\n        sb_config._cdp_timezone = kwargs[\"timezone\"]\r\n    else:\r\n        sb_config._cdp_timezone = None\r\n    if geoloc:\r\n        sb_config._cdp_geolocation = geoloc\r\n    elif \"geolocation\" in kwargs:\r\n        sb_config._cdp_geolocation = kwargs[\"geolocation\"]\r\n    else:\r\n        sb_config._cdp_geolocation = None\r\n    if agent:\r\n        sb_config._cdp_user_agent = agent\r\n    elif \"user_agent\" in kwargs:\r\n        sb_config._cdp_user_agent = kwargs[\"user_agent\"]\r\n    else:\r\n        sb_config._cdp_user_agent = None\r\n    if \"platform\" in kwargs:\r\n        sb_config._cdp_platform = kwargs[\"platform\"]\r\n    elif \"plat\" in kwargs:\r\n        sb_config._cdp_platform = kwargs[\"plat\"]\r\n    elif platform_var:\r\n        sb_config._cdp_platform = platform_var\r\n    else:\r\n        sb_config._cdp_platform = None\r\n    driver.page = driver.main_tab\r\n    driver.solve_captcha = driver.page.solve_captcha\r\n    return driver\r\n\r\n\r\nasync def start_async(*args, **kwargs) -> Browser:\r\n    return await start(*args, **kwargs)\r\n\r\n\r\ndef start_sync(*args, **kwargs) -> Browser:\r\n    loop = None\r\n    if (\r\n        \"loop\" in kwargs\r\n        and kwargs[\"loop\"]\r\n        and hasattr(kwargs[\"loop\"], \"create_task\")\r\n    ):\r\n        loop = kwargs[\"loop\"]\r\n    else:\r\n        loop = asyncio.new_event_loop()\r\n    return loop.run_until_complete(start(*args, **kwargs))\r\n\r\n\r\nasync def create_from_driver(driver) -> Browser:\r\n    \"\"\"Create a CDP Browser instance from a running UC driver.\r\n    This method is DEPRECATED in favor of activate_cdp_mode(),\r\n     which includes the option of switching between the modes,\r\n     and also properly handles configuration based on options.\"\"\"\r\n    from .config import Config\r\n\r\n    conf = Config()\r\n    host, port = driver.options.debugger_address.split(\":\")\r\n    conf.host, conf.port = host, int(port)\r\n    # Create Browser instance\r\n    browser = await start(conf)\r\n    browser._process_pid = driver.browser_pid\r\n    # Stop chromedriver binary\r\n    try:\r\n        driver.service.send_remote_shutdown_command()\r\n    except TypeError:\r\n        pass\r\n    finally:\r\n        with suppress(Exception):\r\n            driver.service._terminate_process()\r\n    driver.browser_pid = -1\r\n    driver.user_data_dir = None\r\n    return browser\r\n\r\n\r\ndef free_port() -> int:\r\n    \"\"\"Find and return a free port number assigned by the OS.\"\"\"\r\n    import socket\r\n\r\n    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\r\n        # Binding to port 0 lets the OS pick a free port\r\n        s.bind((\"127.0.0.1\", 0))\r\n        s.listen(5)\r\n        return s.getsockname()[1]\r\n\r\n\r\ndef filter_recurse_all(\r\n    doc: T, predicate: Callable[[cdp.dom.Node, Element], bool]\r\n) -> List[T]:\r\n    \"\"\"\r\n    Test each child using predicate(child),\r\n    and return all children for which predicate(child) == True\r\n    :param doc: The cdp.dom.Node object or :py:class:`cdp_driver.Element`\r\n    :param predicate: A function which takes a node as first parameter\r\n     and returns a boolean, where True means include.\r\n    \"\"\"\r\n    if not hasattr(doc, \"children\"):\r\n        raise TypeError(\"Object should have a .children attribute!\")\r\n    out = []\r\n    if doc and doc.children:\r\n        for child in doc.children:\r\n            if predicate(child):\r\n                out.append(child)\r\n            if child.shadow_roots is not None:\r\n                out.extend(\r\n                    filter_recurse_all(child.shadow_roots[0], predicate)\r\n                )\r\n            out.extend(filter_recurse_all(child, predicate))\r\n    return out\r\n\r\n\r\ndef filter_recurse(\r\n    doc: T, predicate: Callable[[cdp.dom.Node, Element], bool]\r\n) -> T:\r\n    \"\"\"\r\n    Test each child using predicate(child),\r\n    and return the first child of which predicate(child) == True\r\n    :param doc: the cdp.dom.Node object or :py:class:`cdp_driver.Element`\r\n    :param predicate: a function which takes a node as first parameter\r\n     and returns a boolean, where True means include.\r\n    \"\"\"\r\n    if not hasattr(doc, \"children\"):\r\n        raise TypeError(\"Object should have a .children attribute!\")\r\n    if doc and doc.children:\r\n        for child in doc.children:\r\n            if predicate(child):\r\n                return child\r\n            if child.shadow_roots:\r\n                shadow_root_result = filter_recurse(\r\n                    child.shadow_roots[0], predicate\r\n                )\r\n                if shadow_root_result:\r\n                    return shadow_root_result\r\n            result = filter_recurse(child, predicate)\r\n            if result:\r\n                return result\r\n\r\n\r\ndef circle(\r\n    x, y=None, radius=10, num=10, dir=0\r\n) -> typing.Generator[typing.Tuple[float, float], None, None]:\r\n    \"\"\"\r\n    A generator will calculate coordinates around a circle.\r\n    :param x: start x position\r\n    :type x: int\r\n    :param y: start y position\r\n    :type y: int\r\n    :param radius: size of the circle\r\n    :type radius: int\r\n    :param num: the amount of points calculated\r\n     (higher => slower, more cpu, but more detailed)\r\n    :type num: int\r\n    \"\"\"\r\n    import math\r\n\r\n    r = radius\r\n    w = num\r\n    if not y:\r\n        y = x\r\n    a = int(x - r * 2)\r\n    b = int(y - r * 2)\r\n    m = (2 * math.pi) / w\r\n    if dir == 0:\r\n        # Regular direction\r\n        ran = 0, w + 1, 1\r\n    else:\r\n        # Opposite direction\r\n        ran = w + 1, 0, -1\r\n    for i in range(*ran):\r\n        x = a + r * math.sin(m * i)\r\n        y = b + r * math.cos(m * i)\r\n        yield x, y\r\n\r\n\r\ndef remove_from_tree(tree: cdp.dom.Node, node: cdp.dom.Node) -> cdp.dom.Node:\r\n    if not hasattr(tree, \"children\"):\r\n        raise TypeError(\"Object should have a .children attribute!\")\r\n    if tree and tree.children:\r\n        for child in tree.children:\r\n            if child.backend_node_id == node.backend_node_id:\r\n                tree.children.remove(child)\r\n            remove_from_tree(child, node)\r\n    return tree\r\n\r\n\r\nasync def html_from_tree(\r\n    tree: Union[cdp.dom.Node, Element], target: Tab\r\n):\r\n    if not hasattr(tree, \"children\"):\r\n        raise TypeError(\"Object should have a .children attribute!\")\r\n    out = \"\"\r\n    if tree and tree.children:\r\n        for child in tree.children:\r\n            if isinstance(child, Element):\r\n                out += await child.get_html()\r\n            else:\r\n                out += await target.send(\r\n                    cdp.dom.get_outer_html(\r\n                        backend_node_id=child.backend_node_id\r\n                    )\r\n                )\r\n            out += await html_from_tree(child, target)\r\n    return out\r\n\r\n\r\ndef compare_target_info(\r\n    info1: cdp.target.TargetInfo, info2: cdp.target.TargetInfo\r\n) -> List[typing.Tuple[str, typing.Any, typing.Any]]:\r\n    \"\"\"\r\n    When logging mode is set to debug, browser object will log when target info\r\n    is changed. To provide more meaningful log messages,\r\n    this function is called to check what has actually changed\r\n    between the 2 (by simple dict comparison).\r\n    It returns a list of tuples\r\n        [ ... ( key_which_has_changed, old_value, new_value) ]\r\n    :param info1:\r\n    :param info2:\r\n    \"\"\"\r\n    d1 = info1.__dict__\r\n    d2 = info2.__dict__\r\n    return [(k, v, d2[k]) for (k, v) in d1.items() if d2[k] != v]\r\n\r\n\r\ndef loop():\r\n    loop = asyncio.new_event_loop()\r\n    asyncio.set_event_loop(loop)\r\n    return loop\r\n\r\n\r\ndef cdp_get_module(domain: Union[str, types.ModuleType]):\r\n    \"\"\"\r\n    Get cdp module by given string.\r\n    :param domain:\r\n    \"\"\"\r\n    import importlib\r\n\r\n    if isinstance(domain, types.ModuleType):\r\n        domain_mod = domain\r\n    else:\r\n        try:\r\n            if domain in (\"input\",):\r\n                domain = \"input_\"\r\n            domain_mod = getattr(cdp, domain)\r\n            if not domain_mod:\r\n                raise AttributeError\r\n        except AttributeError:\r\n            try:\r\n                domain_mod = importlib.import_module(domain)\r\n            except ModuleNotFoundError:\r\n                raise ModuleNotFoundError(\r\n                    \"Could not find cdp module from input '%s'\" % domain\r\n                )\r\n    return domain_mod\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/config.py",
    "content": "import logging\r\nimport os\r\nimport pathlib\r\nimport secrets\r\nimport sys\r\nimport tempfile\r\nimport zipfile\r\nfrom contextlib import suppress\r\nfrom seleniumbase.config import settings\r\nfrom seleniumbase.drivers import chromium_drivers\r\nfrom seleniumbase.fixtures import constants\r\nfrom seleniumbase.fixtures import shared_utils\r\nfrom typing import Union, List, Optional\r\n\r\n__all__ = [\r\n    \"Config\",\r\n    \"find_chrome_executable\",\r\n    \"temp_profile_dir\",\r\n    \"is_root\",\r\n    \"is_posix\",\r\n    \"PathLike\",\r\n]\r\n\r\nlogger = logging.getLogger(__name__)\r\nis_posix = sys.platform.startswith((\"darwin\", \"cygwin\", \"linux\", \"linux2\"))\r\n\r\nPathLike = Union[str, pathlib.Path]\r\nAUTO = None\r\nIS_MAC = shared_utils.is_mac()\r\nIS_LINUX = shared_utils.is_linux()\r\nIS_WINDOWS = shared_utils.is_windows()\r\nCHROMIUM_DIR = os.path.dirname(\r\n    os.path.realpath(chromium_drivers.__file__)\r\n)\r\n\r\n\r\nclass Config:\r\n    \"\"\"Config object\"\"\"\r\n\r\n    def __init__(\r\n        self,\r\n        user_data_dir: Optional[PathLike] = AUTO,\r\n        headless: Optional[bool] = False,\r\n        incognito: Optional[bool] = False,\r\n        guest: Optional[bool] = False,\r\n        browser_executable_path: Optional[PathLike] = AUTO,\r\n        browser_args: Optional[List[str]] = AUTO,\r\n        sandbox: Optional[bool] = True,\r\n        lang: Optional[str] = \"en-US\",\r\n        host: str = AUTO,\r\n        port: int = AUTO,\r\n        expert: bool = AUTO,\r\n        proxy: Optional[str] = None,\r\n        extension_dir: Optional[str] = None,\r\n        **kwargs: dict,\r\n    ):\r\n        \"\"\"\r\n        Creates a config object.\r\n        Can be called without any arguments to generate a best-practice config,\r\n        which is recommended.\r\n        Calling the object, eg: myconfig(), returns the list of arguments which\r\n        are provided to the browser.\r\n        Additional args can be added using the :py:obj:`~add_argument method`.\r\n        Instances of this class are usually not instantiated by end users.\r\n        :param user_data_dir: the data directory to use\r\n        :param headless: set to True for headless mode\r\n        :param browser_executable_path:\r\n         Specify browser executable, instead of using autodetect.\r\n        :param browser_args: Forwarded to browser executable.\r\n         Eg: [\"--some-chromeparam=somevalue\", \"some-other-param=someval\"]\r\n        :param sandbox: disables sandbox\r\n        :param autodiscover_targets: use autodiscovery of targets\r\n        :param lang:\r\n         Language string to use other than the default \"en-US,en;q=0.9\"\r\n        :param expert: When set to True, \"expert\" mode is enabled.\r\n         This adds: --disable-web-security --disable-site-isolation-trials,\r\n         as well as some scripts and patching useful for debugging.\r\n         (For example, ensuring shadow-root is always in \"open\" mode.)\r\n        :param kwargs:\r\n        :type user_data_dir: PathLike\r\n        :type headless: bool\r\n        :type browser_executable_path: PathLike\r\n        :type browser_args: list[str]\r\n        :type sandbox: bool\r\n        :type lang: str\r\n        :type kwargs: dict\r\n        \"\"\"\r\n        if not browser_args:\r\n            browser_args = []\r\n        if not user_data_dir:\r\n            self.user_data_dir = temp_profile_dir()\r\n            self._user_data_dir = self.user_data_dir\r\n            self._custom_data_dir = False\r\n        else:\r\n            self.user_data_dir = user_data_dir\r\n            profile = os.path.join(self.user_data_dir, \"Default\")\r\n            preferences_file = os.path.join(profile, \"Preferences\")\r\n            preferences = get_default_preferences()\r\n            if not os.path.exists(profile):\r\n                with suppress(Exception):\r\n                    os.makedirs(profile)\r\n            with open(preferences_file, \"w\") as f:\r\n                f.write(preferences)\r\n        mock_keychain = False\r\n        if not browser_executable_path:\r\n            browser_executable_path = find_chrome_executable()\r\n        elif browser_executable_path == \"_chromium_\":\r\n            from filelock import FileLock\r\n            binary_folder = None\r\n            if IS_MAC:\r\n                binary_folder = \"chrome-mac\"\r\n            elif IS_LINUX:\r\n                binary_folder = \"chrome-linux\"\r\n            elif IS_WINDOWS:\r\n                binary_folder = \"chrome-win\"\r\n            binary_location = os.path.join(CHROMIUM_DIR, binary_folder)\r\n            gui_lock = FileLock(constants.MultiBrowser.DRIVER_FIXING_LOCK)\r\n            with gui_lock:\r\n                with suppress(Exception):\r\n                    shared_utils.make_writable(\r\n                        constants.MultiBrowser.DRIVER_FIXING_LOCK\r\n                    )\r\n                if not os.path.exists(binary_location):\r\n                    from seleniumbase.console_scripts import sb_install\r\n                    sys_args = sys.argv  # Save a copy of sys args\r\n                    sb_install.log_d(\"\\nWarning: Chromium binary not found...\")\r\n                    sb_install.main(override=\"chromium\")\r\n                    sys.argv = sys_args  # Put back original args\r\n            binary_name = binary_location.split(\"/\")[-1].split(\"\\\\\")[-1]\r\n            if binary_name in [\"chrome-mac\"]:\r\n                binary_name = \"Chromium\"\r\n                binary_location += \"/Chromium.app\"\r\n                binary_location += \"/Contents/MacOS/Chromium\"\r\n            elif binary_name == \"chrome-linux\":\r\n                binary_name = \"chrome\"\r\n                binary_location += \"/chrome\"\r\n            elif binary_name in [\"chrome-win\"]:\r\n                binary_name = \"chrome.exe\"\r\n                binary_location += \"\\\\chrome.exe\"\r\n            if os.path.exists(binary_location):\r\n                mock_keychain = True\r\n                browser_executable_path = binary_location\r\n            else:\r\n                print(\r\n                    f\"{binary_location} not found. \"\r\n                    f\"Defaulting to regular Chrome!\"\r\n                )\r\n                browser_executable_path = find_chrome_executable()\r\n        self._browser_args = browser_args\r\n        self.browser_executable_path = browser_executable_path\r\n        self.headless = headless\r\n        self.incognito = incognito\r\n        self.guest = guest\r\n        self.sandbox = sandbox\r\n        self.host = host\r\n        self.port = port\r\n        self.expert = expert\r\n        self.proxy = proxy\r\n        self.extension_dir = extension_dir\r\n        self._extensions = []\r\n        # When using posix-ish operating system and running as root,\r\n        # you must use no_sandbox=True\r\n        if is_posix and is_root() and sandbox:\r\n            logger.info(\"Detected root usage, auto-disabling sandbox mode.\")\r\n            self.sandbox = False\r\n        self.autodiscover_targets = True\r\n        self.lang = lang\r\n        # Other keyword args will be accessible by attribute\r\n        self.__dict__.update(kwargs)\r\n        super().__init__()\r\n        start_width = settings.CHROME_START_WIDTH\r\n        start_height = settings.CHROME_START_HEIGHT\r\n        start_x = settings.WINDOW_START_X\r\n        start_y = settings.WINDOW_START_Y\r\n        self._default_browser_args = [\r\n            \"--window-size=%s,%s\" % (start_width, start_height),\r\n            \"--window-position=%s,%s\" % (start_x, start_y),\r\n            \"--no-first-run\",\r\n            \"--no-service-autorun\",\r\n            \"--disable-auto-reload\",\r\n            \"--no-default-browser-check\",\r\n            \"--homepage=about:blank\",\r\n            \"--no-pings\",\r\n            \"--enable-unsafe-extension-debugging\",\r\n            \"--wm-window-animations-disabled\",\r\n            \"--animation-duration-scale=0\",\r\n            \"--enable-privacy-sandbox-ads-apis\",\r\n            \"--safebrowsing-disable-download-protection\",\r\n            '--simulate-outdated-no-au=\"Tue, 31 Dec 2099 23:59:59 GMT\"',\r\n            \"--test-type\",\r\n            \"--ash-no-nudges\",\r\n            \"--password-store=basic\",\r\n            \"--deny-permission-prompts\",\r\n            \"--disable-breakpad\",\r\n            \"--disable-setuid-sandbox\",\r\n            \"--disable-prompt-on-repost\",\r\n            \"--disable-application-cache\",\r\n            \"--disable-password-generation\",\r\n            \"--disable-save-password-bubble\",\r\n            \"--disable-single-click-autofill\",\r\n            \"--disable-ipc-flooding-protection\",\r\n            \"--disable-background-timer-throttling\",\r\n            \"--disable-search-engine-choice-screen\",\r\n            \"--disable-backgrounding-occluded-windows\",\r\n            \"--disable-client-side-phishing-detection\",\r\n            \"--disable-device-discovery-notifications\",\r\n            \"--disable-top-sites\",\r\n            \"--disable-translate\",\r\n            \"--dns-prefetch-disable\",\r\n            \"--disable-renderer-backgrounding\",\r\n            \"--disable-dev-shm-usage\",\r\n        ]\r\n        if mock_keychain:\r\n            self._default_browser_args.append(\"--use-mock-keychain\")\r\n\r\n    @property\r\n    def browser_args(self):\r\n        return sorted(self._default_browser_args + self._browser_args)\r\n\r\n    @property\r\n    def user_data_dir(self):\r\n        return self._user_data_dir\r\n\r\n    @user_data_dir.setter\r\n    def user_data_dir(self, path: PathLike):\r\n        self._user_data_dir = str(path)\r\n        self._custom_data_dir = True\r\n\r\n    @property\r\n    def uses_custom_data_dir(self) -> bool:\r\n        return self._custom_data_dir\r\n\r\n    def add_extension(self, extension_path: PathLike):\r\n        \"\"\"\r\n        Adds an extension to load. You can set the extension_path to a\r\n        folder (containing the manifest), or an extension zip file (.crx)\r\n        :param extension_path:\r\n        \"\"\"\r\n        path = pathlib.Path(extension_path)\r\n        if not path.exists():\r\n            raise FileNotFoundError(\r\n                \"Could not find anything here: %s\" % str(path)\r\n            )\r\n        if path.is_file():\r\n            tf = tempfile.mkdtemp(\r\n                prefix=\"extension_\", suffix=secrets.token_hex(4)\r\n            )\r\n            with zipfile.ZipFile(path, \"r\") as z:\r\n                z.extractall(tf)\r\n                self._extensions.append(tf)\r\n        elif path.is_dir():\r\n            for item in path.rglob(\"manifest.*\"):\r\n                path = item.parent\r\n            self._extensions.append(path)\r\n\r\n    def __call__(self):\r\n        # The host and port will be added when starting the browser.\r\n        # By the time it starts, the port is probably already taken.\r\n        args = self._default_browser_args.copy()\r\n        args += [\"--user-data-dir=%s\" % self.user_data_dir]\r\n        args += [\r\n            \"--disable-features=IsolateOrigins,site-per-process,Translate,\"\r\n            \"InsecureDownloadWarnings,DownloadBubble,DownloadBubbleV2,\"\r\n            \"OptimizationTargetPrediction,OptimizationGuideModelDownloading,\"\r\n            \"SidePanelPinning,UserAgentClientHint,PrivacySandboxSettings4,\"\r\n            \"OptimizationHintsFetching,InterestFeedContentSuggestions,\"\r\n            \"Bluetooth,WebBluetooth,UnifiedWebBluetooth,ComponentUpdater,\"\r\n            \"DisableLoadExtensionCommandLineSwitch,\"\r\n            \"WebAuthentication,PasskeyAuth\"\r\n        ]\r\n        if self.expert:\r\n            args += [\r\n                \"--disable-web-security\",\r\n                \"--disable-site-isolation-trials\",\r\n            ]\r\n        if self.proxy:\r\n            args.append(\"--proxy-server=%s\" % self.proxy.split(\"@\")[-1])\r\n            args.append(\"--ignore-certificate-errors\")\r\n            args.append(\"--ignore-ssl-errors=yes\")\r\n        if self.extension_dir:\r\n            args.append(\"--load-extension=%s\" % self.extension_dir)\r\n        if self._browser_args:\r\n            args.extend([arg for arg in self._browser_args if arg not in args])\r\n        if self.headless:\r\n            args.append(\"--headless=new\")\r\n        if self.incognito:\r\n            args.append(\"--incognito\")\r\n        if self.guest:\r\n            args.append(\"--guest\")\r\n        if not self.sandbox:\r\n            args.append(\"--no-sandbox\")\r\n        if self.host:\r\n            args.append(\"--remote-debugging-host=%s\" % self.host)\r\n        if self.port:\r\n            args.append(\"--remote-debugging-port=%s\" % self.port)\r\n        return args\r\n\r\n    def add_argument(self, arg: str):\r\n        if any(\r\n            x in arg.lower()\r\n            for x in [\r\n                \"headless\",\r\n                \"data-dir\",\r\n                \"data_dir\",\r\n                \"no-sandbox\",\r\n                \"no_sandbox\",\r\n                \"lang\",\r\n            ]\r\n        ):\r\n            raise ValueError(\r\n                '\"%s\" is not allowed. Please use one of the '\r\n                'attributes of the Config object to set it.'\r\n                % arg\r\n            )\r\n        self._browser_args.append(arg)\r\n\r\n    def __repr__(self):\r\n        s = f\"{self.__class__.__name__}\"\r\n        for k, v in ({**self.__dict__, **self.__class__.__dict__}).items():\r\n            if k[0] == \"_\":\r\n                continue\r\n            if not v:\r\n                continue\r\n            if isinstance(v, property):\r\n                v = getattr(self, k)\r\n            if callable(v):\r\n                continue\r\n            s += f\"\\n\\t{k} = {v}\"\r\n        return s\r\n\r\n\r\ndef is_root():\r\n    \"\"\"\r\n    Helper function to determine if the user is trying to launch chrome\r\n    under linux as root, which needs some alternative handling.\r\n    \"\"\"\r\n    import ctypes\r\n    import os\r\n\r\n    try:\r\n        return os.getuid() == 0\r\n    except AttributeError:\r\n        return ctypes.windll.shell32.IsUserAnAdmin() != 0\r\n\r\n\r\ndef get_default_preferences():\r\n    return (\r\n        \"\"\"{\"credentials_enable_service\": false,\r\n        \"password_manager_enabled\": false,\r\n        \"password_manager_leak_detection\": false}\"\"\"\r\n    )\r\n\r\n\r\ndef temp_profile_dir():\r\n    \"\"\"Generate a temp dir (path)\"\"\"\r\n    path = os.path.normpath(tempfile.mkdtemp(prefix=\"uc_\"))\r\n    profile = os.path.join(path, \"Default\")\r\n    preferences_file = os.path.join(profile, \"Preferences\")\r\n    preferences = get_default_preferences()\r\n    if not os.path.exists(profile):\r\n        with suppress(Exception):\r\n            os.makedirs(profile)\r\n    with open(preferences_file, \"w\") as f:\r\n        f.write(preferences)\r\n    return path\r\n\r\n\r\ndef find_chrome_executable(return_all=False):\r\n    \"\"\"\r\n    Finds the chrome, beta, canary, chromium executable\r\n    and returns the disk path.\r\n    \"\"\"\r\n    candidates = []\r\n    if is_posix:\r\n        for item in os.environ.get(\"PATH\").split(os.pathsep):\r\n            for subitem in (\r\n                \"google-chrome\",\r\n                \"google-chrome-stable\",\r\n                \"google-chrome-beta\",\r\n                \"google-chrome-dev\",\r\n                \"google-chrome-unstable\",\r\n                \"chrome\",\r\n                \"chromium\",\r\n                \"chromium-browser\",\r\n            ):\r\n                candidates.append(os.sep.join((item, subitem)))\r\n        if \"darwin\" in sys.platform:\r\n            candidates += [\r\n                \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\",\r\n                \"/Applications/Chromium.app/Contents/MacOS/Chromium\",\r\n            ]\r\n    else:\r\n        for item in map(\r\n            os.environ.get,\r\n            (\r\n                \"PROGRAMFILES\",\r\n                \"PROGRAMFILES(X86)\",\r\n                \"LOCALAPPDATA\",\r\n                \"PROGRAMW6432\",\r\n            ),\r\n        ):\r\n            if item is not None:\r\n                for subitem in (\r\n                    \"Google/Chrome/Application\",\r\n                    \"Google/Chrome Beta/Application\",\r\n                    \"Google/Chrome Canary/Application\",\r\n                ):\r\n                    candidates.append(\r\n                        os.sep.join((item, subitem, \"chrome.exe\"))\r\n                    )\r\n    rv = []\r\n    for candidate in candidates:\r\n        if (\r\n            os.path.exists(candidate)\r\n            and os.access(candidate, os.R_OK)\r\n            and os.access(candidate, os.X_OK)\r\n        ):\r\n            logger.debug(\"%s is a valid candidate... \" % candidate)\r\n            rv.append(candidate)\r\n        else:\r\n            logger.debug(\r\n                \"%s is not a valid candidate because it doesn't exist \"\r\n                \"or isn't an executable.\"\r\n                % candidate\r\n            )\r\n    winner = None\r\n    if return_all and rv:\r\n        return rv\r\n    if rv and len(rv) > 1:\r\n        # Assuming the shortest path wins\r\n        winner = min(rv, key=lambda x: len(x))\r\n    elif len(rv) == 1:\r\n        winner = rv[0]\r\n    if winner:\r\n        return os.path.normpath(winner)\r\n    raise FileNotFoundError(\r\n        \"Could not find a valid chrome browser binary. \"\r\n        \"Please make sure Chrome is installed. \"\r\n        \"Or use the keyword argument: \"\r\n        \"'browser_executable_path=/path/to/your/browser'.\"\r\n    )\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/connection.py",
    "content": "from __future__ import annotations\r\nimport asyncio\r\nimport collections\r\nimport inspect\r\nimport itertools\r\nimport json\r\nimport logging\r\nimport sys\r\nimport types\r\nimport warnings\r\nfrom typing import (\r\n    Optional,\r\n    Generator,\r\n    Union,\r\n    Awaitable,\r\n    Callable,\r\n    Any,\r\n    TypeVar,\r\n)\r\nimport websockets\r\nfrom websockets.protocol import State\r\nfrom . import cdp_util as util\r\nimport mycdp as cdp\r\nimport mycdp.network\r\nimport mycdp.page\r\nimport mycdp.storage\r\nimport mycdp.runtime\r\nimport mycdp.target\r\nimport mycdp.util\r\n\r\nT = TypeVar(\"T\")\r\nGLOBAL_DELAY = 0.005\r\nMAX_SIZE: int = 2**28\r\nPING_TIMEOUT: int = 1800  # 30 minutes\r\nTargetType = Union[cdp.target.TargetInfo, cdp.target.TargetID]\r\nlogging.getLogger(\"asyncio\").setLevel(logging.CRITICAL)\r\nlogger = logging.getLogger(\"uc.connection\")\r\n\r\n\r\nclass ProtocolException(Exception):\r\n    def __init__(self, *args, **kwargs):\r\n        self.message = None\r\n        self.code = None\r\n        self.args = args\r\n        if isinstance(args[0], dict):\r\n            self.message = args[0].get(\"message\", None)  # noqa\r\n            self.code = args[0].get(\"code\", None)\r\n        elif hasattr(args[0], \"to_json\"):\r\n            def serialize(obj, _d=0):\r\n                res = \"\\n\"\r\n                for k, v in obj.items():\r\n                    space = \"\\t\" * _d\r\n                    if isinstance(v, dict):\r\n                        res += f\"{space}{k}: {serialize(v, _d + 1)}\\n\"\r\n                    else:\r\n                        res += f\"{space}{k}: {v}\\n\"\r\n                return res\r\n            self.message = serialize(args[0].to_json())\r\n        else:\r\n            self.message = \"| \".join(str(x) for x in args)\r\n\r\n    def __str__(self):\r\n        return f\"{self.message} [code: {self.code}]\" if self.code else f\"{self.message}\"  # noqa\r\n\r\n\r\nclass SettingClassVarNotAllowedException(PermissionError):\r\n    pass\r\n\r\n\r\nclass Transaction(asyncio.Future):\r\n    __cdp_obj__: Generator = None\r\n    method: str = None\r\n    params: dict = None\r\n    id: int = None\r\n\r\n    def __init__(self, cdp_obj: Generator):\r\n        \"\"\"\r\n        :param cdp_obj:\r\n        \"\"\"\r\n        super().__init__()\r\n        self.__cdp_obj__ = cdp_obj\r\n        self.connection = None\r\n        self.method, *params = next(self.__cdp_obj__).values()\r\n        if params:\r\n            params = params.pop()\r\n        self.params = params\r\n\r\n    @property\r\n    def message(self):\r\n        return json.dumps(\r\n            {\"method\": self.method, \"params\": self.params, \"id\": self.id}\r\n        )\r\n\r\n    @property\r\n    def has_exception(self):\r\n        try:\r\n            if self.exception():\r\n                return True\r\n        except BaseException:\r\n            return True\r\n        return False\r\n\r\n    def __call__(self, **response: dict):\r\n        \"\"\"\r\n        Parses the response message and marks the future complete.\r\n        :param response:\r\n        \"\"\"\r\n        if \"error\" in response:\r\n            # Set exception and bail out\r\n            return self.set_exception(ProtocolException(response[\"error\"]))\r\n        try:\r\n            # Try to parse the result according to the PyCDP docs.\r\n            self.__cdp_obj__.send(response[\"result\"])\r\n        except StopIteration as e:\r\n            # Exception value holds the parsed response\r\n            return self.set_result(e.value)\r\n        raise ProtocolException(\r\n            \"Could not parse the cdp response:\\n%s\" % response\r\n        )\r\n\r\n    def __repr__(self):\r\n        success = False if (self.done() and self.has_exception) else True\r\n        if self.done():\r\n            status = \"finished\"\r\n        else:\r\n            status = \"pending\"\r\n        fmt = (\r\n            f\"<{self.__class__.__name__}\\n\\t\"\r\n            f\"method: {self.method}\\n\\t\"\r\n            f\"status: {status}\\n\\t\"\r\n            f\"success: {success}>\"\r\n        )\r\n        return fmt\r\n\r\n\r\nclass CantTouchThis(type):\r\n    def __setattr__(cls, attr, value):\r\n        \"\"\":meta private:\"\"\"\r\n        if attr == \"__annotations__\":\r\n            # Fix autodoc\r\n            return super().__setattr__(attr, value)\r\n        raise SettingClassVarNotAllowedException(\r\n            \"\\n\".join(\r\n                (\r\n                    \"don't set '%s' on the %s class directly, \"\r\n                    \"as those are shared with other objects.\",\r\n                    \"use `my_object.%s = %s`  instead\",\r\n                )\r\n            )\r\n            % (attr, cls.__name__, attr, value)\r\n        )\r\n\r\n\r\nclass Connection(metaclass=CantTouchThis):\r\n    attached: bool = None\r\n    websocket: websockets.WebSocketClientProtocol\r\n    _target: cdp.target.TargetInfo\r\n\r\n    def __init__(\r\n        self,\r\n        websocket_url=None,\r\n        target=None,\r\n        browser=None,\r\n        **kwargs,\r\n    ):\r\n        super().__init__()\r\n        self._target = target\r\n        self.__count__ = itertools.count(0)\r\n        self.browser = browser\r\n        self.websocket_url: str = websocket_url\r\n        self.websocket = None\r\n        self.mapper = {}\r\n        self.handlers = collections.defaultdict(list)\r\n        self.recv_task = None\r\n        self.enabled_domains = []\r\n        self._last_result = []\r\n        self.listener: Listener = None\r\n        self.__dict__.update(**kwargs)\r\n\r\n    @property\r\n    def target(self) -> cdp.target.TargetInfo:\r\n        return self._target\r\n\r\n    @target.setter\r\n    def target(self, target: cdp.target.TargetInfo):\r\n        if not isinstance(target, cdp.target.TargetInfo):\r\n            raise TypeError(\r\n                \"target must be set to a '%s' but got '%s\"\r\n                % (cdp.target.TargetInfo.__name__, type(target).__name__)\r\n            )\r\n        self._target = target\r\n\r\n    @property\r\n    def closed(self):\r\n        if not self.websocket:\r\n            return True\r\n        return self.websocket.closed\r\n\r\n    def add_handler(\r\n        self,\r\n        event_type_or_domain: Union[type, types.ModuleType],\r\n        handler: Union[Callable, Awaitable],\r\n    ):\r\n        \"\"\"\r\n        Add a handler for given event.\r\n        If event_type_or_domain is a module instead of a type,\r\n        it will find all available events and add the handler.\r\n        If you want to receive event updates (eg. network traffic),\r\n        you can add handlers for those events.\r\n        Handlers can be regular callback functions\r\n        or async coroutine functions (and also just lambdas).\r\n        For example, if you want to check the network traffic:\r\n        .. code-block::\r\n            page.add_handler(\r\n                cdp.network.RequestWillBeSent, lambda event: print(\r\n                    'network event => %s' % event.request\r\n                )\r\n            )\r\n        Next time there's network traffic, you'll see lots of console output.\r\n        :param event_type_or_domain:\r\n        :param handler:\r\n        \"\"\"\r\n        if isinstance(event_type_or_domain, types.ModuleType):\r\n            for name, obj in inspect.getmembers_static(event_type_or_domain):\r\n                if name.isupper():\r\n                    continue\r\n                if not name[0].isupper():\r\n                    continue\r\n                if not isinstance(obj, type):\r\n                    continue\r\n                if inspect.isbuiltin(obj):\r\n                    continue\r\n                self.handlers[obj].append(handler)\r\n            return\r\n        self.handlers[event_type_or_domain].append(handler)\r\n\r\n    async def aopen(self, **kw):\r\n        \"\"\"\r\n        Opens the websocket connection. Shouldn't be called manually by users.\r\n        \"\"\"\r\n        if not self.websocket or self.websocket.state is State.CLOSED:\r\n            try:\r\n                self.websocket = await websockets.connect(\r\n                    self.websocket_url,\r\n                    ping_timeout=PING_TIMEOUT,\r\n                    max_size=MAX_SIZE,\r\n                )\r\n                self.listener = Listener(self)\r\n            except (Exception,) as e:\r\n                logger.debug(\"Exception during opening of websocket: %s\", e)\r\n                if self.listener:\r\n                    await self.listener.cancel()\r\n                raise\r\n        if not self.listener or not self.listener.running:\r\n            self.listener = Listener(self)\r\n            logger.debug(\r\n                \"\\n✅ Opened websocket connection to %s\", self.websocket_url\r\n            )\r\n        # When a websocket connection is closed (either by error or on purpose)\r\n        # and reconnected, the registered event listeners (if any), should be\r\n        # registered again, so the browser sends those events.\r\n        await self._register_handlers()\r\n\r\n    async def aclose(self):\r\n        \"\"\"\r\n        Closes the websocket connection. Shouldn't be called manually by users.\r\n        \"\"\"\r\n        if self.websocket and self.websocket.state is not State.CLOSED:\r\n            try:\r\n                await self.websocket.close()\r\n            except Exception:\r\n                logger.debug(\r\n                    \"\\n❌ Error closing websocket connection to %s\",\r\n                    self.websocket_url\r\n                )\r\n            if self.listener and self.listener.running:\r\n                await self.listener.cancel()\r\n                self.enabled_domains.clear()\r\n            logger.debug(\r\n                \"\\n❌ Closed websocket connection to %s\", self.websocket_url\r\n            )\r\n\r\n    async def sleep(self, t: Union[int, float] = 0.25):\r\n        await self.update_target()\r\n        await asyncio.sleep(t)\r\n\r\n    def feed_cdp(self, cdp_obj):\r\n        \"\"\"\r\n        Used in specific cases, mostly during cdp.fetch.RequestPaused events,\r\n        in which the browser literally blocks.\r\n        By using feed_cdp, you can issue a response without a blocking \"await\".\r\n        Note: This method won't cause a response.\r\n        Note: This is not an async method, just a regular method!\r\n        :param cdp_obj:\r\n        \"\"\"\r\n        asyncio.ensure_future(self.send(cdp_obj))\r\n\r\n    async def wait(self, t: Union[int, float] = None):\r\n        \"\"\"\r\n        Waits until the event listener reports idle\r\n        (no new events received in certain timespan).\r\n        When `t` is provided, ensures waiting for `t` seconds, no matter what.\r\n        :param t:\r\n        \"\"\"\r\n        await self.update_target()\r\n        loop = asyncio.get_running_loop()\r\n        start_time = loop.time()\r\n        try:\r\n            if isinstance(t, (int, float)):\r\n                await asyncio.wait_for(self.listener.idle.wait(), timeout=t)\r\n                while (loop.time() - start_time) < t:\r\n                    await asyncio.sleep(0.1)\r\n            else:\r\n                await self.listener.idle.wait()\r\n        except asyncio.TimeoutError:\r\n            if isinstance(t, (int, float)):\r\n                # Explicit time is given, which is now passed, so leave now.\r\n                return\r\n        except AttributeError:\r\n            # No listener created yet.\r\n            pass\r\n\r\n    async def set_locale(self, locale: Optional[str] = None):\r\n        \"\"\"Sets the Language Locale code via set_user_agent_override.\"\"\"\r\n        await self.set_user_agent(user_agent=\"\", accept_language=locale)\r\n        await self.send(cdp.emulation.set_locale_override(locale))\r\n\r\n    async def set_timezone(self, timezone: Optional[str] = None):\r\n        \"\"\"Sets the Timezone via set_timezone_override.\"\"\"\r\n        await self.send(cdp.emulation.set_timezone_override(timezone))\r\n\r\n    async def set_user_agent(\r\n        self,\r\n        user_agent: Optional[str] = \"\",\r\n        accept_language: Optional[str] = None,\r\n        platform: Optional[str] = None,  # navigator.platform\r\n    ):\r\n        \"\"\"Sets the User Agent via set_user_agent_override.\"\"\"\r\n        if not user_agent:\r\n            user_agent = \"\"\r\n        await self.send(cdp.network.set_user_agent_override(\r\n            user_agent=user_agent,\r\n            accept_language=accept_language,\r\n            platform=platform,\r\n        ))\r\n\r\n    async def set_geolocation(self, geolocation: Optional[tuple] = None):\r\n        \"\"\"Sets the User Agent via set_geolocation_override.\"\"\"\r\n        await self.send(cdp.browser.set_permission(\r\n            permission={\"name\": \"geolocation\"}, setting=\"granted\"\r\n        ))\r\n        await self.send(cdp.emulation.set_geolocation_override(\r\n            latitude=geolocation[0],\r\n            longitude=geolocation[1],\r\n            accuracy=100,\r\n        ))\r\n\r\n    def __getattr__(self, item):\r\n        \"\"\":meta private:\"\"\"\r\n        try:\r\n            return getattr(self.target, item)\r\n        except AttributeError:\r\n            raise\r\n\r\n    async def __aenter__(self):\r\n        \"\"\":meta private:\"\"\"\r\n        return self\r\n\r\n    async def __aexit__(self, exc_type, exc_val, exc_tb):\r\n        \"\"\":meta private:\"\"\"\r\n        await self.aclose()\r\n        if exc_type and exc_val:\r\n            raise exc_type(exc_val)\r\n\r\n    def __await__(self):\r\n        \"\"\"\r\n        Updates targets and wait for event listener to report idle.\r\n        Idle is reported when no new events are received for 1 second.\r\n        \"\"\"\r\n        return self.wait().__await__()\r\n\r\n    async def update_target(self):\r\n        target_info: cdp.target.TargetInfo = await self.send(\r\n            cdp.target.get_target_info(self.target_id), _is_update=True\r\n        )\r\n        self.target = target_info\r\n\r\n    async def send(\r\n        self,\r\n        cdp_obj: Generator[dict[str, Any], dict[str, Any], Any],\r\n        _is_update=True,\r\n    ) -> Any:\r\n        \"\"\"\r\n        Send a protocol command.\r\n        The commands are made using any of the cdp.<domain>.<method>()'s\r\n        and is used to send custom cdp commands as well.\r\n        :param cdp_obj: The generator object created by a cdp method\r\n        :param _is_update: Internal flag\r\n            Prevents infinite loop by skipping the registeration of handlers\r\n            when multiple calls to connection.send() are made.\r\n        \"\"\"\r\n        await self.aopen()\r\n        if not self.websocket or self.websocket.state is State.CLOSED:\r\n            return\r\n        if self.browser:\r\n            browser = self.browser\r\n            if browser.config:\r\n                if browser.config.expert:\r\n                    await self._prepare_expert()\r\n                if browser.config.headless:\r\n                    await self._prepare_headless()\r\n        if not self.listener or not self.listener.running:\r\n            self.listener = Listener(self)\r\n        try:\r\n            tx = Transaction(cdp_obj)\r\n            tx.connection = self\r\n            if not self.mapper:\r\n                self.__count__ = itertools.count(0)\r\n            tx.id = next(self.__count__)\r\n            self.mapper.update({tx.id: tx})\r\n            if not _is_update:\r\n                await self._register_handlers()\r\n            await self.websocket.send(tx.message)\r\n            with warnings.catch_warnings():\r\n                warnings.filterwarnings(\r\n                    \"ignore\",\r\n                    message=\"coroutine .* was never awaited\",\r\n                    category=RuntimeWarning,\r\n                )\r\n                try:\r\n                    return await tx\r\n                except ProtocolException as e:\r\n                    e.message += f\"\\ncommand:{tx.method}\\nparams:{tx.params}\"\r\n                    raise e\r\n        except Exception:\r\n            await self.aclose()\r\n\r\n    async def _register_handlers(self):\r\n        \"\"\"\r\n        Ensure that for current (event) handlers, the corresponding\r\n        domain is enabled in the protocol.\r\n        \"\"\"\r\n        # Save a copy of current enabled domains in a variable.\r\n        # At the end, this variable will hold the domains that\r\n        # are not represented by handlers, and can be removed.\r\n        enabled_domains = self.enabled_domains.copy()\r\n        for event_type in self.handlers.copy():\r\n            domain_mod = None\r\n            if len(self.handlers[event_type]) == 0:\r\n                self.handlers.pop(event_type)\r\n                continue\r\n            if isinstance(event_type, type):\r\n                domain_mod = util.cdp_get_module(event_type.__module__)\r\n            if domain_mod in self.enabled_domains:\r\n                # At this point, the domain is being used by a handler, so\r\n                # remove that domain from temp variable 'enabled_domains'.\r\n                if domain_mod in enabled_domains:\r\n                    enabled_domains.remove(domain_mod)\r\n                continue\r\n            elif domain_mod not in self.enabled_domains:\r\n                if domain_mod in (cdp.target, cdp.storage):\r\n                    continue\r\n                try:\r\n                    # Prevent infinite loops.\r\n                    logger.debug(\"Registered %s\", domain_mod)\r\n                    self.enabled_domains.append(domain_mod)\r\n                    await self.send(domain_mod.enable(), _is_update=True)\r\n                except BaseException:  # Don't error before request is sent\r\n                    logger.debug(\"\", exc_info=True)\r\n                    try:\r\n                        self.enabled_domains.remove(domain_mod)\r\n                    except BaseException:\r\n                        logger.debug(\"NOT GOOD\", exc_info=True)\r\n                        continue\r\n        for ed in enabled_domains:\r\n            # Items still present at this point are unused and need removal.\r\n            self.enabled_domains.remove(ed)\r\n\r\n    async def _prepare_headless(self):\r\n        return  # (This functionality has moved to a new location!)\r\n\r\n    async def _prepare_expert(self):\r\n        if getattr(self, \"_prep_expert_done\", None):\r\n            return\r\n        if self._owner:\r\n            part1 = \"Element.prototype._attachShadow = \"\r\n            part2 = \"Element.prototype.attachShadow\"\r\n            parts = part1 + part2\r\n            await self._send_oneshot(\r\n                cdp.page.add_script_to_evaluate_on_new_document(\r\n                    \"\"\"\r\n                    %s;\r\n                    Element.prototype.attachShadow = function () {\r\n                        return this._attachShadow( { mode: \"open\" } );\r\n                    };\r\n                    \"\"\" % parts\r\n                )\r\n            )\r\n            await self._send_oneshot(cdp.page.enable())\r\n        setattr(self, \"_prep_expert_done\", True)\r\n\r\n    async def _send_oneshot(self, cdp_obj):\r\n        tx = Transaction(cdp_obj)\r\n        tx.connection = self\r\n        tx.id = -2\r\n        self.mapper.update({tx.id: tx})\r\n        await self.websocket.send(tx.message)\r\n        try:\r\n            # In try/except since if browser connection sends this,\r\n            # then it raises an exception.\r\n            return await tx\r\n        except ProtocolException:\r\n            pass\r\n\r\n\r\nclass Listener:\r\n    def __init__(self, connection: Connection):\r\n        self.connection = connection\r\n        self.history = collections.deque()\r\n        self.max_history = 1000\r\n        self.task: asyncio.Future = None\r\n        is_interactive = getattr(sys, \"ps1\", sys.flags.interactive)\r\n        self._time_before_considered_idle = 0.10 if not is_interactive else 0.75  # noqa\r\n        self.idle = asyncio.Event()\r\n        self.run()\r\n\r\n    def run(self):\r\n        self.task = asyncio.create_task(self.listener_loop())\r\n\r\n    @property\r\n    def time_before_considered_idle(self):\r\n        return self._time_before_considered_idle\r\n\r\n    @time_before_considered_idle.setter\r\n    def time_before_considered_idle(self, seconds: Union[int, float]):\r\n        self._time_before_considered_idle = seconds\r\n\r\n    async def cancel(self):\r\n        if self.task and not self.task.cancelled():\r\n            self.task.cancel()\r\n            try:\r\n                await self.task\r\n            except asyncio.CancelledError:\r\n                pass\r\n\r\n    @property\r\n    def running(self):\r\n        if not self.task:\r\n            return False\r\n        if self.task.done():\r\n            return False\r\n        return True\r\n\r\n    async def listener_loop(self):\r\n        while True:\r\n            try:\r\n                msg = await asyncio.wait_for(\r\n                    self.connection.websocket.recv(),\r\n                    self.time_before_considered_idle,\r\n                )\r\n            except asyncio.TimeoutError:\r\n                self.idle.set()\r\n                # Pause for a moment.\r\n                await asyncio.sleep(self.time_before_considered_idle / 10)\r\n                continue\r\n            except (Exception,) as e:\r\n                logger.debug(\r\n                    \"Connection listener exception \"\r\n                    \"while reading websocket:\\n%s\", e\r\n                )\r\n                self.idle.set()\r\n                break\r\n            if not self.running:\r\n                # If we have been cancelled or otherwise stopped running,\r\n                # then break this loop.\r\n                self.idle.set()\r\n                break\r\n            self.idle.clear()  # Not \"idle\" anymore.\r\n            message = json.loads(msg)\r\n            if \"id\" in message:\r\n                if message[\"id\"] in self.connection.mapper:\r\n                    tx = self.connection.mapper.pop(message[\"id\"])\r\n                    logger.debug(\r\n                        \"Got answer for %s (message_id:%d)\", tx, message[\"id\"]\r\n                    )\r\n                    tx(**message)\r\n                else:\r\n                    if message[\"id\"] == -2:\r\n                        tx = self.connection.mapper.get(-2)\r\n                        if tx:\r\n                            tx(**message)\r\n                        continue\r\n            else:\r\n                # Probably an event\r\n                try:\r\n                    event = cdp.util.parse_json_event(message)\r\n                except Exception as e:\r\n                    logger.info(\r\n                        \"%s: %s during parsing of json from event : %s\"\r\n                        % (type(e).__name__, e.args, message),\r\n                        exc_info=True,\r\n                    )\r\n                    continue\r\n                except KeyError as e:\r\n                    logger.info(\"KeyError: %s\" % e, exc_info=True)\r\n                    continue\r\n                try:\r\n                    if type(event) in self.connection.handlers:\r\n                        callbacks = self.connection.handlers[type(event)]\r\n                    else:\r\n                        continue\r\n                    if not len(callbacks):\r\n                        continue\r\n                    for callback in callbacks:\r\n                        try:\r\n                            if (\r\n                                inspect.iscoroutinefunction(callback)\r\n                                or inspect.iscoroutine(callback)\r\n                            ):\r\n                                try:\r\n                                    asyncio.create_task(\r\n                                        callback(event, self.connection)\r\n                                    )\r\n                                except TypeError:\r\n                                    asyncio.create_task(callback(event))\r\n                            else:\r\n                                try:\r\n                                    callback(event, self.connection)\r\n                                except TypeError:\r\n                                    callback(event)\r\n                        except Exception as e:\r\n                            logger.warning(\r\n                                \"Exception in callback %s for event %s => %s\",\r\n                                callback,\r\n                                event.__class__.__name__,\r\n                                e,\r\n                                exc_info=True,\r\n                            )\r\n                            raise\r\n                except asyncio.CancelledError:\r\n                    break\r\n                except Exception:\r\n                    raise\r\n                continue\r\n\r\n    def __repr__(self):\r\n        s_idle = \"[idle]\" if self.idle.is_set() else \"[busy]\"\r\n        s_cache_length = f\"[cache size: {len(self.history)}]\"\r\n        s_running = f\"[running: {self.running}]\"\r\n        s = f\"{self.__class__.__name__} {s_running} {s_idle} {s_cache_length}>\"\r\n        return s\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/element.py",
    "content": "from __future__ import annotations\r\nimport asyncio\r\nimport logging\r\nimport pathlib\r\nimport secrets\r\nimport typing\r\nfrom contextlib import suppress\r\nfrom . import cdp_util as util\r\nfrom ._contradict import ContraDict\r\nfrom .config import PathLike\r\nimport mycdp as cdp\r\nimport mycdp.input_\r\nimport mycdp.dom\r\nimport mycdp.overlay\r\nimport mycdp.page\r\nimport mycdp.runtime\r\n\r\nlogger = logging.getLogger(__name__)\r\nif typing.TYPE_CHECKING:\r\n    from .tab import Tab\r\n\r\n\r\ndef create(\r\n    node: cdp.dom.Node,\r\n    tab: Tab, tree:\r\n    typing.Optional[cdp.dom.Node] = None\r\n):\r\n    \"\"\"\r\n    Factory for Elements.\r\n    This is used with Tab.query_selector(_all).\r\n    Since we already have the tree,\r\n    we don't need to fetch it for every single element.\r\n    :param node: cdp dom node representation\r\n    :type node: cdp.dom.Node\r\n    :param tab: the target object to which this element belongs\r\n    :type tab: Tab\r\n    :param tree: [Optional] the full node tree to which <node> belongs,\r\n        enhances performance. When not provided, you need to\r\n        call `await elem.update()` before using .children / .parent\r\n    :type tree:\r\n    \"\"\"\r\n    elem = Element(node, tab, tree)\r\n    return elem\r\n\r\n\r\nclass Element:\r\n    def __init__(\r\n        self, node: cdp.dom.Node, tab: Tab, tree: cdp.dom.Node = None\r\n    ):\r\n        \"\"\"\r\n        Represents an (HTML) DOM Element\r\n        :param node: cdp dom node representation\r\n        :type node: cdp.dom.Node\r\n        :param tab: the target object to which this element belongs\r\n        :type tab: Tab\r\n        \"\"\"\r\n        if not node:\r\n            raise Exception(\"Node cannot be None!\")\r\n        self._tab = tab\r\n        # if node.node_name == 'IFRAME':\r\n        #     self._node = node.content_document\r\n        self._node = node\r\n        self._tree = tree\r\n        self._parent = None\r\n        self._remote_object = None\r\n        self._attrs = ContraDict(silent=True)\r\n        self._make_attrs()\r\n\r\n    @property\r\n    def tag(self):\r\n        if self.node_name:\r\n            return self.node_name.lower()\r\n\r\n    @property\r\n    def tag_name(self):\r\n        return self.tag\r\n\r\n    @property\r\n    def node_id(self):\r\n        return self.node.node_id\r\n\r\n    @property\r\n    def backend_node_id(self):\r\n        return self.node.backend_node_id\r\n\r\n    @property\r\n    def node_type(self):\r\n        return self.node.node_type\r\n\r\n    @property\r\n    def node_name(self):\r\n        return self.node.node_name\r\n\r\n    @property\r\n    def local_name(self):\r\n        return self.node.local_name\r\n\r\n    @property\r\n    def node_value(self):\r\n        return self.node.node_value\r\n\r\n    @property\r\n    def parent_id(self):\r\n        return self.node.parent_id\r\n\r\n    @property\r\n    def child_node_count(self):\r\n        return self.node.child_node_count\r\n\r\n    @property\r\n    def attributes(self):\r\n        return self.node.attributes\r\n\r\n    @property\r\n    def document_url(self):\r\n        return self.node.document_url\r\n\r\n    @property\r\n    def base_url(self):\r\n        return self.node.base_url\r\n\r\n    @property\r\n    def public_id(self):\r\n        return self.node.public_id\r\n\r\n    @property\r\n    def system_id(self):\r\n        return self.node.system_id\r\n\r\n    @property\r\n    def internal_subset(self):\r\n        return self.node.internal_subset\r\n\r\n    @property\r\n    def xml_version(self):\r\n        return self.node.xml_version\r\n\r\n    @property\r\n    def value(self):\r\n        return self.node.value\r\n\r\n    @property\r\n    def pseudo_type(self):\r\n        return self.node.pseudo_type\r\n\r\n    @property\r\n    def pseudo_identifier(self):\r\n        return self.node.pseudo_identifier\r\n\r\n    @property\r\n    def shadow_root_type(self):\r\n        return self.node.shadow_root_type\r\n\r\n    @property\r\n    def frame_id(self):\r\n        return self.node.frame_id\r\n\r\n    @property\r\n    def content_document(self):\r\n        return self.node.content_document\r\n\r\n    @property\r\n    def shadow_roots(self):\r\n        return self.node.shadow_roots\r\n\r\n    @property\r\n    def template_content(self):\r\n        return self.node.template_content\r\n\r\n    @property\r\n    def pseudo_elements(self):\r\n        return self.node.pseudo_elements\r\n\r\n    @property\r\n    def imported_document(self):\r\n        return self.node.imported_document\r\n\r\n    @property\r\n    def distributed_nodes(self):\r\n        return self.node.distributed_nodes\r\n\r\n    @property\r\n    def is_svg(self):\r\n        return self.node.is_svg\r\n\r\n    @property\r\n    def compatibility_mode(self):\r\n        return self.node.compatibility_mode\r\n\r\n    @property\r\n    def assigned_slot(self):\r\n        return self.node.assigned_slot\r\n\r\n    @property\r\n    def tab(self):\r\n        return self._tab\r\n\r\n    def __getattr__(self, item):\r\n        # If attribute is not found on the element object,\r\n        # check if it is present in the element attributes\r\n        # (Eg. href=, src=, alt=).\r\n        # Returns None when attribute is not found,\r\n        # instead of raising AttributeError.\r\n        x = getattr(self.attrs, item, None)\r\n        if x:\r\n            return x\r\n\r\n    def __setattr__(self, key, value):\r\n        if key[0] != \"_\":\r\n            if key[1:] not in vars(self).keys():\r\n                self.attrs.__setattr__(key, value)\r\n                return\r\n        super().__setattr__(key, value)\r\n\r\n    def __setitem__(self, key, value):\r\n        if key[0] != \"_\":\r\n            if key[1:] not in vars(self).keys():\r\n                self.attrs[key] = value\r\n\r\n    def __getitem__(self, item):\r\n        return self.attrs.get(item, None)\r\n\r\n    async def save_to_dom_async(self):\r\n        \"\"\"Saves element to DOM.\"\"\"\r\n        self._remote_object = await self._tab.send(\r\n            cdp.dom.resolve_node(backend_node_id=self.backend_node_id)\r\n        )\r\n        await self._tab.send(\r\n            cdp.dom.set_outer_html(self.node_id, outer_html=str(self))\r\n        )\r\n        await self.update()\r\n\r\n    async def remove_from_dom_async(self):\r\n        \"\"\"Removes element from DOM.\"\"\"\r\n        await self.update()  # Ensure we have latest node_id\r\n        node = util.filter_recurse(\r\n            self._tree,\r\n            lambda node: node.backend_node_id == self.backend_node_id\r\n        )\r\n        if node:\r\n            await self.tab.send(cdp.dom.remove_node(node.node_id))\r\n        # self._tree = util.remove_from_tree(self.tree, self.node)\r\n\r\n    async def update(self, _node=None):\r\n        \"\"\"\r\n        Updates element to retrieve more properties.\r\n        For example this enables:\r\n        :py:obj:`~children` and :py:obj:`~parent` attributes.\r\n        Also resolves js object,\r\n        which is a stored object in :py:obj:`~remote_object`.\r\n        Usually you will get element nodes by the usage of:\r\n        :py:meth:`Tab.query_selector_all()`\r\n        :py:meth:`Tab.find_elements_by_text()`\r\n        Those elements are already updated and you can browse\r\n        through children directly.\r\n        \"\"\"\r\n        if _node:\r\n            doc = _node\r\n            # self._node = _node\r\n            # self._children.clear()\r\n            self._parent = None\r\n        else:\r\n            doc = await self._tab.send(cdp.dom.get_document(-1, True))\r\n            self._parent = None\r\n        # if self.node_name != \"IFRAME\":\r\n        updated_node = util.filter_recurse(\r\n            doc, lambda n: n.backend_node_id == self._node.backend_node_id\r\n        )\r\n        if updated_node:\r\n            logger.debug(\"Node changed, and has now been updated.\")\r\n            self._node = updated_node\r\n        self._tree = doc\r\n        self._remote_object = await self._tab.send(\r\n            cdp.dom.resolve_node(backend_node_id=self._node.backend_node_id)\r\n        )\r\n        # self.attrs.clear()\r\n        self._make_attrs()\r\n        if self.node_name != \"IFRAME\":\r\n            parent_node = util.filter_recurse(\r\n                doc, lambda n: n.node_id == self.node.parent_id\r\n            )\r\n            if not parent_node:\r\n                # Could happen if node is for example <html>\r\n                return self\r\n            self._parent = create(parent_node, tab=self._tab, tree=self._tree)\r\n        return self\r\n\r\n    @property\r\n    def node(self):\r\n        return self._node\r\n\r\n    @property\r\n    def tree(self) -> cdp.dom.Node:\r\n        return self._tree\r\n\r\n    @tree.setter\r\n    def tree(self, tree: cdp.dom.Node):\r\n        self._tree = tree\r\n\r\n    @property\r\n    def attrs(self):\r\n        \"\"\"\r\n        Attributes are stored here.\r\n        You can also set them directly on the element object.\r\n        \"\"\"\r\n        return self._attrs\r\n\r\n    @property\r\n    def parent(self) -> typing.Union[Element, None]:\r\n        \"\"\"Get the parent element (node) of current element (node).\"\"\"\r\n        if not self.tree:\r\n            raise RuntimeError(\r\n                \"Could not get parent since the element has no tree set.\"\r\n            )\r\n        parent_node = util.filter_recurse(\r\n            self.tree, lambda n: n.node_id == self.parent_id\r\n        )\r\n        if not parent_node:\r\n            return None\r\n        parent_element = create(parent_node, tab=self._tab, tree=self.tree)\r\n        return parent_element\r\n\r\n    @property\r\n    def children(self) -> typing.Union[typing.List[Element], str]:\r\n        \"\"\"\r\n        Returns the element's children.\r\n        Those children also have a children property\r\n        so that you can browse through the entire tree as well.\r\n        \"\"\"\r\n        _children = []\r\n        if self._node.node_name == \"IFRAME\":\r\n            # iframes are not the same as other nodes.\r\n            # The children of iframes are found under\r\n            # the .content_document property,\r\n            # which is more useful than the node itself.\r\n            frame = self._node.content_document\r\n            if not frame.child_node_count:\r\n                return []\r\n            for child in frame.children:\r\n                child_elem = create(child, self._tab, frame)\r\n                if child_elem:\r\n                    _children.append(child_elem)\r\n            # self._node = frame\r\n            return _children\r\n        elif not self.node.child_node_count:\r\n            return []\r\n        if self.node.children:\r\n            for child in self.node.children:\r\n                child_elem = create(child, self._tab, self.tree)\r\n                if child_elem:\r\n                    _children.append(child_elem)\r\n        return _children\r\n\r\n    @property\r\n    def remote_object(self) -> cdp.runtime.RemoteObject:\r\n        return self._remote_object\r\n\r\n    @property\r\n    def object_id(self) -> cdp.runtime.RemoteObjectId:\r\n        try:\r\n            return self.remote_object.object_id\r\n        except AttributeError:\r\n            pass\r\n\r\n    async def click_async(self):\r\n        \"\"\"Click the element.\"\"\"\r\n        self._remote_object = await self._tab.send(\r\n            cdp.dom.resolve_node(\r\n                backend_node_id=self.backend_node_id\r\n            )\r\n        )\r\n        arguments = [cdp.runtime.CallArgument(\r\n            object_id=self._remote_object.object_id\r\n        )]\r\n        script = 'sessionStorage.getItem(\"pxsid\") !== null;'\r\n        using_px = await self.tab.evaluate(script)\r\n        if not using_px:\r\n            await self.flash_async(0.25)\r\n        await self._tab.send(\r\n            cdp.runtime.call_function_on(\r\n                \"(el) => el.click()\",\r\n                object_id=self._remote_object.object_id,\r\n                arguments=arguments,\r\n                await_promise=True,\r\n                user_gesture=True,\r\n                return_by_value=True,\r\n            )\r\n        )\r\n\r\n    async def get_js_attributes_async(self):\r\n        return ContraDict(\r\n            await self.apply(\r\n                \"\"\"\r\n                function (e) {\r\n                    let o = {}\r\n                    for(let k in e){\r\n                        o[k] = e[k]\r\n                    }\r\n                    return o\r\n                }\r\n                \"\"\"\r\n            )\r\n        )\r\n\r\n    def __await__(self):\r\n        return self.update().__await__()\r\n\r\n    def __call__(self, js_method):\r\n        return self.apply(f\"(e) => e['{js_method}']()\")\r\n\r\n    async def apply(self, js_function, return_by_value=True):\r\n        \"\"\"\r\n        Apply javascript to this element.\r\n        The given js_function string should accept the js element as parameter,\r\n        and can be a arrow function, or function declaration.\r\n        Eg:\r\n            - '(elem) => {\r\n                    elem.value = \"blabla\"; consolelog(elem);\r\n                    alert(JSON.stringify(elem);\r\n                } '\r\n            - 'elem => elem.play()'\r\n            - function myFunction(elem) { alert(elem) }\r\n        :param js_function: JS function definition which received this element.\r\n        :param return_by_value:\r\n        \"\"\"\r\n        self._remote_object = await self._tab.send(\r\n            cdp.dom.resolve_node(backend_node_id=self.backend_node_id)\r\n        )\r\n        result: typing.Tuple[cdp.runtime.RemoteObject, typing.Any] = (\r\n            await self._tab.send(\r\n                cdp.runtime.call_function_on(\r\n                    js_function,\r\n                    object_id=self._remote_object.object_id,\r\n                    arguments=[\r\n                        cdp.runtime.CallArgument(\r\n                            object_id=self._remote_object.object_id\r\n                        )\r\n                    ],\r\n                    return_by_value=True,\r\n                    user_gesture=True,\r\n                )\r\n            )\r\n        )\r\n        try:\r\n            if result and result[0]:\r\n                if return_by_value:\r\n                    return result[0].value\r\n                return result[0]\r\n            elif result[1]:\r\n                return result[1]\r\n        except Exception:\r\n            return self\r\n\r\n    async def get_position_async(self, abs=False) -> Position:\r\n        if not self.parent or not self.object_id:\r\n            self._remote_object = await self._tab.send(\r\n                cdp.dom.resolve_node(backend_node_id=self.backend_node_id)\r\n            )\r\n            # await self.update()\r\n        try:\r\n            quads = await self.tab.send(\r\n                cdp.dom.get_content_quads(\r\n                    object_id=self.remote_object.object_id\r\n                )\r\n            )\r\n            if not quads:\r\n                raise Exception(\"Could not find position for %s \" % self)\r\n            pos = Position(quads[0])\r\n            if abs:\r\n                scroll_y = (await self.tab.evaluate(\"window.scrollY\")).value\r\n                scroll_x = (await self.tab.evaluate(\"window.scrollX\")).value\r\n                abs_x = pos.left + scroll_x + (pos.width / 2)\r\n                abs_y = pos.top + scroll_y + (pos.height / 2)\r\n                pos.abs_x = abs_x\r\n                pos.abs_y = abs_y\r\n            return pos\r\n        except IndexError:\r\n            logger.debug(\r\n                \"No content quads for %s. \"\r\n                \"Mostly caused by element which is not 'in plain sight'.\"\r\n                % self\r\n            )\r\n\r\n    async def mouse_click_async(\r\n        self,\r\n        button: str = \"left\",\r\n        buttons: typing.Optional[int] = 1,\r\n        modifiers: typing.Optional[int] = 0,\r\n        hold: bool = False,\r\n    ):\r\n        \"\"\"\r\n        Native click (on element).\r\n        :param button: str (default = \"left\")\r\n        :param buttons: which button (default 1 = left)\r\n        :param modifiers: *(Optional)*\r\n                Bit field representing pressed modifier keys.\r\n                Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).\r\n        \"\"\"\r\n        try:\r\n            center = (await self.get_position_async()).center\r\n        except AttributeError:\r\n            return\r\n        if not center:\r\n            logger.warning(\"Could not calculate box model for %s\", self)\r\n            return\r\n        logger.debug(\"Clicking on location: %.2f, %.2f\" % center)\r\n        script = 'sessionStorage.getItem(\"pxsid\") !== null;'\r\n        using_px = await self.tab.evaluate(script)\r\n        if not using_px:\r\n            asyncio.create_task(self.flash_async(0.25))\r\n        asyncio.create_task(\r\n            self._tab.send(\r\n                cdp.input_.dispatch_mouse_event(\r\n                    \"mousePressed\",\r\n                    x=center[0],\r\n                    y=center[1],\r\n                    modifiers=modifiers,\r\n                    button=cdp.input_.MouseButton(button),\r\n                    buttons=buttons,\r\n                    click_count=1,\r\n                )\r\n            ),\r\n        )\r\n        asyncio.create_task(\r\n            self._tab.send(\r\n                cdp.input_.dispatch_mouse_event(\r\n                    \"mouseReleased\",\r\n                    x=center[0],\r\n                    y=center[1],\r\n                    modifiers=modifiers,\r\n                    button=cdp.input_.MouseButton(button),\r\n                    buttons=buttons,\r\n                    click_count=1,\r\n                )\r\n            ),\r\n        )\r\n\r\n    async def mouse_click_with_offset_async(\r\n        self,\r\n        x: typing.Union[float, int],\r\n        y: typing.Union[float, int],\r\n        center: bool = False,\r\n        button: str = \"left\",\r\n        buttons: typing.Optional[int] = 1,\r\n        modifiers: typing.Optional[int] = 0,\r\n    ):\r\n        x_offset = int(x)\r\n        y_offset = int(y)\r\n        try:\r\n            position = (await self.get_position_async())\r\n            width = position.width\r\n            height = position.height\r\n            x_pos = position.left\r\n            y_pos = position.top\r\n            center_pos = (await self.get_position_async()).center\r\n        except AttributeError:\r\n            return\r\n        if not center_pos:\r\n            logger.warning(\"Could not calculate box model for %s\", self)\r\n            return\r\n        if center:\r\n            x_pos = center_pos[0]\r\n            y_pos = center_pos[1]\r\n            width = 0\r\n            height = 0\r\n            logger.debug(\"Clicking on location: %.2f, %.2f\" % center_pos)\r\n        else:\r\n            logger.debug(\"Clicking on location: %.2f, %.2f\" % (x_pos, y_pos))\r\n        script = 'sessionStorage.getItem(\"pxsid\") !== null;'\r\n        using_px = await self.tab.evaluate(script)\r\n        if not using_px:\r\n            asyncio.create_task(\r\n                self.flash_async(\r\n                    x_offset=x_offset - (width / 2),\r\n                    y_offset=y_offset - (height / 2),\r\n                ),\r\n            )\r\n        asyncio.create_task(\r\n            self._tab.send(\r\n                cdp.input_.dispatch_mouse_event(\r\n                    \"mousePressed\",\r\n                    x=x_pos + x_offset,\r\n                    y=y_pos + y_offset,\r\n                    modifiers=modifiers,\r\n                    button=cdp.input_.MouseButton(button),\r\n                    buttons=buttons,\r\n                    click_count=1,\r\n                )\r\n            )\r\n        )\r\n        asyncio.create_task(\r\n            self._tab.send(\r\n                cdp.input_.dispatch_mouse_event(\r\n                    \"mouseReleased\",\r\n                    x=x_pos + x_offset,\r\n                    y=y_pos + y_offset,\r\n                    modifiers=modifiers,\r\n                    button=cdp.input_.MouseButton(button),\r\n                    buttons=buttons,\r\n                    click_count=1,\r\n                )\r\n            ),\r\n        )\r\n\r\n    async def mouse_move_async(self):\r\n        \"\"\"\r\n        Moves the mouse to the element position.\r\n        When an element has an hover/mouseover effect, this triggers it.\r\n        \"\"\"\r\n        try:\r\n            center = (await self.get_position_async()).center\r\n        except AttributeError:\r\n            logger.debug(\"Did not find location for %s\", self)\r\n            return\r\n        logger.debug(\r\n            \"Mouse move to location %.2f, %.2f where %s is located\",\r\n            *center,\r\n            self,\r\n        )\r\n        await asyncio.gather(\r\n            self._tab.send(\r\n                cdp.input_.dispatch_mouse_event(\r\n                    \"mouseMoved\", x=center[0], y=center[1]\r\n                )\r\n            ),\r\n            self._tab.sleep(0.05),\r\n        )\r\n\r\n    async def mouse_drag_async(\r\n        self,\r\n        destination: typing.Union[Element, typing.Tuple[int, int]],\r\n        relative: bool = False,\r\n        steps: int = 1,\r\n    ):\r\n        \"\"\"\r\n        Drags an element to another element or target coordinates.\r\n        Dragging of elements should be supported by the site.\r\n        :param destination: Another element where to drag to,\r\n            or a tuple (x,y) of ints representing coordinates.\r\n        :type destination: Element or coordinate as x,y tuple\r\n        :param relative: when True, treats coordinate as relative.\r\n        For example (-100, 200) will move left 100px and down 200px.\r\n        :type relative:\r\n        :param steps: Move in <steps> points.\r\n            This could make it look more \"natural\" (default 1),\r\n            but also a lot slower. (For very smooth actions, use 50-100)\r\n        :type steps: int\r\n        \"\"\"\r\n        try:\r\n            start_point = (await self.get_position_async()).center\r\n        except AttributeError:\r\n            return\r\n        if not start_point:\r\n            logger.warning(\"Could not calculate box model for %s\", self)\r\n            return\r\n        end_point = None\r\n        if isinstance(destination, Element):\r\n            try:\r\n                end_point = (await destination.get_position_async()).center\r\n            except AttributeError:\r\n                return\r\n            if not end_point:\r\n                logger.warning(\r\n                    \"Could not calculate box model for %s\", destination\r\n                )\r\n                return\r\n        elif isinstance(destination, (tuple, list)):\r\n            if relative:\r\n                end_point = (\r\n                    start_point[0] + destination[0],\r\n                    start_point[1] + destination[1],\r\n                )\r\n            else:\r\n                end_point = destination\r\n        await self._tab.send(\r\n            cdp.input_.dispatch_mouse_event(\r\n                \"mousePressed\",\r\n                x=start_point[0],\r\n                y=start_point[1],\r\n                button=cdp.input_.MouseButton(\"left\"),\r\n            )\r\n        )\r\n        steps = 1 if (not steps or steps < 1) else steps\r\n        if steps == 1:\r\n            await self._tab.send(\r\n                cdp.input_.dispatch_mouse_event(\r\n                    \"mouseMoved\",\r\n                    x=end_point[0],\r\n                    y=end_point[1],\r\n                )\r\n            )\r\n        elif steps > 1:\r\n            step_size_x = (end_point[0] - start_point[0]) / steps\r\n            step_size_y = (end_point[1] - start_point[1]) / steps\r\n            pathway = [\r\n                (\r\n                    start_point[0] + step_size_x * i,\r\n                    start_point[1] + step_size_y * i,\r\n                )\r\n                for i in range(steps + 1)\r\n            ]\r\n            for point in pathway:\r\n                await self._tab.send(\r\n                    cdp.input_.dispatch_mouse_event(\r\n                        \"mouseMoved\",\r\n                        x=point[0],\r\n                        y=point[1],\r\n                    )\r\n                )\r\n                await asyncio.sleep(0)\r\n        await self._tab.send(\r\n            cdp.input_.dispatch_mouse_event(\r\n                type_=\"mouseReleased\",\r\n                x=end_point[0],\r\n                y=end_point[1],\r\n                button=cdp.input_.MouseButton(\"left\"),\r\n            )\r\n        )\r\n\r\n    async def scroll_into_view_async(self):\r\n        \"\"\"Scrolls element into view.\"\"\"\r\n        try:\r\n            await self.tab.send(\r\n                cdp.dom.scroll_into_view_if_needed(\r\n                    backend_node_id=self.backend_node_id\r\n                )\r\n            )\r\n        except Exception as e:\r\n            logger.debug(\"Could not scroll into view: %s\", e)\r\n            return\r\n        # await self.apply(\"\"\"(el) => el.scrollIntoView(false)\"\"\")\r\n\r\n    async def clear_input_async(self):\r\n        \"\"\"Clears an input field.\"\"\"\r\n        try:\r\n            await self.apply('function (element) { element.value = \"\" } ')\r\n        except Exception as e:\r\n            logger.debug(\"Could not clear element field: %s\", e)\r\n        return\r\n\r\n    async def send_keys_async(self, text: str):\r\n        \"\"\"\r\n        Send text to an input field, or any other html element.\r\n        Hint: If you ever get stuck where using py:meth:`~click`\r\n        does not work, sending the keystroke \\\\n or \\\\r\\\\n\r\n        or a spacebar works wonders!\r\n        :param text: text to send\r\n        :return: None\r\n        \"\"\"\r\n        await self.apply(\"(elem) => elem.focus()\")\r\n        [\r\n            await self._tab.send(\r\n                cdp.input_.dispatch_key_event(\"char\", text=char)\r\n            )\r\n            for char in list(text)\r\n        ]\r\n\r\n    async def send_file_async(self, *file_paths: PathLike):\r\n        \"\"\"\r\n        Some form input require a file (upload).\r\n        A full path needs to be provided.\r\n        This method sends 1 or more file(s) to the input field.\r\n        Make sure the field accepts multiple files in order to send more files.\r\n        (Otherwise the browser might crash.)\r\n        Example:\r\n        `await fileinputElement.send_file('c:/tmp/img.png', 'c:/dir/lol.gif')`\r\n        \"\"\"\r\n        file_paths = [str(p) for p in file_paths]\r\n        await self._tab.send(\r\n            cdp.dom.set_file_input_files(\r\n                files=[*file_paths],\r\n                backend_node_id=self.backend_node_id,\r\n                object_id=self.object_id,\r\n            )\r\n        )\r\n\r\n    async def focus_async(self):\r\n        \"\"\"Focus the current element. Often useful in form (select) fields.\"\"\"\r\n        return await self.apply(\"(element) => element.focus()\")\r\n\r\n    async def select_option_async(self):\r\n        \"\"\"\r\n        For form (select) fields. When you have queried the options\r\n        you can call this method on the option object.\r\n        Calling :func:`option.select_option()` uses option as selected value.\r\n        (Does not work in all cases.)\r\n        \"\"\"\r\n        if self.node_name == \"OPTION\":\r\n            await self.apply(\r\n                \"\"\"\r\n                (o) => {\r\n                    o.selected = true;\r\n                    o.dispatchEvent(new Event(\r\n                        'change', {view: window,bubbles: true})\r\n                    )\r\n                }\r\n                \"\"\"\r\n            )\r\n\r\n    async def set_value_async(self, value):\r\n        await self._tab.send(\r\n            cdp.dom.set_node_value(node_id=self.node_id, value=value)\r\n        )\r\n\r\n    async def set_text_async(self, value):\r\n        if not self.node_type == 3:\r\n            if self.child_node_count == 1:\r\n                child_node = self.children[0]\r\n                await child_node.set_text_async(value)\r\n                await self.update()\r\n                return\r\n            else:\r\n                raise RuntimeError(\"Could only set value of text nodes.\")\r\n        await self.update()\r\n        await self._tab.send(\r\n            cdp.dom.set_node_value(node_id=self.node_id, value=value)\r\n        )\r\n\r\n    async def get_html_async(self):\r\n        return await self._tab.send(\r\n            cdp.dom.get_outer_html(\r\n                backend_node_id=self.backend_node_id,\r\n                include_shadow_dom=True,\r\n            )\r\n        )\r\n\r\n    @property\r\n    def text_fragment(self) -> str:\r\n        \"\"\"Gets the text content of this specific element node.\"\"\"\r\n        text_node = util.filter_recurse(self.node, lambda n: n.node_type == 3)\r\n        if text_node:\r\n            return text_node.node_value.strip()\r\n        return \"\"\r\n\r\n    @property\r\n    def text(self):\r\n        \"\"\"\r\n        Gets the text contents of this element and child nodes, concatenated.\r\n        Note: This includes text in the form of script content, (text nodes).\r\n        \"\"\"\r\n        with suppress(Exception):\r\n            if self.node.node_name.lower() in [\"input\", \"textarea\"]:\r\n                input_node = self.node.shadow_roots[0].children[-1].children[0]\r\n                if input_node:\r\n                    return input_node.node_value\r\n        text_nodes = util.filter_recurse_all(\r\n            self.node, lambda n: n.node_type == 3\r\n        )\r\n        return \" \".join([n.node_value for n in text_nodes]).strip()\r\n\r\n    @property\r\n    def text_all(self):\r\n        \"\"\"Same as text(). Kept for backwards compatibility.\"\"\"\r\n        with suppress(Exception):\r\n            if self.node.node_name.lower() in [\"input\", \"textarea\"]:\r\n                input_node = self.node.shadow_roots[0].children[-1].children[0]\r\n                if input_node:\r\n                    return input_node.node_value\r\n        text_nodes = util.filter_recurse_all(\r\n            self.node, lambda n: n.node_type == 3\r\n        )\r\n        return \" \".join([n.node_value for n in text_nodes]).strip()\r\n\r\n    async def query_selector_all_async(self, selector: str):\r\n        \"\"\"Like JS querySelectorAll()\"\"\"\r\n        await self.update()\r\n        return await self.tab.query_selector_all(selector, _node=self)\r\n\r\n    async def query_selector_async(self, selector: str):\r\n        \"\"\"Like JS querySelector()\"\"\"\r\n        await self.update()\r\n        return await self.tab.query_selector(selector, self)\r\n\r\n    async def save_screenshot_async(\r\n        self,\r\n        filename: typing.Optional[PathLike] = \"auto\",\r\n        format: typing.Optional[str] = \"png\",\r\n        scale: typing.Optional[typing.Union[int, float]] = 1,\r\n    ):\r\n        \"\"\"\r\n        Saves a screenshot of this element (only).\r\n        This is not the same as :py:obj:`Tab.save_screenshot`,\r\n        which saves a \"regular\" screenshot.\r\n        When the element is hidden, or has no size,\r\n        or is otherwise not capturable, a RuntimeError is raised.\r\n        :param filename: uses this as the save path\r\n        :type filename: PathLike\r\n        :param format: jpeg or png (defaults to png)\r\n        :type format: str\r\n        :param scale: the scale of the screenshot,\r\n         eg: 1 = size as is, 2 = double, 0.5 is half.\r\n        :return: the path/filename of saved screenshot\r\n        :rtype: str\r\n        \"\"\"\r\n        import urllib.parse\r\n        import datetime\r\n        import base64\r\n\r\n        pos = await self.get_position_async()\r\n        if not pos:\r\n            raise RuntimeError(\r\n                \"Could not determine position of element. \"\r\n                \"Probably because it's not in view, or hidden.\"\r\n            )\r\n        viewport = pos.to_viewport(scale)\r\n        path = None\r\n        await self.tab.sleep()\r\n        if not filename or filename == \"auto\":\r\n            parsed = urllib.parse.urlparse(self.tab.target.url)\r\n            parts = parsed.path.split(\"/\")\r\n            last_part = parts[-1]\r\n            last_part = last_part.rsplit(\"?\", 1)[0]\r\n            dt_str = datetime.datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\")\r\n            candidate = f\"{parsed.hostname}__{last_part}_{dt_str}\"\r\n            ext = \"\"\r\n            if format.lower() in [\"jpg\", \"jpeg\"]:\r\n                ext = \".jpg\"\r\n                format = \"jpeg\"\r\n            elif format.lower() in [\"png\"]:\r\n                ext = \".png\"\r\n                format = \"png\"\r\n            path = pathlib.Path(candidate + ext)\r\n        else:\r\n            if filename.lower().endswith(\".png\"):\r\n                format = \"png\"\r\n            elif (\r\n                filename.lower().endswith(\".jpg\")\r\n                or filename.lower().endswith(\".jpeg\")\r\n            ):\r\n                format = \"jpeg\"\r\n            path = pathlib.Path(filename)\r\n        path.parent.mkdir(parents=True, exist_ok=True)\r\n        data = await self._tab.send(\r\n            cdp.page.capture_screenshot(\r\n                format, clip=viewport, capture_beyond_viewport=True\r\n            )\r\n        )\r\n        if not data:\r\n            from .connection import ProtocolException\r\n\r\n            raise ProtocolException(\r\n                \"Could not take screenshot. \"\r\n                \"Most possible cause is the page has not finished loading yet.\"\r\n            )\r\n        data_bytes = base64.b64decode(data)\r\n        if not path:\r\n            raise RuntimeError(\"Invalid filename or path: '%s'\" % filename)\r\n        path.write_bytes(data_bytes)\r\n        return str(path)\r\n\r\n    async def flash_async(\r\n        self,\r\n        duration: typing.Union[float, int] = 0.5,\r\n        color: typing.Optional[str] = \"EE4488\",\r\n        x_offset: typing.Union[float, int] = 0,\r\n        y_offset: typing.Union[float, int] = 0,\r\n    ):\r\n        \"\"\"\r\n        Displays for a short time a red dot on the element.\r\n        (Only if the element itself is visible)\r\n        :param coords: x,y\r\n        :param duration: seconds (default 0.5)\r\n        \"\"\"\r\n        from .connection import ProtocolException\r\n\r\n        if not self.remote_object:\r\n            try:\r\n                self._remote_object = await self.tab.send(\r\n                    cdp.dom.resolve_node(backend_node_id=self.backend_node_id)\r\n                )\r\n            except ProtocolException:\r\n                return\r\n        try:\r\n            pos = await self.get_position_async()\r\n        except (Exception,):\r\n            logger.debug(\"flash() : Could not determine position.\")\r\n            return\r\n        style = (\r\n            \"position:absolute;z-index:99999999;padding:0;margin:0;\"\r\n            \"left:{:.1f}px; top: {:.1f}px; opacity:0.7;\"\r\n            \"width:8px;height:8px;border-radius:50%;background:#{};\"\r\n            \"animation:show-pointer-ani {:.2f}s ease 1;\"\r\n        ).format(\r\n            pos.center[0] + x_offset - 4,  # -4 to account for the circle\r\n            pos.center[1] + y_offset - 4,  # -4 to account for the circle\r\n            color,\r\n            duration,\r\n        )\r\n        script = (\r\n            \"\"\"\r\n            (targetElement) => {{\r\n                var css = document.styleSheets[0];\r\n                for( let css of [...document.styleSheets]) {{\r\n                    try {{\r\n                        css.insertRule(`\r\n                        @keyframes show-pointer-ani {{\r\n                              0% {{ opacity: 1; transform: scale(2, 2);}}\r\n                              25% {{ transform: scale(5,5) }}\r\n                              50% {{ transform: scale(3, 3);}}\r\n                              75%: {{ transform: scale(2,2) }}\r\n                              100% {{ transform: scale(1, 1); opacity: 0;}}\r\n                        }}`,css.cssRules.length);\r\n                        break;\r\n                    }} catch (e) {{\r\n                        console.log(e)\r\n                    }}\r\n                }};\r\n                var _d = document.createElement('div');\r\n                _d.style = `{0:s}`;\r\n                _d.id = `{1:s}`;\r\n                document.body.insertAdjacentElement('afterBegin', _d);\r\n                setTimeout(\r\n                    () => document.getElementById('{1:s}').remove(), {2:d}\r\n                );\r\n            }}\r\n            \"\"\".format(\r\n                style,\r\n                secrets.token_hex(8),\r\n                int(duration * 1000),\r\n            )\r\n            .replace(\"  \", \"\")\r\n            .replace(\"\\n\", \"\")\r\n        )\r\n        try:\r\n            arguments = [cdp.runtime.CallArgument(\r\n                object_id=self._remote_object.object_id\r\n            )]\r\n            await self._tab.send(\r\n                cdp.runtime.call_function_on(\r\n                    script,\r\n                    object_id=self._remote_object.object_id,\r\n                    arguments=arguments,\r\n                    await_promise=True,\r\n                    user_gesture=True,\r\n                )\r\n            )\r\n        except Exception:\r\n            pass\r\n\r\n    async def highlight_overlay_async(self):\r\n        \"\"\"\r\n        Highlights the element devtools-style.\r\n        To remove the highlight, call the method again.\r\n        \"\"\"\r\n        if getattr(self, \"_is_highlighted\", False):\r\n            del self._is_highlighted\r\n            await self.tab.send(cdp.overlay.hide_highlight())\r\n            await self.tab.send(cdp.dom.disable())\r\n            await self.tab.send(cdp.overlay.disable())\r\n            return\r\n        await self.tab.send(cdp.dom.enable())\r\n        await self.tab.send(cdp.overlay.enable())\r\n        conf = cdp.overlay.HighlightConfig(\r\n            show_info=True, show_extension_lines=True, show_styles=True\r\n        )\r\n        await self.tab.send(\r\n            cdp.overlay.highlight_node(\r\n                highlight_config=conf, backend_node_id=self.backend_node_id\r\n            )\r\n        )\r\n        setattr(self, \"_is_highlighted\", 1)\r\n\r\n    async def record_video_async(\r\n        self,\r\n        filename: typing.Optional[str] = None,\r\n        folder: typing.Optional[str] = None,\r\n        duration: typing.Optional[typing.Union[int, float]] = None,\r\n    ):\r\n        \"\"\"\r\n        Experimental option.\r\n        :param filename: the desired filename\r\n        :param folder: the download folder path\r\n        :param duration: record for this many seconds and then download\r\n        On html5 video nodes,\r\n        you can call this method to start recording of the video.\r\n        When any of the follow happens, the video recorded will be downloaded:\r\n            - video ends\r\n            - calling videoelement('pause')\r\n            - video stops\r\n        \"\"\"\r\n        if self.node_name != \"VIDEO\":\r\n            raise RuntimeError(\r\n                \"record_video() can only be called on html5 video elements\"\r\n            )\r\n        if not folder:\r\n            directory_path = pathlib.Path.cwd() / \"downloads\"\r\n        else:\r\n            directory_path = pathlib.Path(folder)\r\n        directory_path.mkdir(exist_ok=True)\r\n        await self._tab.send(\r\n            cdp.browser.set_download_behavior(\r\n                \"allow\", download_path=str(directory_path)\r\n            )\r\n        )\r\n        await self(\"pause\")\r\n        dtm = 'document.title + \".mp4\"'\r\n        await self.apply(\r\n            \"\"\"\r\n            function extractVid(vid) {{\r\n                    var duration = {duration:.1f};\r\n                    var stream = vid.captureStream();\r\n                    var mr = new MediaRecorder(\r\n                        stream, {{audio:true, video:true}}\r\n                    )\r\n                    mr.ondataavailable  = function(e) {{\r\n                        vid['_recording'] = false\r\n                        var blob = e.data;\r\n                        f = new File(\r\n                            [blob], {{name: {filename}, type:'octet/stream'}}\r\n                        );\r\n                        var objectUrl = URL.createObjectURL(f);\r\n                        var link = document.createElement('a');\r\n                        link.setAttribute('href', objectUrl)\r\n                        link.setAttribute('download', {filename})\r\n                        link.style.display = 'none'\r\n                        document.body.appendChild(link)\r\n                        link.click()\r\n                        document.body.removeChild(link)\r\n                    }}\r\n                    mr.start()\r\n                    vid.addEventListener('ended' , (e) => mr.stop())\r\n                    vid.addEventListener('pause' , (e) => mr.stop())\r\n                    vid.addEventListener('abort', (e) => mr.stop())\r\n                    if ( duration ) {{\r\n                        setTimeout(\r\n                            () => {{ vid.pause(); vid.play() }}, duration\r\n                        );\r\n                    }}\r\n                    vid['_recording'] = true\r\n            ;}}\r\n            \"\"\".format(\r\n                filename=f'\"{filename}\"' if filename else dtm,\r\n                duration=int(duration * 1000) if duration else 0,\r\n            )\r\n        )\r\n        await self(\"play\")\r\n        await self._tab\r\n\r\n    async def is_recording_async(self):\r\n        return await self.apply('(vid) => vid[\"_recording\"]')\r\n\r\n    def _make_attrs(self):\r\n        sav = None\r\n        if self.node.attributes:\r\n            for i, a in enumerate(self.node.attributes):\r\n                if i == 0 or i % 2 == 0:\r\n                    if a == \"class\":\r\n                        a = \"class_\"\r\n                    sav = a\r\n                else:\r\n                    if sav:\r\n                        self.attrs[sav] = a\r\n\r\n    def __eq__(self, other: Element) -> bool:\r\n        # if other.__dict__.values() == self.__dict__.values():\r\n        #     return True\r\n        if other.backend_node_id and self.backend_node_id:\r\n            return other.backend_node_id == self.backend_node_id\r\n        return False\r\n\r\n    def __repr__(self):\r\n        tag_name = self.node.node_name.lower()\r\n        content = \"\"\r\n        # Collect all text from this leaf.\r\n        if self.child_node_count:\r\n            if self.child_node_count == 1:\r\n                if self.children:\r\n                    content += str(self.children[0])\r\n            elif self.child_node_count > 1:\r\n                if self.children:\r\n                    for child in self.children:\r\n                        content += str(child)\r\n        if self.node.node_type == 3:  # Could be a text node\r\n            content += self.node_value\r\n            # Return text only. (No tag names)\r\n            # This makes it look most natural.\r\n            return content\r\n        attrs = \" \".join(\r\n            [\r\n                f'{k if k != \"class_\" else \"class\"}=\"{v}\"'\r\n                for k, v in self.attrs.items()\r\n            ]\r\n        )\r\n        s = f\"<{tag_name} {attrs}>{content}</{tag_name}>\"\r\n        return s\r\n\r\n\r\nclass Position(cdp.dom.Quad):\r\n    \"\"\"Helper class for element-positioning.\"\"\"\r\n\r\n    def __init__(self, points):\r\n        super().__init__(points)\r\n        (\r\n            self.left,\r\n            self.top,\r\n            self.right,\r\n            self.top,\r\n            self.right,\r\n            self.bottom,\r\n            self.left,\r\n            self.bottom,\r\n        ) = points\r\n        self.abs_x: float = 0\r\n        self.abs_y: float = 0\r\n        self.x = self.left\r\n        self.y = self.top\r\n        self.height, self.width = (\r\n            self.bottom - self.top, self.right - self.left\r\n        )\r\n        self.center = (\r\n            self.left + (self.width / 2),\r\n            self.top + (self.height / 2),\r\n        )\r\n\r\n    def to_viewport(self, scale=1):\r\n        return cdp.page.Viewport(\r\n            x=self.x,\r\n            y=self.y,\r\n            width=self.width,\r\n            height=self.height,\r\n            scale=scale,\r\n        )\r\n\r\n    def __repr__(self):\r\n        return (\r\n            f\"\"\"<Position(x={self.left}, y={self.top},\r\n            width={self.width}, height={self.height})>\r\n            \"\"\"\r\n        )\r\n\r\n\r\nasync def resolve_node(tab: Tab, node_id: cdp.dom.NodeId):\r\n    remote_obj: cdp.runtime.RemoteObject = await tab.send(\r\n        cdp.dom.resolve_node(node_id=node_id)\r\n    )\r\n    node_id: cdp.dom.NodeId = await tab.send(cdp.dom.request_node(\r\n        remote_obj.object_id\r\n    ))\r\n    node: cdp.dom.Node = await tab.send(cdp.dom.describe_node(node_id))\r\n    return node\r\n"
  },
  {
    "path": "seleniumbase/undetected/cdp_driver/tab.py",
    "content": "from __future__ import annotations\r\nimport asyncio\r\nimport base64\r\nimport datetime\r\nimport logging\r\nimport pathlib\r\nimport re\r\nimport sys\r\nimport urllib.parse\r\nimport warnings\r\nfrom contextlib import suppress\r\nfrom filelock import FileLock\r\nfrom seleniumbase import config as sb_config\r\nfrom seleniumbase.fixtures import constants\r\nfrom seleniumbase.fixtures import js_utils\r\nfrom seleniumbase.fixtures import page_utils\r\nfrom seleniumbase.fixtures import shared_utils\r\nfrom typing import Dict, List, Union, Optional, Tuple\r\nfrom . import browser as cdp_browser\r\nfrom . import element\r\nfrom . import cdp_util as util\r\nfrom .config import PathLike\r\nfrom .connection import Connection, ProtocolException\r\nimport mycdp as cdp\r\n\r\nlogger = logging.getLogger(__name__)\r\n\r\n\r\nclass Tab(Connection):\r\n    \"\"\"\r\n    :ref:`tab` is the controlling mechanism/connection to a 'target',\r\n    for most of us 'target' can be read as 'tab'. However it could also\r\n    be an iframe, serviceworker or background script for example,\r\n    although there isn't much to control for those.\r\n    If you open a new window by using\r\n        :py:meth:`browser.get(..., new_window=True)`\r\n    Your url will open a new window. This window is a 'tab'.\r\n    When you browse to another page, the tab will be the same (browser view).\r\n    It's important to keep some reference to tab objects, in case you're\r\n    done interacting with elements and want to operate on the page level again.\r\n\r\n    Custom CDP commands\r\n    ---------------------------\r\n    Tab object provide many useful and often-used methods. It is also possible\r\n    to utilize the included cdp classes to to something totally custom.\r\n\r\n    The cdp package is a set of so-called \"domains\" with each having methods,\r\n    events and types.\r\n    To send a cdp method, for example :py:obj:`cdp.page.navigate`,\r\n    you'll have to check whether the method accepts any parameters\r\n    and whether they are required or not.\r\n\r\n    You can use:\r\n\r\n    ```python\r\n    await tab.send(cdp.page.navigate(url='https://Your-URL-Here'))\r\n    ```\r\n\r\n    So tab.send() accepts a generator object,\r\n    which is created by calling a cdp method.\r\n    This way you can build very detailed and customized commands.\r\n    (Note: Finding correct command combos can be a time-consuming task.\r\n     A whole bunch of useful methods have been added,\r\n     preferably having the same apis or lookalikes, as in selenium.)\r\n\r\n    Some useful, often needed and simply required methods\r\n    ===================================================================\r\n\r\n    :py:meth:`~find`  |  find(text)\r\n    ----------------------------------------\r\n    Finds and returns a single element by text match.\r\n    By default, returns the first element found.\r\n    Much more powerful is the best_match flag,\r\n    although also much more expensive.\r\n    When no match is found, it will retry for <timeout> seconds (default: 10),\r\n    so this is also suitable to use as wait condition.\r\n\r\n    :py:meth:`~find` |  find(text, best_match=True) or find(text, True)\r\n    -----------------------------------------------------------------------\r\n    Much more powerful (and expensive) than the above is\r\n    the use of the `find(text, best_match=True)` flag.\r\n    It will still return 1 element, but when multiple matches are found,\r\n    it picks the one having the most similar text length.\r\n    How would that help?\r\n    For example, you search for \"login\",\r\n    you'd probably want the \"login\" button element,\r\n    and not thousands of scripts/meta/headings,\r\n    which happens to contain a string of \"login\".\r\n\r\n    When no match is found, it will retry for <timeout> seconds (default: 10),\r\n    so this is also suitable to use as wait condition.\r\n\r\n    :py:meth:`~select` | select(selector)\r\n    ----------------------------------------\r\n    Finds and returns a single element by css selector match.\r\n    When no match is found, it will retry for <timeout> seconds (default: 10),\r\n    so this is also suitable to use as wait condition.\r\n\r\n    :py:meth:`~select_all` | select_all(selector)\r\n    ------------------------------------------------\r\n    Finds and returns all elements by css selector match.\r\n    When no match is found, it will retry for <timeout> seconds (default: 10),\r\n    so this is also suitable to use as wait condition.\r\n\r\n    await :py:obj:`Tab`\r\n    ---------------------------\r\n    Calling `await tab` will do a lot of stuff under the hood,\r\n    and ensures all references are up to date.\r\n    Also it allows for the script to \"breathe\",\r\n    as it is oftentime faster than your browser or webpage.\r\n    So whenever you get stuck and things crashes or element could not be found,\r\n    you should probably let it \"breathe\" by calling `await page`\r\n    and/or `await page.sleep()`.\r\n\r\n    It ensures :py:obj:`~url` will be updated to the most recent one,\r\n    which is quite important in some other methods.\r\n\r\n    Using other and custom CDP commands\r\n    ======================================================\r\n    Using the included cdp module, you can easily craft commands,\r\n    which will always return an generator object.\r\n    This generator object can be easily sent to the :py:meth:`~send` method.\r\n\r\n    :py:meth:`~send`\r\n    ---------------------------\r\n    This is probably the most important method,\r\n    although you won't ever call it, unless you want to go really custom.\r\n    The send method accepts a :py:obj:`cdp` command.\r\n    Each of which can be found in the cdp section.\r\n\r\n    When you import * from this package, cdp will be in your namespace,\r\n    and contains all domains/actions/events you can act upon.\r\n    \"\"\"\r\n    browser: cdp_browser.Browser\r\n    _download_behavior: List[str] = None\r\n\r\n    def __init__(\r\n        self,\r\n        websocket_url: str,\r\n        target: cdp.target.TargetInfo,\r\n        browser: Optional[\"cdp_browser.Browser\"] = None,\r\n        **kwargs,\r\n    ):\r\n        super().__init__(websocket_url, target, browser, **kwargs)\r\n        self.browser = browser\r\n        self._dom = None\r\n        self._window_id = None\r\n\r\n    async def __aenter__(self):\r\n        return self\r\n\r\n    async def __aexit__(self, exc_type, exc_val, exc_tb):\r\n        await self.aclose()\r\n        if exc_type and exc_val:\r\n            raise exc_type(exc_val)\r\n\r\n    @property\r\n    def inspector_url(self):\r\n        \"\"\"\r\n        Get the inspector url.\r\n        This url can be used in another browser to show you\r\n        the devtools interface for current tab.\r\n        Useful for debugging and headless mode.\r\n        \"\"\"\r\n        return f\"http://{self.browser.config.host}:{self.browser.config.port}/devtools/inspector.html?ws={self.websocket_url[5:]}\"  # noqa\r\n\r\n    def inspector_open(self):\r\n        import webbrowser\r\n\r\n        webbrowser.open(self.inspector_url, new=2)\r\n\r\n    async def open_external_inspector(self):\r\n        \"\"\"\r\n        Opens the system's browser containing the devtools inspector page\r\n        for this tab. Could be handy, especially to debug in headless mode.\r\n        \"\"\"\r\n        import webbrowser\r\n\r\n        webbrowser.open(self.inspector_url)\r\n\r\n    async def find(\r\n        self,\r\n        text: str,\r\n        best_match: bool = False,\r\n        return_enclosing_element: bool = True,\r\n        timeout: Union[int, float] = 10,\r\n    ):\r\n        \"\"\"\r\n        Find single element by text.\r\n        Can also be used to wait for such element to appear.\r\n        :param text:\r\n         Text to search for. Note: Script contents are also considered text.\r\n        :type text: str\r\n        :param best_match:  :param best_match:\r\n         When True (default), it will return the element which has the most\r\n         comparable string length. This could help a lot. Eg:\r\n         If you search for \"login\", you probably want the login button element,\r\n         and not thousands of tags/scripts containing a \"login\" string.\r\n         When False, it returns just the first match (but is way faster).\r\n        :type best_match: bool\r\n        :param return_enclosing_element:\r\n            Since we deal with nodes instead of elements,\r\n            the find function most often returns so called text nodes,\r\n            which is actually a element of plain text,\r\n            which is the somehow imaginary \"child\" of a \"span\", \"p\", \"script\"\r\n            or any other elements which have text between their opening\r\n            and closing tags.\r\n            Most often when we search by text, we actually aim for the\r\n            element containing the text instead of a lousy plain text node,\r\n            so by default the containing element is returned.\r\n            There are exceptions. Eg:\r\n            Elements that use the \"placeholder=\" property.\r\n        :type return_enclosing_element: bool\r\n        :param timeout:\r\n         Raise timeout exception when after this many seconds nothing is found.\r\n        :type timeout: float,int\r\n        \"\"\"\r\n        loop = asyncio.get_running_loop()\r\n        start_time = loop.time()\r\n        text = text.strip()\r\n        item = None\r\n        try:\r\n            item = await self.find_element_by_text(\r\n                text, best_match, return_enclosing_element\r\n            )\r\n        except (Exception, TypeError):\r\n            pass\r\n        while not item:\r\n            await self\r\n            item = await self.find_element_by_text(\r\n                text, best_match, return_enclosing_element\r\n            )\r\n            if loop.time() - start_time > timeout:\r\n                raise asyncio.TimeoutError(\r\n                    \"Time ran out while waiting for: {%s}\" % text\r\n                )\r\n            await self.sleep(0.5)\r\n        return item\r\n\r\n    async def select(\r\n        self,\r\n        selector: str,\r\n        timeout: Union[int, float] = 10,\r\n    ) -> element.Element:\r\n        \"\"\"\r\n        Find a single element by css selector.\r\n        Can also be used to wait for such an element to appear.\r\n        :param selector: css selector,\r\n         eg a[href], button[class*=close], a > img[src]\r\n        :type selector: str\r\n        :param timeout:\r\n         Raise timeout exception when after this many seconds nothing is found.\r\n        :type timeout: float,int\r\n        \"\"\"\r\n        return await self.wait_for(selector=selector, timeout=timeout)\r\n\r\n    async def find_all(\r\n        self,\r\n        text: str,\r\n        timeout: Union[int, float] = 10,\r\n    ) -> List[element.Element]:\r\n        \"\"\"\r\n        Find multiple elements by text.\r\n        Can also be used to wait for such elements to appear.\r\n        :param text: Text to search for.\r\n        Note: Script contents are also considered text.\r\n        :type text: str\r\n        :param timeout:\r\n         Raise timeout exception when after this many seconds nothing is found.\r\n        :type timeout: float,int\r\n        \"\"\"\r\n        loop = asyncio.get_running_loop()\r\n        now = loop.time()\r\n        text = text.strip()\r\n        items = []\r\n        try:\r\n            items = await self.find_elements_by_text(text)\r\n        except (Exception, TypeError):\r\n            pass\r\n        while not items:\r\n            await self\r\n            items = await self.find_elements_by_text(text)\r\n            if loop.time() - now > timeout:\r\n                raise asyncio.TimeoutError(\r\n                    \"Time ran out while waiting for: {%s}\" % text\r\n                )\r\n            await self.sleep(0.5)\r\n        return items\r\n\r\n    async def select_all(\r\n        self,\r\n        selector: str,\r\n        timeout: Union[int, float] = 10,\r\n        include_frames=False,\r\n    ) -> List[element.Element]:\r\n        \"\"\"\r\n        Find multiple elements by CSS Selector.\r\n        Can also be used to wait for such elements to appear.\r\n        :param selector: css selector,\r\n         eg a[href], button[class*=close], a > img[src]\r\n        :type selector: str\r\n        :param timeout:\r\n         Raise timeout exception when after this many seconds nothing is found.\r\n        :type timeout: float,int\r\n        :param include_frames: Whether to include results in iframes.\r\n        :type include_frames: bool\r\n        \"\"\"\r\n        loop = asyncio.get_running_loop()\r\n        now = loop.time()\r\n        selector = selector.strip()\r\n        items = []\r\n        if include_frames:\r\n            frames = await self.query_selector_all(\"iframe\")\r\n            # Unfortunately, asyncio.gather is not an option here\r\n            for fr in frames:\r\n                items.extend(await fr.query_selector_all(selector))\r\n        items.extend(await self.query_selector_all(selector))\r\n        while not items:\r\n            await self\r\n            items = await self.query_selector_all(selector)\r\n            if loop.time() - now > timeout:\r\n                raise asyncio.TimeoutError(\r\n                    \"Time ran out while waiting for: {%s}\" % selector\r\n                )\r\n            await self.sleep(0.5)\r\n        return items\r\n\r\n    async def get(\r\n        self,\r\n        url=\"about:blank\",\r\n        new_tab: bool = False,\r\n        new_window: bool = False,\r\n        **kwargs,\r\n    ):\r\n        \"\"\"\r\n        Top level get. Utilizes the first tab to retrieve the given url.\r\n        This is a convenience function known from selenium.\r\n        This function handles waits/sleeps and detects when DOM events fired,\r\n        so it's the safest way of navigating.\r\n        :param url: the url to navigate to\r\n        :param new_tab: open new tab\r\n        :param new_window: open new window\r\n        :return: Page\r\n        \"\"\"\r\n        if not self.browser:\r\n            raise AttributeError(\r\n                \"This page/tab has no browser attribute, \"\r\n                \"so you can't use get()\"\r\n            )\r\n        if new_window and not new_tab:\r\n            new_tab = True\r\n        if new_tab:\r\n            if (\r\n                getattr(sb_config, \"incognito\", None)\r\n                or (\r\n                    getattr(sb_config, \"_cdp_browser\", None)\r\n                    in [\"comet\", \"atlas\"]\r\n                )\r\n            ):\r\n                return await self.browser.get(\r\n                    url, new_tab=False, new_window=True, **kwargs\r\n                )\r\n            else:\r\n                return await self.browser.get(\r\n                    url, new_tab=True, new_window=False, **kwargs\r\n                )\r\n        else:\r\n            if not kwargs:\r\n                frame_id, loader_id, *_ = await self.send(\r\n                    cdp.page.navigate(url)\r\n                )\r\n                await self\r\n                return self\r\n            else:\r\n                return await self.browser.get(\r\n                    url, new_tab=False, new_window=False, **kwargs\r\n                )\r\n\r\n    async def open(self, url=\"about:blank\"):\r\n        return await self.get(url=url)\r\n\r\n    async def query_selector_all(\r\n        self,\r\n        selector: str,\r\n        _node: Optional[Union[cdp.dom.Node, \"element.Element\"]] = None,\r\n    ):\r\n        \"\"\"\r\n        Equivalent of JavaScript \"document.querySelectorAll\".\r\n        This is considered one of the main methods to use in this package.\r\n        It returns all matching :py:obj:`element.Element` objects.\r\n        :param selector: css selector.\r\n         (first time? => https://www.w3schools.com/cssref/css_selectors.php )\r\n        :type selector: str\r\n        :param _node: internal use\r\n        \"\"\"\r\n        if not _node:\r\n            doc: cdp.dom.Node = await self.send(cdp.dom.get_document(-1, True))\r\n        else:\r\n            doc = _node\r\n            if _node.node_name == \"IFRAME\":\r\n                doc = _node.content_document\r\n        node_ids = []\r\n        try:\r\n            node_ids = await self.send(\r\n                cdp.dom.query_selector_all(doc.node_id, selector)\r\n            )\r\n        except ProtocolException as e:\r\n            if _node is not None:\r\n                if \"could not find node\" in e.message.lower():\r\n                    if getattr(_node, \"__last\", None):\r\n                        del _node.__last\r\n                        return []\r\n                    # If the supplied node is not found,\r\n                    # then the DOM has changed since acquiring the element.\r\n                    # Therefore, we need to update our node, and try again.\r\n                    await _node.update()\r\n                    _node.__last = (\r\n                        True  # Make sure this isn't turned into infinite loop.\r\n                    )\r\n                    return await self.query_selector_all(selector, _node)\r\n            else:\r\n                await self.send(cdp.dom.disable())\r\n                raise\r\n        if not node_ids:\r\n            return []\r\n        items = []\r\n        for nid in node_ids:\r\n            node = util.filter_recurse(doc, lambda n: n.node_id == nid)\r\n            # Pass along the retrieved document tree to improve performance.\r\n            if not node:\r\n                continue\r\n            elem = element.create(node, self, doc)\r\n            items.append(elem)\r\n        return items\r\n\r\n    async def query_selector(\r\n        self,\r\n        selector: str,\r\n        _node: Optional[Union[cdp.dom.Node, element.Element]] = None,\r\n    ):\r\n        \"\"\"\r\n        Find a single element based on a CSS Selector string.\r\n        :param selector: CSS Selector(s)\r\n        :type selector: str\r\n        \"\"\"\r\n        selector = selector.strip()\r\n        if not _node:\r\n            doc: cdp.dom.Node = await self.send(cdp.dom.get_document(-1, True))\r\n        else:\r\n            doc = _node\r\n            if _node.node_name == \"IFRAME\":\r\n                doc = _node.content_document\r\n        node_id = None\r\n        try:\r\n            node_id = await self.send(\r\n                cdp.dom.query_selector(doc.node_id, selector)\r\n            )\r\n        except ProtocolException as e:\r\n            if _node is not None:\r\n                if \"could not find node\" in e.message.lower():\r\n                    if getattr(_node, \"__last\", None):\r\n                        del _node.__last\r\n                        return []\r\n                    # If supplied node is not found,\r\n                    # the dom has changed since acquiring the element,\r\n                    # therefore, update our passed node and try again.\r\n                    await _node.update()\r\n                    _node.__last = (\r\n                        True  # Make sure this isn't turned into infinite loop.\r\n                    )\r\n                    return await self.query_selector(selector, _node)\r\n            else:\r\n                await self.send(cdp.dom.disable())\r\n                raise\r\n        if not node_id:\r\n            return\r\n        node = util.filter_recurse(doc, lambda n: n.node_id == node_id)\r\n        if not node:\r\n            return\r\n        return element.create(node, self, doc)\r\n\r\n    async def find_elements_by_text(\r\n        self,\r\n        text: str,\r\n    ) -> List[element.Element]:\r\n        \"\"\"\r\n        Returns element which match the given text.\r\n        Note: This may (or will) also return any other element\r\n        (like inline scripts), which happen to contain that text.\r\n        :param text:\r\n        \"\"\"\r\n        text = text.strip()\r\n        doc = await self.send(cdp.dom.get_document(-1, True))\r\n        search_id, nresult = await self.send(\r\n            cdp.dom.perform_search(text, True)\r\n        )\r\n        if not nresult:\r\n            return []\r\n        if nresult:\r\n            node_ids = await self.send(\r\n                cdp.dom.get_search_results(search_id, 0, nresult)\r\n            )\r\n        else:\r\n            node_ids = []\r\n        await self.send(cdp.dom.discard_search_results(search_id))\r\n        items = []\r\n        for nid in node_ids:\r\n            node = util.filter_recurse(doc, lambda n: n.node_id == nid)\r\n            if not node:\r\n                node = await self.send(cdp.dom.resolve_node(node_id=nid))\r\n                if not node:\r\n                    continue\r\n                # remote_object = await self.send(\r\n                #    cdp.dom.resolve_node(backend_node_id=node.backend_node_id)\r\n                # )\r\n                # node_id = await self.send(\r\n                #    cdp.dom.request_node(object_id=remote_object.object_id)\r\n                # )\r\n            try:\r\n                elem = element.create(node, self, doc)\r\n            except BaseException:\r\n                continue\r\n            if elem.node_type == 3:\r\n                # If found element is a text node (which is plain text,\r\n                # and useless for our purpose), we return the parent element\r\n                # of the node (which is often a tag which can have text\r\n                # between their opening and closing tags (that is most tags,\r\n                # except for example \"img\" and \"video\", \"br\").\r\n                if not elem.parent:\r\n                    # Check if parent actually has a parent\r\n                    # and update it to be absolutely sure.\r\n                    await elem.update()\r\n                items.append(\r\n                    elem.parent or elem\r\n                )  # When there's no parent, use the text node itself.\r\n                continue\r\n            else:\r\n                # Add the element itself.\r\n                items.append(elem)\r\n        # Since we already fetched the entire doc, including shadow and frames,\r\n        # let's also search through the iframes.\r\n        iframes = util.filter_recurse_all(\r\n            doc, lambda node: node.node_name == \"IFRAME\"\r\n        )\r\n        if iframes:\r\n            iframes_elems = [\r\n                element.create(iframe, self, iframe.content_document)\r\n                for iframe in iframes\r\n            ]\r\n            for iframe_elem in iframes_elems:\r\n                if iframe_elem.content_document:\r\n                    iframe_text_nodes = util.filter_recurse_all(\r\n                        iframe_elem,\r\n                        lambda node: node.node_type == 3  # noqa\r\n                        and text.lower() in node.node_value.lower(),\r\n                    )\r\n                    if iframe_text_nodes:\r\n                        iframe_text_elems = [\r\n                            element.create(text_node, self, iframe_elem.tree)\r\n                            for text_node in iframe_text_nodes\r\n                        ]\r\n                        items.extend(\r\n                            text_node.parent for text_node in iframe_text_elems\r\n                        )\r\n        await self.send(cdp.dom.disable())\r\n        return items or []\r\n\r\n    async def find_element_by_text(\r\n        self,\r\n        text: str,\r\n        best_match: Optional[bool] = False,\r\n        return_enclosing_element: Optional[bool] = True,\r\n    ) -> Union[element.Element, None]:\r\n        \"\"\"\r\n        Finds and returns the first element containing <text>, or best match.\r\n        :param text:\r\n        :param best_match:\r\n            When True, which is MUCH more expensive (thus much slower),\r\n            will find the closest match based on length.\r\n            When searching for \"login\", you probably want the button element,\r\n            and not thousands of tags/scripts containing the \"login\" string.\r\n        :type best_match: bool\r\n        :param return_enclosing_element:\r\n        \"\"\"\r\n        doc = await self.send(cdp.dom.get_document(-1, True))\r\n        text = text.strip()\r\n        search_id, nresult = await self.send(\r\n            cdp.dom.perform_search(text, True)\r\n        )\r\n        if not nresult:\r\n            return\r\n        node_ids = await self.send(\r\n            cdp.dom.get_search_results(search_id, 0, nresult)\r\n        )\r\n        await self.send(cdp.dom.discard_search_results(search_id))\r\n        if not node_ids:\r\n            node_ids = []\r\n        items = []\r\n        for nid in node_ids:\r\n            node = util.filter_recurse(doc, lambda n: n.node_id == nid)\r\n            try:\r\n                elem = element.create(node, self, doc)\r\n            except BaseException:\r\n                continue\r\n            if elem.node_type == 3:\r\n                # If found element is a text node\r\n                # (which is plain text, and useless for our purpose),\r\n                # then return the parent element of the node\r\n                # (which is often a tag which can have text between their\r\n                # opening and closing tags (that is most tags,\r\n                # except for example \"img\" and \"video\", \"br\").\r\n                if not elem.parent:\r\n                    # Check if parent has a parent, and update it to be sure.\r\n                    await elem.update()\r\n                items.append(\r\n                    elem.parent or elem\r\n                )  # When it really has no parent, use the text node itself\r\n                continue\r\n            else:\r\n                # Add the element itself\r\n                items.append(elem)\r\n        # Since the entire doc is already fetched, including shadow and frames,\r\n        # also search through the iframes.\r\n        iframes = util.filter_recurse_all(\r\n            doc, lambda node: node.node_name == \"IFRAME\"\r\n        )\r\n        if iframes:\r\n            iframes_elems = [\r\n                element.create(iframe, self, iframe.content_document)\r\n                for iframe in iframes\r\n            ]\r\n            for iframe_elem in iframes_elems:\r\n                iframe_text_nodes = util.filter_recurse_all(\r\n                    iframe_elem,\r\n                    lambda node: node.node_type == 3  # noqa\r\n                    and text.lower() in node.node_value.lower(),\r\n                )\r\n                if iframe_text_nodes:\r\n                    iframe_text_elems = [\r\n                        element.create(text_node, self, iframe_elem.tree)\r\n                        for text_node in iframe_text_nodes\r\n                    ]\r\n                    items.extend(\r\n                        text_node.parent for text_node in iframe_text_elems\r\n                    )\r\n        try:\r\n            if not items:\r\n                return\r\n            if best_match:\r\n                closest_by_length = min(\r\n                    items, key=lambda el: abs(len(text) - len(el.text_all))\r\n                )\r\n                elem = closest_by_length or items[0]\r\n                return elem\r\n            else:\r\n                # Return the first result\r\n                for elem in items:\r\n                    if elem:\r\n                        return elem\r\n        finally:\r\n            await self.send(cdp.dom.disable())\r\n\r\n    async def back(self):\r\n        \"\"\"History back\"\"\"\r\n        await self.send(cdp.runtime.evaluate(\"window.history.back()\"))\r\n\r\n    async def forward(self):\r\n        \"\"\"History forward\"\"\"\r\n        await self.send(cdp.runtime.evaluate(\"window.history.forward()\"))\r\n\r\n    async def get_navigation_history(self):\r\n        \"\"\"Get Navigation History\"\"\"\r\n        return await self.send(cdp.page.get_navigation_history())\r\n\r\n    async def get_user_agent(self):\r\n        \"\"\"Get User Agent String\"\"\"\r\n        return await self.evaluate(\"navigator.userAgent\")\r\n\r\n    async def get_cookie_string(self):\r\n        \"\"\"Get Cookie String\"\"\"\r\n        return await self.evaluate(\"document.cookie\")\r\n\r\n    async def get_locale_code(self):\r\n        \"\"\"Get Locale Code\"\"\"\r\n        return await self.evaluate(\r\n            \"navigator.language || navigator.languages[0]\"\r\n        )\r\n\r\n    async def is_online(self):\r\n        \"\"\"Determine if connected to the Internet\"\"\"\r\n        return await self.evaluate(\"navigator.onLine\")\r\n\r\n    async def reload(\r\n        self,\r\n        ignore_cache: Optional[bool] = True,\r\n        script_to_evaluate_on_load: Optional[str] = None,\r\n    ):\r\n        \"\"\"\r\n        Reloads the page\r\n        :param ignore_cache: When set to True (default),\r\n         it ignores cache, and re-downloads the items.\r\n        :param script_to_evaluate_on_load: Script to run on load.\r\n        \"\"\"\r\n        await self.send(\r\n            cdp.page.reload(\r\n                ignore_cache=ignore_cache,\r\n                script_to_evaluate_on_load=script_to_evaluate_on_load,\r\n            ),\r\n        )\r\n\r\n    async def evaluate(\r\n        self, expression: str, await_promise=False, return_by_value=True\r\n    ):\r\n        remote_object, errors = await self.send(\r\n            cdp.runtime.evaluate(\r\n                expression=expression,\r\n                user_gesture=True,\r\n                await_promise=await_promise,\r\n                return_by_value=return_by_value,\r\n                allow_unsafe_eval_blocked_by_csp=True,\r\n            )\r\n        )\r\n        if errors:\r\n            raise ProtocolException(errors)\r\n        if remote_object:\r\n            if return_by_value:\r\n                if remote_object.value is not None:\r\n                    return remote_object.value\r\n            else:\r\n                if remote_object.deep_serialized_value is not None:\r\n                    return remote_object.deep_serialized_value.value\r\n        return None\r\n\r\n    async def js_dumps(\r\n        self, obj_name: str, return_by_value: Optional[bool] = True\r\n    ) -> Union[\r\n        Dict,\r\n        Tuple[cdp.runtime.RemoteObject, cdp.runtime.ExceptionDetails],\r\n    ]:\r\n        \"\"\"\r\n        Dump Given js object with its properties and values as a dict.\r\n        Note: Complex objects might not be serializable,\r\n        therefore this method is not a \"source of truth\"\r\n        :param obj_name: the js object to dump\r\n        :type obj_name: str\r\n        :param return_by_value: If you want an tuple of cdp objects\r\n         (returnvalue, errors), then set this to False.\r\n        :type return_by_value: bool\r\n\r\n        Example\r\n        -------\r\n\r\n        x = await self.js_dumps('window')\r\n        print(x)\r\n            '...{\r\n            'pageYOffset': 0,\r\n            'visualViewport': {},\r\n            'screenX': 10,\r\n            'screenY': 10,\r\n            'outerWidth': 1050,\r\n            'outerHeight': 832,\r\n            'devicePixelRatio': 1,\r\n            'screenLeft': 10,\r\n            'screenTop': 10,\r\n            'styleMedia': {},\r\n            'onsearch': None,\r\n            'isSecureContext': True,\r\n            'trustedTypes': {},\r\n            'performance': {'timeOrigin': 1707823094767.9,\r\n            'timing': {'connectStart': 0,\r\n            'navigationStart': 1707823094768,\r\n            ]...\r\n        \"\"\"\r\n        js_code_a = (\r\n            \"\"\"\r\n            function ___dump(obj, _d = 0) {\r\n                let _typesA = ['object', 'function'];\r\n                let _typesB = ['number', 'string', 'boolean'];\r\n                if (_d == 2) {\r\n                    console.log('maxdepth reached for ', obj);\r\n                    return\r\n                }\r\n                let tmp = {}\r\n                for (let k in obj) {\r\n                    if (obj[k] == window) continue;\r\n                    let v;\r\n                    try {\r\n                        if (obj[k] === null\r\n                            || obj[k] === undefined\r\n                            || obj[k] === NaN) {\r\n                            console.log('obj[k] is null or undefined or Nan',\r\n                            k, '=>', obj[k])\r\n                            tmp[k] = obj[k];\r\n                            continue\r\n                        }\r\n                    } catch (e) {\r\n                        tmp[k] = null;\r\n                        continue\r\n                    }\r\n                    if (_typesB.includes(typeof obj[k])) {\r\n                        tmp[k] = obj[k]\r\n                        continue\r\n                    }\r\n                    try {\r\n                        if (typeof obj[k] === 'function') {\r\n                            tmp[k] = obj[k].toString()\r\n                            continue\r\n                        }\r\n                        if (typeof obj[k] === 'object') {\r\n                            tmp[k] = ___dump(obj[k], _d + 1);\r\n                            continue\r\n                        }\r\n                    } catch (e) {}\r\n                    try {\r\n                        tmp[k] = JSON.stringify(obj[k])\r\n                        continue\r\n                    } catch (e) {\r\n                    }\r\n                    try {\r\n                        tmp[k] = obj[k].toString();\r\n                        continue\r\n                    } catch (e) {}\r\n                }\r\n                return tmp\r\n            }\r\n            function ___dumpY(obj) {\r\n                var objKeys = (obj) => {\r\n                    var [target, result] = [obj, []];\r\n                    while (target !== null) {\r\n                        result = result.concat(\r\n                            Object.getOwnPropertyNames(target)\r\n                        );\r\n                        target = Object.getPrototypeOf(target);\r\n                    }\r\n                    return result;\r\n                }\r\n                return Object.fromEntries(\r\n                    objKeys(obj).map(_ => [_, ___dump(obj[_])]))\r\n            }\r\n            ___dumpY( %s )\r\n            \"\"\"\r\n            % obj_name\r\n        )\r\n        js_code_b = (\r\n            \"\"\"\r\n            ((obj, visited = new WeakSet()) => {\r\n                 if (visited.has(obj)) {\r\n                     return {}\r\n                 }\r\n                 visited.add(obj)\r\n                 var result = {}, _tmp;\r\n                 for (var i in obj) {\r\n                         try {\r\n                             if (i === 'enabledPlugin'\r\n                                 || typeof obj[i] === 'function') {\r\n                                 continue;\r\n                             } else if (typeof obj[i] === 'object') {\r\n                                 _tmp = recurse(obj[i], visited);\r\n                                 if (Object.keys(_tmp).length) {\r\n                                     result[i] = _tmp;\r\n                                 }\r\n                             } else {\r\n                                 result[i] = obj[i];\r\n                             }\r\n                         } catch (error) {\r\n                             // console.error('Error:', error);\r\n                         }\r\n                     }\r\n                return result;\r\n            })(%s)\r\n        \"\"\"\r\n            % obj_name\r\n        )\r\n        # No self.evaluate here to prevent infinite loop on certain expressions\r\n        remote_object, exception_details = await self.send(\r\n            cdp.runtime.evaluate(\r\n                js_code_a,\r\n                await_promise=True,\r\n                return_by_value=return_by_value,\r\n                allow_unsafe_eval_blocked_by_csp=True,\r\n            )\r\n        )\r\n        if exception_details:\r\n            # Try second variant\r\n            remote_object, exception_details = await self.send(\r\n                cdp.runtime.evaluate(\r\n                    js_code_b,\r\n                    await_promise=True,\r\n                    return_by_value=return_by_value,\r\n                    allow_unsafe_eval_blocked_by_csp=True,\r\n                )\r\n            )\r\n        if exception_details:\r\n            raise ProtocolException(exception_details)\r\n        if return_by_value:\r\n            if remote_object.value:\r\n                return remote_object.value\r\n        else:\r\n            return remote_object, exception_details\r\n\r\n    async def close(self):\r\n        \"\"\"Close the current target (ie: tab,window,page)\"\"\"\r\n        if self.target and self.target.target_id:\r\n            await self.send(\r\n                cdp.target.close_target(target_id=self.target.target_id)\r\n            )\r\n            await self.aclose()\r\n            await asyncio.sleep(0.1)\r\n\r\n    async def get_window(self) -> Tuple[\r\n        cdp.browser.WindowID, cdp.browser.Bounds\r\n    ]:\r\n        \"\"\"Get the window Bounds\"\"\"\r\n        window_id, bounds = await self.send(\r\n            cdp.browser.get_window_for_target(self.target_id)\r\n        )\r\n        return window_id, bounds\r\n\r\n    async def get_content(self):\r\n        \"\"\"Gets the current page source content (html)\"\"\"\r\n        doc: cdp.dom.Node = await self.send(cdp.dom.get_document(-1, True))\r\n        return await self.send(\r\n            cdp.dom.get_outer_html(\r\n                backend_node_id=doc.backend_node_id,\r\n                include_shadow_dom=True,\r\n            )\r\n        )\r\n\r\n    async def maximize(self):\r\n        \"\"\"Maximize page/tab/window\"\"\"\r\n        return await self.set_window_state(state=\"maximize\")\r\n\r\n    async def minimize(self):\r\n        \"\"\"Minimize page/tab/window\"\"\"\r\n        return await self.set_window_state(state=\"minimize\")\r\n\r\n    async def fullscreen(self):\r\n        \"\"\"Minimize page/tab/window\"\"\"\r\n        return await self.set_window_state(state=\"fullscreen\")\r\n\r\n    async def medimize(self):\r\n        return await self.set_window_state(state=\"normal\")\r\n\r\n    async def set_window_size(self, left=0, top=0, width=1280, height=1024):\r\n        \"\"\"\r\n        Set window size and position.\r\n        :param left:\r\n         Pixels from the left of the screen to the window top-left corner.\r\n        :param top:\r\n         Pixels from the top of the screen to the window top-left corner.\r\n        :param width: width of the window in pixels\r\n        :param height: height of the window in pixels\r\n        \"\"\"\r\n        return await self.set_window_state(left, top, width, height)\r\n\r\n    async def set_window_rect(self, left=0, top=0, width=1280, height=1024):\r\n        \"\"\"Same as set_window_size(). Uses a different naming convention.\"\"\"\r\n        return await self.set_window_state(left, top, width, height)\r\n\r\n    async def activate(self):\r\n        \"\"\"Active this target (Eg: tab, window, page)\"\"\"\r\n        await self.send(cdp.target.activate_target(self.target.target_id))\r\n\r\n    async def bring_to_front(self):\r\n        \"\"\"Alias to self.activate\"\"\"\r\n        await self.activate()\r\n\r\n    async def set_window_state(\r\n        self, left=0, top=0, width=1280, height=720, state=\"normal\"\r\n    ):\r\n        \"\"\"\r\n        Sets the window size or state.\r\n        For state you can provide the full name like minimized, maximized,\r\n        normal, fullscreen, or something which leads to either of those,\r\n        like min, mini, mi,  max, ma, maxi, full, fu, no, nor.\r\n        In case state is set other than \"normal\",\r\n        the left, top, width, and height are ignored.\r\n        :param left:\r\n            desired offset from left, in pixels\r\n        :type left: int\r\n        :param top:\r\n            desired offset from the top, in pixels\r\n        :type top: int\r\n        :param width:\r\n            desired width in pixels\r\n        :type width: int\r\n        :param height:\r\n            desired height in pixels\r\n        :type height: int\r\n        :param state:\r\n            can be one of the following strings:\r\n                - normal\r\n                - fullscreen\r\n                - maximized\r\n                - minimized\r\n        :type state: str\r\n        \"\"\"\r\n        available_states = [\"minimized\", \"maximized\", \"fullscreen\", \"normal\"]\r\n        window_id: cdp.browser.WindowID\r\n        bounds: cdp.browser.Bounds\r\n        (window_id, bounds) = await self.get_window()\r\n        for state_name in available_states:\r\n            if all(x in state_name for x in state.lower()):\r\n                break\r\n        else:\r\n            raise NameError(\r\n                \"could not determine any of %s from input '%s'\"\r\n                % (\",\".join(available_states), state)\r\n            )\r\n        window_state = getattr(\r\n            cdp.browser.WindowState,\r\n            state_name.upper(),\r\n            cdp.browser.WindowState.NORMAL,\r\n        )\r\n        if window_state == cdp.browser.WindowState.NORMAL:\r\n            bounds = cdp.browser.Bounds(\r\n                left, top, width, height, window_state\r\n            )\r\n        else:\r\n            # min, max, full can only be used when current state == NORMAL,\r\n            # therefore, first switch to NORMAL\r\n            await self.set_window_state(state=\"normal\")\r\n            bounds = cdp.browser.Bounds(window_state=window_state)\r\n\r\n        await self.send(\r\n            cdp.browser.set_window_bounds(window_id, bounds=bounds)\r\n        )\r\n\r\n    async def scroll_down(self, amount=25):\r\n        \"\"\"\r\n        Scrolls the page down.\r\n        :param amount: Number in percentage.\r\n         25 is a quarter of page, 50 half, and 1000 is 10x the page.\r\n        :type amount: int\r\n        \"\"\"\r\n        window_id: cdp.browser.WindowID\r\n        bounds: cdp.browser.Bounds\r\n        (window_id, bounds) = await self.get_window()\r\n        await self.send(\r\n            cdp.input_.synthesize_scroll_gesture(\r\n                x=0,\r\n                y=0,\r\n                y_distance=-(bounds.height * (amount / 100)),\r\n                y_overscroll=0,\r\n                x_overscroll=0,\r\n                prevent_fling=True,\r\n                repeat_delay_ms=0,\r\n                speed=7777,\r\n            )\r\n        )\r\n\r\n    async def scroll_up(self, amount=25):\r\n        \"\"\"\r\n        Scrolls the page up.\r\n        :param amount: Number in percentage.\r\n         25 is a quarter of page, 50 half, and 1000 is 10x the page.\r\n        :type amount: int\r\n        \"\"\"\r\n        window_id: cdp.browser.WindowID\r\n        bounds: cdp.browser.Bounds\r\n        (window_id, bounds) = await self.get_window()\r\n        await self.send(\r\n            cdp.input_.synthesize_scroll_gesture(\r\n                x=0,\r\n                y=0,\r\n                y_distance=(bounds.height * (amount / 100)),\r\n                x_overscroll=0,\r\n                prevent_fling=True,\r\n                repeat_delay_ms=0,\r\n                speed=7777,\r\n            )\r\n        )\r\n\r\n    async def wait_for(\r\n        self,\r\n        selector: Optional[str] = \"\",\r\n        text: Optional[str] = \"\",\r\n        timeout: Optional[Union[int, float]] = 10,\r\n    ) -> element.Element:\r\n        \"\"\"\r\n        Variant on query_selector_all and find_elements_by_text.\r\n        This variant takes either selector or text,\r\n        and will block until the requested element(s) are found.\r\n        It will block for a maximum of <timeout> seconds,\r\n        after which a TimeoutError will be raised.\r\n        :param selector: css selector\r\n        :param text: text\r\n        :param timeout:\r\n        :return: Element\r\n        :raises: asyncio.TimeoutError\r\n        \"\"\"\r\n        loop = asyncio.get_running_loop()\r\n        now = loop.time()\r\n        if selector:\r\n            item = await self.query_selector(selector)\r\n            while not item:\r\n                item = await self.query_selector(selector)\r\n                if loop.time() - now > timeout:\r\n                    raise asyncio.TimeoutError(\r\n                        \"Time ran out while waiting for: {%s}\" % selector\r\n                    )\r\n                await self.sleep(0.068)\r\n            return item\r\n        if text:\r\n            item = await self.find_element_by_text(text)\r\n            while not item:\r\n                item = await self.find_element_by_text(text)\r\n                if loop.time() - now > timeout:\r\n                    raise asyncio.TimeoutError(\r\n                        \"Time ran out while waiting for: {%s}\" % text\r\n                    )\r\n                await self.sleep(0.068)\r\n            return item\r\n\r\n    async def set_attributes(self, selector, attribute, value):\r\n        \"\"\"This method uses JavaScript to set/update a common attribute.\r\n        All matching selectors from querySelectorAll() are used.\r\n        Example => (Make all links on a website redirect to Google):\r\n        self.set_attributes(\"a\", \"href\", \"https://google.com\")\"\"\"\r\n        attribute = re.escape(attribute)\r\n        attribute = js_utils.escape_quotes_if_needed(attribute)\r\n        value = re.escape(value)\r\n        value = js_utils.escape_quotes_if_needed(value)\r\n        if selector.startswith((\"/\", \"./\", \"(\")):\r\n            with suppress(Exception):\r\n                selector = js_utils.convert_to_css_selector(selector, \"xpath\")\r\n        css_selector = selector\r\n        css_selector = re.escape(css_selector)  # Add \"\\\\\" to special chars\r\n        css_selector = js_utils.escape_quotes_if_needed(css_selector)\r\n        js_code = (\r\n            \"\"\"var $elements = document.querySelectorAll('%s');\r\n            var index = 0, length = $elements.length;\r\n            for(; index < length; index++){\r\n            $elements[index].setAttribute('%s','%s');}\"\"\" % (\r\n                css_selector,\r\n                attribute,\r\n                value,\r\n            )\r\n        )\r\n        with suppress(Exception):\r\n            await self.evaluate(js_code)\r\n\r\n    async def internalize_links(self):\r\n        \"\"\"All `target=\"_blank\"` links become `target=\"_self\"`.\r\n        This prevents those links from opening in a new tab.\"\"\"\r\n        await self.set_attributes('[target=\"_blank\"]', \"target\", \"_self\")\r\n\r\n    async def download_file(\r\n        self, url: str, filename: Optional[PathLike] = None\r\n    ):\r\n        \"\"\"\r\n        Downloads the file by the given url.\r\n        :param url: The URL of the file.\r\n        :param filename: The name for the file.\r\n         If not specified, the name is composed from the url file name\r\n        \"\"\"\r\n        if not self._download_behavior:\r\n            directory_path = pathlib.Path.cwd() / \"downloads\"\r\n            directory_path.mkdir(exist_ok=True)\r\n            await self.set_download_path(directory_path)\r\n\r\n            warnings.warn(\r\n                f\"No download path set, so creating and using a default of \"\r\n                f\"{directory_path}\"\r\n            )\r\n        if not filename:\r\n            filename = url.rsplit(\"/\")[-1]\r\n            filename = filename.split(\"?\")[0]\r\n        code = \"\"\"\r\n         (elem) => {\r\n            async function _downloadFile(\r\n              imageSrc,\r\n              nameOfDownload,\r\n            ) {\r\n              const response = await fetch(imageSrc);\r\n              const blobImage = await response.blob();\r\n              const href = URL.createObjectURL(blobImage);\r\n              const anchorElement = document.createElement('a');\r\n              anchorElement.href = href;\r\n              anchorElement.download = nameOfDownload;\r\n              document.body.appendChild(anchorElement);\r\n              anchorElement.click();\r\n              setTimeout(() => {\r\n                document.body.removeChild(anchorElement);\r\n                window.URL.revokeObjectURL(href);\r\n                }, 500);\r\n            }\r\n            _downloadFile('%s', '%s')\r\n            }\r\n            \"\"\" % (\r\n            url,\r\n            filename,\r\n        )\r\n        body = (await self.query_selector_all(\"body\"))[0]\r\n        await body.update()\r\n        await self.send(\r\n            cdp.runtime.call_function_on(\r\n                code,\r\n                object_id=body.object_id,\r\n                arguments=[cdp.runtime.CallArgument(object_id=body.object_id)],\r\n            )\r\n        )\r\n\r\n    async def save_screenshot(\r\n        self,\r\n        filename: Optional[PathLike] = \"auto\",\r\n        format: Optional[str] = \"png\",\r\n        full_page: Optional[bool] = False,\r\n    ) -> str:\r\n        \"\"\"\r\n        Saves a screenshot of the page.\r\n        This is not the same as :py:obj:`Element.save_screenshot`,\r\n        which saves a screenshot of a single element only.\r\n        :param filename: uses this as the save path\r\n        :type filename: PathLike\r\n        :param format: jpeg or png (defaults to jpeg)\r\n        :type format: str\r\n        :param full_page:\r\n         When False (default), it captures the current viewport.\r\n         When True, it captures the entire page.\r\n        :type full_page: bool\r\n        :return: The path/filename of the saved screenshot.\r\n        :rtype: str\r\n        \"\"\"\r\n        await self.sleep()  # Update the target's URL\r\n        path = None\r\n        if format.lower() in [\"jpg\", \"jpeg\"]:\r\n            ext = \".jpg\"\r\n            format = \"jpeg\"\r\n        elif format.lower() in [\"png\"]:\r\n            ext = \".png\"\r\n            format = \"png\"\r\n        if not filename or filename == \"auto\":\r\n            parsed = urllib.parse.urlparse(self.target.url)\r\n            parts = parsed.path.split(\"/\")\r\n            last_part = parts[-1]\r\n            last_part = last_part.rsplit(\"?\", 1)[0]\r\n            dt_str = datetime.datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\")\r\n            candidate = f\"{parsed.hostname}__{last_part}_{dt_str}\"\r\n            path = pathlib.Path(candidate + ext)  # noqa\r\n        else:\r\n            path = pathlib.Path(filename)\r\n        path.parent.mkdir(parents=True, exist_ok=True)\r\n        data = await self.send(\r\n            cdp.page.capture_screenshot(\r\n                format_=format, capture_beyond_viewport=full_page\r\n            )\r\n        )\r\n        if not data:\r\n            raise ProtocolException(\r\n                \"Could not take screenshot. \"\r\n                \"Most possible cause is the page \"\r\n                \"has not finished loading yet.\"\r\n            )\r\n        data_bytes = base64.b64decode(data)\r\n        if not path:\r\n            raise RuntimeError(\"Invalid filename or path: '%s'\" % filename)\r\n        path.write_bytes(data_bytes)\r\n        return str(path)\r\n\r\n    async def print_to_pdf(\r\n        self,\r\n        filename: Optional[PathLike] = \"auto\",\r\n    ) -> str:\r\n        \"\"\"\r\n        Saves a webpage as a PDF.\r\n        :param filename: uses this as the save path\r\n        :type filename: PathLike\r\n        :return: The path/filename of the saved screenshot.\r\n        :rtype: str\r\n        \"\"\"\r\n        await self.sleep()  # Update the target's URL\r\n        path = None\r\n        ext = \".pdf\"\r\n        if not filename or filename == \"auto\":\r\n            parsed = urllib.parse.urlparse(self.target.url)\r\n            parts = parsed.path.split(\"/\")\r\n            last_part = parts[-1]\r\n            last_part = last_part.rsplit(\"?\", 1)[0]\r\n            dt_str = datetime.datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\")\r\n            candidate = f\"{parsed.hostname}__{last_part}_{dt_str}\"\r\n            path = pathlib.Path(candidate + ext)  # noqa\r\n        else:\r\n            path = pathlib.Path(filename)\r\n        path.parent.mkdir(parents=True, exist_ok=True)\r\n        data, _ = await self.send(cdp.page.print_to_pdf())\r\n        if not data:\r\n            raise ProtocolException(\"Could not save PDF.\")\r\n        data_bytes = base64.b64decode(data)\r\n        if not path:\r\n            raise RuntimeError(\"Invalid filename or path: '%s'\" % filename)\r\n        path.write_bytes(data_bytes)\r\n        return str(path)\r\n\r\n    async def set_download_path(self, path: PathLike):\r\n        \"\"\"\r\n        Sets the download path.\r\n        When not set, a default folder is used.\r\n        :param path:\r\n        \"\"\"\r\n        await self.send(\r\n            cdp.browser.set_download_behavior(\r\n                behavior=\"allow\", download_path=str(path.resolve())\r\n            )\r\n        )\r\n        self._download_behavior = [\"allow\", str(path.resolve())]\r\n\r\n    async def get_all_linked_sources(self) -> List[\"element.Element\"]:\r\n        \"\"\"Get all elements of tag: link, a, img, scripts meta, video, audio\"\"\"\r\n        all_assets = await self.query_selector_all(\r\n            selector=\"a,link,img,script,meta\"\r\n        )\r\n        return [element.create(asset, self) for asset in all_assets]\r\n\r\n    async def get_all_urls(self, absolute=True) -> List[str]:\r\n        \"\"\"\r\n        Convenience function, which returns all links (a,link,img,script,meta).\r\n        :param absolute:\r\n         Try to build all the links in absolute form\r\n         instead of \"as is\", often relative.\r\n        :return: List of URLs.\r\n        \"\"\"\r\n        import urllib.parse\r\n\r\n        res = []\r\n        all_assets = await self.query_selector_all(\r\n            selector=\"a,link,img,script,meta\"\r\n        )\r\n        for asset in all_assets:\r\n            if not absolute:\r\n                res.append(asset.src or asset.href)\r\n            else:\r\n                for k, v in asset.attrs.items():\r\n                    if k in (\"src\", \"href\"):\r\n                        if \"#\" in v:\r\n                            continue\r\n                        if not any([_ in v for _ in (\"http\", \"//\", \"/\")]):\r\n                            continue\r\n                        abs_url = urllib.parse.urljoin(\r\n                            \"/\".join(self.url.rsplit(\"/\")[:3]), v\r\n                        )\r\n                        if not abs_url.startswith((\"http\", \"//\", \"ws\")):\r\n                            continue\r\n                        res.append(abs_url)\r\n        return res\r\n\r\n    async def get_html(self):\r\n        element = await self.find(\"html\", timeout=1)\r\n        return await element.get_html_async()\r\n\r\n    async def get_page_source(self):\r\n        return await self.get_html()\r\n\r\n    async def is_element_present(self, selector):\r\n        try:\r\n            await self.select(selector, timeout=0.01)\r\n            return True\r\n        except Exception:\r\n            return False\r\n\r\n    async def is_element_visible(self, selector):\r\n        if \":contains(\" not in selector:\r\n            try:\r\n                element = await self.select(selector, timeout=0.01)\r\n            except Exception:\r\n                return False\r\n            if not element:\r\n                return False\r\n            try:\r\n                position = await element.get_position_async()\r\n                return (position.width != 0 or position.height != 0)\r\n            except Exception:\r\n                return False\r\n        else:\r\n            with suppress(Exception):\r\n                tag_name = selector.split(\":contains(\")[0].split(\" \")[-1]\r\n                text = selector.split(\":contains(\")[1].split(\")\")[0][1:-1]\r\n                element = await self.select(tag_name, timeout=0.01)\r\n                if not element:\r\n                    raise Exception()\r\n                element = await self.find_element_by_text(text)\r\n                if not element:\r\n                    raise Exception()\r\n                return True\r\n            return False\r\n\r\n    async def __on_a_cf_turnstile_page(self, source=None):\r\n        if not source or len(source) < 400:\r\n            await self.sleep(0.22)\r\n            source = await self.get_html()\r\n        if (\r\n            (\r\n                'data-callback=\"onCaptchaSuccess\"' in source\r\n                and 'title=\"reCAPTCHA\"' not in source\r\n                and 'id=\"recaptcha-token\"' not in source\r\n            )\r\n            or \"/challenge-platform/h/b/\" in source\r\n            or 'id=\"challenge-widget-' in source\r\n            or \"challenges.cloudf\" in source\r\n            or \"cf-turnstile-\" in source\r\n        ):\r\n            return True\r\n        return False\r\n\r\n    async def __on_an_incapsula_hcaptcha_page(self, *args, **kwargs):\r\n        if await self.is_element_visible('iframe[src*=\"Incapsula_Resource?\"]'):\r\n            return True\r\n        return False\r\n\r\n    async def __on_a_g_recaptcha_page(self, *args, **kwargs):\r\n        await self.sleep(0.4)  # reCAPTCHA may need a moment to appear\r\n        source = await self.get_html()\r\n        if (\r\n            (\r\n                'id=\"recaptcha-token\"' in source\r\n                or 'title=\"reCAPTCHA\"' in source\r\n            )\r\n            and await self.is_element_present('iframe[title=\"reCAPTCHA\"]')\r\n        ):\r\n            await self.sleep(0.1)\r\n            return True\r\n        elif \"com/recaptcha/api.js\" in source:\r\n            await self.sleep(1.2)  # Maybe still loading\r\n            return True\r\n        return False\r\n\r\n    async def __gui_click_recaptcha(self):\r\n        selector = None\r\n        if await self.is_element_present('iframe[title=\"reCAPTCHA\"]'):\r\n            selector = 'iframe[title=\"reCAPTCHA\"]'\r\n        else:\r\n            return False\r\n        await self.sleep(0.5)\r\n        with suppress(Exception):\r\n            element_rect = await self.get_element_rect(selector, timeout=0.1)\r\n            e_x = element_rect[\"x\"]\r\n            e_y = element_rect[\"y\"]\r\n            window_rect = await self.get_window_rect()\r\n            win_width = window_rect[\"innerWidth\"]\r\n            win_height = window_rect[\"innerHeight\"]\r\n            if (\r\n                e_x > 1040\r\n                and e_y > 640\r\n                and abs(win_width - e_x) < 110\r\n                and abs(win_height - e_y) < 110\r\n            ):\r\n                # Probably the invisible reCAPTCHA in the bottom right corner\r\n                return False\r\n            gui_element_rect = await self.get_gui_element_rect(\r\n                selector, timeout=1\r\n            )\r\n            gui_e_x = gui_element_rect[\"x\"]\r\n            gui_e_y = gui_element_rect[\"y\"]\r\n            x_offset = 26\r\n            y_offset = 35\r\n            if await asyncio.to_thread(shared_utils.is_windows):\r\n                x_offset = 29\r\n            x = gui_e_x + x_offset\r\n            y = gui_e_y + y_offset\r\n            sb_config._saved_cf_x_y = (x, y)  # For debugging later\r\n            await self.sleep(0.11)\r\n            gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\r\n            with await asyncio.to_thread(gui_lock.acquire):\r\n                await self.bring_to_front()\r\n                await self.sleep(0.05)\r\n                await self.click_with_offset(\r\n                    selector, x_offset, y_offset, timeout=1\r\n                )\r\n                await self.sleep(0.22)\r\n            return True\r\n        return False\r\n\r\n    async def __cdp_click_incapsula_hcaptcha(self):\r\n        selector = None\r\n        if await self.is_element_visible('iframe[src*=\"Incapsula_Resource?\"]'):\r\n            outer_selector = 'iframe[src*=\"Incapsula_Resource?\"]'\r\n            selector = \"iframe[data-hcaptcha-widget-id]\"\r\n            outer_element = await self.find_element_by_text(outer_selector)\r\n            element = await outer_element.query_selector_async(selector)\r\n            if not element:\r\n                return False\r\n        else:\r\n            return False\r\n        await self.sleep(0.55)\r\n        x_offset = 30\r\n        y_offset = 36\r\n        was_clicked = False\r\n        gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\r\n        with gui_lock:  # Prevent issues with multiple processes\r\n            await self.bring_to_front()\r\n            await self.sleep(0.056)\r\n            if \"--debug\" in sys.argv:\r\n                displayed_selector = \"`%s`\" % selector\r\n                if '\"' not in selector:\r\n                    displayed_selector = '\"%s\"' % selector\r\n                elif \"'\" not in selector:\r\n                    displayed_selector = \"'%s'\" % selector\r\n                print(\r\n                    \" <DEBUG> click_with_offset(%s, %s, %s)\"\r\n                    % (displayed_selector, x_offset, y_offset)\r\n                )\r\n            with suppress(Exception):\r\n                await element.mouse_click_with_offset_async(\r\n                    x=x_offset, y=y_offset, center=False\r\n                )\r\n                was_clicked = True\r\n                await self.sleep(0.075)\r\n        if was_clicked:\r\n            # Wait a moment for the click to succeed\r\n            await self.sleep(0.75)\r\n            if \"--debug\" in sys.argv:\r\n                print(\" <DEBUG> hCaptcha was clicked!\")\r\n            return True\r\n        if \"--debug\" in sys.argv:\r\n            print(\" <DEBUG> hCaptcha was NOT clicked!\")\r\n        return False\r\n\r\n    async def get_element_rect(self, selector, timeout=5):\r\n        element = await self.select(selector, timeout=timeout)\r\n        coordinates = None\r\n        if \":contains(\" in selector:\r\n            position = await element.get_position_async()\r\n            x = position.x\r\n            y = position.y\r\n            width = position.width\r\n            height = position.height\r\n            coordinates = {\"x\": x, \"y\": y, \"width\": width, \"height\": height}\r\n        else:\r\n            coordinates = await self.js_dumps(\r\n                \"\"\"document.querySelector('%s').getBoundingClientRect()\"\"\"\r\n                % js_utils.escape_quotes_if_needed(re.escape(selector))\r\n            )\r\n        return coordinates\r\n\r\n    async def get_window_rect(self):\r\n        coordinates = {}\r\n        innerWidth = await self.evaluate(\"window.innerWidth\")\r\n        innerHeight = await self.evaluate(\"window.innerHeight\")\r\n        outerWidth = await self.evaluate(\"window.outerWidth\")\r\n        outerHeight = await self.evaluate(\"window.outerHeight\")\r\n        pageXOffset = await self.evaluate(\"window.pageXOffset\")\r\n        pageYOffset = await self.evaluate(\"window.pageYOffset\")\r\n        scrollX = await self.evaluate(\"window.scrollX\")\r\n        scrollY = await self.evaluate(\"window.scrollY\")\r\n        screenLeft = await self.evaluate(\"window.screenLeft\")\r\n        screenTop = await self.evaluate(\"window.screenTop\")\r\n        x = await self.evaluate(\"window.screenX\")\r\n        y = await self.evaluate(\"window.screenY\")\r\n        coordinates[\"innerWidth\"] = innerWidth\r\n        coordinates[\"innerHeight\"] = innerHeight\r\n        coordinates[\"outerWidth\"] = outerWidth\r\n        coordinates[\"outerHeight\"] = outerHeight\r\n        coordinates[\"width\"] = outerWidth\r\n        coordinates[\"height\"] = outerHeight\r\n        coordinates[\"pageXOffset\"] = pageXOffset if pageXOffset else 0\r\n        coordinates[\"pageYOffset\"] = pageYOffset if pageYOffset else 0\r\n        coordinates[\"scrollX\"] = scrollX if scrollX else 0\r\n        coordinates[\"scrollY\"] = scrollY if scrollY else 0\r\n        coordinates[\"screenLeft\"] = screenLeft if screenLeft else 0\r\n        coordinates[\"screenTop\"] = screenTop if screenTop else 0\r\n        coordinates[\"x\"] = x if x else 0\r\n        coordinates[\"y\"] = y if y else 0\r\n        return coordinates\r\n\r\n    async def get_gui_element_rect(self, selector, timeout=5):\r\n        \"\"\"(Coordinates are relative to the screen. Not the window.)\"\"\"\r\n        element_rect = await self.get_element_rect(selector, timeout=timeout)\r\n        e_width = element_rect[\"width\"]\r\n        e_height = element_rect[\"height\"]\r\n        window_rect = await self.get_window_rect()\r\n        w_bottom_y = window_rect[\"y\"] + window_rect[\"height\"]\r\n        viewport_height = window_rect[\"innerHeight\"]\r\n        x = window_rect[\"x\"] + element_rect[\"x\"]\r\n        y = w_bottom_y - viewport_height + element_rect[\"y\"]\r\n        y_scroll_offset = window_rect[\"pageYOffset\"]\r\n        if (\r\n            hasattr(sb_config, \"_cdp_browser\")\r\n            and sb_config._cdp_browser == \"opera\"\r\n        ):\r\n            # Handle special case where Opera side panel shifts coordinates\r\n            x_offset = window_rect[\"outerWidth\"] - window_rect[\"innerWidth\"]\r\n            if x_offset > 56:\r\n                x_offset = 56\r\n            elif x_offset < 22:\r\n                x_offset = 0\r\n            x = x + x_offset\r\n        y = y - y_scroll_offset\r\n        x = x + window_rect[\"scrollX\"]\r\n        y = y + window_rect[\"scrollY\"]\r\n        return ({\"height\": e_height, \"width\": e_width, \"x\": x, \"y\": y})\r\n\r\n    async def get_title(self):\r\n        return await self.evaluate(\"document.title\")\r\n\r\n    async def get_current_url(self):\r\n        return await self.evaluate(\"window.location.href\")\r\n\r\n    async def get_origin(self):\r\n        return await self.evaluate(\"window.location.origin\")\r\n\r\n    async def send_keys(self, selector, text, timeout=5):\r\n        element = await self.find(selector, timeout=timeout)\r\n        await element.send_keys_async(text)\r\n\r\n    async def type(self, selector, text, timeout=5):\r\n        await self.send_keys(selector, text, timeout=timeout)\r\n\r\n    async def click(self, selector, timeout=5):\r\n        element = await self.find(selector, timeout=timeout)\r\n        await element.click_async()\r\n\r\n    async def click_if_visible(self, selector, timeout=0):\r\n        original_selector = selector\r\n        if (\":contains(\") in selector:\r\n            selector, _ = page_utils.recalculate_selector(\r\n                selector, by=\"css selector\", xp_ok=True\r\n            )\r\n        if await self.is_element_visible(original_selector):\r\n            with suppress(Exception):\r\n                element = await self.find(selector, timeout=0.01)\r\n                await element.click_async()\r\n        elif timeout == 0:\r\n            return\r\n        else:\r\n            with suppress(Exception):\r\n                await self.find(selector, timeout=timeout)\r\n                if await self.is_element_visible(selector):\r\n                    element = await self.find(selector, timeout=0.01)\r\n                    await element.click_async()\r\n\r\n    async def click_with_offset(self, selector, x, y, center=False, timeout=5):\r\n        element = await self.find(selector, timeout=timeout)\r\n        await element.scroll_into_view_async()\r\n        await element.mouse_click_with_offset_async(x=x, y=y, center=center)\r\n\r\n    async def solve_captcha(self):\r\n        await self.sleep(0.11)\r\n        source = await self.get_html()\r\n        if await self.__on_a_cf_turnstile_page(source):\r\n            pass\r\n        elif await self.__on_a_g_recaptcha_page(source):\r\n            result = await self.__gui_click_recaptcha()\r\n            return result\r\n        elif await self.__on_an_incapsula_hcaptcha_page():\r\n            result = await self.__cdp_click_incapsula_hcaptcha()\r\n            return result\r\n        else:\r\n            return False\r\n        selector = None\r\n        if await self.is_element_present('[class=\"cf-turnstile\"]'):\r\n            selector = '[class=\"cf-turnstile\"]'\r\n        elif await self.is_element_present(\"#challenge-form div > div\"):\r\n            selector = \"#challenge-form div > div\"\r\n        elif await self.is_element_present('[style=\"display: grid;\"] div div'):\r\n            selector = '[style=\"display: grid;\"] div div'\r\n        elif await self.is_element_present(\"[class*=spacer] + div div\"):\r\n            selector = '[class*=spacer] + div div'\r\n        elif await self.is_element_present(\".spacer div:not([class])\"):\r\n            selector = \".spacer div:not([class])\"\r\n        elif await self.is_element_present('[data-testid*=\"challenge-\"] div'):\r\n            selector = '[data-testid*=\"challenge-\"] div'\r\n        elif await self.is_element_present(\r\n            \"div#turnstile-widget div:not([class])\"\r\n        ):\r\n            selector = \"div#turnstile-widget div:not([class])\"\r\n        elif await self.is_element_present(\"ngx-turnstile div:not([class])\"):\r\n            selector = \"ngx-turnstile div:not([class])\"\r\n        elif await self.is_element_present(\r\n            'form div:not([class]):has(input[name*=\"cf-turn\"])'\r\n        ):\r\n            selector = 'form div:not([class]):has(input[name*=\"cf-turn\"])'\r\n        elif await self.is_element_present(\"form div:not(:has(*))\"):\r\n            selector = \"form div:not(:has(*))\"\r\n        elif await self.is_element_present(\r\n            \"body > div#check > div:not([class])\"\r\n        ):\r\n            selector = \"body > div#check > div:not([class])\"\r\n        elif await self.is_element_present(\".cf-turnstile-wrapper\"):\r\n            selector = \".cf-turnstile-wrapper\"\r\n        elif await self.is_element_present(\r\n            '[id*=\"turnstile\"] div:not([class])'\r\n        ):\r\n            selector = '[id*=\"turnstile\"] div:not([class])'\r\n        elif await self.is_element_present(\r\n            '[class*=\"turnstile\"] div:not([class])'\r\n        ):\r\n            selector = '[class*=\"turnstile\"] div:not([class])'\r\n        elif await self.is_element_present(\r\n            '[data-callback=\"onCaptchaSuccess\"]'\r\n        ):\r\n            selector = '[data-callback=\"onCaptchaSuccess\"]'\r\n        elif await self.is_element_present(\r\n            \"div:not([class]):not([id]):not([aria-label]) > \"\r\n            \"div:not([class]):not([id]):not([aria-label])\"\r\n        ):\r\n            selector = (\r\n                \"div:not([class]):not([id]):not([aria-label]) > \"\r\n                \"div:not([class]):not([id]):not([aria-label])\"\r\n            )\r\n        else:\r\n            return False\r\n        if not selector:\r\n            return False\r\n        if (\r\n            await self.is_element_present(\"form\")\r\n            and (\r\n                await self.is_element_present('form[class*=\"center\"]')\r\n                or await self.is_element_present('form[class*=\"right\"]')\r\n                or await self.is_element_present('form div[class*=\"center\"]')\r\n                or await self.is_element_present('form div[class*=\"right\"]')\r\n            )\r\n        ):\r\n            script = (\r\n                \"\"\"var $elements = document.querySelectorAll(\r\n                'form[class], form div[class]');\r\n                var index = 0, length = $elements.length;\r\n                for(; index < length; index++){\r\n                the_class = $elements[index].getAttribute('class');\r\n                new_class = the_class.replaceAll('center', 'left');\r\n                new_class = new_class.replaceAll('right', 'left');\r\n                $elements[index].setAttribute('class', new_class);}\"\"\"\r\n            )\r\n            with suppress(Exception):\r\n                await self.evaluate(script)\r\n        elif (\r\n            await self.is_element_present(\"form\")\r\n            and (\r\n                await self.is_element_present('form div[style*=\"center\"]')\r\n                or await self.is_element_present('form div[style*=\"right\"]')\r\n            )\r\n        ):\r\n            script = (\r\n                \"\"\"var $elements = document.querySelectorAll(\r\n                'form[style], form div[style]');\r\n                var index = 0, length = $elements.length;\r\n                for(; index < length; index++){\r\n                the_style = $elements[index].getAttribute('style');\r\n                new_style = the_style.replaceAll('center', 'left');\r\n                new_style = new_style.replaceAll('right', 'left');\r\n                $elements[index].setAttribute('style', new_style);}\"\"\"\r\n            )\r\n            with suppress(Exception):\r\n                await self.evaluate(script)\r\n        elif (\r\n            await self.is_element_present(\r\n                'form [id*=\"turnstile\"] div:not([class])'\r\n            )\r\n            or await self.is_element_present(\r\n                'form [class*=\"turnstile\"] div:not([class])'\r\n            )\r\n        ):\r\n            script = (\r\n                \"\"\"var $elements = document.querySelectorAll(\r\n                'form [id*=\"turnstile\"]');\r\n                var index = 0, length = $elements.length;\r\n                for(; index < length; index++){\r\n                $elements[index].setAttribute('align', 'left');}\r\n                var $elements = document.querySelectorAll(\r\n                'form [class*=\"turnstile\"]');\r\n                var index = 0, length = $elements.length;\r\n                for(; index < length; index++){\r\n                $elements[index].setAttribute('align', 'left');}\"\"\"\r\n            )\r\n            with suppress(Exception):\r\n                await self.evaluate(script)\r\n        elif (\r\n            await self.is_element_present(\r\n                '[style*=\"text-align: center;\"] div:not([class])'\r\n            )\r\n        ):\r\n            script = (\r\n                \"\"\"var $elements = document.querySelectorAll(\r\n                '[style*=\"text-align: center;\"]');\r\n                var index = 0, length = $elements.length;\r\n                for(; index < length; index++){\r\n                the_style = $elements[index].getAttribute('style');\r\n                new_style = the_style.replaceAll('center', 'left');\r\n                $elements[index].setAttribute('style', new_style);}\"\"\"\r\n            )\r\n            with suppress(Exception):\r\n                await self.evaluate(script)\r\n        with suppress(Exception):\r\n            await self.sleep(0.05)\r\n            element_rect = await self.get_gui_element_rect(selector, timeout=1)\r\n            e_x = element_rect[\"x\"]\r\n            e_y = element_rect[\"y\"]\r\n            x_offset = 28\r\n            y_offset = 32\r\n            if await asyncio.to_thread(shared_utils.is_windows):\r\n                y_offset = 28\r\n            x = e_x + x_offset\r\n            y = e_y + y_offset\r\n            sb_config._saved_cf_x_y = (x, y)  # For debugging later\r\n            await self.sleep(0.11)\r\n            gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)\r\n            with await asyncio.to_thread(gui_lock.acquire):\r\n                await self.bring_to_front()\r\n                await self.sleep(0.05)\r\n                await self.click_with_offset(\r\n                    selector, x_offset, y_offset, timeout=1\r\n                )\r\n                await self.sleep(0.22)\r\n            return True\r\n        return False\r\n\r\n    async def click_captcha(self):\r\n        await self.solve_captcha()\r\n\r\n    async def get_document(self):\r\n        return await self.send(cdp.dom.get_document())\r\n\r\n    async def get_flattened_document(self):\r\n        return await self.send(cdp.dom.get_flattened_document())\r\n\r\n    async def get_local_storage(self):\r\n        \"\"\"\r\n        Get local storage items as dict of strings.\r\n        Proper deserialization may need to be done.\r\n        \"\"\"\r\n        if not self.target.url:\r\n            await self\r\n        origin = \"/\".join(self.url.split(\"/\", 3)[:-1])\r\n        items = await self.send(\r\n            cdp.dom_storage.get_dom_storage_items(\r\n                cdp.dom_storage.StorageId(\r\n                    is_local_storage=True, security_origin=origin\r\n                )\r\n            )\r\n        )\r\n        retval = {}\r\n        for item in items:\r\n            retval[item[0]] = item[1]\r\n        return retval\r\n\r\n    async def set_local_storage(self, items: dict):\r\n        \"\"\"\r\n        Set local storage.\r\n        Dict items must be strings.\r\n        Simple types will be converted to strings automatically.\r\n        :param items: dict containing {key:str, value:str}\r\n        :type items: dict[str,str]\r\n        \"\"\"\r\n        if not self.target.url:\r\n            await self\r\n        origin = \"/\".join(self.url.split(\"/\", 3)[:-1])\r\n        await asyncio.gather(\r\n            *[\r\n                self.send(\r\n                    cdp.dom_storage.set_dom_storage_item(\r\n                        storage_id=cdp.dom_storage.StorageId(\r\n                            is_local_storage=True, security_origin=origin\r\n                        ),\r\n                        key=str(key),\r\n                        value=str(val),\r\n                    )\r\n                )\r\n                for key, val in items.items()\r\n            ]\r\n        )\r\n\r\n    def __call__(\r\n        self,\r\n        text: Optional[str] = \"\",\r\n        selector: Optional[str] = \"\",\r\n        timeout: Optional[Union[int, float]] = 10,\r\n    ):\r\n        \"\"\"\r\n        Alias to query_selector_all or find_elements_by_text,\r\n        depending on whether text= is set or selector= is set.\r\n        :param selector: css selector string\r\n        :type selector: str\r\n        \"\"\"\r\n        return self.wait_for(\r\n            selector=selector, text=text, timeout=timeout\r\n        )\r\n\r\n    def __eq__(self, other: Tab):\r\n        try:\r\n            return other.target == self.target\r\n        except (AttributeError, TypeError):\r\n            return False\r\n\r\n    def __getattr__(self, item):\r\n        try:\r\n            return getattr(self._target, item)\r\n        except AttributeError:\r\n            raise AttributeError(\r\n                f'\"{self.__class__.__name__}\" has no attribute \"%s\"' % item\r\n            )\r\n\r\n    def __repr__(self):\r\n        extra = \"\"\r\n        if self.target.url:\r\n            extra = f\"[url: {self.target.url}]\"\r\n        s = f\"<{type(self).__name__} [{self.target_id}] [{self.type_}] {extra}>\"  # noqa\r\n        return s\r\n"
  },
  {
    "path": "seleniumbase/undetected/dprocess.py",
    "content": "import os\r\nimport sys\r\nimport atexit\r\nimport logging\r\nimport platform\r\nfrom contextlib import suppress\r\nfrom subprocess import PIPE\r\nfrom subprocess import Popen\r\n\r\nCREATE_NEW_PROCESS_GROUP = 0x00000200\r\nDETACHED_PROCESS = 0x00000008\r\nREGISTERED = []\r\n\r\n\r\ndef start_detached(executable, *args):\r\n    \"\"\"Starts a fully independent subprocess with no parent.\r\n    :param executable: executable\r\n    :param args: arguments to the executable,\r\n        eg: [\"--param1_key=param1_val\", \"-vvv\"]\r\n    :return: pid of the grandchild process \"\"\"\r\n    import multiprocessing\r\n\r\n    reader, writer = multiprocessing.Pipe(False)  # Create pipe\r\n    multiprocessing.Process(\r\n        target=_start_detached,\r\n        args=(executable, *args),\r\n        kwargs={\"writer\": writer},\r\n        daemon=True,\r\n    ).start()\r\n    # Receive pid from pipe\r\n    pid = reader.recv()\r\n    REGISTERED.append(pid)\r\n    # Close pipes\r\n    writer.close()\r\n    reader.close()\r\n    return pid\r\n\r\n\r\ndef _start_detached(executable, *args, writer=None):\r\n    # Configure Launch\r\n    kwargs = {}\r\n    if platform.system() == \"Windows\":\r\n        kwargs.update(\r\n            creationflags=DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP\r\n        )\r\n    else:\r\n        kwargs.update(start_new_session=True)\r\n    p = Popen(\r\n        [executable, *args], stdin=PIPE, stdout=PIPE, stderr=PIPE, **kwargs\r\n    )\r\n    # Send pid to pipe\r\n    writer.send(p.pid)\r\n    sys.exit()\r\n\r\n\r\ndef _cleanup():\r\n    import signal\r\n\r\n    for pid in REGISTERED:\r\n        with suppress(Exception):\r\n            logging.getLogger(__name__).debug(\"cleaning up pid %d \" % pid)\r\n            os.kill(pid, signal.SIGTERM)\r\n\r\n\r\natexit.register(_cleanup)\r\n"
  },
  {
    "path": "seleniumbase/undetected/options.py",
    "content": "import json\r\nimport os\r\nfrom contextlib import suppress\r\nfrom selenium.webdriver.chromium.options import ChromiumOptions\r\n\r\n\r\nclass ChromeOptions(ChromiumOptions):\r\n    _session = None\r\n    _user_data_dir = None\r\n\r\n    @property\r\n    def user_data_dir(self):\r\n        return self._user_data_dir\r\n\r\n    @user_data_dir.setter\r\n    def user_data_dir(self, path):\r\n        \"\"\"Sets the browser profile folder to use. Creates one if missing.\"\"\"\r\n        abs_path = os.path.abspath(path)\r\n        self._user_data_dir = os.path.normpath(abs_path)\r\n\r\n    @staticmethod\r\n    def _undot_key(key, value):\r\n        \"\"\"Turn a (dotted key, value) into a proper nested dict.\"\"\"\r\n        if \".\" in key:\r\n            key, rest = key.split(\".\", 1)\r\n            value = ChromeOptions._undot_key(rest, value)\r\n        return {key: value}\r\n\r\n    @staticmethod\r\n    def _merge_nested(a, b):\r\n        \"\"\"Merges b into a, overwriting duplicate leaf values using b.\"\"\"\r\n        for key in b:\r\n            if key in a:\r\n                if isinstance(a[key], dict) and isinstance(b[key], dict):\r\n                    ChromeOptions._merge_nested(a[key], b[key])\r\n                    continue\r\n            a[key] = b[key]\r\n        return a\r\n\r\n    def handle_prefs(self, user_data_dir):\r\n        prefs = self.experimental_options.get(\"prefs\")\r\n        if prefs:\r\n            user_data_dir = user_data_dir or self._user_data_dir\r\n            default_path = os.path.join(user_data_dir, \"Default\")\r\n            os.makedirs(default_path, exist_ok=True)\r\n            undot_prefs = {}\r\n            for key, value in prefs.items():\r\n                undot_prefs = self._merge_nested(\r\n                    undot_prefs, self._undot_key(key, value)\r\n                )\r\n            prefs_file = os.path.join(default_path, \"Preferences\")\r\n            with suppress(Exception):\r\n                if os.path.exists(prefs_file):\r\n                    with open(\r\n                        prefs_file, encoding=\"utf-8\", mode=\"r\", errors=\"ignore\"\r\n                    ) as f:\r\n                        undot_prefs = self._merge_nested(\r\n                            json.load(f), undot_prefs\r\n                        )\r\n            with suppress(Exception):\r\n                with open(\r\n                    prefs_file, encoding=\"utf-8\", mode=\"w\", errors=\"ignore\"\r\n                ) as f:\r\n                    json.dump(undot_prefs, f)\r\n            # Remove experimental_options to avoid errors\r\n            del self._experimental_options[\"prefs\"]\r\n        exclude_switches = self.experimental_options.get(\"excludeSwitches\")\r\n        if exclude_switches is not None:\r\n            del self._experimental_options[\"excludeSwitches\"]\r\n        use_auto_ext = self.experimental_options.get(\"useAutomationExtension\")\r\n        if use_auto_ext is not None:\r\n            del self._experimental_options[\"useAutomationExtension\"]\r\n\r\n    @classmethod\r\n    def from_options(cls, options):\r\n        o = cls()\r\n        o.__dict__.update(options.__dict__)\r\n        return o\r\n"
  },
  {
    "path": "seleniumbase/undetected/patcher.py",
    "content": "import io\r\nimport logging\r\nimport os\r\nimport random\r\nimport re\r\nimport string\r\nimport sys\r\nimport time\r\nimport zipfile\r\nfrom contextlib import suppress\r\nfrom seleniumbase.console_scripts import sb_install\r\nfrom seleniumbase.fixtures import shared_utils\r\n\r\nlogger = logging.getLogger(__name__)\r\nIS_POSIX = sys.platform.startswith((\"darwin\", \"cygwin\", \"linux\"))\r\n\r\n\r\nclass Patcher(object):\r\n    from seleniumbase.core import download_helper\r\n\r\n    url_repo = \"https://chromedriver.storage.googleapis.com\"\r\n    zip_name = \"chromedriver_%s.zip\"\r\n    exe_name = \"chromedriver%s\"\r\n    sys_plat = sys.platform\r\n    # downloads_folder = \"~/.undetected_drivers\"\r\n    if sys_plat.endswith(\"win32\"):\r\n        zip_name %= \"win32\"\r\n        exe_name %= \".exe\"\r\n        # downloads_folder = \"~/appdata/roaming/undetected_drivers\"\r\n    if sys_plat.endswith(\"linux\"):\r\n        zip_name %= \"linux64\"\r\n        exe_name %= \"\"\r\n        # downloads_folder = \"~/.local/share/undetected_drivers\"\r\n    if sys_plat.endswith(\"darwin\"):\r\n        zip_name %= \"mac64\"\r\n        exe_name %= \"\"\r\n        # downloads_folder = \"~/Library/Application Support/undetected_drivers\"\r\n    downloads_folder = download_helper.get_downloads_folder()\r\n    if not os.path.exists(downloads_folder):\r\n        try:\r\n            os.makedirs(downloads_folder, exist_ok=True)\r\n        except Exception:\r\n            pass  # Only possible during multithreaded tests\r\n    data_path = os.path.abspath(os.path.expanduser(downloads_folder))\r\n\r\n    def __init__(self, executable_path=None, force=False, version_main=0):\r\n        \"\"\"Args:\r\n        executable_path: None = automatic\r\n            A full file path to the chromedriver executable\r\n        force: False\r\n            Terminate processes which are holding lock\r\n        version_main: 0 = auto\r\n            Specify main chrome version (rounded, ex: 82) \"\"\"\r\n        self.force = force\r\n        self.executable_path = None\r\n        prefix = \"undetected\"\r\n        if not os.path.exists(self.data_path):\r\n            with suppress(Exception):\r\n                os.makedirs(self.data_path, exist_ok=True)\r\n        if not executable_path:\r\n            self.executable_path = os.path.join(\r\n                self.data_path, \"_\".join([prefix, self.exe_name])\r\n            )\r\n        if not IS_POSIX:\r\n            if executable_path:\r\n                if not executable_path[-4:] == \".exe\":\r\n                    executable_path += \".exe\"\r\n        self.zip_path = os.path.join(self.data_path, prefix)\r\n        if not executable_path:\r\n            self.executable_path = os.path.abspath(\r\n                os.path.join(\".\", self.executable_path)\r\n            )\r\n        self._custom_exe_path = False\r\n        if executable_path:\r\n            self._custom_exe_path = True\r\n            self.executable_path = executable_path\r\n        self.version_main = version_main\r\n        self.version_full = None\r\n\r\n    def auto(self, executable_path=None, force=False, version_main=None):\r\n        if executable_path:\r\n            self.executable_path = executable_path\r\n            self._custom_exe_path = True\r\n        if self._custom_exe_path:\r\n            ispatched = self.is_binary_patched(self.executable_path)\r\n            if not ispatched:\r\n                return self.patch_exe()\r\n            else:\r\n                return\r\n        if version_main:\r\n            self.version_main = version_main\r\n        if force is True:\r\n            self.force = force\r\n        try:\r\n            os.unlink(self.executable_path)\r\n        except PermissionError:\r\n            if self.force:\r\n                self.force_kill_instances(self.executable_path)\r\n                not_force = not self.force\r\n                return self.auto(force=not_force)\r\n            try:\r\n                if self.is_binary_patched():\r\n                    return True  # Running AND patched\r\n            except PermissionError:\r\n                pass\r\n        except FileNotFoundError:\r\n            pass\r\n        release = self.fetch_release_number()\r\n        self.version_main = release.split(\".\")[0]\r\n        self.version_full = release\r\n        if int(self.version_main) < 115:\r\n            self.unzip_package(self.fetch_package())\r\n        else:\r\n            sb_install.main(\r\n                override=\"chromedriver %s\" % self.version_main,\r\n                intel_for_uc=shared_utils.is_arm_mac(),\r\n                force_uc=True,\r\n            )\r\n        return self.patch()\r\n\r\n    def patch(self):\r\n        self.patch_exe()\r\n        return self.is_binary_patched()\r\n\r\n    def fetch_release_number(self):\r\n        from urllib.request import urlopen\r\n\r\n        path = \"/latest_release\"\r\n        if self.version_main:\r\n            path += \"_%s\" % self.version_main\r\n        path = path.upper()\r\n        logger.debug(\"Getting release number from %s\" % path)\r\n        if self.version_main and int(self.version_main) > 114:\r\n            return (\r\n                sb_install.get_cft_latest_version_from_milestone(\r\n                    str(self.version_main)\r\n                )\r\n            )\r\n        return urlopen(self.url_repo + path).read().decode()\r\n\r\n    def fetch_package(self):\r\n        \"\"\"Downloads chromedriver from source.\r\n        :return: path to downloaded file \"\"\"\r\n        from urllib.request import urlretrieve\r\n\r\n        u = \"%s/%s/%s\" % (\r\n            self.url_repo, self.version_full, self.zip_name\r\n        )\r\n        logger.debug(\"Downloading from %s\" % u)\r\n        return urlretrieve(u)[0]\r\n\r\n    def unzip_package(self, fp):\r\n        \"\"\" :return: path to unpacked executable \"\"\"\r\n        logger.debug(\"Unzipping %s\" % fp)\r\n        try:\r\n            os.unlink(self.zip_path)\r\n        except (FileNotFoundError, OSError):\r\n            pass\r\n        try:\r\n            os.makedirs(self.zip_path, mode=0o755, exist_ok=True)\r\n        except Exception:\r\n            pass\r\n        with zipfile.ZipFile(fp, mode=\"r\") as zf:\r\n            zf.extract(self.exe_name, self.zip_path)\r\n        try:\r\n            os.rename(\r\n                os.path.join(self.zip_path, self.exe_name),\r\n                self.executable_path,\r\n            )\r\n            os.remove(fp)\r\n        except PermissionError:\r\n            pass\r\n        try:\r\n            os.rmdir(self.zip_path)\r\n            os.chmod(self.executable_path, 0o755)\r\n        except PermissionError:\r\n            pass\r\n        return self.executable_path\r\n\r\n    @staticmethod\r\n    def force_kill_instances(exe_name):\r\n        \"\"\"Terminate instances of UC.\r\n        :param: Executable name to kill. (Can be a path)\r\n        :return: True on success else False.\"\"\"\r\n        exe_name = os.path.basename(exe_name)\r\n        if IS_POSIX:\r\n            r = os.system(\"kill -f -9 $(pidof %s)\" % exe_name)\r\n        else:\r\n            r = os.system(\"taskkill /f /im %s\" % exe_name)\r\n        return not r\r\n\r\n    @staticmethod\r\n    def gen_random_cdc():\r\n        cdc = random.choices(string.ascii_lowercase, k=26)\r\n        cdc[-6:-4] = map(str.upper, cdc[-6:-4])\r\n        cdc[2] = cdc[0]\r\n        cdc[3] = \"_\"\r\n        return \"\".join(cdc).encode()\r\n\r\n    def is_binary_patched(self, executable_path=None):\r\n        executable_path = executable_path or self.executable_path\r\n        with io.open(executable_path, \"rb\") as fh:\r\n            if re.search(\r\n                b\"window.cdc_adoQpoasnfa76pfcZLmcfl_\"\r\n                b\"(Array|Promise|Symbol|Object|Proxy|JSON|Window)\",\r\n                fh.read()\r\n            ):\r\n                return False\r\n        return True\r\n\r\n    def patch_exe(self):\r\n        \"\"\"Patches the ChromeDriver binary\"\"\"\r\n        def gen_js_whitespaces(match):\r\n            return b\"\\n\" * len(match.group())\r\n\r\n        def gen_call_function_js_cache_name(match):\r\n            rep_len = len(match.group()) - 3\r\n            ran_len = random.randint(6, rep_len)\r\n            bb = b\"'\" + bytes(str().join(random.choices(\r\n                population=string.ascii_letters, k=ran_len\r\n            )), 'ascii') + b\"';\" + (b\"\\n\" * (rep_len - ran_len))\r\n            return bb\r\n\r\n        with io.open(self.executable_path, \"r+b\") as fh:\r\n            file_bin = fh.read()\r\n            file_bin = re.sub(\r\n                b\"window\\\\.cdc_[a-zA-Z0-9]{22}_\"\r\n                b\"(Array|Promise|Symbol|Object|Proxy|JSON|Window) \"\r\n                b\"= window\\\\.(Array|Promise|Symbol|Object|Proxy|JSON|Window);\",\r\n                gen_js_whitespaces,\r\n                file_bin,\r\n            )\r\n            file_bin = re.sub(\r\n                b\"window\\\\.cdc_[a-zA-Z0-9]{22}_\"\r\n                b\"(Array|Promise|Symbol|Object|Proxy|JSON|Window) \\\\|\\\\|\",\r\n                gen_js_whitespaces,\r\n                file_bin,\r\n            )\r\n            file_bin = re.sub(\r\n                b\"'\\\\$cdc_[a-zA-Z0-9]{22}_';\",\r\n                gen_call_function_js_cache_name,\r\n                file_bin,\r\n            )\r\n            fh.seek(0)\r\n            fh.write(file_bin)\r\n        return True\r\n\r\n    @staticmethod\r\n    def gen_random_cdc_beta():\r\n        cdc = random.choices(string.ascii_letters, k=27)\r\n        return \"\".join(cdc).encode()\r\n\r\n    def is_binary_patched_beta(self, executable_path=None):\r\n        executable_path = executable_path or self.executable_path\r\n        try:\r\n            with io.open(executable_path, \"rb\") as fh:\r\n                return fh.read().find(b\"undetected chromedriver\") != -1\r\n        except FileNotFoundError:\r\n            return False\r\n\r\n    def patch_exe_beta(self):\r\n        with io.open(self.executable_path, \"r+b\") as fh:\r\n            content = fh.read()\r\n            match_injected_codeblock = re.search(\r\n                rb\"\\{window\\.cdc.*?;\\}\", content\r\n            )\r\n            target_bytes = None\r\n            if match_injected_codeblock:\r\n                target_bytes = match_injected_codeblock[0]\r\n                new_target_bytes = (\r\n                    b'{console.log(\"chromedriver is undetectable!\")}'.ljust(\r\n                        len(target_bytes), b\" \"\r\n                    )\r\n                )\r\n            if target_bytes:\r\n                new_content = content.replace(target_bytes, new_target_bytes)\r\n                if new_content == content:\r\n                    pass  # Unable to patch driver\r\n                else:\r\n                    fh.seek(0)\r\n                    fh.write(new_content)\r\n\r\n    def __repr__(self):\r\n        return \"{0:s}({1:s})\".format(\r\n            self.__class__.__name__,\r\n            self.executable_path,\r\n        )\r\n\r\n    def __del__(self):\r\n        if self._custom_exe_path:\r\n            # If the driver binary is specified by the user,\r\n            # then assume it is important enough to keep it.\r\n            return\r\n        else:\r\n            timeout = 3\r\n            t = time.monotonic()\r\n            while True:\r\n                now = time.monotonic()\r\n                if now - t > timeout:\r\n                    logger.debug(\r\n                        \"Could not unlink %s in time (%d seconds)\"\r\n                        % (self.executable_path, timeout)\r\n                    )\r\n                    break\r\n                try:\r\n                    os.unlink(self.executable_path)\r\n                    logger.debug(\r\n                        \"Successfully unlinked %s\"\r\n                        % self.executable_path\r\n                    )\r\n                    break\r\n                except (OSError, RuntimeError, PermissionError):\r\n                    time.sleep(0.1)\r\n                    continue\r\n                except FileNotFoundError:\r\n                    break\r\n"
  },
  {
    "path": "seleniumbase/undetected/reactor.py",
    "content": "import asyncio\r\nimport json\r\nimport logging\r\nimport threading\r\n\r\nlogger = logging.getLogger(__name__)\r\n\r\n\r\nclass Reactor(threading.Thread):\r\n    def __init__(self, driver):\r\n        super().__init__()\r\n        self.driver = driver\r\n        self.loop = asyncio.new_event_loop()\r\n        self.lock = threading.Lock()\r\n        self.event = threading.Event()\r\n        self.daemon = True\r\n        self.handlers = {}\r\n\r\n    def add_event_handler(self, method_name, callback):\r\n        \"\"\"\r\n        Parameters\r\n        ----------\r\n        event_name: str\r\n            example \"Network.responseReceived\"\r\n\r\n        callback: callable\r\n            callable which accepts 1 parameter: the message object dictionary\r\n        \"\"\"\r\n        with self.lock:\r\n            self.handlers[method_name.lower()] = callback\r\n\r\n    @property\r\n    def running(self):\r\n        return not self.event.is_set()\r\n\r\n    def run(self):\r\n        try:\r\n            asyncio.set_event_loop(self.loop)\r\n            self.loop.run_until_complete(self.listen())\r\n        except Exception as e:\r\n            logger.warning(\"Reactor.run() => %s\", e)\r\n\r\n    async def _wait_service_started(self):\r\n        while True:\r\n            with self.lock:\r\n                if (\r\n                    getattr(self.driver, \"service\", None)\r\n                    and getattr(self.driver.service, \"process\", None)\r\n                    and self.driver.service.process.poll()\r\n                ):\r\n                    await asyncio.sleep(self.driver._delay or 0.25)\r\n                else:\r\n                    break\r\n\r\n    async def listen(self):\r\n        while self.running:\r\n            await self._wait_service_started()\r\n            await asyncio.sleep(1)\r\n            try:\r\n                with self.lock:\r\n                    log_entries = self.driver.get_log(\"performance\")\r\n                for entry in log_entries:\r\n                    try:\r\n                        obj_serialized: str = entry.get(\"message\")\r\n                        obj = json.loads(obj_serialized)\r\n                        message = obj.get(\"message\")\r\n                        method = message.get(\"method\")\r\n                        if \"*\" in self.handlers:\r\n                            await self.loop.run_in_executor(\r\n                                None, self.handlers[\"*\"], message\r\n                            )\r\n                        elif method.lower() in self.handlers:\r\n                            await self.loop.run_in_executor(\r\n                                None, self.handlers[method.lower()], message\r\n                            )\r\n                    except Exception as e:\r\n                        raise e from None\r\n\r\n            except Exception as e:\r\n                if \"invalid session id\" in str(e):\r\n                    pass\r\n                else:\r\n                    logger.debug(\"Exception ignored: %s\", e)\r\n"
  },
  {
    "path": "seleniumbase/undetected/webelement.py",
    "content": "import re\r\nimport selenium.webdriver.remote.webelement\r\nfrom seleniumbase.fixtures import js_utils\r\n\r\n\r\nclass WebElement(selenium.webdriver.remote.webelement.WebElement):\r\n    def uc_click(\r\n        self,\r\n        driver=None,\r\n        selector=None,\r\n        by=None,\r\n        reconnect_time=None,\r\n        tag_name=None,\r\n    ):\r\n        if driver and selector and by:\r\n            delayed_click = False\r\n            if tag_name in [\"span\", \"button\", \"div\", \"a\", \"b\", \"input\"]:\r\n                delayed_click = True\r\n            if delayed_click and \":contains\" not in selector:\r\n                selector = js_utils.convert_to_css_selector(selector, by)\r\n                selector = re.escape(selector)\r\n                selector = js_utils.escape_quotes_if_needed(selector)\r\n                script = 'document.querySelector(\"%s\").click();' % selector\r\n                js_utils.call_me_later(driver, script, 111)\r\n            else:\r\n                driver.js_click(selector, by=by, timeout=1)\r\n        else:\r\n            super().click()\r\n        driver = self._parent\r\n        if not reconnect_time:\r\n            driver.reconnect(0.5)\r\n        else:\r\n            driver.reconnect(reconnect_time)\r\n\r\n    def uc_reconnect(self, reconnect_time=None):\r\n        if not reconnect_time:\r\n            self._parent.reconnect(0.2)\r\n        else:\r\n            self._parent.reconnect(reconnect_time)\r\n"
  },
  {
    "path": "seleniumbase/utilities/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n<a href=\"https://github.com/seleniumbase/SeleniumBase/blob/master/README.md\"><img src=\"https://seleniumbase.github.io/cdn/img/super_logo_sb.png\" title=\"SeleniumBase\" width=\"290\"></a>\n\n<h2><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"28\" /> The Selenium Grid Hub:</h2>\n\nThe Selenium Grid Hub lets you distribute tests to run in parallel across multiple node machines. Each node machine can then run its own allocation of tests. This allows you to run a large suite of tests very quickly.\n\n<h3><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"28\" /> Running the Selenium Grid Hub:</h3>\n\nThe following commands will work once you've installed seleniumbase.\n\n<h4><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"28\" /> Downloading the Selenium Server JAR file:</h4>\n\n```zsh\nseleniumbase download server\n```\n\n* (Required for using your own Selenium Grid)\n\n<h4><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"28\" /> Grid Hub server controls:</h4>\n\n```zsh\nseleniumbase grid-hub {start|stop|restart} [OPTIONS]\n```\n\n<b>Options:</b>\n<ul>\n<li> -v / --verbose  (Increases verbosity of logging output.)</li>\n<li> --timeout=TIMEOUT  (Close idle browser after TIMEOUT sec.)</li>\n</ul>\n\n<h4><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"28\" /> Grid node server controls:</h4>\n\n```zsh\nseleniumbase grid-node {start|stop|restart} --hub=[HUB_IP] [OPTIONS]\n```\n\n<b>Options:</b>\n<ul>\n<li> -v / --verbose  (Increases verbosity of logging output.)</li>\n<li> --hub=[HUB_IP]  (Specifies the Grid Hub to connect to. Default: \"127.0.0.1\".)</li>\n</ul>\n\nWhen the Grid Hub Console is up and running, you'll be able to find it here: [http://127.0.0.1:4444/grid/console](http://127.0.0.1:4444/grid/console)\n\nNow you can run your tests on the Selenium Grid:\n\n```zsh\npytest test_demo_site.py --server=IP_ADDRESS --port=4444\n```\n\nYou can also run your tests on someone else's Selenium Grid to avoid managing your own. Here are some Selenium Grids that you can use (and the run command format):\n\n* [BrowserStack](https://www.browserstack.com/automate#) Selenium Grid:\n\n```zsh\npytest test_demo_site.py --server=USERNAME:KEY@hub.browserstack.com --port=80\n```\n\n* [Sauce Labs](https://saucelabs.com/products/platform-configurator) Selenium Grid:\n\n```zsh\npytest test_demo_site.py --server=USERNAME:KEY@ondemand.us-east-1.saucelabs.com --port=443 --protocol=https\n```\n\nTo use the `https` protocol, add `--protocol=https`:\n(<i>`https` is the default protocol for `--port=443`.</i>)\n\n```zsh\npytest test_demo_site.py --protocol=https --server=IP_ADDRESS --port=PORT\n```\n\n(For setting browser desired capabilities while running Selenium remotely, see the <a href=\"https://seleniumbase.io/help_docs/desired_capabilities/\">desired capabilities documentation</a> and the sample files located in <a href=\"https://github.com/seleniumbase/SeleniumBase/tree/master/examples/capabilities\">SeleniumBase/examples/capabilities</a>)\n\n<h4><img src=\"https://seleniumbase.github.io/img/logo6.png\" title=\"SeleniumBase\" width=\"28\" /> More info about the Selenium Grid Hub can be found here:</h4>\n\n* [https://www.selenium.dev/documentation/grid/](https://www.selenium.dev/documentation/grid/)\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/download_selenium_server.py",
    "content": "\"\"\"Downloads the Selenium Server JAR file and renames it.\"\"\"\nimport os\nimport shutil\nimport sys\nfrom urllib.request import urlopen\n\nSELENIUM_JAR = (\n    \"http://selenium-release.storage.googleapis.com\"\n    \"/3.141/selenium-server-standalone-3.141.59.jar\"\n)\nJAR_FILE = \"selenium-server-standalone-3.141.59.jar\"\nRENAMED_JAR_FILE = \"selenium-server-standalone.jar\"\n\ndir_path = os.path.dirname(os.path.realpath(__file__))\nFULL_EXPECTED_PATH = dir_path + \"/\" + RENAMED_JAR_FILE\nFULL_DOWNLOAD_PATH = os.getcwd() + \"/\" + RENAMED_JAR_FILE\n\n\ndef download_selenium_server():\n    \"\"\"Downloads the Selenium Server JAR file.\"\"\"\n    try:\n        local_file = open(JAR_FILE, mode=\"wb\")\n        remote_file = urlopen(SELENIUM_JAR)\n        print(\"Downloading the Selenium Server JAR file...\\n\")\n        local_file.write(remote_file.read())\n        local_file.close()\n        remote_file.close()\n        print(\"Download Complete!\")\n    except Exception:\n        raise Exception(\n            \"Error downloading the Selenium Server JAR file.\\n\"\n            \"Details: %s\" % sys.exc_info()[1]\n        )\n\n\ndef is_available_locally():\n    return os.path.isfile(FULL_EXPECTED_PATH)\n\n\ndef main(force_download=True):\n    if force_download or not is_available_locally():\n        download_selenium_server()\n        for filename in os.listdir(\".\"):\n            # If multiple copies exist, keep only the latest and rename it.\n            if filename.startswith(\"selenium-server-standalone-\"):\n                shutil.move(filename, RENAMED_JAR_FILE)\n                if FULL_DOWNLOAD_PATH != FULL_EXPECTED_PATH:\n                    shutil.move(RENAMED_JAR_FILE, FULL_EXPECTED_PATH)\n        print(\"%s\\n\" % FULL_EXPECTED_PATH)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/font_color",
    "content": "#!/usr/bin/env bash\n\n# Text color variables\ntxtund=$(tput sgr 0 1)   # Underline\ntxtbld=$(tput bold)      # Bold\nregred=$(tput setaf 1)   # Red\nregblu=$(tput setaf 4)   # Blue\nreggrn=$(tput setaf 2)   # Green\nregwht=$(tput setaf 7)   # White\ntxtrst=$(tput sgr0)      # Reset\ninfo=${regwht}*${txtrst} # Feedback\npass=${regblu}*${txtrst}\nwarn=${regred}*${txtrst}\nques=${regblu}?${txtrst}\n\nFAIL_MSG=\"${regred}[FAILURE]${txtrst}\"\nSUCCESS_MSG=\"${reggrn}[SUCCESS]${txtrst}\"\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/grid-hub",
    "content": "#!/usr/bin/env bash\n\n#\n# Usage: grid-hub {start|stop} [TIMEOUT]\n#\n\nsource $(dirname $0)/font_color\n\nEXPECTED_ARGS=2\nE_BADARGS=65\n\nDO_showUsage() {\n    echo \"Usage: $(basename $0) {start|stop} [TIMEOUT]\"\n    exit $E_BADARGS\n}\n\nif [ $# -ne $EXPECTED_ARGS ]; then\n    DO_showUsage\nfi\n\n################################################################################\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null && pwd )\"\nGRID_HUB_VERBOSE_LOGS_FILE=${DIR}/verbose_hub_server.dat\n\nGRID_HUB_VERBOSE_LOGS=\"False\"\nif [ -f $GRID_HUB_VERBOSE_LOGS_FILE ]; then\n    GRID_HUB_VERBOSE_LOGS=$(cat $GRID_HUB_VERBOSE_LOGS_FILE)\nfi\n\nVERBOSITY_STRING=\"-Djava.util.logging.config.file=test/logging.properties \"\nif [ \"$GRID_HUB_VERBOSE_LOGS\" == \"True\" ]; then\n  VERBOSITY_STRING=\"\"\nfi\n\nWEBDRIVER_SERVER_JAR=${DIR}/selenium-server-standalone.jar\nWEBDRIVER_HUB_PARAMS=\"-role hub -timeout $2 -browserTimeout 170 -port 4444\"\nWEBDRIVER_HUB_PIDFILE=\"/tmp/webdriver_hub.pid\"\n\nif [ ! -f $WEBDRIVER_SERVER_JAR ]; then\n    echo \"You must place the Selenium-WebDriver standalone JAR file at ${WEBDRIVER_SERVER_JAR} before proceeding.\"\n    exit 1\nfi\n\ncase \"$1\" in\n    start)\n        echo \"Starting Selenium-WebDriver Grid Hub...\"\n        if [ -f $WEBDRIVER_HUB_PIDFILE ]; then\n            echo \"${FAIL_MSG} Selenium-WebDriver Grid Hub already running with PID $(cat $WEBDRIVER_HUB_PIDFILE). Run 'grid-hub stop' or 'grid-hub restart'.\"\n            echo \"\"\n            echo \"Grid Hub console: http://127.0.0.1:4444/grid/console\"\n            echo \"\"\n            exit 1\n        else\n            START_HUB_CMD=\"java ${VERBOSITY_STRING}-jar ${WEBDRIVER_SERVER_JAR} ${WEBDRIVER_HUB_PARAMS}\"\n            echo \"\"\n            echo $START_HUB_CMD\n            echo \"\"\n            $START_HUB_CMD &\n            PID=$!\n            echo $PID > \"${WEBDRIVER_HUB_PIDFILE}\"\n            echo \"${SUCCESS_MSG} Selenium-WebDriver Grid Hub started successfully.\"\n            echo \"\"\n            echo \"Grid Hub console: http://127.0.0.1:4444/grid/console\"\n            echo \"\"\n            # echo \"To see full log output, remove the java.util.logging.config.file parameter from script/grid-hub\"\n        fi\n        ;;\n    stop)\n        echo \"Stopping Selenium-WebDriver Grid Hub...\"\n        if [ -f $WEBDRIVER_HUB_PIDFILE ]; then\n            PID=$(cat $WEBDRIVER_HUB_PIDFILE)\n            rm $WEBDRIVER_HUB_PIDFILE\n            kill $PID\n            sleep 1\n            if [[ $(ps -A | egrep \"^${PID}\") ]]; then\n                echo \"${FAIL_MSG} Tried to kill the Grid Hub with PID ${PID}, but was unsuccessful. You need to kill it with something stronger, like 'kill -9'\"\n                exit 1\n            else\n                echo \"${SUCCESS_MSG} Selenium-WebDriver Grid Hub stopped successfully.\"\n                exit 0\n            fi\n        else\n            echo \"${SUCCESS_MSG} Selenium-WebDriver Grid Hub has already been stopped.\"\n            exit 0\n        fi\n        ;;\n    restart)\n        $0 stop\n        $0 start\n        ;;\n    *)\n        DO_showUsage\nesac\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/grid-node",
    "content": "#!/usr/bin/env bash\n\n#\n# Usage: grid-node {start|stop}\n#\n\nsource $(dirname $0)/font_color\n\nEXPECTED_ARGS=1\nE_BADARGS=65\n\nDO_showUsage() {\n    echo \"Usage: $(basename $0) {start|stop}\"\n    exit $E_BADARGS\n}\n\nif [ $# -ne $EXPECTED_ARGS ]; then\n    DO_showUsage\nfi\n\n################################################################################\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null && pwd )\"\nGRID_HUB_SERVER_IP_FILE=${DIR}/ip_of_grid_hub.dat\nGRID_NODE_VERBOSE_LOGS_FILE=${DIR}/verbose_node_server.dat\n\nGRID_HUB_SERVER_IP=\"127.0.0.1\"\nif [ -f $GRID_HUB_SERVER_IP_FILE ]; then\n    GRID_HUB_SERVER_IP=$(cat $GRID_HUB_SERVER_IP_FILE)\nfi\n\nGRID_NODE_VERBOSE_LOGS=\"False\"\nif [ -f $GRID_NODE_VERBOSE_LOGS_FILE ]; then\n    GRID_NODE_VERBOSE_LOGS=$(cat $GRID_NODE_VERBOSE_LOGS_FILE)\nfi\n\nVERBOSITY_STRING=\"-Djava.util.logging.config.file=test/logging.properties \"\nif [ \"$GRID_NODE_VERBOSE_LOGS\" == \"True\" ]; then\n  VERBOSITY_STRING=\"\"\nfi\n\nWEBDRIVER_SERVER_JAR=${DIR}/selenium-server-standalone.jar\nWEBDRIVER_NODE_PARAMS=\"-role node -hub http://${GRID_HUB_SERVER_IP}:4444/grid/register -browser browserName=chrome,maxInstances=5,version=latest,seleniumProtocol=WebDriver -browser browserName=firefox,maxInstances=5,version=latest,seleniumProtocol=WebDriver\"\nWEBDRIVER_NODE_PIDFILE=\"/tmp/webdriver_node.pid\"\n\nif [ ! -f $WEBDRIVER_SERVER_JAR ]; then\n    echo \"You must place the Selenium-WebDriver standalone JAR file at ${WEBDRIVER_SERVER_JAR} before proceeding.\"\n    exit 1\nfi\n\ncase \"$1\" in\n    start)\n        echo \"Starting Selenium-WebDriver Grid node...\"\n        if [ -f $WEBDRIVER_NODE_PIDFILE ]; then\n            echo \"${FAIL_MSG} Selenium-WebDriver Grid node already running with PID $(cat $WEBDRIVER_NODE_PIDFILE). Run 'grid-node stop' or 'grid-node restart'.\"\n            exit 1\n        else\n            START_NODE_CMD=\"java ${VERBOSITY_STRING}-jar ${WEBDRIVER_SERVER_JAR} ${WEBDRIVER_NODE_PARAMS}\"\n            echo \"\"\n            echo $START_NODE_CMD\n            echo \"\"\n            $START_NODE_CMD &\n            PID=$!\n            echo $PID > \"${WEBDRIVER_NODE_PIDFILE}\"\n            echo \"${SUCCESS_MSG} Selenium-WebDriver Grid node started successfully.\"\n            echo \"\"\n            # echo \"To see full log output, remove the java.util.logging.config.file parameter from script/grid-node\"\n        fi\n        ;;\n    stop)\n        echo \"Stopping Selenium-WebDriver Grid node...\"\n        if [ -f $WEBDRIVER_NODE_PIDFILE ]; then\n            PID=$(cat $WEBDRIVER_NODE_PIDFILE)\n            rm $WEBDRIVER_NODE_PIDFILE\n            kill $PID\n            sleep 1\n            if [[ $(ps -A | egrep \"^${PID}\") ]]; then\n                echo \"${FAIL_MSG} Tried to kill the node with PID ${PID}, but was unsuccessful. You need to kill it with something stronger, like 'kill -9'\"\n                exit 1\n            else\n                echo \"${SUCCESS_MSG} Selenium-WebDriver Grid node stopped successfully.\"\n                exit 0\n            fi\n        else\n            echo \"${SUCCESS_MSG} Selenium-WebDriver Grid node has already been stopped.\"\n            exit 0\n        fi\n        ;;\n    restart)\n        $0 stop\n        $0 start\n        ;;\n    *)\n        DO_showUsage\nesac\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/grid_hub.py",
    "content": "import os\nimport subprocess\nimport sys\nfrom seleniumbase import drivers  # webdriver storage folder for SeleniumBase\nfrom seleniumbase.fixtures import shared_utils\n\nDRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))\n# Make sure that the SeleniumBase DRIVER_DIR is at the top of the System PATH\n# (Changes to the System PATH with os.environ only last during the test run)\nif not os.environ[\"PATH\"].startswith(DRIVER_DIR):\n    # Remove existing SeleniumBase DRIVER_DIR from System PATH if present\n    os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(DRIVER_DIR, \"\")\n    # If two path separators are next to each other, replace with just one\n    os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(\n        os.pathsep + os.pathsep, os.pathsep\n    )\n    # Put the SeleniumBase DRIVER_DIR at the beginning of the System PATH\n    os.environ[\"PATH\"] = DRIVER_DIR + os.pathsep + os.environ[\"PATH\"]\n\n\ndef invalid_run_command(msg=None):\n    exp = \"  ** grid-hub **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"        seleniumbase grid-hub {start|stop|restart} [OPTIONS]\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"        -v, --verbose  (Increase verbosity of logging output.)\\n\"\n    exp += \"                       (Default: Quiet logging / not verbose.)\\n\"\n    exp += \"        --timeout=TIMEOUT  (Close idle browser after TIMEOUT.)\\n\"\n    exp += \"                           (The default TIMEOUT: 230 seconds.)\\n\"\n    exp += \"                           (Use --timeout=0 to skip timeouts.)\\n\"\n    exp += \"  Example:\\n\"\n    exp += \"        seleniumbase grid-hub start\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"        Controls the Selenium Grid Hub Server, which allows\\n\"\n    exp += \"        for running tests on multiple machines in parallel\\n\"\n    exp += \"        to speed up test runs and reduce the total time\\n\"\n    exp += \"        of test suite execution.\\n\"\n    exp += \"        You can start, restart, or stop the Grid Hub Server.\\n\"\n    if msg:\n        exp += msg\n    raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n\n\ndef main():\n    timeout = 230  # The default number of seconds that a test can be idle\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    num_args = len(sys.argv)\n    if (\n        sys.argv[0].split(\"/\")[-1] == \"seleniumbase\"\n        or (sys.argv[0].split(\"\\\\\")[-1] == \"seleniumbase\")\n        or (sys.argv[0].split(\"/\")[-1] == \"sbase\")\n        or (sys.argv[0].split(\"\\\\\")[-1] == \"sbase\")\n    ):\n        if num_args < 3:\n            invalid_run_command()\n    else:\n        invalid_run_command()\n    grid_hub_command = sys.argv[2]\n    if grid_hub_command not in [\"start\", \"stop\", \"restart\"]:\n        invalid_run_command()\n\n    verbose = \"False\"\n    if num_args >= 4:\n        options = sys.argv[3:]\n        for option in options:\n            if option == \"-v\" or option == \"--verbose\":\n                verbose = \"True\"\n            elif option.startswith(\"--timeout=\") and len(option) > 10:\n                timeout = option.split(\"--timeout=\")[1]\n                if not timeout.isdigit():\n                    msg = '\\n\"timeout\" must be a non-negative integer!\\n'\n                    print(msg)\n                    invalid_run_command(msg)\n            else:\n                invalid_run_command()\n\n    data = []\n    data.append(verbose)\n    file_path = os.path.join(dir_path, \"verbose_hub_server.dat\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    from seleniumbase.utilities.selenium_grid import download_selenium_server\n\n    download_selenium_server.main(force_download=False)  # Only runs if needed\n\n    if shared_utils.is_linux() or shared_utils.is_mac():\n        if grid_hub_command == \"start\":\n            subprocess.check_call(\n                dir_path + \"/grid-hub start %s\" % timeout, shell=True\n            )\n        elif grid_hub_command == \"restart\":\n            subprocess.check_call(dir_path + \"/grid-hub stop .\", shell=True)\n            subprocess.check_call(\n                dir_path + \"/grid-hub start %s\" % timeout, shell=True\n            )\n        elif grid_hub_command == \"stop\":\n            subprocess.check_call(dir_path + \"/grid-hub stop .\", shell=True)\n        else:\n            invalid_run_command()\n    else:\n        if grid_hub_command == \"start\" or grid_hub_command == \"restart\":\n            shell_command = (\n                \"\"\"java -jar %s/selenium-server-standalone.jar -role hub \"\"\"\n                \"\"\"-timeout %s -browserTimeout 170 -port 4444\"\"\"\n                \"\" % (dir_path, timeout)\n            )\n            print(\"\\nStarting Selenium-WebDriver Grid Hub...\\n\")\n            print(shell_command)\n            print(\"\")\n            print(\"Grid Hub Console: http://127.0.0.1:4444/grid/console\")\n            print(\"\")\n            subprocess.check_call(shell_command, shell=True)\n        elif grid_hub_command == \"stop\":\n            print(\"\")\n            print(\"To stop the Grid Hub, use CTRL+C inside the server shell!\")\n            print(\"\")\n        else:\n            invalid_run_command()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/grid_node.py",
    "content": "import os\nimport subprocess\nimport sys\nfrom seleniumbase import drivers  # webdriver storage folder for SeleniumBase\nfrom seleniumbase.fixtures import shared_utils\n\nDRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))\n# Make sure that the SeleniumBase DRIVER_DIR is at the top of the System PATH\n# (Changes to the System PATH with os.environ only last during the test run)\nif not os.environ[\"PATH\"].startswith(DRIVER_DIR):\n    # Remove existing SeleniumBase DRIVER_DIR from System PATH if present\n    os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(DRIVER_DIR, \"\")\n    # If two path separators are next to each other, replace with just one\n    os.environ[\"PATH\"] = os.environ[\"PATH\"].replace(\n        os.pathsep + os.pathsep, os.pathsep\n    )\n    # Put the SeleniumBase DRIVER_DIR at the beginning of the System PATH\n    os.environ[\"PATH\"] = DRIVER_DIR + os.pathsep + os.environ[\"PATH\"]\n\n\ndef is_chromedriver_on_path():\n    paths = os.environ[\"PATH\"].split(os.pathsep)\n    for path in paths:\n        if os.path.exists(path + \"/\" + \"chromedriver\"):\n            return True\n    return False\n\n\ndef invalid_run_command():\n    exp = \"  ** grid-node **\\n\\n\"\n    exp += \"  Usage:\\n\"\n    exp += \"        seleniumbase grid-node {start|stop|restart} [OPTIONS]\\n\"\n    exp += \"  Options:\\n\"\n    exp += \"        --hub=[HUB_IP] (The Grid Hub IP Address to connect to.)\\n\"\n    exp += \"                       (Default: 127.0.0.1 if not set)\\n\"\n    exp += \"        -v, --verbose  (Increase verbosity of logging output.)\\n\"\n    exp += \"                       (Default: Quiet logging / not verbose.)\\n\"\n    exp += \"  Example:\\n\"\n    exp += \"        seleniumbase grid-node start --hub=127.0.0.1\\n\"\n    exp += \"  Output:\\n\"\n    exp += \"        Controls the Selenium Grid Node, which serves as a\\n\"\n    exp += \"        worker machine for your Selenium Grid Hub Server.\\n\"\n    exp += \"        You can start, restart, or stop the Grid Node.\\n\"\n    raise Exception(\"INVALID RUN COMMAND!\\n\\n%s\" % exp)\n\n\ndef main():\n    # Install chromedriver if not installed\n    if not is_chromedriver_on_path():\n        from seleniumbase.console_scripts import sb_install\n\n        sys_args = sys.argv  # Save a copy of current sys args\n        print(\"\\nWarning: chromedriver not found. Installing now:\")\n        sb_install.main(override=\"chromedriver\")\n        sys.argv = sys_args  # Put back the original sys args\n\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    num_args = len(sys.argv)\n    if (\n        sys.argv[0].split(\"/\")[-1] == \"seleniumbase\"\n        or (sys.argv[0].split(\"\\\\\")[-1] == \"seleniumbase\")\n        or (sys.argv[0].split(\"/\")[-1] == \"sbase\")\n        or (sys.argv[0].split(\"\\\\\")[-1] == \"sbase\")\n    ):\n        if num_args < 3:\n            invalid_run_command()\n    else:\n        invalid_run_command()\n    grid_hub_command = sys.argv[2]\n    if grid_hub_command not in [\"start\", \"stop\", \"restart\"]:\n        invalid_run_command()\n\n    server_ip = \"127.0.0.1\"\n    verbose = \"False\"\n    if num_args >= 4:\n        options = sys.argv[3:]\n        for option in options:\n            if option.startswith(\"--hub=\") and (\n                len(option.split(\"--hub=\")[1]) > 0\n            ):\n                server_ip = option.split(\"--hub=\")[1]\n            elif option == \"-v\" or option == \"--verbose\":\n                verbose = \"True\"\n            else:\n                invalid_run_command()\n\n    data = []\n    data.append(server_ip)\n    file_path = os.path.join(dir_path, \"ip_of_grid_hub.dat\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    data = []\n    data.append(verbose)\n    file_path = os.path.join(dir_path, \"verbose_node_server.dat\")\n    file = open(file_path, mode=\"w+\", encoding=\"utf-8\")\n    file.writelines(\"\\r\\n\".join(data))\n    file.close()\n\n    from seleniumbase.utilities.selenium_grid import download_selenium_server\n\n    download_selenium_server.main(force_download=False)  # Only runs if needed\n\n    if shared_utils.is_linux() or shared_utils.is_mac():\n        if grid_hub_command == \"start\":\n            subprocess.check_call(dir_path + \"/grid-node start\", shell=True)\n        elif grid_hub_command == \"restart\":\n            subprocess.check_call(dir_path + \"/grid-node stop\", shell=True)\n            subprocess.check_call(dir_path + \"/grid-node start\", shell=True)\n        elif grid_hub_command == \"stop\":\n            subprocess.check_call(dir_path + \"/grid-node stop\", shell=True)\n        else:\n            invalid_run_command()\n    else:\n        if grid_hub_command == \"start\" or grid_hub_command == \"restart\":\n            shell_command = (\n                \"\"\"java -jar %s/selenium-server-standalone.jar -role node\"\"\"\n                \"\"\" -hub http://%s:4444/grid/register -browser browser\"\"\"\n                \"\"\"Name=chrome,maxInstances=5,version=latest,\"\"\"\n                \"\"\"seleniumProtocol=WebDriver -browser browserName=firefox,\"\"\"\n                \"\"\"maxInstances=5,version=latest,seleniumProtocol=WebDriver\"\"\"\n                % (dir_path, server_ip)\n            )\n            print(\"\\nStarting Selenium-WebDriver Grid node...\\n\")\n            print(shell_command)\n            print(\"\")\n            subprocess.check_call(shell_command, shell=True)\n        elif grid_hub_command == \"stop\":\n            print(\"\")\n            print(\"To stop the Grid node, use CTRL+C inside the server shell!\")\n            print(\"\")\n        else:\n            invalid_run_command()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/register-grid-node.bat",
    "content": "java -jar selenium-server-standalone.jar -role node -hub http://127.0.0.1:4444/grid/register -browser browserName=chrome,maxInstances=5,version=latest,seleniumProtocol=WebDriver -browser browserName=firefox,maxInstances=5,version=latest,seleniumProtocol=WebDriver"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/register-grid-node.sh",
    "content": "#!/bin/bash\njava -jar selenium-server-standalone.jar -role node -hub http://127.0.0.1:4444/grid/register -browser browserName=chrome,maxInstances=5,version=latest,seleniumProtocol=WebDriver -browser browserName=firefox,maxInstances=5,version=latest,seleniumProtocol=WebDriver"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/start-grid-hub.bat",
    "content": "java -jar selenium-server-standalone.jar -role hub -timeout 230 -browserTimeout 170 -port 4444"
  },
  {
    "path": "seleniumbase/utilities/selenium_grid/start-grid-hub.sh",
    "content": "#!/bin/bash\njava -jar selenium-server-standalone.jar -role hub -timeout 230 -browserTimeout 170 -port 4444"
  },
  {
    "path": "seleniumbase/utilities/selenium_ide/ReadMe.md",
    "content": "<!-- SeleniumBase Docs -->\n\n### Converting Katalon recordings into SeleniumBase test scripts\n\n### (NOTE: **[SeleniumBase now has Recorder Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md)**, which is recommended over other record & playback tools.)\n\n--------\n\nKatalon Recorder (Selenium IDE) is a tool that allows you to record and playback actions performed inside a web browser. It's available as a [downloadable Chrome extension](https://chrome.google.com/webstore/detail/katalon-recorder-selenium/ljdobmomdgdljniojadhoplhkpialdid) and a [downloadable Firefox extension](https://addons.mozilla.org/en-US/firefox/addon/katalon-automation-record/). The Katalon Recorder comes with an option to export recordings as various WebDriver test scripts, one of which is `Python 2 (WebDriver + unittest)`. Unfortunately, these natively-exported scripts can be very messy and don't always run reliably. The purpose of this converter is to clean up and improve the scripts so that they can be used in production-level environments.\n\n#### Step 1: Make a recording with the Katalon Recorder\n\n![](https://seleniumbase.io/cdn/img/katalon_recorder_2.png \"Katalon Recorder example\")\n\n#### Step 2: Export your recording as a Python 2 Webdriver script\n\n* `{} Export` => `Python 2 (WebDriver + unittest)` => `Save As File`\n\n#### Step 3: Run `seleniumbase convert` on your exported Python file\n\n```zsh\nseleniumbase convert MY_TEST.py\n```\n\n* You should see a [MY_TEST_SB.py] file appear in the folder. (`_SB` is added to the file name so that the original file stays intact in case you still need it.) This new clean & reliable SeleniumBase test script is ready to be added into your test suite for running.\n\n--------\n\n--------\n\nThe following is an example of a Katalon Recorder exported file (**WebDriver + unittest format**).\nIt is **messy** and has **unnecessary lines of code** to do the task that was recorded:\n\n```python\n# -*- coding: utf-8 -*-\nfrom selenium import webdriver\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.common.keys import Keys\nfrom selenium.webdriver.support.ui import Select\nfrom selenium.common.exceptions import NoSuchElementException\nfrom selenium.common.exceptions import NoAlertPresentException\nimport unittest, time, re\n\nclass Swag(unittest.TestCase):\n    def setUp(self):\n        self.driver = webdriver.Firefox()\n        self.driver.implicitly_wait(30)\n        self.base_url = \"https://www.google.com/\"\n        self.verificationErrors = []\n        self.accept_next_alert = True\n\n    def test_swag(self):\n        driver = self.driver\n        driver.get(\"https://www.saucedemo.com/\")\n        driver.find_element_by_id(\"user-name\").click()\n        driver.find_element_by_id(\"user-name\").clear()\n        driver.find_element_by_id(\"user-name\").send_keys(\"standard_user\")\n        driver.find_element_by_id(\"password\").click()\n        driver.find_element_by_id(\"password\").clear()\n        driver.find_element_by_id(\"password\").send_keys(\"secret_sauce\")\n        driver.find_element_by_id(\"login-button\").click()\n\n    def is_element_present(self, how, what):\n        try: self.driver.find_element(by=how, value=what)\n        except NoSuchElementException as e: return False\n        return True\n\n    def is_alert_present(self):\n        try: self.driver.switch_to_alert()\n        except NoAlertPresentException as e: return False\n        return True\n\n    def close_alert_and_get_its_text(self):\n        try:\n            alert = self.driver.switch_to_alert()\n            alert_text = alert.text\n            if self.accept_next_alert:\n                alert.accept()\n            else:\n                alert.dismiss()\n            return alert_text\n        finally: self.accept_next_alert = True\n\n    def tearDown(self):\n        self.driver.quit()\n        self.assertEqual([], self.verificationErrors)\n\nif __name__ == \"__main__\":\n    unittest.main()\n```\n\n<div><b>This can be improved on...</b></div>\n\n<b>After running <code>seleniumbase convert [FILE.py]</code> on it, here is the new result:</b>\n\n```python\n# -*- coding: utf-8 -*-\nfrom seleniumbase import BaseCase\n\n\nclass Swag(BaseCase):\n\n    def test_swag(self):\n        self.open('https://www.saucedemo.com/')\n        self.type('#user-name', 'standard_user')\n        self.type('#password', 'secret_sauce')\n        self.click('#login-button')\n```\n\n<b>This is much cleaner than the original version.\nIt also uses the more reliable SeleniumBase methods.</b>\n"
  },
  {
    "path": "seleniumbase/utilities/selenium_ide/__init__.py",
    "content": ""
  },
  {
    "path": "seleniumbase/utilities/selenium_ide/convert_ide.py",
    "content": "\"\"\"Converts a Selenium IDE recording that was exported as a\nPython WebDriver unittest file into SeleniumBase Python file.\nAlso works with exported Katalon Recorder Selenium scripts:\n    https://chrome.google.com/webstore/detail\n        /katalon-recorder-selenium/ljdobmomdgdljniojadhoplhkpialdid\nUsage:\n        seleniumbase convert [PYTHON_WEBDRIVER_UNITTEST_FILE].py\nOutput:\n        [NEW_FILE_SB].py  (adds \"_SB\" to the original file name)\n                          (the original file is kept intact)\"\"\"\nimport re\nimport sys\nfrom seleniumbase.fixtures import js_utils\n\n\ndef main():\n    expected_arg = (\n        \"[A PYTHON_WEBDRIVER_UNITTEST_FILE exported from a \"\n        \"Katalon/Selenium-IDE recording].py\"\n    )\n    num_args = len(sys.argv)\n    if sys.argv[0].split(\"/\")[-1] == \"seleniumbase\" or (\n        sys.argv[0].split(\"\\\\\")[-1] == \"seleniumbase\"\n    ):\n        if num_args < 3 or num_args > 3:\n            raise Exception(\n                \"\\n\\n* INVALID RUN COMMAND! *  Usage:\\n\"\n                '\"seleniumbase convert %s\"\\n' % expected_arg\n            )\n    elif sys.argv[0].split(\"/\")[-1] == \"sbase\" or (\n        sys.argv[0].split(\"\\\\\")[-1] == \"sbase\"\n    ):\n        if num_args < 3 or num_args > 3:\n            raise Exception(\n                \"\\n\\n* INVALID RUN COMMAND! *  Usage:\\n\"\n                '\"sbase convert %s\"\\n' % expected_arg\n            )\n    else:\n        if num_args < 2 or num_args > 2:\n            raise Exception(\n                \"\\n\\n* INVALID RUN COMMAND! *  Usage:\\n\"\n                '\"python convert_ide.py %s\"\\n' % expected_arg\n            )\n    webdriver_python_file = sys.argv[num_args - 1]\n    if not webdriver_python_file.endswith(\".py\"):\n        raise Exception(\n            \"\\n\\n`%s` is not a Python file!\\n\\n\"\n            \"Expecting: %s\\n\" % (webdriver_python_file, expected_arg)\n        )\n\n    seleniumbase_lines = []\n    seleniumbase_lines.append(\"from seleniumbase import BaseCase\")\n    seleniumbase_lines.append(\"BaseCase.main(__name__, __file__)\")\n    seleniumbase_lines.append(\"\")  # flake8 needs the whitespace\n    seleniumbase_lines.append(\"\")\n\n    ide_base_url = \"\"\n    in_test_method = False\n    uses_keys = False\n    uses_select = False\n\n    with open(webdriver_python_file, mode=\"r\", encoding=\"utf-8\") as f:\n        all_code = f.read()\n    if \"def test_\" not in all_code:\n        raise Exception(\n            \"\\n\\n`%s` is not a valid Python unittest.TestCase \"\n            \"file!\\n\\nExpecting: %s\\n\\n\"\n            \"Did you properly export your Katalon/Selenium-IDE \"\n            \"recording as a Python WebDriver unittest file?\\n\"\n            % (webdriver_python_file, expected_arg)\n        )\n    code_lines = all_code.split(\"\\n\")\n    for line in code_lines:\n\n        # Handle utf-8 encoding if present\n        data = re.findall(r\"^\\s*# -\\*- coding: utf-8 -\\*-\\s*$\", line)\n        if data:\n            continue\n\n        # Keep SeleniumBase classes if already used in the test script\n        data = re.findall(r\"^class\\s\\S+\\(BaseCase\\):\\s*$\", line)\n        if data:\n            seleniumbase_lines.append(line)\n            continue\n\n        # Have unittest.TestCase classes inherit BaseCase instead\n        data = re.findall(r\"^class\\s\\S+\\(unittest\\.TestCase\\):\\s*$\", line)\n        if data:\n            data = data[0].replace(\"unittest.TestCase\", \"BaseCase\")\n            seleniumbase_lines.append(data)\n            continue\n\n        # Get base_url if defined\n        data = re.match(r'^\\s*self.base_url = \"(\\S+)\"\\s*$', line)\n        if data:\n            ide_base_url = data.group(1)\n            continue\n\n        # Handle method definitions\n        data = re.match(r\"^\\s*def\\s(\\S+)\\(self[,\\s\\S]*\\):\\s*$\", line)\n        if data:\n            method_name = data.group(1)\n            if method_name.startswith(\"test_\"):\n                in_test_method = True\n                seleniumbase_lines.append(data.group())\n            else:\n                in_test_method = False\n            continue\n\n        # If not in a test method, skip\n        if not in_test_method:\n            continue\n\n        # If a comment, skip\n        if line.strip().startswith(\"#\"):\n            continue\n\n        # If a blank line, skip\n        if len(line.strip()) == 0:\n            continue\n\n        # If .clear(), skip because self.type() already does this\n        if line.strip().endswith(\".clear()\"):\n            continue\n\n        # Skip edge case\n        data = re.findall(r\"^\\s*driver = self.driver\\s*$\", line)\n        if data:\n            continue\n\n        # Handle page loads\n        data = re.match(\n            r\"^(\\s*)driver\\.get\\((self\\.base_url \\+ \\\"/\\S*\\\")\\)\\s*$\", line\n        )\n        if data:\n            whitespace = data.group(1)\n            url = data.group(2)\n            url = url.replace(\"self.base_url\", '\"%s\"' % ide_base_url)\n            if '/\" + \"/' in url:\n                url = url.replace('/\" + \"/', \"/\")\n            if \"/' + '/\" in url:\n                url = url.replace(\"/' + '/\", \"/\")\n            command = \"\"\"%sself.open(%s)\"\"\" % (whitespace, url)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle more page loads\n        data = re.match(r\"^(\\s*)driver\\.get\\(\\\"(\\S*)\\\"\\)\\s*$\", line)\n        if data:\n            whitespace = data.group(1)\n            url = data.group(2)\n            command = \"\"\"%sself.open('%s')\"\"\" % (whitespace, url)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_id() + .click()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_id\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.click\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"#%s\" % data.group(2).replace(\"#\", \"\\\\#\")\n            selector = selector.replace(\"[\", \"\\\\[\").replace(\"]\", \"\\\\]\")\n            selector = selector.replace(\".\", \"\\\\.\")\n            raw = \"\"\n            if \"\\\\[\" in selector or \"\\\\]\" in selector or \"\\\\.\" in selector:\n                raw = \"r\"\n            command = \"\"\"%sself.click(%s'%s')\"\"\" % (whitespace, raw, selector)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_id() + .submit()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_id\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.submit\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"#%s\" % data.group(2).replace(\"#\", \"\\\\#\")\n            selector = selector.replace(\"[\", \"\\\\[\").replace(\"]\", \"\\\\]\")\n            selector = selector.replace(\".\", \"\\\\.\")\n            raw = \"\"\n            if \"\\\\[\" in selector or \"\\\\]\" in selector or \"\\\\.\" in selector:\n                raw = \"r\"\n            command = \"\"\"%sself.submit(%s'%s')\"\"\" % (whitespace, raw, selector)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_id() + .send_keys()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_id\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"#%s\" % data.group(2).replace(\"#\", \"\\\\#\")\n            selector = selector.replace(\"[\", \"\\\\[\").replace(\"]\", \"\\\\]\")\n            selector = selector.replace(\".\", \"\\\\.\")\n            raw = \"\"\n            if \"\\\\[\" in selector or \"\\\\]\" in selector or \"\\\\.\" in selector:\n                raw = \"r\"\n            text = data.group(3)\n            command = \"\"\"%sself.type(%s'%s', '%s')\"\"\" % (\n                whitespace,\n                raw,\n                selector,\n                text,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_id() + .send_keys(Keys.<KEY>)\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_id\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(Keys\\.([\\S]+)\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            uses_keys = True\n            whitespace = data.group(1)\n            selector = \"#%s\" % data.group(2).replace(\"#\", \"\\\\#\")\n            selector = selector.replace(\"[\", \"\\\\[\").replace(\"]\", \"\\\\]\")\n            selector = selector.replace(\".\", \"\\\\.\")\n            raw = \"\"\n            if \"\\\\[\" in selector or \"\\\\]\" in selector or \"\\\\.\" in selector:\n                raw = \"r\"\n            key = \"Keys.%s\" % data.group(3)\n            command = \"\"\"%sself.send_keys(%s'%s', %s)\"\"\" % (\n                whitespace,\n                raw,\n                selector,\n                key,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_name() + .click()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_name\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.click\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = '[name=\"%s\"]' % data.group(2)\n            command = \"\"\"%sself.click('%s')\"\"\" % (whitespace, selector)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_name() + .submit()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_name\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.submit\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = '[name=\"%s\"]' % data.group(2)\n            command = \"\"\"%sself.submit('%s')\"\"\" % (whitespace, selector)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_name() + .send_keys()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_name\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = '[name=\"%s\"]' % data.group(2)\n            text = data.group(3)\n            command = \"\"\"%sself.type('%s', '%s')\"\"\" % (\n                whitespace,\n                selector,\n                text,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_name() + .send_keys(Keys.<KEY>)\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_name\\(\\\"(\\S+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(Keys\\.([\\S]+)\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            uses_keys = True\n            whitespace = data.group(1)\n            selector = '[name=\"%s\"]' % data.group(2)\n            key = \"Keys.%s\" % data.group(3)\n            command = \"\"\"%sself.send_keys('%s', %s)\"\"\" % (\n                whitespace,\n                selector,\n                key,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_css_selector() + .click()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_css_selector\\(\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.click\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            command = \"\"\"%sself.click('%s')\"\"\" % (whitespace, selector)\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_css_selector() + .submit()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_css_selector\\(\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.submit\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            command = \"\"\"%sself.submit('%s')\"\"\" % (whitespace, selector)\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_css_selector() + .send_keys()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_css_selector\\(\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            text = data.group(3)\n            command = \"\"\"%sself.type('%s', '%s')\"\"\" % (\n                whitespace,\n                selector,\n                text,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_css_selector() + .send_keys(Keys.<KEY>)\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_css_selector\\(\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(Keys\\.([\\S]+)\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            uses_keys = True\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            key = \"Keys.%s\" % data.group(3)\n            command = \"\"\"%sself.send_keys('%s', %s)\"\"\" % (\n                whitespace,\n                selector,\n                key,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_xpath() + .send_keys()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_xpath\\(\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            text = data.group(3)\n            command = \"\"\"%sself.type(\"%s\", '%s')\"\"\" % (\n                whitespace,\n                selector,\n                text,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_xpath() + .send_keys(Keys.<KEY>)\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_xpath\\(\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.send_keys\\(Keys\\.([\\S]+)\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            uses_keys = True\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            key = \"Keys.%s\" % data.group(3)\n            command = \"\"\"%sself.send_keys(\"%s\", %s)\"\"\" % (\n                whitespace,\n                selector,\n                key,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle Select / by_css_selector() / select_by_visible_text()\n        data = re.match(\n            r\"\"\"^(\\s*)Select\\(driver\\.find_element_by_css_selector\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\)\\.select_by_visible_text\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            visible_text = \"%s\" % data.group(3)\n            command = \"\"\"%sself.select_option_by_text('%s', '%s')\"\"\" % (\n                whitespace,\n                selector,\n                visible_text,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle Select / by_id() / select_by_visible_text()\n        data = re.match(\n            r\"\"\"^(\\s*)Select\\(driver\\.find_element_by_id\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\)\\.select_by_visible_text\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"#%s\" % data.group(2).replace(\"#\", \"\\\\#\")\n            selector = selector.replace(\"[\", \"\\\\[\").replace(\"]\", \"\\\\]\")\n            selector = selector.replace(\".\", \"\\\\.\")\n            raw = \"\"\n            if \"\\\\[\" in selector or \"\\\\]\" in selector or \"\\\\.\" in selector:\n                raw = \"r\"\n            visible_text = \"%s\" % data.group(3)\n            command = \"\"\"%sself.select_option_by_text(%s'%s', '%s')\"\"\" % (\n                whitespace,\n                raw,\n                selector,\n                visible_text,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle Select / by_xpath() / select_by_visible_text()\n        data = re.match(\n            r\"\"\"^(\\s*)Select\\(driver\\.find_element_by_xpath\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\)\\.select_by_visible_text\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = \"%s\" % data.group(2)\n            visible_text = \"%s\" % data.group(3)\n            command = \"\"\"%sself.select_option_by_text(\"%s\", '%s')\"\"\" % (\n                whitespace,\n                selector,\n                visible_text,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle Select / by_name() / select_by_visible_text()\n        data = re.match(\n            r\"\"\"^(\\s*)Select\\(driver\\.find_element_by_name\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\)\\.select_by_visible_text\\(\"\"\"\n            r\"\"\"\\\"([\\S\\s]+)\\\"\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            selector = '[name=\"%s\"]' % data.group(2)\n            visible_text = \"%s\" % data.group(3)\n            command = \"\"\"%sself.select_option_by_text('%s', '%s')\"\"\" % (\n                whitespace,\n                selector,\n                visible_text,\n            )\n            if command.count('\\\\\"') == command.count('\"'):\n                command = command.replace('\\\\\"', '\"')\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_xpath() + .click()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_xpath\\(u?\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.click\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            xpath = \"%s\" % data.group(2)\n            if \".//*[normalize-space(text())\" in xpath and (\n                \"normalize-space(.)='\" in xpath\n            ):\n                x_match = re.match(\n                    r\"\"\"^[\\S\\s]+normalize-\"\"\"\n                    r\"\"\"space\\(\\.\\)=\\'([\\S\\s]+)\\'\\]\\)[\\S\\s]+\"\"\",\n                    xpath,\n                )\n                if x_match:\n                    partial_link_text = x_match.group(1)\n                    xpath = \"partial_link=%s\" % partial_link_text\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%sself.click(%s\"%s\")\"\"\" % (whitespace, uni, xpath)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_xpath() + .submit()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_xpath\\(u?\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.submit\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            xpath = \"%s\" % data.group(2)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%sself.submit(%s\"%s\")\"\"\" % (whitespace, uni, xpath)\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle .find_element_by_link_text() + .click()\n        data = re.match(\n            r\"\"\"^(\\s*)driver\\.find_element_by_link_text\\(u?\\\"([\\S\\s]+)\\\"\\)\"\"\"\n            r\"\"\"\\.click\\(\\)\\s*$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            link_text = \"\"\"%s\"\"\" % data.group(2)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%sself.click(%s\"link=%s\")\"\"\" % (\n                whitespace,\n                uni,\n                link_text,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.is_element_present(By.LINK_TEXT, *)\n        data = re.match(\n            r\"\"\"^(\\s*)([\\S\\s]*)self\\.is_element_present\\(By.LINK_TEXT, \"\"\"\n            r\"\"\"u?\\\"([\\S\\s]+)\\\"\\)([\\S\\s]*)$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            pre = data.group(2)\n            link_text = \"\"\"%s\"\"\" % data.group(3)\n            post = data.group(4)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%s%sself.is_link_text_present(%s\"%s\")%s\"\"\" % (\n                whitespace,\n                pre,\n                uni,\n                link_text,\n                post,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.is_element_present(By.NAME, *)\n        data = re.match(\n            r\"\"\"^(\\s*)([\\S\\s]*)self\\.is_element_present\\(By.NAME, \"\"\"\n            r\"\"\"u?\\\"([\\S\\s]+)\\\"\\)([\\S\\s]*)$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            pre = data.group(2)\n            name = \"\"\"%s\"\"\" % data.group(3)\n            post = data.group(4)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%s%sself.is_element_present('[name=\"%s\"]')%s\"\"\" % (\n                whitespace,\n                pre,\n                name,\n                post,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.is_element_present(By.ID, *)\n        data = re.match(\n            r\"\"\"^(\\s*)([\\S\\s]*)self\\.is_element_present\\(By.ID, \"\"\"\n            r\"\"\"u?\\\"([\\S\\s]+)\\\"\\)([\\S\\s]*)$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            pre = data.group(2)\n            the_id = \"\"\"%s\"\"\" % data.group(3)\n            post = data.group(4)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%s%sself.is_element_present(\"#%s\")%s\"\"\" % (\n                whitespace,\n                pre,\n                the_id,\n                post,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.is_element_present(By.CLASS, *)\n        data = re.match(\n            r\"\"\"^(\\s*)([\\S\\s]*)self\\.is_element_present\\(By.CLASS, \"\"\"\n            r\"\"\"u?\\\"([\\S\\s]+)\\\"\\)([\\S\\s]*)$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            pre = data.group(2)\n            the_class = \"\"\"%s\"\"\" % data.group(3)\n            post = data.group(4)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%s%sself.is_element_present(\".%s\")%s\"\"\" % (\n                whitespace,\n                pre,\n                the_class,\n                post,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.is_element_present(By.CSS_SELECTOR, *)\n        data = re.match(\n            r\"\"\"^(\\s*)([\\S\\s]*)self\\.is_element_present\\(By.CSS_SELECTOR, \"\"\"\n            r\"\"\"u?\\\"([\\S\\s]+)\\\"\\)([\\S\\s]*)$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            pre = data.group(2)\n            selector = \"\"\"%s\"\"\" % data.group(3)\n            post = data.group(4)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%s%sself.is_element_present(\"%s\")%s\"\"\" % (\n                whitespace,\n                pre,\n                selector,\n                post,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Handle self.is_element_present(By.XPATH, *)\n        data = re.match(\n            r\"\"\"^(\\s*)([\\S\\s]*)self\\.is_element_present\\(By.XPATH, \"\"\"\n            r\"\"\"u?\\\"([\\S\\s]+)\\\"\\)([\\S\\s]*)$\"\"\",\n            line,\n        )\n        if data:\n            whitespace = data.group(1)\n            pre = data.group(2)\n            xpath = \"\"\"%s\"\"\" % data.group(3)\n            post = data.group(4)\n            uni = \"\"\n            if '(u\"' in line:\n                uni = \"u\"\n            command = \"\"\"%s%sself.is_element_present(\"%s\")%s\"\"\" % (\n                whitespace,\n                pre,\n                xpath,\n                post,\n            )\n            seleniumbase_lines.append(command)\n            continue\n\n        # Replace \"self.base_url\" with actual url if not already done\n        if \"self.base_url\" in line:\n            line = line.replace(\"self.base_url\", '\"%s\"' % ide_base_url)\n\n        # Convert \"driver.\" to \"self.driver.\" if not already done\n        if \"driver.\" in line and \"self.driver\" not in line:\n            line = line.replace(\"driver.\", \"self.driver.\")\n\n        # Add all other lines to final script without making changes\n        seleniumbase_lines.append(line)\n\n    # Chunk processing of inefficient waiting from Selenium IDE\n    in_inefficient_wait = False\n    whitespace = \"\"\n    lines = seleniumbase_lines\n    seleniumbase_lines = []\n    for line in lines:\n        data = re.match(r\"^(\\s*)for i in range\\(60\\):\\s*$\", line)\n        if data:\n            in_inefficient_wait = True\n            whitespace = data.group(1)\n            continue\n\n        data = re.match(r'^(\\s*)else: self.fail\\(\"time out\"\\)\\s*$', line)\n        if data:\n            in_inefficient_wait = False\n            continue\n\n        if in_inefficient_wait:\n            data = re.match(\n                r\"\"\"^\\s*if self.is_element_present\\(\"([\\S\\s]+)\"\\)\"\"\"\n                r\"\"\": break\\s*$\"\"\",\n                line,\n            )\n            if data:\n                selector = data.group(1)\n                command = '%sself.wait_for_element(\"%s\")' % (\n                    whitespace,\n                    selector,\n                )\n                seleniumbase_lines.append(command)\n                continue\n\n            data = re.match(\n                r\"\"\"^\\s*if self.is_element_present\\('([\\S\\s]+)'\\)\"\"\"\n                r\"\"\": break\\s*$\"\"\",\n                line,\n            )\n            if data:\n                selector = data.group(1)\n                command = \"%sself.wait_for_element('%s')\" % (\n                    whitespace,\n                    selector,\n                )\n                seleniumbase_lines.append(command)\n                continue\n\n            data = re.match(\n                r\"\"\"^\\s*if self.is_link_text_present\"\"\"\n                r\"\"\"\\(\"([\\S\\s]+)\"\\): break\\s*$\"\"\",\n                line,\n            )\n            if data:\n                uni = \"\"\n                if '(u\"' in line:\n                    uni = \"u\"\n                link_text = data.group(1)\n                command = \"\"\"%sself.wait_for_link_text(%s\"%s\")\"\"\" % (\n                    whitespace,\n                    uni,\n                    link_text,\n                )\n                seleniumbase_lines.append(command)\n                continue\n        else:\n            seleniumbase_lines.append(line)\n            continue\n\n    # Is there a Select() still present?\n    lines = seleniumbase_lines\n    for line_num in range(len(lines)):\n        if \"Select(self.driver\" in lines[line_num]:\n            uses_select = True\n\n    # Remove duplicate functionality (wait_for_element)\n    lines = seleniumbase_lines\n    seleniumbase_lines = []\n    num_lines = len(lines)\n    for line_num in range(len(lines)):\n        data = re.match(\n            r\"\"\"^\\s*self.wait_for_element\"\"\"\n            r\"\"\"\\(([\"|'])([\\S\\s]+)([\"|'])\\)\"\"\"\n            r\"\"\"\\s*$\"\"\",\n            lines[line_num],\n        )\n        if data:\n            # quote_type = data.group(1)\n            selector = data.group(2)\n            selector = re.escape(selector)\n            selector = js_utils.escape_quotes_if_needed(selector)\n            if int(line_num) < num_lines - 1:\n                regex_string = (\n                    r\"\"\"^\\s*self.click\\([\"|']\"\"\"\n                    \"\" + selector + r\"\"\"[\"|']\\)\\s*$\"\"\"\n                )\n                data2 = re.match(regex_string, lines[line_num + 1])\n                if data2:\n                    continue\n                regex_string = (\n                    r\"\"\"^\\s*self.type\\([\"|']\"\"\"\n                    \"\" + selector + \"\"\n                    \"\" + r\"\"\"[\"|'], [\\S\\s]+\\)\\s*$\"\"\"\n                )\n                data2 = re.match(regex_string, lines[line_num + 1])\n                if data2:\n                    continue\n        seleniumbase_lines.append(lines[line_num])\n\n    # Remove duplicate functionality: \"click(SEL)\" before \"type(SEL, TEXT)\"\n    lines = seleniumbase_lines\n    seleniumbase_lines = []\n    num_lines = len(lines)\n    for line_num in range(len(lines)):\n        data = re.match(\n            r\"\"\"^\\s*self.click\"\"\"\n            r\"\"\"\\(([\"|'])([\\S\\s]+)([\"|'])\\)\"\"\"\n            r\"\"\"\\s*$\"\"\",\n            lines[line_num],\n        )\n        if data:\n            # quote_type = data.group(1)\n            selector = data.group(2)\n            selector = re.escape(selector)\n            selector = js_utils.escape_quotes_if_needed(selector)\n            if int(line_num) < num_lines - 1:\n                regex_string = (\n                    r\"\"\"^\\s*self.type\\([\"|']\"\"\"\n                    \"\" + selector + \"\"\n                    \"\" + r\"\"\"[\"|'], [\\S\\s]+\\)\\s*$\"\"\"\n                )\n                data2 = re.match(regex_string, lines[line_num + 1])\n                if data2:\n                    continue\n        seleniumbase_lines.append(lines[line_num])\n\n    # Remove duplicate functionality (wait_for_link_text)\n    lines = seleniumbase_lines\n    seleniumbase_lines = []\n    num_lines = len(lines)\n    for line_num in range(len(lines)):\n        data = re.match(\n            r\"\"\"^\\s*self.wait_for_link_text\"\"\"\n            r\"\"\"\\(([\"|'])([\\S\\s]+)([\"|'])\\)\"\"\"\n            r\"\"\"\\s*$\"\"\",\n            lines[line_num],\n        )\n        if data:\n            # quote_type = data.group(1)\n            link_text = data.group(2)\n            link_text = re.escape(link_text)\n            link_text = js_utils.escape_quotes_if_needed(link_text)\n            if int(line_num) < num_lines - 2:\n                regex_string = (\n                    r\"\"\"^\\s*self.click\\([\"|']link=\"\"\"\n                    \"\" + link_text + r\"\"\"[\"|']\\)\\s*$\"\"\"\n                )\n                data2 = re.match(regex_string, lines[line_num + 1])\n                if data2:\n                    continue\n        seleniumbase_lines.append(lines[line_num])\n\n    seleniumbase_code = \"\"\n    if uses_keys:\n        seleniumbase_code += (\n            \"from selenium.webdriver.common.keys import Keys\\n\"\n        )\n    if uses_select:\n        seleniumbase_code += (\n            \"from selenium.webdriver.support.ui import Select\\n\"\n        )\n    for line in seleniumbase_lines:\n        seleniumbase_code += line\n        seleniumbase_code += \"\\n\"\n    # print(seleniumbase_code)  # (For debugging)\n\n    # Create SeleniumBase test file\n    base_file_name = webdriver_python_file.split(\".py\")[0]\n    converted_file_name = base_file_name + \"_SB.py\"\n    out_file = open(converted_file_name, mode=\"w+\", encoding=\"utf-8\")\n    out_file.writelines(seleniumbase_code)\n    out_file.close()\n    print(\n        \"\\n>>> [%s] was created from [%s]\\n\"\n        % (converted_file_name, webdriver_python_file)\n    )\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "setup.cfg",
    "content": "[flake8]\n# W503 (line break before binary operator) can be ignored.\nexclude=recordings,temp\nignore=W503\n\n[nosetests]\n# nocapture=1 (Display print statements from output)\n#             (Undo this by using: \"--nologcapture\")\n# logging-level=INFO (Shorter logs than using DEBUG)\nnocapture=1\nlogging-level=INFO\n\n[behave]\nshow_skipped=false\nshow_timings=false\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"Setup steps for installing SeleniumBase dependencies and plugins.\n(Uses selenium 4.x and is compatible with Python 3.9+)\"\"\"\nfrom setuptools import setup, find_packages  # noqa: F401\nimport os\nimport sys\n\n\nthis_dir = os.path.abspath(os.path.dirname(__file__))\nlong_description = None\ntotal_description = None\ntry:\n    with open(os.path.join(this_dir, \"README.md\"), mode=\"rb\") as f:\n        total_description = f.read().decode(\"utf-8\")\n    description_lines = total_description.split(\"\\n\")\n    long_description_lines = []\n    for line in description_lines:\n        if not line.startswith(\"<meta \") and not line.startswith(\"<link \"):\n            long_description_lines.append(line)\n    long_description = \"\\n\".join(long_description_lines)\nexcept IOError:\n    long_description = \"A complete library for building end-to-end tests.\"\nabout = {}\n# Get the package version from the seleniumbase/__version__.py file\nwith open(\n    os.path.join(this_dir, \"seleniumbase\", \"__version__.py\"), mode=\"rb\"\n) as f:\n    exec(f.read().decode(\"utf-8\"), about)\n\nif sys.argv[-1] == \"publish\":\n    reply = None\n    input_method = input\n    confirm_text = \">>> Confirm release PUBLISH to PyPI? (yes/no): \"\n    reply = str(input_method(confirm_text)).lower().strip()\n    if reply == \"yes\":\n        if sys.version_info < (3, 10):\n            current_ver = \".\".join(str(ver) for ver in sys.version_info[:3])\n            print(\"\\nERROR! Publishing to PyPI requires Python>=3.10\")\n            print(\"You are currently using Python %s\\n\" % current_ver)\n            sys.exit()\n        print(\"\\n*** Checking code health with flake8:\\n\")\n        os.system(\"python -m pip install 'flake8==7.3.0'\")\n        flake8_status = os.system(\"flake8 --exclude=recordings,temp\")\n        if flake8_status != 0:\n            print(\"\\nERROR! Fix flake8 issues before publishing to PyPI!\\n\")\n            sys.exit()\n        else:\n            print(\"*** No flake8 issues detected. Continuing...\")\n        print(\"\\n*** Removing existing distribution packages: ***\\n\")\n        os.system(\"rm -f dist/*.egg; rm -f dist/*.tar.gz; rm -f dist/*.whl\")\n        os.system(\"rm -rf build/bdist.*; rm -rf build/lib\")\n        print(\"\\n*** Installing build: *** (Required for PyPI uploads)\\n\")\n        os.system(\"python -m pip install --upgrade 'build'\")\n        print(\"\\n*** Installing pkginfo: *** (Required for PyPI uploads)\\n\")\n        os.system(\"python -m pip install 'pkginfo'\")\n        print(\"\\n*** Installing readme-renderer: *** (For PyPI uploads)\\n\")\n        os.system(\"python -m pip install --upgrade 'readme-renderer'\")\n        print(\"\\n*** Installing jaraco.classes: *** (For PyPI uploads)\\n\")\n        os.system(\"python -m pip install --upgrade 'jaraco.classes'\")\n        print(\"\\n*** Installing more-itertools: *** (For PyPI uploads)\\n\")\n        os.system(\"python -m pip install --upgrade 'more-itertools'\")\n        print(\"\\n*** Installing keyring, requests-toolbelt: *** (For PyPI)\\n\")\n        os.system(\"python -m pip install --upgrade keyring requests-toolbelt\")\n        print(\"\\n*** Installing twine: *** (Required for PyPI uploads)\\n\")\n        os.system(\"python -m pip install --upgrade 'twine'\")\n        print(\"\\n*** Rebuilding distribution packages: ***\\n\")\n        os.system(\"python -m build\")  # Create new tar/wheel\n        print(\"\\n*** Publishing The Release to PyPI: ***\\n\")\n        os.system(\"python -m twine upload dist/*\")  # Requires ~/.pypirc Keys\n        print(\"\\n*** The Release was PUBLISHED SUCCESSFULLY to PyPI! :) ***\\n\")\n    else:\n        print(\"\\n>>> The Release was NOT PUBLISHED to PyPI! <<<\\n\")\n    sys.exit()\n\nsetup(\n    name=\"seleniumbase\",\n    version=about[\"__version__\"],\n    description=\"A complete web automation framework for end-to-end testing.\",\n    long_description=long_description,\n    long_description_content_type=\"text/markdown\",\n    url=\"https://github.com/seleniumbase/SeleniumBase\",\n    project_urls={\n        \"Homepage\": \"https://github.com/seleniumbase/SeleniumBase\",\n        \"Changelog\": \"https://github.com/seleniumbase/SeleniumBase/releases\",\n        \"Download\": \"https://pypi.org/project/seleniumbase/#files\",\n        \"Blog\": \"https://seleniumbase.com/\",\n        \"Discord\": \"https://discord.gg/EdhQTn3EyE\",\n        \"PyPI\": \"https://pypi.org/project/seleniumbase/\",\n        \"Source\": \"https://github.com/seleniumbase/SeleniumBase\",\n        \"Repository\": \"https://github.com/seleniumbase/SeleniumBase\",\n        \"Documentation\": \"https://seleniumbase.io/\",\n    },\n    platforms=[\"Windows\", \"Linux\", \"Mac OS-X\"],\n    author=\"Michael Mintz\",\n    author_email=\"mdmintz@gmail.com\",\n    maintainer=\"Michael Mintz\",\n    license=\"MIT\",\n    keywords=[\n        \"pytest\",\n        \"selenium\",\n        \"framework\",\n        \"automation\",\n        \"browser\",\n        \"testing\",\n        \"webdriver\",\n        \"seleniumbase\",\n        \"sbase\",\n        \"crawling\",\n        \"scraping\",\n    ],\n    classifiers=[\n        \"Development Status :: 5 - Production/Stable\",\n        \"Environment :: Console\",\n        \"Environment :: MacOS X\",\n        \"Environment :: Win32 (MS Windows)\",\n        \"Environment :: Web Environment\",\n        \"Framework :: Pytest\",\n        \"Intended Audience :: Developers\",\n        \"Intended Audience :: Information Technology\",\n        \"Operating System :: MacOS :: MacOS X\",\n        \"Operating System :: Microsoft :: Windows\",\n        \"Operating System :: POSIX :: Linux\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.9\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n        \"Programming Language :: Python :: 3.12\",\n        \"Programming Language :: Python :: 3.13\",\n        \"Programming Language :: Python :: 3.14\",\n        \"Topic :: Internet\",\n        \"Topic :: Internet :: WWW/HTTP :: Browsers\",\n        \"Topic :: Scientific/Engineering\",\n        \"Topic :: Scientific/Engineering :: Artificial Intelligence\",\n        \"Topic :: Scientific/Engineering :: Image Processing\",\n        \"Topic :: Scientific/Engineering :: Visualization\",\n        \"Topic :: Software Development\",\n        \"Topic :: Software Development :: Quality Assurance\",\n        \"Topic :: Software Development :: Code Generators\",\n        \"Topic :: Software Development :: Libraries\",\n        \"Topic :: Software Development :: Libraries :: Application Frameworks\",\n        \"Topic :: Software Development :: Libraries :: Python Modules\",\n        \"Topic :: Software Development :: Testing\",\n        \"Topic :: Software Development :: Testing :: Acceptance\",\n        \"Topic :: Software Development :: Testing :: Traffic Generation\",\n        \"Topic :: Utilities\",\n    ],\n    python_requires=\">=3.9\",\n    install_requires=[\n        'pip>=26.0.1',\n        'packaging>=26.0',\n        'setuptools~=70.2;python_version<\"3.10\"',  # Newer ones had issues\n        'setuptools>=82.0.1;python_version>=\"3.10\"',\n        'wheel>=0.46.3',\n        'attrs>=25.4.0',\n        'certifi>=2026.2.25',\n        'exceptiongroup>=1.3.1',\n        'websockets~=15.0.1;python_version<\"3.10\"',\n        'websockets>=16.0;python_version>=\"3.10\"',\n        'filelock~=3.19.1;python_version<\"3.10\"',\n        'filelock>=3.25.2;python_version>=\"3.10\"',\n        'fasteners>=0.20',\n        'mycdp>=1.3.6',\n        'pynose>=1.5.5',\n        'platformdirs~=4.4.0;python_version<\"3.10\"',\n        'platformdirs>=4.9.4;python_version>=\"3.10\"',\n        'typing-extensions>=4.15.0',\n        'sbvirtualdisplay>=1.4.0',\n        'MarkupSafe>=3.0.3',\n        \"Jinja2>=3.1.6\",\n        \"six>=1.17.0\",\n        'parse>=1.21.1',\n        'parse-type>=0.6.6',\n        'colorama>=0.4.6',\n        'pyyaml>=6.0.3',\n        'pygments>=2.19.2',\n        'pyreadline3>=3.5.4;platform_system==\"Windows\"',\n        'tabcompleter>=1.4.0',\n        'pdbp>=1.8.2',\n        'idna>=3.11',\n        'charset-normalizer>=3.4.6,<4',\n        'urllib3>=1.26.20,<2;python_version<\"3.10\"',\n        'urllib3>=1.26.20,<3;python_version>=\"3.10\"',\n        'requests~=2.32.5',\n        'sniffio==1.3.1',\n        'h11==0.16.0',\n        'outcome==1.3.0.post0',\n        'trio>=0.31.0,<1;python_version<\"3.10\"',\n        'trio>=0.33.0,<1;python_version>=\"3.10\"',\n        'trio-websocket~=0.12.2',\n        'wsproto==1.2.0;python_version<\"3.10\"',\n        'wsproto~=1.3.2;python_version>=\"3.10\"',\n        'websocket-client~=1.9.0',\n        'selenium==4.32.0;python_version<\"3.10\"',\n        'selenium==4.41.0;python_version>=\"3.10\"',\n        'cssselect==1.3.0;python_version<\"3.10\"',\n        'cssselect>=1.4.0,<2;python_version>=\"3.10\"',\n        'nest-asyncio==1.6.0',\n        'sortedcontainers==2.4.0',\n        'execnet==2.1.1;python_version<\"3.10\"',\n        'execnet==2.1.2;python_version>=\"3.10\"',\n        'iniconfig==2.1.0;python_version<\"3.10\"',\n        'iniconfig==2.3.0;python_version>=\"3.10\"',\n        'pluggy==1.6.0',\n        'pytest==8.4.2;python_version<\"3.11\"',\n        'pytest==9.0.2;python_version>=\"3.11\"',\n        'pytest-html==4.0.2',  # Newer ones had issues\n        'pytest-metadata==3.1.1',\n        'pytest-ordering==0.6',\n        'pytest-rerunfailures==16.0.1;python_version<\"3.10\"',\n        'pytest-rerunfailures==16.1;python_version>=\"3.10\"',\n        'pytest-xdist==3.8.0',\n        'parameterized==0.9.0',\n        'behave==1.2.6',  # Newer ones had issues\n        'soupsieve~=2.8.3',\n        'beautifulsoup4~=4.14.3',\n        'pyotp==2.9.0',\n        'python-xlib==0.33;platform_system==\"Linux\"',\n        'PyAutoGUI>=0.9.54;platform_system==\"Linux\"',\n        'markdown-it-py==3.0.0;python_version<\"3.10\"',\n        'markdown-it-py==4.0.0;python_version>=\"3.10\"',\n        'mdurl==0.1.2',\n        'rich>=14.3.3,<15',\n    ],\n    extras_require={\n        # pip install -e .[allure]\n        # Usage: pytest --alluredir=allure_results\n        # Serve: allure serve allure_results\n        \"allure\": [\n            'allure-pytest>=2.13.5',\n            'allure-python-commons>=2.13.5',\n            'allure-behave>=2.13.5',\n        ],\n        # pip install -e .[coverage]\n        # Usage: coverage run -m pytest; coverage html; coverage report\n        \"coverage\": [\n            'coverage>=7.10.7;python_version<\"3.10\"',\n            'coverage>=7.13.5;python_version>=\"3.10\"',\n            'pytest-cov>=7.0.0',\n        ],\n        # pip install -e .[flake8]\n        # Usage: flake8\n        \"flake8\": [\n            'flake8==7.3.0',\n            \"mccabe==0.7.0\",\n            'pyflakes==3.4.0',\n            'pycodestyle==2.14.0',\n        ],\n        # pip install -e .[ipdb]\n        # (Not needed for debugging anymore. SeleniumBase now includes \"pdbp\".)\n        \"ipdb\": [\n            \"ipdb==0.13.13\",\n            'ipython==7.34.0',\n        ],\n        # pip install -e .[mss]\n        # (An optional library for tile_windows() in CDP Mode.)\n        \"mss\": [\n            'mss==10.1.0',\n        ],\n        # pip install -e .[pdfminer]\n        # (An optional library for parsing PDF files.)\n        \"pdfminer\": [\n            'pdfminer.six==20251107;python_version<\"3.10\"',\n            'pdfminer.six==20260107;python_version>=\"3.10\"',\n            'cryptography==46.0.5',\n            'cffi==2.0.0',\n            'pycparser==2.23;python_version<\"3.10\"',\n            'pycparser==3.0;python_version>=\"3.10\"',\n        ],\n        # pip install -e .[pillow]\n        # (An optional library for image-processing.)\n        \"pillow\": [\n            'Pillow>=11.3.0;python_version<\"3.10\"',\n            'Pillow>=12.1.1;python_version>=\"3.10\"',\n        ],\n        # pip install -e .[pip-system-certs]\n        # (If you see [SSL: CERTIFICATE_VERIFY_FAILED], then get this.)\n        # (May help those with corporate self-signed certs on Windows.)\n        \"pip-system-certs\": [\n            'pip-system-certs==4.0;platform_system==\"Windows\"',\n        ],\n        # pip install -e .[proxy]\n        # Usage: proxy\n        # (That starts a proxy server on \"127.0.0.1:8899\".)\n        \"proxy\": [\n            \"proxy.py==2.4.3\",  # 2.4.4 did not have \"Listening on ...\"\n        ],\n        # pip install -e .[playwright]\n        # (For the Playwright integration.)\n        \"playwright\": [\n            \"playwright>=1.58.0\",\n        ],\n        # pip install -e .[psutil]\n        \"psutil\": [\n            \"psutil>=7.2.2\",\n        ],\n        # pip install -e .[pyautogui]\n        # (Already a required dependency on Linux now.)\n        \"pyautogui\": [\n            'PyAutoGUI>=0.9.54;platform_system!=\"Linux\"',\n        ],\n        # pip install -e .[selenium-stealth]\n        \"selenium-stealth\": [\n            'selenium-stealth==1.0.6',\n        ],\n        # pip install -e .[selenium-wire]\n        \"selenium-wire\": [\n            'selenium-wire==5.1.0',\n            'pyOpenSSL>=24.2.1',\n            'pyparsing>=3.1.4',\n            'Brotli==1.1.0',\n            'blinker==1.7.0',  # Newer ones had issues\n            'h2==4.1.0',\n            'hpack==4.0.0',\n            'hyperframe==6.0.1',\n            'kaitaistruct==0.10',\n            'pyasn1==0.6.1',\n            'zstandard>=0.23.0',\n        ],\n    },\n    packages=[\n        \"seleniumbase\",\n        \"sbase\",\n        \"seleniumbase.behave\",\n        \"seleniumbase.common\",\n        \"seleniumbase.config\",\n        \"seleniumbase.console_scripts\",\n        \"seleniumbase.core\",\n        \"seleniumbase.drivers\",\n        \"seleniumbase.drivers.cft_drivers\",\n        \"seleniumbase.drivers.chs_drivers\",\n        \"seleniumbase.drivers.opera_drivers\",\n        \"seleniumbase.drivers.brave_drivers\",\n        \"seleniumbase.drivers.comet_drivers\",\n        \"seleniumbase.drivers.atlas_drivers\",\n        \"seleniumbase.drivers.chromium_drivers\",\n        \"seleniumbase.extensions\",\n        \"seleniumbase.fixtures\",\n        \"seleniumbase.js_code\",\n        \"seleniumbase.masterqa\",\n        \"seleniumbase.plugins\",\n        \"seleniumbase.resources\",\n        \"seleniumbase.translate\",\n        \"seleniumbase.undetected\",\n        \"seleniumbase.undetected.cdp_driver\",\n        \"seleniumbase.utilities\",\n        \"seleniumbase.utilities.selenium_grid\",\n        \"seleniumbase.utilities.selenium_ide\",\n    ],\n    include_package_data=True,\n    entry_points={\n        \"console_scripts\": [\n            \"seleniumbase = seleniumbase.console_scripts.run:main\",\n            \"sbase = seleniumbase.console_scripts.run:main\",  # Simplified name\n        ],\n        \"nose.plugins\": [\n            \"base_plugin = seleniumbase.plugins.base_plugin:Base\",\n            \"selenium = seleniumbase.plugins.selenium_plugin:SeleniumBrowser\",\n            \"page_source = seleniumbase.plugins.page_source:PageSource\",\n            \"screen_shots = seleniumbase.plugins.screen_shots:ScreenShots\",\n            \"test_info = seleniumbase.plugins.basic_test_info:BasicTestInfo\",\n            (\n                \"db_reporting = \"\n                \"seleniumbase.plugins.db_reporting_plugin:DBReporting\"\n            ),\n            \"s3_logging = seleniumbase.plugins.s3_logging_plugin:S3Logging\",\n        ],\n        \"pytest11\": [\"seleniumbase = seleniumbase.plugins.pytest_plugin\"],\n    },\n)\n\n# print(os.system(\"cat seleniumbase.egg-info/PKG-INFO\"))\nprint(\"\\n*** SeleniumBase Installation Complete! ***\\n\")\n"
  },
  {
    "path": "virtualenv_install.sh",
    "content": "# Performs necessary setup steps to allow the use of\n# virtualenv commands such as \"mkvirtualenv [ENV_NAME]\"\n# for creating and using Python virtual environments.\n#\n# Run by using the following command: \"source virtualenv_install.sh\"\n\n[[ $0 != \"$BASH_SOURCE\" ]] && sourced=1 || sourced=0\nif [ $sourced = 1 ]\nthen\n  python3 -m pip install --upgrade pip setuptools wheel\n  python3 -m pip install --upgrade virtualenvwrapper --upgrade-strategy=eager\n  export WORKON_HOME=$HOME/.virtualenvs\n  source `which virtualenvwrapper.sh`\n  echo \"\"\n  echo \"\"\n  echo \"*** You may now use virtualenv commands in your command shell. ***\"\n  echo \"\"\n  echo \"virtualenv commands:\"\n  echo '  *  \"mkvirtualenv [ENV_NAME]\"  -  Create a Python virtual environment'\n  echo '  *  \"deactivate\"               -  Exit the current virtual environment'\n  echo '  *  \"workon [ENV_NAME]\"        -  Enter an existing virtual environment'\n  echo '  *  \"lsvirtualenv\" OR \"workon\" -  List all virtual environments'\n  echo '  *  \"rmvirtualenv [ENV_NAME]\"  -  Delete a virtual environment'\n  echo \"\"\n  echo \"Example:\"\n  echo \"      mkvirtualenv seleniumbase \"\n  echo \"      mkvirtualenv seleniumbase --python=[PATH_TO_PYTHON]\"\n  echo \"\"\nelse\n  echo \"\"\n  echo \"--------------------\"\n  echo '*** - WARNING! - ***'\n  echo \"--------------------\"\n  echo \"\"\n  echo 'You need to \"source\" this file for virtualenv commands to work!'\n  echo \"\"\n  echo '*** USE:  source virtualenv_install.sh'\n  echo \"          ----------------------------\"\n  echo \"\"\nfi\n"
  },
  {
    "path": "win_install.bat",
    "content": "@ECHO OFF\npip install -e . --use-pep517 --config-settings=\"editable_mode=compat\"\n"
  },
  {
    "path": "win_virtualenv.bat",
    "content": "@ECHO OFF\n:: Performs necessary setup steps to allow the use of\n:: virtualenv commands such as \"mkvirtualenv [ENV_NAME]\"\n:: for creating and using Python virtual environments.\n\npy -m pip install --upgrade pip --user\npy -m pip install --upgrade wheel --user\npy -m pip install virtualenvwrapper-win --force-reinstall --user\necho:\necho:\necho: *** You may now use virtualenv commands in your command shell. ***\necho:\necho: virtualenv commands:\necho:   *  \"mkvirtualenv [ENV_NAME]\"  -  Create a Python virtual environment\necho:   *  \"deactivate\"               -  Exit the current virtual environment\necho:   *  \"workon [ENV_NAME]\"        -  Enter an existing virtual environment\necho:   *  \"lsvirtualenv\" OR \"workon\" -  List all virtual environments\necho:   *  \"rmvirtualenv [ENV_NAME]\"  -  Delete a virtual environment\necho:\necho: Example:\necho:       mkvirtualenv seleniumbase\necho:       mkvirtualenv seleniumbase --python=[PATH_TO_PYTHON]\necho:\n"
  }
]