Skip to main content

AI-Driven E2E Test Self-Healing Engine

English · 한국어 · 日本語 · 简体中文

CI Python 3.13+ License: MIT Powered by OrcaRouter

Using LLM APIs? This project optionally supports OrcaRouter: one API for 200+ models with zero markup and cost-aware routing. Using this referral link supports the project with a 5% commission at no additional cost to you.

OrcaRouter is an optional LLM provider for this project. See Using OrcaRouter for the configuration.

A self-healing engine that automatically repairs broken Playwright E2E tests with an AI agent built on LangGraph. When a UI change renames or restructures an element and a test's selector breaks, the engine diagnoses the failure, patches the broken selector/wait, verifies the new selector against the live DOM, then re-runs the test until it passes (or a retry cap is hit) and writes the fix back — as a local CLI or a CI GitHub Action that opens a patch PR.

Scope guardrail: the engine only fixes failing locators and wait conditions. It never touches assertions or test logic, and every patch stays human-reviewable.

Two modes: heal and review

Auto-healing patches the test. That is fast, but on its own it can look like papering over a real problem — the source that broke the selector. So the engine offers a second mode:

  • heal (default) — patch the broken selector/wait and re-run until green. Best when the UI change is intentional and the test simply needs to catch up.
  • review — diagnose why the selector broke and post source-level suggestions as inline PR comments (e.g. "this className rename broke #cta; add a stable data-testid or use getByRole"). It never edits the test — it advises the fix at the source and pushes teams toward resilient, accessibility-first selectors.

Same diagnosis engine, two outputs: a patch, or a review. Pick per-project or per-PR.

e2e-healer demo — diagnose, verify against the live DOM, re-run, fixed

How it works

Four layers drive a LangGraph repair loop:

  1. CLI core — the single entry point (e2e-healer); everything, including CI, calls it.
  2. Data Preprocessor — abstracts the raw Playwright log and the git diff into compact, hallucination-resistant context (the failing selector + the DOM attribute that changed).
  3. LangGraph agent — Diagnoser → Patch Generator → Selector Verifier → Test Runner, looping via a conditional Router until the test passes or max_loops is reached.
  4. Selector Verifier — checks each patched selector against the real page DOM so it resolves to exactly one element (Node/Playwright helper). Hallucinated (0 matches) or ambiguous (>1) selectors are reverted and re-patched before a full test run.
  5. Test Runner — runs npx playwright test via subprocess to validate each attempt.
   ┌──────────┐    ┌─────────────────┐    ┌───────────────────┐    ┌─────────────┐
──▶│ Diagnoser│──▶ │ Patch Generator │──▶ │ Selector Verifier │─┬─▶│ Test Runner │──┐
   └──────────┘    └─────────────────┘    └───────────────────┘ │  └─────────────┘  │
        ▲                   ▲  verify fail (0/2+ match) → repatch ┘                   │
        │                   └───────────────────────────────────────────────────────┘│
        │                          fail & loop_count < max                            │
        └───────────────────────────────  Router  ◀───────────────────────────────────┘
                                            │ pass or loop cap
                                            ▼
                                          [End]

The Selector Verifier skips gracefully (loop proceeds unverified) when E2E_HEALER_APP_URL is empty or the page is unreachable (e.g. Node/Playwright not installed) — tooling problems never block a heal.

See docs/design.md for the full design, and docs/shadow-testing.md for the planned Shadow Testing pipeline.

Usage (CI / GitHub Action)

The flagship workflow: run your suite and auto-heal on failure, opening a patch PR for review:

- 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:
      body-path: ${{ steps.heal.outputs.summary-path }}
      branch: e2e-self-heal/${{ github.run_id }}

The action's outcome output is passed | healed | unhealed (heal mode) or reviewed (review mode). For a Playwright suite in a subdirectory, pass working-directory:. A runnable self-demo that heals this repo's own examples/ project lives in ci/github-workflow.example.yml.

To run as a PR review bot instead, pass mode: review and post the findings as inline PR comments — a ready-to-copy workflow lives in ci/github-review-bot.example.yml:

