{"slug": "9lives-self-healing-test-runner-that-refuses-to-mask-real-bugs", "title": "9lives – Self-healing test runner that refuses to mask real bugs (", "summary": "QualityMax launched 9lives, a self-healing test runner that automatically fixes broken Playwright, Cypress, and Selenium tests by repairing drifted selectors without masking real bugs. The tool uses offline selector repair and optionally leverages existing coding-agent subscriptions like Claude Code, aiming to reduce CI failures and developer toil in the coding-agent era.", "body_md": "**Your tests have nine lives.** Self-healing QA for the coding-agent era, by [QualityMax](https://qualitymax.io).\n\nA coding agent renamed a button, the test went red,\n\n`9l heal`\n\nread the live page, fixed the locator, re-ran it green, and showed the diff — no API key.Run the demo yourself →\n\nYour coding agent shipped a change and your Playwright test went red? Don't rewrite it — resurrect it:\n\n```\n9l heal login.spec.ts\n```\n\n`9l`\n\nruns the test, classifies the failure, and heals it in tiers:\n\n**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.**Tier 2 — the subscription you already pay for.** Structural change? 9lives shells out to your installed coding-agent CLI —**Claude Code (**— 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?`claude -p`\n\n), Codex (`codex exec`\n\n), or OpenCode (`opencode run`\n\n)`ANTHROPIC_API_KEY`\n\n/`OPENAI_API_KEY`\n\nwork too.)**Always a diff, never a surprise.** Healed code is shown as a unified diff and applied only when you approve (or`--yes`\n\nin CI).\n\n**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`\n\nif you really want it to propose an assertion update.\n\n```\ncurl -sL 9lives.run | sh\npip install 9lives          # or: uv tool install 9lives\n```\n\nRequires Node.js ≥ 18 (Playwright itself runs on Node). Check your setup with `9l doctor`\n\n.\n\n```\n9l run <spec>         # run a spec locally; screenshots/videos/traces in .9lives/\n9l heal <spec>        # run → heal → re-run → diff → apply on confirm\n9l heal <spec> --yes  # CI mode: apply automatically, exit code tells the story\n9l watch [dir]        # heal on save — polls specs, heals whatever changes\n9l report             # brittle-selector report from your local heal history\n9l mcp                # serve heal_test/run_test as MCP tools for coding agents\n9l doctor             # environment check\n```\n\nWorks inside an existing Playwright project (uses your `package.json`\n\n) or on a bare `.spec.ts`\n\nfile (scaffolds an ephemeral project automatically). All commands accept multiple specs/globs.\n\nThe heal verb is framework-agnostic. `9l heal`\n\nauto-detects the framework per spec — `.cy.js`\n\n/`.cy.ts`\n\n(or a package.json depending on cypress) runs through **Cypress**, `.py`\n\nspecs run through **Selenium via your own pytest**, everything else is Playwright. Force it with `--framework cypress|selenium|playwright`\n\n.\n\n```\n9l heal cypress/e2e/login.cy.js     # runs `npx cypress run` in your project\n9l heal tests/test_checkout.py      # runs your pytest + selenium\n```\n\nSame loop everywhere: classify → Tier 1 offline selector repair → Tier 2 via your subscription → re-run → diff → approve. Cypress's `Expected to find element: \\`\n\n#x``and Selenium's`\n\nNoSuchElementException`payloads both carry the failing selector, and both are correctly treated as *selector drift* — never as assertion failures (Cypress wraps locator misses in`\n\nAssertionError`; 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.\n\nYour agent wrote code, a test went red — let it heal the test **in-loop** instead of waiting for CI. `9l mcp`\n\nspeaks MCP over stdio (zero extra dependencies) and exposes two tools: `heal_test`\n\n(run → heal → verify → return the diff; `apply: true`\n\nwrites it in place, otherwise a `.healed`\n\ncopy is saved for review) and `run_test`\n\n.\n\n```\n# Claude Code\nclaude mcp add 9lives -- 9l mcp\n# or without installing first:\nclaude mcp add 9lives -- uvx --from 9lives 9l mcp\n// Cursor (.cursor/mcp.json) / Codex — any MCP host with stdio servers\n{ \"mcpServers\": { \"9lives\": { \"command\": \"9l\", \"args\": [\"mcp\"] } } }\n```\n\nThe behavior-vs-drift guard applies to agents too: a failing assertion comes back as `needs-human`\n\n, with an explicit note that forcing it green would mask a real bug. `heal_test`\n\nonly accepts existing test files under the directory `9l mcp`\n\nwas started in (`NINELIVES_MCP_UNRESTRICTED=1`\n\nlifts this), since healing a spec ultimately executes it.\n\n`9l watch`\n\nmakes 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`\n\napplies automatically.\n\n```\n9l watch tests/ --yes\n```\n\nAs a [pre-commit](https://pre-commit.com) hook — heal (or just run) changed specs before they ever reach CI:\n\n```\nrepos:\n  - repo: https://github.com/Quality-Max/9lives\n    rev: v0.2.0\n    hooks:\n      - id: 9lives-heal   # heals drifted selectors in place; assertion failures still block\n      # - id: 9lives-run  # strict variant: run only, never modify\n```\n\nEvery heal appends a line to `.9lives/history.jsonl`\n\nnext to the spec — locally, never uploaded. `9l report`\n\naggregates 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.\n\n```\n9l report            # terminal table, worst selectors first\n9l report --md brittle-selectors.md\n- uses: quality-max/9lives/action@v1\n  with:\n    specs: \"tests/**/*.spec.ts\"\n    anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}\n    commit-healed: \"true\"\n```\n\nRuns 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/](/Quality-Max/9lives/blob/main/action/README.md).\n\n**Your subscription:** an installed`claude`\n\n/`codex`\n\n/`opencode`\n\nCLI 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`\n\n/`OPENAI_API_KEY`\n\n. Force a choice with`NINELIVES_PROVIDER`\n\n(`claude-code`\n\n,`codex`\n\n,`opencode`\n\n,`anthropic`\n\n,`openai`\n\n) and`NINELIVES_MODEL`\n\n.**Your runner:** everything executes on your machine or your CI. Nothing leaves it, nothing phones home.**No account.** Ever, for anything in this tool.\n\nTier 1 healing is fully offline and never sends anything anywhere.\n\nTier 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`\n\n/ `codex`\n\n/ `opencode`\n\n), 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:\n\n```\nNINELIVES_PROVIDER=anthropic   # or openai\n```\n\n`9l check`\n\n— map your git diff to affected user flows, run/generate targeted tests:*\"did my agent break anything?\"*`9lives-action`\n\n— GitHub Action that posts the QA report on PRs, healed commits included`9l gen <url>`\n\n— crawl a live app and generate a starter test suite\n\n**v0.1 prototype** — built by [QualityMax](https://qualitymax.io) for local, BYO self-healing Playwright workflows. MIT licensed.", "url": "https://wpnews.pro/news/9lives-self-healing-test-runner-that-refuses-to-mask-real-bugs", "canonical_source": "https://github.com/Quality-Max/9lives", "published_at": "2026-07-11 07:44:16+00:00", "updated_at": "2026-07-11 08:05:00.355143+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "artificial-intelligence"], "entities": ["QualityMax", "Playwright", "Cypress", "Selenium", "Claude Code", "Codex", "OpenCode"], "alternates": {"html": "https://wpnews.pro/news/9lives-self-healing-test-runner-that-refuses-to-mask-real-bugs", "markdown": "https://wpnews.pro/news/9lives-self-healing-test-runner-that-refuses-to-mask-real-bugs.md", "text": "https://wpnews.pro/news/9lives-self-healing-test-runner-that-refuses-to-mask-real-bugs.txt", "jsonld": "https://wpnews.pro/news/9lives-self-healing-test-runner-that-refuses-to-mask-real-bugs.jsonld"}}