Selenium find_element By.CLASS_NAME and CSS Class Selectors (2026)

Selenium find_element By.CLASS_NAME and CSS Class Selectors (2026)

In Selenium Python, find elements by class with By.CLASS_NAME (single class, no spaces) or By.CSS_SELECTOR with dot notation (.class-name, supports multiple classes). By.CLASS_NAME throws InvalidSelectorException if the class name contains spaces. For multiple classes use By.CSS_SELECTOR, ".class1.class2". For partial class matching use XPath: //div[contains(@class, 'partial-name')].

Key Takeaways

By.CLASS_NAME only accepts a single class name. If you pass "btn btn-primary" it throws InvalidSelectorException. Use By.CSS_SELECTOR, ".btn.btn-primary" for multiple classes.

By.CSS_SELECTOR with dots is more powerful. .btn (one class), .btn.btn-primary (both classes), .card.active (multiple), and even .btn:not(.disabled) — all work.

Order of classes doesn't matter in CSS selectors. .btn.primary matches class="primary btn" and class="btn primary btn-large" equally.

Use find_elements (plural) for class-based searches. Class names are rarely unique — you usually want all matching elements: driver.find_elements(By.CSS_SELECTOR, ".product-card").

Don't use By.CLASS_NAME for dynamically generated class names. CSS Modules, styled-components, and Tailwind generate unpredictable class names like _card_1a2b3. Use data-testid instead.

By.CLASS_NAME — Single Class

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/products")

# Find first element with this class
product = driver.find_element(By.CLASS_NAME, "product-card")

# Find ALL elements with this class
products = driver.find_elements(By.CLASS_NAME, "product-card")
print(f"Found {len(products)} products")

By.CLASS_NAME maps to getElementsByClassName() in the browser — fast and efficient.

By.CLASS_NAME Limitations

Cannot Use Multiple Classes

# ❌ Throws InvalidSelectorException
button = driver.find_element(By.CLASS_NAME, "btn btn-primary")
# Error: Compound class names not permitted

# ✅ Use CSS selector instead
button = driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")

Cannot Use Descendant Notation

# ❌ CSS descendant selector doesn't work in CLASS_NAME
driver.find_element(By.CLASS_NAME, "container .btn")

# ✅ Use CSS selector
driver.find_element(By.CSS_SELECTOR, ".container .btn")

By.CSS_SELECTOR — Full Power

CSS selectors give you everything By.CLASS_NAME can't:

# Single class
driver.find_element(By.CSS_SELECTOR, ".btn")

# Multiple classes (element must have BOTH)
driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")
driver.find_element(By.CSS_SELECTOR, ".card.active.featured")

# Class inside a parent
driver.find_element(By.CSS_SELECTOR, "#main-content .product-card")

# Class + element type
driver.find_element(By.CSS_SELECTOR, "button.submit-btn")
driver.find_element(By.CSS_SELECTOR, "div.modal-dialog")

# Class + attribute
driver.find_element(By.CSS_SELECTOR, "input.form-control[required]")
driver.find_element(By.CSS_SELECTOR, ".btn[data-action='delete']")

# Class with pseudo-class
driver.find_element(By.CSS_SELECTOR, ".list-item:first-child")
driver.find_element(By.CSS_SELECTOR, ".nav-item:not(.disabled)")

Finding Multiple Elements by Class

# Get all matching elements — returns list (empty if none)
items = driver.find_elements(By.CSS_SELECTOR, ".product-card")

# Process each
for item in items:
    title = item.find_element(By.CSS_SELECTOR, ".product-title").text
    price = item.find_element(By.CSS_SELECTOR, ".product-price").text
    print(f"{title}: {price}")

# Count elements
count = len(driver.find_elements(By.CSS_SELECTOR, ".notification"))
print(f"Active notifications: {count}")

# Check if at least one exists (no exception on zero results)
errors = driver.find_elements(By.CSS_SELECTOR, ".error-message")
if errors:
    print(f"Error: {errors[0].text}")

Partial Class Name Matching (XPath)

CSS selectors require exact class names. For partial matching:

