Advanced
Chapter 20 · 13 min read
Best Practices & Design Patterns
Master test automation best practices — retry logic, test independence, data management, flaky test handling, and advanced design patterns.
Best Practices & Design Patterns
Writing Selenium tests is easy. Writing maintainable, reliable, fast Selenium tests that scale — that's the real skill. These best practices come from years of industry experience and will save you countless hours of debugging.
Golden Rules
- Tests must be independent — no test should depend on another test's state
- Use explicit waits — never Thread.sleep, never implicit + explicit together
- Keep tests fast — use API calls for setup/teardown, not UI
- One assertion theme per test — test one behavior, not everything
- Clean up after yourself — delete test data, reset state
RetryAnalyzer.java
package com.selenium.course.utils;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
// Retry flaky tests automatically
public class RetryAnalyzer implements IRetryAnalyzer {
private int retryCount = 0;
private static final int MAX_RETRIES = 2;
@Override
public boolean retry(ITestResult result) {
if (retryCount < MAX_RETRIES) {
retryCount++;
System.out.println("Retrying test: " + result.getName()
+ " (attempt " + (retryCount + 1) + ")");
return true;
}
return false;
}
}
// Usage: @Test(retryAnalyzer = RetryAnalyzer.class)
// public void testFlaky() { ... }
BestPracticesTest.java
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.*;
import org.testng.Assert;
import org.testng.annotations.*;
import java.time.Duration;
public class BestPracticesTest {
WebDriver driver;
WebDriverWait wait;
@BeforeMethod
public void setup() {
driver = new ChromeDriver();
// Use explicit waits, not implicit
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
// GOOD: Single responsibility
@Test
public void testFormFieldAcceptsInput() {
driver.get("https://www.selenium.dev/selenium/web/web-form.html");
WebElement input = wait.until(
ExpectedConditions.elementToBeClickable(By.name("my-text")));
input.sendKeys("Test value");
Assert.assertEquals(input.getAttribute("value"), "Test value");
}
// GOOD: Use helper methods to reduce duplication
private WebElement waitAndClick(By locator) {
WebElement element = wait.until(
ExpectedConditions.elementToBeClickable(locator));
element.click();
return element;
}
private void waitAndType(By locator, String text) {
WebElement element = wait.until(
ExpectedConditions.visibilityOfElementLocated(locator));
element.clear();
element.sendKeys(text);
}
// GOOD: Descriptive test names
@Test
public void shouldDisplayErrorWhenSubmittingEmptyForm() {
driver.get("https://www.selenium.dev/selenium/web/web-form.html");
waitAndClick(By.cssSelector("button[type='submit']"));
// Verify error state
}
// GOOD: Use @DataProvider for multiple scenarios
@DataProvider(name = "validInputs")
public Object[][] validInputs() {
return new Object[][] {
{ "Simple text" },
{ "Text with numbers 123" },
{ "Special chars !@#" },
};
}
@Test(dataProvider = "validInputs")
public void shouldAcceptVariousInputTypes(String input) {
driver.get("https://www.selenium.dev/selenium/web/web-form.html");
waitAndType(By.name("my-text"), input);
WebElement field = driver.findElement(By.name("my-text"));
Assert.assertEquals(field.getAttribute("value"), input);
}
@AfterMethod
public void teardown() { if (driver != null) driver.quit(); }
}
utils/retry.py
"""Retry decorator for flaky tests"""
import functools, time
def retry(max_attempts=3, delay=1):
"""Retry a test function on failure"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
if attempt < max_attempts:
print(f"Retry {attempt}/{max_attempts}: {e}")
time.sleep(delay)
raise last_exception
return wrapper
return decorator
# Or use pytest-rerunfailures plugin:
# pip install pytest-rerunfailures
# pytest --reruns 2 --reruns-delay 1
test_best_practices.py
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
import pytest
@pytest.fixture
def driver():
d = webdriver.Chrome()
yield d
d.quit()
# Helper functions — reduce duplication
def wait_and_click(driver, locator, timeout=10):
element = WebDriverWait(driver, timeout).until(
EC.element_to_be_clickable(locator))
element.click()
return element
def wait_and_type(driver, locator, text, timeout=10):
element = WebDriverWait(driver, timeout).until(
EC.visibility_of_element_located(locator))
element.clear()
element.send_keys(text)
return element
# GOOD: Single responsibility
def test_form_field_accepts_input(driver):
driver.get("https://www.selenium.dev/selenium/web/web-form.html")
el = wait_and_type(driver, (By.NAME, "my-text"), "Test value")
assert el.get_attribute("value") == "Test value"
# GOOD: Descriptive names
def test_should_display_error_when_submitting_empty_form(driver):
driver.get("https://www.selenium.dev/selenium/web/web-form.html")
wait_and_click(driver, (By.CSS_SELECTOR, "button[type='submit']"))
# GOOD: Parametrize for multiple scenarios
@pytest.mark.parametrize("input_text", [
"Simple text",
"Text with numbers 123",
"Special chars !@#",
])
def test_should_accept_various_input_types(driver, input_text):
driver.get("https://www.selenium.dev/selenium/web/web-form.html")
wait_and_type(driver, (By.NAME, "my-text"), input_text)
field = driver.find_element(By.NAME, "my-text")
assert field.get_attribute("value") == input_text
# GOOD: Marks for selective execution
@pytest.mark.smoke
def test_critical_page_loads(driver):
driver.get("https://www.selenium.dev/")
assert "Selenium" in driver.title
# Run: pytest -m smoke
# Run: pytest --reruns 2 (with pytest-rerunfailures)
Selenium
Advanced
Best Practices & Design Patterns
Written by PV
© 2026 All Rights Reserved