Series Navigation
→ Part 5: WebElement Interactions — Click, Type, Select, Hover
The Eight Locator Strategies
driver.findElement(By.id("username"));
driver.findElement(By.name("email"));
driver.findElement(By.className("btn-primary"));
driver.findElement(By.tagName("h1"));
driver.findElement(By.linkText("Sign in"));
driver.findElement(By.partialLinkText("Sign"));
driver.findElement(By.xpath("//input[@type='email']"));
driver.findElement(By.cssSelector("input[type='email']"));
Priority order — most stable to most brittle:
id ← fastest, most stable, always prefer
name ← stable if unique
cssSelector ← fast, readable, recommended over xpath
xpath ← powerful, flexible, slower
linkText ← good for links
className ← risky if multiple classes
tagName ← too generic for most uses
partialLink ← useful for dynamic text
Inspecting Elements in Chrome DevTools
Before writing a locator, inspect the element:
1. Open Chrome → right-click any element → Inspect
2. DevTools opens → element is highlighted in the HTML panel
3. Right-click the element in HTML panel:
→ Copy → Copy selector (gives CSS selector)
→ Copy → Copy XPath (gives absolute XPath)
→ Copy → Copy JS path (gives JavaScript path)
DevTools Console — test your locator before writing code:
// Test CSS selector in DevTools console
document.querySelector("#username")
// Returns the element if found, null if not
// Test if multiple elements match (should be exactly 1 for unique locators)
document.querySelectorAll(".btn-primary").length
// Returns count of matching elements
// Test XPath
$x("//input[@id='username']")
// Returns array of matching elements
This saves enormous time — test the locator before running the full test.
By.id() — Always Your First Choice
The id attribute is unique per page by HTML spec. It's the fastest locator because browsers index by ID.
<input id="username" type="text" placeholder="Enter username">
<button id="login-btn" class="btn btn-primary">Login</button>
WebElement usernameField = driver.findElement(By.id("username"));
WebElement loginButton = driver.findElement(By.id("login-btn"));
When ID doesn't work:
- ID is dynamically generated:
id="j_idt47:j_idt52"(JSF frameworks often do this) - ID changes on every page load
- No ID attribute at all
In these cases, move to CSS or XPath.
By.name() — Good for Form Fields
HTML forms traditionally use name attributes for form submission:
<input type="email" name="userEmail" id="email-field">
<input type="password" name="userPassword">
WebElement email = driver.findElement(By.name("userEmail"));
WebElement password = driver.findElement(By.name("userPassword"));
name is stable for form inputs because changing it breaks form submission.
By.cssSelector() — Fast and Readable
CSS selectors are faster than XPath and more readable. Learn these patterns:
// By ID (same as By.id)
driver.findElement(By.cssSelector("#username"));
// By class
driver.findElement(By.cssSelector(".btn-primary"));
// By tag
driver.findElement(By.cssSelector("button"));
// By attribute
driver.findElement(By.cssSelector("input[type='email']"));
driver.findElement(By.cssSelector("input[placeholder='Search...']"));
// By data attribute (preferred for test automation)
driver.findElement(By.cssSelector("[data-testid='login-button']"));
driver.findElement(By.cssSelector("[data-qa='submit']"));
// Child element (direct child)
driver.findElement(By.cssSelector("form > button"));
// Descendant (any level)
driver.findElement(By.cssSelector("form button.submit"));
// Adjacent sibling
driver.findElement(By.cssSelector("label + input"));
// Attribute starts with
driver.findElement(By.cssSelector("a[href^='https']"));
// Attribute ends with
driver.findElement(By.cssSelector("a[href$='.pdf']"));
// Attribute contains
driver.findElement(By.cssSelector("a[href*='login']"));
// Nth child
driver.findElement(By.cssSelector("table tr:nth-child(3)"));
driver.findElement(By.cssSelector("ul li:first-child"));
driver.findElement(By.cssSelector("ul li:last-child"));
// Combining selectors
driver.findElement(By.cssSelector("div#loginForm input[type='password']"));
By.xpath() — Powerful but Use Carefully
XPath is an XML query language. It's the most powerful locator but also slowest.
Absolute vs Relative XPath
// ABSOLUTE — starts from root, breaks if page structure changes
// Never use this in production tests
driver.findElement(By.xpath("/html/body/div[2]/form/div[1]/input"));
// RELATIVE — starts from anywhere in DOM, much more stable
// Always use // at the start
driver.findElement(By.xpath("//input[@id='username']"));
XPath Syntax Reference
// By attribute
driver.findElement(By.xpath("//input[@id='username']"));
driver.findElement(By.xpath("//input[@type='email']"));
driver.findElement(By.xpath("//input[@name='userEmail']"));
// By text (exact match)
driver.findElement(By.xpath("//button[text()='Login']"));
driver.findElement(By.xpath("//a[text()='Sign in']"));
// By text (partial match)
driver.findElement(By.xpath("//button[contains(text(),'Login')]"));
driver.findElement(By.xpath("//div[contains(@class,'btn-primary')]"));
// By attribute contains
driver.findElement(By.xpath("//a[contains(@href,'login')]"));
driver.findElement(By.xpath("//input[contains(@placeholder,'Search')]"));
// By attribute starts-with (useful for dynamic IDs)
driver.findElement(By.xpath("//input[starts-with(@id,'user')]"));
// AND condition
driver.findElement(By.xpath("//input[@type='text' and @name='username']"));
// OR condition
driver.findElement(By.xpath("//input[@type='email' or @type='text']"));
// Index (1-based in XPath, unlike CSS which is 1-based too)
driver.findElement(By.xpath("(//input[@type='text'])[1]")); // first
driver.findElement(By.xpath("(//input[@type='text'])[2]")); // second
driver.findElement(By.xpath("(//input[@type='text'])[last()]")); // last
// Parent element
driver.findElement(By.xpath("//input[@id='username']/.."));
// Child element
driver.findElement(By.xpath("//form/input[@type='email']"));
// Sibling elements
driver.findElement(By.xpath("//label[@for='username']/following-sibling::input"));
driver.findElement(By.xpath("//input[@id='password']/preceding-sibling::label"));
XPath Axes — The Power Features
// Find element relative to a known element
// following-sibling: elements after, same parent
driver.findElement(By.xpath(
"//td[text()='John Doe']/following-sibling::td[1]"
));
// If table row is: | John Doe | Admin | Active | Edit |
// This finds the "Admin" cell
// preceding-sibling: elements before, same parent
driver.findElement(By.xpath(
"//td[text()='Active']/preceding-sibling::td[1]"
));
// Finds the "Admin" cell
// following: all elements after in document order
driver.findElement(By.xpath(
"//h2[text()='Login']/following::input[1]"
));
// ancestor: any parent element
driver.findElement(By.xpath(
"//input[@id='username']/ancestor::form"
));
// descendant: any child element
driver.findElement(By.xpath(
"//div[@class='login-container']/descendant::button"
));
Finding Multiple Elements
// findElements returns a List<WebElement> — never throws NoSuchElementException
List<WebElement> allLinks = driver.findElements(By.tagName("a"));
System.out.println("Total links: " + allLinks.size());
// Iterate and interact
List<WebElement> tableRows = driver.findElements(By.cssSelector("table tbody tr"));
for (WebElement row : tableRows) {
System.out.println(row.getText());
}
// Get specific element from list
List<WebElement> buttons = driver.findElements(By.cssSelector(".action-button"));
buttons.get(0).click(); // first button
buttons.get(buttons.size() - 1).click(); // last button
// Check element exists without throwing exception
List<WebElement> errorMsg = driver.findElements(By.cssSelector(".error-message"));
if (!errorMsg.isEmpty()) {
System.out.println("Error: " + errorMsg.get(0).getText());
}
Practical Real-World Locator Examples
HTML: Login Form
<form id="loginForm" class="auth-form">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="userEmail"
class="form-control" placeholder="Enter email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="userPassword"
class="form-control" placeholder="Enter password">
</div>
<div class="error-message" id="loginError" style="display:none">
Invalid credentials
</div>
<button type="submit" id="loginBtn" class="btn btn-primary btn-block">
Sign In
</button>
<a href="/forgot-password" class="forgot-link">Forgot password?</a>
</form>
// Locating elements in this form
WebElement emailField = driver.findElement(By.id("email"));
WebElement passwordField = driver.findElement(By.id("password"));
WebElement submitButton = driver.findElement(By.id("loginBtn"));
WebElement errorMessage = driver.findElement(By.id("loginError"));
WebElement forgotLink = driver.findElement(By.linkText("Forgot password?"));
// Alternative CSS approaches
emailField = driver.findElement(By.cssSelector("#loginForm input[type='email']"));
submitButton = driver.findElement(By.cssSelector("button[type='submit']"));
forgotLink = driver.findElement(By.cssSelector("a.forgot-link"));
// Alternative XPath approaches
emailField = driver.findElement(By.xpath("//input[@id='email']"));
submitButton = driver.findElement(By.xpath("//button[contains(text(),'Sign In')]"));
forgotLink = driver.findElement(By.xpath("//a[contains(@href,'forgot')]"));
HTML: Data Table
<table id="usersTable">
<thead>
<tr>
<th>Name</th><th>Email</th><th>Role</th><th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>alice@example.com</td>
<td>Admin</td>
<td><button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button></td>
</tr>
<tr>
<td>Bob</td>
<td>bob@example.com</td>
<td>User</td>
<td><button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button></td>
</tr>
</tbody>
</table>
// Get all rows in tbody
List<WebElement> rows = driver.findElements(
By.cssSelector("#usersTable tbody tr")
);
// Find Edit button for specific user by name
WebElement aliceEditBtn = driver.findElement(
By.xpath("//td[text()='Alice']/following-sibling::td//button[text()='Edit']")
);
// Get email for a specific user
WebElement bobEmail = driver.findElement(
By.xpath("//td[text()='Bob']/following-sibling::td[1]")
);
System.out.println(bobEmail.getText()); // bob@example.com
// Get text of all users in first column
List<WebElement> nameCells = driver.findElements(
By.cssSelector("#usersTable tbody tr td:first-child")
);
nameCells.forEach(cell -> System.out.println(cell.getText()));
Locator Best Practices
// ✅ Good — stable, unique, semantic
By.id("submitButton")
By.cssSelector("[data-testid='login-btn']")
By.cssSelector("input[name='email']")
// ⚠️ Use carefully — can match multiple elements
By.className("btn")
By.tagName("input")
// ❌ Bad — breaks on any structural change
By.xpath("/html/body/div[3]/div[1]/form/div[2]/input")
By.cssSelector("div > div > form > div > input")
// ❌ Bad — brittle if text changes (internationalisation)
By.xpath("//button[text()='Sign In']")
// ✅ Better — partial match is more resilient
By.xpath("//button[contains(text(),'Sign')]")
The golden rule: Discuss data-testid or data-qa attributes with your dev team and add them to interactive elements. This gives you the most stable locators that survive CSS and structural changes.
What's Next
In Part 5 we use these locators to actually interact with elements — typing text, clicking buttons, selecting dropdowns, handling checkboxes, radio buttons, and mouse hover actions.
Discussion
Loading...Leave a Comment
All comments are reviewed before appearing. No links please.