Different Types of Locators in UI Automation Testing
What are Locators?
Locators are strategies for identifying and finding specific elements on a web page. When you record a UI automation scenario, the recording agent needs to remember which button you clicked, which input field you typed in, or which link you selected. Locators are how it “remembers” and “finds” these elements when replaying your test.
Think of locators like giving directions to find a specific house:
- ID-based: “It’s the house with address 123” (unique identifier)
- Class-based: “It’s the blue house on Main Street” (characteristic)
- XPath: “Take the first street, second house on the left” (path-based navigation)
- CSS Selector: “Look for the house with a red door and white fence” (attributes)
- Playwright: “Find the house where the family named ‘Smith’ lives” (semantic meaning)
Why Multiple Locator Types Matter
E2E Test Automation provides multiple locator strategies for each element captured during recording. This is crucial because:
Resilience:
- If the UI changes slightly (a class name is updated), other locators may still work
- Multiple fallback options prevent tests from breaking unnecessarily
- Tests remain stable across minor UI updates
Flexibility:
- Choose the most appropriate locator for each situation
- Balance between specificity and maintainability
- Adapt to different application structures
Reliability:
- Some locators are more reliable for certain element types
- Dynamic content requires different strategies than static elements
- Proper locator selection reduces test flakiness
Types of Locators in E2E Test Automation
E2E Test Automation provides five main types of locators, each with unique strengths and use cases:
- Playwright Locators
- CSS Selectors
- XPath Locators
- Class-Based Locators
- ID-Based Locators
Let’s explore each in detail.
1. Playwright Locators
What are Playwright Locators?
Playwright locators are modern, user-centric locators that focus on how users interact with elements rather than technical implementation details. They’re designed to be resilient to changes and closely mimic how humans identify elements on a page.
How They Work
Playwright locators find elements based on:
- Role: The semantic role of the element (button, link, heading)
- Text: Visible text content
- Label: Associated label text
- Placeholder: Placeholder text in inputs
- Alt text: Alternative text for images
- Title: Title attributes
Syntax Examples
By Role:
page.getByRole('button', { name: 'Submit' })
page.getByRole('link', { name: 'Learn More' })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('checkbox', { name: 'Remember Me' })By Text:
page.getByText('Welcome, John!')
page.getByText('Shopping Cart')
page.getByText('Add to Cart')By Label:
page.getByLabel('Email Address')
page.getByLabel('Password')
page.getByLabel('First Name')By Placeholder:
page.getByPlaceholder('Enter your email')
page.getPlaceholder('Search products...')By Alt Text (for images):
page.getByAltText('User Avatar')
page.getByAltText('Product Image')By Title:
page.getByTitle('Close Dialog')
page.getByTitle('Help')When to Use Playwright Locators
Best For:
- Modern web applications with semantic HTML
- Elements with clear, visible text
- Form inputs with labels
- Buttons and links with descriptive text
- Accessible applications (ARIA roles)
Advantages:
- User-centric: Finds elements the way users do (by visible text, role)
- Resilient: Less affected by implementation changes
- Readable: Easy to understand what element is being targeted
- Accessible: Encourages building accessible applications
- Maintainable: Changes to CSS classes or IDs don’t break tests
Limitations:
- Requires semantic HTML and proper accessibility attributes
- May struggle with purely decorative or complex custom components
- Needs unique, descriptive text (problems if text is duplicated)
- Not ideal for elements without text or labels
Example Scenarios
Login Form:
// Good Playwright locators
page.getByLabel('Username')
page.getByLabel('Password')
page.getByRole('button', { name: 'Log In' })
// Instead of fragile selectors like:
// #username-field-2023-v2
// button.btn-primary-submit-newProduct Card:
// Find product by its heading
page.getByRole('heading', { name: 'Wireless Headphones' })
// Find "Add to Cart" button for specific product
page.getByRole('button', { name: 'Add to Cart' })Navigation:
// Find navigation links by their text
page.getByRole('link', { name: 'Products' })
page.getByRole('link', { name: 'About Us' })
page.getByRole('link', { name: 'Contact' })2. CSS Selectors
What are CSS Selectors?
CSS Selectors use the same syntax as CSS stylesheets to identify elements. They’re powerful, flexible, and widely understood by web developers.
How They Work
CSS Selectors target elements based on:
- Element type:
button,input,div,a - IDs:
#submit-button - Classes:
.primary-button,.form-input - Attributes:
[name="email"],[type="password"] - Hierarchy:
div > button,form input - Pseudo-classes:
:first-child,:nth-child(2),:hover
Syntax Examples
By ID:
#login-button
#email-input
#main-headerBy Class:
.submit-btn
.form-control
.product-cardBy Attribute:
[name="username"]
[type="email"]
[data-testid="checkout-button"]By Element Type:
button
input
a
divBy Combination:
button.primary
input[type="email"]
div.card > h2
form#login-form input[name="password"]By Hierarchy:
div > button /* Direct child */
form input /* Descendant */
ul > li:first-child /* First list item */By Multiple Classes:
.btn.btn-primary
.card.activeWhen to Use CSS Selectors
Best For:
- Elements with unique IDs or classes
- When you need to target specific element types
- Combining multiple attributes
- Selecting based on element position
- Modern web applications with consistent class naming
Advantages:
- Fast: CSS selectors are highly optimized
- Flexible: Many ways to target the same element
- Familiar: Most developers know CSS syntax
- Powerful: Can combine multiple criteria
- Precise: Can be very specific when needed
Limitations:
- Can be fragile if classes or structure change frequently
- May become overly complex for deeply nested elements
- Relies on implementation details (classes, IDs) that might change
- Position-based selectors (
:nth-child) can break when order changes
Example Scenarios
Simple Selectors:
/* By ID - most reliable if ID is stable */
#submit-button
#user-email
/* By Class - good for styled components */
.primary-button
.form-input
/* By Data Attribute - excellent for testing */
[data-testid="login-form"]
[data-qa="checkout-button"]Combined Selectors:
/* Button with specific class inside a form */
form.login-form button.submit
/* Input with specific type and name */
input[type="email"][name="user-email"]
/* First product in a list */
.product-list .product-card:first-child
/* Active navigation link */
nav a.nav-link.activeComplex Selectors:
/* Second item in a specific list */
ul#product-list > li:nth-child(2)
/* Submit button inside checkout form */
form[id="checkout"] button[type="submit"]
/* Email input in registration section */
section.registration input[name="email"]Best Practices
Do:
- Use data attributes (
data-testid) for test-specific selectors - Prefer IDs when they’re stable and unique
- Use classes for styled components
- Keep selectors simple and readable
Don’t:
- Use overly specific selectors (
.header > nav > ul > li:nth-child(2) > a) - Rely on generated or dynamic class names (
.css-123abc-xyz) - Use fragile position-based selectors without good reason
- Create selectors that will break with minor UI changes
3. XPath Locators
What are XPath Locators?
XPath (XML Path Language) is a powerful query language for selecting elements based on their path in the document structure. It can navigate both up and down the DOM tree and select elements based on complex criteria.
How They Work
XPath locators navigate the document tree using:
- Absolute paths: Full path from root (
/html/body/div/button) - Relative paths: Search from any point (
//button) - Attributes: Select by attributes (
//button[@id='submit']) - Text content: Find by text (
//button[text()='Submit']) - Hierarchy: Navigate parent/child relationships
- Axes: Navigate in various directions (parent, sibling, ancestor)
Syntax Examples
Basic XPath:
//button // All buttons
//input[@type='email'] // Email inputs
//a[text()='Learn More'] // Link with specific text
//div[@class='product-card'] // Divs with specific classBy Attributes:
//button[@id='submit-btn']
//input[@name='username']
//div[@data-testid='product-card']
//a[@href='/products']By Text Content:
//h1[text()='Welcome']
//button[contains(text(), 'Add to Cart')]
//span[text()='Shopping Cart']
//p[contains(text(), 'Thank you')]By Hierarchy:
//form[@id='login']//input[@name='password']
//div[@class='cart']//button[@class='checkout']
//ul[@class='products']/li[1]By Position:
//div[@class='product'][1] // First product
//div[@class='product'][last()] // Last product
//li[2] // Second list itemComplex XPath:
// Button inside form with specific ID
//form[@id='checkout']//button[@type='submit']
// Input that follows a label with specific text
//label[text()='Email']/following-sibling::input
// Parent div of a button
//button[@id='delete']/parent::div
// All buttons inside a card but not disabled
//div[@class='card']//button[not(@disabled)]XPath Axes:
// Ancestor
//button[@id='save']/ancestor::form
// Parent
//button[@id='cancel']/parent::div
// Following sibling
//label[@for='email']/following-sibling::input
// Preceding sibling
//button[@id='next']/preceding-sibling::button
// Child
//form[@id='login']/child::inputWhen to Use XPath Locators
Best For:
- Complex document structures
- When you need to navigate up the DOM tree (find parent)
- Selecting elements based on text content
- Dynamic elements with predictable patterns
- When CSS selectors can’t express the relationship
Advantages:
- Powerful: Can express complex relationships
- Flexible: Navigate in any direction (parent, child, sibling)
- Text-based: Can search by visible text
- Universal: Works with any HTML/XML document
- Dynamic: Can use functions and conditions
Limitations:
- Slower: Generally slower than CSS selectors
- Complex: Can become hard to read and maintain
- Fragile: Sensitive to document structure changes
- Verbose: Often longer and more complex than alternatives
- Browser differences: Slight variations in XPath support
Example Scenarios
Finding by Text:
// Find "Add to Cart" button by its text
//button[text()='Add to Cart']
// Find heading containing "Welcome"
//h1[contains(text(), 'Welcome')]
// Find link with exact text
//a[text()='Sign In']Navigating Relationships:
// Find input that comes after a specific label
//label[text()='Username']/following-sibling::input
// Find the form containing a specific button
//button[@id='submit']/ancestor::form
// Find the parent div of an error message
//span[@class='error']/parent::divComplex Conditions:
// Find enabled buttons with specific class
//button[@class='primary' and not(@disabled)]
// Find products with price less than $50 (if encoded in attribute)
//div[@class='product' and @data-price < 50]
// Find inputs that are required
//input[@required]
// Find all links except those with specific class
//a[not(@class='external')]Position-Based:
// First product card
(//div[@class='product-card'])[1]
// Last item in navigation
(//nav//a)[last()]
// Third row in a table
//table//tr[3]
// Second child of a specific div
//div[@id='container']/*[2]Best Practices
Do:
- Use relative XPath (
//) instead of absolute paths - Combine attributes for more specific targeting
- Use
contains()for partial text matches - Comment complex XPath expressions
- Test XPath in browser console before using
Don’t:
- Use absolute paths from root (
/html/body/div[1]/div[2]...) - Create overly complex expressions
- Rely solely on position-based selection
- Use XPath when simpler selectors work
- Forget to handle special characters in text
4. Class-Based Locators
What are Class-Based Locators?
Class-based locators specifically target elements using their CSS class names. They’re essentially a subset of CSS selectors, but worth discussing separately because of their common usage.
How They Work
Class-based locators find elements by matching their class attribute:
- Single class:
.button - Multiple classes:
.button.primary - Partial class:
[class*='product']
Syntax Examples
Single Class:
.submit-button
.form-input
.product-card
.error-messageMultiple Classes (element has ALL these classes):
.btn.btn-primary
.card.featured
.input.invalid
.alert.alert-dangerClass with Element Type:
button.primary
input.email
div.container
a.nav-linkPartial Class Match:
[class*='product'] // Any element with 'product' in class
[class^='btn-'] // Class starts with 'btn-'
[class$='-card'] // Class ends with '-card'When to Use Class-Based Locators
Best For:
- Components with consistent styling
- UI frameworks with predictable class patterns (Bootstrap, Tailwind, Material UI)
- Elements grouped by visual appearance
- When classes are stable and meaningful
Advantages:
- Simple: Easy to read and write
- Common: Most applications use classes for styling
- Grouping: Can target multiple similar elements
- Framework-friendly: Works well with CSS frameworks
Limitations:
- Fragile: Classes can change when redesigning or refactoring
- Non-unique: Multiple elements often share classes
- Generated classes: CSS-in-JS creates random class names
- Styling dependency: Tied to presentation layer, not semantic meaning
Example Scenarios
UI Component Libraries:
/* Bootstrap classes */
.btn.btn-primary
.form-control
.alert.alert-success
/* Material UI */
.MuiButton-root
.MuiTextField-root
.MuiCard-root
/* Tailwind (avoid generated classes, use custom ones) */
.submit-button
.product-card
.nav-linkCustom Application Classes:
/* Feature-based classes */
.checkout-button
.product-title
.user-profile
/* State-based classes */
.active
.disabled
.selected
.loading
/* Layout classes */
.container
.sidebar
.main-contentCombined with Specificity:
/* Button with specific state */
button.btn-primary.active
/* Card in specific section */
section.products .product-card
/* Input with error state */
input.form-control.is-invalidBest Practices
Do:
- Use semantic, descriptive class names
- Prefer stable, semantic classes over generated ones
- Combine classes to be more specific when needed
- Use utility classes from frameworks when appropriate
Don’t:
- Rely on dynamically generated classes (
.css-xyz123) - Use classes that are purely for styling and change often
- Select by multiple unrelated classes
- Use overly generic classes (
.buttonwhen there are many buttons)
5. ID-Based Locators
What are ID-Based Locators?
ID-based locators target elements using their unique id attribute. In valid HTML, IDs should be unique within a page, making them the most reliable locator when available.
How They Work
ID locators find elements by their id attribute:
- Direct ID:
#submit-button - With element type:
button#submit - In XPath:
//button[@id='submit']
Syntax Examples
CSS Selector:
#login-form
#submit-button
#user-email
#main-headerWith Element Type:
form#login-form
button#submit-button
input#user-email
div#main-headerXPath:
//form[@id='login-form']
//button[@id='submit-button']
//input[@id='user-email']
//div[@id='main-header']Playwright:
page.locator('#submit-button')
page.locator('button#submit')When to Use ID-Based Locators
Best For:
- Unique elements on the page
- Major page sections (header, footer, main)
- Important forms and inputs
- Elements that rarely change
- Legacy applications with stable IDs
Advantages:
- Fastest: Most performant locator type
- Unique: IDs should be unique per page
- Simple: Easy to read and write
- Stable: Less likely to change than classes
- Universal: Supported everywhere
Limitations:
- Availability: Not all elements have IDs
- Generated IDs: Some frameworks generate dynamic IDs
- Uniqueness: Developers sometimes create duplicate IDs (invalid HTML)
- Coverage: Can’t always rely on IDs for every element
Example Scenarios
Form Elements:
/* Login form */
#login-form
#username-input
#password-input
#remember-me-checkbox
#submit-button
/* Registration form */
#registration-form
#first-name
#last-name
#email
#confirm-passwordPage Sections:
/* Major page components */
#header
#navigation
#main-content
#sidebar
#footer
/* Specific sections */
#product-details
#shopping-cart
#user-profile
#checkout-summaryImportant Elements:
/* Key functionality */
#search-box
#add-to-cart-button
#place-order-button
#logout-link
#notification-areaCombined Selections:
/* Button inside specific form */
#login-form button
#login-form #submit-button
/* Input within specific section */
#checkout-section input[name='email']
/* Link in navigation */
#main-nav a.activeBest Practices
Do:
- Use IDs as the primary locator when available
- Prefer IDs over classes for unique elements
- Keep ID names semantic and descriptive
- Use IDs for major page landmarks
- Verify IDs are truly unique
Don’t:
- Rely on auto-generated or dynamic IDs (`.NET ViewState, React keys)
- Use IDs that change between environments
- Assume all elements will have IDs
- Create duplicate IDs (invalid HTML)
- Use overly generic IDs (
#button1,#div2)
Comparing Locator Types
Quick Comparison Table
Locator Selection Decision Tree
Start
↓
Does element have unique, stable ID?
Yes → Use ID-based locator
No → Continue
↓
Is element user-facing with semantic role/text?
Yes → Use Playwright locator
No → Continue
↓
Does element have stable data-testid or unique class?
Yes → Use CSS selector
No → Continue
↓
Can you select by stable class name?
Yes → Use Class-based locator
No → Continue
↓
Need complex relationship or text-based selection?
Yes → Use XPath locator
No → Reconsider or combine strategiesManaging Locators in E2E Test Automation
Viewing Locators for an Element
When you record a scenario, E2E Test Automation generates multiple locators for each element:
- Open Manage Actions in your scenario
- Select an action (click, type, etc.)
- View available locators for that action’s target element
- You’ll see all the locators generated (ID, Playwright, CSS, Class, XPath)
Switching Between Locators
If one locator breaks due to UI changes:
- Navigate to the failing action
- Open the locator selector
- View all available locators for that element
- Select a different locator that’s more stable
- Save and test
Testing Locators
Before committing to a locator:
-
Use browser DevTools to test locators
- Open console (F12)
- Test CSS:
document.querySelector('.my-class') - Test XPath:
$x('//button[@id="submit"]')
-
Verify uniqueness: Ensure only one element matches
-
Check stability: Will it work after UI updates?
-
Test in different states: Does it work when element is hidden, disabled, etc.?
Adding Custom Locators
If generated locators aren’t satisfactory:
- Open the action in Manage Actions
- Add a new locator option
- Enter your custom locator
- CSS:
.my-custom-class - XPath:
//button[text()='Submit'] - Playwright:
page.getByRole('button', { name: 'Submit' })
- CSS:
- Test the locator
- Save and use
Best Practices for Locator Selection
General Guidelines
Priority Order (recommended):
- ID – If unique and stable
- Playwright – If element has semantic meaning
- Data attributes (CSS) – If using
data-testidor similar - CSS Selectors – For elements with stable attributes
- Class-based – For consistently styled components
- XPath – For complex relationships or text-based selection
Dos and Don’ts
Do:
- Use the simplest locator that uniquely identifies the element
- Prefer semantic locators (Playwright, ID) over implementation-specific ones
- Add
data-testidattributes to elements specifically for testing - Review and update locators when UI changes
- Test locators in multiple scenarios
- Document why you chose specific locators
Don’t:
- Use overly complex selectors when simple ones work
- Rely on generated or dynamic identifiers
- Use position-based selectors (
:nth-child) unless necessary - Change locators unnecessarily
- Use text-based locators for frequently changing content
- Forget to verify locator uniqueness
Special Considerations
Dynamic Content:
- Avoid relying on dynamic IDs or classes
- Use stable attributes or data attributes
- Consider partial matches with
contains() - Use parent-child relationships
Multiple Matches:
- Make locators more specific
- Combine multiple attributes
- Use contextual locators (within a specific section)
- Add explicit indexing as last resort
Hidden Elements:
- Some locators may not find hidden elements
- Consider element visibility in assertions
- Use appropriate wait strategies
Iframes:
- Locators must account for iframe context
- Switch to iframe before locating elements
- Use frame-aware locators
Troubleshooting Locator Issues
Element Not Found
Possible Causes:
- Element doesn’t exist on the page
- Element is hidden or not yet loaded
- Locator is incorrect or too specific
- Element is in an iframe
- Dynamic content hasn’t loaded
Solutions:
- Verify element exists in the page source
- Add explicit waits
- Try a different locator strategy
- Check for iframes
- Inspect the element to see current attributes
Multiple Elements Match
Possible Causes:
- Locator is too generic
- Multiple similar elements on the page
- Dynamic elements with same attributes
Solutions:
- Make locator more specific
- Use contextual locators (combine with parent)
- Add additional attributes
- Use index or position (last resort)
Locator Works Sometimes
Possible Causes:
- Element loads at different speeds
- Content is dynamic or randomly generated
- A/B testing or feature flags
- Browser or environment differences
Solutions:
- Add explicit waits
- Use more resilient locators
- Check for conditional logic
- Test in multiple environments
Locator Changed After UI Update
Possible Causes:
- Classes or IDs were refactored
- HTML structure changed
- Element was replaced with different component
Solutions:
- Update to a more stable locator
- Use semantic locators (Playwright)
- Consider adding
data-testidattributes - Review and update affected scenarios
Summary
E2E Test Automation provides five types of locators to identify elements:
- Playwright Locators – User-centric, semantic, resilient to changes
- CSS Selectors – Fast, flexible, developer-friendly
- XPath Locators – Powerful, complex relationships, text-based
- Class-Based Locators – Simple, style-based, common
- ID-Based Locators – Fastest, unique, most reliable when available
Key Takeaways:
- Multiple locators provide resilience – Tests don’t break from minor UI changes
- Choose the right locator for each element – Balance simplicity, stability, and specificity
- Prefer semantic locators – ID and Playwright locators are generally most reliable
- Avoid fragile locators – Don’t rely on dynamic classes, generated IDs, or deep nesting
- Test and verify – Always test locators before committing to them
- Maintain locators – Update when UI changes, document your choices
By understanding and effectively using these different locator types, you’ll create more resilient, maintainable UI automation tests that remain stable even as your application evolves.
Master locators, master UI automation.
Different Types of Locators in UI Automation Testing
What are Locators?
Locators are strategies for identifying and finding specific elements on a web page. When you record a UI automation scenario, the recording agent needs to remember which button you clicked, which input field you typed in, or which link you selected. Locators are how it “remembers” and “finds” these elements when replaying your test.
Think of locators like giving directions to find a specific house:
- ID-based: “It’s the house with address 123” (unique identifier)
- Class-based: “It’s the blue house on Main Street” (characteristic)
- XPath: “Take the first street, second house on the left” (path-based navigation)
- CSS Selector: “Look for the house with a red door and white fence” (attributes)
- Playwright: “Find the house where the family named ‘Smith’ lives” (semantic meaning)
Why Multiple Locator Types Matter
E2E Test Automation provides multiple locator strategies for each element captured during recording. This is crucial because:
Resilience:
- If the UI changes slightly (a class name is updated), other locators may still work
- Multiple fallback options prevent tests from breaking unnecessarily
- Tests remain stable across minor UI updates
Flexibility:
- Choose the most appropriate locator for each situation
- Balance between specificity and maintainability
- Adapt to different application structures
Reliability:
- Some locators are more reliable for certain element types
- Dynamic content requires different strategies than static elements
- Proper locator selection reduces test flakiness
Types of Locators in E2E Test Automation
E2E Test Automation provides five main types of locators, each with unique strengths and use cases:
- Playwright Locators
- CSS Selectors
- XPath Locators
- Class-Based Locators
- ID-Based Locators
Let’s explore each in detail.
1. Playwright Locators
What are Playwright Locators?
Playwright locators are modern, user-centric locators that focus on how users interact with elements rather than technical implementation details. They’re designed to be resilient to changes and closely mimic how humans identify elements on a page.
How They Work
Playwright locators find elements based on:
- Role: The semantic role of the element (button, link, heading)
- Text: Visible text content
- Label: Associated label text
- Placeholder: Placeholder text in inputs
- Alt text: Alternative text for images
- Title: Title attributes
Syntax Examples
By Role:
page.getByRole('button', { name: 'Submit' })
page.getByRole('link', { name: 'Learn More' })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('checkbox', { name: 'Remember Me' })By Text:
page.getByText('Welcome, John!')
page.getByText('Shopping Cart')
page.getByText('Add to Cart')By Label:
page.getByLabel('Email Address')
page.getByLabel('Password')
page.getByLabel('First Name')By Placeholder:
page.getByPlaceholder('Enter your email')
page.getPlaceholder('Search products...')By Alt Text (for images):
page.getByAltText('User Avatar')
page.getByAltText('Product Image')By Title:
page.getByTitle('Close Dialog')
page.getByTitle('Help')When to Use Playwright Locators
Best For:
- Modern web applications with semantic HTML
- Elements with clear, visible text
- Form inputs with labels
- Buttons and links with descriptive text
- Accessible applications (ARIA roles)
Advantages:
- User-centric: Finds elements the way users do (by visible text, role)
- Resilient: Less affected by implementation changes
- Readable: Easy to understand what element is being targeted
- Accessible: Encourages building accessible applications
- Maintainable: Changes to CSS classes or IDs don’t break tests
Limitations:
- Requires semantic HTML and proper accessibility attributes
- May struggle with purely decorative or complex custom components
- Needs unique, descriptive text (problems if text is duplicated)
- Not ideal for elements without text or labels
Example Scenarios
Login Form:
// Good Playwright locators
page.getByLabel('Username')
page.getByLabel('Password')
page.getByRole('button', { name: 'Log In' })
// Instead of fragile selectors like:
// #username-field-2023-v2
// button.btn-primary-submit-newProduct Card:
// Find product by its heading
page.getByRole('heading', { name: 'Wireless Headphones' })
// Find "Add to Cart" button for specific product
page.getByRole('button', { name: 'Add to Cart' })Navigation:
// Find navigation links by their text
page.getByRole('link', { name: 'Products' })
page.getByRole('link', { name: 'About Us' })
page.getByRole('link', { name: 'Contact' })2. CSS Selectors
What are CSS Selectors?
CSS Selectors use the same syntax as CSS stylesheets to identify elements. They’re powerful, flexible, and widely understood by web developers.
How They Work
CSS Selectors target elements based on:
- Element type:
button,input,div,a - IDs:
#submit-button - Classes:
.primary-button,.form-input - Attributes:
[name="email"],[type="password"] - Hierarchy:
div > button,form input - Pseudo-classes:
:first-child,:nth-child(2),:hover
Syntax Examples
By ID:
#login-button
#email-input
#main-headerBy Class:
.submit-btn
.form-control
.product-cardBy Attribute:
[name="username"]
[type="email"]
[data-testid="checkout-button"]By Element Type:
button
input
a
divBy Combination:
button.primary
input[type="email"]
div.card > h2
form#login-form input[name="password"]By Hierarchy:
div > button /* Direct child */
form input /* Descendant */
ul > li:first-child /* First list item */By Multiple Classes:
.btn.btn-primary
.card.activeWhen to Use CSS Selectors
Best For:
- Elements with unique IDs or classes
- When you need to target specific element types
- Combining multiple attributes
- Selecting based on element position
- Modern web applications with consistent class naming
Advantages:
- Fast: CSS selectors are highly optimized
- Flexible: Many ways to target the same element
- Familiar: Most developers know CSS syntax
- Powerful: Can combine multiple criteria
- Precise: Can be very specific when needed
Limitations:
- Can be fragile if classes or structure change frequently
- May become overly complex for deeply nested elements
- Relies on implementation details (classes, IDs) that might change
- Position-based selectors (
:nth-child) can break when order changes
Example Scenarios
Simple Selectors:
/* By ID - most reliable if ID is stable */
#submit-button
#user-email
/* By Class - good for styled components */
.primary-button
.form-input
/* By Data Attribute - excellent for testing */
[data-testid="login-form"]
[data-qa="checkout-button"]Combined Selectors:
/* Button with specific class inside a form */
form.login-form button.submit
/* Input with specific type and name */
input[type="email"][name="user-email"]
/* First product in a list */
.product-list .product-card:first-child
/* Active navigation link */
nav a.nav-link.activeComplex Selectors:
/* Second item in a specific list */
ul#product-list > li:nth-child(2)
/* Submit button inside checkout form */
form[id="checkout"] button[type="submit"]
/* Email input in registration section */
section.registration input[name="email"]Best Practices
Do:
- Use data attributes (
data-testid) for test-specific selectors - Prefer IDs when they’re stable and unique
- Use classes for styled components
- Keep selectors simple and readable
Don’t:
- Use overly specific selectors (
.header > nav > ul > li:nth-child(2) > a) - Rely on generated or dynamic class names (
.css-123abc-xyz) - Use fragile position-based selectors without good reason
- Create selectors that will break with minor UI changes
3. XPath Locators
What are XPath Locators?
XPath (XML Path Language) is a powerful query language for selecting elements based on their path in the document structure. It can navigate both up and down the DOM tree and select elements based on complex criteria.
How They Work
XPath locators navigate the document tree using:
- Absolute paths: Full path from root (
/html/body/div/button) - Relative paths: Search from any point (
//button) - Attributes: Select by attributes (
//button[@id='submit']) - Text content: Find by text (
//button[text()='Submit']) - Hierarchy: Navigate parent/child relationships
- Axes: Navigate in various directions (parent, sibling, ancestor)
Syntax Examples
Basic XPath:
//button // All buttons
//input[@type='email'] // Email inputs
//a[text()='Learn More'] // Link with specific text
//div[@class='product-card'] // Divs with specific classBy Attributes:
//button[@id='submit-btn']
//input[@name='username']
//div[@data-testid='product-card']
//a[@href='/products']By Text Content:
//h1[text()='Welcome']
//button[contains(text(), 'Add to Cart')]
//span[text()='Shopping Cart']
//p[contains(text(), 'Thank you')]By Hierarchy:
//form[@id='login']//input[@name='password']
//div[@class='cart']//button[@class='checkout']
//ul[@class='products']/li[1]By Position:
//div[@class='product'][1] // First product
//div[@class='product'][last()] // Last product
//li[2] // Second list itemComplex XPath:
// Button inside form with specific ID
//form[@id='checkout']//button[@type='submit']
// Input that follows a label with specific text
//label[text()='Email']/following-sibling::input
// Parent div of a button
//button[@id='delete']/parent::div
// All buttons inside a card but not disabled
//div[@class='card']//button[not(@disabled)]XPath Axes:
// Ancestor
//button[@id='save']/ancestor::form
// Parent
//button[@id='cancel']/parent::div
// Following sibling
//label[@for='email']/following-sibling::input
// Preceding sibling
//button[@id='next']/preceding-sibling::button
// Child
//form[@id='login']/child::inputWhen to Use XPath Locators
Best For:
- Complex document structures
- When you need to navigate up the DOM tree (find parent)
- Selecting elements based on text content
- Dynamic elements with predictable patterns
- When CSS selectors can’t express the relationship
Advantages:
- Powerful: Can express complex relationships
- Flexible: Navigate in any direction (parent, child, sibling)
- Text-based: Can search by visible text
- Universal: Works with any HTML/XML document
- Dynamic: Can use functions and conditions
Limitations:
- Slower: Generally slower than CSS selectors
- Complex: Can become hard to read and maintain
- Fragile: Sensitive to document structure changes
- Verbose: Often longer and more complex than alternatives
- Browser differences: Slight variations in XPath support
Example Scenarios
Finding by Text:
// Find "Add to Cart" button by its text
//button[text()='Add to Cart']
// Find heading containing "Welcome"
//h1[contains(text(), 'Welcome')]
// Find link with exact text
//a[text()='Sign In']Navigating Relationships:
// Find input that comes after a specific label
//label[text()='Username']/following-sibling::input
// Find the form containing a specific button
//button[@id='submit']/ancestor::form
// Find the parent div of an error message
//span[@class='error']/parent::divComplex Conditions:
// Find enabled buttons with specific class
//button[@class='primary' and not(@disabled)]
// Find products with price less than $50 (if encoded in attribute)
//div[@class='product' and @data-price < 50]
// Find inputs that are required
//input[@required]
// Find all links except those with specific class
//a[not(@class='external')]Position-Based:
// First product card
(//div[@class='product-card'])[1]
// Last item in navigation
(//nav//a)[last()]
// Third row in a table
//table//tr[3]
// Second child of a specific div
//div[@id='container']/*[2]Best Practices
Do:
- Use relative XPath (
//) instead of absolute paths - Combine attributes for more specific targeting
- Use
contains()for partial text matches - Comment complex XPath expressions
- Test XPath in browser console before using
Don’t:
- Use absolute paths from root (
/html/body/div[1]/div[2]...) - Create overly complex expressions
- Rely solely on position-based selection
- Use XPath when simpler selectors work
- Forget to handle special characters in text
4. Class-Based Locators
What are Class-Based Locators?
Class-based locators specifically target elements using their CSS class names. They’re essentially a subset of CSS selectors, but worth discussing separately because of their common usage.
How They Work
Class-based locators find elements by matching their class attribute:
- Single class:
.button - Multiple classes:
.button.primary - Partial class:
[class*='product']
Syntax Examples
Single Class:
.submit-button
.form-input
.product-card
.error-messageMultiple Classes (element has ALL these classes):
.btn.btn-primary
.card.featured
.input.invalid
.alert.alert-dangerClass with Element Type:
button.primary
input.email
div.container
a.nav-linkPartial Class Match:
[class*='product'] // Any element with 'product' in class
[class^='btn-'] // Class starts with 'btn-'
[class$='-card'] // Class ends with '-card'When to Use Class-Based Locators
Best For:
- Components with consistent styling
- UI frameworks with predictable class patterns (Bootstrap, Tailwind, Material UI)
- Elements grouped by visual appearance
- When classes are stable and meaningful
Advantages:
- Simple: Easy to read and write
- Common: Most applications use classes for styling
- Grouping: Can target multiple similar elements
- Framework-friendly: Works well with CSS frameworks
Limitations:
- Fragile: Classes can change when redesigning or refactoring
- Non-unique: Multiple elements often share classes
- Generated classes: CSS-in-JS creates random class names
- Styling dependency: Tied to presentation layer, not semantic meaning
Example Scenarios
UI Component Libraries:
/* Bootstrap classes */
.btn.btn-primary
.form-control
.alert.alert-success
/* Material UI */
.MuiButton-root
.MuiTextField-root
.MuiCard-root
/* Tailwind (avoid generated classes, use custom ones) */
.submit-button
.product-card
.nav-linkCustom Application Classes:
/* Feature-based classes */
.checkout-button
.product-title
.user-profile
/* State-based classes */
.active
.disabled
.selected
.loading
/* Layout classes */
.container
.sidebar
.main-contentCombined with Specificity:
/* Button with specific state */
button.btn-primary.active
/* Card in specific section */
section.products .product-card
/* Input with error state */
input.form-control.is-invalidBest Practices
Do:
- Use semantic, descriptive class names
- Prefer stable, semantic classes over generated ones
- Combine classes to be more specific when needed
- Use utility classes from frameworks when appropriate
Don’t:
- Rely on dynamically generated classes (
.css-xyz123) - Use classes that are purely for styling and change often
- Select by multiple unrelated classes
- Use overly generic classes (
.buttonwhen there are many buttons)
5. ID-Based Locators
What are ID-Based Locators?
ID-based locators target elements using their unique id attribute. In valid HTML, IDs should be unique within a page, making them the most reliable locator when available.
How They Work
ID locators find elements by their id attribute:
- Direct ID:
#submit-button - With element type:
button#submit - In XPath:
//button[@id='submit']
Syntax Examples
CSS Selector:
#login-form
#submit-button
#user-email
#main-headerWith Element Type:
form#login-form
button#submit-button
input#user-email
div#main-headerXPath:
//form[@id='login-form']
//button[@id='submit-button']
//input[@id='user-email']
//div[@id='main-header']Playwright:
page.locator('#submit-button')
page.locator('button#submit')When to Use ID-Based Locators
Best For:
- Unique elements on the page
- Major page sections (header, footer, main)
- Important forms and inputs
- Elements that rarely change
- Legacy applications with stable IDs
Advantages:
- Fastest: Most performant locator type
- Unique: IDs should be unique per page
- Simple: Easy to read and write
- Stable: Less likely to change than classes
- Universal: Supported everywhere
Limitations:
- Availability: Not all elements have IDs
- Generated IDs: Some frameworks generate dynamic IDs
- Uniqueness: Developers sometimes create duplicate IDs (invalid HTML)
- Coverage: Can’t always rely on IDs for every element
Example Scenarios
Form Elements:
/* Login form */
#login-form
#username-input
#password-input
#remember-me-checkbox
#submit-button
/* Registration form */
#registration-form
#first-name
#last-name
#email
#confirm-passwordPage Sections:
/* Major page components */
#header
#navigation
#main-content
#sidebar
#footer
/* Specific sections */
#product-details
#shopping-cart
#user-profile
#checkout-summaryImportant Elements:
/* Key functionality */
#search-box
#add-to-cart-button
#place-order-button
#logout-link
#notification-areaCombined Selections:
/* Button inside specific form */
#login-form button
#login-form #submit-button
/* Input within specific section */
#checkout-section input[name='email']
/* Link in navigation */
#main-nav a.activeBest Practices
Do:
- Use IDs as the primary locator when available
- Prefer IDs over classes for unique elements
- Keep ID names semantic and descriptive
- Use IDs for major page landmarks
- Verify IDs are truly unique
Don’t:
- Rely on auto-generated or dynamic IDs (`.NET ViewState, React keys)
- Use IDs that change between environments
- Assume all elements will have IDs
- Create duplicate IDs (invalid HTML)
- Use overly generic IDs (
#button1,#div2)
Comparing Locator Types
Quick Comparison Table
Locator Selection Decision Tree
Start
↓
Does element have unique, stable ID?
Yes → Use ID-based locator
No → Continue
↓
Is element user-facing with semantic role/text?
Yes → Use Playwright locator
No → Continue
↓
Does element have stable data-testid or unique class?
Yes → Use CSS selector
No → Continue
↓
Can you select by stable class name?
Yes → Use Class-based locator
No → Continue
↓
Need complex relationship or text-based selection?
Yes → Use XPath locator
No → Reconsider or combine strategiesManaging Locators in E2E Test Automation
Viewing Locators for an Element
When you record a scenario, E2E Test Automation generates multiple locators for each element:
- Open Manage Actions in your scenario
- Select an action (click, type, etc.)
- View available locators for that action’s target element
- You’ll see all the locators generated (ID, Playwright, CSS, Class, XPath)
Switching Between Locators
If one locator breaks due to UI changes:
- Navigate to the failing action
- Open the locator selector
- View all available locators for that element
- Select a different locator that’s more stable
- Save and test
Testing Locators
Before committing to a locator:
-
Use browser DevTools to test locators
- Open console (F12)
- Test CSS:
document.querySelector('.my-class') - Test XPath:
$x('//button[@id="submit"]')
-
Verify uniqueness: Ensure only one element matches
-
Check stability: Will it work after UI updates?
-
Test in different states: Does it work when element is hidden, disabled, etc.?
Adding Custom Locators
If generated locators aren’t satisfactory:
- Open the action in Manage Actions
- Add a new locator option
- Enter your custom locator
- CSS:
.my-custom-class - XPath:
//button[text()='Submit'] - Playwright:
page.getByRole('button', { name: 'Submit' })
- CSS:
- Test the locator
- Save and use
Best Practices for Locator Selection
General Guidelines
Priority Order (recommended):
- ID – If unique and stable
- Playwright – If element has semantic meaning
- Data attributes (CSS) – If using
data-testidor similar - CSS Selectors – For elements with stable attributes
- Class-based – For consistently styled components
- XPath – For complex relationships or text-based selection
Dos and Don’ts
Do:
- Use the simplest locator that uniquely identifies the element
- Prefer semantic locators (Playwright, ID) over implementation-specific ones
- Add
data-testidattributes to elements specifically for testing - Review and update locators when UI changes
- Test locators in multiple scenarios
- Document why you chose specific locators
Don’t:
- Use overly complex selectors when simple ones work
- Rely on generated or dynamic identifiers
- Use position-based selectors (
:nth-child) unless necessary - Change locators unnecessarily
- Use text-based locators for frequently changing content
- Forget to verify locator uniqueness
Special Considerations
Dynamic Content:
- Avoid relying on dynamic IDs or classes
- Use stable attributes or data attributes
- Consider partial matches with
contains() - Use parent-child relationships
Multiple Matches:
- Make locators more specific
- Combine multiple attributes
- Use contextual locators (within a specific section)
- Add explicit indexing as last resort
Hidden Elements:
- Some locators may not find hidden elements
- Consider element visibility in assertions
- Use appropriate wait strategies
Iframes:
- Locators must account for iframe context
- Switch to iframe before locating elements
- Use frame-aware locators
Troubleshooting Locator Issues
Element Not Found
Possible Causes:
- Element doesn’t exist on the page
- Element is hidden or not yet loaded
- Locator is incorrect or too specific
- Element is in an iframe
- Dynamic content hasn’t loaded
Solutions:
- Verify element exists in the page source
- Add explicit waits
- Try a different locator strategy
- Check for iframes
- Inspect the element to see current attributes
Multiple Elements Match
Possible Causes:
- Locator is too generic
- Multiple similar elements on the page
- Dynamic elements with same attributes
Solutions:
- Make locator more specific
- Use contextual locators (combine with parent)
- Add additional attributes
- Use index or position (last resort)
Locator Works Sometimes
Possible Causes:
- Element loads at different speeds
- Content is dynamic or randomly generated
- A/B testing or feature flags
- Browser or environment differences
Solutions:
- Add explicit waits
- Use more resilient locators
- Check for conditional logic
- Test in multiple environments
Locator Changed After UI Update
Possible Causes:
- Classes or IDs were refactored
- HTML structure changed
- Element was replaced with different component
Solutions:
- Update to a more stable locator
- Use semantic locators (Playwright)
- Consider adding
data-testidattributes - Review and update affected scenarios
Summary
E2E Test Automation provides five types of locators to identify elements:
- Playwright Locators – User-centric, semantic, resilient to changes
- CSS Selectors – Fast, flexible, developer-friendly
- XPath Locators – Powerful, complex relationships, text-based
- Class-Based Locators – Simple, style-based, common
- ID-Based Locators – Fastest, unique, most reliable when available
Key Takeaways:
- Multiple locators provide resilience – Tests don’t break from minor UI changes
- Choose the right locator for each element – Balance simplicity, stability, and specificity
- Prefer semantic locators – ID and Playwright locators are generally most reliable
- Avoid fragile locators – Don’t rely on dynamic classes, generated IDs, or deep nesting
- Test and verify – Always test locators before committing to them
- Maintain locators – Update when UI changes, document your choices
By understanding and effectively using these different locator types, you’ll create more resilient, maintainable UI automation tests that remain stable even as your application evolves.
Master locators, master UI automation.