Getting Started with Selenium: Full Guide's Complete Guide
Everything you need to start automating web tests with Selenium WebDriver — setup, writing your first tests, handling dynamic elements, the Page Object.
Selenium has been the backbone of web test automation for over 15 years. Despite newer tools like Playwright and Cypress gaining popularity, Selenium remains the most widely used automation framework in the world — and for good reason. Its cross-browser support, multi-language bindings (Java, Python, C#, Ruby, JavaScript), and huge community make it a safe, well-understood choice for enterprise environments.
This guide walks you through everything you need to start with Selenium WebDriver, from zero to a running CI pipeline.
Selenium vs Modern Alternatives
Before committing to Selenium, it's worth understanding where it sits relative to Playwright and Cypress.
| Selenium | Playwright | Cypress | |
|---|---|---|---|
| Languages | Java, Python, C#, Ruby, JS | JS/TS, Python, Java, C# | JS/TS only |
| Browsers | All (inc. IE, Safari) | Chrome, Firefox, WebKit | Chrome-family |
| Auto-wait | No (manual waits required) | Yes | Yes |
| Setup complexity | Higher | Low | Low |
| Enterprise adoption | Very high | Growing | Growing |
| Community | Largest | Large | Large |
Choose Selenium if: your organisation uses Java, C#, or Python and has existing Selenium investment, you need to test on Internet Explorer or real Safari, or your compliance requirements dictate a specific tool.
Consider Playwright if: you're starting fresh, especially on a JavaScript/TypeScript stack. Playwright's auto-waiting and modern API significantly reduce flakiness.
Setup
Java + Maven
Add Selenium to your pom.xml:
XML1<dependencies> 2 <dependency> 3 <groupId>org.seleniumhq.selenium</groupId> 4 <artifactId>selenium-java</artifactId> 5 <version>4.18.1</version> 6 </dependency> 7 <dependency> 8 <groupId>org.junit.jupiter</groupId> 9 <artifactId>junit-jupiter</artifactId> 10 <version>5.10.2</version> 11 <scope>test</scope> 12 </dependency> 13 <dependency> 14 <groupId>io.github.bonigarcia</groupId> 15 <artifactId>webdrivermanager</artifactId> 16 <version>5.8.0</version> 17 </dependency> 18</dependencies>
WebDriverManager handles browser driver downloads automatically — no more manually downloading chromedriver.exe.
Python + pip
BASH1pip install selenium pytest
Writing Your First Test
Java
JAVA1import io.github.bonigarcia.wdm.WebDriverManager; 2import org.junit.jupiter.api.*; 3import org.openqa.selenium.*; 4import org.openqa.selenium.chrome.ChromeDriver; 5 6public class FirstTest { 7 8 private WebDriver driver; 9 10 @BeforeEach 11 void setUp() { 12 WebDriverManager.chromedriver().setup(); 13 driver = new ChromeDriver(); 14 driver.manage().window().maximize(); 15 } 16 17 @AfterEach 18 void tearDown() { 19 if (driver != null) driver.quit(); 20 } 21 22 @Test 23 void homepageHasCorrectTitle() { 24 driver.get("https://www.innovatebits.com"); 25 Assertions.assertTrue(driver.getTitle().contains("InnovateBits")); 26 } 27 28 @Test 29 void blogLinkNavigatesToBlog() { 30 driver.get("https://www.innovatebits.com"); 31 driver.findElement(By.linkText("Blog")).click(); 32 Assertions.assertTrue(driver.getCurrentUrl().contains("/blog")); 33 } 34}
Python
PYTHON1import pytest 2from selenium import webdriver 3from selenium.webdriver.common.by import By 4from selenium.webdriver.chrome.service import Service 5from webdriver_manager.chrome import ChromeDriverManager 6 7@pytest.fixture 8def driver(): 9 d = webdriver.Chrome(service=Service(ChromeDriverManager().install())) 10 d.maximize_window() 11 yield d 12 d.quit() 13 14def test_homepage_title(driver): 15 driver.get("https://www.innovatebits.com") 16 assert "InnovateBits" in driver.title 17 18def test_blog_navigation(driver): 19 driver.get("https://www.innovatebits.com") 20 driver.find_element(By.LINK_TEXT, "Blog").click() 21 assert "/blog" in driver.current_url
Locator Strategies
Finding the right element is the core skill in Selenium. Selenium 4 supports several strategies:
JAVA1// By ID — fastest, most reliable when available 2driver.findElement(By.id("submit-button")); 3 4// By CSS selector — flexible and widely used 5driver.findElement(By.cssSelector(".btn-primary")); 6driver.findElement(By.cssSelector("input[data-testid='email-input']")); 7driver.findElement(By.cssSelector("div.container > button:first-child")); 8 9// By XPath — powerful but verbose; use as last resort 10driver.findElement(By.xpath("//button[@type='submit']")); 11driver.findElement(By.xpath("//h1[contains(text(), 'Welcome')]")); 12 13// By link text 14driver.findElement(By.linkText("Sign In")); 15 16// Selenium 4: Relative locators — position-based 17import static org.openqa.selenium.support.locators.RelativeLocator.with; 18driver.findElement(with(By.tagName("input")).below(By.id("username-label")));
Locator priority (best to worst):
data-testidattribute (add to your app specifically for testing)- ID
- CSS selector (class, attribute)
- Link text
- XPath (avoid if possible)
Handling Dynamic Elements and Waits
The most common source of Selenium test failures is timing — trying to interact with an element before it's ready. Selenium does not auto-wait like Playwright does, so you must manage waits explicitly.
Never use Thread.sleep()
JAVA1// ❌ Never do this 2Thread.sleep(3000); 3driver.findElement(By.id("result")).click();
Thread.sleep is the leading cause of slow, flaky Selenium tests.
Explicit waits (correct approach)
JAVA1import org.openqa.selenium.support.ui.WebDriverWait; 2import org.openqa.selenium.support.ui.ExpectedConditions; 3import java.time.Duration; 4 5WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); 6 7// Wait until element is visible 8WebElement element = wait.until( 9 ExpectedConditions.visibilityOfElementLocated(By.id("result")) 10); 11 12// Wait until element is clickable 13wait.until(ExpectedConditions.elementToBeClickable(By.id("submit-btn"))).click(); 14 15// Wait until URL changes 16wait.until(ExpectedConditions.urlContains("/dashboard")); 17 18// Wait until text appears 19wait.until(ExpectedConditions.textToBePresentInElementLocated( 20 By.id("status-message"), "Success" 21));
Implicit waits (use sparingly)
JAVA1// Set once at driver initialisation 2driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
Implicit waits apply globally to every findElement call. They can hide slow pages and interact unpredictably with explicit waits. Use explicit waits where you need to wait for specific conditions.
Page Object Model
Just like Playwright, Selenium tests benefit enormously from Page Object Model:
JAVA1// pages/LoginPage.java 2import org.openqa.selenium.*; 3import org.openqa.selenium.support.ui.*; 4import java.time.Duration; 5 6public class LoginPage { 7 private final WebDriver driver; 8 private final WebDriverWait wait; 9 10 private final By emailField = By.id("email"); 11 private final By passwordField = By.id("password"); 12 private final By submitButton = By.cssSelector("button[type='submit']"); 13 private final By errorMessage = By.cssSelector(".error-alert"); 14 15 public LoginPage(WebDriver driver) { 16 this.driver = driver; 17 this.wait = new WebDriverWait(driver, Duration.ofSeconds(10)); 18 } 19 20 public void navigate() { 21 driver.get("https://yourapp.com/login"); 22 } 23 24 public void login(String email, String password) { 25 wait.until(ExpectedConditions.visibilityOfElementLocated(emailField)) 26 .sendKeys(email); 27 driver.findElement(passwordField).sendKeys(password); 28 driver.findElement(submitButton).click(); 29 } 30 31 public String getErrorMessage() { 32 return wait.until( 33 ExpectedConditions.visibilityOfElementLocated(errorMessage) 34 ).getText(); 35 } 36} 37 38// Test using the page object 39class LoginTest { 40 @Test 41 void invalidCredentialsShowError() { 42 LoginPage loginPage = new LoginPage(driver); 43 loginPage.navigate(); 44 loginPage.login("user@example.com", "wrongpassword"); 45 Assertions.assertEquals("Invalid credentials", loginPage.getErrorMessage()); 46 } 47}
Running Tests in Parallel
TestNG (popular Selenium alternative to JUnit) supports parallel execution via its XML configuration:
XML1<!-- testng.xml --> 2<suite name="RegressionSuite" parallel="classes" thread-count="4"> 3 <test name="UITests"> 4 <classes> 5 <class name="tests.LoginTest"/> 6 <class name="tests.CheckoutTest"/> 7 <class name="tests.SearchTest"/> 8 </classes> 9 </test> 10</suite>
For thread-safe parallel execution, use ThreadLocal to manage driver instances:
JAVA1public class BaseTest { 2 private static final ThreadLocal<WebDriver> driverThreadLocal = new ThreadLocal<>(); 3 4 protected WebDriver getDriver() { 5 return driverThreadLocal.get(); 6 } 7 8 @BeforeMethod 9 public void setUp() { 10 WebDriverManager.chromedriver().setup(); 11 ChromeOptions options = new ChromeOptions(); 12 options.addArguments("--headless", "--no-sandbox", "--disable-dev-shm-usage"); 13 driverThreadLocal.set(new ChromeDriver(options)); 14 } 15 16 @AfterMethod 17 public void tearDown() { 18 if (getDriver() != null) { 19 getDriver().quit(); 20 driverThreadLocal.remove(); 21 } 22 } 23}
CI/CD Integration
GitHub Actions (Java + Maven)
YAML1name: Selenium Tests 2on: [push, pull_request] 3 4jobs: 5 selenium: 6 runs-on: ubuntu-latest 7 steps: 8 - uses: actions/checkout@v4 9 - uses: actions/setup-java@v4 10 with: 11 java-version: '21' 12 distribution: 'temurin' 13 cache: maven 14 - name: Run Selenium tests 15 run: mvn test -Dheadless=true 16 - name: Publish test results 17 uses: dorny/test-reporter@v1 18 if: always() 19 with: 20 name: Selenium Results 21 path: target/surefire-reports/*.xml 22 reporter: java-junit
What to Try Next
With Selenium running locally and in CI, the natural next steps are:
- Cross-browser testing with Sauce Labs or BrowserStack for real device/browser combinations
- Screenshot on failure for better debugging — add a
@AfterEachmethod that captures a screenshot on test failure - Reporting with Allure or Extent Reports for rich HTML reports
- Comparing Selenium to Playwright for new projects — see our Playwright guide to understand the tradeoffs
For demo sites to practice your Selenium skills before testing on your own app, see our UI Automation Testing Demo Websites guide.
Share this article
Follow for more
Follow me on social media for more developer tips, tricks, and tutorials. Let's connect and build something great together!