Table of Contents

Understanding Element Identification

Element identification is the cornerstone of successful automation with Ranorex. This guide will teach you how to effectively find and interact with UI elements using RanoreXPath and other identification strategies.

What is RanoreXPath?

RanoreXPath is Ranorex's proprietary element identification language, similar to XPath but optimized for desktop, web, and mobile applications. It provides a powerful way to locate UI elements based on their properties and hierarchical relationships.

Basic RanoreXPath Syntax

// Basic structure: /element_type[@attribute='value']
var button = "/form[@title='MyApp']/button[@text='Submit']";

// Multiple attributes
var textField = "/form[@title='Login']/text[@name='username' and @visible='True']";

// Hierarchical navigation
var menuItem = "/form/menubar/menuitem[@text='File']/menuitem[@text='Open']";

Element Types by Technology

Desktop Applications (Win32/WinForms/WPF)

// Windows Forms
var winFormButton = "/form[@title='Calculator']/button[@text='1']";
var winFormTextBox = "/form[@title='MyApp']/text[@name='inputField']";

// WPF Applications
var wpfButton = "/form[@automationid='MainWindow']/button[@automationid='SubmitBtn']";
var wpfListBox = "/form/list[@automationid='ItemsList']/listitem[@index='0']";

// Win32 Applications
var win32Menu = "/form[@title='Notepad']/menubar/menuitem[@text='File']";

Web Applications

// HTML Elements
var webButton = "/dom[@domain='example.com']//input[@type='submit']";
var webLink = "/dom//a[@href='https://example.com/page']";
var webDiv = "/dom//div[@id='content']";

// CSS Selectors (alternative approach)
var byCssClass = "/dom//div[@class='header-navigation']";
var byId = "/dom//input[@id='searchBox']";

Mobile Applications

// Android Elements
var androidButton = "/mobileapp[@package='com.example.app']/button[@text='Login']";
var androidEdit = "/mobileapp//edittext[@resource-id='username']";

// iOS Elements
var iosButton = "/mobileapp[@bundle='com.example.app']/button[@name='Login']";
var iosTextField = "/mobileapp//textfield[@name='username']";

Advanced RanoreXPath Techniques

Using Wildcards and Patterns

// Wildcard matching
var anyForm = "/form[@title~'.*Dialog.*']"; // Title contains "Dialog"
var anyButton = "//button[@text~'Save.*']"; // Text starts with "Save"

// Case-insensitive matching
var caseInsensitive = "/form[@title~'(?i)calculator']"; // Matches "Calculator", "CALCULATOR", etc.

Index-Based Selection

// Select by index
var firstButton = "/form/button[1]"; // First button (1-based index)
var lastItem = "/form/list/listitem[-1]"; // Last item
var thirdItem = "/form/list/listitem[3]"; // Third item

// Range selection
var firstThreeItems = "/form/list/listitem[position()<=3]"; // First three items

Attribute Conditions

// Multiple conditions
var enabledButton = "/form/button[@enabled='True' and @visible='True']";
var specificText = "/form/text[@value!='' and @readonly='False']";

// Attribute existence
var hasAttribute = "/form/button[@tooltip]"; // Has tooltip attribute
var noAttribute = "/form/button[not(@disabled)]"; // Doesn't have disabled attribute

Hierarchical Navigation

// Parent-child relationships
var childOfForm = "/form[@title='MyApp']//button"; // Any button under the form
var directChild = "/form[@title='MyApp']/button"; // Direct child button only

// Sibling relationships
var nextSibling = "/form/button[@text='OK']/following-sibling::button[1]";
var previousSibling = "/form/button[@text='Cancel']/preceding-sibling::button[1]";

// Ancestor relationships
var ancestorForm = "//button[@text='Submit']/ancestor::form";

Element Finding Methods

FindSingle vs Find

// FindSingle - expects exactly one element
try
{
    var element = Host.Local.FindSingle<Button>("/form[@title='Untitled - Notepad']//button[@name='Bold (Ctrl+B)']", 2000);
    element.Click();
}
catch (RanorexException ex)
{
    // Element not found or multiple elements found
    Console.WriteLine($"Element lookup failed: {ex.Message}");
}

// Find - returns a collection
var elements = Host.Local.Find<Button>("/form[@title='Untitled - Notepad']//button");
Console.WriteLine($"Found {elements.Count} buttons");

foreach (var button in elements)
{
    Console.WriteLine($"Button text: {button.Text}");
}

TryFindSingle - Safe Element Finding

// Safe element finding without exceptions
if (Host.Local.TryFindSingle("/form[@title='Untitled - Notepad']//button[@name='Bold (Ctrl+B)']", out Element submitButton))
{
    submitButton.As<Button>().Click();
    Console.WriteLine("Bold button clicked");
}
else
{
    Console.WriteLine("Bold button not found");
}

Element Exists Checking

// Check if element exists
string buttonPath = "/form/button[@text='Submit']";

if (Host.Local.FindChildren<Button>(buttonPath).Any())
{
    Console.WriteLine("Button exists");
}

// Alternative using Exists property
var elements = Host.Local.Find<Button>(buttonPath);
if (elements.Count > 0 && elements[0].Exists)
{
    Console.WriteLine("Button exists and is accessible");
}

Dynamic Element Identification

Runtime Path Construction

// Build paths dynamically
string appTitle = "MyApplication";
string buttonText = "Submit";
string dynamicPath = $"/form[@title='{appTitle}']/button[@text='{buttonText}']";

var element = Host.Local.FindSingle<Button>(dynamicPath);

Parameterized Paths

