A self-healing locator system for Playwright that automatically detects broken selectors at runtime, finds alternative locators using CSS and text strategies, and keeps your tests running even when the UI changes. Includes a Healenium-style scoring engine, a lightweight ML model for re-ranking candidates, a self-hosted web dashboard with multi-project support, and full CLI tooling.
- How It Works
- Folder Structure
- Requirements
- Installation
- Quick Start
- Integrating With Your Project
- CLI Reference
- Dashboard
- Configuration
- Healing Trigger Rules
- Auto-Apply Safety Gates
- Scoring Engine and ML Model
- Multi-Project Support
- History and Reports
- Environment Variables
The healing engine operates per-action, not per-test. Every Playwright action (click, fill, hover, etc.) is wrapped in a try-catch. When a locator fails, the system follows this flow:
hp.click('#broken-selector')
│
▼
Playwright throws error
│
▼
Failure Classifier
├── Non-healable? (assertion, visibility, API error) → rethrow immediately
└── Healable? (locator not found, timeout, strict mode) → continue ▼
│
▼
Capture DOM Snapshot (full page HTML, URL, title)
│
▼
Generate Candidates
├── History Store → past successful heals for this locator
├── CSS Strategy → IDs, data-testid, roles, aria-labels, names, placeholders
└── Text Strategy → text=, :has-text(), role-based, label-based selectors
│
▼
Rank Candidates (history success count → similarity score → confidence)
│
▼
Verify Top 10 Candidates Against Live Page
├── Does locator.count() === 1? (unique match)
└── Retry after 200ms delay? (stability check)
│
▼
┌─ Candidate verified ──→ Execute action with healed locator → Record in history
└─ No candidate works ──→ Throw original error with healing context
The system does not compare against a stored snapshot of the old element. Instead, it searches the current live DOM for alternative ways to locate elements, ranks them by similarity to the original selector, and verifies each against the real page.
auto-heal-locators/
├── package.json # Project config, bin entry, scripts
├── tsconfig.json # TypeScript config (ES2020, CommonJS, strict)
├── README.md # This file
│
├── src/ # Source code (TypeScript)
│ ├── engine/ # Core healing logic
│ │ ├── failureClassifier.ts # Module 1: Classifies errors as healable/non-healable
│ │ ├── healingEngine.ts # Module 5: Orchestrates candidate generation, ranking, verification
│ │ └── locatorStrategies.ts # Module 4: CSS and Text locator generation strategies
│ │
│ ├── context/ # Failure context data structures
│ │ └── failureContext.ts # Module 2: DomSnapshot, SourceLocation, HealingResult types
│ │
│ ├── adapter/ # Playwright integration
│ │ └── playwrightAdapter.ts # Module 3: HealingPage wrapper, per-action healing for 9 actions
│ │
│ ├── history/ # Healing history persistence
│ │ └── historyStore.ts # Module 6: File-based JSON store, lookup/record success/failure
│ │
│ ├── config/ # Configuration management
│ │ └── defaultConfig.ts # Module 7a: Central config with env var overrides
│ │
│ ├── cli/ # Command-line interface
│ │ └── index.ts # Module 7b: run, report, history, dashboard, register commands
│ │
│ ├── report/ # Report generation
│ │ ├── jsonReporter.ts # Module 8: JSON reports with full healing details
│ │ └── htmlReporter.ts # Module 9: Styled HTML reports with summary cards
│ │
│ ├── patch/ # Source code patching
│ │ ├── diffGenerator.ts # Module 10a: Unified diff file generation
│ │ └── patchApplier.ts # Module 10b: 5-gate safety system for auto-patching test files
│ │
│ ├── scoring/ # Healenium-style scoring engine
│ │ ├── nodeModel.ts # DOM node model (tag, id, classes, text, attributes)
│ │ ├── lcsDistance.ts # LCS path distance, heuristic node distance, Jaccard similarity
│ │ └── scoringEngine.ts # 6-level detail cascade selector generation, combined scoring
│ │
│ ├── ml/ # Machine learning components
│ │ ├── featureExtractor.ts # 16-feature extraction from candidates
│ │ └── mlModel.ts # Logistic regression with online learning, 60/40 blend re-ranking
│ │
│ ├── registry/ # Multi-project management
│ │ └── projectRegistry.ts # Global project registry at ~/.auto-heal/projects.json
│ │
│ └── dashboard/ # Self-hosted web dashboard
│ └── server/
│ └── dashboardServer.ts # HTTP server with embedded SPA, multi-project API routes
│
├── dist/ # Compiled JavaScript output (generated by tsc)
│
├── types/ # Type stubs for compilation
│ ├── node/index.d.ts # Node.js type stubs (fs, path, http, os, child_process)
│ └── playwright/index.d.ts # Playwright type stubs (Page, Locator, Browser, ElementHandle)
│
└── examples/
└── playwright/
└── sample.spec.ts # Example test file
- Node.js >= 16.x
- Playwright >= 1.30.0 (
@playwright/test) - TypeScript >= 5.x (dev dependency, included)
- Operating System: macOS, Linux, or Windows
# Clone or copy the project
cd auto-heal-locators
# Install dependencies
npm install
# Build the TypeScript source
npm run build
# (Optional) Link globally for the auto-heal CLI command
npm linkAfter npm link, you can use auto-heal from any directory:
auto-heal dashboard
auto-heal register
auto-heal projectsFrom your Playwright test project:
npm install ../auto-heal-locators # or the path to auto-heal-locatorsCreate core/healing/healingSetup.js in your project:
const path = require('path');
const { HealingEngine } = require('auto-heal-locators/dist/engine/healingEngine');
const { HistoryStore } = require('auto-heal-locators/dist/history/historyStore');
const { HealingPage } = require('auto-heal-locators/dist/adapter/playwrightAdapter');
const { loadConfig } = require('auto-heal-locators/dist/config/defaultConfig');
const { autoRegister } = require('auto-heal-locators/dist/registry/projectRegistry');
let _engine = null, _config = null, _historyStore = null;
function initHealing() {
if (_engine) return { config: _config, engine: _engine, historyStore: _historyStore };
const projectRoot = path.resolve(__dirname, '..', '..');
_config = loadConfig({
historyFilePath: path.join(projectRoot, '.auto-heal', 'history.json'),
reportDir: path.join(projectRoot, '.auto-heal', 'reports'),
verbose: true,
});
_historyStore = new HistoryStore(_config.historyFilePath);
_engine = new HealingEngine(_historyStore);
// Auto-register in global project registry
try { autoRegister(projectRoot); } catch {}
console.log('[auto-heal] Healing engine initialized');
return { config: _config, engine: _engine, historyStore: _historyStore };
}
function createHealingPage(page, options = {}) {
const { engine } = initHealing();
return new HealingPage(page, engine, { verbose: true, ...options });
}
module.exports = { initHealing, createHealingPage };Create core/healing/healingFixture.js:
const base = require('@playwright/test');
const { createHealingPage } = require('./healingSetup');
const test = base.test.extend({
hp: async ({ page }, use) => {
const healingPage = createHealingPage(page);
await use(healingPage);
},
});
module.exports = { test, expect: base.expect };const { test, expect } = require('./core/healing/healingFixture');
test('my test with healing', async ({ page, hp }) => {
await page.goto('https://example.com');
// Use hp instead of page for actions you want healed
await hp.click('#submit-button'); // If #submit-button breaks, healing kicks in
await hp.fill('#email-input', 'test@example.com');
await hp.hover('.nav-menu-item');
// Use page directly for assertions (these should NOT be healed)
await expect(page.locator('.success-message')).toBeVisible();
});# Run the tests
npx playwright test my-test.spec.js
# Launch the dashboard
npm run heal:dashboard
# or
auto-heal dashboardAfter building, use via node dist/cli/index.js <command> or auto-heal <command> (if npm linked).
| Command | Description |
|---|---|
dashboard |
Start the web dashboard at http://localhost:4040 |
dashboard --port 5050 |
Start on a custom port |
dashboard --project <id> |
Open with a specific project selected |
register |
Register the current directory as a project |
register --name "My App" |
Register with a custom display name |
unregister |
Remove the current project from the registry |
projects |
List all registered projects with entry counts |
run <testFile> |
Run a Playwright test with healing env vars set |
report |
Generate JSON + HTML reports from history |
history |
Display healing history in terminal |
clear-history |
Delete all healing history |
help |
Show usage information |
The dashboard is a self-hosted web application with no external dependencies (embedded SPA using Node's built-in http module).
Features:
- Overview page: Total events, healed count, failed count, success rate, ML model status, timeline chart, strategy breakdown donut chart, top healed locators table
- Healing History page: Full table of all healing events with original locator, healed locator, strategy used, success/failure counts, timestamps
- ML Model page: Model status (active/training), training example count, learning rate, feature importance bar chart with positive/negative weights
- Reports page: List of generated JSON reports with view links
- Project Switcher: Dropdown in the sidebar to switch between registered projects (see Multi-Project Support below)
Launch:
# From your test project (using convenience script)
npm run heal:dashboard
# From anywhere (if auto-heal is npm linked)
auto-heal dashboard
# From the auto-heal-locators directory
npm run dashboardAll settings have sensible defaults and can be overridden via environment variables, CLI flags, or the loadConfig() function.
| Setting | Default | Env Variable | CLI Flag |
|---|---|---|---|
| Healing enabled | true |
AUTO_HEAL_ENABLED=false |
— |
| Auto-apply patches | false |
AUTO_HEAL_APPLY=true |
--auto-apply |
| Verbose logging | false |
AUTO_HEAL_VERBOSE=true |
--verbose |
| Locator timeout | 5000ms |
AUTO_HEAL_TIMEOUT=5000 |
--timeout |
| Max candidates | 10 |
AUTO_HEAL_MAX_CANDIDATES=10 |
— |
| History file path | {cwd}/.auto-heal/history.json |
AUTO_HEAL_HISTORY_PATH |
--history-file |
| Report directory | {cwd}/.auto-heal/reports |
AUTO_HEAL_REPORT_DIR |
--report-dir |
Not every test failure should be healed. The failure classifier enforces strict rules:
Healable (healing is attempted):
- "No element found for selector" — locator doesn't match anything
- "Timeout waiting for selector" — element never appeared
- "Strict mode violation" — locator matched multiple elements
Non-healable (error rethrown immediately):
- Assertion failures (
expect(...).toBe(...)) - Visibility/interaction failures ("element not visible", "not interactable")
- API/network errors
- Navigation failures
- Authentication errors
- JavaScript evaluation errors
Non-healable patterns are checked first to prevent false positives. The system will never try to "heal" a legitimate test failure.
When AUTO_HEAL_APPLY=true, the patch applier can rewrite your test file to replace the broken locator with the healed one. This goes through 5 mandatory safety gates:
- The healed locator must resolve successfully on the live page
- The healed locator must pass a retry verification (200ms delay, re-check)
- A
.bakbackup of the original file must be created - A
.difffile must be generated showing the exact change - The
AUTO_HEAL_APPLYenvironment variable must be explicitly set totrue
If any gate fails, patching is skipped and the test continues with runtime healing only.
The scoring engine uses a weighted heuristic to compare DOM nodes:
| Attribute | Weight |
|---|---|
| Tag name | 25% |
| ID | 25% |
| CSS classes | 15% |
| Inner text | 10% |
| Other attributes | 15% |
| DOM index | 10% |
It also computes LCS (Longest Common Subsequence) path distance between the original element's DOM path and each candidate's path. The combined score is pathScore * 0.6 + nodeScore * 0.4.
The scoring engine generates selectors using a 6-level detail cascade:
TAG#idTAG.classTAG[data-testid]TAG.class:nth-of-type(n)TAG[attr1][attr2][attr3]- Full XPath (last resort)
A logistic regression model is trained online on each healing outcome. It extracts 16 features from each candidate:
pathScore, nodeScore, combinedScore, sameTag, hasId, hasTestId, hasRole, classOverlap, attrOverlap, textSimilarity, depthDifference, indexDifference, historySuccessRate, historyUsageCount, detailLevel, isXPath
Until 20 examples are collected, the system uses rule-based scoring only. After that, the ML model re-ranks candidates with a 60/40 blend (ML prediction / original score). Model weights persist to .auto-heal/model.json.
The system supports multiple projects through a global registry at ~/.auto-heal/projects.json.
Registering projects:
# From inside your project directory
auto-heal register --name "My E2E Tests"
# Projects also auto-register on first test run (via healingSetup.js)Listing projects:
auto-heal projectsDashboard with project switcher:
When multiple projects are registered, the dashboard shows a dropdown in the sidebar to switch between them. Each project's healing history, stats, timeline, and reports are completely segregated.
# Launch — automatically detects all registered projects
auto-heal dashboard
# Open with a specific project pre-selected
auto-heal dashboard --project my-e2e-testsRemoving a project:
cd /path/to/project
auto-heal unregisterLocated at {project}/.auto-heal/history.json. Contains all healing events:
{
"version": 1,
"entries": [
{
"originalLocator": "#submit-btn",
"healedLocator": "button[name=\"submit\"]",
"domFingerprint": "a3f8c2...",
"strategy": "css-name",
"successCount": 3,
"failureCount": 0,
"lastUsed": "2026-02-06T10:30:00.000Z"
}
]
}Generated in {project}/.auto-heal/reports/ in both JSON and HTML formats. Each report includes the test file, line number, action type, original and healed locators, strategy used, whether the patch was applied, and timestamp.
# Generate reports from history
auto-heal report
# Reports are also auto-generated after test runs via the CLI
auto-heal run tests/my-test.spec.js| Variable | Values | Description |
|---|---|---|
AUTO_HEAL_ENABLED |
true / false |
Enable or disable healing entirely |
AUTO_HEAL_APPLY |
true / false |
Enable auto-patching of test source files |
AUTO_HEAL_VERBOSE |
true / false |
Enable detailed logging of healing attempts |
AUTO_HEAL_TIMEOUT |
milliseconds | Locator resolution timeout |
AUTO_HEAL_MAX_CANDIDATES |
number | Max candidates to try per failure |
AUTO_HEAL_HISTORY_PATH |
file path | Override the history file location |
AUTO_HEAL_REPORT_DIR |
directory path | Override the report output directory |
The HealingPage wrapper supports per-action healing for these Playwright methods:
| Method | Signature |
|---|---|
click |
hp.click(selector, options?) |
fill |
hp.fill(selector, value, options?) |
type |
hp.type(selector, text, options?) |
hover |
hp.hover(selector, options?) |
check |
hp.check(selector, options?) |
uncheck |
hp.uncheck(selector, options?) |
focus |
hp.focus(selector, options?) |
press |
hp.press(selector, key, options?) |
selectOption |
hp.selectOption(selector, values, options?) |
For non-healed operations (navigation, assertions, waiting), use the page object directly or access it via hp.rawPage.
MIT