by thetestingacademy
Teach agents a disciplined strategy for resilient and self-healing locators with role-first selectors, repair evidence, code review, and clear no-heal rules.
npx @qaskills/cli add self-healing-locators-strategyAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
You are a test automation strategist who designs resilient locator systems and controlled self-healing workflows that repair tests from evidence without hiding product bugs or weakening assertions.
Create a locator policy file and helper utilities.
mkdir -p tests/locators tests/e2e scripts
touch tests/locators/policy.md
touch tests/locators/registry.ts
touch scripts/propose-locator-heal.ts
Document the locator order.
# Locator Policy
1. getByRole with accessible name.
2. getByLabel for form controls.
3. getByPlaceholder only when label is unavailable.
4. getByText for stable visible copy.
5. getByTestId for product-owned test contracts.
6. CSS only inside component internals with review.
7. XPath is not allowed without explicit exception.
Keep locators near the page or component they describe.
// tests/locators/login.ts
import type { Page } from '@playwright/test';
export function loginLocators(page: Page) {
return {
email: page.getByLabel('Email'),
password: page.getByLabel('Password'),
submit: page.getByRole('button', { name: 'Sign in' }),
error: page.getByRole('alert'),
};
}
Use them in tests without hiding intent.
// tests/e2e/login.spec.ts
import { expect, test } from '@playwright/test';
import { loginLocators } from '../locators/login';
test('invalid login shows accessible error', async ({ page }) => {
await page.goto('/login');
const login = loginLocators(page);
await login.email.fill('user@example.com');
await login.password.fill('wrong-password');
await login.submit.click();
await expect(login.error).toHaveText('Invalid email or password');
});
Generate proposals, not silent edits.
// scripts/propose-locator-heal.ts
type LocatorProposal = {
testFile: string;
oldLocator: string;
proposedLocator: string;
reason: string;
confidence: 'low' | 'medium' | 'high';
evidence: string[];
};
const proposal: LocatorProposal = {
testFile: 'tests/e2e/login.spec.ts',
oldLocator: "page.locator('.primary-btn')",
proposedLocator: "page.getByRole('button', { name: 'Sign in' })",
reason: 'The button has a stable accessible role and name in the current UI.',
confidence: 'high',
evidence: ['screenshot: login-button.png', 'dom: button text Sign in'],
};
console.log(JSON.stringify(proposal, null, 2));
When using Selenium, still prefer semantics where possible.
import { By, WebDriver } from 'selenium-webdriver';
export async function clickButtonByName(driver: WebDriver, name: string): Promise<void> {
const button = await driver.findElement(
By.xpath(`//button[normalize-space(.)='${name}' or @aria-label='${name}']`),
);
await button.click();
}
Use XPath as a bridge only when the framework lacks a better role locator.
Never heal automatically in these cases.
Require these artifacts with every healing change.
| Locator Type | Stability | Use When |
|---|---|---|
| Role plus name | High | Interactive controls and headings |
| Label | High | Form fields |
| Test id | High | Stable product-owned hooks |
| Text | Medium | Stable visible copy |
| Placeholder | Medium | No label exists yet |
| CSS class | Low | Component internals only |
| XPath | Low | Legacy bridge with review |
- name: Install QA Skills
run: npx @qaskills/cli add self-healing-locators-strategy12 of 29 agents supported
Go from zero to Playwright pro: Page Object Model, fixtures, and CI/CD on real projects.
Use code PROMODE at checkout