Locator Strategies Overview — ID, Name, Class, Tag and When to Use Each

Every Selenium test follows the same pattern: navigate to a page, find an element, interact with it, and verify the result. The “find an element” step is where most tests break. If your locator is fragile — tied to a CSS class that a developer renames, or an XPath that depends on exact DOM position — your test fails even though the application works perfectly. Choosing the right locator strategy is the single most important skill for building stable, maintainable Selenium tests.

The Eight Locator Strategies — Speed, Reliability and Trade-offs

Selenium provides eight ways to find elements on a page. Each has different strengths, and knowing when to use which one prevents the locator-related flakiness that plagues many automation projects.

from selenium.webdriver.common.by import By

# ── The 8 Selenium locator strategies ──

LOCATOR_STRATEGIES = [
    {
        "strategy": "By.ID",
        "example": 'driver.find_element(By.ID, "login-button")',
        "html": '<button id="login-button">Sign In</button>',
        "speed": "Fastest",
        "reliability": "Highest — IDs should be unique per page",
        "when_to_use": "Always the first choice when an ID is available",
        "limitation": "Not every element has an ID; some frameworks generate dynamic IDs",
    },
    {
        "strategy": "By.NAME",
        "example": 'driver.find_element(By.NAME, "username")',
        "html": '<input name="username" type="text">',
        "speed": "Fast",
        "reliability": "High — names are stable in form elements",
        "when_to_use": "Form fields (input, select, textarea) that have name attributes",
        "limitation": "Names are not guaranteed unique; multiple forms may reuse names",
    },
    {
        "strategy": "By.CSS_SELECTOR",
        "example": 'driver.find_element(By.CSS_SELECTOR, "form.login input[type=\'email\']")',
        "html": '<form class="login"><input type="email"></form>',
        "speed": "Fast",
        "reliability": "High when using stable attributes",
        "when_to_use": "Complex targeting: attribute combinations, hierarchy, nth-child",
        "limitation": "Cannot traverse upward (parent) or search by text content",
    },
    {
        "strategy": "By.XPATH",
        "example": 'driver.find_element(By.XPATH, "//button[text()=\'Submit\']")',
        "html": '<button>Submit</button>',
        "speed": "Slower than CSS",
        "reliability": "Medium — powerful but easy to write fragile paths",
        "when_to_use": "Text-based search, parent traversal, complex conditions",
        "limitation": "Verbose syntax; position-dependent XPaths are very fragile",
    },
    {
        "strategy": "By.CLASS_NAME",
        "example": 'driver.find_element(By.CLASS_NAME, "btn-primary")',
        "html": '<button class="btn btn-primary">Save</button>',
        "speed": "Fast",
        "reliability": "Low — classes are shared, change often for styling reasons",
        "when_to_use": "Only when the class is semantically meaningful and unique",
        "limitation": "Only matches a single class name; cannot use compound selectors",
    },
    {
        "strategy": "By.TAG_NAME",
        "example": 'driver.find_elements(By.TAG_NAME, "li")',
        "html": '<ul><li>Item 1</li><li>Item 2</li></ul>',
        "speed": "Fast",
        "reliability": "Low — returns all matching tags on the page",
        "when_to_use": "Counting elements or scoping within a parent container",
        "limitation": "Almost never unique; always returns multiple results",
    },
    {
        "strategy": "By.LINK_TEXT",
        "example": 'driver.find_element(By.LINK_TEXT, "Forgot Password?")',
        "html": '<a href="/reset">Forgot Password?</a>',
        "speed": "Fast",
        "reliability": "Medium — breaks if link text changes or is translated",
        "when_to_use": "Navigational links with stable, unique text",
        "limitation": "Exact match only; breaks with internationalisation",
    },
    {
        "strategy": "By.PARTIAL_LINK_TEXT",
        "example": 'driver.find_element(By.PARTIAL_LINK_TEXT, "Forgot")',
        "html": '<a href="/reset">Forgot Password?</a>',
        "speed": "Fast",
        "reliability": "Low — partial matches can be ambiguous",
        "when_to_use": "Links with dynamic suffixes but stable prefixes",
        "limitation": "May match unintended links if the partial text is too generic",
    },
]

# Priority hierarchy
LOCATOR_PRIORITY = [
    "1. By.ID         — unique, fast, stable (first choice)",
    "2. By.NAME        — great for form fields",
    "3. By.CSS_SELECTOR— flexible, readable, fast",
    "4. By.XPATH       — when CSS cannot do it (text search, parent traversal)",
    "5. By.LINK_TEXT   — only for stable navigation links",
    "6. By.CLASS_NAME  — only if semantically meaningful",
    "7. By.TAG_NAME    — only for counting or scoping",
    "8. By.PARTIAL_LINK_TEXT — last resort",
]

print("Selenium Locator Strategies — Priority Hierarchy")
print("=" * 60)
for p in LOCATOR_PRIORITY:
    print(f"  {p}")

print(f"\n\nDetailed Comparison:")
for loc in LOCATOR_STRATEGIES:
    print(f"\n  {loc['strategy']}")
    print(f"    Speed:       {loc['speed']}")
    print(f"    Reliability: {loc['reliability']}")
    print(f"    Use when:    {loc['when_to_use']}")
    print(f"    Limitation:  {loc['limitation']}")
Note: The locator priority hierarchy — ID first, then Name, then CSS, then XPath — is not arbitrary. It reflects both performance and stability. IDs are the fastest to resolve because browsers maintain an internal ID index. They are the most stable because IDs have semantic meaning and rarely change during refactoring. CSS selectors are next because they use the browser’s native CSS engine, which is heavily optimised. XPath is last among the powerful options because it is evaluated by the browser’s XML engine, which is slower, and because XPath expressions tend to be more position-dependent and therefore more fragile.
Tip: When you encounter an element without an ID, ask the development team to add a data-testid attribute. This is a widely accepted practice: developers add attributes like data-testid="submit-order" specifically for test automation. These attributes are immune to CSS refactoring, invisible to users, and signal their purpose clearly. Many teams adopt a convention where every interactive element gets a data-testid, making automation locators trivial and rock-solid.
Warning: Never use auto-generated IDs as locators. Frameworks like React, Angular and ASP.NET sometimes generate IDs like mat-input-3, ctl00_ContentPlaceHolder1_TextBox1, or ember-432. These IDs change between page loads, builds, or when components are reordered. If the ID looks like it was generated by a framework rather than written by a human, treat it as unstable and use a CSS selector or data attribute instead.

Common Mistakes

Mistake 1 — Using XPath for everything because it is the most powerful

❌ Wrong: driver.find_element(By.XPATH, "/html/body/div[3]/form/div[2]/input") — an absolute XPath that breaks when any ancestor element changes.

✅ Correct: driver.find_element(By.ID, "email") or driver.find_element(By.CSS_SELECTOR, "form.login input[type='email']") — simpler, faster, and more resilient to DOM changes.

Mistake 2 — Using class names that are purely visual styling

❌ Wrong: driver.find_element(By.CLASS_NAME, "mt-4") — a Tailwind CSS spacing class that has no semantic meaning and could be changed by any designer.

✅ Correct: driver.find_element(By.CSS_SELECTOR, "[data-testid='login-form']") — a purpose-built test attribute that is stable and meaningful.

🧠 Test Yourself

Which locator strategy should be your first choice when the element has a stable, human-written ID attribute?