Skip to main content

Quickstart — GitHub Action

Add self-healing to your PRs in ~5 minutes. This is the recommended way to run e2e-healer: it runs in CI, in the cloudyou don't install or run anything locally. When a PR breaks a selector, the Action heals the test and opens a patch PR for you to review and merge.

Prerequisites

  • A repo with a Playwright suite (npx playwright test works).
  • An LLM API key stored as a GitHub Actions secret. The default provider is NVIDIA NIM — a free key works. (Other providers: Configuration.)

Add the workflow

Create .github/workflows/e2e-self-heal.yml. On PRs it runs your suite; if it fails, the engine heals the target test and opens a patch PR:

.github/workflows/e2e-self-heal.yml
name: e2e-self-heal
on:
pull_request:

permissions:
contents: write
pull-requests: write

jobs:
self-heal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history so diff-base can resolve the PR base

- name: E2E self-heal
id: heal
uses: Lee-Dongwook/E2E-Self-Heal@v0.4.0
with:
test-path: tests/example.spec.ts
nvidia-api-key: ${{ secrets.NVIDIA_API_KEY }}
diff-base: ${{ github.event.pull_request.base.sha }}
app-url: http://localhost:4173 # optional: enables live selector verification

- name: Open patch PR
if: steps.heal.outputs.outcome == 'healed'
uses: peter-evans/create-pull-request@v6
with:
commit-message: "fix(e2e): auto-heal broken selector"
title: "Auto-healed E2E test"
body-path: ${{ steps.heal.outputs.summary-path }}
branch: e2e-self-heal/${{ github.run_id }}

- name: Fail if unhealed
if: steps.heal.outputs.outcome == 'unhealed'
run: exit 1

That's the whole setup. A runnable self-demo that heals this repo's own examples/ project lives in ci/github-workflow.example.yml.

Inputs that matter

InputRequiredWhat it's for
test-pathThe Playwright spec to heal when the suite fails (e.g. tests/login.spec.ts).
nvidia-api-keyYour LLM API key, from a GitHub secret. Never hardcode it.
diff-baseBase git ref (usually the PR base SHA). Scopes the git diff the engine reasons over.
app-urlURL of your running app. Enables live-DOM selector verification — patched selectors are checked against the real page before a full test run.
working-directorySet this if your Playwright project lives in a subdirectory (monorepos).

Full list: GitHub Action reference.

What comes out: the outcome output

The Action reports what happened via steps.heal.outputs.outcome, so you can branch in YAML:

outcomeMeaningTypical follow-up
passedSuite was green — nothing to do.
healedA selector broke and was fixed.Open a patch PR (as above).
unhealedBroke and couldn't be fixed within the loop cap.Fail the job so a human looks.

What the patch PR looks like

The healer changes only the broken selector — the assertion is left exactly as written:

test('guest enters the demo workspace from the landing CTA', async ({ page }) => {
await page.goto('/')
- await page.click('#enter-demo-btn')
+ await page.click('#demo-cta-btn')
await expect(page).toHaveURL(/\/w\//) // assertion left untouched
})

The PR body is generated from the machine-readable RepairSummary (steps.heal.outputs.summary-path), so reviewers see exactly what changed and why.

:::warning Scope guardrail Even in CI, the engine only patches locators and wait conditions — never expect(...) or control flow. The patch always lands as a reviewable PR; nothing is merged automatically. :::

Next steps