Self-healing test automation framework for fragile frontends. When a selector breaks, PhoenixQA doesn't crash β it heals.
Frontend tests break constantly β not because the feature is broken, but because the page underneath it changed.
A class was renamed. A data-testid rotated. A wrapper <div> appeared around a button after a refactor. A component moved into a Shadow DOM boundary.
PhoenixQA intercepts those failures, feeds the context to an LLM, and either:
- proposes a fix for human review (Safe Mode β live)
- applies the fix automatically within a confidence/budget policy and continues (Autonomous Mode β live)
Every decision β human-reviewed or autonomous β is logged today, including which provider made the call, how many tokens it cost, and how long it took. Once Healing History (Sprint 7) lands, that log becomes the basis for a self-training loop that improves future healing (Sprint 8) β not yet built, but the logging that feeds it already is.
"Test fails even though the app is fine" has more than one root cause. PhoenixQA classifies failures into four types, but builds them in phases rather than all at once:
The original four-way FailureType split (selector_not_found /
detached_from_dom / not_visible / timeout_waiting) has been fully
replaced internally by FailureCategory (LOCATOR_RESOLUTION /
ACTIONABILITY / dormant REFERENCE) + ActionabilityReason (5
values) β see below for why. Mapped onto the original four labels for
orientation:
| Failure type | Status |
|---|---|
selector_not_found β FailureCategory.LOCATOR_RESOLUTION |
β
Live (Sprint 2-5), SelectorReplacement action, Safe + Autonomous Mode both verified end-to-end |
detached_from_dom β dormant FailureCategory.REFERENCE |
π¬ Investigated (Sprint 6A) β no reproduction found for Locator-based automation across four escalating attempts; deprioritized based on current evidence, not proven impossible β see docs/gaps.md Gap #4 |
not_visible / timeout_waiting β FailureCategory.ACTIONABILITY (5 ActionabilityReason values) |
π§ In progress β RECEIVES_EVENTS and VISIBLE fully implemented, unit-tested, AND live-verified (both directions of the policy guardrail β see below); ENABLED fully implemented, unit-tested, AND live-verified 2/2 (Sprint 8); EDITABLE not started; STABLE blocked on a deterministic Chaos App mechanism. Healer EXECUTES WAIT_AND_RETRY/NO_SAFE_RECOVERY for all three implemented reasons β VISIBLE, RECEIVES_EVENTS, and ENABLED β all live-verified. RECEIVES_EVENTS' DISMISS_BLOCKER remains proposal-only, still rejected (no DOM-validated guardrail exists for it yet). |
Why phase it: better to prove the full pipeline (collect β analyze β heal
β validate) end-to-end on one well-understood failure type first, then
extend to the others with real lessons learned β rather than build four
shallow strategies at once. See LEARNINGS.md for the full reasoning, or
docs/gaps.md for a quick-scan status table of every
open architectural question.
Note on detached_from_dom: the original working assumption (based
on direct Selenium/Salesforce Lightning experience) was that framework
re-render-mid-action failures would be the dominant real-world case for
PhoenixQA to handle. Sprint 6A tested that assumption directly β a real
reproduction mechanism (componentRemount.jsx) was built and run across
four escalating configurations, ending with a deterministic,
zero-timing-luck trigger. The consistent finding: no observable
DETACHED_FROM_DOM failure, at any setting, for this project's specific
interaction pattern. Research into Playwright's own documentation and
issue tracker offers a likely explanation, not just a coincidence β
Locator-based actions (all BasePage ever uses; never ElementHandle)
are documented to retry automatically on mid-action detachment, which
would plausibly absorb much of the failure class Selenium-style
automation is vulnerable to. This is deliberately framed as evidence
supporting a deprioritization, not proof that the failure type doesn't
exist β see LEARNINGS.md "Sprint 6A conclusion" for the precise scope
of the claim, the full hypothesis β experiment β finding β decision
trail, sources, and what it means for the project's remaining scope.
Note on not_visible/timeout_waiting: before picking one of these
as the redirected target, a set of small diagnostic experiments checked
whether Playwright's own error messages can actually tell them apart β
timeout_waiting's definition ("never reached an actionable state") is
close to a superset of not_visible's. They can't, in the way the
original four-way FailureType split assumed. Real production logs plus
targeted live captures show Playwright's model is genuinely two-stage:
first "did the locator resolve at all" (a bare
waiting for locator(...) message, identical for click() and fill(),
means no), and only if it resolved, one of five distinct actionability
reasons (visible, enabled, editable, stable, receives events).
A flat choice between not_visible and timeout_waiting as separate
top-level types would have inherited the same ambiguity the classifier
already had for selector_not_found before Sprint 4's fix.
The replacement, now implemented and live: FailureCategory
(LOCATOR_RESOLUTION / ACTIONABILITY / a dormant REFERENCE for the
detached_from_dom family) plus ActionabilityReason for the five
concrete reasons above. LOCATOR_RESOLUTION, deliberately not
SELECTOR β an unresolved locator has more than one plausible real
cause (genuine selector drift, an element that's conditionally not yet
mounted, the app being in an unexpected state), which Playwright's own
message can't distinguish; naming the category after "selector" would
have presupposed the diagnosis the evidence argued against.
parse_playwright_call_log() (the classifier), the
ContextCollector/LocatorResolutionCollector/ActionabilityCollector
router split, the HealingAction hierarchy, and two of the five
ActionabilityReason provider paths (RECEIVES_EVENTS, VISIBLE) are
all implemented, unit-verified, and live-verified against a real Chaos
App + Ollama. ENABLED is implemented AND live-verified too (2/2); EDITABLE is not started; STABLE is blocked
on a deterministic Chaos App mechanism. See LEARNINGS.md "Sprint 6B
(decision)" and docs/gaps.md Gap #5/#12 for the full model, and Gap
#13/#14 for the two limits this decision explicitly does not resolve
(the model's dependence on Playwright's unversioned diagnostic text, and
LocatorResolutionCollector's continued inability to tell apart why
a locator never resolved).
LLM proposes, PhoenixQA validates (Sprint 6B): live testing on
RECEIVES_EVENTS found llama3.2 deterministically proposing
wait_and_retry for a blocker it had itself correctly described, in
its own reasoning, as persistent with no dismiss affordance β
confidence unchanged. Prompt engineering alone did not fix this (see
LEARNINGS.md Sprint 6B). The response is
phoenix/healing/actionability_policy.py: a deterministic guardrail
that validates a proposed wait_and_retry against
collector_metadata (structured DOM facts gathered by
ActionabilityCollector), never against the model's freeform reasoning
text, and corrects the strategy when the evidence doesn't support it.
Live-verified in both directions β correctly blocking
RECEIVES_EVENTS's unsupported wait_and_retry every time it was
tried, and correctly passing through VISIBLE's two ground-truth cases
(permanent β model self-selects no_safe_recovery, transient β
model self-selects wait_and_retry) without needing to intervene at
all. See Gap #11 in docs/gaps.md.
Important framing shift for the remaining failure types (Gap #12):
for selector_not_found, the selector itself is what's broken, and the
fix is a replacement selector. For the remaining failure types the
selector may be perfectly correct β what actually failed is the
in-flight ACTION (e.g. a click against an element that isn't yet
actionable, or never becomes actionable in time). PhoenixQA treats
selector_not_found as the special case where "recover the action" happens
to mean "propose a new selector" β the other failure types recover the
action a different way (retry with a wait, dismiss an overlay, scroll
into view, etc.). See docs/gaps.md Gap #12 and LEARNINGS.md Sprint 6.
How this project approaches quality as a whole β not just unit
tests on the framework's own code, but a layered strategy covering
integration, end-to-end behavior against a real LLM, regression
benchmarking of healing effectiveness, and non-functional resilience to
malformed model output β is laid out in
docs/testing-strategy.md.
The open empirical questions driving the project's direction β does
an LLM meaningfully outperform a cheap heuristic, which failure types
actually justify LLM-based healing, is model confidence a reliable
proxy for correctness, and others β are collected in one place in
docs/research_hypotheses.md, separate
from docs/gaps.md's architectural TODOs: a gap is something to build,
a hypothesis is something to find out.
Test Failure
β
βΌ
Failure Classifier β parse_playwright_call_log() β ClassifiedFailure
β FailureCategory (locator_resolution, actionability, reference)
β + ActionabilityReason (5 values) β live, replaces the old flat
β FailureType enum entirely (no compatibility adapter kept)
β
βΌ
Context Collector β router (context_collector.py) over one collector per FailureCategory:
β locator_resolution_collector.py (DOM snapshot, weighted scoring) β live
β actionability_collector.py (RECEIVES_EVENTS, VISIBLE β live;
β ENABLED/EDITABLE not started; STABLE blocked)
β reference_collector.py (dormant, no active plan)
βΌ
LLM Analyzer β Ollama (local) or Anthropic API [stub, NotImplementedError] β
β structured HealingAction:
β SelectorReplacement β live, Safe + Autonomous Mode verified
β ActionabilityStrategy β live for RECEIVES_EVENTS/VISIBLE, guarded by
β actionability_policy.py (deterministic
β evidence check, not the model's own text) β
β PROPOSED and validated, but Healer still
β rejects it outright; no execution yet
β RetryStrategy (reference, dormant) β declared only
β
ββββΊ Safe Mode β Human reviews full context, accepts/rejects β Ground Truth
β
ββββΊ Autonomous Mode β Confidence gate + budget check, auto-applies fix, retries
β
βΌ
Healing History β SQLite log of all decisions (Sprint 7)
β
βΌ
Self-Training β Few-shot context for better future repairs (Sprint 8)
Note: PhoenixQA recovers the ABILITY to perform an action after a
failure β it does not judge whether the resulting behavior was
business-correct (e.g. "did login actually succeed"). That judgment
stays with the test's own assertions, same as it always has. See
docs/gaps.md Gap #11 for why this boundary is deliberate.
Autonomous Mode raises one of three distinct exception types depending on why it didn't heal β HealingRejectedError (bad/low-confidence proposal), HealingLimitExceededError (budget exhausted), HealingFailedError (provider/API crashed) β so a CI failure report says exactly what happened, not just "healing didn't work."
Current implementation status: the Context Collector β LLM Analyzer β HealingAction split above is implemented, not just planned β ContextCollector is a router over per-category collectors, prompts live under phoenix/ai/prompts/ (plus the not-yet-migrated phoenix/ai/prompt_templates.py for the selector path β see docs/known-limitations.md), and providers return HealingAction subtypes. Healer executes SelectorReplacement (live-verified) and WAIT_AND_RETRY/NO_SAFE_RECOVERY for VISIBLE, RECEIVES_EVENTS, and ENABLED β all three live-verified. What's NOT yet done: ActionabilityReason.EDITABLE/STABLE, RECEIVES_EVENTS' DISMISS_BLOCKER execution, and AnthropicProvider (both methods still raise NotImplementedError β ollama is the only working provider today, despite the badge/table above listing both). See docs/known-limitations.md for the precise current-vs-planned boundary.
Chaos App isn't randomized weirdness β each level isolates one variable and answers a specific research question. This is closer to a controlled experiment than a typical "Playwright + sample app" portfolio repo.
| Level | Mechanisms (cumulative) | Research question |
|---|---|---|
| LOW | selector rotation | Does the test survive a selector rename? |
| MEDIUM | + DOM structure mutation | Does the test survive a UI refactor? |
| HIGH | + async delay | Does the test survive a refactor + timing issues? |
Shadow DOM is a separate, independent flag (SHADOW_DOM_ENABLED), not a 4th level β it's a different kind of difficulty (structural DOM access), combinable with any level above (e.g. HIGH + Shadow DOM tests refactor + timing + structural access at once).
Mechanisms ranked by real-world realism (most enterprise frontends break this way most often):
| Mechanism | Realism | Why |
|---|---|---|
| DOM Mutation | 10/10 | UI library upgrades, wrapper changes, component migrations |
| Selector Rotation | 9/10 | Classic β renamed class/id/data-testid |
| Async Delay | 8/10 | Lazy loading, animations, network-dependent rendering |
| Shadow DOM | 5/10 | Real, but narrower β mostly Web Components / LWC-style platforms |
A 5th, independent mechanism, component remount / detach-mid-action (componentRemount.jsx, COMPONENT_REMOUNT_ENABLED), was built in Sprint 6A specifically to give detached_from_dom something real to classify against. It's implemented and verified live, but β see the Scope section above β did not reproduce the target failure against Locator-based interactions even under a deterministic, zero-timing-randomness trigger. It remains in the codebase as a verified research tool, not as an active target for the next healing strategy.
Two more independent mechanisms were built in Sprint 6B, specifically to give ActionabilityReason.RECEIVES_EVENTS and .VISIBLE real, deterministic ground truth to classify against β both are live and verified:
pointerEventsOverlay.jsx(VITE_POINTER_EVENTS_OVERLAY_MODE=off|permanent|transient,VITE_POINTER_EVENTS_OVERLAY_MS) β a transparent, full-viewport overlay that intercepts every click, simulating a real cookie banner / modal / sticky header blocking a click.permanentnever removes itself (ground truth:no_safe_recovery);transientunmounts after the configured delay (ground truth:wait_and_retry) β added (Sprint 8) mirroringvisibilityDelay.jsx's two-mode split, onceHealer's execution widened to coverRECEIVES_EVENTStoo.RECEIVES_EVENTS' evidence is a declared CSS capability checked once, notVISIBLE's observed-change-across-a-narrow-window β but live testing still found a real lower bound: the delay is measured from component mount, not from whenclick()is first attempted (login()fills two fields first), so too short a value lets the overlay vanish beforeHealeris ever invoked at all. Verified working value:15000β seedocs/known-limitations.md.disabledState.js(VITE_DISABLED_STATE_MODE=off|permanent|transient,VITE_DISABLED_STATE_MS) β the login button carries a plaindisabledattribute, simulatingActionabilityReason.ENABLED(a "Submit" button disabled until validation passes, or while an async save is in flight).permanentnever enables it (ground truth:no_safe_recovery);transientenables it after the configured delay (ground truth:wait_and_retry) β the thirdActionabilityReason, built directly with both modes from the start (Sprint 8) now that the PERMANENT/TRANSIENT pattern is established. Evidence model mirrorsVISIBLE(an observed state change across two snapshots), notRECEIVES_EVENTS(a declared CSS capability) β seedisabledState.js's own docstring.visibilityDelay.jsx(VITE_VISIBILITY_DELAY_MODE=off|permanent|transient,VITE_VISIBILITY_DELAY_MS) β hides the password input viavisibility: hidden.permanentnever reveals it (ground truth:no_safe_recovery);transientreveals it after the configured delay (ground truth:wait_and_retry) β the first mechanism built to test both directions of the policy guardrail live. Important:VITE_VISIBILITY_DELAY_MSmust land INSIDEActionabilityCollector's fixed observation window ([T, T+1200]whereT βPlaywright's ownfill()timeout,30000msby default) β not merely exceed30000ms, and not too far past it either.30500is intermittent (system-jitter-dependent),32000misses deterministically (past the window entirely). Working value:30800. Seedocs/known-limitations.mdandLEARNINGS.md's "[Verification] Margin correction" entry for the full reasoning.
Controlling the chaos level:
# chaos_app/.env
VITE_CHAOS_LEVEL=HIGH # LOW | MEDIUM | HIGH
VITE_SHADOW_DOM_ENABLED=true # true | false β independent of level
VITE_COMPONENT_REMOUNT_ENABLED=false # true | false β independent of level, see note above
VITE_POINTER_EVENTS_OVERLAY_MODE=off # off | permanent | transient β independent of level, ActionabilityReason.RECEIVES_EVENTS
VITE_VISIBILITY_DELAY_MODE=off # off | permanent | transient β independent of level, ActionabilityReason.VISIBLEEdit chaos_app/.env, then restart npm run dev. The "Active chaos config" panel at the top of the running app confirms which mechanisms are live β no guessing required.
Mechanism overrides (development/verification only, never for the official benchmark): each core mechanism (selector_rotation, dom_mutation, async_delay) can be forced on/off independently of the active level via VITE_OVERRIDE_SELECTOR_ROTATION / VITE_OVERRIDE_DOM_MUTATION / VITE_OVERRIDE_ASYNC_DELAY, useful for isolating one mechanism's effect during development without inventing a new named chaos level for every combination. CHAOS_LEVELS's three named scenarios remain the only thing the eventual benchmark runner configures via plain CHAOS_LEVEL.
End goal (Sprint 8 β Healing Benchmark Runner): run the full suite at every level, comparing No Healer vs. Heuristic Healer vs. LLM Healer β not just "it works," but "here's exactly how much the LLM adds over a cheap fuzzy-match baseline, and where."
| Chaos Level | No Healer | Heuristic Healer | LLM Healer |
|---|---|---|---|
| LOW | ~72% | ?% | ~98% |
| MEDIUM | ~51% | ?% | ~95% |
| HIGH | ~29% | ?% | ~90% |
The middle column is the actual experiment β a simple fuzzy/Levenshtein selector matcher (zero LLM calls, same provider interface as Anthropic/Ollama) might already solve a surprising fraction of cases. Without this baseline, "90% healed" doesn't prove the LLM was necessary.
Autonomous Mode has hard stop conditions from day one (max_attempts_total, token budget, max_time_per_heal) β no infinite LLM retry loops in CI, by design, not as a later hardening pass. Budget is tracked in tokens and elapsed time, never in currency β model pricing changes over time, token counts don't. See docs/architecture-decisions.md for the full reasoning.
PhoenixQA/
βββ LEARNINGS.md # chronological journal β problem β analysis β decision β test β conclusion
βββ docs/ # thematic indexes (fast lookup by topic, not by sprint)
β βββ gaps.md # all numbered architectural gaps, status at a glance
β βββ architecture-decisions.md
β βββ known-limitations.md
β βββ future-ideas.md
β βββ testing-strategy.md # unit/integration/e2e/regression-benchmark/non-functional plan + actual state
β βββ research_hypotheses.md # open empirical questions the project is trying to answer, not just architectural gaps to fix
βββ chaos_app/ # React/Vite β intentionally unstable test target
β βββ src/chaos/ # selectorRotation, domMutation, asyncDelay, shadowDom, componentRemount (Sprint 6A)
βββ phoenix/
β βββ collector/ # failure_classifier, context_collector (router, planned) + collectors/ (per-FailureType, planned)
β βββ healing/ # healer, safe_mode, decision_logger, autonomous_mode
β βββ ai/ # base_provider, ollama_provider, anthropic_provider,
β β # prompts/ (per-FailureType, planned), response_parser, provider_factory
β βββ training/ # Healing history (Sprint 7)
β βββ reporting/ # Allure Phoenix Healing Report (Sprint 9)
βββ pages/ # Page Objects for Chaos App (POM pattern)
βββ tests/
β βββ chaos/ # tests running against Chaos App
β βββ unit/ # tokenizer, classifier, parser, logger, healer tests
β βββ integration/
βββ config/
| Provider | When to use | Status |
|---|---|---|
ollama |
Air-gapped / NDA environments, local LLM | β Live β the only working provider today |
anthropic |
Cloud projects, best quality healing suggestions | π΄ Stub β AnthropicProvider.analyze_failure()/health_check() both raise NotImplementedError |
Switch via single env variable β architecturally true (provider_factory.py already dispatches on AI_PROVIDER), but setting AI_PROVIDER=anthropic today raises immediately, not a working alternative yet.
| Sprint | Focus | Status |
|---|---|---|
| Sprint 0 | Repo scaffold, config, AI provider stubs | β Done |
| Sprint 1 | Chaos App β React/Vite, selector rotation, DOM mutation, async delay, Shadow DOM | β Done |
| Sprint 2 | Context Collector β selector_not_found only (DOM snapshot, weighted scoring) |
β Done |
| Sprint 3 | LLM Analyzer β prompt engineering, structured JSON response, confidence score | β Done |
| Sprint 4 | Safe Mode β Human-in-the-loop terminal review, JSON-lines decision log | β Done |
| Sprint 5 | Autonomous Mode β stop conditions (attempts/tokens/time budget), confidence policy gate, distinct exception types | β Done |
| Sprint 6 | Failure type expansion β action-recovery reframing, polymorphic collector, split prompts, HealingAction hierarchy all implemented; target failure type redirected after Sprint 6A findings |
π§ In progress |
| Sprint 6A | componentRemount.jsx (TIMEOUT + MOUSEDOWN triggers) + classifier extended to recognize DETACHED_FROM_DOM |
β
Done β controlled experiment (4 escalating configurations) found no reproduction against Locator-based automation; a finding about Playwright's architecture, not a mechanism gap. See LEARNINGS.md "Sprint 6A conclusion" |
| Sprint 6B | FailureCategory/ActionabilityReason model: classifier rewrite, ContextCollector router split, HealingAction hierarchy, ActionabilityCollector + provider path + actionability_policy.py guardrail for RECEIVES_EVENTS and VISIBLE |
β
Both reasons implemented, unit-tested (134/134), AND live-verified end-to-end, including both directions of the policy guardrail. Healer still rejects any ActionabilityStrategy β proposal only, no execution. ENABLED/EDITABLE not started, STABLE blocked. See LEARNINGS.md Sprint 6B |
| Sprint 6C-D | Remaining ActionabilityReason slice (EDITABLE) |
βΈοΈ ENABLED done instead (Sprint 8) β see below; EDITABLE still not started |
| Sprint 8, third reason | ENABLED β full vertical slice (Chaos App disabledState.js, collector, prompt, policy, provider wiring, Healer execution) |
β
Implemented, unit-verified (214/214), AND live-verified 2/2 (VITE_DISABLED_STATE_MS=11200) |
| Sprint 7 | Healing History β SQLite store, decision log, healing correctness definition | β³ Planned |
| Sprint 8 | Narrow baseline slice (Gap #9, decided) β HeuristicProvider (LOCATOR_RESOLUTION, fuzzy matching) + PolicyOnlyProvider (ACTIONABILITY/VISIBLE, decides off collector_metadata directly) vs. llama3.2, small-scale, deliberately BEFORE the full multi-level Benchmark Runner. Sequenced ahead of Sprint 6C-D β see LEARNINGS.md "Sprint 8 (pre-coding)" |
β
Complete β 8/8 across LOW/MEDIUM, baseline matched llama3.2 every time. ticket_row_ambiguity near-tie idea investigated and deprioritized (Gap #16) |
| Sprint 8, Option A (narrowed) | Healer executes an approved ActionabilityStrategy β scoped to VISIBLE's WAIT_AND_RETRY/NO_SAFE_RECOVERY only, RECEIVES_EVENTS untouched |
β Implemented, unit-tested (185/185) β live verification against a real Chaos App still pending |
| Sprint 9 | Allure Phoenix Report, CI/CD, demo GIF | β³ Planned |
Sprint 6 architectural decisions made before any code was written (full reasoning in LEARNINGS.md, indexed in docs/gaps.md Gap #12 and docs/architecture-decisions.md):
- The non-
selector_not_foundfailure types are reframed as action recovery, not selector replacement β there may be nothing wrong with the selector itself. ContextCollectorbecomes a router over oneBaseContextCollectorsubclass perFailureType, replacing the current if/elif ladder.- The prompt layer splits the same way β one prompt module per
FailureType, since "find a selector" and "should this action be retried, and after what wait" are different cognitive tasks for the model. HealingProposalis retired as the universal provider return type, replaced by aHealingActionhierarchy βSelectorReplacement(locator_resolution), one mergedActionabilityStrategyparameterized by reason + recovery kind (actionability, superseding the originally-declared separateWaitStrategy/VisibilityStrategy), and a dormantRetryStrategy(reference) β a required, blocking refactor touchingHealer,safe_mode.py,decision_logger.py, andresponse_parser.py. SeeLEARNINGS.md"Sprint 6B (decision)" for the finalized shape.
These four decisions have all been implemented as described β they were designed to generalize, not built around detached_from_dom specifically, and turned out to generalize cleanly to ActionabilityReason. What changed after Sprint 6A is only the answer to "which failure type comes next": a controlled experiment against Playwright's Locator API (see the Scope section above and LEARNINGS.md "Sprint 6A conclusion" for the full hypothesis β experiment β finding β decision trail) found that detached_from_dom isn't a high-value target for this architecture right now. A follow-up round of diagnostics (Sprint 6B) then found that picking not_visible or timeout_waiting outright wasn't the right move either β Playwright's own model splits failures into "did the locator resolve" and, if so, one of five actionability reasons. Two of those five (RECEIVES_EVENTS, VISIBLE) are now fully built and live-verified; the remaining open decision is whether Sprint 6C-D adds a third reason or builds actual execution of an already-approved ActionabilityStrategy β see docs/gaps.md Gap #12.
# 1. Clone
git clone https://github.com/MarcinMikula/PhoenixQA.git
cd PhoenixQA
# 2. Install Python deps
pip install -r requirements.txt
playwright install chromium
# 3. Configure
cp .env.example .env
# Edit .env β choose AI provider, chaos level
# 4. Run the Chaos App (test target)
cd chaos_app
npm install
cp .env.example .env
npm run dev
# β http://localhost:5173
# 5. In a SEPARATE terminal (npm run dev keeps step 4's terminal busy):
cd ..
# Run tests against it β both Safe Mode (Sprint 4) and Autonomous Mode
# (Sprint 5) are live. Switch via .env: HEALING_MODE=safe | autonomous
#
# -s is REQUIRED for Safe Mode: it prompts for accept/reject via input(),
# and pytest swallows stdin/stdout without -s β the prompt never
# reaches the terminal and the run just hangs with no explanation.
# Autonomous Mode doesn't need -s (no prompts), but it doesn't hurt either.
pytest tests/chaos/ -m chaos -sHealing is confirmed working live as of Sprint 5 β Safe Mode and Autonomous Mode have both been run end-to-end against the real Chaos App and a local LLM, with selectors successfully healed and retried in place.
The actual demo artifact, though, is parked until Sprint 9: rather than a pile of terminal screenshots, the plan is a single Allure Healing Dashboard (success rate, healing timeline, confidence distribution, top repaired selectors, failure reasons, budget usage, provider comparison) β built once Sprint 6-8 (failure type expansion, healing history, benchmark runner) produce real data for it to render. See docs/future-ideas.md for the reasoning.
PhoenixQA is one piece of a larger AI-powered QA toolkit:
| Repo | Role |
|---|---|
| qa-automation-framework | POM/SOM skeleton β PhoenixQA heals its selectors |
| defect-pilot | AI bug reproduction & retest agent |
| llm-qa-toolkit | LLM-as-judge test framework for AI chatbots |
MIT