A CLI tool and Chrome extension that parses HTML, identifies target elements, and outputs ranked locators optimized for test automation stability.
Test automation often fails due to brittle selectors. This tool analyzes HTML elements and generates locators based on a resilience hierarchy—prioritizing attributes that developers intentionally add for testing over fragile structural paths.
git clone <repo-url>
cd Resilient-Locator-Extractor
npm install
npm run build# From HTML string
node dist/index.js --html "<button data-testid='submit-btn'>Submit</button>" --selector "button"
# From file
node dist/index.js --file ./snippet.html --selector "button.login"| Option | Description | Required |
|---|---|---|
--html |
Raw HTML string to parse | One of --html or --file |
--file |
Path to HTML file | One of --html or --file |
--selector |
CSS selector to identify target element | Yes |
--help |
Show usage information | No |
The Chrome extension provides a visual UI for extracting locators directly from any webpage.
-
Generate PNG icons (required for Chrome):
# Open in browser open extension/icons/generate-icons.html # Right-click each canvas → Save image as: # - icon16.png # - icon48.png # - icon128.png # Save to extension/icons/
-
Load in Chrome:
- Open
chrome://extensions/ - Enable "Developer mode" (toggle in top-right)
- Click "Load unpacked"
- Select the
extension/folder
- Open
- Navigate to any webpage
- Right-click on any element
- Select "Get Resilient Locators" from context menu
- Click the extension icon to view ranked locators
- Click any locator to copy to clipboard
| Feature | Description |
|---|---|
| Right-click extraction | Instantly analyze any element |
| Visual tier badges | Color-coded by stability |
| One-click copy | Copy any locator to clipboard |
| No setup required | Works on any webpage |
extension/
├── manifest.json # Extension configuration
├── background.js # Service worker (context menu + scoring)
├── content.js # Element extraction from pages
├── popup/ # Results display UI
│ ├── popup.html
│ ├── popup.css
│ └── popup.js
├── icons/ # Extension icons
└── lib/ # Shared scoring logic
├── scorer.js
├── types.js
└── utils.js
✓ Found target element: <button>
Ranked Locators:
1. [Tier 1] [data-testid="submit-btn"]
Explicit test attribute: data-testid
2. [Tier 2] [aria-label="Submit form"]
Accessibility attribute: aria-label
3. [Tier 3] //button[text()='Submit']
Text content: "Submit"
4. [Tier 4] #login-btn
Static ID: login-btn
5. [Tier 5] div > form > button:nth-child(2)
Structural path (least stable)
| Tier | Priority | Attribute | Why It's Stable |
|---|---|---|---|
| 1 | Highest | data-testid, data-cy, data-qa |
Explicitly added for automation—rarely changes |
| 2 | High | aria-label, title, role |
Tied to accessibility compliance |
| 3 | Medium | Text content | Semantic text like "Submit" or "Log In" |
| 4 | Low | id |
Static IDs only—dynamic IDs filtered out |
| 5 | Lowest | Structural path | Fallback: CSS path based on DOM position |
Tier 4 filters out IDs that appear dynamically generated:
- IDs with 6+ consecutive digits (e.g.,
btn-123456789) - Common in React, Angular, Vue apps
node dist/index.js --html "<button data-testid='login-btn' class='btn primary'>Login</button>" --selector "button"Output:
✓ Found target element: <button>
Ranked Locators:
1. [Tier 1] [data-testid=login-btn]
Explicit test attribute: data-testid
2. [Tier 3] //button[text()='Login']
Text content: "Login"
3. [Tier 5] button.btn
Class-based selector
node dist/index.js --html "<input type='email' aria-label='Email address' name='email'>" --selector "input"Output:
✓ Found target element: <input>
Ranked Locators:
1. [Tier 2] [aria-label="Email address"]
Accessibility attribute: aria-label
2. [Tier 5] html:nth-child(1) > body:nth-child(2) > input:nth-child(1)
Structural path (least stable)
node dist/index.js --html "<button id='submit-847293847'>Click</button>" --selector "button"Output:
✓ Found target element: <button>
Ranked Locators:
1. [Tier 3] //button[text()='Click']
Text content: "Click"
2. [Tier 5] html:nth-child(1) > body:nth-child(2) > button:nth-child(1)
Structural path (least stable)
Note: Tier 4 (ID selector) is skipped because submit-847293847 contains 9 consecutive digits.
Create login.html:
<!DOCTYPE html>
<html>
<body>
<form id="login-form">
<input type="email" aria-label="Email address" name="email">
<input type="password" title="Password" name="password">
<button type="submit" data-cy="login-submit" class="btn-primary">Log In</button>
</form>
</body>
</html>Run:
node dist/index.js --file login.html --selector "button"Output:
✓ Found target element: <button>
Ranked Locators:
1. [Tier 1] [data-cy=login-submit]
Explicit test attribute: data-cy
2. [Tier 3] //button[text()='Log In']
Text content: "Log In"
3. [Tier 5] button.btn-primary
Class-based selector
4. [Tier 5] html:nth-child(1) > body:nth-child(2) > form:nth-child(1) > button:nth-child(3)
Structural path (least stable)
Copy the highest-tier locator into your automation code:
// Tier 1 (recommended)
await page.locator('[data-testid=submit-btn]').click();
// Tier 3 (XPath)
await page.locator('xpath=//button[text()="Submit"]').click();// Tier 1
cy.get('[data-testid=submit-btn]').click();
// Tier 2
cy.get('[aria-label="Submit form"]').click();from selenium.webdriver.common.by import By
# Tier 1 (CSS)
driver.find_element(By.CSS_SELECTOR, '[data-testid=submit-btn]').click()
# Tier 3 (XPath)
driver.find_element(By.XPATH, '//button[text()="Submit"]').click()// Tier 1
await page.click('[data-testid=submit-btn]');
// Tier 2
await page.click('[aria-label="Submit form"]');src/
├── index.ts # CLI entry point, argument parsing
├── parser.ts # HTML parsing with jsdom
├── scorer.ts # Heuristic scoring engine (5 tiers)
├── locator-generator.ts # Main extraction logic
├── types.ts # TypeScript interfaces
└── utils.ts # Helpers (ID validation, escaping)
Supported:
- Static HTML parsing
- CSS selector targeting
- Ranked locator output (CSS + XPath)
- Dynamic ID detection
Not Supported (MVP):
- Shadow DOM traversal
- Cross-origin iframes
- Client-side JavaScript rendering
- Dynamic content loaded via AJAX
# Build
npm run build
# Run locally (without build)
npx ts-node src/index.ts --html "<button>Test</button>" --selector "button"
# Run compiled
node dist/index.js --html "<button>Test</button>" --selector "button"- Language: TypeScript
- Runtime: Node.js
- Virtual DOM: jsdom
MIT