Selenium's Python bindings drive real browsers for UI automation: create a WebDriver, locate elements with By strategies, interact, and assert. It pairs naturally with pytest fixtures for setup/teardown.
driver = webdriver.Chrome() launches a browser; driver.get(url) navigates; driver.find_element(By.ID, 'user') locates an element; .send_keys(), .click(), .text interact and read. Prefer explicit waits — WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, 'submit'))) — over time.sleep to handle async rendering. Selenium 4 uses the By class and Service objects, and Selenium Manager auto-resolves drivers. Use pytest fixtures to create/quit the driver and Page Objects to organize locators. Common assertions check element text, presence, or URL after actions.
A pytest test: def test_login(driver): driver.get(URL); driver.find_element(By.ID, 'user').send_keys('qa'); ...; WebDriverWait(driver, 10).until(EC.url_contains('/dashboard')) — waiting on the real post-login state.
A Selenium+Python test is flaky because it uses time.sleep(3) after clicking. What's the correct fix?