Test Forge Automation Framework is an AI-augmented UI automation framework built on Playwright and Pytest. Designed as an architectural showcase, it modernizes traditional Page Object Model (POM) design by injecting advanced intelligence directly into the testing layer—featuring AI-driven self-healing locators, automated failure classification, and stateful flaky test tracking.
-
Execution Branches
tests/no_ai/: Completely deterministic production-ready tests using localized self-healing heuristics.tests/ai/: AI-assisted testing integrating open-source local and cloud-hosted LLM endpoints.
-
Failure Classification
- Parses test failures and categorizes exceptions into typed results (
Timeout,Locator Issue,Assertion,Other). - Automatically extracts failed locators from exception messages.
- Parses test failures and categorizes exceptions into typed results (
-
Process-Safe Flaky Detection
- Tracks historical results using SQLite (WAL-mode) or JSON (with cross-platform file locking).
- Enforces
min_runs=3sample guard before flagging test cases as flaky.
-
Self-Healing Locator Guardrails
- Captures
before_heal.pngandafter_heal.pngscreenshots on successful locator healing. - Caps healing attempts at 3 per action, requiring a minimum similarity score threshold of 0.5.
- Healed tests are tracked as
PASSED (healed)in summaries.
- Captures
-
Multi-Provider AI Client
- Dynamically routes requests for selector healing and planning to:
- Local Ollama:
qwen2.5-coder:1.5brunning onhttp://localhost:11434. - Hugging Face Serverless API:
Qwen/Qwen2.5-Coder-7B-InstructusingHF_API_TOKEN. - Gemini API:
gemini-2.5-flashusingGEMINI_API_KEY. - Local Heuristics: Falls back to offline similarity logic if APIs are unavailable.
- Local Ollama:
- Dynamically routes requests for selector healing and planning to:
Nexus Automate — Hybrid Playwright + Stagehand Framework complements Test Forge with a second approach to AI-assisted end-to-end testing. While Test Forge adds intelligence inside a Python, Pytest, and Page Object Model architecture, Nexus Automate combines deterministic Playwright locators with AI-driven Stagehand fallback actions in a TypeScript-based runtime.
- CDP Bridge: Playwright and Stagehand share a single browser instance through
connectOverCDP. - Hybrid Routing: Standard locators run deterministically with Playwright, while instructions beginning with
ai:route to Stagehand LLM actions. - Fast Auth Reuse: A persistent JSON cookie store skips repeated UI logins.
- Spec Timeout Enforcement: Strict timeout limits are applied to every spec run.
- Strict Exit Codes: Failed runs return exit code
1for reliable CI pipeline integration.
Together, Test Forge and Nexus Automate demonstrate two complementary architectures for AI-assisted browser automation: framework-level self-healing and failure intelligence, and runtime-level hybrid deterministic/LLM action routing.
For a complete architectural overview, refer to docs/architecture.md.
- Action-Level Interception: Intercepts element failures in real-time inside
HealingLocator._run_action_with_healing()before the test case fails. - Before/After Evidence: Captures
before_heal_<ts>.png(showing missing/broken selector state) andafter_heal_<ts>.png(showing recovered state). - Candidate Scoring:
dom_extractor.pyscans active browser elements via client-side JavaScript, andstrategies.pyranks candidates using string similarity & attribute intersection boosts (ID +0.2, Name +0.2, Data-Test +0.3). - Git Patch Generation: Writes
reports/no_ai/healing_patch.diffwith 3-line unified diff context lines, allowing CI/CD to auto-create Pull Requests.
- Invoked By:
pytest_runtest_makereporthook in conftest.py. - When: Runs at test completion (
rep.when == 'call') wheneverrep.failed == Trueand--classifyis active. - Rules: Categorizes failures into taxonomy buckets (
Assertion,Locator Issue,Timeout,Other) and extracts target locators using regex.
-
Phase 1 (Recording):
conftest.pyrecords'passed'or'failed'for every run/retry intoreports/flaky_history.json(atomic viaFileLock). -
Phase 2 (Scoring):
reporter.pycomputes variance score:$$\text{Flaky Score} = \frac{2 \times \min(\text{passes}, \text{failures})}{\text{total runs}}$$ Tests alternating between pass and fail ($\text{score} \ge 0.2$ ) are flagged as FLAKY DETECTED on the HTML dashboard.
test-forge/
├── src/
│ ├── pages/ # Page Object Model classes (LoginPage, Inventory, Cart, Checkout)
│ ├── framework/ # Core framework layer
│ │ ├── no_ai/ # Heuristic healing: healer, strategies, dom_extractor
│ │ ├── ai/ # AI-assisted healing: ai_helper routing client
│ │ ├── core/ # Shared core features: failure_classification, flaky_detection
│ │ └── core/ # Shared core features: failure_classification, flaky_detection
│ └── tests/ # Internal test helpers / utilities
├── tests/ # Pytest tests divided by execution branch
│ ├── no_ai/ # Deterministic healing & classification (unique test flows)
│ ├── ai/ # AI-assisted healing (distinct checkout and locator flows)
│ ├── utils/ # Framework unit tests
│ └── conftest.py # Pytest hooks, page wrappers, command flags
├── scripts/
│ └── generate_dashboard.py # Python script generating GitHub Pages dashboard index.html
├── runner.py # CLI entrypoint
├── orchestrator.py # Runner pipeline execution manager
├── reporter.py # Summary generator & visual report manager
├── Makefile # Standardized local execution commands (Ruff, Install, Test)
└── docs/ # Design architecture & documentation
All execution and environment setup is standardized around the Makefile.
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activatemake install
make install-browsersWe use Ruff for linting and code formatting:
make lint # Checks lint and formatting
make lint-fix # Automatically fixes lint violations and reformats filesRun branch-specific suites with automated setup flags using simple make targets:
- Deterministic Suite (No AI): Runs with self-healing, classification, and 2 retries.
make test-no-ai
- AI-Assisted Suite: Runs with AI-assisted locator healing and classification.
# Set your API token before running: export HF_API_TOKEN="your-hf-token" # Or: export GEMINI_API_KEY="your-gemini-key" make test-ai
- Run All Suites: Runs both execution branches sequentially.
make test-all
- Clean Outputs: Cleans all generated reports, screenshots, videos, and logs.
make clear
Outputs are saved under the reports/[branch_name]/ directory (e.g., reports/no_ai/ or reports/ai/):
report.html: Custom HTML report with embedded screenshots and Playwright execution videos.run_summary.json: Unified statistics containing passed, healed, failed, and flaky counts.failure_classification.json: Categorized exceptions and target locator details.healing_patch.diff: Git diff containing the suggested code modifications for healed locators.
The repository uses two scheduled and event-driven GitHub Actions workflows:
- Test Forge CI/CD Pipeline (
.github/workflows/test-forge.yml):- Triggers: Push to
master, manually, or daily at 02:00 UTC. - Jobs: Enforces Ruff linter, runs all three test suites in parallel, automatically opens a Pull Request with healed locators if a patch is found, and publishes the consolidated dashboard to GitHub Pages.
- PR Title Formats:
[AI Self-Healing] Fix locator drift in ai - DO NOT MERGE[Non-AI Self-Healing] Fix locator drift in no_ai - DO NOT MERGE
- Triggers: Push to
- Cleanup Stale PRs (
.github/workflows/cleanup-stale-prs.yml):- Triggers: Daily at 03:00 UTC (1 hour after tests) or manually.
- Jobs: Finds open Pull Requests containing
Self-Healingin the title that are older than 1 day, automatically closes them, and deletes their temporary branches to keep the repository clean.
Problem: Running the full suite on every push is expensive and slow.
Solution: Build an impact_analyzer.py pre-flight script that runs before Pytest:
- Run
git diff origin/masterto identify changed application files. - Send the diff + test file mapping to
ai_helper(Ollama / Gemini). - The LLM returns a list of test IDs affected by those changes.
runner.pyconsumes the output as a-kexpression, slicing 500 tests down to the 10 that matter.
Outcome: CI runtimes drop by 80–90%. PR feedback loops shrink from minutes to seconds.
Problem: The framework heals in-flight every single run, even for the same broken locator — paying the full timeout + API cost each time.
Solution: Implement a reports/active_heals.json healing cache:
- On successful healing, write
{ "original_selector": "healed_selector" }to the cache. - Intercept
HealingPage.locator()before it even tries the original — if a cached mapping exists, swap immediately. - Bypass the timeout and AI query entirely until the developer merges the
.diff.
Outcome: Zero-latency re-healing for known-broken locators. Eliminates redundant API calls entirely.
Problem: DOM extraction can fail on obfuscated dynamic class names (React, Angular minified output). A JSON DOM snapshot is insufficient context for ambiguous layouts.
Solution: When a locator fails, take a full-page Playwright screenshot and send the base64-encoded image alongside the DOM snippet to gemini-2.5-flash (multimodal) or gpt-4o:
"I was trying to click the 'Checkout' button. Look at this screenshot, find the button visually, and return its CSS selector from the DOM provided."
Outcome: Healing accuracy on complex/obfuscated UIs improves dramatically. Positions the framework for real-world enterprise applications.
Problem: How do you know the AI healed the right element and didn't hallucinate? (e.g., healed "Submit Order" to "Cancel Order" instead.)
Solution: Create an evals/ directory with a nightly evaluation script:
- Feed historical
pytest_healing.jsonlogs into DeepEval. - DeepEval uses an LLM judge to score healing quality: "Was the healed locator semantically equivalent to the original intent?"
- Flag low-confidence heals for human review.
Outcome: Makes AI healing trustworthy and auditable, not just functional. Critical for any regulated or enterprise environment.
Problem: The hardcoded prompts in ai_helper.py were written once and never optimized. Their performance degrades as page complexity grows and models change.
Solution: Replace hardcoded string prompts with DSPy modules:
- Collect 20+ examples of
(broken_locator, DOM_context) -> correct_healed_locatorfrom past run logs. - DSPy automatically rewrites and optimizes the prompt via few-shot bootstrapping.
- The optimized prompt is serialized and checked into
prompts/healer_prompt.json.
Outcome: The framework self-improves its own AI instructions over time. Prompt quality compounds — the more the framework is used, the better it gets.
Problem: FileLock + SQLite WAL works perfectly on a single machine. With 10 parallel GitHub Actions runners, there is no shared filesystem.
Solution: Add a FLAKY_STORAGE=redis environment variable flag:
- If set,
FlakyDetectorwrites results to a free Redis cloud instance (e.g., Upstash) or PostgreSQL via a simple REST API call. - All parallel runners converge on the same shared state.
- Keep the local JSON fallback when
FLAKY_STORAGEis not set.
Outcome: Proves enterprise-scale architectural awareness. Flakiness detection works correctly across massive distributed test grids.
Recommended implementation order: Priority 1 → 2 → 3 → 4 → 5 → 6