- name: E2E review
  id: review
  uses: Lee-Dongwook/E2E-Self-Heal@v0.4.0
  with:
      mode: review
      test-path: tests/example.spec.ts
      nvidia-api-key: ${{ secrets.NVIDIA_API_KEY }}
      diff-base: ${{ github.event.pull_request.base.sha }}
# then read steps.review.outputs.review-path and post inline comments (see the example)

Demo (verified end-to-end)

The examples/ project is a runnable React + Vite app the engine heals end to end. It ships green — on a clean checkout every spec passes against the real app. Applying a scenario's real git diff breaks it: the id-rename scenario renames the button id submit-btn → submit, so scenarios/id-rename/spec.ts times out on #submit-btn. Running the healer against it (with a live NVIDIA key) produces:

diagnoser_finished
patch_generator_finished        instruction_count=1
selector_verify_started         selector_count=1 url=http://localhost:4173
selector_verify_passed          counts={'#submit': 1}
test_runner_passed              loop_count=0
fixed after 0 loop(s)
- await page.click("#submit-btn");
+ await page.click("#submit");        # assertion on "Thanks!" left untouched

Reproduce it yourself (the demo uses pnpm): see examples/README.md.

In practice

A real run against a landing-page CTA test. A UI refactor renamed the button id #enter-demo-btn → #demo-cta-btn, so demo-cta.spec.ts started failing on locator.click. Pointed at the file, the engine diagnosed the break against the git diff, patched only the broken selector (leaving the toHaveURL assertion untouched), re-ran the suite, and passed on the first attempt — end to end on NVIDIA NIM (integrate.api.nvidia.com, openai/gpt-oss-120b):

Real-world run: diagnose the renamed CTA selector, patch it, re-run, fixed after 0 loops

playwright_run_finished     passed=False                    # original selector times out
diagnoser_started           loop_count=0
diagnoser_finished
patch_generator_finished    instruction_count=1
test_runner_started
playwright_run_finished     passed=True
repair_run_finished         is_success=True loop_count=0
fixed after 0 loop(s)
  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
  })

Install

Requires Python 3.13+ and a Playwright project (Node) in your repo.

Recommended — one-line global install:

pipx install ai-driven-e2e
# or, before PyPI release:
uv tool install git+https://github.com/Lee-Dongwook/E2E-Self-Heal.git

Then in any Playwright project:

cp .env.example .env    # set E2E_HEALER_LLM_API_KEY (or your provider's key)
e2e-healer tests/login.spec.ts

Works with NVIDIA NIM (default), OpenAI, Anthropic (Claude), DeepSeek, OrcaRouter, or a local Ollama model — see Configuration to pick one. For the default, get a free NVIDIA NIM API key at build.nvidia.com (default model openai/gpt-oss-120b).

Development install from a local clone
git clone https://github.com/Lee-Dongwook/E2E-Self-Heal.git
cd E2E-Self-Heal
uv sync --extra dev
uv tool install --force .    # global `e2e-healer` from this checkout

Re-run uv tool install --force . after pulling changes.

Usage (CLI)

# Heal the WHOLE suite — run every test, then repair each failing file (aggregate summary):
uv run e2e-healer

# Heal a single failing test (with no --log, the tool runs it to capture the failure):
uv run e2e-healer tests/example.spec.ts

# Preview only — run the loop but write nothing:
uv run e2e-healer tests/example.spec.ts --dry-run

# Disable local healing-history lookup and storage for this run:
uv run e2e-healer tests/example.spec.ts --no-memory

# Feed a pre-captured log and a PR-scoped diff (the CI path):
uv run e2e-healer tests/example.spec.ts --log playwright.log --diff-base origin/main --json

# Enable live-DOM selector verification against a running app:
uv run e2e-healer tests/example.spec.ts --app-url http://localhost:4173

# Review mode — suggest source-level fixes instead of patching (never edits the test):
uv run e2e-healer review tests/example.spec.ts --log playwright.log --diff-base origin/main --json

