CSS Selectors in Selenium Java

In the previous article, you learned Selenium’s locator menu and a simple order of preference: unique id first, then a clear CSS selector when id is missing, then XPath when relationships demand it.

This lesson zooms into CSS. You will learn the selector patterns you will reuse on almost every real page: id, class, attributes, descendants, and combinations that stay short enough to trust.

Open https://testkru.com/Elements/TextFields in a second tab while you read. Almost every example below comes from that page, so you can inspect the same HTML and try the same selector in DevTools.

A CSS selector is a pattern that describes which elements in the DOM you mean. Front-end developers use selectors to style pages. Selenium reuses the same language to find controls for automation.

That reuse is why CSS feels natural in WebDriver: the browser already knows how to evaluate the pattern. In Java you pass the selector string to By.cssSelector(...), then call findElement or findElements.

WebElement lastName = driver.findElement(
        By.cssSelector("input[name='lastName']"));

Read that call in two layers:

  1. "input[name='lastName']" is ordinary CSS: “an input whose name attribute equals lastName.”
  2. By.cssSelector(...) wraps that pattern as a Selenium locator strategy.

Official locator docs list css selector as one of the eight traditional strategies: Selenium locator strategies. Selenium’s Java API notes that cssSelector finds elements through the driver’s selector engine (the browser’s CSS selector support): By.cssSelector Javadoc

Why this matters for beginners:

  • You do not invent a Selenium-only mini language for everyday attribute searches.
  • The same pattern you test in Chrome DevTools can move into your Java test with almost no change.
  • Selenium’s encouraged practice prefers a well-written CSS selector when unique ids are unavailable: Tips on working with locators.

Before you paste a selector into Java, prove it in the browser.

  1. Open the page and press F12 (or right-click Inspect).
  2. Open the Elements panel.
  3. Press Ctrl+F (Windows/Linux) or Cmd+F (macOS) inside that panel.
  4. Type your CSS selector in the search box.

DevTools highlights matches as you type. If you see zero matches, the selector is wrong for the live DOM. If you see many matches, findElement will still return only the first one, so tighten the pattern until one intended control stands out.

This habit saves hours later. A failing Java test often means “the selector never uniquely matched,” not “Selenium is broken.”

CSS has three building blocks you will use constantly. They map cleanly to HTML you already inspected in Module 1.

PatternMeaningExample
tagAny element with that tag nameinput, button, a
#idElement whose id equals the value#lastNameWithPlaceholder
.classElement that includes that class token.lastNameWithPlaceholder

On Text Fields, field 2 looks like this:

<input type="text" name="lastName" id="lastNameWithPlaceholder"
       class="pt-1 pb-1 pr-2 pl-2 lastNameWithPlaceholder"
       placeholder="Enter your last name...">

These three selectors all find that field today:

driver.findElement(By.cssSelector("input"));                 // too broad on most pages
driver.findElement(By.cssSelector("#lastNameWithPlaceholder"));
driver.findElement(By.cssSelector(".lastNameWithPlaceholder"));

#lastNameWithPlaceholder is the CSS form of “find this id.” It overlaps By.id("lastNameWithPlaceholder"). That overlap is normal.

For a single unique id, By.id remains the clearest beginner call. Teams sometimes standardize on CSS for every locator so the codebase uses one style. Both are valid. Do not rewrite a perfect By.id into CSS just for ceremony.

Look at the class list again: pt-1 pb-1 pr-2 pl-2 lastNameWithPlaceholder. The first four tokens are layout utilities shared by many fields. .pt-1 alone is a weak locator. .lastNameWithPlaceholder is specific on this page, so it is useful.

Rule of thumb: prefer classes that name the control’s role, not its padding.

By.className accepts one class token only. This fails:

// Wrong: compound class string
driver.findElement(By.className("pt-1 pb-1"));

CSS can require several classes on the same element by chaining dots with no space:

driver.findElement(By.cssSelector(".pt-1.pb-1.pr-2.pl-2.lastNameWithPlaceholder"));

That works, but it is still a poor everyday choice because it leans on layout classes. Prefer #lastNameWithPlaceholder or input[name='lastName'] here. Remember the compound-class rule for the day a meaningful multi-class marker is your only stable clue.

When id is missing, duplicated, or generated, attributes become your best friends. CSS attribute selectors let you match exact values or partial values.

Field 2 again is the teaching example:

driver.findElement(By.cssSelector("input[name='lastName']"));
driver.findElement(By.cssSelector("input[placeholder='Enter your last name...']"));
driver.findElement(By.cssSelector("[id='lastNameWithPlaceholder']"));

Quotes inside the CSS string matter in Java. A common, readable style is double quotes for the Java string and single quotes for the attribute value:

By.cssSelector("input[name='lastName']")
Selector shapeMatches whenExample
[attr='value']Attribute equals valueinput[name='lastName']
[attr]Attribute exists (any value)input[placeholder]
[attr^='value']Attribute starts with valueinput[id^='lastName']
[attr$='value']Attribute ends with valueinput[id$='Placeholder']
[attr*='value']Attribute contains valueinput[placeholder*='last name']

