Skills101

Software Testing & Automation · Lesson 3 of 11 · Updated ·

Selenium Locators — id, name, CSS Selectors and XPath with Examples

A test is only as stable as its locators — the way it finds elements on the page. Good locators survive design changes; bad ones break every sprint.

Locator choices from best to worst: By.id, By.name, By.cssSelector, By.xpath, By.linkText, and className or tagName which are rarely unique

1. The page we test

Part of our practice login page:

<!-- File: src/test/resources/site/login.html (excerpt) -->
<form id="login-form">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" placeholder="you@example.com">
  <label for="password">Password</label>
  <input id="password" name="password" type="password">
  <button id="login-btn" class="btn primary" type="submit">Log in</button>
</form>
<p id="error" class="error" role="alert" hidden></p>
<a href="practice.html">Practice page</a>

2. Eight locator strategies

// File: src/test/java/com/skills101/qa/ui/LocatorsTest.java
package com.skills101.qa.ui;

import static org.testng.Assert.assertEquals;

import com.skills101.qa.base.BaseUiTest;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.testng.annotations.Test;

public class LocatorsTest extends BaseUiTest {

    @Test
    public void eightWaysToFindTheSameKindOfElement() {
        driver.get(baseUrl + "/login.html");

        WebElement byId = driver.findElement(By.id("email"));
        WebElement byName = driver.findElement(By.name("password"));
        WebElement byClass = driver.findElement(By.className("primary"));
        WebElement byTag = driver.findElement(By.tagName("h1"));
        WebElement byLinkText = driver.findElement(By.linkText("Practice page"));
        WebElement byPartialLink = driver.findElement(By.partialLinkText("Practice"));
        WebElement byCss = driver.findElement(By.cssSelector("input[type='email']"));
        WebElement byXpath = driver.findElement(By.xpath("//button[text()='Log in']"));

        assertEquals(byId.getDomAttribute("placeholder"), "you@example.com");
        assertEquals(byName.getDomAttribute("type"), "password");
        assertEquals(byClass.getText(), "Log in");
        assertEquals(byTag.getText(), "Log in");
        assertEquals(byLinkText.getDomProperty("href"), baseUrl + "/practice.html");
        assertEquals(byPartialLink, byLinkText);   // same element
        assertEquals(byCss, byId);
        assertEquals(byXpath, byClass);
    }

    @Test
    public void findElementsReturnsAList() {
        driver.get(baseUrl + "/practice.html");

        List<WebElement> rows = driver.findElements(By.cssSelector("#marks tbody tr"));
        assertEquals(rows.size(), 3);

        // XPath can move from one cell to its neighbour: the marks next to "Neha"
        String nehaMarks = driver.findElement(By.xpath("//table[@id='marks']//td[text()='Neha']/following-sibling::td")).getText();
        assertEquals(nehaMarks, "85");

        // findElements never throws: an empty list means "not on the page"
        assertEquals(driver.findElements(By.id("does-not-exist")).size(), 0);
    }
}

Both tests pass. The first one proves that the different locators find the same elements; the second reads a table and shows that findElements returns an empty list instead of failing.

3. CSS selector cheat sheet

CSSFinds
#emailid = email
.primaryclass contains primary
input[type='email']attribute equals
a[href*='practice']attribute contains
input[name^='pass']attribute starts with
#marks tbody trdescendants (any depth)
form > buttondirect child
tr:nth-child(2) td:first-childposition

4. XPath cheat sheet

XPathFinds
//button[@id='login-btn']any button with that id
//button[text()='Log in']exact text
//a[contains(text(),'Practice')]text contains
//input[@type='email' and @name='email']two conditions
//td[text()='Neha']/following-sibling::tdthe next cell
//td[text()='Neha']/parent::trgo up to the row
(//tr)[2]the second row on the page

Absolute XPath (/html/body/div[2]/form/input[1]) breaks as soon as the layout changes — always use relative XPath starting with //.

CSS vs XPath: CSS is shorter and a little faster; XPath can match text and move up to parents. Use CSS by default, XPath when you need text or parents.

5. Best practices

  • Prefer id, name or a dedicated test attribute: ask developers to add data-testid="login-button" and use By.cssSelector("[data-testid='login-button']").
  • Avoid auto-generated ids (id="ember1234") and long position-based paths.
  • Check a locator in Chrome DevTools first: press Ctrl+F in the Elements panel and type the CSS or XPath.

Common mistakes

  • By.className("btn primary") — className takes one class; use By.cssSelector(".btn.primary").
  • Absolute XPath copied from DevTools.
  • Locators that depend on text that will be translated (e.g. Hindi and English versions of the site).

Quick recap

  • Order of preference: id → name → CSS → XPath (for text/parents) → linkText.
  • CSS: #id, .class, [attr='v'], parent child. XPath: //tag[@attr='v'], text(), contains(), axes like following-sibling and parent.
  • Ask for data-testid attributes; never use absolute XPath.

Practice what you learned

📝 Quiz: Locators5 questions · instant answers & explanations · +10 XP · counts towards your free certificateTake the quiz →