Same Test, Different Tool
Rewrite the Playwright search test using Selenium and Python. See the syntax differences, feel the waiting-model differences, and decide for yourself which you prefer.
1 Goal
By the end of this exercise you will have a Python project with Selenium installed, a test script that opens DuckDuckGo, fills the search box, submits the form, and asserts the URL plus a visible result link — the same scenario as Practice 02, rewritten in a different tool. You will understand why Selenium needs explicit waits, how webdriver-manager eliminates the driver-download headache, and how the assertion model differs from Playwright's web-first expectations.
2 Install the tool (Python + Selenium)
-
Install Python 3.12 or newer
Download from python.org/downloads.
Run the installer. Critical: tick "Add python.exe to PATH" on the first screen before clicking Install — otherwise
pythonwill not work from PowerShell.Run the
.pkginstaller, or install via Homebrew:brew install python@3.12
Most distros have 3.10+ pre-installed. On Debian/Ubuntu if you need the newer version:
sudo apt update sudo apt install -y python3.12 python3.12-venv
-
Verify the install
Open a fresh terminal and run:
python --version pip --version
python3 --version pip3 --version
Expect
Python 3.12.xor newer. On macOS/Linux you will usepython3andpip3throughout this exercise — substitute in the commands below. -
Create a project folder and virtual environment
A venv keeps this project's dependencies isolated from the system Python.
cd $HOME\Desktop mkdir selenium-practice-03 cd selenium-practice-03 python -m venv .venv .\.venv\Scripts\Activate.ps1
If PowerShell refuses with an execution-policy error, run
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSignedonce, then try the activate command again.cd ~/Desktop mkdir selenium-practice-03 cd selenium-practice-03 python3 -m venv .venv source .venv/bin/activate
Your prompt should now show
(.venv)at the start — that confirms the venv is active. -
Install Selenium and webdriver-manager
pip install selenium webdriver-manager
seleniumis the automation library itself.webdriver-managerdownloads the matching ChromeDriver binary at runtime so you don't have to match versions manually — historically the single most painful part of using Selenium. -
Confirm versions
pip show selenium webdriver-manager
You should see
selenium4.x andwebdriver-manager4.x. Selenium 4 uses the W3C WebDriver protocol and has the modern locator API — anything older is not worth learning.
3 Project setup
Create one script file at the project root:
New-Item test_search.py
touch test_search.py
Final layout:
selenium-practice-03/ ├── .venv/ └── test_search.py
4 Write the script
Paste this exact content into test_search.py:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
def test_duckduckgo_returns_results_for_an_nz_query():
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
try:
driver.get("https://duckduckgo.com/")
search_box = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.NAME, "q"))
)
search_box.send_keys("Resync Consulting New Zealand")
search_box.send_keys(Keys.RETURN)
WebDriverWait(driver, 10).until(
EC.url_contains("q=Resync+Consulting+New+Zealand")
)
assert "q=Resync+Consulting+New+Zealand" in driver.current_url
first_result = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located(
(By.XPATH, "//a[contains(translate(., 'RESYNC', 'resync'), 'resync')]")
)
)
assert first_result.is_displayed()
driver.save_screenshot("results.png")
finally:
driver.quit()
if __name__ == "__main__":
test_duckduckgo_returns_results_for_an_nz_query()
print("PASS")What each piece does:
Service(ChromeDriverManager().install())— downloads the correct ChromeDriver for your installed Chrome and returns the path. Selenium 4 requires you to pass this as aServiceobject — passing the path as a string directly is deprecated.webdriver.Chrome(service=service)— launches Chrome. No Firefox/WebKit flag here — Selenium needs a separate driver for each browser.try / finally with driver.quit()— Selenium will leave a zombie Chrome process running if the test errors without callingquit(). Thefinallyblock guarantees cleanup. Playwright does this for you automatically — Selenium does not.WebDriverWait(driver, 10).until(EC.element_to_be_clickable(...))— Selenium's explicit wait. Unlike Playwright's auto-wait, you must tell Selenium to wait for the element to be interactable. Skipping this is the #1 cause of flaky Selenium tests.(By.NAME, "q")— the locator tuple. Selenium's locator options areID,NAME,CLASS_NAME,CSS_SELECTOR,XPATH,LINK_TEXT,TAG_NAME. No built-in role-based locator — you fall back to attributes.send_keys(Keys.RETURN)— submits the form by pressing Enter. Equivalent to Playwright's.press('Enter').EC.url_contains(...)— the closest Selenium equivalent to Playwright'sexpect(page).toHaveURL(). You wrap it inWebDriverWaitbecause there is no auto-retry on a plainassert.XPATH with translate()— Selenium's XPath 1.0 has no case-insensitivecontains, sotranslate()is the standard workaround to make the match case-insensitive.driver.save_screenshot("results.png")— writes a viewport-only PNG. Selenium does not have Playwright'sfullPage: trueequivalent — full-page capture requires a CDP call or a third-party lib.
playwright.dev, duckduckgo.com, and example.com are the approved practice targets.
Playwright vs Selenium at a glance (same test, key differences):
| Concern | Playwright (P02) | Selenium (P03) |
|---|---|---|
| Install | npm init playwright@latest — browsers included | pip install selenium webdriver-manager — then WDM downloads driver at runtime |
| Waits | Automatic — expect(...) polls up to 5 s by default | Manual — wrap every lookup in WebDriverWait(...).until(...) |
| Cross-browser | Chromium + Firefox + WebKit by default in one run | One browser per run; each needs its own driver binary |
| Locators | getByRole, getByLabel, getByText built in | By.NAME, By.XPATH, By.CSS_SELECTOR — lower level |
| Cleanup | Handled by the test runner | You write try/finally with driver.quit() |
| Reporting | HTML report, trace viewer, UI mode out of the box | You add pytest-html / allure yourself |
5 Run & verify
With the venv active, from the project root:
python test_search.py
python3 test_search.py
Expected output (first run will also download ChromeDriver — 10-20 MB):
====== WebDriver manager ====== Get LATEST chromedriver version for google-chrome Driver [~/.wdm/drivers/chromedriver/.../chromedriver] found in cache PASS
A Chrome window will pop open, perform the search, and close. Check the project folder — results.png is there.
Want to run it as a proper test with pytest? Install pytest and re-run:
pip install pytest pytest test_search.py -v
pytest discovers the test_-prefixed function automatically and gives you a nicer report. This is how you would structure a real project.
PASS (or pytest shows 1 passed), no uncaught exception, and results.png exists. If Chrome doesn't close automatically when the script ends, the finally block didn't fire — investigate before proceeding.
6 Troubleshooting
Error: session not created: This version of ChromeDriver only supports Chrome version X
Your Chrome auto-updated and the cached ChromeDriver is now older than your browser. webdriver-manager usually handles this, but its cache can go stale.
Fix: clear the cache and let it re-download.
Remove-Item $HOME\.wdm -Recurse -Force
rm -rf ~/.wdm
Error: NoSuchElementException: Unable to locate element: {"method":"name","selector":"q"}
You didn't wrap the lookup in a wait, and the page wasn't done loading. This is the Selenium classic — Playwright auto-waits, Selenium does not.
Fix: always use WebDriverWait(...).until(EC.presence_of_element_located(...)) or element_to_be_clickable when looking up elements. Never use a bare driver.find_element(By.NAME, 'q') for anything that might not be instant.
Error: ModuleNotFoundError: No module named 'selenium'
Your venv isn't active, or you installed into a different Python. Check the prompt shows (.venv) and run:
pip list | grep -i selenium
If empty, re-activate the venv and pip install selenium webdriver-manager again.
Chrome opens but the test times out on the result link
DuckDuckGo may show a cookie consent banner based on your region, or the results layout may have shifted.
Fix: open the browser in headed mode (it already is by default), watch what happens, and either dismiss the consent with an extra click, or loosen the XPath to match any link containing the word "resync" — even one with a broader class list.
7 Challenge
Stretch yourself
- Run Chrome headless. Add the option before launching:
This is how you will run in CI — no visible browser window. Confirm the test still passes.
from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--headless=new") driver = webdriver.Chrome(service=service, options=options) - Convert the assertion into a proper pytest test with a
@pytest.fixturethat yields the driver and handlesdriver.quit()in teardown — so you can drop thetry/finallyfrom the test body. This is the standard Selenium-plus-pytest pattern you will see in real codebases.
Bonus: write a short reflection in a README — which tool felt cleaner for this test, and which would you reach for if a client asked you to stand up a new automation project tomorrow?