Skip to main content

A simplified Python-playwright utility that provides more flexible locator strategies which is easier for users. Now with self-healing capabilities

Project description

smart-locators-playwright

๐Ÿ” A smart, resilient way to locate elements in Playwright for Python โ€” reduce flakiness, simplify locators, and write cleaner test code.

PyPI version

๐Ÿš€ What is it?

smart-locators-playwright is a utility wrapper around Playwright's native locator system. It allows you to define multiple locator strategies at once, and it will automatically try each until one succeeds โ€” no more writing fallback logic or suffering flaky tests due to minor DOM changes.

Why Smart Locators?

Element location is often the most brittle part of web automation. Web UIs change frequently, and a single broken locator can cascade failures across your entire test suite. Smart Locators solves this by providing:

  • Unified API: One simple find() method instead of chaining multiple Playwright methods
  • Automatic Fallback Strategy: Tries multiple locators in sequence until one succeeds
  • Custom Attribute Support: Locate elements by any HTML attribute automatically converted to XPath
  • Reduced Maintenance: When UI changes occur, your tests can still pass using fallback locators
  • Both Sync & Async Support: Works seamlessly with playwright.sync_api and playwright.async_api
  • Self-Healing Locators: Automatically extract and store element attributes for easy updates

๐Ÿ“ฆ Installation

Install smart-locators-playwright with pip:

pip install smart-locators-playwright

โœ… Key Features

1. ๐Ÿ”— Unified Element Location API

Instead of writing multiple locator calls:

# Traditional Playwright (brittle)
element = page.get_by_role("button")
element_2 = page.locator("//button[@id='submit_btn']")

Use a single, intuitive find() method:

# Smart Locators (resilient)
element = smart.find(id="submit", text="Submit", role="button")

2. ๐Ÿงฉ Multiple Location Strategies

The find() method supports all standard Playwright locators, plus custom strategies:

โœ… Built-in Playwright Strategies:

  • role - ARIA role (button, textbox, etc.)
  • text - Text content
  • label - Associated label text
  • alt - Alt text (for images)
  • placeholder - Placeholder text
  • title - Title attribute

๐Ÿ› ๏ธ Additional Custom Strategies:

  • id - ID attribute (auto-converted to XPath)
  • name - Name attribute (auto-converted to XPath)
  • css - CSS selector
  • xpath - XPath expression
  • Any custom attribute - Auto-converted to XPath (e.g., data-testid, data-cy)

3. ๐Ÿ”„ Automatic Fallback Strategy

Locators are tried sequentially until one succeeds. This makes your tests resilient to DOM changes:

# Will try: id โ†’ name โ†’ text (in that order)
element = smart.find(id="loginBtn", name="submitLogin", text="Login")

If the element's ID changes, the library automatically falls back to the name attribute, then text content. Your test remains stable without refactoring.

4. ๐Ÿ‘ฅ Support for Multiple Elements

By default, find() returns the first matching element. Set first_match=False to retrieve all matching elements:

# Get the first matching button (default)
button = smart.find(role="button", first_match=True)
button.click()

# Get all matching buttons
all_buttons = smart.find(role="button", first_match=False)

# Iterate through all buttons
for button in all_buttons.all():
    print(button.text_content())

5. ๐Ÿ”ง Self-Healing Locators

Smart Locators can automatically extract and store element attributes for a self-healing mechanism:

# Enable self-healing: save element attributes to JSON

# Read locators.json
with open("./tests/locators.json", "r", encoding="utf-8") as f:
    locators_data = json.load(f)

# Extract ID value
element_id = locators_data["submit_btn"]["id"]

element = smart.find(
    id=element_id,
    name="submit",
    text="Submit",
    first_match=True,
    locator_update=True,              # Enable extraction
    element_name="submit_button",     # Locator entry name
    locators_file="./locators/app_locators.json"
)

element.click()

Generated JSON file (default location: {cwd}/SmartLocatorsLogs/default.json):

{
  "submit_button": {
    "id": "submit-btn",
    "class": "btn btn-primary",
    "type": "button",
    "data-testid": "submit-button",
    "aria-label": "Submit form"
  }
}

Benefits:

  • Maintain a centralized locator library
  • Document all available attributes for each element
  • Make updates quick when UI changes
  • Share locators across multiple test files
  • Automatic XPath generation for custom attributes

๐Ÿ“š Usage Examples

Finding a Single Element

