Get started

From npm install to a test that heals its own broken selectors. You need Playwright 1.62 or newer, and either an API key for a cloud model or Ollama running on your machine.

Install

Add healwright to the project that holds your Playwright tests, with the SDK for your provider. OpenAI is the default.

Using another provider? Install its SDK instead: @anthropic-ai/sdk, @google/genai, @typesafe-ai/sdk or ollama. If it's missing, the first heal tells you the exact command to run.

Pick a provider

Healing stays off until SELF_HEAL=1, so your suite runs exactly as before until you turn it on. healwright reads environment variables: set them in CI, or load a .env file with import 'dotenv/config' at the top of playwright.config.ts.

SELF_HEAL=1
AI_PROVIDER=openai
AI_API_KEY=sk-…
# AI_MODEL=…   optional, overrides the default model

Behind a gateway? AI_BASE_URL points any built-in provider at Azure, OpenRouter, vLLM or LiteLLM. For a model healwright has no client for, pass your own with the aiProvider option.

Add the fixture

Wrap the page fixture once. Every test that imports test from this file gets page.heal; everything else on page works as usual.

fixtures.ts
import { test as base } from '@playwright/test';
import { createHealingFixture, HealPage } from 'healwright';

export const test = base.extend<{ page: HealPage }>(createHealingFixture());
export { expect } from '@playwright/test';

Write a test

Each page.heal action takes a selector and a description. The selector runs first; the description is what the model looks for when the selector stops matching. Leave the selector empty and the description is enough.

checkout.spec.ts
import { test, expect } from './fixtures';

test('customer can check out', async ({ page }) => {
  await page.goto('https://shop.example/checkout');

  // No selector: the description is enough
  await page.heal.fill('', 'Email field', 'ada@example.com');

  // The selector runs first; the description takes over if it breaks
  await page.heal.click('#buy-now', 'Place order button');

  await expect(page.getByText('Order confirmed')).toBeVisible();
});

Describe elements the way you'd point them out to a colleague: "Place order button", not "button". The model has only your words and the page to go on.

Run it

While your selectors match, nothing changes and tests run at full speed. When one breaks, the heal shows up in the output. The next run reads the fix from the cache and doesn't call the model.

Terminal
$ SELF_HEAL=1 npx playwright test

  ✦ healwright
  │
  ├─ ⚡︎ CLICK Place order button
  │  ⬡ analyzing 14 elements... (filtered from 63)
  │  ↳ received 412 chars
  │
  ├─ ✓ Place order button
  │  → getByRole("button", { name: "Place order" })
  │  ↑ 1180 input · 96 output · 1276 total tokens
  └──────────────────────────────
  ✓ checkout.spec.ts › customer can check out (2.8s)

Keep it honest

A heal that lands on the wrong element still passes. These three settings decide how far the model is trusted and keep every heal visible. You can try them on the home page.

minConfidence
Fail the action instead of guessing when the model's confidence is below this score (0 to 1).
mode: 'warn'
Report what would have healed and keep the original failure. Use it in CI to see how far your selectors have drifted.
getHealSummary()
Count every heal in the run, so global teardown can fail the build and list the selectors to fix.
fixtures.ts
export const test = base.extend<{ page: HealPage }>(
  createHealingFixture({
    minConfidence: 0.8,
    mode: process.env.CI ? 'warn' : 'heal',
  })
);
global-teardown.ts
// playwright.config.ts: globalTeardown: './global-teardown.ts'
import { getHealSummary } from 'healwright';

export default function () {
  const { healed, entries } = getHealSummary();
  if (healed === 0) return;

  console.error(`${healed} locator(s) needed healing. Update these selectors:`);
  for (const e of entries.filter((e) => e.outcome === 'healed')) {
    console.error(`  ${e.action} "${e.contextName}"`);
  }
  process.exitCode = 1;
}

Running Playwright in UI or watch mode? Call resetHealSummary() from globalSetup so each run starts at zero.

Cache and logs

healwright keeps three files in .self-heal/.

File What's in it In git?
healed_locators.json Every heal that worked, by action, page path and description. Commit it to share heals with your team and CI.
heal_events.jsonl One line per heal attempt, with the model's reason, confidence and token count. Ignore
heal_summary.jsonl The outcomes getHealSummary() reads at the end of a run. Ignore
.gitignore
.self-heal/heal_events.jsonl
.self-heal/heal_summary.jsonl
.self-heal/*.lock
.self-heal/*.tmp
# Or keep heals local to each machine instead:
# .self-heal/

Reference

Methods on page.heal

Every action takes a selector or Locator (or '' for description only) and a description.

Method What it does
click(target, desc, { force? })Click. force: true clicks controls that only appear on hover.
dblclick(target, desc)Double-click.
fill(target, desc, value)Type into an input or textarea.
selectOption(target, desc, value)Choose an option in a dropdown.
check(target, desc)Check a checkbox or radio.
uncheck(target, desc)Uncheck a checkbox.
hover(target, desc)Hover over an element.
focus(target, desc)Focus an element.
locator(selector, desc)A chainable locator with the same actions: .locator('.save', 'Save button').click().
getLocator(selector, desc)Returns a real Playwright Locator, for expect(), .nth(), .filter() and the rest.

Fixture options

Pass these to createHealingFixture() or withHealing(page, options).

Option Default What it does
enabledSELF_HEAL=1Turns healing on.
mode'heal''heal', 'warn' (report only) or 'off'.
provider'openai'openai, anthropic, google, typesafe or local.
modelProvider defaultOverrides the model.
apiKeyAI_API_KEYThe provider's API key.
baseURLAI_BASE_URLSends requests to another endpoint, such as a gateway.
aiProviderNoneYour own provider, used instead of the built-in clients.
minConfidence0Rejects heals scored below this (0 to 1). 0 accepts any.
quickTimeout1000Milliseconds your own selector gets before healing starts.
timeout5000Milliseconds for the healed locator and the action.
maxCandidates40How many ranked elements are sent to the model.
maxAiTries4How many suggested strategies are checked before giving up.
cacheFile.self-heal/healed_locators.jsonWhere heals are cached.
reportFile.self-heal/heal_events.jsonlWhere heal attempts are logged.
logLevel'info''info', 'error' or 'silent'.

Environment variables

Variable What it does
SELF_HEALSet to 1 to turn healing on.
AI_PROVIDERopenai/gpt, anthropic/claude, google/gemini, typesafe/jev, or local/ollama.
AI_API_KEYThe provider's API key. Not needed for Ollama.
TYPESAFE_API_KEYTypeSafe's key, read before AI_API_KEY when the provider is TypeSafe.
AI_MODELOverrides the provider's default model.
AI_BASE_URLSends requests to another endpoint.
OLLAMA_HOSTYour Ollama server. Defaults to http://127.0.0.1:11434.
HEALWRIGHT_MODEheal, warn or off.
HEALWRIGHT_MIN_CONFIDENCESame as minConfidence.
HEALWRIGHT_LOGinfo, error or silent.
HEALWRIGHT_SUMMARY_FILEMoves the summary file from .self-heal/heal_summary.jsonl.