AI-powered self-healing for Playwright tests. Heal automatically fixes broken selectors and adapts your tests to UI changes — no manual maintenance required.
- Clone this repo
- Download the heal CLI tarball from here
- Move it to the root of the repo
- Run
npm i
Peer dependencies: @playwright/test@1.58.1 and playwright@1.58.1.
Heal Autopilot is an AI agent that can read, write, and execute code in your project. It can write and run Playwright tests, fix broken tests, and perform general coding tasks — all from a single natural-language prompt.
# Run with a prompt
npx heal autopilot "Write a login test for https://the-internet.herokuapp.com/login"
# Interactive mode
npx heal autopilot| Option | Description | Default |
|---|---|---|
--dir <path> |
Working directory | current directory |
--max-iterations <n> |
Maximum agent iterations | 500 |
Autopilot reads your project files, generates or edits test code, and executes it — iterating until the task is complete. Press Escape to stop at any time.
Run your Playwright tests with Heal's self-healing layer:
# Run all tests
npx heal test
# Run specific tests
npx heal test "e2e/auth"
npx heal test "**/*login*.spec.ts"
npx heal test "tests/login.spec.ts"| Option | Description | Default |
|---|---|---|
--headless <bool> |
Run in headless mode (true/false) |
- |
--timeout <ms> |
Timeout per test in milliseconds | 30000 |
--concurrency <n> |
Tests to run in parallel | 1 |
--retries <n> |
Retry attempts on failure | 2 |
Use the SDK to integrate Heal directly into your Playwright tests.
All healing calls live directly in the test body (note that, 1 test <> 1 file):
import { expect, test as base } from '@playwright/test';
import { Heal } from '@heal-dev/heal-cli/sdk';
type HealFixture = { heal: Heal };
const test = base.extend<HealFixture>({
heal: async ({}, use) => {
const heal = await Heal.init({ headless: false });
await use(heal);
await heal.close();
},
});
test('login with valid credentials', async ({ heal }) => {
const { agent, page } = await heal.getPage();
await page.goto('https://the-internet.herokuapp.com/login');
await agent.healType('Username', 'tomsmith');
await agent.healType('Password', 'SuperSecretPassword!');
await agent.healClick('Login');
await expect(page.locator('#flash')).toContainText('You logged into a secure area!');
});Extract common flows into block functions for reuse across tests.
Note that import of a block function must be done with import * as blocks from '<path>/blocks'; in the test file.
// blocks.ts
import type { Page } from '@playwright/test';
import type { Heal, HealPageAgent } from '@heal-dev/heal-cli/sdk';
export async function loginWithCredentials(
_heal: Heal,
_page: Page,
agent: HealPageAgent,
credentials: { username: string; password: string }[]
) {
await agent.healType('Username', username);
await agent.healType('Password', password);
await agent.healClick('Login');
}// login.test.ts
import { expect, test as base } from '@playwright/test';
import { Heal } from '@heal-dev/heal-cli/sdk';
import * as blocks from './blocks';
type HealFixture = { heal: Heal };
const test = base.extend<HealFixture>({
heal: async ({}, use) => {
const heal = await Heal.init({ headless: false });
await use(heal);
await heal.close();
},
});
test('login with multiple credentials via block', async ({ heal }) => {
const { agent, page } = await heal.getPage();
await page.goto('https://the-internet.herokuapp.com/login');
await blocks.loginWithCredentials(heal, page, agent, [
{ username: 'wronguser', password: 'wrongpass' },
{ username: 'tomsmith', password: 'SuperSecretPassword!' },
]);
await expect(page.locator('#flash')).toContainText('You logged into a secure area!');
});Creates a new Heal instance with an auto-healing browser context.
| Option | Type | Description |
|---|---|---|
headless |
boolean |
Run browser in headless mode |
extensionPaths |
string[] |
Browser extension paths to load |
locale |
string |
Browser locale |
extraHttpHeaders |
Record<string, string> |
Extra HTTP headers for all requests |
storageStatePath |
string |
Path to storage state JSON for auth/cookies |
getPage()— Returns{ page, agent }with Heal's self-healing proxy.newPage()— Opens a new page in the browser context.getBrowserContext()— Returns the underlying PlaywrightBrowserContext.waitForNewPage(action, timeoutMS?)— Waits for a new page to open as a result of an action.close()— Closes the browser and cleans up resources.
agent.healClick(description)— Click an element described in natural language.agent.healType(description, value)— Type into an element described in natural language.agent.healSelectOption(description, option)— Select an option from a dropdown described in natural language.agent.healHover(description)— Hover over an element described in natural language.agent.healLocator(description)— Returns a PlaywrightLocatorfor an element described in natural language.agent.healAssert(assertion)— Assert a condition on the page described in natural language.agent.healScreenshot()— Takes a screenshot of the current page and returns it as a base64 string.
testDir— root test directoryprojects— array of project configurations withname,testMatch,testDir, anddependencies- Project dependency graph with cycle detection and topological ordering
test()andit()declarationstest.describe()with nestingtest.afterEach()(scope-aware)- Custom fixtures via
base.extend()— function-style, array-style with{ auto: true }, and{ option: true } - Fixture teardown via
use()boundary detection - Fixture dependency resolution and topological sorting
test.use()overrides (scope-aware within describe blocks)- Built-in fixtures auto-provided when needed:
page,context,browser,request
*.spec.ts, *.spec.js, *.test.ts, *.test.js (also .tsx and .jsx)
- Control flow blocks (
if/else,for,while,try/catch,switch) treated as atomic executable units - File-level and describe-block-level variable declarations
- Import statements preserved for execution context
import * as blocks from './blocks'pattern- Automatic inlining of
await blocks.functionName(...)calls - Parameter binding, return value capture, and setup/teardown support
beforeEach/beforeAll/afterAllhooks are not extractedtest.only(),test.skip(),test.slow()not detected- Tag-based filtering not supported
test.step()treated as regular code- Fixture
scopeoption (worker,module) not parsed - Only
import * asblock import pattern recognized (not default imports)