Selenium WebDriver core API for automating web browsers across different platforms and programming languages.
—
Utilities for implementing the Page Object Model pattern including element initialization, locator annotations, and event handling for maintainable test automation.
Utility class for initializing Page Object Model classes with automatic element location and caching.
/**
* PageFactory utility for initializing Page Object Model classes
* Automatically initializes WebElement fields marked with @FindBy annotations
*/
class PageFactory {
/**
* Initialize page object elements using default element locator factory
* @param driver - WebDriver instance for element location
* @param page - Page object instance to initialize
*/
static void initElements(WebDriver driver, Object page);
/**
* Initialize page object elements using custom element locator factory
* @param factory - Custom ElementLocatorFactory for element location strategy
* @param page - Page object instance to initialize
*/
static void initElements(ElementLocatorFactory factory, Object page);
/**
* Initialize page object elements with custom field decorator
* @param decorator - FieldDecorator for customizing element initialization
* @param page - Page object instance to initialize
*/
static void initElements(FieldDecorator decorator, Object page);
}Annotation for marking WebElement fields with their location strategy.
/**
* FindBy annotation for specifying element location strategy
* Used on WebElement fields in Page Object classes
*/
@interface FindBy {
/**
* Locate by ID attribute
* @return Element ID value
*/
String id() default "";
/**
* Locate by name attribute
* @return Element name value
*/
String name() default "";
/**
* Locate by CSS class name
* @return Element class name
*/
String className() default "";
/**
* Locate by HTML tag name
* @return Element tag name
*/
String tagName() default "";
/**
* Locate by XPath expression
* @return XPath expression
*/
String xpath() default "";
/**
* Locate by CSS selector
* @return CSS selector expression
*/
String css() default "";
/**
* Locate link by exact text
* @return Link text content
*/
String linkText() default "";
/**
* Locate link by partial text
* @return Partial link text content
*/
String partialLinkText() default "";
}Annotation for combining multiple locators with AND logic.
/**
* FindBys annotation for chaining multiple locators with AND logic
* Element must match ALL specified locator conditions
*/
@interface FindBys {
/**
* Array of FindBy annotations that must all match
* @return Array of FindBy conditions
*/
FindBy[] value();
}Annotation for combining multiple locators with OR logic.
/**
* FindAll annotation for combining multiple locators with OR logic
* Element matches if ANY of the specified locator conditions match
*/
@interface FindAll {
/**
* Array of FindBy annotations where any can match
* @return Array of FindBy conditions
*/
FindBy[] value();
}Annotation for caching element lookups to improve performance.
/**
* CacheLookup annotation for caching WebElement lookups
* Element is located once and cached for subsequent access
* Use with caution on dynamic content
*/
@interface CacheLookup {
}import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
// Login page object class
public class LoginPage {
private WebDriver driver;
// Element declarations with @FindBy annotations
@FindBy(id = "username")
private WebElement usernameField;
@FindBy(id = "password")
private WebElement passwordField;
@FindBy(xpath = "//button[@type='submit']")
private WebElement loginButton;
@FindBy(className = "error-message")
private WebElement errorMessage;
@FindBy(linkText = "Forgot Password?")
private WebElement forgotPasswordLink;
// Constructor
public LoginPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
// Page actions
public void enterUsername(String username) {
usernameField.clear();
usernameField.sendKeys(username);
}
public void enterPassword(String password) {
passwordField.clear();
passwordField.sendKeys(password);
}
public void clickLogin() {
loginButton.click();
}
public void login(String username, String password) {
enterUsername(username);
enterPassword(password);
clickLogin();
}
public String getErrorMessage() {
return errorMessage.getText();
}
public boolean isErrorDisplayed() {
return errorMessage.isDisplayed();
}
public void clickForgotPassword() {
forgotPasswordLink.click();
}
}
// Usage in test
WebDriver driver = new ChromeDriver();
LoginPage loginPage = new LoginPage(driver);
loginPage.login("testuser", "testpass");public class AdvancedPage {
@FindBy(css = "input[data-testid='search-input']")
private WebElement searchBox;
@FindBy(xpath = "//div[@class='product-list']//div[contains(@class, 'product-item')]")
private List<WebElement> productItems;
// Using FindBys for AND logic (element must match ALL conditions)
@FindBys({
@FindBy(className = "btn"),
@FindBy(xpath = ".//span[text()='Submit']")
})
private WebElement submitButton;
// Using FindAll for OR logic (element matches ANY condition)
@FindAll({
@FindBy(id = "closeButton"),
@FindBy(xpath = "//button[text()='Close']"),
@FindBy(css = ".close-btn")
})
private WebElement closeButton;
// Cached lookup for static elements
@CacheLookup
@FindBy(id = "header-logo")
private WebElement headerLogo;
// Multiple elements with same locator
@FindBy(className = "nav-item")
private List<WebElement> navigationItems;
public AdvancedPage(WebDriver driver) {
PageFactory.initElements(driver, this);
}
public void searchFor(String query) {
searchBox.clear();
searchBox.sendKeys(query);
searchBox.submit();
}
public int getProductCount() {
return productItems.size();
}
public List<String> getNavigationItemTexts() {
return navigationItems.stream()
.map(WebElement::getText)
.collect(Collectors.toList());
}
}import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
public class WaitingPage {
private WebDriver driver;
private WebDriverWait wait;
@FindBy(id = "dynamic-content")
private WebElement dynamicContent;
@FindBy(className = "loading-spinner")
private WebElement loadingSpinner;
@FindBy(id = "ajax-button")
private WebElement ajaxButton;
@FindBy(css = ".result-item")
private List<WebElement> results;
public WaitingPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
PageFactory.initElements(driver, this);
}
public void waitForPageLoad() {
wait.until(ExpectedConditions.invisibilityOf(loadingSpinner));
wait.until(ExpectedConditions.visibilityOf(dynamicContent));
}
public void clickAjaxButtonAndWait() {
ajaxButton.click();
wait.until(ExpectedConditions.invisibilityOf(loadingSpinner));
wait.until(driver -> results.size() > 0);
}
public String getDynamicContentText() {
wait.until(ExpectedConditions.visibilityOf(dynamicContent));
return dynamicContent.getText();
}
}// Base page class with common elements
public abstract class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
@FindBy(id = "header")
protected WebElement header;
@FindBy(id = "footer")
protected WebElement footer;
@FindBy(className = "user-menu")
protected WebElement userMenu;
@FindBy(linkText = "Logout")
protected WebElement logoutLink;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
PageFactory.initElements(driver, this);
}
public void logout() {
userMenu.click();
logoutLink.click();
}
public String getPageTitle() {
return driver.getTitle();
}
public abstract boolean isPageLoaded();
}
// Specific page extending base
public class DashboardPage extends BasePage {
@FindBy(className = "dashboard-widget")
private List<WebElement> widgets;
@FindBy(id = "welcome-message")
private WebElement welcomeMessage;
@FindBy(css = ".quick-action-btn")
private List<WebElement> quickActionButtons;
public DashboardPage(WebDriver driver) {
super(driver);
}
@Override
public boolean isPageLoaded() {
return welcomeMessage.isDisplayed() && widgets.size() > 0;
}
public int getWidgetCount() {
return widgets.size();
}
public void clickQuickAction(String actionText) {
quickActionButtons.stream()
.filter(btn -> btn.getText().equals(actionText))
.findFirst()
.ifPresent(WebElement::click);
}
}import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
// Custom locator factory with retry logic
public class RetryingElementLocatorFactory implements ElementLocatorFactory {
private final WebDriver driver;
private final int timeOutInSeconds;
public RetryingElementLocatorFactory(WebDriver driver, int timeOutInSeconds) {
this.driver = driver;
this.timeOutInSeconds = timeOutInSeconds;
}
@Override
public ElementLocator createLocator(Field field) {
return new RetryingElementLocator(driver, field, timeOutInSeconds);
}
}
// Custom element locator with retry mechanism
public class RetryingElementLocator implements ElementLocator {
private final WebDriver driver;
private final By by;
private final boolean shouldCache;
private final int timeOutInSeconds;
private WebElement cachedElement;
private List<WebElement> cachedElementList;
public RetryingElementLocator(WebDriver driver, Field field, int timeOutInSeconds) {
this.driver = driver;
this.timeOutInSeconds = timeOutInSeconds;
this.shouldCache = field.getAnnotation(CacheLookup.class) != null;
this.by = buildByFromField(field);
}
@Override
public WebElement findElement() {
if (shouldCache && cachedElement != null) {
return cachedElement;
}
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(timeOutInSeconds));
WebElement element = wait.until(ExpectedConditions.presenceOfElementLocated(by));
if (shouldCache) {
cachedElement = element;
}
return element;
}
@Override
public List<WebElement> findElements() {
if (shouldCache && cachedElementList != null) {
return cachedElementList;
}
List<WebElement> elements = driver.findElements(by);
if (shouldCache) {
cachedElementList = elements;
}
return elements;
}
private By buildByFromField(Field field) {
FindBy findBy = field.getAnnotation(FindBy.class);
if (findBy != null) {
return buildByFromFindBy(findBy);
}
throw new IllegalArgumentException("Field must be annotated with @FindBy");
}
private By buildByFromFindBy(FindBy findBy) {
if (!findBy.id().isEmpty()) return By.id(findBy.id());
if (!findBy.name().isEmpty()) return By.name(findBy.name());
if (!findBy.className().isEmpty()) return By.className(findBy.className());
if (!findBy.tagName().isEmpty()) return By.tagName(findBy.tagName());
if (!findBy.xpath().isEmpty()) return By.xpath(findBy.xpath());
if (!findBy.css().isEmpty()) return By.cssSelector(findBy.css());
if (!findBy.linkText().isEmpty()) return By.linkText(findBy.linkText());
if (!findBy.partialLinkText().isEmpty()) return By.partialLinkText(findBy.partialLinkText());
throw new IllegalArgumentException("No locator strategy specified in @FindBy");
}
}
// Usage with custom factory
public class CustomPage {
@FindBy(id = "slow-loading-element")
private WebElement slowElement;
public CustomPage(WebDriver driver) {
RetryingElementLocatorFactory factory = new RetryingElementLocatorFactory(driver, 15);
PageFactory.initElements(factory, this);
}
}// Complete page object example with best practices
public class ProductPage {
private final WebDriver driver;
private final WebDriverWait wait;
// Page elements
@FindBy(id = "product-title")
private WebElement productTitle;
@FindBy(className = "price")
private WebElement price;
@FindBy(id = "add-to-cart")
private WebElement addToCartButton;
@FindBy(id = "quantity")
private WebElement quantityInput;
@FindBy(css = ".product-image img")
private WebElement productImage;
@FindBy(className = "reviews")
private List<WebElement> reviews;
public ProductPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
PageFactory.initElements(driver, this);
}
// Verification methods
public boolean isProductPageLoaded() {
return productTitle.isDisplayed() && price.isDisplayed();
}
// Information retrieval methods
public String getProductTitle() {
return productTitle.getText();
}
public String getPrice() {
return price.getText();
}
public int getReviewCount() {
return reviews.size();
}
// Action methods
public void setQuantity(int quantity) {
quantityInput.clear();
quantityInput.sendKeys(String.valueOf(quantity));
}
public void addToCart() {
wait.until(ExpectedConditions.elementToBeClickable(addToCartButton));
addToCartButton.click();
}
public void addToCartWithQuantity(int quantity) {
setQuantity(quantity);
addToCart();
}
// Navigation methods
public CartPage addToCartAndGoToCart(int quantity) {
addToCartWithQuantity(quantity);
// Wait for success indication or navigate to cart
return new CartPage(driver);
}
// Validation methods
public boolean isAddToCartButtonEnabled() {
return addToCartButton.isEnabled();
}
public boolean isProductImageDisplayed() {
return productImage.isDisplayed();
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-seleniumhq-selenium--selenium-api