Selenium is a free, open-source tool that automates web browsers, and it is the most widely used starting point for web test automation. In this Selenium tutorial for beginners you will set up Selenium with Python, learn how to find elements, write your first login test, handle waits correctly, and organise tests with pytest and the Page Object Model. Every code sample runs against a public practice website, so you can follow along today.
What is Selenium?
Selenium is a family of tools. The part you will use is Selenium WebDriver, a library that controls a real browser (Chrome, Firefox, Edge or Safari) through code. You write a script that opens pages, clicks buttons, types text and checks the results, exactly as a manual tester would, but repeatably and much faster.
WebDriver supports Java, Python, C#, JavaScript and Ruby. Java is common in Indian service companies, while Python is quicker to learn. The concepts are identical, so this tutorial uses Python for shorter code.
Step 1: Set up your environment
python -m venv venv
# Windows: venv/Scripts/activate macOS or Linux: source venv/bin/activate
pip install selenium pytestYou also need Google Chrome or another browser installed. Since Selenium 4.6, a built-in tool called Selenium Manager downloads the matching browser driver automatically, so you no longer need to download ChromeDriver by hand.
Step 2: Your first script
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://the-internet.herokuapp.com/')
print(driver.title)
driver.quit()Run it with python first.py. A Chrome window opens, loads the page, prints the title and closes. Always call driver.quit(), or browser processes will pile up in the background.
Step 3: Find elements with locators
To interact with a page, you first locate elements. Selenium offers several strategies through the By class:
| Locator | Example | When to use |
|---|---|---|
By.ID | (By.ID, 'username') | First choice when a stable id exists |
By.NAME | (By.NAME, 'email') | Form fields |
By.CSS_SELECTOR | (By.CSS_SELECTOR, 'button.radius') | Fast and flexible; preferred for most cases |
By.XPATH | (By.XPATH, '//h2[text()="Login Page"]') | Matching by text or complex relationships |
By.LINK_TEXT | (By.LINK_TEXT, 'Form Authentication') | Links with exact visible text |
Use your browser’s developer tools (right-click, then Inspect) to see an element’s attributes. Prefer short, meaningful locators. Long absolute XPaths such as /html/body/div[2]/div/form/input[1] break whenever the layout changes. If you work with developers, ask them to add data-testid attributes for automation.
Step 4: Write a login test
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('https://the-internet.herokuapp.com/login')
driver.find_element(By.ID, 'username').send_keys('tomsmith')
driver.find_element(By.ID, 'password').send_keys('SuperSecretPassword!')
driver.find_element(By.CSS_SELECTOR, 'button[type=submit]').click()
message = driver.find_element(By.ID, 'flash').text
assert 'You logged into a secure area!' in message
print('Login test passed')
driver.quit()The practice site publishes these demo credentials on its login page. Notice the pattern used in every UI test: arrange (open the page), act (type and click), assert (check the result).
Step 5: Use waits the right way
Modern web pages load content dynamically. If your script looks for an element before it appears, you get a NoSuchElementException. The fix is to wait for a condition, not to add time.sleep() everywhere.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
flash = wait.until(EC.visibility_of_element_located((By.ID, 'flash')))- Explicit waits (above) wait up to a maximum time for a specific condition, then continue as soon as it is true. Use these.
- Implicit waits set a global timeout for every element lookup. Avoid mixing them with explicit waits, as the combination causes unpredictable delays.
- Fixed sleeps make tests slow and still flaky. Use them only while debugging.
Step 6: Organise tests with pytest
Scripts are fine for learning, but real projects use a test framework. With pytest, any function whose name starts with test_ is a test, and fixtures handle setup and cleanup.
# conftest.py
import pytest
from selenium import webdriver
@pytest.fixture
def driver():
d = webdriver.Chrome()
d.implicitly_wait(0)
yield d
d.quit()
# test_login.py
from selenium.webdriver.common.by import By
def test_valid_login(driver):
driver.get('https://the-internet.herokuapp.com/login')
driver.find_element(By.ID, 'username').send_keys('tomsmith')
driver.find_element(By.ID, 'password').send_keys('SuperSecretPassword!')
driver.find_element(By.CSS_SELECTOR, 'button[type=submit]').click()
assert 'secure area' in driver.find_element(By.ID, 'flash').text
def test_invalid_password(driver):
driver.get('https://the-internet.herokuapp.com/login')
driver.find_element(By.ID, 'username').send_keys('tomsmith')
driver.find_element(By.ID, 'password').send_keys('wrong')
driver.find_element(By.CSS_SELECTOR, 'button[type=submit]').click()
assert 'invalid' in driver.find_element(By.ID, 'flash').text.lower()Run all tests with pytest -v. Notice the negative test: good testers check what should fail, not only what should pass.
Step 7: The Page Object Model
When many tests use the same page, keep that page’s locators and actions in one class. If the page changes, you update one file instead of fifty tests.
# pages/login_page.py
from selenium.webdriver.common.by import By
class LoginPage:
URL = 'https://the-internet.herokuapp.com/login'
USER = (By.ID, 'username')
PASS = (By.ID, 'password')
SUBMIT = (By.CSS_SELECTOR, 'button[type=submit]')
FLASH = (By.ID, 'flash')
def __init__(self, driver):
self.driver = driver
def open(self):
self.driver.get(self.URL)
def login(self, user, password):
self.driver.find_element(*self.USER).send_keys(user)
self.driver.find_element(*self.PASS).send_keys(password)
self.driver.find_element(*self.SUBMIT).click()
def message(self):
return self.driver.find_element(*self.FLASH).textYour test then reads like plain English: open the login page, log in, check the message.
Other things you will need
- Dropdowns: the
Selectclass for standardselectelements. - Alerts, frames and new tabs:
driver.switch_to.alert,switch_to.frameandswitch_to.window. - Screenshots on failure:
driver.save_screenshot('fail.png'), ideally triggered from a pytest hook. - Headless runs for CI servers, using browser options.
- Reports with a plugin such as pytest-html or Allure.
- Running in CI with Jenkins or GitHub Actions, so tests run on every code change.
What should you automate?
Automate stable, repetitive, high-value checks: login, checkout, search, and regression suites that run on every release. Keep exploratory testing, usability checks and one-off scenarios manual. Our guide to manual vs automation testing explains the trade-offs.
Frequently asked questions
Should I learn Selenium with Java or Python?
Check the job listings you are targeting. Java with TestNG is very common in service companies; Python with pytest is quicker to learn. The WebDriver concepts transfer directly between them.
Do I need manual testing knowledge before Selenium?
Yes. Automation only speeds up testing. You still need to know what to test, how to write test cases and how to report bugs.
Is Selenium still relevant with Playwright and Cypress available?
Yes. Newer tools are popular for new projects, but Selenium has a huge installed base, supports many languages and browsers, and is still listed in a large number of testing jobs. Learning it also makes other tools easy to pick up.
Why do my Selenium tests fail randomly?
The usual causes are missing waits, fragile locators, tests that depend on each other, and shared test data. Fix waits and locators first.
Prepare for interviews with our manual testing interview questions, and explore more software testing tutorials.
