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.
๐ 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_apiandplaywright.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 contentlabel- Associated label textalt- Alt text (for images)placeholder- Placeholder texttitle- Title attribute
๐ ๏ธ Additional Custom Strategies:
id- ID attribute (auto-converted to XPath)name- Name attribute (auto-converted to XPath)css- CSS selectorxpath- 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.Pageorplaywright.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)
- When
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 entrywebelement(Locator): The Playwright Locator objectlocators_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 entryattributes(dict): Element attributes dictionarylocators_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
- Start with most specific locators: Place exact identifiers first (id, role) before generic ones (text)
- Use self-healing for critical elements: Enable
locator_update=Truefor elements that frequently change - Maintain a centralized locator file: Keep all
locators_filepaths consistent across tests - Combine strategies: Use multiple locators to create resilient element location
- Regular maintenance: Review and update JSON locator files when UI changes occur
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smart_locators_playwright-2.0.4.tar.gz.
File metadata
- Download URL: smart_locators_playwright-2.0.4.tar.gz
- Upload date:
- Size: 17.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e0672371c50811f5efaa652f4b7ffccbe32273cadb7883d002fa84a4c72639f8
|
|
| MD5 |
bad026164fd53b9037bd341a7e2087cc
|
|
| BLAKE2b-256 |
52d9c2ce215da8c12aa7be8095a146b64898349ae2b15a4d32e4dacc57831015
|
File details
Details for the file smart_locators_playwright-2.0.4-py3-none-any.whl.
File metadata
- Download URL: smart_locators_playwright-2.0.4-py3-none-any.whl
- Upload date:
- Size: 14.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4630afcffee34ca37e94bd252a94174f1c2d3f540700902c661bd222f5250b82
|
|
| MD5 |
180d163c134d14cf12e31face3b90a88
|
|
| BLAKE2b-256 |
50a5b6a1433aaad97a03159ca82acebc512f2763e4a6a974b6f88f678f7445e1
|