Set up Selenium with Python and pytest
On this page
This chapter gets a browser opening from a pytest test. It takes about ten minutes, most of which is installing things once.
Check your Python version#
python3 --version
You need 3.10 or newer. On Windows the command is usually py --version. If your system Python is older, install a current one from python.org or your package manager rather than upgrading the system copy.
Create a project and a virtual environment#
A virtual environment keeps this project's packages separate from every other Python project on your machine. Always use one for test suites: it is what makes "works on my machine" reproducible in CI.
mkdir selenium-python-course
cd selenium-python-course
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
Your prompt now starts with (.venv). Every pip and pytest command from here on runs inside it. Add .venv/ to .gitignore; the environment is rebuilt from requirements.txt, never committed.
Install Selenium and pytest#
Create requirements.txt with pinned versions:
selenium==4.52.0
pytest==9.2.0
pytest-xdist==3.8.0
pytest-html==4.2.0
testingbotclient==0.2.0
and install it:
pip install -r requirements.txt
Only the first two are needed right away. pytest-xdist (chapter 7), pytest-html (chapter 5) and the TestingBot API client (chapter 8) are listed now so the file does not change under you later. Pinning exact versions means a new release cannot break your suite on a Monday morning; upgrade deliberately, run the suite, then commit the new pins.
No driver downloads: Selenium Manager#
A browser is controlled through a driver binary: chromedriver for Chrome, geckodriver for Firefox, msedgedriver for Edge. Older tutorials have you download it, unzip it and put it on your PATH, then repeat that every time Chrome auto-updates.
Since Selenium 4.6 you skip all of that. When you call webdriver.Chrome(), Selenium runs Selenium Manager, a small binary bundled inside the selenium package. It detects which browser version is installed, downloads the matching driver if it is not cached yet, and stores it under ~/.cache/selenium. You can watch it work by setting SE_DEBUG:
SE_DEBUG=1 python -c "from selenium import webdriver; d = webdriver.Chrome(); d.quit()"
Among the output you will see lines like these (versions will differ on your machine):
chromedriver not found in PATH
Detected browser: chrome 157.0.8103.78
Required driver: chromedriver 157.0.8103.78
One trap: if an old chromedriver is on your PATH, Selenium uses it, and you get SessionNotCreatedException: This version of ChromeDriver only supports Chrome version N. The fix is to delete the stray driver, not to download another one. Do not add third-party driver managers either; Selenium Manager replaces them.
Your first test#
Create a tests folder and tests/test_first.py:
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_web_form_title():
driver = webdriver.Chrome()
try:
driver.get("https://www.selenium.dev/selenium/web/web-form.html")
assert driver.title == "Web form"
driver.find_element(By.ID, "my-text-id").send_keys("Selenium")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
assert driver.find_element(By.ID, "message").text == "Received!"
finally:
driver.quit()
Run it from the project folder:
pytest -q
Chrome opens, fills in the text field, submits, and closes. pytest prints:
. [100%]
1 passed in 4.27s
What just happened#
-
Discovery. pytest looked for files named
test_*.pyand, inside them, functions namedtest_*. Name a filefirst.pyand pytest silently ignores it; that is the most common "my test doesn't run" question. -
webdriver.Chrome()asked Selenium Manager for a driver, started Chrome and opened a WebDriver session. -
driver.get(url)navigated and waited for the page's load event. It does not wait for JavaScript that runs afterwards; chapter 3 deals with that. -
find_element(By.ID, ...)located one element. Chapter 2 covers everyBystrategy. -
try/finallymakes surequit()runs even when an assertion fails. Without it, every failing test leaves a Chrome process behind. Chapter 4 replaces this with a pytest fixture, which is the idiomatic way to do the same thing.
After a Chrome update, a teammate's suite fails with "This version of ChromeDriver only supports Chrome version 155". Selenium is 4.52. What is the right fix?
A driver found on the PATH takes precedence over Selenium Manager, so a stale manual download causes exactly this error. Downloading a newer one only postpones the problem to the next Chrome update.
Headless runs#
On a CI server there is no screen, so browsers run headless. Pass options to the driver:
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
Always set the window size with headless Chrome. The default headless window is much smaller than your monitor, and responsive layouts can hide or overlap elements at that size, which produces failures you never see when the browser is visible. Chapter 4 turns headless into a --headless command-line switch so you do not edit code to change it.
Useful pytest flags from day one#
| Command | Does |
|---|---|
pytest -q |
quiet output, one character per test |
pytest -v |
one line per test with its full name |
pytest -x |
stop at the first failure |
pytest -k title |
run only tests whose name contains "title" |
pytest tests/test_first.py::test_web_form_title |
run one test |
pytest --lf |
rerun only the tests that failed last time |
The test works, but it has a weakness: it reads #message right after clicking submit. It passed because the click triggers a page load that the driver waits for. On a page that renders the message with JavaScript after a delay, the same line would fail. That is the subject of chapter 3; first, chapter 2 looks at how to find elements in a way that survives changes to the page.