from playwright.sync_api import sync_playwright
from smart_locators_playwright import SmartLocators

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com")

    smart = SmartLocators(page)

    # Try multiple strategies
    element = smart.find(
        id="submit-btn",
        name="submit",
        text="Submit",
        role="button",
        first_match=True
    )

    element.click()
    browser.close()

Finding Multiple Elements

# Return all matching elements
buttons = smart.find(
    role="button",
    first_match=False
)

# Iterate through elements
for button in buttons.all():
    print(button.text_content())

Custom Attributes with Auto XPath Conversion

# Find by data attributes or any custom attribute
# These are automatically converted to XPath expressions
element = smart.find(
    data_testid="submit-button",
    role="button"
)

# Auto-converted to: //*[@data-testid='submit-button']
element.click()

Combining with Playwright's Chain API

The returned object is a standard Playwright Locator that can be further refined:

# Find a button with additional filtering
button = smart.find(role="button", text="Submit").filter(has_text="Submit")

# Find all table rows
rows = smart.find(
    css="table#users tr",
    first_match=False
)

# Process each row
for row in rows.all():
    username = row.locator("td").nth(0).text_content()
    email = row.locator("td").nth(1).text_content()
    print(f"User: {username}, Email: {email}")

Self-Healing with Locator Update

# Automatically extract and save element attributes

# Read locators.json
with open("./tests/locators.json", "r", encoding="utf-8") as f:
    locators_data = json.load(f)

# Extract ID value
element_id = locators_data["submit_btn"]["id"]

button = smart.find(
    id=element_id,
    name="submit_button",
    text="Submit",
    element_name="submit_btn",
    locator_update=True,
    locators_file="./tests/locators.json"
)

# Element attributes are now saved to ./tests/locators.json
button.click()

Async Support

from playwright.async_api import async_playwright
from smart_locators_playwright import SmartLocators

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.goto("https://example.com")

        smart = SmartLocators(page)
        element = smart.find(role="button", text="Submit")
        await element.click()
        await browser.close()

import asyncio
asyncio.run(main())

๐Ÿ“– Supported Locators

Locator Type Description Playwright Native Auto-Conversion
role ARIA role (button, textbox, etc.) โœ… โ€”
text Element text content โœ… โ€”
label Associated label text โœ… โ€”
alt Alt attribute (images, media) โœ… โ€”
placeholder Input placeholder text โœ… โ€”
title Title attribute โœ… โ€”
id ID attribute โŒ XPath
name Name attribute โŒ XPath
css CSS selector โœ… โ€”
xpath XPath expression โœ… โ€”
Custom attributes Any HTML attribute โŒ XPath

๐Ÿ”Œ API Reference

SmartLocators(page)

Initializes the Smart Locators instance.

Parameters:

  • page (playwright.sync_api.Page or playwright.async_api.Page): The Playwright page object.

Attributes:

  • _playwright_allowed_locators: List of native Playwright locator types
  • _custom_locators: List of custom locator types (css, xpath)
  • _locators_file: Default locators file path ({cwd}/SmartLocatorsLogs/default.json)

Example:

from playwright.sync_api import sync_playwright
from smart_locators_playwright import SmartLocators

with sync_playwright() as p:
    page = p.chromium.launch().new_page()
    smart = SmartLocators(page)

find(...)

Locates web elements using one or more locator strategies with automatic fallback.

Parameters:

Parameter Type Default Description
id str None Element's id attribute
name str None Element's name attribute
css str None CSS selector expression
xpath str None XPath expression
label str None Associated label text
alt str None Alt text (images, media)
placeholder str None Input placeholder text
role str None ARIA role (button, textbox, etc.)
text str None Element text content
title str None Element title attribute
first_match bool True Return first match only; if False, returns all matches
locator_update bool False [Self-Healing] Enable automatic attribute extraction
element_name str "" [Self-Healing] Key name for locator entry in JSON
locators_file str "" [Self-Healing] Path to JSON file (uses default if empty)
**kwargs dict {} Custom HTML attributes (auto-converted to XPath)

Returns:

  • Locator: A Playwright Locator object
    • When first_match=True: Returns single element Locator
    • When first_match=False: Returns Locator collection (use .all() to iterate)

Raises:

  • custom_exceptions.NoSuchElementException: If no element matches any provided locators

Example:

# Single element
button = smart.find(role="button", text="Submit")

# Multiple elements
all_buttons = smart.find(role="button", first_match=False)

# With self-healing
element = smart.find(
    id="test-id",
    role="button",
    element_name="my_button",
    locator_update=True,
    locators_file="./locators.json"
)