# Compare the Diagnoser's full-file and semantic-context prompt-token estimates for shipped examples:
uv run e2e-healer benchmark

Pass --root <dir> to anchor all relative paths (the spec, --diff, test-results/, and the git diff/Playwright subprocess cwd) to a project root — useful when invoking the CLI from a directory other than the project root (e.g. from a Playwright Reporter).

Exit code is 0 when the test is healed, non-zero otherwise. --json prints a machine-readable summary to stdout (human output goes to stderr) so CI can branch on it: a RepairSummary for a successful single-file heal, a RefusalReport when it declines a single-file repair, a SuiteSummary for suite mode, and a ReviewReport in review mode. Every emitted summary is self-describing: it carries a schema_version (currently "2.0") and a kind discriminator ("repair" / "refusal" / "suite" / "review") so the CI wrapper can dispatch without guessing on keys. e2e-healer <path> is shorthand for e2e-healer heal <path>; review is a separate subcommand that emits a ReviewReport (findings anchored to the changed source line) and always exits 0 — the CI wrapper branches on has_findings.

Healing history is enabled by default. Pass --no-memory to fully bypass local history lookup and verified-repair storage for a run; pass --memory explicitly to enable the default behavior.

Benchmark semantic-context token usage

Run uv run e2e-healer benchmark to render a rich table comparing the full-file and semantic-context Diagnoser prompt sizes for each checked-in breakage scenario. It needs neither provider credentials nor a running Playwright app. Counts include the Diagnoser system and user prompt content, estimated with the fixed cl100k_base tokenizer; they are a stable comparison metric, not a provider-billed-token statement. tiktoken may download this tokenizer on first use, then serves it from its local cache. A scenario that cannot yield an enclosing JSX node is labeled whole-file fallback and correctly reports zero savings.

Configuration

All settings use the E2E_HEALER_ prefix (see .env.example). LLM configuration is provider-neutral: pick a backend with E2E_HEALER_LLM_PROVIDER and configure it via the generic LLM_* vars. The legacy NVIDIA_* vars still work — when the provider is nvidia they are folded into the generic ones, so existing setups need no changes.

Provider matrix

Provider Install Required env Structured outputs Notes
nvidia built-in (default) E2E_HEALER_LLM_API_KEY (or legacy NVIDIA_API_KEY) strict json_schema OpenAI-compatible NIM endpoint; default openai/gpt-oss-120b.
openai built-in E2E_HEALER_LLM_API_KEY or OPENAI_API_KEY strict json_schema (native) Set LLM_BASE_URL for Azure / OpenAI-compatible endpoints.
anthropic ai-driven-e2e[anthropic] E2E_HEALER_LLM_API_KEY or ANTHROPIC_API_KEY tool-use No OpenAI response_format; schema enforced via Claude tool-use.
ollama ai-driven-e2e[ollama] none (local) native JSON-schema format Fully offline; smaller models are less reliable at strict JSON.
deepseek built-in E2E_HEALER_LLM_API_KEY or DEEPSEEK_API_KEY Responses API json_schema Stateless Responses API; default deepseek-v4-flash.
orcarouter built-in E2E_HEALER_LLM_API_KEY or ORCAROUTER_API_KEY LangChain default OpenAI-compatible endpoint; default base URL is https://api.orcarouter.ai/v1.

All providers read the generic E2E_HEALER_LLM_MODEL / E2E_HEALER_LLM_MAX_TOKENS / E2E_HEALER_LLM_BASE_URL. On a structured-output parse failure the engine retries (tenacity) and then falls back to the Patch Generator feedback loop, so a flaky JSON response never crashes the run.

Using OpenAI

Set the provider to openai and supply a key — either the generic E2E_HEALER_LLM_API_KEY or an existing standard OPENAI_API_KEY (used as a fallback). Patch/review use OpenAI's native strict Structured Outputs. Point E2E_HEALER_LLM_BASE_URL at an Azure or OpenAI-compatible endpoint to override the default.