# Contains substring in class attribute
driver.find_element(By.XPATH, "//div[contains(@class, 'alert')]")
# Matches: "alert", "alert-danger", "my-alert-box"

# Starts with
driver.find_element(By.XPATH, "//button[starts-with(@class, 'btn-')]")
# Matches: "btn-primary", "btn-danger", "btn-outline-secondary"

# Multiple class conditions
driver.find_element(
    By.XPATH,
    "//div[contains(@class, 'card') and contains(@class, 'active')]"
)

Searching Within a Container

Once you find a parent element, search for classes within it:

# Find container first
sidebar = driver.find_element(By.ID, "sidebar")

# Search within container (faster, more specific)
nav_links = sidebar.find_elements(By.CLASS_NAME, "nav-link")
active_link = sidebar.find_element(By.CSS_SELECTOR, ".nav-link.active")

This scopes the search and avoids ambiguity when the same class appears in multiple places.

With Explicit Waits

On JavaScript-rendered pages, wait for elements to appear:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)

# Wait for single element
card = wait.until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, ".product-card"))
)

# Wait for all elements to be present
wait.until(
    EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".search-result"))
)
results = driver.find_elements(By.CSS_SELECTOR, ".search-result")

# Wait for element to be clickable
btn = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, ".btn.btn-submit"))
)
btn.click()

# Wait for a class to appear on an element
wait.until(lambda d: "loaded" in d.find_element(By.ID, "map").get_attribute("class"))

Dynamic Class Names: When to Avoid By.CLASS_NAME

Modern CSS tooling generates unpredictable class names:

<!-- CSS Modules -->
<div class="_container_1a2b3 _active_4c5d6">

<!-- Tailwind CSS -->
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">

<!-- styled-components / Emotion -->
<div class="sc-abc123 hDfGhI">

These break between builds. Solutions:

# ✅ Use data-testid (most reliable)
driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-button']")

# ✅ Use aria attributes
driver.find_element(By.CSS_SELECTOR, "[role='button'][aria-label='Submit']")

# ✅ Use stable semantic attributes
driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
driver.find_element(By.CSS_SELECTOR, "input[name='email']")

# ✅ For Tailwind — use data attributes instead of classes
# Ask devs to add: data-testid="nav-menu"
driver.find_element(By.CSS_SELECTOR, "[data-testid='nav-menu']")

Common CSS Selector Patterns

# Form controls by type
email_input = driver.find_element(By.CSS_SELECTOR, "input.form-control[type='email']")
checkboxes = driver.find_elements(By.CSS_SELECTOR, "input.form-check-input[type='checkbox']")

# Bootstrap components
modal = driver.find_element(By.CSS_SELECTOR, ".modal.show")
active_tab = driver.find_element(By.CSS_SELECTOR, ".nav-link.active")
primary_btn = driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")

# Error states
error_fields = driver.find_elements(By.CSS_SELECTOR, ".form-control.is-invalid")
success_messages = driver.find_elements(By.CSS_SELECTOR, ".alert.alert-success")

# Navigation
breadcrumb_items = driver.find_elements(By.CSS_SELECTOR, ".breadcrumb-item")
active_breadcrumb = driver.find_element(By.CSS_SELECTOR, ".breadcrumb-item.active")

# Lists and tables
table_rows = driver.find_elements(By.CSS_SELECTOR, "tr.data-row")
selected_rows = driver.find_elements(By.CSS_SELECTOR, "tr.data-row.selected")

Practical Test Examples

Verify Active Navigation Item

def test_settings_nav_active(driver):
    driver.get("https://example.com/settings")
    wait = WebDriverWait(driver, 10)

    # Wait for nav to load
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".nav-item")))

    active_nav = driver.find_element(By.CSS_SELECTOR, ".nav-item.active")
    assert "Settings" in active_nav.text

Check Form Validation State

