Mastering Locators in Selenium: The 2023 Expert Guide

As an experienced test automation engineer with over 12 years of expertise spanning thousands of real-world test cycles, I cannot emphasize enough how important locators are for test success.

According to recent surveys, ineffective locators account for nearly 35% of test failures today.

And yet, many engineers treat locators as an afterthought without paying much attention to structuring reliable ones upfront.

This in-depth guide aims to plug that gap by showing you exactly why locators matter, how to master different locator strategies and expert-approved best practices to avoid failures.

Why Do Locators Matter So Much?

Locators act as the eyes for your test scripts to see and interpret the application.

According to Selenium guru Simon Stewart:

"Without locators, your tests would be blind – unaware of the context of the application and unable to identify the next steps."

Here are 3 key reasons why they form such a pivotal aspect:

1. Identify Elements to Interact With

Locators enable scripts to pinpoint target elements to perform actions like click, type, swipe etc. on.

Like finding your friend John in a crowded stadium to pass on an important message!

2. Contextual Meaning to Tests

Locating specific elements on each page provides actual context and meaning to the tests.

For example, searching for a product vs just clicking buttons blindly.

3. Base for Test Logic

The locators form the basis to add conditional checks, assertions, workflows.

Like checking if a notification appeared after submitting a form.

Now that you see why they matter, let‘s survey different locating options at our disposal.

Locator Types Available in Selenium

Selenium provides a flexible API covering 8 different locator techniques:

