mrkeyoor.com_
Sat 19 Sept 08:53 UTC
PyPITestingupdated 19 Sept 2026

selenium review

Selenium 4.47.0 is the official Python binding for W3C WebDriver. It drives installed browsers locally or sends commands to a remote Grid, which makes it useful for end-to-end tests that must cover Chrome, Edge, Firefox, or Safari through the vendors' own drivers. The binding can locate elements, perform user input, switch browsing contexts, execute scripts, and expose newer BiDi features. It does not include a browser. Version 4.47.0 adds CDP mappings for Chrome 149 through 151, accepts `By` in locator type hints, blocks CDP access on Firefox, fixes `no_proxy` substring bypasses, honors Grid-advertised remote URLs, and cleans up driver-service subprocess resources more reliably.

Verdict

Selenium 4.47.0 installed in 0.6 seconds and used 28 MB across 15 packages in our sandbox, with typed code and 0 known vulnerabilities, but a working test still depends on a compatible browser, driver, and wait strategy. Choose it for Safari, Grid, vendor clouds, or an established WebDriver estate; compare Playwright for a new suite limited to its browser set.

We installed it

Lab card: what happened when we installed seleniumScreenshot of selenium documentation
Install✓ · 0.6s15 packages on disk · 28 MB
Importimport selenium in 0.02s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does selenium install cleanly?

Yes. In a fresh container with an empty cache, pip install selenium finished in 0.6s, leaving 15 packages and 28 MB on disk. pip-audit reported no known vulnerabilities.

What does selenium need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import selenium succeeded in 0.02s, and the package ships py.typed for type checkers.

selenium or playwright: which should you use?

playwright: Use it for a new supported-browser suite that benefits from locator auto-waits, traces, and isolated browser contexts. Selenium 4.47.0 installed in 0.6 seconds and used 28 MB across 15 packages in our sandbox, with typed code and 0 known vulnerabilities, but a working test still depends on a compatible browser, driver, and wait strategy.

When should you not use selenium?

You are starting a Chrome, Firefox, or WebKit-only suite and want built-in tracing, request interception, and locator auto-waiting; compare Playwright before accepting Selenium's explicit synchronization work

API stability5/5Selenium 4 is anchored to the W3C WebDriver protocol, and its Python surface for `WebDriver`, `By`, explicit waits, actions, and remote options has stayed recognizable through frequent releases. Version 4.47.0 changes locator typing and driver cleanup without rewriting ordinary tests. CDP bindings are versioned more tightly, so code that calls DevTools commands has a narrower compatibility window than WebDriver code.
Docs4/5The official manual covers drivers, locators, waits, elements, interactions, browser options, Grid, BiDi, and troubleshooting across supported languages. Python API references expose signatures and return types, while Selenium Manager has its own setup material. Some examples require switching between language tabs, and the fast-moving BiDi and CDP pages need closer version checking than basic WebDriver examples.
Maintenance5/5PyPI published Selenium 4.47.0 on 10 August 2026, and SeleniumHQ/selenium was pushed on 26 August 2026. The release fixes proxy matching and subprocess cleanup while adding current Chrome DevTools mappings. GitHub reports 187 open issues and pull requests for the full multi-language framework, Grid server, Manager, and browser integrations rather than for the Python wheel alone.
Ecosystem5/5The recorded PyPI snapshot has 14,760,233 weekly downloads, and SeleniumHQ/selenium has 34,395 GitHub stars. Browser vendors implement WebDriver, commercial device clouds expose Selenium endpoints, and Grid can run the same protocol on owned infrastructure. pytest fixtures, reporting plugins, page-object libraries, and years of support material surround the binding, though age also means many search results use removed APIs.

Use it if

  • You must test Safari or an existing WebDriver Grid alongside Chrome and Firefox
  • Your organization already has Selenium page objects, fixtures, cloud-browser capabilities, or Grid operations
  • You need the browser vendor's WebDriver behavior instead of a simulated DOM
  • Your test suite spans languages and should use the same W3C WebDriver protocol across them
Skip it if

Setup reality

Our fresh Python 3.12 sandbox installed selenium 4.47.0 in 0.6 seconds. It left 15 packages using 28 MB on disk. The pure-Python wheel declares 6 direct dependencies and includes py.typed; import selenium completed in 0.02 seconds. pip-audit found 0 known vulnerabilities. These numbers describe our install only, and How we test documents the unprivileged container. No browser session was started by that import check.

The first webdriver.Chrome() or webdriver.Firefox() call is the real setup test. Selenium Manager can discover, download, and cache a matching driver when you do not provide a Service path, but the machine still needs a supported browser and network or a seeded cache. Proxies, locked-down home directories, architecture mismatches, and browser auto-updates can break that step. Pin the browser image in CI and record its version beside Selenium 4.47.0 when failures depend on the driver pair.

WebDriver commands are synchronous, while modern pages replace nodes between calls. Use WebDriverWait for a condition tied to the next action, then find the element again after navigation or DOM replacement. Mixing implicit waits with 10-second explicit waits makes elapsed time harder to reason about. Frames, alerts, and windows each change the active context; a locator valid in the top document cannot see an iframe until switch_to.frame() succeeds. Always call quit() from teardown.