def test_form_validation(driver):
    wait = WebDriverWait(driver, 10)

    driver.get("https://example.com/register")

    # Submit empty form
    submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".btn-submit")))
    submit.click()

    # Wait for validation errors to appear
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".form-control.is-invalid")))

    # Count invalid fields
    invalid_fields = driver.find_elements(By.CSS_SELECTOR, ".form-control.is-invalid")
    assert len(invalid_fields) >= 3, f"Expected at least 3 invalid fields, got {len(invalid_fields)}"

    # Check error messages
    error_messages = driver.find_elements(By.CSS_SELECTOR, ".invalid-feedback")
    assert any("required" in msg.text.lower() for msg in error_messages)

Test Product Listing

def test_product_listing(driver):
    wait = WebDriverWait(driver, 10)
    driver.get("https://shop.example.com/products")

    # Wait for products to load
    wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".product-card")))

    products = driver.find_elements(By.CSS_SELECTOR, ".product-card")
    assert len(products) > 0, "No products loaded"

    # Verify each product has required elements
    for product in products:
        assert product.find_element(By.CSS_SELECTOR, ".product-title").text
        assert product.find_element(By.CSS_SELECTOR, ".product-price").text

    # Check featured products
    featured = driver.find_elements(By.CSS_SELECTOR, ".product-card.featured")
    print(f"Featured products: {len(featured)}")

Test Modal Behavior

def test_delete_confirmation_modal(driver):
    wait = WebDriverWait(driver, 10)
    driver.get("https://example.com/items")

    # Click delete on first item
    delete_btn = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".btn.btn-danger.delete-item")))
    delete_btn.click()

    # Wait for modal
    modal = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".modal.show")))

    # Confirm in modal
    confirm = wait.until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, ".modal.show .btn.btn-danger"))
    )
    confirm.click()

    # Wait for modal to close
    wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".modal")))

    # Verify item removed
    wait.until(EC.staleness_of(modal))

By.CLASS_NAME vs By.CSS_SELECTOR Comparison

Scenario By.CLASS_NAME By.CSS_SELECTOR
Single class .find_element(By.CLASS_NAME, "card") .find_element(By.CSS_SELECTOR, ".card")
Multiple classes ❌ Not supported .find_element(By.CSS_SELECTOR, ".card.active")
Class + type ❌ Not supported .find_element(By.CSS_SELECTOR, "div.card")
Class + attribute ❌ Not supported .find_element(By.CSS_SELECTOR, ".card[data-id]")
Child selector ❌ Not supported .find_element(By.CSS_SELECTOR, ".parent .child")
Pseudo-class ❌ Not supported .find_element(By.CSS_SELECTOR, ".item:first-child")
Negation ❌ Not supported .find_element(By.CSS_SELECTOR, ".btn:not(.disabled)")

Recommendation: Always use By.CSS_SELECTOR — it's a strict superset of By.CLASS_NAME and more flexible.

The Alternative: AI-Powered Element Finding

Class-based selectors break when CSS frameworks change or designers rename classes. HelpMeTest finds elements by visual context:

Click the primary blue "Submit" button
Verify the success toast appears
Select the first product card
Click its "Add to Cart" button

The AI identifies "primary blue Submit button" visually — it doesn't care if the class is .btn-primary, .button--primary, or bg-blue-500. Class renames don't break tests.

Class-based selectors are right when:

  • Classes are stable, meaningful, and developer-controlled
  • Testing state changes that manifest as class changes (.active, .error, .selected)
  • Large Selenium codebases with established patterns

When to reconsider:

  • Styling framework changes break tests frequently
  • Designers regularly rename CSS classes
  • Teams want non-technical testers to write tests

Summary

Find elements by class in Selenium Python:

# Single class (By.CLASS_NAME)
element = driver.find_element(By.CLASS_NAME, "product-card")
elements = driver.find_elements(By.CLASS_NAME, "product-card")

# Single class (CSS selector — equivalent but more flexible)
element = driver.find_element(By.CSS_SELECTOR, ".product-card")

# Multiple classes
element = driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")

# Class + element type
element = driver.find_element(By.CSS_SELECTOR, "button.submit-btn")

# Class + attribute
element = driver.find_element(By.CSS_SELECTOR, ".form-control[required]")

# Partial class (XPath)
element = driver.find_element(By.XPATH, "//div[contains(@class, 'alert')]")

# With explicit wait
element = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, ".modal.show"))
)

Read more