├── .coveragerc ├── .github ├── FUNDING.yml ├── set_win_reg_keys.ps1 └── workflows │ ├── deploy.yml │ ├── test.yml │ └── test_xdist.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Pipfile ├── README.md ├── release-process.rst ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── test_brave_driver.py ├── test_chrome_driver.py ├── test_chromium_driver.py ├── test_custom_http_client.py ├── test_custom_logger.py ├── test_downloader.py ├── test_edge_driver.py ├── test_firefox_manager.py ├── test_ie_driver.py ├── test_opera_manager.py ├── test_silent_global_logs.py └── test_utils.py ├── tests_negative ├── __init__.py └── test_browsers_not_installed.py ├── tests_xdist ├── __init__.py ├── conftest.py ├── test_cuncurent_1.py ├── test_cuncurent_2.py └── test_cuncurent_3.py └── webdriver_manager ├── __init__.py ├── chrome.py ├── core ├── __init__.py ├── archive.py ├── config.py ├── constants.py ├── download_manager.py ├── driver.py ├── driver_cache.py ├── file_manager.py ├── http.py ├── logger.py ├── manager.py ├── os_manager.py └── utils.py ├── drivers ├── __init__.py ├── chrome.py ├── edge.py ├── firefox.py ├── ie.py └── opera.py ├── firefox.py ├── microsoft.py ├── opera.py └── py.typed /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | 4 | [report] 5 | exclude_lines = 6 | pass 7 | raise NoSuchElementException 8 | raise NotImplementedError 9 | if 0: 10 | def __str__ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: automation_remarks 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /.github/set_win_reg_keys.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Sadly registry keys for chromium based browsers aren't set properly on the worker (it does on PC). 3 | # This is why it needs to be set manually. 4 | function set_registry_verion_key { 5 | param([string]$version, [string]$registryPath) 6 | if(!(Test-Path $registryPath)) 7 | { 8 | New-Item -Path $registryPath -Force | Out-Null 9 | New-ItemProperty -Path $registryPath -Name version -Value $version -PropertyType String -Force | Out-Null} 10 | 11 | else { 12 | New-ItemProperty -Path $registryPath -Name version -Value $version -PropertyType String -Force | Out-Null} 13 | 14 | Get-ItemProperty -Path $registryPath -Name version 15 | } 16 | 17 | "#####################" 18 | "Setting Registry Keys" 19 | "#####################" 20 | 21 | $edge_version = (Get-Item "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe").VersionInfo.FileVersion 22 | $edge_registryPath = "HKCU:\SOFTWARE\Microsoft\Edge\BLBeacon" 23 | set_registry_verion_key $edge_version $edge_registryPath 24 | 25 | $cromium_version = (choco info chromium -l).Split("\n")[1] 26 | $cromium_registryPath = "HKCU:\Software\Chromium\BLBeacon" 27 | set_registry_verion_key $cromium_version $cromium_registryPath 28 | 29 | $googlechrome_version = (Get-Item "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe").VersionInfo.FileVersion 30 | $googlechrome_registryPath = "HKCU:\Software\Google\Chrome\BLBeacon" 31 | set_registry_verion_key $googlechrome_version $googlechrome_registryPath 32 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy to pypi" 2 | on: 3 | release: 4 | types: [ published ] 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up Python 3.7 12 | uses: actions/setup-python@v4 13 | with: 14 | python-version: 3.7 15 | - name: Install Python dependencies 16 | run: | 17 | python -m pip install -U pip 18 | pip install pipenv 19 | pipenv install --dev 20 | - name: Build dist 21 | run: | 22 | pipenv run python setup.py sdist bdist_wheel 23 | 24 | - name: Publish package 25 | uses: pypa/gh-action-pypi-publish@release/v1 26 | with: 27 | user: __token__ 28 | password: ${{ secrets.pypi_password }} 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Tests" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths-ignore: 7 | - CHANGELOG.md 8 | - README.md 9 | - LICENSE 10 | - LICENSE.txt 11 | - release-process.rst 12 | - .gitignore 13 | - .coveragerc 14 | - .github/workflows/deploy.yml 15 | - .github/workflows/codeql-analysis.yml 16 | - .github/workflows/FUNDING.yml 17 | - setup.cfg 18 | - setup.py 19 | - webdriver_manager/__init__.py 20 | 21 | pull_request: 22 | branches: 23 | - master 24 | paths-ignore: 25 | - CHANGELOG.md 26 | - README.md 27 | - LICENSE 28 | - LICENSE.txt 29 | - release-process.rst 30 | - .gitignore 31 | - .coveragerc 32 | - .github/workflows/deploy.yml 33 | - .github/workflows/codeql-analysis.yml 34 | - .github/workflows/FUNDING.yml 35 | - setup.cfg 36 | - setup.py 37 | - webdriver_manager/__init__.py 38 | 39 | jobs: 40 | test: 41 | runs-on: ${{ matrix.os }} 42 | env: 43 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | python-version: ['3.11'] 48 | selenium-version: ['4.10.0'] 49 | os: [ windows-latest ] 50 | wdm-log: [''] 51 | include: 52 | - python-version: '3.11' 53 | selenium-version: '4.10.0' 54 | os: ubuntu-latest 55 | - python-version: '3.11' 56 | selenium-version: '4.10.0' 57 | os: macos-latest 58 | wdm-log: '0' 59 | 60 | steps: 61 | - uses: actions/checkout@v3 62 | 63 | - name: Set up Python ${{ matrix.python-version }} 64 | uses: actions/setup-python@v4 65 | with: 66 | python-version: ${{ matrix.python-version }} 67 | 68 | - name: Install browsers on Linux 69 | if: runner.os == 'Linux' 70 | run: | 71 | sudo apt-get update 72 | sudo apt-get install software-properties-common apt-transport-https wget curl 73 | 74 | wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 75 | sudo dpkg -i google-chrome-stable_current_amd64.deb 76 | 77 | wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add - 78 | sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" 79 | sudo apt-get update && sudo apt install microsoft-edge-beta 80 | 81 | wget -qO- https://deb.opera.com/archive.key | sudo apt-key add - 82 | sudo add-apt-repository "deb [arch=i386,amd64] https://deb.opera.com/opera-stable/ stable non-free" 83 | sudo apt-get update 84 | sudo apt-get -y --no-install-recommends install opera-stable 85 | 86 | sudo apt-get install chromium-browser 87 | 88 | sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg 89 | echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main"|sudo tee /etc/apt/sources.list.d/brave-browser-release.list 90 | sudo apt update 91 | sudo apt install brave-browser 92 | 93 | - name: Check browser versions on Linux 94 | if: runner.os == 'Linux' 95 | run: | 96 | google-chrome --version 97 | microsoft-edge --version 98 | opera --version 99 | chromium --version 100 | brave-browser --version 101 | 102 | - name: Install browsers on Windows 103 | if: runner.os == 'Windows' 104 | shell: powershell 105 | run: | 106 | choco install chromium opera brave googlechrome --no-progress -y --force 107 | 108 | - name: Install browsers on MacOS 109 | if: startsWith(runner.os, 'macOS') 110 | run: | 111 | brew tap domt4/chromium 112 | brew update 113 | brew install --cask mac-chromium opera brave-browser 114 | brew upgrade google-chrome 115 | 116 | - name: Install Python dependencies 117 | run: | 118 | python -m pip install -U pip wheel 119 | pip install pipenv 120 | pipenv install --dev --skip-lock --python=${{ matrix.python-version }} 121 | pipenv install selenium==${{ matrix.selenium-version }} 122 | 123 | - name: Run tests on Linux (with xvfb) 124 | if: runner.os == 'Linux' 125 | env: 126 | WDM_LOG: ${{ matrix.wdm-log }} 127 | uses: coactions/setup-xvfb@v1 128 | with: 129 | run: | 130 | pipenv run py.test -sv --cov-config .coveragerc --cov-report xml --cov-report term:skip-covered --cov=webdriver_manager --tb=short tests/ 131 | 132 | - name: Run tests on Windows (without xvfb) 133 | if: runner.os == 'Windows' 134 | run: | 135 | pipenv run py.test -sv --cov-config .coveragerc --cov-report xml --cov-report term:skip-covered --cov=webdriver_manager --tb=short tests/ 136 | 137 | - name: Run tests on MacOS (without xvfb) 138 | if: startsWith(runner.os, 'macOS') 139 | run: | 140 | pipenv run py.test -sv --cov-config .coveragerc --cov-report xml --cov-report term:skip-covered --cov=webdriver_manager --tb=short tests/ 141 | 142 | - name: Codecov Upload 143 | uses: codecov/codecov-action@v3 144 | if: always() 145 | with: 146 | file: ./coverage.xml 147 | name: ${{ matrix.os }}-py${{ matrix.python-version }} 148 | 149 | test-negative: 150 | runs-on: ${{ matrix.os }} 151 | env: 152 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 153 | strategy: 154 | fail-fast: false 155 | matrix: 156 | python-version: [ '3.7' ] 157 | selenium-version: [ '3.141.0' ] 158 | os: [ ubuntu-latest ] 159 | 160 | steps: 161 | - uses: actions/checkout@v3 162 | 163 | - name: Set up Python ${{ matrix.python-version }} 164 | uses: actions/setup-python@v4 165 | with: 166 | python-version: ${{ matrix.python-version }} 167 | 168 | - name: Uninstall browsers 169 | run: | 170 | sudo apt remove firefox google-chrome-stable -y 171 | sudo apt autoremove && sudo apt autoclean -y 172 | sudo apt clean 173 | 174 | - name: Install Python dependencies 175 | run: | 176 | python -m pip install -U pip wheel 177 | pip install pipenv 178 | pipenv install --dev --skip-lock --python=${{ matrix.python-version }} 179 | pipenv install selenium==${{ matrix.selenium-version }} 180 | 181 | - name: Run tests on Linux (with xvfb) 182 | if: runner.os == 'Linux' 183 | uses: coactions/setup-xvfb@v1 184 | with: 185 | run: | 186 | pipenv run py.test -sv --cov-config .coveragerc --cov-report xml --cov-report term:skip-covered --cov=webdriver_manager --tb=short tests_negative/ 187 | 188 | - name: Codecov Upload 189 | uses: codecov/codecov-action@v3 190 | if: always() 191 | with: 192 | file: ./coverage.xml 193 | name: ${{ matrix.os }}-py${{ matrix.python-version }} 194 | -------------------------------------------------------------------------------- /.github/workflows/test_xdist.yml: -------------------------------------------------------------------------------- 1 | name: "Tests xdist" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths-ignore: 7 | - CHANGELOG.md 8 | - README.md 9 | - LICENSE 10 | - LICENSE.txt 11 | - release-process.rst 12 | - .gitignore 13 | - .coveragerc 14 | - .github/workflows/deploy.yml 15 | - .github/workflows/codeql-analysis.yml 16 | - .github/workflows/FUNDING.yml 17 | - setup.cfg 18 | - setup.py 19 | - webdriver_manager/__init__.py 20 | 21 | pull_request: 22 | branches: 23 | - master 24 | paths-ignore: 25 | - CHANGELOG.md 26 | - README.md 27 | - LICENSE 28 | - LICENSE.txt 29 | - release-process.rst 30 | - .gitignore 31 | - .coveragerc 32 | - .github/workflows/deploy.yml 33 | - .github/workflows/codeql-analysis.yml 34 | - .github/workflows/FUNDING.yml 35 | - setup.cfg 36 | - setup.py 37 | - webdriver_manager/__init__.py 38 | 39 | jobs: 40 | test-xdist: 41 | runs-on: ${{ matrix.os }} 42 | env: 43 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | python-version: ['3.10'] 48 | selenium-version: ['4.1.0'] 49 | os: [ ubuntu-latest ] 50 | 51 | steps: 52 | - uses: actions/checkout@v3 53 | 54 | - name: Set up Python ${{ matrix.python-version }} 55 | uses: actions/setup-python@v4 56 | with: 57 | python-version: ${{ matrix.python-version }} 58 | 59 | - name: Install browsers on Linux 60 | if: runner.os == 'Linux' 61 | run: | 62 | sudo apt-get update 63 | sudo apt-get install software-properties-common apt-transport-https wget curl 64 | 65 | wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 66 | sudo dpkg -i google-chrome-stable_current_amd64.deb 67 | google-chrome --version 68 | 69 | - name: Install Python dependencies 70 | run: | 71 | python -m pip install -U pip wheel 72 | pip install pipenv 73 | pipenv install --dev --skip-lock --python=${{ matrix.python-version }} 74 | pipenv install selenium==${{ matrix.selenium-version }} 75 | 76 | - name: Run tests on Linux (with xvfb) 77 | if: runner.os == 'Linux' 78 | uses: coactions/setup-xvfb@v1 79 | env: 80 | WDM_LOG: ${{ matrix.wdm-log }} 81 | with: 82 | run: | 83 | pipenv run pytest -sv tests_xdist/ -n 3 84 | 85 | - name: List folders 86 | run: ls -la ~/.wdm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project 2 | ssl_disabled/ 3 | custom/ 4 | .venv*/ 5 | venv*/ 6 | .wdm/ 7 | drivers.json 8 | 9 | # Created by .ignore support plugin (hsz.mobi) 10 | ### Python template 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | env/ 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *,cover 56 | .hypothesis/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # IPython Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # dotenv 89 | .env 90 | 91 | # virtualenv 92 | .venv/ 93 | venv/ 94 | ENV/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | .idea/ 102 | .drivers/ 103 | 104 | # VS code config 105 | .vscode 106 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | --- 4 | 5 | ## 4.0.1 6 | - Add correct os_type determination while on cygwin 7 | - Up-to date README.md 8 | 9 | --- 10 | 11 | ## 4.0.0 12 | - Refactor 13 | 14 | --- 15 | 16 | ## 3.9.1 17 | - Fix new chromedriver path 18 | - Rework Manager os_type to class - Os System Manager 19 | 20 | --- 21 | 22 | ## 3.8.6 23 | - Officially support python 3.11 (3.11.1+) 24 | - Add MyPy stubs 25 | 26 | --- 27 | ### 3.7.1-3.8.5 changelog TBD 28 | 29 | --- 30 | ## 3.7.0 31 | - pytest-xdist support 32 | 33 | ## 3.6.2 34 | - Fix support of Apple M1 35 | - Add support of dotenv for config 36 | 37 | ## 3.6.1 38 | - Small fixes 39 | 40 | ## 3.6.0 41 | ### Features 42 | - Add download manager 43 | - Fix logger disable issue 44 | 45 | ## 3.5.4 46 | ### Features 47 | - Add Brave support, look "Use with Brave" chapter in README.md ([#331](https://github.com/SergeyPirogov/webdriver_manager/issues/331)) 48 | - Speed up webdriver-manager in `driver.Driver.get_version()` method. 49 | - Disable logs by `os.environ['WDM_LOG'] = '0'` (Resolves [#277](https://github.com/SergeyPirogov/webdriver_manager/issues/277)) 50 | ### Fixes 51 | - Error in `webdriver.util` `get_browser_version_from_os` for 32 bit applications ([#315](https://github.com/SergeyPirogov/webdriver_manager/issues/315)) 52 | - `EdgeChromiumDriverManager().install()` fails in 3.5.3 when no Edge found ([#312](https://github.com/SergeyPirogov/webdriver_manager/issues/312)) 53 | - Driver cache doesn't work with WDM 3.5.3 (Win) ([#311](https://github.com/SergeyPirogov/webdriver_manager/issues/311)) 54 | - `google-chrome` version is "UNKNOWN" with webdriver_manager 3.5.3 (Win) ([#307](https://github.com/SergeyPirogov/webdriver_manager/issues/307)) 55 | --- 56 | ## 3.5.3 Determine browser versions on Windows 57 | ### Fixes 58 | - Fixed logger for EdgeChromiumDriverManager and IEDriverManager ([#269](https://github.com/SergeyPirogov/webdriver_manager/issues/269), [#272](https://github.com/SergeyPirogov/webdriver_manager/issues/272)). 59 | - Fixed `JSONDecodeError` when raising `ValueError` message of failed request. ([#273](https://github.com/SergeyPirogov/webdriver_manager/issues/273)). 60 | - Fixed `geckodriver` permissions. When `webdriver.Firefox(GeckoDriverManager().install())` caused `os error 10061`. 61 | ### Features 62 | - MSEdge: Take the latest stable major version **bound to OS(!)** when Edge browser version was not determined. ([#302](https://github.com/SergeyPirogov/webdriver_manager/issues/302), ([#305](https://github.com/SergeyPirogov/webdriver_manager/issues/305)) 63 | - Windows: Determine browsers versions on Windows 32/64 bit by many ways. MSEdge, Chrome, Chromium, Firefox. PowerShell required. ([#261](https://github.com/SergeyPirogov/webdriver_manager/issues/261), [#193](https://github.com/SergeyPirogov/webdriver_manager/issues/193), [#293](https://github.com/SergeyPirogov/webdriver_manager/issues/293)). 64 | - Chrome/Chromium: Determine architecture of Mac M1 ARM64 for in ChromeDriverManager ([#299](https://github.com/SergeyPirogov/webdriver_manager/issues/299), [#205](https://github.com/SergeyPirogov/webdriver_manager/issues/205), [#285](https://github.com/SergeyPirogov/webdriver_manager/issues/285)) 65 | - Re-download webdriver binary in cases when it was not found in cache (was deleted) ([#286](https://github.com/SergeyPirogov/webdriver_manager/issues/286)) 66 | --- 67 | - ## 3.5.2 68 | ### Features 69 | - SSL verification can be disabled by setting `os.environ['WDM_SSL_VERIFY']='0'` in case if you have troubles with SSL Certificates or SSL Certificate Chain (like in issues 70 | [#219](https://github.com/SergeyPirogov/webdriver_manager/issues/219), [#226](https://github.com/SergeyPirogov/webdriver_manager/issues/226)) 71 | ### Fixes 72 | - Log duplication ([#259](https://github.com/SergeyPirogov/webdriver_manager/issues/259)) 73 | - Failed to Download the Edge driver for particular Version ([#251](https://github.com/SergeyPirogov/webdriver_manager/issues/251)) 74 | - WDM_LOG_LEVEL not work ([#255](https://github.com/SergeyPirogov/webdriver_manager/issues/255)) 75 | ### Improvements 76 | - Softly download latest webdriver version even if browser version is unknown. ([#254](https://github.com/SergeyPirogov/webdriver_manager/issues/254), also fixes [#251](https://github.com/SergeyPirogov/webdriver_manager/issues/251)) 77 | - Speed up when using "latest" version ([#259](https://github.com/SergeyPirogov/webdriver_manager/issues/259)) 78 | --- 79 | ## 3.5.1 80 | ### IEDriver 81 | - Fix: huge typo in IEDriver (appeared accidentally in 3.5.0 version) 82 | - Adopt finding latest IEDriverSever for irregular releases in selenium ([#247](https://github.com/SergeyPirogov/webdriver_manager/issues/247)) 83 | ### EdgeDriver 84 | - Feature: finding EdgeDriver version for MAC & LINUX depends on MSEdge browser version and OS type [#243](https://github.com/SergeyPirogov/webdriver_manager/issues/243), Fix for [#242](https://github.com/SergeyPirogov/webdriver_manager/issues/242) 85 | - Fix: Add rights to execute edgedriver binary on linux. 86 | - Test Coverage: More tests for EdgeDriver 87 | 88 | --- 89 | ## 3.5.0 90 | ### Fixes 91 | - Fix: WinError6 while executing script, packed in .exe by pyinstaller ([#198](https://github.com/SergeyPirogov/webdriver_manager/issues/198)) 92 | - Fix: stdio problem when making exe using pyinstaller with noconsole flag ([#198](https://github.com/SergeyPirogov/webdriver_manager/issues/198)) 93 | - Fix: error with execution right on linux afer extraction from zip ([#208](https://github.com/SergeyPirogov/webdriver_manager/issues/208)) 94 | - Fix: In Manager.DriverManager constructor named argument log_level is never passed to log() (#[172](https://github.com/SergeyPirogov/webdriver_manager/issues/172)) 95 | - Fix: first-line not hidden when logs disabled ([#212](https://github.com/SergeyPirogov/webdriver_manager/issues/212)) 96 | 97 | ### Features 98 | - Download IEDriverServer from GitHub ([#227](https://github.com/SergeyPirogov/webdriver_manager/issues/227)) 99 | 100 | ### Other 101 | - webdriver_manager now tests on 3.6, **3.7, 3.8, 3.9, 3.10** ([#235](https://github.com/SergeyPirogov/webdriver_manager/issues/235)) 102 | - webdriver_manager now supports not only 3.6, 3.7, 3.8, but also **3.9, 3.10** ([#235](https://github.com/SergeyPirogov/webdriver_manager/issues/235)) (tbh always has been) 103 | - webdriver_manager now releases to pypi by clicking "Publish GitHub release" button ([#238](https://github.com/SergeyPirogov/webdriver_manager/issues/238)) 104 | --- 105 | 106 | lots of releases ago... 107 | 108 | --- 109 | 110 | ## 1.7 (Released) 111 | * Configuration supports environment variables 112 | --- 113 | ## 1.5 (Released) 114 | * IEDriver bug fix for Win x64 115 | * Additional logging addednged 116 | * Cache path changed 117 | --- 118 | ## 1.4.5 (Released ) 119 | * Colorfull console output added 120 | --- 121 | ## 1.4.4 (Released ) 122 | * IEDriver support added 123 | --- 124 | ## 1.4.2 (Released 24.01.2017) 125 | * PhantomJS support added 126 | --- 127 | ## 1.4 (Released 21.01.2017) 128 | * Edge driver support added 129 | * config.ini support added 130 | --- 131 | ## 1.3 (Released 29.12.2016) 132 | * Python 3.5 support added 133 | --- 134 | ## 1.2 (Released 27.12.2016) 135 | * Windows platform support added 136 | * Github api token support added for Firefox 137 | * Code refactoring 138 | --- 139 | ## 1.1 (Released 26.12.2016) 140 | * Mac support added 141 | * Cache support added 142 | --- 143 | ## 1.0 (Released 25.12.2016) 144 | * Chrome support on linux 145 | * Firefox support on linux 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [requires] 7 | python = "3.7" 8 | 9 | [dev-packages] 10 | selenium = "*" 11 | pytest = "*" 12 | "pytest-cov" = "*" 13 | bump2version = "*" 14 | pytest-xdist = "*" 15 | pybrowsers = "*" 16 | mock = "*" 17 | 18 | [packages] 19 | requests = "*" 20 | python-dotenv = "*" 21 | packaging = "*" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webdriver Manager for Python 2 | 3 | [![Tests](https://github.com/SergeyPirogov/webdriver_manager/actions/workflows/test.yml/badge.svg)](https://github.com/SergeyPirogov/webdriver_manager/actions/workflows/test.yml) 4 | [![PyPI](https://img.shields.io/pypi/v/webdriver_manager.svg)](https://pypi.org/project/webdriver-manager) 5 | [![Supported Python Versions](https://img.shields.io/pypi/pyversions/webdriver_manager.svg)](https://pypi.org/project/webdriver-manager/) 6 | [![codecov](https://codecov.io/gh/SergeyPirogov/webdriver_manager/branch/master/graph/badge.svg)](https://codecov.io/gh/SergeyPirogov/webdriver_manager) 7 | 8 | **Because of the War in Ukraine the project is on hold😔** 9 | 10 | Now it's time to produce FPV drone for Ukrainian army. 11 | 12 | Support via paypal: semen4ik20@gmail.com 13 | 14 | ## Support the library on [Patreon](https://www.patreon.com/automation_remarks) 15 | 16 | The main idea is to simplify management of binary drivers for different browsers. 17 | 18 | For now support: 19 | 20 | - [ChromeDriver](#use-with-chrome) 21 | - [EdgeChromiumDriver](#use-with-edge) 22 | - [GeckoDriver](#use-with-firefox) 23 | - [IEDriver](#use-with-ie) 24 | - [OperaDriver](#use-with-opera) 25 | 26 | Compatible with Selenium 4.x and below. 27 | 28 | Before: 29 | You need to download the chromedriver binary, unzip it somewhere on your PC and set the path to this driver like this: 30 | 31 | ```python 32 | from selenium import webdriver 33 | driver = webdriver.Chrome('/home/user/drivers/chromedriver') 34 | ``` 35 | 36 | It’s boring!!! Moreover, every time a new version of the driver is released, you need to repeat all these steps again and again. 37 | 38 | With webdriver manager, you just need to do two simple steps: 39 | 40 | #### Install manager: 41 | 42 | ```bash 43 | pip install webdriver-manager 44 | ``` 45 | 46 | #### Use with Chrome 47 | 48 | ```python 49 | # selenium 3 50 | from selenium import webdriver 51 | from webdriver_manager.chrome import ChromeDriverManager 52 | 53 | driver = webdriver.Chrome(ChromeDriverManager().install()) 54 | ``` 55 | ```python 56 | # selenium 4 57 | from selenium import webdriver 58 | from selenium.webdriver.chrome.service import Service as ChromeService 59 | from webdriver_manager.chrome import ChromeDriverManager 60 | 61 | driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) 62 | ``` 63 | 64 | #### Use with Chromium 65 | 66 | ```python 67 | # selenium 3 68 | from selenium import webdriver 69 | from webdriver_manager.chrome import ChromeDriverManager 70 | from webdriver_manager.core.os_manager import ChromeType 71 | 72 | driver = webdriver.Chrome(ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install()) 73 | ``` 74 | 75 | ```python 76 | # selenium 4 77 | from selenium import webdriver 78 | from selenium.webdriver.chrome.service import Service as ChromiumService 79 | from webdriver_manager.chrome import ChromeDriverManager 80 | from webdriver_manager.core.os_manager import ChromeType 81 | 82 | driver = webdriver.Chrome(service=ChromiumService(ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install())) 83 | ``` 84 | 85 | #### Use with Brave 86 | 87 | ```python 88 | # selenium 3 89 | from selenium import webdriver 90 | from webdriver_manager.chrome import ChromeDriverManager 91 | from webdriver_manager.core.os_manager import ChromeType 92 | 93 | driver = webdriver.Chrome(ChromeDriverManager(chrome_type=ChromeType.BRAVE).install()) 94 | ``` 95 | 96 | ```python 97 | # selenium 4 98 | from selenium import webdriver 99 | from selenium.webdriver.chrome.service import Service as BraveService 100 | from webdriver_manager.chrome import ChromeDriverManager 101 | from webdriver_manager.core.os_manager import ChromeType 102 | 103 | driver = webdriver.Chrome(service=BraveService(ChromeDriverManager(chrome_type=ChromeType.BRAVE).install())) 104 | ``` 105 | 106 | 107 | #### Use with Edge 108 | 109 | ```python 110 | # selenium 3 111 | from selenium import webdriver 112 | from webdriver_manager.microsoft import EdgeChromiumDriverManager 113 | 114 | driver = webdriver.Edge(EdgeChromiumDriverManager().install()) 115 | ``` 116 | ```python 117 | # selenium 4 118 | from selenium import webdriver 119 | from selenium.webdriver.edge.service import Service as EdgeService 120 | from webdriver_manager.microsoft import EdgeChromiumDriverManager 121 | 122 | driver = webdriver.Edge(service=EdgeService(EdgeChromiumDriverManager().install())) 123 | ``` 124 | 125 | #### Use with Firefox 126 | 127 | ```python 128 | # selenium 3 129 | from selenium import webdriver 130 | from webdriver_manager.firefox import GeckoDriverManager 131 | 132 | driver = webdriver.Firefox(executable_path=GeckoDriverManager().install()) 133 | ``` 134 | ```python 135 | # selenium 4 136 | from selenium import webdriver 137 | from selenium.webdriver.firefox.service import Service as FirefoxService 138 | from webdriver_manager.firefox import GeckoDriverManager 139 | 140 | driver = webdriver.Firefox(service=FirefoxService(GeckoDriverManager().install())) 141 | ``` 142 | 143 | #### Use with IE 144 | 145 | ```python 146 | # selenium 3 147 | from selenium import webdriver 148 | from webdriver_manager.microsoft import IEDriverManager 149 | 150 | driver = webdriver.Ie(IEDriverManager().install()) 151 | ``` 152 | ```python 153 | # selenium 4 154 | from selenium import webdriver 155 | from selenium.webdriver.ie.service import Service as IEService 156 | from webdriver_manager.microsoft import IEDriverManager 157 | 158 | driver = webdriver.Ie(service=IEService(IEDriverManager().install())) 159 | ``` 160 | 161 | 162 | #### Use with Opera 163 | 164 | ```python 165 | # selenium 3 166 | from selenium import webdriver 167 | from selenium.webdriver.chrome import service 168 | from webdriver_manager.opera import OperaDriverManager 169 | 170 | webdriver_service = service.Service(OperaDriverManager().install()) 171 | webdriver_service.start() 172 | 173 | driver = webdriver.Remote(webdriver_service.service_url, webdriver.DesiredCapabilities.OPERA) 174 | ``` 175 | ```python 176 | # selenium 4 177 | from selenium import webdriver 178 | from selenium.webdriver.chrome import service 179 | from webdriver_manager.opera import OperaDriverManager 180 | 181 | webdriver_service = service.Service(OperaDriverManager().install()) 182 | webdriver_service.start() 183 | 184 | options = webdriver.ChromeOptions() 185 | options.add_experimental_option('w3c', True) 186 | 187 | driver = webdriver.Remote(webdriver_service.service_url, options=options) 188 | ``` 189 | 190 | If the Opera browser is installed in a location other than `C:/Program Files` or `C:/Program Files (x86)` on Windows 191 | and `/usr/bin/opera` for all unix variants and mac, then use the below code, 192 | 193 | ```python 194 | options = webdriver.ChromeOptions() 195 | options.binary_location = "path/to/opera.exe" 196 | driver = webdriver.Remote(webdriver_service.service_url, options=options) 197 | ``` 198 | 199 | #### Get browser version from path 200 | 201 | To get the version of the browser from the executable of the browser itself: 202 | 203 | ```python 204 | from webdriver_manager.firefox import GeckoDriverManager 205 | 206 | from webdriver_manager.core.utils import read_version_from_cmd 207 | from webdriver_manager.core.os_manager import PATTERN 208 | 209 | version = read_version_from_cmd("/usr/bin/firefox-bin --version", PATTERN["firefox"]) 210 | driver_binary = GeckoDriverManager(version=version).install() 211 | ``` 212 | 213 | #### Custom Cache, File manager and OS Manager 214 | 215 | ```python 216 | from webdriver_manager.chrome import ChromeDriverManager 217 | from webdriver_manager.core.file_manager import FileManager 218 | from webdriver_manager.core.driver_cache import DriverCacheManager 219 | from webdriver_manager.core.os_manager import OperationSystemManager 220 | 221 | cache_manager = DriverCacheManager(file_manager=FileManager(os_system_manager=OperationSystemManager())) 222 | manager = ChromeDriverManager(cache_manager=cache_manager) 223 | os_manager = OperationSystemManager(os_type="win64") 224 | ``` 225 | 226 | ## Configuration 227 | 228 | **webdriver_manager** has several configuration variables you can be interested in. 229 | Any variable can be set using either .env file or via python directly 230 | 231 | ### `GH_TOKEN` 232 | **webdriver_manager** downloading some webdrivers from their official GitHub repositories but GitHub has [limitations](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) like 60 requests per hour for unauthenticated users. 233 | In case not to face an error related to GitHub credentials, you need to [create](https://help.github.com/articles/creating-an-access-token-for-command-line-use) GitHub token and place it into your environment: (\*) 234 | 235 | Example: 236 | 237 | ```bash 238 | export GH_TOKEN = "asdasdasdasd" 239 | ``` 240 | 241 | (\*) access_token required to work with GitHub API [more info](https://help.github.com/articles/creating-an-access-token-for-command-line-use/). 242 | 243 | There is also possibility to set same variable via ENV VARIABLES, example: 244 | 245 | ```python 246 | import os 247 | 248 | os.environ['GH_TOKEN'] = "asdasdasdasd" 249 | ``` 250 | 251 | ### `WDM_LOG` 252 | Turn off webdriver-manager logs use: 253 | 254 | ```python 255 | import logging 256 | import os 257 | 258 | os.environ['WDM_LOG'] = str(logging.NOTSET) 259 | ``` 260 | 261 | ### `WDM_LOCAL` 262 | By default, all driver binaries are saved to user.home/.wdm folder. You can override this setting and save binaries to project.root/.wdm. 263 | 264 | ```python 265 | import os 266 | 267 | os.environ['WDM_LOCAL'] = '1' 268 | ``` 269 | 270 | ### `WDM_SSL_VERIFY` 271 | SSL verification can be disabled for downloading webdriver binaries in case when you have troubles with SSL Certificates or SSL Certificate Chain. Just set the environment variable `WDM_SSL_VERIFY` to `"0"`. 272 | 273 | ```python 274 | import os 275 | 276 | os.environ['WDM_SSL_VERIFY'] = '0' 277 | ``` 278 | 279 | ### `version` 280 | Specify the version of webdriver you need. And webdriver-manager will download it from sources for your os. 281 | ```python 282 | from webdriver_manager.chrome import ChromeDriverManager 283 | 284 | ChromeDriverManager(driver_version="2.26").install() 285 | ``` 286 | 287 | ### `cache_valid_range` 288 | Driver cache by default is valid for 1 day. You are able to change this value using constructor parameter: 289 | 290 | ```python 291 | from webdriver_manager.chrome import ChromeDriverManager 292 | from webdriver_manager.core.driver_cache import DriverCacheManager 293 | 294 | ChromeDriverManager("2.26", cache_manager=DriverCacheManager(valid_range=1)).install() 295 | ``` 296 | 297 | ### `os_type` 298 | For some reasons you may use custom OS/arch. You are able to change this value using constructor parameter: 299 | 300 | ```python 301 | from webdriver_manager.chrome import ChromeDriverManager 302 | from webdriver_manager.core.os_manager import OperationSystemManager 303 | 304 | ChromeDriverManager(os_system_manager=OperationSystemManager(os_type="linux-mips64")).install() 305 | ``` 306 | 307 | ### `url` 308 | You may use any other repo with drivers and release URl. You are able to change this value using constructor parameters: 309 | 310 | ```python 311 | from webdriver_manager.chrome import ChromeDriverManager 312 | 313 | ChromeDriverManager(url="https://custom-repo.url", latest_release_url="https://custom-repo.url/LATEST").install() 314 | ``` 315 | 316 | --- 317 | 318 | ### Custom Logger 319 | 320 | If you need to use a custom logger, you can create a logger and set it with `set_logger()`. 321 | 322 | ```python 323 | import logging 324 | from webdriver_manager.core.logger import set_logger 325 | 326 | logger = logging.getLogger("custom_logger") 327 | logger.setLevel(logging.DEBUG) 328 | logger.addHandler(logging.StreamHandler()) 329 | logger.addHandler(logging.FileHandler("custom.log")) 330 | 331 | set_logger(logger) 332 | ``` 333 | 334 | --- 335 | 336 | ### Custom HTTP Client 337 | If you need to add custom HTTP logic like session or proxy you can define your custom HttpClient implementation. 338 | 339 | ```python 340 | import os 341 | 342 | import requests 343 | from requests import Response 344 | 345 | from webdriver_manager.chrome import ChromeDriverManager 346 | from webdriver_manager.core.download_manager import WDMDownloadManager 347 | from webdriver_manager.core.http import HttpClient 348 | from webdriver_manager.core.logger import log 349 | 350 | class CustomHttpClient(HttpClient): 351 | 352 | def get(self, url, params=None, **kwargs) -> Response: 353 | """ 354 | Add you own logic here like session or proxy etc. 355 | """ 356 | log("The call will be done with custom HTTP client") 357 | return requests.get(url, params, **kwargs) 358 | 359 | 360 | def test_can_get_chrome_driver_with_custom_http_client(): 361 | http_client = CustomHttpClient() 362 | download_manager = WDMDownloadManager(http_client) 363 | path = ChromeDriverManager(download_manager=download_manager).install() 364 | assert os.path.exists(path) 365 | ``` 366 | 367 | --- 368 | 369 | This will make your test automation more elegant and robust! 370 | 371 | Cheers 372 | -------------------------------------------------------------------------------- /release-process.rst: -------------------------------------------------------------------------------- 1 | Release process 2 | =============== 3 | 4 | Run tests on target brunch 5 | -------------------------- 6 | 7 | Steps:: 8 | 9 | tox -epep8 10 | tox -esphinx-docs 11 | 12 | 13 | Release new version 14 | ------------------- 15 | 16 | In repo root and master branch run:: 17 | 18 | bump2version 19 | git push --follow-tags 20 | 21 | Cut off stable branch 22 | --------------------- 23 | 24 | Steps:: 25 | 26 | git checkout -b vX.X.X-stable 27 | git push origin vX.X.X-stable 28 | 29 | 30 | Create GitHub tag 31 | ----------------- 32 | 33 | Steps:: 34 | 35 | Releases ---> Draft New Release 36 | Name: python-testrelease version X.X.X stable release 37 | 38 | 39 | Collect changes from previous version 40 | ------------------------------------- 41 | 42 | Steps:: 43 | 44 | git log --oneline --decorate 45 | 46 | 47 | Build distribution package 48 | -------------------------- 49 | 50 | Steps:: 51 | 52 | python setup.py bdist_wheel 53 | 54 | 55 | Check install capability for the wheel 56 | -------------------------------------- 57 | 58 | Steps:: 59 | 60 | virtualenv .test_venv 61 | source .test_venv/bin/activate 62 | pip install dist/testcontainer-X.X.X-py2.py3-none-any.whl 63 | 64 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 4.0.1 3 | commit = True 4 | tag = True 5 | 6 | [bdist_wheel] 7 | universal = 1 8 | 9 | [bumpversion:file:setup.py] 10 | search = version='{current_version}', 11 | replace = version='{new_version}', 12 | 13 | [bumpversion:file:webdriver_manager/__init__.py] 14 | search = __version__ = '{current_version}' 15 | replace = __version__ = '{new_version}' 16 | 17 | [metadata] 18 | description-file = README.md 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | import setuptools 15 | 16 | with open("README.md", encoding="utf-8") as readme_file: 17 | readme = readme_file.read() 18 | 19 | setuptools.setup( 20 | name='webdriver_manager', 21 | python_requires=">=3.7", 22 | long_description=readme, 23 | long_description_content_type="text/markdown", 24 | packages=setuptools.find_packages(include=['webdriver_manager*']), 25 | include_package_data=True, 26 | version='4.0.2', 27 | description='Library provides the way to automatically manage drivers for different browsers', 28 | author='Sergey Pirogov', 29 | author_email='automationremarks@gmail.com', 30 | url='https://github.com/SergeyPirogov/webdriver_manager', 31 | keywords=['testing', 'selenium', 'driver', 'test automation'], 32 | classifiers=[ 33 | 'License :: OSI Approved :: Apache Software License', 34 | 'Intended Audience :: Information Technology', 35 | 'Intended Audience :: Developers', 36 | 'Programming Language :: Python :: 3.7', 37 | 'Programming Language :: Python :: 3.8', 38 | 'Programming Language :: Python :: 3.9', 39 | 'Programming Language :: Python :: 3.10', 40 | 'Programming Language :: Python :: 3.11', 41 | 'Topic :: Software Development :: ' 42 | 'Libraries :: Python Modules', 43 | 'Operating System :: Microsoft :: Windows', 44 | 'Operating System :: POSIX', 45 | 'Operating System :: Unix', 46 | 'Operating System :: MacOS', 47 | ], 48 | install_requires=[ 49 | 'requests', 50 | 'python-dotenv', 51 | 'packaging' 52 | ], 53 | package_data={ 54 | "webdriver_manager": ["py.typed"] 55 | }, 56 | ) 57 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SergeyPirogov/webdriver_manager/1c59212c6dae5335d2aead14b119307084b44293/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from pprint import pprint 4 | 5 | import pytest 6 | import browsers 7 | from webdriver_manager.core.constants import DEFAULT_PROJECT_ROOT_CACHE_PATH, DEFAULT_USER_HOME_CACHE_PATH 8 | from webdriver_manager.core.logger import log 9 | 10 | 11 | @pytest.fixture(scope="session", autouse=True) 12 | def list_browsers(): 13 | pprint(list(browsers.browsers())) 14 | 15 | 16 | @pytest.fixture() 17 | def delete_drivers_dir(): 18 | try: 19 | if os.path.exists(DEFAULT_USER_HOME_CACHE_PATH): 20 | log(f"Delete {DEFAULT_USER_HOME_CACHE_PATH} folder") 21 | shutil.rmtree(DEFAULT_USER_HOME_CACHE_PATH) 22 | if os.path.exists(DEFAULT_PROJECT_ROOT_CACHE_PATH): 23 | log(f"Delete {DEFAULT_PROJECT_ROOT_CACHE_PATH} folder") 24 | shutil.rmtree(DEFAULT_PROJECT_ROOT_CACHE_PATH) 25 | except PermissionError as e: 26 | print(f"Can not delete folder {e}") 27 | 28 | 29 | @pytest.fixture(scope='function') 30 | def ssl_verify_enable(): 31 | yield 32 | os.environ['WDM_SSL_VERIFY'] = '' 33 | -------------------------------------------------------------------------------- /tests/test_brave_driver.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from selenium import webdriver 5 | 6 | from webdriver_manager.chrome import ChromeDriverManager 7 | from webdriver_manager.core.driver_cache import DriverCacheManager 8 | from webdriver_manager.core.logger import log 9 | from webdriver_manager.core.os_manager import ChromeType, OSType, OperationSystemManager 10 | 11 | 12 | def test_driver_with_ssl_verify_disabled_can_be_downloaded(ssl_verify_enable): 13 | os.environ['WDM_SSL_VERIFY'] = '0' 14 | custom_path = os.path.join( 15 | os.path.dirname(os.path.dirname(__file__)), 16 | '.wdm', 17 | "ssl_disabled", 18 | ) 19 | driver_path = ChromeDriverManager( 20 | driver_version="87.0.4280.88", 21 | cache_manager=DriverCacheManager(custom_path), 22 | chrome_type=ChromeType.BRAVE, 23 | ).install() 24 | 25 | assert os.path.exists(driver_path) 26 | 27 | 28 | def test_brave_manager_with_specific_version(): 29 | bin_path = ChromeDriverManager("87.0.4280.88", chrome_type=ChromeType.BRAVE).install() 30 | assert os.path.exists(bin_path) 31 | 32 | 33 | @pytest.mark.skip(reason='Brave version is strange on CI') 34 | def test_brave_manager_with_selenium(): 35 | binary_location = { 36 | OSType.LINUX: "/usr/bin/brave-browser", 37 | OSType.MAC: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", 38 | OSType.WIN: f"{os.getenv('LOCALAPPDATA')}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe", 39 | }[os_name()] 40 | log(binary_location) 41 | option = webdriver.ChromeOptions() 42 | option.binary_location = binary_location 43 | driver_path = ChromeDriverManager(chrome_type=ChromeType.BRAVE).install() 44 | driver = webdriver.Chrome(driver_path, options=option) 45 | 46 | driver.get("http://automation-remarks.com") 47 | driver.close() 48 | 49 | 50 | def test_brave_manager_with_wrong_version(): 51 | with pytest.raises(ValueError) as ex: 52 | ChromeDriverManager("0.2", chrome_type=ChromeType.BRAVE).install() 53 | assert "There is no such driver by url" in ex.value.args[0] 54 | 55 | 56 | @pytest.mark.parametrize('os_type', ['win32', 'win64']) 57 | def test_can_get_brave_for_win(os_type): 58 | path = ChromeDriverManager(driver_version="83.0.4103.39", 59 | os_system_manager=OperationSystemManager(os_type), 60 | chrome_type=ChromeType.BRAVE).install() 61 | assert os.path.exists(path) 62 | -------------------------------------------------------------------------------- /tests/test_chrome_driver.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import pytest 5 | import browsers 6 | from selenium import webdriver 7 | from mock import patch 8 | 9 | from webdriver_manager.chrome import ChromeDriverManager 10 | from webdriver_manager.core.constants import ROOT_FOLDER_NAME 11 | from selenium.webdriver.chrome.service import Service 12 | from webdriver_manager.core.driver import Driver 13 | 14 | from webdriver_manager.core.driver_cache import DriverCacheManager 15 | from webdriver_manager.core.os_manager import OperationSystemManager 16 | 17 | os.environ.setdefault("WDM_LOCAL", "false") 18 | 19 | 20 | def test_chrome_manager_with_cache(delete_drivers_dir): 21 | ChromeDriverManager().install() 22 | driver_binary = ChromeDriverManager().install() 23 | assert os.path.exists(driver_binary) 24 | 25 | 26 | def test_chrome_manager_with_specific_version(delete_drivers_dir): 27 | driver_binary = ChromeDriverManager("87.0.4280.88").install() 28 | assert os.path.exists(driver_binary) 29 | 30 | 31 | @patch.object(Driver, 'get_browser_version_from_os', return_value="112.0.5615.165") 32 | def test_chrome_manager_with_old_detected_version(mock_version, delete_drivers_dir): 33 | driver_binary = ChromeDriverManager().install() 34 | assert os.path.exists(driver_binary) 35 | 36 | 37 | def test_106_0_5249_61_chrome_version(delete_drivers_dir): 38 | driver_binary = ChromeDriverManager("106.0.5249.61").install() 39 | assert os.path.exists(driver_binary) 40 | 41 | 42 | def test_chrome_manager_with_project_root_local_folder(delete_drivers_dir): 43 | os.environ['WDM_LOCAL'] = "1" 44 | driver_binary = ChromeDriverManager("87.0.4280.88").install() 45 | os.environ['WDM_LOCAL'] = "0" 46 | assert os.path.exists(driver_binary) 47 | 48 | 49 | def test_driver_can_be_saved_to_custom_path(): 50 | custom_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "custom") 51 | path = ChromeDriverManager(driver_version="87.0.4280.88", cache_manager=DriverCacheManager(custom_path)).install() 52 | assert os.path.exists(path) 53 | assert custom_path in path 54 | 55 | 56 | def test_chrome_manager_with_wrong_version(): 57 | with pytest.raises(ValueError) as ex: 58 | ChromeDriverManager("0.2").install() 59 | assert "There is no such driver by url" in ex.value.args[0] 60 | 61 | 62 | def test_chrome_manager_with_selenium(): 63 | options = webdriver.ChromeOptions() 64 | options.binary_location = browsers.get("chrome")["path"] 65 | driver_path = ChromeDriverManager().install() 66 | driver = webdriver.Chrome(service=Service(driver_path), options=options) 67 | driver.get("http://automation-remarks.com") 68 | driver.close() 69 | 70 | 71 | def test_driver_with_ssl_verify_disabled_can_be_downloaded(ssl_verify_enable): 72 | os.environ['WDM_SSL_VERIFY'] = '0' 73 | custom_path = os.path.join(os.path.dirname( 74 | os.path.dirname(__file__)), 75 | "ssl_disabled", 76 | ) 77 | driver_path = ChromeDriverManager(cache_manager=DriverCacheManager(custom_path)).install() 78 | os.environ['WDM_SSL_VERIFY'] = '1' 79 | assert os.path.exists(driver_path) 80 | 81 | 82 | def test_chrome_manager_cached_driver_with_selenium(): 83 | options = webdriver.ChromeOptions() 84 | options.binary_location = browsers.get("chrome")["path"] 85 | custom_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "custom-cache") 86 | manager = ChromeDriverManager(cache_manager=DriverCacheManager(custom_path)) 87 | driver = webdriver.Chrome(service=Service(manager.install()), options=options) 88 | driver.get("http://automation-remarks.com") 89 | 90 | metadata_file = os.path.join(custom_path, ROOT_FOLDER_NAME, 'drivers.json') 91 | 92 | with open(metadata_file) as json_file: 93 | data = json.load(json_file) 94 | 95 | for k in data.keys(): 96 | data[k]['timestamp'] = "08/06/2019" 97 | 98 | with open(metadata_file, 'w') as outfile: 99 | json.dump(data, outfile) 100 | 101 | ChromeDriverManager(cache_manager=DriverCacheManager(custom_path)).install() 102 | 103 | 104 | @pytest.mark.parametrize('os_type', ['win32', 'win64', 'mac64', 'mac64_m1']) 105 | def test_can_get_chrome_for_os(os_type): 106 | path = ChromeDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 107 | assert os.path.exists(path) 108 | -------------------------------------------------------------------------------- /tests/test_chromium_driver.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from webdriver_manager.chrome import ChromeDriverManager 6 | from webdriver_manager.core.driver_cache import DriverCacheManager 7 | from webdriver_manager.core.os_manager import ChromeType, OperationSystemManager 8 | 9 | 10 | def test_driver_with_ssl_verify_disabled_can_be_downloaded(ssl_verify_enable): 11 | os.environ['WDM_SSL_VERIFY'] = '0' 12 | custom_path = os.path.join( 13 | os.path.dirname(os.path.dirname(__file__)), 14 | "ssl_disabled", 15 | ) 16 | driver_path = ChromeDriverManager( 17 | driver_version="87.0.4280.88", 18 | cache_manager=DriverCacheManager(custom_path), 19 | chrome_type=ChromeType.CHROMIUM, 20 | ).install() 21 | 22 | assert os.path.exists(driver_path) 23 | 24 | 25 | def test_chromium_manager_with_specific_version(): 26 | bin_path = ChromeDriverManager("87.0.4280.88", chrome_type=ChromeType.CHROMIUM).install() 27 | assert os.path.exists(bin_path) 28 | 29 | 30 | def test_driver_can_be_saved_to_custom_path(): 31 | custom_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "custom") 32 | 33 | path = ChromeDriverManager(driver_version="87.0.4280.88", cache_manager=DriverCacheManager(custom_path), 34 | chrome_type=ChromeType.CHROMIUM).install() 35 | assert os.path.exists(path) 36 | assert custom_path in path 37 | 38 | 39 | def test_chromium_manager_with_wrong_version(): 40 | with pytest.raises(ValueError) as ex: 41 | ChromeDriverManager("0.2", chrome_type=ChromeType.CHROMIUM).install() 42 | assert "There is no such driver by url" in ex.value.args[0] 43 | 44 | 45 | @pytest.mark.parametrize('os_type', ['win32', 'win64']) 46 | def test_can_get_chromium_for_win(os_type): 47 | path = ChromeDriverManager(driver_version="83.0.4103.39", 48 | os_system_manager=OperationSystemManager(os_type), 49 | chrome_type=ChromeType.CHROMIUM).install() 50 | assert os.path.exists(path) 51 | -------------------------------------------------------------------------------- /tests/test_custom_http_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import requests 4 | from requests import Response 5 | 6 | from webdriver_manager.chrome import ChromeDriverManager 7 | from webdriver_manager.core.download_manager import WDMDownloadManager 8 | from webdriver_manager.core.http import HttpClient 9 | from webdriver_manager.core.logger import log 10 | 11 | 12 | class CustomHttpClient(HttpClient): 13 | 14 | def get(self, url, params=None, **kwargs) -> Response: 15 | log("The call will be done with custom HTTP client") 16 | return requests.get(url, params, **kwargs) 17 | 18 | 19 | def test_can_get_chrome_driver_with_custom_http_client(): 20 | http_client = CustomHttpClient() 21 | download_manager = WDMDownloadManager(http_client) 22 | path = ChromeDriverManager(download_manager=download_manager).install() 23 | assert os.path.exists(path) 24 | -------------------------------------------------------------------------------- /tests/test_custom_logger.py: -------------------------------------------------------------------------------- 1 | """tests.test_custom_logger.py""" 2 | 3 | import logging 4 | 5 | import pytest 6 | 7 | from webdriver_manager.core.logger import log, set_logger, __logger 8 | 9 | # Cache the old logger to restore it later 10 | __old_logger = __logger 11 | 12 | 13 | @pytest.fixture 14 | def create_logger(): 15 | """Create a logger.""" 16 | 17 | # Create a Custom Debug Logger 18 | logger = logging.getLogger("WDM-DEBUG") 19 | logger.setLevel(logging.DEBUG) 20 | logger.addHandler(logging.StreamHandler()) 21 | 22 | return logger 23 | 24 | 25 | def test_custom_logger(capsys, create_logger): 26 | """Test the custom logger.""" 27 | 28 | # Set the custom logger 29 | set_logger(create_logger) 30 | 31 | # send a log message 32 | log_msg = "This is a test log message from the custom logger" 33 | log(log_msg) 34 | 35 | # Check if the log message is in the output 36 | captured = capsys.readouterr() 37 | assert log_msg in captured.err 38 | capsys.close() 39 | 40 | # Restore the old logger 41 | set_logger(__old_logger) 42 | -------------------------------------------------------------------------------- /tests/test_downloader.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from webdriver_manager.core.constants import DEFAULT_PROJECT_ROOT_CACHE_PATH 6 | from webdriver_manager.core.download_manager import WDMDownloadManager 7 | from webdriver_manager.core.file_manager import FileManager 8 | from webdriver_manager.core.http import WDMHttpClient 9 | from webdriver_manager.core.os_manager import OperationSystemManager, ChromeType 10 | from webdriver_manager.drivers.chrome import ChromeDriver 11 | 12 | os_manager = OperationSystemManager() 13 | download_manager = WDMDownloadManager() 14 | file_manager = FileManager(os_manager) 15 | 16 | 17 | def test_can_download_driver_as_zip_file(delete_drivers_dir): 18 | file = download_manager.download_file("http://chromedriver.storage.googleapis.com/2.26/chromedriver_win32.zip") 19 | assert file.filename == "chromedriver_win32.zip" 20 | archive = file_manager.save_archive_file(file, DEFAULT_PROJECT_ROOT_CACHE_PATH) 21 | assert archive.file_path == f"{DEFAULT_PROJECT_ROOT_CACHE_PATH}{os.sep}{file.filename}" 22 | assert file_manager.unpack_archive(archive, DEFAULT_PROJECT_ROOT_CACHE_PATH) == ["chromedriver.exe"] 23 | 24 | 25 | def test_can_download_driver_as_tar_gz(delete_drivers_dir): 26 | file = download_manager.download_file( 27 | "https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux32.tar.gz") 28 | assert file.filename == 'geckodriver-v0.26.0-linux32.tar.gz' 29 | archive = file_manager.save_archive_file(file, DEFAULT_PROJECT_ROOT_CACHE_PATH) 30 | assert archive.file_path == f"{DEFAULT_PROJECT_ROOT_CACHE_PATH}{os.sep}{file.filename}" 31 | assert file_manager.unpack_archive(archive, DEFAULT_PROJECT_ROOT_CACHE_PATH) == ["geckodriver"] 32 | 33 | 34 | @pytest.mark.parametrize('version', ["2.26"]) 35 | def test_can_download_chrome_driver(delete_drivers_dir, version): 36 | os_sys_manager = OperationSystemManager("win32") 37 | driver = ChromeDriver(name="chromedriver", 38 | driver_version=version, 39 | os_system_manager=os_sys_manager, 40 | url="http://chromedriver.storage.googleapis.com", 41 | latest_release_url="http://chromedriver.storage.googleapis.com/LATEST_RELEASE", 42 | chrome_type=ChromeType.GOOGLE, http_client=WDMHttpClient()) 43 | 44 | file = download_manager.download_file(driver.get_driver_download_url(os_sys_manager.get_os_type())) 45 | assert file.filename == "chromedriver_win32.zip" 46 | archive = file_manager.save_archive_file(file, DEFAULT_PROJECT_ROOT_CACHE_PATH) 47 | assert "chromedriver.exe" in file_manager.unpack_archive(archive, DEFAULT_PROJECT_ROOT_CACHE_PATH) 48 | -------------------------------------------------------------------------------- /tests/test_edge_driver.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | import pytest 5 | from selenium import webdriver 6 | from selenium.webdriver.edge.service import Service 7 | 8 | from webdriver_manager.core.driver_cache import DriverCacheManager 9 | from webdriver_manager.core.os_manager import PATTERN, ChromeType, OperationSystemManager 10 | from webdriver_manager.microsoft import EdgeChromiumDriverManager 11 | 12 | 13 | def test_edge_manager_with_selenium(): 14 | driver_path = EdgeChromiumDriverManager().install() 15 | driver = webdriver.Edge(service=Service(driver_path)) 16 | driver.get("http://automation-remarks.com") 17 | driver.quit() 18 | 19 | 20 | def test_driver_with_ssl_verify_disabled_can_be_downloaded(ssl_verify_enable): 21 | os.environ['WDM_SSL_VERIFY'] = '0' 22 | custom_path = os.path.join( 23 | os.path.dirname(os.path.dirname(__file__)), 24 | "ssl_disabled", 25 | ) 26 | driver_path = EdgeChromiumDriverManager(cache_manager=DriverCacheManager(custom_path)).install() 27 | os.environ['WDM_SSL_VERIFY'] = '1' 28 | assert os.path.exists(driver_path) 29 | 30 | 31 | def test_edge_manager_with_wrong_version(): 32 | with pytest.raises(ValueError) as ex: 33 | EdgeChromiumDriverManager( 34 | version="0.2", 35 | os_system_manager=OperationSystemManager("win64") 36 | ).install() 37 | 38 | assert ( 39 | "There is no such driver by url " 40 | "https://msedgedriver.azureedge.net/0.2/edgedriver_win64.zip" 41 | ) in ex.value.args[0] 42 | 43 | 44 | @pytest.mark.parametrize('os_type', ['win32', 'win64', 'mac64', 'linux64']) 45 | @pytest.mark.parametrize('specific_version', ['86.0.600.0']) 46 | def test_edge_with_specific_version(os_type, specific_version): 47 | bin_path = EdgeChromiumDriverManager( 48 | version=specific_version, 49 | os_system_manager=OperationSystemManager(os_type), 50 | ).install() 51 | assert os.path.exists(bin_path) 52 | 53 | 54 | @pytest.mark.parametrize('os_type', ['win32', 'win64', 'mac64', 'linux64']) 55 | @pytest.mark.parametrize('specific_version', ['87.0.637.0']) 56 | def test_can_get_edge_driver_from_cache(os_type, specific_version): 57 | EdgeChromiumDriverManager( 58 | version=specific_version, 59 | os_system_manager=OperationSystemManager(os_type), 60 | ).install() 61 | driver_path = EdgeChromiumDriverManager( 62 | version=specific_version, 63 | os_system_manager=OperationSystemManager(os_type) 64 | ).install() 65 | assert os.path.exists(driver_path) 66 | 67 | 68 | def test_get_stable_release_version(): 69 | pattern = PATTERN[ChromeType.MSEDGE] 70 | edge_driver = EdgeChromiumDriverManager( 71 | ).driver 72 | 73 | version = edge_driver.get_stable_release_version() 74 | version = re.search(pattern, version).group(0) 75 | 76 | assert len(version.split('.')) == 3, ( 77 | f"version '{version}' doesn't match version's count parts" 78 | ) 79 | -------------------------------------------------------------------------------- /tests/test_firefox_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from selenium import webdriver 5 | from selenium.webdriver.firefox.service import Service 6 | 7 | from webdriver_manager.core.driver_cache import DriverCacheManager 8 | from webdriver_manager.core.os_manager import OperationSystemManager 9 | from webdriver_manager.firefox import GeckoDriverManager 10 | 11 | 12 | def test_firefox_manager_with_cache(delete_drivers_dir): 13 | GeckoDriverManager().install() 14 | binary = GeckoDriverManager().install() 15 | assert os.path.exists(binary) 16 | 17 | 18 | def test_gecko_manager_with_selenium(): 19 | driver_path = GeckoDriverManager().install() 20 | ff = webdriver.Firefox(service=Service(driver_path)) 21 | ff.get("http://automation-remarks.com") 22 | ff.quit() 23 | 24 | 25 | def test_driver_with_ssl_verify_disabled_can_be_downloaded(ssl_verify_enable): 26 | os.environ['WDM_SSL_VERIFY'] = '0' 27 | custom_path = os.path.join( 28 | os.path.dirname(os.path.dirname(__file__)), 29 | "ssl_disabled", 30 | ) 31 | driver_path = GeckoDriverManager(cache_manager=DriverCacheManager(custom_path)).install() 32 | os.environ['WDM_SSL_VERIFY'] = '1' 33 | assert os.path.exists(driver_path) 34 | 35 | 36 | def test_gecko_manager_with_wrong_version(): 37 | with pytest.raises(ValueError) as ex: 38 | GeckoDriverManager("0.2").install() 39 | 40 | assert "There is no such driver by url " \ 41 | "https://api.github.com/repos/mozilla/geckodriver/releases/tags/0.2" \ 42 | in ex.value.args[0] 43 | 44 | 45 | def test_gecko_manager_with_correct_version_and_token(delete_drivers_dir): 46 | driver_path = GeckoDriverManager(version="v0.11.0").install() 47 | assert os.path.exists(driver_path) 48 | 49 | 50 | def test_can_download_ff_x64(delete_drivers_dir): 51 | driver_path = GeckoDriverManager(os_system_manager=OperationSystemManager("win64")).install() 52 | assert os.path.exists(driver_path) 53 | 54 | 55 | @pytest.mark.parametrize('os_type', ['win32', 56 | 'win64', 57 | 'linux32', 58 | 'linux64', 59 | 'mac64', 60 | 'mac64_m1']) 61 | def test_can_get_driver_from_cache(os_type): 62 | GeckoDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 63 | driver_path = GeckoDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 64 | assert os.path.exists(driver_path) 65 | -------------------------------------------------------------------------------- /tests/test_ie_driver.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from webdriver_manager.core.driver_cache import DriverCacheManager 6 | from webdriver_manager.core.os_manager import OperationSystemManager 7 | from webdriver_manager.microsoft import IEDriverManager 8 | 9 | 10 | @pytest.mark.parametrize("version", [ 11 | "3.0", 12 | "3.150.0" 13 | ]) 14 | def test_ie_manager_with_different_versions(version): 15 | path = IEDriverManager(version).install() 16 | assert os.path.exists(path) 17 | 18 | 19 | def test_driver_with_ssl_verify_disabled_can_be_downloaded(ssl_verify_enable): 20 | os.environ['WDM_SSL_VERIFY'] = '0' 21 | custom_path = os.path.join( 22 | os.path.dirname(os.path.dirname(__file__)), 23 | "ssl_disabled", 24 | ) 25 | driver_path = IEDriverManager(cache_manager=DriverCacheManager(custom_path)).install() 26 | os.environ['WDM_SSL_VERIFY'] = '1' 27 | assert os.path.exists(driver_path) 28 | 29 | 30 | @pytest.mark.parametrize('os_type', ['win32', 'win64']) 31 | def test_can_download_ie_driver_x64(os_type): 32 | path = IEDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 33 | assert os.path.exists(path) 34 | 35 | 36 | @pytest.mark.parametrize('os_type', ['win32', 'win64']) 37 | def test_can_get_ie_driver_from_cache(os_type): 38 | IEDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 39 | driver_path = IEDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 40 | assert os.path.exists(driver_path) 41 | 42 | 43 | @pytest.mark.parametrize('version', ['', '3', '3.4.5.6']) 44 | def test__get_divided_version_raises_exception(version): 45 | iedriver = IEDriverManager().driver 46 | 47 | with pytest.raises(ValueError) as exception: 48 | iedriver._IEDriver__get_divided_version(version=version) 49 | 50 | expected_msg = ( 51 | "Version must consist of major, minor and/or patch, " 52 | "but given was: '{}'".format(version) 53 | ) 54 | assert str(exception.value) == expected_msg 55 | -------------------------------------------------------------------------------- /tests/test_opera_manager.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | 4 | import pytest 5 | from selenium import webdriver 6 | from selenium.webdriver.chrome.service import Service 7 | 8 | from webdriver_manager.core.driver_cache import DriverCacheManager 9 | from webdriver_manager.core.os_manager import OperationSystemManager 10 | from webdriver_manager.opera import OperaDriverManager 11 | 12 | 13 | def test_opera_driver_manager_with_correct_version(): 14 | driver_path = OperaDriverManager("v.2.45").install() 15 | assert os.path.exists(driver_path) 16 | 17 | 18 | def test_driver_with_ssl_verify_disabled_can_be_downloaded(ssl_verify_enable): 19 | os.environ['WDM_SSL_VERIFY'] = '0' 20 | custom_path = os.path.join( 21 | os.path.dirname(os.path.dirname(__file__)), 22 | "ssl_disabled", 23 | ) 24 | driver_path = OperaDriverManager(cache_manager=DriverCacheManager(custom_path)).install() 25 | os.environ['WDM_SSL_VERIFY'] = '1' 26 | assert os.path.exists(driver_path) 27 | 28 | 29 | def test_operadriver_manager_with_selenium(): 30 | driver_path = OperaDriverManager().install() 31 | options = webdriver.ChromeOptions() 32 | options.add_experimental_option('w3c', True) 33 | os_type = OperationSystemManager().get_os_type() 34 | if os_type in ["win64", "win32"]: 35 | paths = [f for f in glob.glob( 36 | f"C:/Users/{os.getlogin()}/AppData/Local/Programs/Opera/**", 37 | recursive=True 38 | )] 39 | for path in paths: 40 | if os.path.isfile(path) and path.endswith("opera.exe"): 41 | options.binary_location = path 42 | elif ( 43 | (os_type in ["linux64", "linux32"]) 44 | and not os.path.exists('/usr/bin/opera') 45 | ): 46 | options.binary_location = "/usr/bin/opera" 47 | elif os_type in "mac64": 48 | options.binary_location = "/Applications/Opera.app/Contents/MacOS/Opera" 49 | web_service = Service(driver_path) 50 | web_service.start() 51 | 52 | opera_driver = webdriver.Remote(web_service.service_url, options=options) 53 | opera_driver.get("http://automation-remarks.com") 54 | opera_driver.quit() 55 | 56 | 57 | def test_opera_driver_manager_with_wrong_version(): 58 | with pytest.raises(ValueError) as ex: 59 | OperaDriverManager("0.2").install() 60 | 61 | assert "There is no such driver by url " \ 62 | "https://api.github.com/repos/operasoftware/operachromiumdriver/" \ 63 | "releases/tags/0.2" in ex.value.args[0] 64 | 65 | 66 | @pytest.mark.parametrize('path', ['.', None]) 67 | def test_opera_driver_manager_with_correct_version_and_token(path): 68 | driver_path = OperaDriverManager(version="v.2.45", cache_manager=DriverCacheManager(path)).install() 69 | assert os.path.exists(driver_path) 70 | 71 | 72 | @pytest.mark.parametrize('os_type', ['win32', 73 | 'win64', 74 | 'linux64', 75 | 'mac64']) 76 | def test_can_get_driver_from_cache(os_type): 77 | OperaDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 78 | driver_path = OperaDriverManager(os_system_manager=OperationSystemManager(os_type)).install() 79 | assert os.path.exists(driver_path) 80 | -------------------------------------------------------------------------------- /tests/test_silent_global_logs.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import pytest 5 | 6 | from webdriver_manager.chrome import ChromeDriverManager 7 | 8 | 9 | @pytest.fixture 10 | def log(): 11 | os.environ['WDM_LOG'] = str(logging.NOTSET) 12 | yield 13 | os.environ['WDM_LOG'] = str(logging.INFO) 14 | 15 | 16 | def test_chrome_manager_with_specific_version(log): 17 | bin = ChromeDriverManager("87.0.4280.88").install() 18 | assert os.path.exists(bin) 19 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from requests import Response, Request 3 | 4 | from webdriver_manager.core.http import HttpClient 5 | 6 | 7 | def test_validate_response_value_error_not_200_and_not_404(): 8 | resp = Response() 9 | resp.headers = {} 10 | resp.request = Request() 11 | resp.request.url = "https://example.com" 12 | resp.status_code = 301 13 | resp._content = b"abc" 14 | 15 | with pytest.raises(ValueError) as excinfo: 16 | HttpClient.validate_response(resp) 17 | 18 | assert str(excinfo.value) == "\n".join( 19 | [ 20 | "response body:", 21 | "abc", 22 | "request url:", 23 | "https://example.com", 24 | "response headers:", 25 | "{}", 26 | "" 27 | ] 28 | ) 29 | 30 | 31 | def test_validate_response_value_error_404(): 32 | resp = Response() 33 | resp.request = Request() 34 | resp.url = "https://example.com" 35 | resp.status_code = 404 36 | 37 | with pytest.raises(ValueError) as excinfo: 38 | HttpClient.validate_response(resp) 39 | 40 | expected_message = "There is no such driver by url https://example.com" 41 | assert str(excinfo.value) == expected_message 42 | -------------------------------------------------------------------------------- /tests_negative/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SergeyPirogov/webdriver_manager/1c59212c6dae5335d2aead14b119307084b44293/tests_negative/__init__.py -------------------------------------------------------------------------------- /tests_negative/test_browsers_not_installed.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from selenium import webdriver 5 | from selenium.common.exceptions import WebDriverException 6 | from selenium.webdriver.chrome.service import Service as ChromeService 7 | from selenium.webdriver.edge.service import Service as EdgeService 8 | from selenium.webdriver.firefox.service import Service as FFService 9 | 10 | from webdriver_manager.chrome import ChromeDriverManager 11 | from webdriver_manager.core.os_manager import OperationSystemManager, ChromeType, OSType 12 | from webdriver_manager.firefox import GeckoDriverManager 13 | from webdriver_manager.microsoft import EdgeChromiumDriverManager 14 | 15 | 16 | @pytest.mark.skip(reason="it is not stable on CI") 17 | def test_brave_not_installed(): 18 | binary_location = { 19 | OSType.LINUX: "/usr/bin/brave-browser", 20 | OSType.MAC: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", 21 | OSType.WIN: f"{os.getenv('LOCALAPPDATA')}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe", 22 | }[OperationSystemManager.get_os_name()] 23 | 24 | option = webdriver.ChromeOptions() 25 | option.binary_location = binary_location 26 | driver_path = ChromeDriverManager(chrome_type=ChromeType.BRAVE).install() 27 | with pytest.raises(WebDriverException): 28 | webdriver.Chrome(service=ChromeService(driver_path), options=option) 29 | 30 | 31 | @pytest.mark.skip(reason="it is not stable on CI") 32 | def test_chrome_not_installed(): 33 | driver_path = ChromeDriverManager().install() 34 | with pytest.raises(WebDriverException): 35 | webdriver.Chrome(service=ChromeService(driver_path)) 36 | 37 | 38 | @pytest.mark.skip(reason="it is not stable on CI") 39 | def test_firefox_not_installed(): 40 | driver_path = GeckoDriverManager().install() 41 | with pytest.raises(WebDriverException): 42 | webdriver.Firefox(service=FFService(driver_path)) 43 | 44 | 45 | @pytest.mark.skip(reason="it is not stable on CI") 46 | def test_msedge_not_installed(): 47 | driver_path = EdgeChromiumDriverManager().install() 48 | with pytest.raises(WebDriverException): 49 | webdriver.Edge(service=EdgeService(driver_path)) 50 | -------------------------------------------------------------------------------- /tests_xdist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SergeyPirogov/webdriver_manager/1c59212c6dae5335d2aead14b119307084b44293/tests_xdist/__init__.py -------------------------------------------------------------------------------- /tests_xdist/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture() 7 | def user_account(worker_id): 8 | """ use a different account in each xdist worker """ 9 | os.environ['WDM_LOCAL'] = '1' 10 | -------------------------------------------------------------------------------- /tests_xdist/test_cuncurent_1.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.service import Service 3 | 4 | from webdriver_manager.chrome import ChromeDriverManager 5 | 6 | 7 | def test_chrome_manager_with_selenium(): 8 | driver_path = ChromeDriverManager().install() 9 | driver = webdriver.Chrome(service=Service(driver_path)) 10 | driver.get("http://automation-remarks.com") 11 | driver.close() 12 | -------------------------------------------------------------------------------- /tests_xdist/test_cuncurent_2.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.service import Service 3 | 4 | from webdriver_manager.chrome import ChromeDriverManager 5 | 6 | 7 | def test_chrome_manager_with_selenium(): 8 | driver_path = ChromeDriverManager().install() 9 | driver = webdriver.Chrome(service=Service(driver_path)) 10 | driver.get("http://automation-remarks.com") 11 | driver.close() 12 | -------------------------------------------------------------------------------- /tests_xdist/test_cuncurent_3.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.service import Service 3 | 4 | from webdriver_manager.chrome import ChromeDriverManager 5 | 6 | 7 | def test_chrome_manager_with_selenium(): 8 | driver_path = ChromeDriverManager().install() 9 | driver = webdriver.Chrome(service=Service(driver_path)) 10 | driver.get("http://automation-remarks.com") 11 | driver.close() 12 | -------------------------------------------------------------------------------- /webdriver_manager/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "4.0.2" 2 | -------------------------------------------------------------------------------- /webdriver_manager/chrome.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from webdriver_manager.core.download_manager import DownloadManager 5 | from webdriver_manager.core.driver_cache import DriverCacheManager 6 | from webdriver_manager.core.manager import DriverManager 7 | from webdriver_manager.core.os_manager import OperationSystemManager, ChromeType 8 | from webdriver_manager.drivers.chrome import ChromeDriver 9 | 10 | 11 | class ChromeDriverManager(DriverManager): 12 | def __init__( 13 | self, 14 | driver_version: Optional[str] = None, 15 | name: str = "chromedriver", 16 | url: str = "https://storage.googleapis.com/chrome-for-testing-public/", 17 | latest_release_url: str = "https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_STABLE", 18 | chrome_type: str = ChromeType.GOOGLE, 19 | download_manager: Optional[DownloadManager] = None, 20 | cache_manager: Optional[DriverCacheManager] = None, 21 | os_system_manager: Optional[OperationSystemManager] = None 22 | ): 23 | super().__init__( 24 | download_manager=download_manager, 25 | cache_manager=cache_manager, 26 | os_system_manager=os_system_manager 27 | ) 28 | 29 | self.driver = ChromeDriver( 30 | name=name, 31 | driver_version=driver_version, 32 | url=url, 33 | latest_release_url=latest_release_url, 34 | chrome_type=chrome_type, 35 | http_client=self.http_client, 36 | os_system_manager=os_system_manager 37 | ) 38 | 39 | def install(self) -> str: 40 | driver_path = self._get_driver_binary_path(self.driver) 41 | os.chmod(driver_path, 0o755) 42 | return driver_path 43 | 44 | def get_os_type(self): 45 | os_type = super().get_os_type() 46 | if "win" in os_type: 47 | return "win32" 48 | 49 | if not self._os_system_manager.is_mac_os(os_type): 50 | return os_type 51 | 52 | if self._os_system_manager.is_arch(os_type): 53 | return "mac_arm64" 54 | 55 | return os_type 56 | -------------------------------------------------------------------------------- /webdriver_manager/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SergeyPirogov/webdriver_manager/1c59212c6dae5335d2aead14b119307084b44293/webdriver_manager/core/__init__.py -------------------------------------------------------------------------------- /webdriver_manager/core/archive.py: -------------------------------------------------------------------------------- 1 | import os 2 | import zipfile 3 | 4 | 5 | class LinuxZipFileWithPermissions(zipfile.ZipFile): 6 | """Class for extract files in linux with right permissions""" 7 | 8 | def extract(self, member, path=None, pwd=None): 9 | if not isinstance(member, zipfile.ZipInfo): 10 | member = self.getinfo(member) 11 | 12 | if path is None: 13 | path = os.getcwd() 14 | 15 | ret_val = self._extract_member(member, path, pwd) # noqa 16 | attr = member.external_attr >> 16 17 | os.chmod(ret_val, attr) 18 | return ret_val 19 | 20 | 21 | class Archive(object): 22 | def __init__(self, path: str): 23 | self.file_path = path 24 | -------------------------------------------------------------------------------- /webdriver_manager/core/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | 5 | def str2bool(value): 6 | return value.lower() in ['true', '1'] 7 | 8 | 9 | load_dotenv() 10 | 11 | 12 | def ssl_verify(): 13 | return str2bool(os.getenv("WDM_SSL_VERIFY", "true")) 14 | 15 | 16 | def gh_token(): 17 | return os.getenv("GH_TOKEN", None) 18 | 19 | 20 | def wdm_local(): 21 | return str2bool(os.getenv("WDM_LOCAL", "false")) 22 | 23 | 24 | def wdm_log_level(): 25 | default_level = 20 26 | try: 27 | return int(os.getenv("WDM_LOG", default_level)) 28 | except Exception: 29 | return default_level 30 | 31 | 32 | def wdm_progress_bar(): 33 | default_level = 1 34 | try: 35 | return int(os.getenv("WDM_PROGRESS_BAR", default_level)) 36 | except Exception: 37 | return default_level 38 | 39 | 40 | def get_xdist_worker_id(): 41 | return os.getenv("PYTEST_XDIST_WORKER", '') 42 | -------------------------------------------------------------------------------- /webdriver_manager/core/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | ROOT_FOLDER_NAME = ".wdm" 5 | DEFAULT_PROJECT_ROOT_CACHE_PATH = os.path.join(sys.path[0], ROOT_FOLDER_NAME) 6 | DEFAULT_USER_HOME_CACHE_PATH = os.path.join( 7 | os.path.expanduser("~"), ROOT_FOLDER_NAME) 8 | -------------------------------------------------------------------------------- /webdriver_manager/core/download_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | from abc import ABC 3 | 4 | from webdriver_manager.core.file_manager import File 5 | from webdriver_manager.core.http import WDMHttpClient 6 | from webdriver_manager.core.logger import log 7 | 8 | 9 | class DownloadManager(ABC): 10 | def __init__(self, http_client): 11 | self._http_client = http_client 12 | 13 | def download_file(self, url: str) -> File: 14 | raise NotImplementedError 15 | 16 | @property 17 | def http_client(self): 18 | return self._http_client 19 | 20 | 21 | class WDMDownloadManager(DownloadManager): 22 | def __init__(self, http_client=None): 23 | if http_client is None: 24 | http_client = WDMHttpClient() 25 | super().__init__(http_client) 26 | 27 | def download_file(self, url: str) -> File: 28 | log(f"About to download new driver from {url}") 29 | response = self._http_client.get(url) 30 | log(f"Driver downloading response is {response.status_code}") 31 | file_name = self.extract_filename_from_url(url) 32 | return File(response, file_name) 33 | 34 | @staticmethod 35 | def extract_filename_from_url(url): 36 | # Split the URL by '/' 37 | url_parts = url.split('/') 38 | # Get the last part of the URL, which should be the filename 39 | filename = url_parts[-1] 40 | # Decode the URL-encoded filename 41 | filename = os.path.basename(filename) 42 | return filename 43 | -------------------------------------------------------------------------------- /webdriver_manager/core/driver.py: -------------------------------------------------------------------------------- 1 | from webdriver_manager.core.logger import log 2 | from webdriver_manager.core.config import gh_token 3 | from webdriver_manager.core.os_manager import OperationSystemManager 4 | 5 | 6 | class Driver(object): 7 | def __init__( 8 | self, 9 | name, 10 | driver_version_to_download, 11 | url, 12 | latest_release_url, 13 | http_client, 14 | os_system_manager): 15 | self._name = name 16 | self._url = url 17 | self._latest_release_url = latest_release_url 18 | self._http_client = http_client 19 | self._browser_version = None 20 | self._driver_version_to_download = driver_version_to_download 21 | self._os_system_manager = os_system_manager 22 | if not self._os_system_manager: 23 | self._os_system_manager = OperationSystemManager() 24 | 25 | @property 26 | def auth_header(self): 27 | token = gh_token() 28 | if token: 29 | log("GH_TOKEN will be used to perform requests") 30 | return {"Authorization": f"token {token}"} 31 | return None 32 | 33 | def get_name(self): 34 | return self._name 35 | 36 | def get_driver_download_url(self, os_type): 37 | return f"{self._url}/{self.get_driver_version_to_download()}/{self._name}_{os_type}.zip" 38 | 39 | def get_driver_version_to_download(self): 40 | """ 41 | Downloads version from parameter if version not None or "latest". 42 | Downloads latest, if version is "latest" or browser could not been determined. 43 | Downloads determined browser version driver in all other ways as a bonus fallback for lazy users. 44 | """ 45 | if self._driver_version_to_download: 46 | return self._driver_version_to_download 47 | 48 | return self.get_latest_release_version() 49 | 50 | def get_latest_release_version(self): 51 | # type: () -> str 52 | raise NotImplementedError("Please implement this method") 53 | 54 | def get_browser_version_from_os(self): 55 | """ 56 | Use-cases: 57 | - for key in metadata; 58 | - for printing nice logs; 59 | - for fallback if version was not set at all. 60 | Note: the fallback may have collisions in user cases when previous browser was not uninstalled properly. 61 | """ 62 | if self._browser_version is None: 63 | self._browser_version = self._os_system_manager.get_browser_version_from_os(self.get_browser_type()) 64 | return self._browser_version 65 | 66 | def get_browser_type(self): 67 | raise NotImplementedError("Please implement this method") 68 | 69 | def get_binary_name(self, os_type): 70 | driver_name = self.get_name() 71 | driver_binary_name = ( 72 | "msedgedriver" if driver_name == "edgedriver" else driver_name 73 | ) 74 | driver_binary_name = ( 75 | f"{driver_binary_name}.exe" if "win" in os_type else driver_binary_name 76 | ) 77 | return driver_binary_name 78 | -------------------------------------------------------------------------------- /webdriver_manager/core/driver_cache.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import os 4 | 5 | from webdriver_manager.core.config import wdm_local, get_xdist_worker_id 6 | from webdriver_manager.core.constants import ( 7 | DEFAULT_PROJECT_ROOT_CACHE_PATH, 8 | DEFAULT_USER_HOME_CACHE_PATH, ROOT_FOLDER_NAME, 9 | ) 10 | from webdriver_manager.core.driver import Driver 11 | from webdriver_manager.core.file_manager import FileManager, File 12 | from webdriver_manager.core.logger import log 13 | from webdriver_manager.core.os_manager import OperationSystemManager 14 | from webdriver_manager.core.utils import get_date_diff 15 | 16 | 17 | class DriverCacheManager(object): 18 | def __init__(self, root_dir=None, valid_range=1, file_manager=None): 19 | self._root_dir = DEFAULT_USER_HOME_CACHE_PATH 20 | is_wdm_local = wdm_local() 21 | xdist_worker_id = get_xdist_worker_id() 22 | if xdist_worker_id: 23 | log(f"xdist worker is: {xdist_worker_id}") 24 | self._root_dir = os.path.join(self._root_dir, xdist_worker_id) 25 | 26 | if root_dir: 27 | self._root_dir = os.path.join(root_dir, ROOT_FOLDER_NAME, xdist_worker_id) 28 | 29 | if is_wdm_local: 30 | self._root_dir = os.path.join(DEFAULT_PROJECT_ROOT_CACHE_PATH, xdist_worker_id) 31 | 32 | self._drivers_root = "drivers" 33 | self._drivers_json_path = os.path.join(self._root_dir, "drivers.json") 34 | self._date_format = "%d/%m/%Y" 35 | self._drivers_directory = os.path.join(self._root_dir, self._drivers_root) 36 | self._cache_valid_days_range = valid_range 37 | self._cache_key_driver_version = None 38 | self._metadata_key = None 39 | self._driver_binary_path = None 40 | self._file_manager = file_manager 41 | self._os_system_manager = OperationSystemManager() 42 | if not self._file_manager: 43 | self._file_manager = FileManager(self._os_system_manager) 44 | 45 | def save_archive_file(self, file: File, path): 46 | return self._file_manager.save_archive_file(file, path) 47 | 48 | def unpack_archive(self, archive, path): 49 | return self._file_manager.unpack_archive(archive, path) 50 | 51 | def save_file_to_cache(self, driver: Driver, file: File): 52 | path = self.__get_path(driver) 53 | archive = self.save_archive_file(file, path) 54 | files = self.unpack_archive(archive, path) 55 | binary = self.__get_binary(files, driver.get_name()) 56 | binary_path = os.path.join(path, binary) 57 | self.__save_metadata(driver, binary_path) 58 | log(f"Driver has been saved in cache [{path}]") 59 | return binary_path 60 | 61 | def __get_binary(self, files, driver_name): 62 | if not files: 63 | raise Exception(f"Can't find binary for {driver_name} among {files}") 64 | 65 | if len(files) == 1: 66 | return files[0] 67 | 68 | for f in files: 69 | if 'LICENSE' in f: 70 | continue 71 | if 'THIRD_PARTY' in f: 72 | continue 73 | if driver_name in f: 74 | return f 75 | 76 | raise Exception(f"Can't find binary for {driver_name} among {files}") 77 | 78 | def __save_metadata(self, driver: Driver, binary_path, date=None): 79 | if date is None: 80 | date = datetime.date.today() 81 | 82 | metadata = self.load_metadata_content() 83 | key = self.__get_metadata_key(driver) 84 | data = { 85 | key: { 86 | "timestamp": date.strftime(self._date_format), 87 | "binary_path": binary_path, 88 | } 89 | } 90 | 91 | metadata.update(data) 92 | with open(self._drivers_json_path, "w+") as outfile: 93 | json.dump(metadata, outfile, indent=4) 94 | 95 | def get_os_type(self): 96 | return self._os_system_manager.get_os_type() 97 | 98 | def find_driver(self, driver: Driver): 99 | """Find driver by '{os_type}_{driver_name}_{driver_version}_{browser_version}'.""" 100 | os_type = self.get_os_type() 101 | driver_name = driver.get_name() 102 | browser_type = driver.get_browser_type() 103 | browser_version = self._os_system_manager.get_browser_version_from_os(browser_type) 104 | if not browser_version: 105 | return None 106 | 107 | driver_version = self.get_cache_key_driver_version(driver) 108 | metadata = self.load_metadata_content() 109 | 110 | key = self.__get_metadata_key(driver) 111 | if key not in metadata: 112 | log(f'There is no [{os_type}] {driver_name} "{driver_version}" for browser {browser_type} ' 113 | f'"{browser_version}" in cache') 114 | return None 115 | 116 | driver_info = metadata[key] 117 | path = driver_info["binary_path"] 118 | if not os.path.exists(path): 119 | return None 120 | 121 | if not self.__is_valid(driver_info): 122 | return None 123 | 124 | path = driver_info["binary_path"] 125 | log(f"Driver [{path}] found in cache") 126 | return path 127 | 128 | def __is_valid(self, driver_info): 129 | dates_diff = get_date_diff( 130 | driver_info["timestamp"], datetime.date.today(), self._date_format 131 | ) 132 | return dates_diff < self._cache_valid_days_range 133 | 134 | def load_metadata_content(self): 135 | if os.path.exists(self._drivers_json_path): 136 | with open(self._drivers_json_path, "r") as outfile: 137 | return json.load(outfile) 138 | return {} 139 | 140 | def __get_metadata_key(self, driver: Driver): 141 | if self._metadata_key: 142 | return self._metadata_key 143 | 144 | driver_version = self.get_cache_key_driver_version(driver) 145 | browser_version = driver.get_browser_version_from_os() 146 | browser_version = browser_version if browser_version else "" 147 | self._metadata_key = f"{self.get_os_type()}_{driver.get_name()}_{driver_version}" \ 148 | f"_for_{browser_version}" 149 | return self._metadata_key 150 | 151 | def get_cache_key_driver_version(self, driver: Driver): 152 | if self._cache_key_driver_version: 153 | return self._cache_key_driver_version 154 | return driver.get_driver_version_to_download() 155 | 156 | def __get_path(self, driver: Driver): 157 | if self._driver_binary_path is None: 158 | self._driver_binary_path = os.path.join( 159 | self._drivers_directory, 160 | driver.get_name(), 161 | self.get_os_type(), 162 | driver.get_driver_version_to_download(), 163 | ) 164 | return self._driver_binary_path 165 | -------------------------------------------------------------------------------- /webdriver_manager/core/file_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import tarfile 4 | import zipfile 5 | 6 | from webdriver_manager.core.archive import Archive, LinuxZipFileWithPermissions 7 | from webdriver_manager.core.os_manager import OperationSystemManager 8 | 9 | 10 | class File(object): 11 | def __init__(self, stream, file_name): 12 | self.content = stream.content 13 | self.__stream = stream 14 | self.file_name = file_name 15 | self.__temp_name = "driver" 16 | self.__regex_filename = r"""filename.+"(.+)"|filename.+''(.+)|filename=([\w.-]+)""" 17 | 18 | @property 19 | def filename(self) -> str: 20 | if self.file_name: 21 | return self.file_name 22 | try: 23 | content = self.__stream.headers["content-disposition"] 24 | 25 | content_disposition_list = re.split(";", content) 26 | filenames = [re.findall(self.__regex_filename, element) for element in content_disposition_list] 27 | filename = next(filter(None, next(filter(None, next(filter(None, filenames)))))) 28 | except KeyError: 29 | filename = f"{self.__temp_name}.zip" 30 | except (IndexError, StopIteration): 31 | filename = f"{self.__temp_name}.exe" 32 | 33 | if '"' in filename: 34 | filename = filename.replace('"', "") 35 | 36 | return filename 37 | 38 | 39 | class FileManager(object): 40 | 41 | def __init__(self, os_system_manager: OperationSystemManager): 42 | self._os_system_manager = os_system_manager 43 | 44 | def save_archive_file(self, file: File, directory: str): 45 | os.makedirs(directory, exist_ok=True) 46 | 47 | archive_path = f"{directory}{os.sep}{file.filename}" 48 | with open(archive_path, "wb") as code: 49 | code.write(file.content) 50 | if not os.path.exists(archive_path): 51 | raise FileExistsError(f"No file has been saved on such path {archive_path}") 52 | return Archive(archive_path) 53 | 54 | def unpack_archive(self, archive_file: Archive, target_dir): 55 | file_path = archive_file.file_path 56 | if file_path.endswith(".zip"): 57 | return self.__extract_zip(archive_file, target_dir) 58 | elif file_path.endswith(".tar.gz"): 59 | return self.__extract_tar_file(archive_file, target_dir) 60 | 61 | def __extract_zip(self, archive_file, to_directory): 62 | zip_class = (LinuxZipFileWithPermissions if self._os_system_manager.get_os_name() == "linux" else zipfile.ZipFile) 63 | archive = zip_class(archive_file.file_path) 64 | try: 65 | archive.extractall(to_directory) 66 | except Exception as e: 67 | if e.args[0] not in [26, 13] and e.args[1] not in [ 68 | "Text file busy", 69 | "Permission denied", 70 | ]: 71 | raise e 72 | file_names = [] 73 | for n in archive.namelist(): 74 | if "/" not in n: 75 | file_names.append(n) 76 | else: 77 | file_path, file_name = n.split("/") 78 | full_file_path = os.path.join(to_directory, file_path) 79 | source = os.path.join(full_file_path, file_name) 80 | destination = os.path.join(to_directory, file_name) 81 | os.replace(source, destination) 82 | file_names.append(file_name) 83 | return sorted(file_names, key=lambda x: x.lower()) 84 | return archive.namelist() 85 | 86 | def __extract_tar_file(self, archive_file, to_directory): 87 | try: 88 | tar = tarfile.open(archive_file.file_path, mode="r:gz") 89 | except tarfile.ReadError: 90 | tar = tarfile.open(archive_file.file_path, mode="r:bz2") 91 | members = tar.getmembers() 92 | tar.extractall(to_directory) 93 | tar.close() 94 | return [x.name for x in members] 95 | -------------------------------------------------------------------------------- /webdriver_manager/core/http.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests import Response, exceptions 3 | 4 | from webdriver_manager.core.config import ssl_verify 5 | 6 | 7 | class HttpClient: 8 | def get(self, url, params=None, **kwargs) -> Response: 9 | raise NotImplementedError 10 | 11 | @staticmethod 12 | def validate_response(resp: requests.Response): 13 | status_code = resp.status_code 14 | if status_code == 404: 15 | raise ValueError(f"There is no such driver by url {resp.url}") 16 | elif status_code == 401: 17 | raise ValueError(f"API Rate limit exceeded. You have to add GH_TOKEN!!!") 18 | elif resp.status_code != 200: 19 | raise ValueError( 20 | f"response body:\n{resp.text}\n" 21 | f"request url:\n{resp.request.url}\n" 22 | f"response headers:\n{dict(resp.headers)}\n" 23 | ) 24 | 25 | 26 | class WDMHttpClient(HttpClient): 27 | def __init__(self): 28 | self._ssl_verify = ssl_verify() 29 | 30 | def get(self, url, **kwargs) -> Response: 31 | try: 32 | resp = requests.get( 33 | url=url, verify=self._ssl_verify, stream=True, **kwargs) 34 | except exceptions.ConnectionError: 35 | raise exceptions.ConnectionError(f"Could not reach host. Are you offline?") 36 | self.validate_response(resp) 37 | return resp 38 | 39 | -------------------------------------------------------------------------------- /webdriver_manager/core/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from webdriver_manager.core.config import wdm_log_level 4 | 5 | __logger = logging.getLogger("WDM") 6 | __logger.addHandler(logging.NullHandler()) 7 | 8 | 9 | def log(text): 10 | """Emitting the log message.""" 11 | __logger.log(wdm_log_level(), text) 12 | 13 | 14 | def set_logger(logger): 15 | """ 16 | Set the global logger. 17 | 18 | Parameters 19 | ---------- 20 | logger : logging.Logger 21 | The custom logger to use. 22 | 23 | Returns None 24 | """ 25 | 26 | # Check if the logger is a valid logger 27 | if not isinstance(logger, logging.Logger): 28 | raise ValueError("The logger must be an instance of logging.Logger") 29 | 30 | # Bind the logger input to the global logger 31 | global __logger 32 | __logger = logger 33 | -------------------------------------------------------------------------------- /webdriver_manager/core/manager.py: -------------------------------------------------------------------------------- 1 | from webdriver_manager.core.download_manager import WDMDownloadManager 2 | from webdriver_manager.core.driver_cache import DriverCacheManager 3 | from webdriver_manager.core.logger import log 4 | from webdriver_manager.core.os_manager import OperationSystemManager 5 | 6 | 7 | class DriverManager(object): 8 | def __init__( 9 | self, 10 | download_manager=None, 11 | cache_manager=None, 12 | os_system_manager=None 13 | ): 14 | self._cache_manager = cache_manager 15 | if not self._cache_manager: 16 | self._cache_manager = DriverCacheManager() 17 | 18 | self._download_manager = download_manager 19 | if self._download_manager is None: 20 | self._download_manager = WDMDownloadManager() 21 | 22 | self._os_system_manager = os_system_manager 23 | if not self._os_system_manager: 24 | self._os_system_manager = OperationSystemManager() 25 | log("====== WebDriver manager ======") 26 | 27 | @property 28 | def http_client(self): 29 | return self._download_manager.http_client 30 | 31 | def install(self) -> str: 32 | raise NotImplementedError("Please Implement this method") 33 | 34 | def _get_driver_binary_path(self, driver): 35 | binary_path = self._cache_manager.find_driver(driver) 36 | if binary_path: 37 | return binary_path 38 | 39 | os_type = self.get_os_type() 40 | file = self._download_manager.download_file(driver.get_driver_download_url(os_type)) 41 | binary_path = self._cache_manager.save_file_to_cache(driver, file) 42 | return binary_path 43 | 44 | def get_os_type(self): 45 | return self._os_system_manager.get_os_type() 46 | -------------------------------------------------------------------------------- /webdriver_manager/core/os_manager.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import sys 3 | 4 | from webdriver_manager.core.utils import linux_browser_apps_to_cmd, windows_browser_apps_to_cmd, \ 5 | read_version_from_cmd 6 | 7 | 8 | class ChromeType(object): 9 | GOOGLE = "google-chrome" 10 | CHROMIUM = "chromium" 11 | BRAVE = "brave-browser" 12 | MSEDGE = "edge" 13 | 14 | 15 | class OSType(object): 16 | LINUX = "linux" 17 | MAC = "mac" 18 | WIN = "win" 19 | 20 | 21 | PATTERN = { 22 | ChromeType.CHROMIUM: r"\d+\.\d+\.\d+", 23 | ChromeType.GOOGLE: r"\d+\.\d+\.\d+", 24 | ChromeType.MSEDGE: r"\d+\.\d+\.\d+", 25 | "brave-browser": r"\d+\.\d+\.\d+(\.\d+)?", 26 | "firefox": r"(\d+.\d+)", 27 | } 28 | 29 | 30 | class OperationSystemManager(object): 31 | 32 | def __init__(self, os_type=None): 33 | self._os_type = os_type 34 | 35 | @staticmethod 36 | def get_os_name(): 37 | pl = sys.platform 38 | if pl == "linux" or pl == "linux2": 39 | return OSType.LINUX 40 | elif pl == "darwin": 41 | return OSType.MAC 42 | elif pl == "win32" or pl == "cygwin": 43 | return OSType.WIN 44 | 45 | @staticmethod 46 | def get_os_architecture(): 47 | if platform.machine().endswith("64"): 48 | return 64 49 | else: 50 | return 32 51 | 52 | def get_os_type(self): 53 | if self._os_type: 54 | return self._os_type 55 | return f"{self.get_os_name()}{self.get_os_architecture()}" 56 | 57 | @staticmethod 58 | def is_arch(os_sys_type): 59 | if '_m1' in os_sys_type: 60 | return True 61 | return platform.processor() != 'i386' 62 | 63 | @staticmethod 64 | def is_mac_os(os_sys_type): 65 | return OSType.MAC in os_sys_type 66 | 67 | def get_browser_version_from_os(self, browser_type=None): 68 | """Return installed browser version.""" 69 | cmd_mapping = { 70 | ChromeType.GOOGLE: { 71 | OSType.LINUX: linux_browser_apps_to_cmd( 72 | "google-chrome", 73 | "google-chrome-stable", 74 | "google-chrome-beta", 75 | "google-chrome-dev", 76 | ), 77 | OSType.MAC: r"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version", 78 | OSType.WIN: windows_browser_apps_to_cmd( 79 | r'(Get-Item -Path "$env:PROGRAMFILES\Google\Chrome\Application\chrome.exe").VersionInfo.FileVersion', 80 | r'(Get-Item -Path "$env:PROGRAMFILES (x86)\Google\Chrome\Application\chrome.exe").VersionInfo.FileVersion', 81 | r'(Get-Item -Path "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe").VersionInfo.FileVersion', 82 | r'(Get-ItemProperty -Path Registry::"HKCU\SOFTWARE\Google\Chrome\BLBeacon").version', 83 | r'(Get-ItemProperty -Path Registry::"HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Google Chrome").version', 84 | ), 85 | }, 86 | ChromeType.CHROMIUM: { 87 | OSType.LINUX: linux_browser_apps_to_cmd("chromium", "chromium-browser"), 88 | OSType.MAC: r"/Applications/Chromium.app/Contents/MacOS/Chromium --version", 89 | OSType.WIN: windows_browser_apps_to_cmd( 90 | r'(Get-Item -Path "$env:PROGRAMFILES\Chromium\Application\chrome.exe").VersionInfo.FileVersion', 91 | r'(Get-Item -Path "$env:PROGRAMFILES (x86)\Chromium\Application\chrome.exe").VersionInfo.FileVersion', 92 | r'(Get-Item -Path "$env:LOCALAPPDATA\Chromium\Application\chrome.exe").VersionInfo.FileVersion', 93 | r'(Get-ItemProperty -Path Registry::"HKCU\SOFTWARE\Chromium\BLBeacon").version', 94 | r'(Get-ItemProperty -Path Registry::"HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Chromium").version', 95 | ), 96 | }, 97 | ChromeType.BRAVE: { 98 | OSType.LINUX: linux_browser_apps_to_cmd( 99 | "brave-browser", "brave-browser-beta", "brave-browser-nightly" 100 | ), 101 | OSType.MAC: r"/Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser --version", 102 | OSType.WIN: windows_browser_apps_to_cmd( 103 | r'(Get-Item -Path "$env:PROGRAMFILES\BraveSoftware\Brave-Browser\Application\brave.exe").VersionInfo.FileVersion', 104 | r'(Get-Item -Path "$env:PROGRAMFILES (x86)\BraveSoftware\Brave-Browser\Application\brave.exe").VersionInfo.FileVersion', 105 | r'(Get-Item -Path "$env:LOCALAPPDATA\BraveSoftware\Brave-Browser\Application\brave.exe").VersionInfo.FileVersion', 106 | r'(Get-ItemProperty -Path Registry::"HKCU\SOFTWARE\BraveSoftware\Brave-Browser\BLBeacon").version', 107 | r'(Get-ItemProperty -Path Registry::"HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\BraveSoftware Brave-Browser").version', 108 | ), 109 | }, 110 | ChromeType.MSEDGE: { 111 | OSType.LINUX: linux_browser_apps_to_cmd( 112 | "microsoft-edge", 113 | "microsoft-edge-stable", 114 | "microsoft-edge-beta", 115 | "microsoft-edge-dev", 116 | ), 117 | OSType.MAC: r"/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge --version", 118 | OSType.WIN: windows_browser_apps_to_cmd( 119 | # stable edge 120 | r'(Get-Item -Path "$env:PROGRAMFILES\Microsoft\Edge\Application\msedge.exe").VersionInfo.FileVersion', 121 | r'(Get-Item -Path "$env:PROGRAMFILES (x86)\Microsoft\Edge\Application\msedge.exe").VersionInfo.FileVersion', 122 | r'(Get-ItemProperty -Path Registry::"HKCU\SOFTWARE\Microsoft\Edge\BLBeacon").version', 123 | r'(Get-ItemProperty -Path Registry::"HKLM\SOFTWARE\Microsoft\EdgeUpdate\Clients\{56EB18F8-8008-4CBD-B6D2-8C97FE7E9062}").pv', 124 | # beta edge 125 | r'(Get-Item -Path "$env:LOCALAPPDATA\Microsoft\Edge Beta\Application\msedge.exe").VersionInfo.FileVersion', 126 | r'(Get-Item -Path "$env:PROGRAMFILES\Microsoft\Edge Beta\Application\msedge.exe").VersionInfo.FileVersion', 127 | r'(Get-Item -Path "$env:PROGRAMFILES (x86)\Microsoft\Edge Beta\Application\msedge.exe").VersionInfo.FileVersion', 128 | r'(Get-ItemProperty -Path Registry::"HKCU\SOFTWARE\Microsoft\Edge Beta\BLBeacon").version', 129 | # dev edge 130 | r'(Get-Item -Path "$env:LOCALAPPDATA\Microsoft\Edge Dev\Application\msedge.exe").VersionInfo.FileVersion', 131 | r'(Get-Item -Path "$env:PROGRAMFILES\Microsoft\Edge Dev\Application\msedge.exe").VersionInfo.FileVersion', 132 | r'(Get-Item -Path "$env:PROGRAMFILES (x86)\Microsoft\Edge Dev\Application\msedge.exe").VersionInfo.FileVersion', 133 | r'(Get-ItemProperty -Path Registry::"HKCU\SOFTWARE\Microsoft\Edge Dev\BLBeacon").version', 134 | # canary edge 135 | r'(Get-Item -Path "$env:LOCALAPPDATA\Microsoft\Edge SxS\Application\msedge.exe").VersionInfo.FileVersion', 136 | r'(Get-ItemProperty -Path Registry::"HKCU\SOFTWARE\Microsoft\Edge SxS\BLBeacon").version', 137 | # highest edge 138 | r"(Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe').'(Default)').VersionInfo.ProductVersion", 139 | r"[System.Diagnostics.FileVersionInfo]::GetVersionInfo((Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe').'(Default)').ProductVersion", 140 | r"Get-AppxPackage -Name *MicrosoftEdge.* | Foreach Version", 141 | r'(Get-ItemProperty -Path Registry::"HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge").version', 142 | ), 143 | }, 144 | "firefox": { 145 | OSType.LINUX: linux_browser_apps_to_cmd("firefox"), 146 | OSType.MAC: r"/Applications/Firefox.app/Contents/MacOS/firefox --version", 147 | OSType.WIN: windows_browser_apps_to_cmd( 148 | r'(Get-Item -Path "$env:PROGRAMFILES\Mozilla Firefox\firefox.exe").VersionInfo.FileVersion', 149 | r'(Get-Item -Path "$env:PROGRAMFILES (x86)\Mozilla Firefox\firefox.exe").VersionInfo.FileVersion', 150 | r"(Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\firefox.exe').'(Default)').VersionInfo.ProductVersion", 151 | r'(Get-ItemProperty -Path Registry::"HKLM\SOFTWARE\Mozilla\Mozilla Firefox").CurrentVersion', 152 | ), 153 | }, 154 | } 155 | 156 | try: 157 | cmd_mapping = cmd_mapping[browser_type][OperationSystemManager.get_os_name()] 158 | pattern = PATTERN[browser_type] 159 | version = read_version_from_cmd(cmd_mapping, pattern) 160 | return version 161 | except Exception: 162 | return None 163 | # raise Exception("Can not get browser version from OS") 164 | -------------------------------------------------------------------------------- /webdriver_manager/core/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import re 4 | import subprocess 5 | 6 | 7 | def get_date_diff(date1, date2, date_format): 8 | a = datetime.datetime.strptime(date1, date_format) 9 | b = datetime.datetime.strptime( 10 | str(date2.strftime(date_format)), date_format) 11 | 12 | return (b - a).days 13 | 14 | 15 | def linux_browser_apps_to_cmd(*apps: str) -> str: 16 | """Create 'browser --version' command from browser app names. 17 | 18 | Result command example: 19 | chromium --version || chromium-browser --version 20 | """ 21 | ignore_errors_cmd_part = " 2>/dev/null" if os.getenv( 22 | "WDM_LOG_LEVEL") == "0" else "" 23 | return " || ".join(f"{i} --version{ignore_errors_cmd_part}" for i in apps) 24 | 25 | 26 | def windows_browser_apps_to_cmd(*apps: str) -> str: 27 | """Create analogue of browser --version command for windows.""" 28 | powershell = determine_powershell() 29 | 30 | first_hit_template = """$tmp = {expression}; if ($tmp) {{echo $tmp; Exit;}};""" 31 | script = "$ErrorActionPreference='silentlycontinue'; " + " ".join( 32 | first_hit_template.format(expression=e) for e in apps 33 | ) 34 | 35 | return f'{powershell} -NoProfile "{script}"' 36 | 37 | 38 | def read_version_from_cmd(cmd, pattern): 39 | with subprocess.Popen( 40 | cmd, 41 | stdout=subprocess.PIPE, 42 | stdin=subprocess.DEVNULL, 43 | stderr=subprocess.DEVNULL, 44 | shell=True, 45 | ) as stream: 46 | stdout = stream.communicate()[0].decode() 47 | version = re.search(pattern, stdout) 48 | version = version.group(0) if version else None 49 | return version 50 | 51 | 52 | def determine_powershell(): 53 | """Returns "True" if runs in Powershell and "False" if another console.""" 54 | cmd = "(dir 2>&1 *`|echo CMD);&<# rem #>echo powershell" 55 | with subprocess.Popen( 56 | cmd, 57 | stdout=subprocess.PIPE, 58 | stderr=subprocess.DEVNULL, 59 | stdin=subprocess.DEVNULL, 60 | shell=True, 61 | ) as stream: 62 | stdout = stream.communicate()[0].decode() 63 | return "" if stdout == "powershell" else "powershell" 64 | -------------------------------------------------------------------------------- /webdriver_manager/drivers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SergeyPirogov/webdriver_manager/1c59212c6dae5335d2aead14b119307084b44293/webdriver_manager/drivers/__init__.py -------------------------------------------------------------------------------- /webdriver_manager/drivers/chrome.py: -------------------------------------------------------------------------------- 1 | from packaging import version 2 | 3 | from webdriver_manager.core.driver import Driver 4 | from webdriver_manager.core.logger import log 5 | from webdriver_manager.core.os_manager import ChromeType 6 | import json 7 | 8 | 9 | class ChromeDriver(Driver): 10 | 11 | def __init__( 12 | self, 13 | name, 14 | driver_version, 15 | url, 16 | latest_release_url, 17 | http_client, 18 | os_system_manager, 19 | chrome_type=ChromeType.GOOGLE 20 | ): 21 | super(ChromeDriver, self).__init__( 22 | name, 23 | driver_version, 24 | url, 25 | latest_release_url, 26 | http_client, 27 | os_system_manager 28 | ) 29 | self._browser_type = chrome_type 30 | 31 | def get_driver_download_url(self, os_type): 32 | driver_version_to_download = self.get_driver_version_to_download() 33 | # For Mac ARM CPUs after version 106.0.5249.61 the format of OS type changed 34 | # to more unified "mac_arm64". For newer versions, it'll be "mac_arm64" 35 | # by default, for lower versions we replace "mac_arm64" to old format - "mac64_m1". 36 | if version.parse(driver_version_to_download) < version.parse("106.0.5249.61"): 37 | os_type = os_type.replace("mac_arm64", "mac64_m1") 38 | 39 | if version.parse(driver_version_to_download) >= version.parse("115"): 40 | if os_type == "mac64": 41 | os_type = "mac-x64" 42 | if os_type in ["mac_64", "mac64_m1", "mac_arm64"]: 43 | os_type = "mac-arm64" 44 | 45 | modern_version_url = self.get_url_for_version_and_platform(driver_version_to_download, os_type) 46 | log(f"Modern chrome version {modern_version_url}") 47 | return modern_version_url 48 | 49 | return f"{self._url}/{driver_version_to_download}/{self.get_name()}_{os_type}.zip" 50 | 51 | def get_browser_type(self): 52 | return self._browser_type 53 | 54 | def get_latest_release_version(self): 55 | determined_browser_version = self.get_browser_version_from_os() 56 | log(f"Get LATEST {self._name} version for {self._browser_type}") 57 | if determined_browser_version is not None and version.parse(determined_browser_version) >= version.parse("115"): 58 | url = "https://googlechromelabs.github.io/chrome-for-testing/latest-patch-versions-per-build.json" 59 | response = self._http_client.get(url) 60 | response_dict = json.loads(response.text) 61 | determined_browser_version = response_dict.get("builds").get(determined_browser_version).get("version") 62 | return determined_browser_version 63 | elif determined_browser_version is not None: 64 | # Remove the build version (the last segment) from determined_browser_version for version < 113 65 | determined_browser_version = ".".join(determined_browser_version.split(".")[:3]) 66 | latest_release_url = f"{self._latest_release_url}_{determined_browser_version}" 67 | else: 68 | latest_release_url = self._latest_release_url 69 | 70 | resp = self._http_client.get(url=latest_release_url) 71 | return resp.text.rstrip() 72 | 73 | def get_url_for_version_and_platform(self, browser_version, platform): 74 | url = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json" 75 | response = self._http_client.get(url) 76 | data = response.json() 77 | versions = data["versions"] 78 | 79 | if version.parse(browser_version) >= version.parse("115"): 80 | short_version = ".".join(browser_version.split(".")[:3]) 81 | compatible_versions = [v for v in versions if short_version in v["version"]] 82 | if compatible_versions: 83 | latest_version = compatible_versions[-1] 84 | log(f"WebDriver version {latest_version['version']} selected") 85 | downloads = latest_version["downloads"]["chromedriver"] 86 | for d in downloads: 87 | if d["platform"] == platform: 88 | return d["url"] 89 | else: 90 | for v in versions: 91 | if v["version"] == browser_version: 92 | downloads = v["downloads"]["chromedriver"] 93 | for d in downloads: 94 | if d["platform"] == platform: 95 | return d["url"] 96 | 97 | raise Exception(f"No such driver version {browser_version} for {platform}") 98 | -------------------------------------------------------------------------------- /webdriver_manager/drivers/edge.py: -------------------------------------------------------------------------------- 1 | from webdriver_manager.core.driver import Driver 2 | from webdriver_manager.core.logger import log 3 | from webdriver_manager.core.os_manager import OSType, ChromeType 4 | 5 | 6 | class EdgeChromiumDriver(Driver): 7 | 8 | def __init__( 9 | self, 10 | name, 11 | driver_version, 12 | url, 13 | latest_release_url, 14 | http_client, 15 | os_system_manager 16 | ): 17 | super(EdgeChromiumDriver, self).__init__( 18 | name, 19 | driver_version, 20 | url, 21 | latest_release_url, 22 | http_client, 23 | os_system_manager 24 | ) 25 | 26 | def get_stable_release_version(self): 27 | """Stable driver version when browser version was not determined.""" 28 | stable_url = self._latest_release_url.replace("LATEST_RELEASE", "LATEST_STABLE") 29 | resp = self._http_client.get(url=stable_url) 30 | return resp.text.rstrip() 31 | 32 | def get_latest_release_version(self) -> str: 33 | determined_browser_version = self.get_browser_version_from_os() 34 | log(f"Get LATEST {self._name} version for Edge {determined_browser_version}") 35 | 36 | edge_driver_version_to_download = ( 37 | self.get_stable_release_version() 38 | if (determined_browser_version is None) 39 | else determined_browser_version 40 | ) 41 | major_edge_version = edge_driver_version_to_download.split(".")[0] 42 | os_type = self._os_system_manager.get_os_type() 43 | latest_release_url = { 44 | OSType.WIN 45 | in os_type: f"{self._latest_release_url}_{major_edge_version}_WINDOWS", 46 | OSType.MAC 47 | in os_type: f"{self._latest_release_url}_{major_edge_version}_MACOS", 48 | OSType.LINUX 49 | in os_type: f"{self._latest_release_url}_{major_edge_version}_LINUX", 50 | }[True] 51 | resp = self._http_client.get(url=latest_release_url) 52 | return resp.text.rstrip() 53 | 54 | def get_browser_type(self): 55 | return ChromeType.MSEDGE 56 | -------------------------------------------------------------------------------- /webdriver_manager/drivers/firefox.py: -------------------------------------------------------------------------------- 1 | from webdriver_manager.core.driver import Driver 2 | from webdriver_manager.core.logger import log 3 | 4 | 5 | class GeckoDriver(Driver): 6 | def __init__( 7 | self, 8 | name, 9 | driver_version, 10 | url, 11 | latest_release_url, 12 | mozila_release_tag, 13 | http_client, 14 | os_system_manager 15 | ): 16 | super(GeckoDriver, self).__init__( 17 | name, 18 | driver_version, 19 | url, 20 | latest_release_url, 21 | http_client, 22 | os_system_manager, 23 | ) 24 | self._mozila_release_tag = mozila_release_tag 25 | 26 | def get_latest_release_version(self) -> str: 27 | determined_browser_version = self.get_browser_version_from_os() 28 | log(f"Get LATEST {self._name} version for {determined_browser_version} firefox") 29 | resp = self._http_client.get( 30 | url=self.latest_release_url, 31 | headers=self.auth_header 32 | ) 33 | return resp.json()["tag_name"] 34 | 35 | def get_driver_download_url(self, os_type): 36 | """Like https://github.com/mozilla/geckodriver/releases/download/v0.11.1/geckodriver-v0.11.1-linux64.tar.gz""" 37 | driver_version_to_download = self.get_driver_version_to_download() 38 | log(f"Getting latest mozilla release info for {driver_version_to_download}") 39 | resp = self._http_client.get( 40 | url=self.tagged_release_url(driver_version_to_download), 41 | headers=self.auth_header 42 | ) 43 | assets = resp.json()["assets"] 44 | name = f"{self.get_name()}-{driver_version_to_download}-{os_type}." 45 | output_dict = [ 46 | asset for asset in assets if asset["name"].startswith(name)] 47 | return output_dict[0]["browser_download_url"] 48 | 49 | @property 50 | def latest_release_url(self): 51 | return self._latest_release_url 52 | 53 | def tagged_release_url(self, version): 54 | return self._mozila_release_tag.format(version) 55 | 56 | def get_browser_type(self): 57 | return "firefox" 58 | -------------------------------------------------------------------------------- /webdriver_manager/drivers/ie.py: -------------------------------------------------------------------------------- 1 | from webdriver_manager.core.driver import Driver 2 | from webdriver_manager.core.logger import log 3 | 4 | 5 | class IEDriver(Driver): 6 | 7 | def __init__( 8 | self, 9 | name, 10 | driver_version, 11 | url, 12 | latest_release_url, 13 | ie_release_tag, 14 | http_client, 15 | os_system_manager 16 | ): 17 | super(IEDriver, self).__init__( 18 | name, 19 | driver_version, 20 | url, 21 | latest_release_url, 22 | http_client, 23 | os_system_manager 24 | ) 25 | self._ie_release_tag = ie_release_tag 26 | # todo: for 'browser_version' implement installed IE version detection 27 | # like chrome or firefox 28 | 29 | def get_latest_release_version(self) -> str: 30 | log(f"Get LATEST driver version for Internet Explorer") 31 | resp = self._http_client.get( 32 | url=self.latest_release_url, 33 | headers=self.auth_header 34 | ) 35 | 36 | releases = resp.json() 37 | release = next( 38 | release 39 | for release in releases 40 | for asset in release["assets"] 41 | if asset["name"].startswith(self.get_name()) 42 | ) 43 | return release["tag_name"].replace("selenium-", "") 44 | 45 | def get_driver_download_url(self, os_type): 46 | """Like https://github.com/seleniumhq/selenium/releases/download/3.141.59/IEDriverServer_Win32_3.141.59.zip""" 47 | driver_version_to_download = self.get_driver_version_to_download() 48 | log(f"Getting latest ie release info for {driver_version_to_download}") 49 | resp = self._http_client.get( 50 | url=self.tagged_release_url(driver_version_to_download), 51 | headers=self.auth_header 52 | ) 53 | 54 | assets = resp.json()["assets"] 55 | 56 | name = f"{self._name}_{os_type}_{driver_version_to_download}" + "." 57 | output_dict = [ 58 | asset for asset in assets if asset["name"].startswith(name)] 59 | return output_dict[0]["browser_download_url"] 60 | 61 | @property 62 | def latest_release_url(self): 63 | return self._latest_release_url 64 | 65 | def tagged_release_url(self, version): 66 | version = self.__get_divided_version(version) 67 | return self._ie_release_tag.format(version) 68 | 69 | def __get_divided_version(self, version): 70 | divided_version = version.split(".") 71 | if len(divided_version) == 2: 72 | return f"{version}.0" 73 | elif len(divided_version) == 3: 74 | return version 75 | else: 76 | raise ValueError( 77 | "Version must consist of major, minor and/or patch, " 78 | "but given was: '{version}'".format(version=version) 79 | ) 80 | 81 | def get_browser_type(self): 82 | return "msie" 83 | -------------------------------------------------------------------------------- /webdriver_manager/drivers/opera.py: -------------------------------------------------------------------------------- 1 | from webdriver_manager.core.driver import Driver 2 | from webdriver_manager.core.logger import log 3 | 4 | 5 | class OperaDriver(Driver): 6 | def __init__( 7 | self, 8 | name, 9 | driver_version, 10 | url, 11 | latest_release_url, 12 | opera_release_tag, 13 | http_client, 14 | os_system_manager 15 | ): 16 | super(OperaDriver, self).__init__( 17 | name, 18 | driver_version, 19 | url, 20 | latest_release_url, 21 | http_client, 22 | os_system_manager 23 | ) 24 | self.opera_release_tag = opera_release_tag 25 | 26 | def get_latest_release_version(self) -> str: 27 | resp = self._http_client.get( 28 | url=self.latest_release_url, 29 | headers=self.auth_header 30 | ) 31 | return resp.json()["tag_name"] 32 | 33 | def get_driver_download_url(self, os_type) -> str: 34 | """Like https://github.com/operasoftware/operachromiumdriver/releases/download/v.2.45/operadriver_linux64.zip""" 35 | driver_version_to_download = self.get_driver_version_to_download() 36 | log(f"Getting latest opera release info for {driver_version_to_download}") 37 | resp = self._http_client.get( 38 | url=self.tagged_release_url(driver_version_to_download), 39 | headers=self.auth_header 40 | ) 41 | assets = resp.json()["assets"] 42 | name = "{0}_{1}".format(self.get_name(), os_type) 43 | output_dict = [ 44 | asset for asset in assets if asset["name"].startswith(name)] 45 | return output_dict[0]["browser_download_url"] 46 | 47 | @property 48 | def latest_release_url(self): 49 | return self._latest_release_url 50 | 51 | def tagged_release_url(self, version): 52 | return self.opera_release_tag.format(version) 53 | 54 | def get_browser_type(self): 55 | return "opera" 56 | -------------------------------------------------------------------------------- /webdriver_manager/firefox.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from webdriver_manager.core.download_manager import DownloadManager 5 | from webdriver_manager.core.driver_cache import DriverCacheManager 6 | from webdriver_manager.core.manager import DriverManager 7 | from webdriver_manager.core.os_manager import OperationSystemManager 8 | from webdriver_manager.drivers.firefox import GeckoDriver 9 | 10 | 11 | class GeckoDriverManager(DriverManager): 12 | def __init__( 13 | self, 14 | version: Optional[str] = None, 15 | name: str = "geckodriver", 16 | url: str = "https://github.com/mozilla/geckodriver/releases/download", 17 | latest_release_url: str = "https://api.github.com/repos/mozilla/geckodriver/releases/latest", 18 | mozila_release_tag: str = "https://api.github.com/repos/mozilla/geckodriver/releases/tags/{0}", 19 | download_manager: Optional[DownloadManager] = None, 20 | cache_manager: Optional[DriverCacheManager] = None, 21 | os_system_manager: Optional[OperationSystemManager] = None 22 | ): 23 | super(GeckoDriverManager, self).__init__( 24 | download_manager=download_manager, 25 | cache_manager=cache_manager 26 | ) 27 | 28 | self.driver = GeckoDriver( 29 | driver_version=version, 30 | name=name, 31 | url=url, 32 | latest_release_url=latest_release_url, 33 | mozila_release_tag=mozila_release_tag, 34 | http_client=self.http_client, 35 | os_system_manager=os_system_manager 36 | ) 37 | 38 | def install(self) -> str: 39 | driver_path = self._get_driver_binary_path(self.driver) 40 | os.chmod(driver_path, 0o755) 41 | return driver_path 42 | 43 | def get_os_type(self): 44 | os_type = super().get_os_type() 45 | if not self._os_system_manager.is_mac_os(os_type): 46 | return os_type 47 | 48 | macos = 'macos' 49 | if self._os_system_manager.is_arch(os_type): 50 | return f"{macos}-aarch64" 51 | return macos 52 | -------------------------------------------------------------------------------- /webdriver_manager/microsoft.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from webdriver_manager.core.download_manager import DownloadManager 5 | from webdriver_manager.core.driver_cache import DriverCacheManager 6 | from webdriver_manager.core.os_manager import OperationSystemManager 7 | from webdriver_manager.drivers.edge import EdgeChromiumDriver 8 | from webdriver_manager.drivers.ie import IEDriver 9 | from webdriver_manager.core.manager import DriverManager 10 | 11 | 12 | class IEDriverManager(DriverManager): 13 | def __init__( 14 | self, 15 | version: Optional[str] = None, 16 | name: str = "IEDriverServer", 17 | url: str = "https://github.com/seleniumhq/selenium/releases/download", 18 | latest_release_url: str = "https://api.github.com/repos/seleniumhq/selenium/releases", 19 | ie_release_tag: str = "https://api.github.com/repos/seleniumhq/selenium/releases/tags/selenium-{0}", 20 | download_manager: Optional[DownloadManager] = None, 21 | cache_manager: Optional[DriverCacheManager] = None, 22 | os_system_manager: Optional[OperationSystemManager] = None 23 | ): 24 | super().__init__( 25 | download_manager=download_manager, 26 | cache_manager=cache_manager 27 | ) 28 | 29 | self.driver = IEDriver( 30 | driver_version=version, 31 | name=name, 32 | url=url, 33 | latest_release_url=latest_release_url, 34 | ie_release_tag=ie_release_tag, 35 | http_client=self.http_client, 36 | os_system_manager=os_system_manager 37 | ) 38 | 39 | def install(self) -> str: 40 | return self._get_driver_binary_path(self.driver) 41 | 42 | def get_os_type(self): 43 | return "x64" if self._os_system_manager.get_os_type() == "win64" else "Win32" 44 | 45 | 46 | class EdgeChromiumDriverManager(DriverManager): 47 | def __init__( 48 | self, 49 | version: Optional[str] = None, 50 | name: str = "edgedriver", 51 | url: str = "https://msedgedriver.azureedge.net", 52 | latest_release_url: str = "https://msedgedriver.azureedge.net/LATEST_RELEASE", 53 | download_manager: Optional[DownloadManager] = None, 54 | cache_manager: Optional[DriverCacheManager] = None, 55 | os_system_manager: Optional[OperationSystemManager] = None 56 | ): 57 | super().__init__( 58 | download_manager=download_manager, 59 | cache_manager=cache_manager, 60 | os_system_manager=os_system_manager 61 | ) 62 | 63 | self.driver = EdgeChromiumDriver( 64 | driver_version=version, 65 | name=name, 66 | url=url, 67 | latest_release_url=latest_release_url, 68 | http_client=self.http_client, 69 | os_system_manager=os_system_manager 70 | ) 71 | 72 | def install(self) -> str: 73 | driver_path = self._get_driver_binary_path(self.driver) 74 | os.chmod(driver_path, 0o755) 75 | return driver_path 76 | -------------------------------------------------------------------------------- /webdriver_manager/opera.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | from webdriver_manager.core.download_manager import DownloadManager 5 | from webdriver_manager.core.driver_cache import DriverCacheManager 6 | from webdriver_manager.core.manager import DriverManager 7 | from webdriver_manager.core.os_manager import OperationSystemManager 8 | from webdriver_manager.drivers.opera import OperaDriver 9 | 10 | 11 | class OperaDriverManager(DriverManager): 12 | def __init__( 13 | self, 14 | version: Optional[str] = None, 15 | name: str = "operadriver", 16 | url: str = "https://github.com/operasoftware/operachromiumdriver/" 17 | "releases/", 18 | latest_release_url: str = "https://api.github.com/repos/" 19 | "operasoftware/operachromiumdriver/releases/latest", 20 | opera_release_tag: str = "https://api.github.com/repos/" 21 | "operasoftware/operachromiumdriver/releases/tags/{0}", 22 | download_manager: Optional[DownloadManager] = None, 23 | cache_manager: Optional[DriverCacheManager] = None, 24 | os_system_manager: Optional[OperationSystemManager] = None 25 | ): 26 | super().__init__( 27 | download_manager=download_manager, 28 | cache_manager=cache_manager 29 | ) 30 | 31 | self.driver = OperaDriver( 32 | name=name, 33 | driver_version=version, 34 | url=url, 35 | latest_release_url=latest_release_url, 36 | opera_release_tag=opera_release_tag, 37 | http_client=self.http_client, 38 | os_system_manager=os_system_manager 39 | ) 40 | 41 | def install(self) -> str: 42 | driver_path = self._get_driver_binary_path(self.driver) 43 | if not os.path.isfile(driver_path): 44 | for name in os.listdir(driver_path): 45 | if "sha512_sum" in name: 46 | os.remove(os.path.join(driver_path, name)) 47 | break 48 | driver_path = os.path.join(driver_path, os.listdir(driver_path)[0]) 49 | os.chmod(driver_path, 0o755) 50 | return driver_path 51 | -------------------------------------------------------------------------------- /webdriver_manager/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SergeyPirogov/webdriver_manager/1c59212c6dae5335d2aead14b119307084b44293/webdriver_manager/py.typed --------------------------------------------------------------------------------