Remote Grid adds queueing, capabilities, and server-side cleanup to the 28 MB client install. Version 4.47.0 now honors the Grid's se:remoteUrl for reachable BiDi, CDP, and VNC addresses, but your runner still needs a route to those advertised URLs. Save the 1 session ID plus screenshot, page source, browser logs, and current URL before teardown on failure. A killed runner can leave a remote session alive until the Grid's timeout policy removes it.

Patterns

Start Chrome and guarantee teardown start-browser

from selenium import webdriver

driver = webdriver.Chrome()
try:
    driver.get('https://example.com')
    print(driver.title)
finally:
    driver.quit()

`quit()` ends the WebDriver session and closes its browser windows; closing 1 tab with `close()` may leave the session running.

Set a fixed headless viewport configure-headless

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument('--headless=new')
options.add_argument('--window-size=1440,1000')
options.add_argument('--lang=en-US')
driver = webdriver.Chrome(options=options)

A 1440 by 1000 viewport makes responsive breakpoints explicit; headless defaults can differ from a developer's visible window.

Locate one element or collect many find-elements

from selenium.webdriver.common.by import By

submit = driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
links = driver.find_elements(By.TAG_NAME, 'a')
print(len(links))

`find_element()` raises `NoSuchElementException` for 0 matches; `find_elements()` returns an empty list.

Wait for the next actionable control wait-until-clickable

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

button = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, 'save'))
)
button.click()

This wait stops as soon as the control is visible and enabled, with a 10-second ceiling if the condition never becomes true.

Wait for a URL change after submission wait-for-navigation

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

old_url = driver.current_url
driver.find_element(By.CSS_SELECTOR, 'form button').click()
WebDriverWait(driver, 10).until(EC.url_changes(old_url))

`url_changes()` observes navigation without keeping an element reference that may become stale during the page swap.

Re-locate a node after DOM replacement refind-stale-element

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

old_row = driver.find_element(By.CSS_SELECTOR, '[data-row="42"]')
driver.find_element(By.ID, 'refresh').click()
WebDriverWait(driver, 10).until(EC.staleness_of(old_row))
new_row = driver.find_element(By.CSS_SELECTOR, '[data-row="42"]')

A stale reference still points to the removed node; waiting for staleness and locating again returns the replacement.

Enter an iframe and return to the page switch-frame

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

WebDriverWait(driver, 10).until(
    EC.frame_to_be_available_and_switch_to_it((By.ID, 'payment'))
)
driver.find_element(By.NAME, 'card').send_keys(card_number)
driver.switch_to.default_content()

Top-level locators see 0 elements inside an iframe until WebDriver switches to that frame's browsing context.

Detect and enter a newly opened tab switch-window

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

known = set(driver.window_handles)
driver.find_element(By.LINK_TEXT, 'Open report').click()
WebDriverWait(driver, 10).until(
    lambda d: len(d.window_handles) > len(known)
)
new_handle = (set(driver.window_handles) - known).pop()
driver.switch_to.window(new_handle)

Window-handle order has no contract, so set subtraction identifies the 1 new handle without assuming it is last.

Wait for and accept a browser alert handle-alert

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

alert = WebDriverWait(driver, 5).until(EC.alert_is_present())
message = alert.text
alert.accept()
print(message)

An open modal alert blocks ordinary page commands; the 5-second wait returns the alert object when it appears.

Find a control inside an open shadow root use-shadow-dom

from selenium.webdriver.common.by import By

host = driver.find_element(By.CSS_SELECTOR, 'settings-panel')
shadow = host.shadow_root
toggle = shadow.find_element(By.CSS_SELECTOR, 'button[role="switch"]')
toggle.click()

WebDriver can traverse an open shadow root through `shadow_root`; a closed root is not exposed to page automation.

Save browser evidence before teardown capture-failure

from pathlib import Path

def save_failure(driver, test_name):
    out = Path('artifacts')
    out.mkdir(exist_ok=True)
    driver.save_screenshot(out / f'{test_name}.png')
    (out / f'{test_name}.html').write_text(
        driver.page_source, encoding='utf-8'
    )
    (out / f'{test_name}.url').write_text(driver.current_url)

After `quit()` there is 0 active session, so screenshots, source, and the current URL must be collected first.

Create a named remote Grid session run-on-grid

from selenium import webdriver

options = webdriver.FirefoxOptions()
options.set_capability('se:name', 'checkout flow')
driver = webdriver.Remote(
    command_executor='http://selenium-grid:4444',
    options=options,
)
print(driver.session_id)

Selenium 4.47.0 honors Grid-advertised `se:remoteUrl` values, but the runner must be able to reach the returned BiDi, CDP, or VNC address.

Alternatives

PackageRegistryPick it when
playwrightPyPIUse it for a new supported-browser suite that benefits from locator auto-waits, traces, and isolated browser contexts
splinterPyPIUse it when a smaller acceptance-test API is worth adding an abstraction over browser drivers
beautifulsoup4PyPIUse it with an HTTP client when the response already contains the HTML and no user interaction is needed

More testing guides

pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.