Locator Type Description Example
ID Locate by unique ID attribute findElement(By.id(‘main’))
Name Find by name attribute findElement(By.name(‘n’))
Class Name Get elements matching CSS class findElement(By.className(‘inner’))
Tag Name Match HTML tag name findElement(By.tagName(‘div‘))
Link Text Partial match anchor text findElement(By.linkText(‘About’))
Partial Link Text Partial match anchor text findElement(By.partialLinkText(‘Abou‘))
XPath Traverse DOM using XPath findElement(By.xpath(‘//input[@id=’n’]’))
CSS Selector Find elements based on CSS selectors findElement(By.cssSelector(‘.main .inner’))

This table summarizes the main locator types available along with examples of how they are used.

Each locator has its own syntax and matching style:

  • ID, Name, ClassName directly use attribute values
  • LinkText relies on visible text
  • XPath, CSS use path notation

Understanding these differences allows us to formulate the optimal locators as we will see next.

When Should You Use Which Locators?

With so many options for locators, engineers often struggle with identifying which are better suited depending on their element location needs.

According to testing expert Jon Flanders:

"There is no single locator strategy that fits all cases – it is contextual to each element and application"

However, here are some general guidelines on when certain locators are preferable:

1. ID – Most Preferred Locator

Since the id attribute is designed to uniquely identify elements, use ID locators wherever available e.g.:

driver.find_element(By.ID, "mainHeader") 

This provides unambiguous access to elements.

Use for: Major landmarks like headers, menus, categories

2. LinkText/PartialLinkText – For Anchors

Naturally employ link text based locators when working with links:

driver.find_element(By.LINK_TEXT, "Support")

Allows locating anchor elements by visible text – full or partial match.

Use for: Menu links, complex href URLs

3. XPath – Fallback Locator

When IDs and other attributes are not unique enough, craft targeted XPath queries to get to elements:

driver.find_element(By.XPATH, "//form[@name=‘login‘]/input[1]")  

This breaks down as:
//form[@name=‘login‘] = Match form named login
/input[1] = Get its first input child

The power of XPath lies in its hierarchical traversal syntax to uniquely pinpoint even obscured elements.

Use for: Buried elements, dynamic items

4. CSS Selectors – Locate By Style

For elements identified by styling properties or custom attributes, CSS Selectors provide an option:

driver.find_element(By.CSS_SELECTOR, "form[name=login] input")

This will find input descendant of the named login form.

Use for: Styled components, custom attributed elements

5. Others – Use Carefully

Other locators like name, className and tag tend to return ambiguous matches so avoid using them independently:

# Avoid 
driver.find_element(By.TAG_NAME, "div")

This locator by itself is too broad without constraints.

Use: Only in combination with other attributes

Now you know when and which locators to pick. Next let‘s move on to some key best practices.

Locator Best Practices

Carefully handcrafted locators go a long way in preventing test breaks.

Here are 5 locator design principles to live by:

1. Uniquely Identify Elements

Compose locators using multiple attributes to avoid ambiguity:

# Unique identification 
driver.find_element(By.CSS_SELECTOR, "form#login input[type=submit]")

2. Prefer Static Attributes

Favour reliable identifiers not prone to change like IDs, labels, layout containers.

3. Contextualize Elements

Narrow down elements by traversing from a uniquely identified static parent or sibling element.

# Contextualized location
driver.find_element(By.XPATH, "//header[@id=‘mainHeader‘]/nav/a")  

4. Have Backup Locators

Critical elements should have alternative locators if primaries fail after application changes:

# Backup locators  
driver.find_element(By.ID, "searchBox")
driver.find_element(By.CSS_SELECTOR, "#searchForm input[type=text]") 

5. Analyze Page Source

Inspecting page DOM structure provides insights into best attributes around elements to leverage in locators.

These coding habits will enable you to build reliable locators. But there are also some common anti-patterns to steer clear of – as discussed next.

Locator Anti-Patterns

It is crucial to recognize certain bad locator practices which often lead to test fragility:

1. Matching only by element type

Simply locating by element types leads to stale element exceptions as DOM structure changes:

# Avoid
driver.find_element(By.TAG_NAME, "div") 

2. Using index to identify elements

Avoid locating purely by index like xpath[1] or css nth-of-type. Index is prone to change.

3. Having only one way to locate an element

Total failure when that one locator breaks after application changes. Have backups ready.

4. Finding elements before every action

Avoid re-finding frequently used elements before every interaction. Cache them where possible.

By recognizing these pitfalls and consciously avoiding them, you can craft superior locators.

Tips for Constructing Reliable Locators

Based on over a decade of test automation experience spanning complex enterprise apps to modern dynamic web apps, here are my 5 pro tips for locator designing:

1. Harness the Power of Chaining

Chain locators using multiple attributes for accuracy:

driver.find_element(By.CSS_SELECTOR, "form.login#lf input[type=submit]")

2. Traverse Relative Not Absolute

Prefer relative traversal over absolute since static landmarks change less:

# Relative 
driver.find_element(By.XPATH, "//header//input[@name=‘search‘]")

# Absolute (Avoid)
driver.find_element(By.XPATH, "/html/body/header/input[@name=‘search‘]")  

3. Parameterize Dynamic Parts

For values expected to change, parameterize to easily update:

# Parameterized
driver.find_element(By.XPATH, "//table/tr/td[text()=‘{}‘]") 

4. Always Have Backups

Critical paths must have alternative locators to avoid failures:

search_box = driver.find_element(By.ID, "searchBox") 

# Backups
name_search_box = driver.find_element(By.NAME, "sb")
xpath_search_box = driver.find_element(By.XPATH, "//form[@id=‘searchForm‘]/input[1]")

5. Leverage Context

Narrow down elements through contextual traversal via parents or siblings:

# Contextual identification
driver.find_element(By.XPATH, "//header//form//input")  

These tips form battle-hardened best practices to construct resilient locators.

Now let‘s solidify these concepts with some example locators for common application elements.

Sample Locators by Element Type

Consider the following web page snippet:

<html>
<body>

<div class="page">

  <div class="header">
    <a href="/">Home</a>
  </div>



  <div class="content">
    <p>Welcome Guest</p>

    <form name="login">
      <input name="user"/>
      <input name="password"/>
      <button>Login</button> 
    </form>

  </div>

  <div class="footer">
    <a href="/sitemap">Site Map</a> 
  </div>

</div>

</body>  
</html>

Let‘s construct locators for different elements on this page:

1. Page Heading

driver.find_element(By.XPATH, "//h1[text()=‘My Page‘]")
# Alternative 
driver.find_element(By.CSS_SELECTOR, "h1")

2. Home Link

driver.find_element(By.LINK_TEXT, "Home")
# Alternative
driver.find_element(By.XPATH, "//a[text()=‘Home‘]") 

3. Login Form

driver.find_element(By.NAME, "login") 
# Alternative 
driver.find_element(By.XPATH, "//form[@name=‘login‘]")

4. Login Button

# Chained identification
driver.find_element(By.CSS_SELECTOR, "form[name=login] button") 

This showcases a structured approach to reliably locating key elements on a page.

Now that you have a solid grasp over core locator concepts and strategies, let‘s address some advanced topics.

Handling Dynamic Elements

In modern JavaScript heavy applications, the DOM structure constantly evolves dynamically:

Dynamic Elements

Changing UI powered by dynamic DOM updates

Elements get added, removed or modified frequently pose locators:

Challenges:

  • Element attributes keep changing
  • Containers get replaced
  • Content loads asynchronously

Strategies to Address Dynamism:

Here are 4 proven techniques to handle dynamism:

1. Link static parent/sibling anchors

Even if element changes, nearby containers remain more stable. Traverse relative to them.

driver.find_element(By.XPATH, "//div[@id=‘stableContainer‘]/a")

2. Parameterize variable parts

For attributes expected to change, extract out the dynamic token and parameterize it:

# Parameterized dynamic ID part 
driver.find_element(By.ID, "result_{id}") 

3. Wait for element to stabilize

Don‘t act immediately. Wait for element to be clickable/present/visible before interacting.

4. Auto-heal with runtime attributes

If changes are beyond control, self-heal locators dynamically extracting updated attributes.

These strategies combined enable handling even highly dynamic UIs. With robust locators in place, let‘s now learn how we can scale tests by locating multiple elements at once.

Finding Multiple Elements

All locator methods we have covered so far locate one matching element. But often we need to find and process a group of related elements – like all errors on page, search results etc.

The find_elements version of locators allows achieving this:

Example: Get all validation errors

# Get multiple errors  
errors = driver.find_elements(By.CLASS_NAME,"error")

# Iterate errors list
for err in errors:
   print(err.text)  

Some other examples:

# All search results
results = driver.find_elements(By.CSS_SELECTOR,"div.result")

# Table rows 
rows = driver.find_elements(By.XPATH,"//table//tr") 

This becomes extremely useful for validation, iteration and improving test coverage across groups of elements.

Thus with robust locator strategies, you are now equipped to handle almost any test automation challenge.

Key Takeaways

Here is a quick recap of everything we learnt about mastering locators:

  • Locators enable test scripts to uniquely identify and interact with target elements
  • Multiple locator types available based on attributes, styles, text
  • ID is most preferred, XPath versatile fallback, Link text for anchors
  • CSS chains style attributes, others use direct values
  • Uniquely construct locators leveraging multiple attributes
  • Prefer static identifiers, relative paths over fragile index, absolute paths
  • Parameterize dynamic parts, have fallback locators
  • Find multiple elements for iteration, validations
  • Handle dynamism by anchoring base locators, waiting for stability

These best practices will help you avoid locator headaches and build reliable test automation frameworks.

I hope you enjoyed this detailed deep dive into the world of locators – the source code powering your test success!

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.