Selenium predates most of the modern browser automation tooling ecosystem, and that history shows up in both directions — the broadest cross-browser and cross-language support available, alongside a more verbose, less ergonomic API than newer tools built with the benefit of hindsight.
Selenium is a browser automation framework that drives real browsers programmatically — clicking, typing, navigating, and reading page content — commonly used for end-to-end testing and web scraping of JavaScript-heavy sites. It supports the widest range of browsers and language bindings of any major automation tool, which remains its strongest differentiator against newer alternatives.
Why Selenium Matters (and When to Skip It)
For automating interactions with real browsers — testing user flows end-to-end, or scraping sites that require actual JavaScript execution and DOM interaction — Selenium provides mature, broadly supported tooling with a large ecosystem of documentation, community knowledge, and integrations built up over many years of use.
Skip Selenium for new projects where Playwright is a viable option — Playwright generally offers a more modern API, better built-in waiting/retry semantics, and faster execution for the same automation tasks. Selenium remains the right choice mainly for legacy codebases already built on it, or genuinely niche browser/platform support it covers that Playwright doesn't.
Getting Started with Selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys("myuser")
driver.find_element(By.ID, "password").send_keys("mypassword")
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
# explicit wait for an element to appear, rather than a fixed sleep
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "dashboard"))
)
driver.quit()
Core Selenium Concepts Every Developer Should Know
Explicit waits are essential, not optional. Selenium doesn't automatically wait for dynamic content to load — using WebDriverWait with an expected condition is the correct pattern, versus arbitrary time.sleep() calls, which are both slower (waiting longer than necessary) and less reliable (not waiting long enough on a slow load).
Locator strategy affects test/script fragility. IDs and data attributes specifically added for automation (data-testid) are far more stable selectors than relying on CSS class names or DOM structure that changes with routine styling updates — worth advocating for data-testid attributes in the application code itself if you're maintaining test automation against it.
driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-button']")
WebDriver instances are resource-heavy and should be properly cleaned up. Always call driver.quit() (not just driver.close(), which only closes the current window/tab) when done, ideally in a finally block or context manager, to avoid leaking browser processes during long test runs.
try:
driver = webdriver.Chrome()
# ... automation logic
finally:
driver.quit()
Headless mode runs the browser without a visible UI, appropriate for CI environments and faster execution when visual debugging isn't needed:
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
Common Selenium Mistakes and How to Fix Them
Mistake 1: using time.sleep() instead of explicit waits. This makes scripts both slower than necessary and unreliable under variable load times. Fix: always use WebDriverWait with an appropriate expected condition rather than fixed sleeps.
Mistake 2: fragile selectors tied to CSS classes or DOM structure that change with routine UI updates. Fix: prefer stable, purpose-built selectors like data-testid attributes over classes or structural selectors.
Mistake 3: not cleaning up WebDriver instances, leaking browser processes especially in long-running test suites or scripts. Fix: always quit the driver in a finally block or equivalent cleanup mechanism.
When Should You Use Selenium Instead of Playwright?
Use Selenium when working within an existing Selenium-based test suite where migration cost outweighs the benefit, or when you need Selenium's specific broad legacy browser/platform support that Playwright doesn't cover. Use Playwright for new projects — its more modern API, built-in auto-waiting, and generally faster execution make it the stronger default choice when starting fresh without existing Selenium investment.
Selenium Automation in Production
Use explicit waits consistently and prefer stable, purpose-built selectors to keep automation resilient against routine UI changes — these two practices address the majority of flaky Selenium test/script failures. Also run in headless mode for CI environments to reduce resource usage and speed up execution, reserving headed mode for local debugging where visual feedback is actually useful.
If your existing Selenium suite relies heavily on time.sleep() calls, replacing them with explicit waits is usually the single highest-impact reliability fix available without a larger migration effort.