public class ElementPaths
{
    public static string GetButtonPath(string formTitle, string buttonText)
    {
        return $"/form[@title='{formTitle}']/button[@text='{buttonText}']";
    }
    
    public static string GetTextFieldByName(string fieldName)
    {
        return $"//text[@name='{fieldName}']";
    }
}

// Usage
var loginButton = ElementPaths.GetButtonPath("Login Dialog", "Sign In");
var usernameField = ElementPaths.GetTextFieldByName("username");

try
{
    var foundElement = Host.Local.FindSingle<Button>(loginButton, 2000);
    Console.WriteLine("Found button via parameterized path.");
}
catch (RanorexException)
{
    Console.WriteLine("Parameterized path did not find a button.");
}

Handling Dynamic Content

Waiting for Elements

// Wait for element to appear
var element = Host.Local.FindSingle<Button>(buttonPath, TimeSpan.FromSeconds(10));

// Wait for element to disappear
Host.Local.WaitForNotExists(loadingSpinner, TimeSpan.FromSeconds(30));

// Wait with custom condition
Host.Local.WaitFor(() => 
{
    var button = Host.Local.FindSingle<Button>(buttonPath);
    return button.Enabled;
}, TimeSpan.FromSeconds(5));

Handling Changing Attributes

// Elements with changing IDs or dynamic attributes
var dynamicElement = "/dom//div[contains(@class, 'dynamic-') and @data-status='active']";

// Wait for attribute to change
Host.Local.WaitFor(() =>
{
    var element = Host.Local.FindSingle(elementPath);
    return element.GetAttributeValue("status") == "ready";
}, TimeSpan.FromSeconds(10));

Best Practices for Element Identification

1. Use Stable Attributes

// Preferred - stable attributes
var goodPath = "/form/button[@automationid='SubmitButton']";
var betterPath = "/form/button[@name='submitBtn']";

// Avoid - unstable attributes
var badPath = "/form/button[@text='Submit Now!']"; // Text might change
var worsePath = "/form/button[3]"; // Position might change

2. Balance Specificity and Flexibility

// Too specific - might break easily
var tooSpecific = "/form[@title='MyApp v2.1.0']/panel[@index='2']/button[@text='Submit']";

// Too generic - might find wrong element
var tooGeneric = "//button";

// Just right - specific enough but flexible
var justRight = "/form[@title~'MyApp.*']//*[@automationid='SubmitButton']";

3. Use Logical Hierarchies

// Start from a unique parent and navigate down
var loginForm = "/form[@title='Login']";
var usernameInLogin = loginForm + "/text[@name='username']";
var passwordInLogin = loginForm + "/text[@name='password']";
var loginButton = loginForm + "/button[@text='Login']";

Debugging Element Identification

Element Inspector Techniques

// Highlight elements to verify identification
var element = Host.Local.FindSingle(elementPath);
element.Highlight();
Delay.Milliseconds(2000); // Give time to see the highlight

// Print element information
Console.WriteLine($"Element Type: {element.GetType().Name}");
Console.WriteLine($"Text: {element.GetAttributeValueOrDefault("Text", "N/A")}");
Console.WriteLine($"Enabled: {element.GetAttributeValueOrDefault("Enabled", "N/A")}");
Console.WriteLine($"Visible: {element.GetAttributeValueOrDefault("Visible", "N/A")}");

Path Validation

public static void ValidatePath(string xpath)
{
    try
    {
        var elements = Host.Local.Find<Unknown>(xpath);
        Console.WriteLine($"Path '{xpath}' found {elements.Count} elements");
        
        if (elements.Count > 0)
        {
            var firstElement = elements[0];
            Console.WriteLine($"First element: {firstElement.GetType().Name}");
            
            // Show available attributes
            var attributes = firstElement.Element.GetAttributeNames();
            Console.WriteLine("Available attributes:");
            foreach (var attr in attributes)
            {
                var value = firstElement.GetAttributeValueOrDefault(attr, "");
                Console.WriteLine($"  {attr}: {value}");
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Path validation failed: {ex.Message}");
    }
}

Common Identification Challenges

Challenge 1: Elements Without Unique Identifiers

// Solution: Use position relative to unique parent
var uniqueParent = "/form[@title='UniqueWindow']";
var targetButton = uniqueParent + "/panel[2]/button[1]";

// Or use sibling relationships
var targetBySebling = "/form/button[@text='Cancel']/preceding-sibling::button[1]";

Challenge 2: Elements in Frames or Containers

// Web frames
var frameElement = "/dom[@domain='example.com']//iframe[@name='contentFrame']";
var elementInFrame = frameElement + "//button[@id='submit']";

// Container panels
var containerPanel = "/form/panel[@title='Settings']";
var settingOption = containerPanel + "//checkbox[@text='Enable Feature']";

Challenge 3: Mobile-Specific Challenges

// Scrollable lists - find by content
var listItem = "/mobileapp//scrollviewer//listitem[contains(@text, 'Target Item')]";

// Native vs hybrid content
var nativeButton = "/mobileapp/button[@text='Native Button']";
var webViewButton = "/mobileapp/webview//button[@id='webButton']";

Next Steps

Now that you understand element identification:

  1. Working with Repository - Organize your elements efficiently
  2. Advanced UI Interaction - Learn complex interaction patterns
  3. Examples - See practical identification examples

Performance Tips

  • Cache frequently used paths in constants or configuration
  • Use specific parents to narrow search scope
  • Avoid deep hierarchies when possible
  • Test paths regularly as applications evolve

For more advanced techniques, explore our comprehensive guides and practical examples.