E2E_HEALER_LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...            # or E2E_HEALER_LLM_API_KEY=sk-...
E2E_HEALER_LLM_MODEL=gpt-4o-mini

Using OrcaRouter

OrcaRouter is an optional OpenAI-compatible provider. Select it explicitly; it never changes the default NVIDIA configuration. Supply either the generic E2E_HEALER_LLM_API_KEY or ORCAROUTER_API_KEY. ORCAROUTER_BASE_URL defaults to https://api.orcarouter.ai/v1, and can be overridden without changing application code.

E2E_HEALER_LLM_PROVIDER=orcarouter
ORCAROUTER_API_KEY=your_orcarouter_api_key_here
E2E_HEALER_LLM_MODEL=your_structured_output_model
# ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1

Using Anthropic (Claude)

Anthropic is an optional dependency — install the extra so non-Anthropic users don't pull it in:

pip install "ai-driven-e2e[anthropic]"   # or: uv sync --extra anthropic

Then set the provider and a key — either E2E_HEALER_LLM_API_KEY or an existing standard ANTHROPIC_API_KEY (fallback). Claude has no OpenAI-style response_format, so PatchOutput/ReviewOutput are enforced via tool-use.

E2E_HEALER_LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...     # or E2E_HEALER_LLM_API_KEY=sk-ant-...
E2E_HEALER_LLM_MODEL=claude-sonnet-4-6

Using Ollama (local, offline)

Run fully offline with no API key. Ollama is an optional dependency:

pip install "ai-driven-e2e[ollama]"      # or: uv sync --extra ollama

Start Ollama and pull a model, then point the provider at it. Structured outputs use Ollama's native JSON-schema format, so pick a model that handles structured output well — recommended: llama3.1, qwen2.5-coder, or mistral-nemo. Smaller/older models are less reliable at strict JSON; on a parse failure the engine retries and falls back to the Patch Generator feedback loop.

E2E_HEALER_LLM_PROVIDER=ollama
E2E_HEALER_LLM_MODEL=llama3.1
# E2E_HEALER_LLM_BASE_URL=http://localhost:11434   # default; override for a remote host

Using DeepSeek (Responses API)

