AI-powered test automation framework that automatically heals broken element locators in Selenium and Playwright tests
Project description
AutoHeal Locator — Python
AI-powered test automation library that automatically heals broken locators for Selenium and Playwright.
When element selectors break due to UI changes, AutoHeal finds the elements using DOM analysis, visual recognition, and smart fallback strategies — with results cached so AI is called only once per broken selector.
Python port of the original Java AutoHeal Locator.
Quick Start
New here? See START_HERE.md first — it guides you to the right place based on what you want to do.
# Selenium project
pip install autoheal-locator
# Playwright project
pip install autoheal-locator[playwright]
# With Redis cache support
pip install autoheal-locator[redis]
# Everything
pip install autoheal-locator[all]
Quickstart Config (Zero Setup)
For the fastest way to get started, use the built-in get_autoheal_config(). No config file needed!
from autoheal import get_autoheal_config
from autoheal.impl.adapter import PlaywrightWebAutomationAdapter
from autoheal.reporting import ReportingAutoHealLocator
# Auto-detects provider from environment variables
config = get_autoheal_config()
adapter = PlaywrightWebAutomationAdapter(page)
locator = ReportingAutoHealLocator(adapter, config)
# Start healing broken selectors
element = await locator.find_element_async("#broken-selector", "Submit button")
Just set one environment variable (in .env or shell):
# Option 1: Groq (FREE - recommended)
GROQ_API_KEY=gsk_your_key_here
# Option 2: OpenAI
OPENAI_API_KEY=sk-your_key_here
# Option 3: Google Gemini
GEMINI_API_KEY=AIza_your_key_here
# Option 4: Anthropic Claude
ANTHROPIC_API_KEY=sk-ant-your_key_here
# Option 5: DeepSeek
DEEPSEEK_API_KEY=your_key_here
# Option 6: Local Model (Ollama/LM Studio)
AUTOHEAL_API_URL=http://localhost:11434/v1/chat/completions
AUTOHEAL_MODEL=qwen2.5:7b
Optionally override the default model:
GROQ_API_KEY=gsk_your_key_here
GROQ_MODEL=llama-3.1-8b-instant
Custom Config (Optional)
Need more control? Create your own config/autoheal_config.py to customize:
- Cache settings (type, size, expiry)
- Execution strategy (DOM_ONLY, VISUAL_FIRST, etc.)
- Timeouts and retry settings
- Report output directory
See Full Configuration Reference or copy from the demo projects
Demo Projects
| Project | Framework | Link |
|---|---|---|
| Selenium demo | Selenium + Python | autoheal-selenium-python-demo |
| Playwright demo | Playwright + Python | autoheal-playwright-demo-python |
Both demos include a START_HERE.md with full step-by-step setup instructions.
Table of Contents
- How It Works
- Key Features
- Selenium Quick Start
- Playwright Quick Start
- AI Provider Configuration
- Execution Strategies
- Cache Configuration
- Performance Configuration
- Reporting
- pytest Integration
- Full Configuration Reference
- API Reference
- Best Practices
- Troubleshooting
How It Works
Test calls find_element("#selector", "description")
│
▼
┌─────────────────────┐
│ 1. Try Original │──── Found ────► Return element
│ Selector │
└─────────────────────┘
│ Not found (quick timeout)
▼
┌─────────────────────┐
│ 2. Check Cache │──── Hit ──────► Try cached selector ──► Return element
└─────────────────────┘
│ Cache miss
▼
┌─────────────────────┐
│ 3. AI Healing │
│ · DOM Analysis │──── Found ────► Cache result ──► Return element
│ · Visual Analysis │
└─────────────────────┘
│ All failed
▼
ElementNotFoundException
- Try Original — attempts the selector with a short timeout (default 500ms)
- Check Cache — looks up previously healed selectors to avoid repeat AI calls
- AI Healing — DOM analysis reads the page HTML; Visual analysis reads a screenshot
- Cache Result — stores the healed selector so future runs skip the AI step
Key Features
- AI-Powered Healing — DOM analysis and visual recognition to find relocated elements
- Multiple AI Providers — Groq (free), Gemini, OpenAI, Anthropic, DeepSeek, Local (Ollama/LM Studio)
- Flexible Strategies — SMART_SEQUENTIAL, DOM_ONLY, VISUAL_FIRST, SEQUENTIAL, PARALLEL
- Smart Caching — Persistent file, in-memory, Redis, or no cache
- Sync and Async APIs —
find_element()andfind_element_async() - Selenium Support — CSS, XPath, ID, Name, Class, Tag selectors
- Playwright Support — CSS selectors, XPath, and native Playwright Locators (
get_by_role,get_by_text, etc.) - HTML / JSON / Text Reports — Detailed healing reports per test session
- pytest-Ready — Drop-in fixture pattern, no test rewrites needed
Framework Comparison
| Feature | Selenium | Playwright |
|---|---|---|
| Locator types | CSS, XPath, ID, Name, Class, Tag, Link Text | CSS, XPath, get_by_role, get_by_text, get_by_placeholder, etc. |
| Native objects | WebElement |
Locator |
| Sync API | find_element() |
— |
| Async API | find_element_async() |
find_element_async(), find_async() |
| Visual analysis | Supported | Supported |
| DOM analysis | Supported | Supported |
| Caching | Unified cache | Unified cache |
Selenium Quick Start
1. Set Your API Key
# .env — Groq is FREE and the fastest option to get started
GROQ_API_KEY=gsk_your_api_key_here
Get a free key at console.groq.com — no credit card required.
2. Before and After
Before AutoHeal — breaks when UI changes:
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/login")
button = driver.find_element(By.ID, "submit-btn") # breaks if renamed
button.click()
After AutoHeal — self-healing:
from selenium import webdriver
from autoheal import AutoHealLocator
from autoheal.impl.adapter import SeleniumWebAutomationAdapter
driver = webdriver.Chrome()
adapter = SeleniumWebAutomationAdapter(driver)
locator = AutoHealLocator.builder().with_web_adapter(adapter).build()
driver.get("https://example.com/login")
button = locator.find_element("#submit-btn", "Submit button") # heals automatically
button.click()
3. All Supported Locator Types
AutoHeal detects the locator type from the string format — no By.X needed:
# CSS selectors
locator.find_element("#submit-btn", "Submit button")
locator.find_element(".btn-primary", "Primary button")
locator.find_element("button[type='submit']", "Submit button")
locator.find_element("form > input.email", "Email input")
# XPath
locator.find_element("//button[@id='submit']", "Submit button")
locator.find_element("//input[@placeholder='Username']", "Username field")
locator.find_element("//a[contains(text(),'Login')]", "Login link")
# Bare ID / Name / Class
locator.find_element("submit-btn", "Submit button")
locator.find_element("username", "Username field")
# Multiple elements
items = locator.find_elements(".product-card", "Product cards")
# Presence check — no exception if missing
if locator.is_element_present("#promo-banner", "Promo banner"):
locator.find_element("#promo-banner", "Promo banner").click()
4. Full Login Example
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from autoheal import AutoHealLocator
from autoheal.impl.adapter import SeleniumWebAutomationAdapter
from dotenv import load_dotenv
load_dotenv()
@pytest.fixture
def driver():
options = Options()
options.add_argument("--start-maximized")
service = Service(ChromeDriverManager().install())
d = webdriver.Chrome(service=service, options=options)
d.implicitly_wait(10)
yield d
d.quit()
@pytest.fixture
def autoheal(driver):
adapter = SeleniumWebAutomationAdapter(driver)
return AutoHealLocator.builder().with_web_adapter(adapter).build()
def test_login(driver, autoheal):
driver.get("https://www.saucedemo.com")
autoheal.find_element("#user-name", "Username field").send_keys("standard_user")
autoheal.find_element("#password", "Password field").send_keys("secret_sauce")
autoheal.find_element("#login-button", "Login button").click()
assert "/inventory.html" in driver.current_url
def test_login_broken_selectors(driver, autoheal):
"""AutoHeal heals intentionally broken selectors."""
driver.get("https://www.saucedemo.com")
autoheal.find_element("#user-name-wrong", "Username field").send_keys("standard_user")
autoheal.find_element("#password-wrong", "Password field").send_keys("secret_sauce")
autoheal.find_element("#login-button-wrong", "Login button").click()
assert "/inventory.html" in driver.current_url # still passes!
Playwright Quick Start
1. Install with Playwright Extra
pip install autoheal-locator[playwright]
playwright install chromium
2. Usage
AutoHeal for Playwright is fully async. It supports both CSS string selectors and native Playwright Locators:
import pytest
from playwright.async_api import async_playwright
from autoheal.impl.adapter import PlaywrightWebAutomationAdapter
from autoheal.reporting.reporting_autoheal_locator import ReportingAutoHealLocator
# Using a broken CSS selector — AI heals it
async def test_heal_broken_selector(page, autoheal_locator):
await page.goto("https://www.saucedemo.com")
username = await autoheal_locator.find_element_async(
"#user-name-BROKEN",
"Username input field on the SauceDemo login page"
)
await username.fill("standard_user")
assert await username.input_value() == "standard_user"
# Using a broken native Playwright Locator — AI heals it
async def test_heal_broken_native_locator(page, autoheal_locator):
await page.goto("https://www.saucedemo.com")
username = await autoheal_locator.find_async(
page.get_by_role("textbox", name="Username-BROKEN"),
"Username input field on the SauceDemo login page"
)
await username.fill("standard_user")
assert await username.input_value() == "standard_user"
3. pytest Fixture
import pytest
from playwright.async_api import async_playwright
from autoheal.impl.adapter import PlaywrightWebAutomationAdapter
from autoheal.reporting.reporting_autoheal_locator import ReportingAutoHealLocator
from config.autoheal_config import get_autoheal_config
@pytest.fixture(scope="function")
async def browser():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
yield browser
await browser.close()
@pytest.fixture(scope="function")
async def page(browser):
page = await browser.new_page()
yield page
await page.close()
@pytest.fixture(scope="function")
def autoheal_locator(page):
config = get_autoheal_config()
adapter = PlaywrightWebAutomationAdapter(page)
locator = ReportingAutoHealLocator(adapter, config)
yield locator
locator.shutdown() # generates HTML/JSON/text reports
4. Native Locator Types Supported
# Role-based
await autoheal_locator.find_async(page.get_by_role("button", name="Login"), "Login button")
await autoheal_locator.find_async(page.get_by_role("textbox", name="Username"), "Username field")
# Text-based
await autoheal_locator.find_async(page.get_by_text("Add to cart", exact=True), "Add to cart button")
# Placeholder
await autoheal_locator.find_async(page.get_by_placeholder("Email address"), "Email input")
# CSS / XPath
await autoheal_locator.find_async(page.locator("#submit-btn"), "Submit button")
# CSS string shorthand
await autoheal_locator.find_element_async("#submit-btn", "Submit button")
AI Provider Configuration
Configure one provider only. AutoHeal detects which one to use based on which environment variable is set.
Groq — Free, Recommended for Getting Started
GROQ_API_KEY=gsk_your_api_key_here
GROQ_MODEL=llama-3.3-70b-versatile # optional
Get a free key at console.groq.com.
Google Gemini
GEMINI_API_KEY=AIza_your_api_key_here
GEMINI_MODEL=gemini-2.0-flash # optional
Free tier has rate limits. Use
DOM_ONLYorSMART_SEQUENTIALstrategy to reduce API calls.
OpenAI
OPENAI_API_KEY=sk-proj-your_key
OPENAI_MODEL=gpt-4o-mini # optional — gpt-4o-mini is cheapest
Local LLM — Ollama / LM Studio (Free, Private)
# Ollama
AUTOHEAL_API_URL=http://localhost:11434/v1/chat/completions
AUTOHEAL_MODEL=deepseek-coder-v2:16b
AUTOHEAL_API_KEY=not-needed
# LM Studio
AUTOHEAL_API_URL=http://localhost:1234/v1/chat/completions
AUTOHEAL_MODEL=your-loaded-model-name
AUTOHEAL_API_KEY=not-needed
Provider Comparison
| Provider | Visual | Cost | Speed | Notes |
|---|---|---|---|---|
| Groq | Yes | Free | Fastest | Best for getting started |
| Gemini | Yes | Low | Fast | Free tier has rate limits |
| OpenAI | Yes | Medium | Fast | gpt-4o-mini is cost-effective |
| Anthropic | Yes | Medium | Medium | Requires LiteLLM proxy |
| DeepSeek | No | Low | Fast | Good for DOM-only use |
| Ollama | Depends | Free | Varies | Private, no data sent externally |
Programmatic Configuration
from autoheal.config import AIConfig
from autoheal.models.enums import AIProvider
ai_config = AIConfig.builder() \
.provider(AIProvider.GROQ) \
.api_key(os.getenv("GROQ_API_KEY")) \
.model("llama-3.3-70b-versatile") \
.temperature_dom(0.1) \
.build()
Execution Strategies
# .env
AUTOHEAL_EXECUTION_STRATEGY=SMART_SEQUENTIAL
| Strategy | Cost | Speed | Best For |
|---|---|---|---|
SMART_SEQUENTIAL |
Low | Medium | Default — DOM first, visual fallback |
DOM_ONLY |
Lowest | Fastest | CI/CD, cost-sensitive |
VISUAL_FIRST |
High | Medium | Complex UIs where DOM structure unreliable |
SEQUENTIAL |
Medium | Medium | Debugging |
PARALLEL |
Highest | Fastest healing | Speed-critical scenarios |
Cache Configuration
from autoheal.config import CacheConfig
from autoheal.config.cache_config import CacheType
from datetime import timedelta
# Persistent file (default) — survives restarts
cache_config = CacheConfig.builder() \
.cache_type(CacheType.PERSISTENT_FILE) \
.maximum_size(500) \
.expire_after_write(timedelta(hours=24)) \
.build()
# In-memory — fastest, lost when process ends
cache_config = CacheConfig.builder() \
.cache_type(CacheType.CAFFEINE) \
.maximum_size(1000) \
.build()
# Redis — shared across machines
cache_config = CacheConfig.builder() \
.cache_type(CacheType.REDIS) \
.redis_host("localhost") \
.redis_port(6379) \
.build()
Cache Strategy via YAML
Create config/cache_strategy.yaml:
cache:
type: PERSISTENT_FILE # PERSISTENT_FILE | CAFFEINE | REDIS
maximum_size: 500
expire_after_hours: 24
# Redis-only settings
redis:
host: localhost
port: 6379
password: null
Cache Management
locator.clear_cache()
locator.remove_cached_selector("#old-btn", "Submit button")
print(locator.get_cache_size())
metrics = locator.get_cache_metrics()
print(f"Hit rate: {metrics.total_hits / (metrics.total_hits + metrics.total_misses):.0%}")
Performance Configuration
# .env
AUTOHEAL_QUICK_TIMEOUT_MS=500 # timeout to try original selector before healing
AUTOHEAL_ELEMENT_TIMEOUT_SEC=10 # max time per element lookup
from autoheal.config import PerformanceConfig
from autoheal.models.enums import ExecutionStrategy
from datetime import timedelta
perf_config = PerformanceConfig.builder() \
.execution_strategy(ExecutionStrategy.SMART_SEQUENTIAL) \
.quick_check_timeout(timedelta(milliseconds=500)) \
.element_timeout(timedelta(seconds=10)) \
.build()
Reporting
from autoheal.config import ReportingConfig
from pathlib import Path
reporting_config = ReportingConfig.builder() \
.enabled(True) \
.generate_html(True) \
.generate_json(True) \
.generate_text(True) \
.output_directory(str(Path(__file__).parent.parent / "autoheal-reports")) \
.report_name_prefix("MyProject") \
.console_logging(True) \
.build()
Console output during test run:
[SUCCESS] [DOM] [1250ms] [820 tokens] #user-name-wrong -> #user-name
[SUCCESS] [VISUAL] [3400ms] [1200 tokens] #btn-wrong -> .btn-login
[SUCCESS] [CACHED] [2ms] #password-wrong -> #password
[FAILED] [FAIL] [350ms] #nonexistent -> FAILED
pytest Integration
conftest.py — Selenium
import pytest
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from dotenv import load_dotenv
from autoheal import AutoHealLocator
from autoheal.impl.adapter import SeleniumWebAutomationAdapter
from autoheal.reporting.reporting_autoheal_locator import ReportingAutoHealLocator
load_dotenv(Path(__file__).parent / ".env")
@pytest.fixture(scope="function")
def driver():
options = Options()
options.add_argument("--start-maximized")
service = Service(ChromeDriverManager().install())
d = webdriver.Chrome(service=service, options=options)
d.implicitly_wait(10)
yield d
d.quit()
@pytest.fixture(scope="function")
def autoheal(driver):
from config.autoheal_config import get_autoheal_config
adapter = SeleniumWebAutomationAdapter(driver)
locator = ReportingAutoHealLocator(adapter, get_autoheal_config())
yield locator
metrics = locator.autoheal.get_metrics()
print(f"\nAutoHeal: {metrics.successful_requests}/{metrics.total_requests} healed")
conftest.py — Playwright
import pytest
from playwright.async_api import async_playwright
from autoheal.impl.adapter import PlaywrightWebAutomationAdapter
from autoheal.reporting.reporting_autoheal_locator import ReportingAutoHealLocator
@pytest.fixture(scope="function")
async def browser():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
yield browser
await browser.close()
@pytest.fixture(scope="function")
async def page(browser):
page = await browser.new_page()
yield page
await page.close()
@pytest.fixture(scope="function")
def autoheal_locator(page):
from config.autoheal_config import get_autoheal_config
adapter = PlaywrightWebAutomationAdapter(page)
locator = ReportingAutoHealLocator(adapter, get_autoheal_config())
yield locator
locator.shutdown()
config/autoheal_config.py
import os
from pathlib import Path
from datetime import timedelta
from autoheal import AutoHealConfiguration
from autoheal.config import AIConfig, CacheConfig, PerformanceConfig, ResilienceConfig, ReportingConfig
from autoheal.config.cache_config import CacheType
from autoheal.models.enums import AIProvider, ExecutionStrategy
def get_autoheal_config() -> AutoHealConfiguration:
# Auto-detect AI provider from env vars
if os.getenv("GROQ_API_KEY"):
ai = AIConfig.builder() \
.provider(AIProvider.GROQ) \
.api_key(os.getenv("GROQ_API_KEY")) \
.api_url("https://api.groq.com/openai/v1/chat/completions") \
.model(os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")) \
.build()
elif os.getenv("GEMINI_API_KEY"):
ai = AIConfig.builder() \
.provider(AIProvider.GOOGLE_GEMINI) \
.api_key(os.getenv("GEMINI_API_KEY")) \
.model(os.getenv("GEMINI_MODEL", "gemini-2.0-flash")) \
.build()
elif os.getenv("OPENAI_API_KEY"):
ai = AIConfig.builder() \
.provider(AIProvider.OPENAI) \
.api_key(os.getenv("OPENAI_API_KEY")) \
.api_url("https://api.openai.com/v1/chat/completions") \
.model(os.getenv("OPENAI_MODEL", "gpt-4o-mini")) \
.build()
elif os.getenv("AUTOHEAL_API_URL"):
ai = AIConfig.builder() \
.provider(AIProvider.OPENAI) \
.api_key(os.getenv("AUTOHEAL_API_KEY", "not-needed")) \
.api_url(os.getenv("AUTOHEAL_API_URL")) \
.model(os.getenv("AUTOHEAL_MODEL", "deepseek-coder-v2:16b")) \
.build()
else:
raise ValueError("No AI provider configured. Set one of: GROQ_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY")
strategy_map = {
"SMART_SEQUENTIAL": ExecutionStrategy.SMART_SEQUENTIAL,
"DOM_ONLY": ExecutionStrategy.DOM_ONLY,
"VISUAL_FIRST": ExecutionStrategy.VISUAL_FIRST,
"SEQUENTIAL": ExecutionStrategy.SEQUENTIAL,
"PARALLEL": ExecutionStrategy.PARALLEL,
}
strategy = strategy_map.get(
os.getenv("AUTOHEAL_EXECUTION_STRATEGY", "SMART_SEQUENTIAL").upper(),
ExecutionStrategy.SMART_SEQUENTIAL
)
project_root = Path(__file__).parent.parent
return AutoHealConfiguration.builder() \
.ai(ai) \
.cache(
CacheConfig.builder()
.cache_type(CacheType.PERSISTENT_FILE)
.maximum_size(500)
.expire_after_write(timedelta(hours=24))
.build()
) \
.performance(
PerformanceConfig.builder()
.execution_strategy(strategy)
.quick_check_timeout(timedelta(milliseconds=500))
.element_timeout(timedelta(seconds=10))
.build()
) \
.resilience(ResilienceConfig.builder().retry_max_attempts(3).build()) \
.reporting(
ReportingConfig.builder()
.enabled(True)
.generate_html(True)
.generate_json(True)
.generate_text(True)
.output_directory(str(project_root / "autoheal-reports"))
.report_name_prefix("AutoHeal")
.console_logging(True)
.build()
) \
.build()
Full Configuration Reference
.env File
# AI Provider — configure ONLY ONE
GROQ_API_KEY=gsk_your_key
GROQ_MODEL=llama-3.3-70b-versatile
# GEMINI_API_KEY=AIza_your_key
# GEMINI_MODEL=gemini-2.0-flash
# OPENAI_API_KEY=sk-proj-your_key
# OPENAI_MODEL=gpt-4o-mini
# AUTOHEAL_API_URL=http://localhost:11434/v1/chat/completions
# AUTOHEAL_MODEL=deepseek-coder-v2:16b
# AUTOHEAL_API_KEY=not-needed
# Execution Strategy
# Options: SMART_SEQUENTIAL | DOM_ONLY | VISUAL_FIRST | SEQUENTIAL | PARALLEL
AUTOHEAL_EXECUTION_STRATEGY=SMART_SEQUENTIAL
# Performance
AUTOHEAL_QUICK_TIMEOUT_MS=500
AUTOHEAL_ELEMENT_TIMEOUT_SEC=10
Builder Reference
| Builder | Method | Default | Purpose |
|---|---|---|---|
AIConfig |
.provider(AIProvider.X) |
— | Required |
AIConfig |
.api_key(str) |
— | Required |
AIConfig |
.model(str) |
Provider default | Model name |
AIConfig |
.temperature_dom(float) |
0.1 |
DOM analysis temperature |
CacheConfig |
.cache_type(CacheType.X) |
PERSISTENT_FILE |
Cache backend |
CacheConfig |
.maximum_size(int) |
500 |
Max cached entries |
CacheConfig |
.expire_after_write(timedelta) |
24h |
TTL |
PerformanceConfig |
.execution_strategy(X) |
SMART_SEQUENTIAL |
Healing strategy |
PerformanceConfig |
.quick_check_timeout(timedelta) |
500ms |
Original selector timeout |
PerformanceConfig |
.element_timeout(timedelta) |
10s |
Full lookup timeout |
API Reference
# Find single element (sync)
element = locator.find_element(selector, description)
# Find single element (async)
element = await locator.find_element_async(selector, description)
# Playwright only — pass native Locator
element = await locator.find_async(page.get_by_role("button"), description)
# Find multiple elements
elements = locator.find_elements(selector, description)
# Presence check — no exception if missing
if locator.is_element_present(selector, description):
...
# Cache management
locator.clear_cache()
locator.remove_cached_selector(selector, description)
locator.get_cache_size()
# Metrics
metrics = locator.get_metrics()
cache = locator.get_cache_metrics()
health = locator.get_health_status()
# Shutdown — flush cache, close connections, generate reports
locator.shutdown()
await locator.shutdown() # async version
Best Practices
Use descriptive element names — the description is sent to the AI when healing is needed:
# Vague — AI has little context
locator.find_element("#btn-1", "button")
# Specific — AI understands exactly what to find
locator.find_element("#btn-1", "Submit payment button on checkout page")
Match strategy to environment:
SMART_SEQUENTIAL # production and CI — cost-effective
DOM_ONLY # local dev when you want fast runs
VISUAL_FIRST # highly visual UIs where DOM structure is unreliable
Use absolute paths for reports so they always land in the project root regardless of where pytest is run from:
.output_directory(str(Path(__file__).parent.parent / "autoheal-reports"))
Scope fixtures to function — each test should get its own driver and locator instance to avoid cache collisions between tests.
Troubleshooting
No AI provider configured
Set exactly one API key in your .env. See AI Provider Configuration.
Multiple AI providers configured
Comment out all but one provider block in .env.
Gemini 404
The correct Gemini base URL is https://generativelanguage.googleapis.com/v1 — without a trailing /models.
Gemini 429 rate limited
Switch to AUTOHEAL_EXECUTION_STRATEGY=DOM_ONLY or SMART_SEQUENTIAL to reduce screenshot-based calls.
Visual analysis returns wrong selector
Visual AI infers selectors from screenshots — it cannot read actual HTML attributes. Use SMART_SEQUENTIAL so DOM analysis runs first; visual is the fallback.
Reports landing in wrong folder
Use Path(__file__).parent.parent / "autoheal-reports" instead of "./autoheal-reports". Relative paths depend on where pytest is launched from.
Project Structure
autoheal/
├── autoheal_locator.py # Main AutoHealLocator class
├── __init__.py
├── config/ # AIConfig, CacheConfig, PerformanceConfig, etc.
├── impl/
│ ├── adapter/ # SeleniumWebAutomationAdapter, PlaywrightWebAutomationAdapter
│ ├── ai/providers/ # GeminiProvider, OpenAIProvider, GroqProvider, ...
│ ├── cache/ # FileSelectorCache, RedisCache, CachetoolsCache
│ └── locator/ # DOMElementLocator, VisualElementLocator, HybridLocator
├── models/
│ └── enums.py # AIProvider, ExecutionStrategy, CacheType
├── reporting/ # ReportingAutoHealLocator, HTML/JSON/text reporters
└── utils/ # Locator type detection, helpers
License
MIT License. See LICENSE.
Links
- PyPI: https://pypi.org/project/autoheal-locator/
- GitHub: https://github.com/SanjayPG/autoheal-locator-python
- Java version: https://github.com/SanjayPG/autoheal-locator
- Selenium demo: https://github.com/SanjayPG/autoheal-selenium-python-demo
- Playwright demo: https://github.com/SanjayPG/autoheal-playwright-demo-python
- Free Groq API key: https://console.groq.com
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
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 autoheal_locator-1.0.11.tar.gz.
File metadata
- Download URL: autoheal_locator-1.0.11.tar.gz
- Upload date:
- Size: 125.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74005ca354ed153f72fc726e546221e2ad4cbacf50433320b87047870ff2c4d2
|
|
| MD5 |
0c69b215ab19d96782c8acc1f2131e46
|
|
| BLAKE2b-256 |
6a171aa2a20f3d51043483c7daa4e43bff082ea45caa4282913b41fa6dcc8bbf
|
File details
Details for the file autoheal_locator-1.0.11-py3-none-any.whl.
File metadata
- Download URL: autoheal_locator-1.0.11-py3-none-any.whl
- Upload date:
- Size: 172.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
109fbde0998535ef1a08d2367bcdba3c843f8f981ff39c2ef7c9fc1679f48935
|
|
| MD5 |
54ca35c9b7575a805d4b9c253be67ad6
|
|
| BLAKE2b-256 |
db91db485aca387de1de8c24e1a6dcb28e898929629dca7427c53a231dc73b83
|