update_locators(element_name, webelement, locators_file)

Manually updates a locator by extracting and saving element attributes.

Parameters:

  • element_name (str): Key name for the locator entry
  • webelement (Locator): The Playwright Locator object
  • locators_file (str): Path to the JSON file

Returns:

  • bool: True if successful, False if failed

Example:

element = smart.find(role="button")
smart.update_locators("submit_button", element, "./locators.json")

extract_html_attributes(html_string)

Parses HTML string and extracts all attributes.

Parameters:

  • html_string (str): HTML content to parse

Returns:

  • dict: Dictionary of attribute name-value pairs

Example:

html = '<button id="btn" class="primary" data-test="submit">Click</button>'
attrs = smart.extract_html_attributes(html)
# {'id': 'btn', 'class': 'primary', 'data-test': 'submit'}

write_locators_to_json(element_name, attributes, locators_file)

Writes element attributes to a JSON file with validation.

Parameters:

  • element_name (str): Key name for the locator entry
  • attributes (dict): Element attributes dictionary
  • locators_file (str): Path to JSON file (creates if doesn't exist)

Returns:

  • bool: True if successful, False otherwise

Features:

  • Auto-creates parent directories
  • Validates inputs
  • Appends to existing JSON files
  • Formatted with 2-space indentation
  • Handles UTF-8 encoding

Example:

attrs = {"id": "btn-1", "class": "primary"}
success = smart.write_locators_to_json("my_button", attrs, "./locators.json")

๐ŸŽฏ Advanced Use Cases

Smart Fallback for Dynamic IDs

# If element ID changes dynamically, fallback to other strategies
element = smart.find(
    id="dynamic-id-12345",  # Tried first
    name="login_button",     # Fallback
    role="button",           # Fallback
    text="Login"             # Fallback
)

Centralized Locator Repository

# Define once, use everywhere
LOGIN_BUTTON = {
    "id": "login-btn",
    "role": "button",
    "text": "Login"
}

element = smart.find(**LOGIN_BUTTON)

Maintaining Locator Library

# Build locator library on first run
element = smart.find(
    role="button",
    text="Submit",
    element_name="submit_button",
    locator_update=True,
    locators_file="./tests/locators/main.json"
)

# Library automatically grows as you test

๐Ÿ› Error Handling

The library raises NoSuchElementException when no locator strategy succeeds:

from smart_locators_playwright import custom_exceptions

try:
    element = smart.find(
        id="nonexistent",
        name="also-nonexistent"
    )
except custom_exceptions.NoSuchElementException:
    print("Element not found with any locator")

๐Ÿ“ Logging

The library uses Python's logging module for debugging:

import logging

logging.basicConfig(level=logging.DEBUG)
# View detailed locator extraction and file operations

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request on GitHub.

๐Ÿ“ License

This project is licensed under the Apache License - see the LICENSE file for details.

๐Ÿ› Issues & Support

Found a bug or have a feature request? Please open an issue on GitHub.

๐ŸŽ“ Best Practices

  1. Start with most specific locators: Place exact identifiers first (id, role) before generic ones (text)
  2. Use self-healing for critical elements: Enable locator_update=True for elements that frequently change
  3. Maintain a centralized locator file: Keep all locators_file paths consistent across tests
  4. Combine strategies: Use multiple locators to create resilient element location
  5. Regular maintenance: Review and update JSON locator files when UI changes occur

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smart_locators_playwright-2.0.4.tar.gz (17.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smart_locators_playwright-2.0.4-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file smart_locators_playwright-2.0.4.tar.gz.

File metadata

File hashes

Hashes for smart_locators_playwright-2.0.4.tar.gz
Algorithm Hash digest
SHA256 e0672371c50811f5efaa652f4b7ffccbe32273cadb7883d002fa84a4c72639f8
MD5 bad026164fd53b9037bd341a7e2087cc
BLAKE2b-256 52d9c2ce215da8c12aa7be8095a146b64898349ae2b15a4d32e4dacc57831015

See more details on using hashes here.

File details

Details for the file smart_locators_playwright-2.0.4-py3-none-any.whl.

File metadata

File hashes

Hashes for smart_locators_playwright-2.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 4630afcffee34ca37e94bd252a94174f1c2d3f540700902c661bd222f5250b82
MD5 180d163c134d14cf12e31face3b90a88
BLAKE2b-256 50a5b6a1433aaad97a03159ca82acebc512f2763e4a6a974b6f88f678f7445e1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page