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"))
)