Skip to main content

🐾 9lives

Your tests have nine lives. Self-healing QA for the coding-agent era, by QualityMax.

9lives healing a broken Playwright selector — offline, in seconds

A coding agent renamed a button, the test went red, 9l heal read the live page, fixed the locator, re-ran it green, and showed the diff — no API key. Run the demo yourself →

Your coding agent shipped a change and your Playwright test went red? Don't rewrite it — resurrect it:

9l heal login.spec.ts

9l runs the test, classifies the failure, and heals it in tiers:

  1. Tier 1 — offline, free, instant. Selector drifted? 9lives finds the element again from the failure-time page snapshot (text, testid, id, class, aria-label) and rewrites the locator. No LLM, no network, no account.
  2. Tier 2 — the subscription you already pay for. Structural change? 9lives shells out to your installed coding-agent CLI — Claude Code (claude -p), Codex (codex exec), or OpenCode (opencode run) — so your existing subscription does the thinking. No API key to mint, nothing to configure: if the CLI is logged in, healing works. (Prefer raw API? ANTHROPIC_API_KEY / OPENAI_API_KEY work too.)
  3. Always a diff, never a surprise. Healed code is shown as a unified diff and applied only when you approve (or --yes in CI).

Won't hide your bugs. A failing assertion means the app's behavior changed — not that a selector moved. 9lives refuses to rewrite assertions to force a green (that's how naive auto-healers mask regressions) and flags it as a possible real bug instead. Opt in with NINELIVES_HEAL_ASSERTIONS=1 if you really want it to propose an assertion update.

Install

curl -sL 9lives.run | sh
pip install 9lives          # or: uv tool install 9lives

Requires Node.js ≥ 18 (Playwright itself runs on Node). Check your setup with 9l doctor.

Commands

9l run <spec>         # run a spec locally; screenshots/videos/traces in .9lives/
9l heal <spec>        # run → heal → re-run → diff → apply on confirm
9l heal <spec> --yes  # CI mode: apply automatically, exit code tells the story
9l heal <spec> --run-timeout 900   # long suite? one spec run may take up to 15 min (default 300s)
9l watch [dir]        # heal on save — polls specs, heals whatever changes
9l report             # brittle-selector report from your local heal history
9l mcp                # serve heal_test/run_test as MCP tools for coding agents
9l doctor             # environment check

Works inside an existing Playwright project (uses your package.json) or on a bare .spec.ts file (scaffolds an ephemeral project automatically). All commands accept multiple specs/globs.

One spec run gets 300 seconds by default. Suites that legitimately take longer can raise it with --run-timeout <seconds> on run/heal/watch, or NINELIVES_RUN_TIMEOUT=<seconds> in the environment (the MCP tools take run_timeout too). When the budget is exceeded, 9lives stops with a clear error and changes nothing — a heal that never observed a test result never emits a diff.

Cypress & Selenium too

The heal verb is framework-agnostic. 9l heal auto-detects the framework per spec — .cy.js/.cy.ts (or a package.json depending on cypress) runs through Cypress, .py specs run through Selenium via your own pytest, everything else is Playwright. Force it with --framework cypress|selenium|playwright.

9l heal cypress/e2e/login.cy.js     # runs `npx cypress run` in your project
9l heal tests/test_checkout.py      # runs your pytest + selenium

Same loop everywhere: classify → Tier 1 offline selector repair → Tier 2 via your subscription → re-run → diff → approve. Cypress's Expected to find element: \#x`and Selenium'sNoSuchElementExceptionpayloads both carry the failing selector, and both are correctly treated as *selector drift* — never as assertion failures (Cypress wraps locator misses inAssertionError`; 9lives sees through that so the behavior-vs-drift guard doesn't misfire). 9lives never scaffolds a Cypress project or a Python env — those specs run against your own install.

For coding agents: 9l mcp

Your agent wrote code, a test went red — let it heal the test in-loop instead of waiting for CI. 9l mcp speaks MCP over stdio (zero extra dependencies) and exposes two tools: heal_test (run → heal → verify → return the diff; apply: true writes it in place, otherwise a .healed copy is saved for review) and run_test.

# Claude Code
claude mcp add 9lives -- 9l mcp
# or without installing first:
claude mcp add 9lives -- uvx --from 9lives 9l mcp
// Cursor (.cursor/mcp.json) / Codex — any MCP host with stdio servers
{ "mcpServers": { "9lives": { "command": "9l", "args": ["mcp"] } } }

The behavior-vs-drift guard applies to agents too: a failing assertion comes back as needs-human, with an explicit note that forcing it green would mask a real bug. heal_test only accepts existing test files under the directory 9l mcp was started in (NINELIVES_MCP_UNRESTRICTED=1 lifts this), since healing a spec ultimately executes it.

Healing is half the loop — the spec has to come from somewhere. 9lives' sibling MCP server, qmax-mcp (npx -y @qualitymax/qmax-mcp, MIT), covers the other half: scan a page for defects, inspect it for stability-ranked locators and a testability score, generate a Playwright repro, and run it. Same rules — local, free, no account. A spec born on qmax-mcp's stability-ranked locators is exactly the kind 9lives can keep alive when it drifts.

Heal on save & pre-commit

9l watch makes healing part of the edit-save loop: it polls your specs (no OS-specific watchers, works everywhere) and runs the heal loop on whatever changed. --yes applies automatically.

9l watch tests/ --yes

As a pre-commit hook — heal (or just run) changed specs before they ever reach CI:

repos:
  - repo: https://github.com/Quality-Max/9lives
    rev: v0.2.0
    hooks:
      - id: 9lives-heal   # heals drifted selectors in place; assertion failures still block
      # - id: 9lives-run  # strict variant: run only, never modify

Which selectors are rotting? 9l report

Every heal appends a line to .9lives/history.jsonl next to the spec — locally, never uploaded. 9l report aggregates that history into a brittle-selector report: which selectors keep breaking, which anchor (testid/id/text/class) keeps re-finding them, and what to pin instead.

9l report            # terminal table, worst selectors first
9l report --md brittle-selectors.md

In CI: the GitHub Action

- uses: quality-max/9lives/action@v1
  with:
    specs: "tests/**/*.spec.ts"
    anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
    commit-healed: "true"

Runs on your runner, heals with your key, posts a 🐾 report comment on the PR, and can commit healed specs straight back to the branch. See action/.

BYO everything

  • Your subscription: an installed claude / codex / opencode CLI is auto-detected and used for Tier 2, so the subscription you already use for coding can heal tests too.
  • Or your key: ANTHROPIC_API_KEY / OPENAI_API_KEY. Force a choice with NINELIVES_PROVIDER (claude-code, codex, opencode, anthropic, openai) and NINELIVES_MODEL.
  • Your runner: everything executes on your machine or your CI. Nothing leaves it, nothing phones home.
  • No account. Ever, for anything in this tool.

Security & trust boundary

Tier 1 healing is fully offline and never sends anything anywhere.

Tier 2 builds its prompt from the failing test plus the page snapshot captured at failure. If you heal tests against a site you don't control, that page content becomes model input. In subscription mode it is handed to your local coding-agent CLI (claude / codex / opencode), which can run tools — so a hostile page could attempt prompt injection against your agent. 9lives runs the CLI in an empty scratch directory to limit blast radius, but if you heal against untrusted targets, force plain API mode (no agent tools) with:

NINELIVES_PROVIDER=anthropic   # or openai

Roadmap

  • 9l check — map your git diff to affected user flows, run/generate targeted tests: "did my agent break anything?"
  • 9lives-action — GitHub Action that posts the QA report on PRs, healed commits included
  • 9l gen <url> — crawl a live app and generate a starter test suite

Status

v0.1 prototype — built by QualityMax for local, BYO self-healing Playwright workflows. MIT licensed.

Metadata

Release files for 9lives 0.2.1

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

Source distribution (sdist)

Source distribution for 9lives 0.2.1
File Size Uploaded Provenance Actions
9lives-0.2.1.tar.gz 1.8 MB With provenance Details for 9lives-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for 9lives 0.2.1
File Interpreter ABI Platform Actions
9lives-0.2.1-py3-none-any.whl Python 3 none any Details for 9lives-0.2.1-py3-none-any.whl

Total release size: 1.9 MB

Release files / 9lives-0.2.1.tar.gz

Download URL 9lives-0.2.1.tar.gz
Size 1.8 MB
Tags Source
SHA-256 checksum
How to use checksums
0c5c752ee4390a0dbe3124244909536f97f2653a9be1e6dba280ceb1542a8e4b
BLAKE2b-256 checksum
How to use checksums
e8f7a19fcce86c5ddef572b3419063fd54a0e550857bbc2238451890a928076e
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 10, 2026.

Transparency log

Release files / 9lives-0.2.1-py3-none-any.whl

Download URL 9lives-0.2.1-py3-none-any.whl
Size 51.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
52a94c65765034105735db1d0ed4e16ae2556da12641b04aede7357cc5b3cbc8
BLAKE2b-256 checksum
How to use checksums
dd3148d222787b2e8054d9e5f0d69ed44a6e72783ae993b0afab0a0203524791
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 10, 2026.

Transparency log

Security for 9lives 0.2.1

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for 9lives-0.2.1.tar.gz

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 10, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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