These partial forms are powerful on dynamic pages where part of an id stays stable (user_, order-, and so on). They are also easy to overuse. input[id*='Name'] might match more fields than you expect. Always confirm match count in DevTools.

A practical middle ground on Text Fields:

driver.findElement(By.cssSelector("input[id^='lastName']"));

That finds field 2 because its id starts with lastName. It stays shorter than copying a long placeholder string, and it still avoids the duplicated firstName trap on the same page.

[name='lastName'] can work alone. Prefacing the tag (input[name='lastName']) documents the kind of control you expect and reduces accidental matches on non-input nodes that somehow share the attribute. Prefer the clearer form unless you have a reason to stay tag-agnostic.

So far, every selector looked at one element in isolation. Combinators let you describe relationships between elements. That is how you scope a search: “the field inside this section,” not “any matching field on the whole page.”

You only need the everyday ones for this lesson:

CombinatorSyntaxMeaning
DescendantA B (space)B anywhere inside A
ChildA > BB is a direct child of A
Adjacent siblingA + BB immediately follows A
General siblingA ~ BB follows A under the same parent

MDN’s overview of these patterns is a good reference when you want more depth: CSS selectors and combinators.

On Text Fields, the practice inputs sit inside a wrapper with id="textFieldElements". That wrapper is a real scoping anchor.

// Matches: last name input nested somewhere under the section
driver.findElement(
        By.cssSelector("#textFieldElements input[name='lastName']"));

The space between #textFieldElements and input[...] is the descendant combinator. It means “find that input anywhere inside the section,” even if extra layout divs sit in between.

Descendant

Now compare the child combinator:

// Does NOT match on this page: the input is not a direct child
driver.findElement(
        By.cssSelector("#textFieldElements > input[name='lastName']"));
Child CSS

On the live page, each input is wrapped in nested layout divs, so #textFieldElements > input... finds nothing and Selenium throws NoSuchElementException. That contrast is the whole lesson: use > only when the element truly is a direct child. Use the descendant space when nesting depth can change without changing meaning.

Real apps use the same idea with forms, cards, dialogs, and side panels. Scope first, then name the control inside.

label + input finds an input that immediately follows a label. h2 ~ p finds paragraphs that follow a heading under the same parent. These help when a neighbor is easier to identify than the control’s own attributes.

Do not build long sibling chains to reconstruct the whole page. If you need “go up to a parent, then sideways, then down,” that path is often clearer in XPath, which the next lesson teaches.

A good CSS locator is specific enough to be unique and short enough to read aloud.

Weak:   body > div > div:nth-child(2) > div > input:nth-child(3)
Better: input[name='lastName']
Also good: #textFieldElements input[name='lastName']
Best:   #lastNameWithPlaceholder   (when that id is unique and stable)

Selenium’s own guidance matches this instinct: keep locators compact and readable, and narrow the search whenever you can. Long :nth-child chains feel precise and break when one marketing banner appears above your form.

CSS often expresses the same idea as a dedicated By method. Knowing the overlap helps you read other people’s tests.

GoalDedicated By methodEquivalent CSS idea
Match an idBy.id("x")#x
Match a nameBy.name("email")[name='email']
Match one class tokenBy.className("btn").btn
Match a tagBy.tagName("input")input
Match several clues at once(no single shortcut)input.btn[name='save']

Use the dedicated methods when they keep the line obvious. Reach for CSS when you need combinations, partial attribute matches, or scoped searches that those shortcuts cannot express alone.

Practical checklist before you commit a selector:

  1. Does it match exactly one intended element in DevTools?
  2. Does it anchor on a stable clue (id, name, role-like class, test id) rather than layout order?
  3. Can a teammate understand it without opening the page?
  4. Would a small UI copy change break it? (Prefer attributes over visible text when you can.)

CSS is excellent at tags, ids, classes, attributes, and downward/sibling structure. It is a weaker everyday tool for “find by visible text that is not a link” and for walking upward to parents. Those gaps are why Module 2 continues with XPath next, not because CSS failed.

This example stays in your existing Maven TestNG project. It opens Text Fields, finds the same last-name control with several CSS styles, types codekru, then uses an attribute selector on the Buttons page to find a disabled control.

Create CssSelectorsPracticeTest.java under src/test/java/com/codekru/tests/ (adjust the package if your project differs).