DeepSeek is exposed through its Responses API (client.responses.create() at https://api.deepseek.com) rather than the LangChain chat path, so the provider is a small built-in adapter that speaks that API directly. Supply a key — either E2E_HEALER_LLM_API_KEY or an existing standard DEEPSEEK_API_KEY (fallback). The default model is deepseek-v4-flash (the Responses API currently supports only this model; deepseek-v4-pro is pending). Setting E2E_HEALER_LLM_MODEL to any other model is rejected unless E2E_HEALER_LLM_BASE_URL points at a custom OpenAI-compatible endpoint that may serve it. Structured output is requested with the Responses API text.format json_schema parameter, and the returned JSON is validated against PatchOutput/ReviewOutput, so schema enforcement holds without LangChain.

E2E_HEALER_LLM_PROVIDER=deepseek
DEEPSEEK_API_KEY=sk-...          # or E2E_HEALER_LLM_API_KEY=sk-...
# E2E_HEALER_LLM_MODEL=deepseek-v4-flash   # default; set only to override

Try it live against the API (set DEEPSEEK_API_KEY first):

uv run e2e-healer --help >/dev/null  # import smoke check
E2E_HEALER_LLM_PROVIDER=deepseek uv run python - <<'PY'
from app.llm import generate_diagnosis
print(generate_diagnosis("You are a terse assistant.", "Say OK"))
PY
Variable Default Purpose
E2E_HEALER_LLM_PROVIDER nvidia LLM backend: nvidia, openai, anthropic, ollama, deepseek, orcarouter
E2E_HEALER_LLM_API_KEY — API key for the selected provider
E2E_HEALER_LLM_BASE_URL — OpenAI-compatible endpoint (empty = SDK default)
E2E_HEALER_LLM_MODEL — Structured-Outputs-capable model
E2E_HEALER_LLM_MAX_TOKENS 4096 Completion token cap (headroom for reasoning)
E2E_HEALER_NVIDIA_API_KEY — NVIDIA NIM API key (legacy; maps to LLM_API_KEY)
E2E_HEALER_NVIDIA_BASE_URL https://integrate.api.nvidia.com/v1 OpenAI-compatible endpoint (legacy)
E2E_HEALER_NVIDIA_MODEL openai/gpt-oss-120b Structured-Outputs-capable model (legacy)
E2E_HEALER_NVIDIA_MAX_TOKENS 4096 Completion token cap (legacy)
ORCAROUTER_API_KEY — OrcaRouter API key (used when provider is orcarouter)
ORCAROUTER_BASE_URL https://api.orcarouter.ai/v1 OrcaRouter OpenAI-compatible endpoint
E2E_HEALER_MAX_LOOPS 3 Repair loop cap
E2E_HEALER_PLAYWRIGHT_CMD npx playwright test Playwright invocation
E2E_HEALER_VERIFY_SELECTORS true Toggle live-DOM selector verification
E2E_HEALER_APP_URL — URL the Selector Verifier loads (empty = skip)
E2E_HEALER_NODE_CMD node Node executable for the verifier
E2E_HEALER_SANDBOX_MODE relaxed strict, relaxed, or off
E2E_HEALER_WORKSPACE_ROOT . Root for strict path checks
E2E_HEALER_WRITE_GLOBS *.spec.js,... Writable test-file globs
E2E_HEALER_DENY_GLOBS .env,.git/**,... Paths blocked by the sandbox
E2E_HEALER_ALLOW_TEMP_HELPER true Permit selector verifier helper file

The --app-url CLI flag overrides E2E_HEALER_APP_URL. To actually run selector verification locally, the Playwright project needs browsers installed (npm install && npx playwright install).

Development

make install    # uv sync --extra dev
make check      # ruff lint + format check + pyright
make coverage   # pytest with a 90% coverage floor
make test       # pytest

See CONTRIBUTING.md.

Documentation site

The docs-site/ directory contains our Docusaurus documentation site — this is where frontend contributors can help.

To run it locally:

cd docs-site
npm install
npm run start      # dev server with hot reload
npm run build       # production build

Picking up a docs-site component issue? Read docs-site/DESIGN.md first — it's the locked design spec (tokens, layout, dark-mode rules) all components must follow.

Contributing

Contributions of every size are welcome — bug reports, docs, tests, or code. Start with CONTRIBUTING.md, then browse good first issues and help wanted.

Limitations

  • Fixes selectors and waits only — never assertions or control flow.
  • The Selector Verifier checks the entry-page state at APP_URL in v1. Elements that only appear after clicks/navigation aren't verified here; the Test Runner remains the final arbiter (failure-time snapshot capture is planned).
  • Healing quality depends on the LLM and the clarity of the git diff.

License

MIT

Metadata

Release files for ai-driven-e2e 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ai-driven-e2e 0.5.0
File Size Uploaded
ai_driven_e2e-0.5.0.tar.gz 1.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for ai-driven-e2e 0.5.0
File Interpreter ABI Platform
ai_driven_e2e-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.6 MB

Release files / ai_driven_e2e-0.5.0.tar.gz

Download URL ai_driven_e2e-0.5.0.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
2d22ca86d7c38727619ce1228c5f90fcb237382369d739bb709369df2ee38216
BLAKE2b-256 checksum
How to use checksums
73a285d523b383918f3776fd49afc05609584f8f8a2bff10dbe177ddd83f7dae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / ai_driven_e2e-0.5.0-py3-none-any.whl

Download URL ai_driven_e2e-0.5.0-py3-none-any.whl
Size 129.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
df016be784fec91200aeaa5e76c5122c12e50e426f3a107b3775b67df76974f8
BLAKE2b-256 checksum
How to use checksums
ea3cc643120527563b19ae48671fd98643d4beeeb0e8dc2e0ad31100185f6d10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page