Selectors

Learn how TestFlowKit finds and interacts with elements on web pages

Selectors

TestFlowKit uses a smart element detection system that tries multiple selectors until it finds a match. This makes your tests more resilient to UI changes.

How Selectors Work

When you reference an element in a test step:

When the user clicks the "submit_button" button

TestFlowKit:

  1. Looks up "submit_button" in your configuration
  2. Tries each selector in order until one matches
  3. Performs the action on the matched element

Defining Selectors

Elements must be grouped by page or feature context. Groups can be common for shared elements, or match your page names.

Single Selector

For elements with reliable, unique selectors:

frontend:
  elements:
    common:
      header: "#main-header"
      footer: ".site-footer"

Multiple Fallback Selectors

For resilience, provide multiple selectors that TestFlowKit will try in parallel:

frontend:
  elements:
    login_page:
      login_button:
        - "[data-testid='login']"      # Best: Test ID
        - "#login-btn"                  # Good: ID
        - "button.login-button"         # OK: Class
        - "button[type='submit']"       # Fallback: Attribute

Selector Types

CSS Selectors

The most common and recommended selector type:

frontend:
  elements:
    common:
      # By ID
      header: "#main-header"
      
      # By class
      nav: ".navigation"
      
      # By data attribute (recommended)
      login_btn: "[data-testid='login']"
      
      # Combination
      primary_btn: "button.btn.btn-primary"
      
      # Descendant
      nav_link: "nav.main a.nav-link"
      
      # Child
      menu_item: "ul.menu > li"
    
    login_page:
      # By attribute
      email_field: "[name='email']"
      submit_btn: "[type='submit']"

XPath Selectors

For complex element location, prefix with xpath::

frontend:
  elements:
    common:
      # Text content matching
      submit_by_text: "xpath://button[contains(text(), 'Submit')]"
      
      # Position-based
      first_row: "xpath://table/tbody/tr[1]"
      last_item: "xpath://ul/li[last()]"
    
    form_page:
      # Parent navigation
      input_parent: "xpath://input[@id='email']/parent::div"
      
      # Following sibling
      error_for_email: "xpath://input[@id='email']/following-sibling::span[@class='error']"
      
      # Complex conditions
      active_user: "xpath://tr[contains(@class, 'active') and .//td[text()='Admin']]"

When to Use XPath

Use XPath when you need to:

  • Match elements by text content
  • Navigate to parent or sibling elements
  • Use complex conditional logic
  • Access elements by position

⚠️ XPath Performance: CSS selectors are generally faster than XPath. Use XPath only when CSS selectors aren't sufficient.

Selector Best Practices

1. Prefer Test IDs

Work with your development team to add data-testid attributes:

<button data-testid="submit-form">Submit</button>
frontend:
  elements:
    form_page:
      submit_btn: "[data-testid='submit-form']"

Benefits:

  • Won't change with styling updates
  • Clear purpose (for testing)
  • Easy to locate in code

2. Avoid Brittle Selectors

Good:

frontend:
  elements:
    form_page:
      submit_btn:
        - "[data-testid='submit']"
        - "#submit-btn"
        - "[type='submit']"

Bad:

frontend:
  elements:
    form_page:
      submit_btn: "div.container > form > div:nth-child(3) > button"

3. Use Semantic Selectors

Prefer selectors that reflect the element's purpose:

frontend:
  elements:
    common:
      # Good: Role-based
      search_input: "[role='search'] input"
      navigation: "nav[aria-label='Main']"
      
      # Good: Semantic HTML
      main_heading: "main h1"
      article_content: "article.post"

4. Order by Reliability

List selectors from most to least reliable:

frontend:
  elements:
    login_page:
      email_input:
        - "[data-testid='email']"     # 1. Test ID (most reliable)
        - "#email"                     # 2. ID
        - "[name='email']"             # 3. Name attribute
        - "input[type='email']"        # 4. Type (least reliable)

Dynamic Selectors

For elements with dynamic content, use variables:

# Store a dynamic value
When I store the "user_123" into "user_id" variable

# Use in selector (if supported)
Then the "[data-user-id='{{user_id}}']" should be visible

Debugging Selectors

Validate in Browser

  1. Open browser DevTools (F12)
  2. Go to Console
  3. Test CSS selectors:
    document.querySelector('#my-element')
    document.querySelectorAll('.my-class')
    
  4. Test XPath:
    $x("//button[text()='Submit']")
    

Slow Motion Mode

Run tests with slow motion to see element detection:

settings:
  think_time: 500  # 500ms delay between actions

Or via command line:

tkit run --think-time 500

Verbose Logging

Enable verbose logging to see which selectors are tried:

tkit run --verbose

Common Selector Patterns

Forms

frontend:
  elements:
    login_page:
      # Input fields
      email_field: "[name='email']"
      password_field: "[name='password']"
      
      # Checkboxes
      remember_me: "[name='remember']"
      terms_checkbox: "#accept-terms"
      
      # Submit buttons
      login_btn: "form button[type='submit']"
    
    registration_page:
      # Select dropdowns
      country_select: "select[name='country']"

Tables

frontend:
  elements:
    table_page:
      # Table structure
      data_table: "table.data-grid"
      table_header: "table.data-grid thead"
      table_body: "table.data-grid tbody"
      
      # Specific rows
      first_row: "table.data-grid tbody tr:first-child"
      last_row: "table.data-grid tbody tr:last-child"

Modals and Dialogs

frontend:
  elements:
    common:
      modal: "[role='dialog']"
      modal_title: "[role='dialog'] h2"
      modal_close: "[role='dialog'] button[aria-label='Close']"
      modal_confirm: "[role='dialog'] button.btn-primary"
frontend:
  elements:
    common:
      main_nav: "nav[aria-label='Main navigation']"
      nav_home: "nav a[href='/']"
      nav_about: "nav a[href='/about']"
      mobile_menu_toggle: "button[aria-label='Toggle menu']"

Next Steps