package com.codekru.tests;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class CssSelectorsPracticeTest {

    private WebDriver driver;

    @BeforeMethod
    public void setUp() {
        driver = new ChromeDriver();
    }

    @Test
    public void shouldFindElementsWithPracticalCssSelectors() {
        driver.get("https://testkru.com/Elements/TextFields");

        WebElement byIdCss = driver.findElement(
                By.cssSelector("#lastNameWithPlaceholder"));
        byIdCss.clear();
        byIdCss.sendKeys("codekru");
        Assert.assertEquals(byIdCss.getAttribute("value"), "codekru");

        WebElement byNameAttr = driver.findElement(
                By.cssSelector("input[name='lastName']"));
        Assert.assertEquals(
                byNameAttr.getAttribute("id"),
                "lastNameWithPlaceholder");

        WebElement byStartsWith = driver.findElement(
                By.cssSelector("input[id^='lastName']"));
        Assert.assertEquals(
                byStartsWith.getAttribute("placeholder"),
                "Enter your last name...");

        WebElement byClass = driver.findElement(
                By.cssSelector("input.lastNameWithPlaceholder"));
        Assert.assertEquals(byClass.getAttribute("name"), "lastName");

        WebElement byScoped = driver.findElement(
                By.cssSelector("#textFieldElements input[name='lastName']"));
        Assert.assertEquals(
                byScoped.getAttribute("id"),
                "lastNameWithPlaceholder");

        driver.get("https://testkru.com/Elements/Buttons");

        WebElement disabled = driver.findElement(
                By.cssSelector("button[id='disabledButton'][disabled]"));
        Assert.assertFalse(disabled.isEnabled());
        Assert.assertEquals(disabled.getText().trim(), "Disabled button");
    }

    @AfterMethod
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

What each part does:

  1. @BeforeMethod opens a fresh Chrome session.
  2. #lastNameWithPlaceholder shows the CSS id selector. Typing codekru proves you landed on an editable field.
  3. input[name='lastName'] is the everyday attribute pattern that replaces a missing unique id on many forms.
  4. input[id^='lastName'] practices a starts-with match without grabbing the duplicated firstName fields.
  5. input.lastNameWithPlaceholder combines tag + class. The meaningful class token is intentional; layout utilities are not.
  6. #textFieldElements input[name='lastName'] scopes the attribute search inside the practice section with a descendant combinator.
  7. On Buttons, button[id='disabledButton'][disabled] chains two attribute conditions: the id you want, and the presence of the disabled attribute.
  8. @AfterMethod calls quit() so the browser closes after pass or fail.
  1. Save the class in your test package.
  2. Run shouldFindElementsWithPracticalCssSelectors from IntelliJ or your usual TestNG/Maven command.
  3. Chrome should open Text Fields, type into the last name box, move to Buttons, pass the assertions, and close.

If a selector fails, paste the exact string into DevTools search on the same page. Fix the pattern there first, then update the Java string.

  1. Copying a long DevTools “Copy selector” path. Browser tools often generate brittle nth-child chains. Rewrite them into id or attribute form.
  2. Using a space when you meant one element. .a .b means “.b inside .a.” .a.b means “one element that has both classes.” That single space changes everything.
  3. Trusting layout classes. .pt-1 and friends match half the form. Prefer role-like classes or attributes.
  4. Forgetting that findElement returns the first match. Broad selectors such as input or .btn silently automate the wrong control.
  5. Putting two classes into By.className. Use CSS chaining (.one.two) instead.
  6. Matching on visible text with CSS by default. CSS is not the everyday tool for “the button labeled Save” unless that text lives in an attribute. Prefer attributes now; learn text-based XPath next when you need it.

Turn today’s patterns into muscle memory: inspect first, prove the selector in DevTools, then use By.cssSelector in Java.

Your task:

  1. Open the assignment page linked below.
  2. Inspect the target element and choose a CSS selector (id, class, or attribute).
  3. Confirm the selector matches the intended control in DevTools search.
  4. Write a Selenium Java test that finds it with By.cssSelector(...).
  5. Assert one fact that proves you found the right element.
  6. Do not look up a finished solution.

Continue practicing: CSS Selectors – Basics

When you want more attribute practice after that, continue with CSS Selectors – Attribute Selectors. Browse the full set anytime in the Selenium learning catalog.

You can now write CSS selectors that match by id, class, attributes, and simple relationships, and you know how to prove them in DevTools before they enter a test.

Next up is XPath: Relative Paths, Functions, Axes, and Dynamic Elements. You will learn when the tree relationship or nearby text makes XPath the clearer tool, and how to keep XPath relative instead of brittle.

  • A CSS selector is a DOM pattern; Selenium’s By.cssSelector asks the browser’s selector engine to find matching elements.
  • Prove selectors in DevTools search before you hard-code them in Java.
  • Start with #id, meaningful .class, and attribute selectors such as input[name='...'].
  • Use ^=, $=, and *= carefully for partial attribute matches on dynamic values.
  • Combinators ( , >, +, ~) scope searches by relationship; keep them short.
  • Prefer compact, stable clues over long nth-child paths.
  • Scope with descendant selectors such as #textFieldElements input[...] when a section wrapper exists.
  • Use CSS as the flexible default after unique id; save upward travel and many text cases for XPath.
Liked the article? Share this on

Leave a Comment

Your email address will not be published. Required fields are marked *