Skip to main content

Deterministic DSL-first browser automation platform powered by Playwright heuristics with optional local AI self-healing (Ollama)

Project description

ManulEngine mascot

ManulEngine

PyPI PyPI Downloads Manul Engine Extension MCP Server Status: Alpha

Write browser automation in plain English. ManulEngine interprets .hunt files through deterministic DOM heuristics on top of Playwright — no selectors, no cloud APIs, no AI required.

Status: Alpha. Solo-developed, actively battle-tested. Bugs are expected, APIs may evolve, and there are no promises about stability or production readiness. The core claim is transparency: when a step works, you can see exactly why; when it fails, you get the scoring breakdown to diagnose it.


Syntax First

ManulEngine runs .hunt files — plain-English automation scripts that read like manual QA steps. Here is the DSL in action.

A complete flow

@context: Smoke test for a login page
@title: Login Smoke
@var: {email} = admin@example.com
@var: {password} = secret123

STEP 1: Open the app
    NAVIGATE to https://example.com/login
    VERIFY that 'Sign In' is present

STEP 2: Authenticate
    FILL 'Email' field with '{email}'
    VERIFY "Email" field has value "{email}"
    FILL 'Password' field with '{password}'
    CLICK the 'Sign In' button
    VERIFY that 'Dashboard' is present

DONE.

Run it:

manul path/to/login.hunt

Every @var: is declared up front — never hardcode test data inside steps. VERIFY confirms state after every significant action. DONE. closes the mission.

Case-insensitive keywords. All DSL keywords are case-insensitive at runtime — CLICK, Click, and click all work. The canonical form used in documentation and generated files is ALL UPPERCASE.

Element type hints are optional. Words like button, link, field, dropdown placed after the target outside quotes are not required, but they provide a strong heuristic signal that boosts scoring accuracy. CLICK the 'Login' button and CLICK the 'Login' both work — the former is more precise.

Conditional branching

Branch test logic with IF / ELIF / ELSE based on what the page actually contains. Nesting is supported.

STEP 1: Adaptive login
    IF button 'SSO Login' exists:
        CLICK the 'SSO Login' button
        VERIFY that 'SSO Portal' is present
    ELIF text 'Sign In' is present:
        FILL 'Username' field with '{username}'
        CLICK the 'Sign In' button
    ELSE:
        CLICK the 'Create Account' link

Conditions can check element existence, visible text, variable equality, substring containment, or simple truthiness — all evaluated against the live page.

Contextual navigation

When a page has repeating controls — multiple "Delete" buttons, "Edit" links in every row — use contextual qualifiers instead of brittle selectors.

CLICK the 'Edit' button NEAR 'John Doe'
CLICK the 'Login' button ON HEADER
CLICK the 'Privacy Policy' link ON FOOTER
CLICK the 'Delete' button INSIDE 'Actions' row with 'John Doe'

NEAR ranks by pixel distance. ON HEADER / ON FOOTER scopes to viewport zones. INSIDE restricts scoring to a resolved row or container subtree.

Variables, data-driven runs, and backend hooks

@var: {email} = admin@example.com
@var: {password} = secret123
@script: {db} = scripts.db_helpers
@data: users.csv
@tags: smoke, auth

[SETUP]
    CALL PYTHON {db}.seed_user "{email}" "{password}"
[END SETUP]

STEP 1: Login
    NAVIGATE to https://example.com/login
    FILL 'Email' field with '{email}'
    FILL 'Password' field with '{password}'
    CLICK the 'Sign In' button
    VERIFY that 'Dashboard' is present

STEP 2: Fetch and use an OTP
    CLICK the 'Send OTP' button
    CALL PYTHON api_helpers.fetch_otp "{email}" into {otp}
    FILL 'OTP' field with '{otp}'
    CLICK the 'Verify' button
    VERIFY that 'Welcome' is present

[TEARDOWN]
    CALL PYTHON {db}.clean_database "{email}"
[END TEARDOWN]

@data: loops the entire mission over each row in a CSV or JSON file. @tags: lets you filter runs with manul --tags smoke. [SETUP] / [TEARDOWN] run Python outside the browser for data seeding and cleanup. CALL PYTHON ... into {var} captures return values mid-test — ideal for OTPs, tokens, and backend state.

Explicit waits and strict assertions

WAIT FOR 'Submit' to be visible
WAIT FOR 'Loading...' to disappear

VERIFY "Email" field has value "{email}"
VERIFY "Save" button has text "Save Changes"
VERIFY "Search" input has placeholder "Type to search..."

Explicit waits use Playwright's locator.wait_for() instead of hardcoded sleeps. Strict assertions resolve the element through heuristics and compare exact text, value, or placeholder with ==.

Shared libraries and scheduling

@import: Login, Logout from lib/auth.hunt
@export: Checkout
@schedule: every 5 minutes

STEP 1: Setup
    USE Login

STEP 2: Purchase flow
    CLICK the 'Buy Now' button
    VERIFY that 'Order Confirmed' is present

STEP 3: Cleanup
    USE Logout

DONE.

@import: / USE reuses named STEP blocks across files. @export: controls visibility. @schedule: plus manul daemon turns any hunt into a recurring monitor or RPA job.

CLI

manul path/to/hunts/                             # run all .hunt files in a directory
manul --headless path/to/file.hunt               # headless single file
manul --tags smoke path/to/hunts/                # filter by tags
manul --html-report --screenshot on-fail path/   # reports + failure screenshots
manul --explain path/to/file.hunt                # per-step scoring breakdown
manul --debug path/to/file.hunt                  # pause before every step
manul --retries 2 path/to/hunts/                 # retry failed hunts
manul scan https://example.com                   # scan a page → draft.hunt
manul daemon path/to/hunts/ --headless           # run scheduled hunts

Philosophy

Determinism, not prompt variance

The primary resolver is not an LLM. It is a weighted heuristic scorer (DOMScorer) backed by a native JavaScript TreeWalker. Scores are normalized on a 0.0–1.0 confidence scale across five channels: cache, semantics, text, attributes, and proximity. The result is repeatable: same page state plus same step text equals same resolution — no prompt variance, no cloud dependency.

When --explain is enabled, every resolved step prints a per-channel breakdown so you can see exactly which signals drove the decision and which lost:

┌─ EXPLAIN: Target = "Login"
│  Step: Click the 'Login' button
│
│  #1 <button> "Login"
│     total:      0.593
│     text:       0.281
│     attributes: 0.050
│     semantics:  0.225
│     proximity:  0.037
│     cache:      0.000
│
└─ Decision: selected "Login" with score 0.593

Dual-persona workflow

Manual QA writes plain-English .hunt steps — no code required. SDETs extend the same files with Python hooks ([SETUP] / [TEARDOWN], CALL PYTHON), lifecycle orchestration (@before_all / @after_all), and @custom_control handlers for complex widgets. Both personas work on the same artifact.

Optional AI, off by default

"model": null is the recommended default. When a local Ollama model is enabled, it acts as a last-resort fallback for genuinely ambiguous elements. The engine is not AI-powered — it is heuristics-first with an optional AI safety net.


Four Automation Pillars

The same runtime and the same DSL serve four use cases:

Pillar How
QA / E2E testing Write plain-English flows, verify outcomes, attach HTML reports and screenshots. No selectors in the test source.
RPA workflows Log into portals, fill forms, extract values, hand off to Python for backend or filesystem steps.
Synthetic monitoring Pair .hunt files with @schedule: and manul daemon for recurring health checks.
AI agent targets Constrained DSL execution is safer than raw Playwright for external agents — the runtime still owns scoring, retries, and validation.

Key Features

  • Explainability--explain prints per-channel scoring breakdowns on the CLI. The VS Code extension shows hover tooltips and a title-bar "Explain Current Step" action during debug pauses.
  • What-If Analysis REPL — During a --debug pause, type w to enter an interactive REPL that evaluates hypothetical steps against the live DOM without executing them. Returns a 0–10 confidence score, risk assessment, and highlights the best match on the page.
  • Desktop / Electron — Set executable_path in the config and use OPEN APP instead of NAVIGATE to drive Electron apps with the same DSL.
  • Python API (ManulSession) — Async context manager for pure-Python automation. Routes every call through the full heuristic pipeline.
    from manul_engine import ManulSession
    
    async with ManulSession(headless=True) as session:
        await session.navigate("https://example.com/login")
        await session.fill("Username field", "admin")
        await session.click("Log in button")
        await session.verify("Welcome")
    
  • Smart recorder — Captures semantic intent (e.g., Select 'Option' from 'Dropdown') instead of brittle pointer events.
  • Custom controls@custom_control(page, target) decorator lets SDETs handle complex widgets (datepickers, virtual tables, canvas elements) with raw Playwright while the hunt file keeps a single readable step.
  • Lifecycle hooks@before_all, @after_all, @before_group, @after_group in manul_hooks.py for suite-wide setup and teardown.
  • HTML reports--html-report generates a self-contained dark-themed report with accordions, screenshots, tag filters, and run-session merging across CLI invocations.
  • Docker CI runnerghcr.io/alexbeatnik/manul-engine:0.0.9.28 runs headless in CI with dumb-init, non-root user, and MANUL_* env overrides.

Quickstart

Install

pip install manul-engine==0.0.9.28
playwright install

Optional local AI fallback (not required):

pip install "manul-engine[ai]==0.0.9.28"
ollama pull qwen2.5:0.5b && ollama serve

Configure

Create manul_engine_configuration.json in the workspace root. All keys are optional:

{
  "model": null,
  "browser": "chromium",
  "controls_cache_enabled": true,
  "semantic_cache_enabled": true
}

This is the minimal recommended config — fully heuristics-only, no AI dependency.

Run

manul tests/login.hunt                           # single file
manul tests/                                     # all hunts in a directory
manul --headless --html-report tests/            # CI mode with reports

Configuration reference

Key Default Description
model null Ollama model name. null = heuristics-only.
headless false Hide the browser window.
browser "chromium" chromium, firefox, or webkit.
browser_args [] Extra browser launch flags.
ai_threshold auto Score threshold before LLM fallback.
ai_always false Always invoke LLM picker (requires model).
ai_policy "prior" Heuristic score as prior hint or strict constraint.
controls_cache_enabled true Persistent per-site controls cache.
controls_cache_dir "cache" Cache directory (relative or absolute).
semantic_cache_enabled true In-session semantic cache (+200k score boost).
custom_controls_dirs ["controls"] Directories scanned for @custom_control modules.
timeout 5000 Action timeout (ms).
nav_timeout 30000 Navigation timeout (ms).
workers 1 Max parallel hunt files.
tests_home "tests" Default output for new hunts and scans.
auto_annotate false Insert # 📍 Auto-Nav: comments on URL changes.
channel null Installed browser channel (chrome, msedge).
executable_path null Path to Electron or custom browser executable.
retries 0 Retry failed hunts N times.
screenshot "on-fail" none, on-fail, or always.
html_report false Generate reports/manul_report.html.
explain_mode false Per-channel scoring breakdown in output.

Environment variables (MANUL_HEADLESS, MANUL_BROWSER, MANUL_MODEL, MANUL_WORKERS, etc.) always override JSON config.

Docker

docker run --rm --shm-size=1g \
  -v $(pwd)/hunts:/workspace/hunts:ro \
  -v $(pwd)/reports:/workspace/reports \
  ghcr.io/alexbeatnik/manul-engine:0.0.9.28 \
  --html-report --screenshot on-fail hunts/

Non-root (manul, UID 1000), dumb-init as PID 1, --no-sandbox baked in. A docker-compose.yml ships with manul and manul-daemon services.


Ecosystem

Manul Engine Extension for VS Code

Test Explorer integration, syntax highlighting, config sidebar, cache browser, interactive debug runner with gutter breakpoints, and hover-based explain tooltips.

code --install-extension manul-engine.manul-engine

Marketplace

MCP Server for GitHub Copilot

A separate extension turns ManulEngine into a native MCP server. Copilot Chat gains tools like manul_run_step, manul_run_goal, manul_scan_page, and manul_save_hunt — driving a real browser session from natural language.

code --install-extension manul-engine.manul-mcp-server

Marketplace · Developer guide

Contributing and running tests

git clone https://github.com/alexbeatnik/ManulEngine.git
cd ManulEngine
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
playwright install

python run_tests.py                              # synthetic + unit suite
python demo/run_demo.py                          # integration hunts (needs network)
python demo/benchmarks/run_benchmarks.py         # adversarial DOM fixtures

Get Involved

ManulEngine is alpha-stage and solo-developed. If deterministic, explainable browser automation interests you:


What's New in v0.0.9.28

  • Conditional branching (IF / ELIF / ELSE): Block-style branching in .hunt files based on element presence, visible text, or variable state. Indentation-based body detection, nesting support, and ConditionalSyntaxError for malformed blocks. 97-assertion test suite.
  • What-If Analysis REPL (ExplainNextDebugger): Interactive debug REPL for hypothetical step evaluation. During a debug pause, type w (terminal) to enter the REPL or e / send explain-next (extension protocol) for one-shot evaluation. Combines DOMScorer heuristic scoring with optional LLM analysis to produce a 0–10 confidence rating, element match info, risk assessment, and corrective suggestions. The best heuristic match is highlighted with a persistent magenta outline on the live page. 112-assertion test suite.
  • What-If execute bug fixes: _execute_step() recursive call now passes strategic_context and step_idx by keyword. Injected What-If steps run through substitute_memory() so {var} placeholders resolve before execution.
  • LLM JSON fence-stripping: _parse_llm_json() now strips markdown code fences before JSON parsing.
v0.0.9.26
  • EngineConfig frozen dataclass: Injectable EngineConfig replacing module-level globals. validate() checks configuration invariants.
  • Structured exception hierarchy: ManulEngineError base with 7 concrete subclasses. All re-exported from manul_engine.
  • Thread safety: Registry and module-cache access guarded by locks.
  • Scoring early exit: DOMScorer.score_all() short-circuits when threshold is exceeded.
  • Import depth guard: Recursive .hunt imports capped at depth 10.
  • CI quality gates: Ruff lint + format check, Dependabot.
  • Demo directory restructure: Integration hunts, scripts, benchmarks moved to demo/.
  • Security hygiene: Eliminated false-positive shell-access alert from package scanners.

License

Version: 0.0.9.28

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

manul_engine-0.0.9.28.tar.gz (172.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

manul_engine-0.0.9.28-py3-none-any.whl (179.8 kB view details)

Uploaded Python 3

File details

Details for the file manul_engine-0.0.9.28.tar.gz.

File metadata

  • Download URL: manul_engine-0.0.9.28.tar.gz
  • Upload date:
  • Size: 172.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for manul_engine-0.0.9.28.tar.gz
Algorithm Hash digest
SHA256 6109ec0156af32c3093d7ab506f8227e502aed381ee2f66fb416d3ddecf859b2
MD5 dc8d53bd665df0c02be95329a3014b3e
BLAKE2b-256 1998d976704599a7d873a8e26edd73ea2c11cf65836b6f59338d53b08c1bca41

See more details on using hashes here.

Provenance

The following attestation bundles were made for manul_engine-0.0.9.28.tar.gz:

Publisher: release.yml on alexbeatnik/ManulEngine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file manul_engine-0.0.9.28-py3-none-any.whl.

File metadata

  • Download URL: manul_engine-0.0.9.28-py3-none-any.whl
  • Upload date:
  • Size: 179.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for manul_engine-0.0.9.28-py3-none-any.whl
Algorithm Hash digest
SHA256 6fdef9635d24589f5e17fe6a7aa2872eefb5c6781ed368665b6f3848f88da7c3
MD5 b2a8e4f3fd2bf6c5eebc06f63c4d1df8
BLAKE2b-256 15f635d5c34d265b6477de6a5bcdd3b2c6adcb0ab891a4b80156db8f4df175a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for manul_engine-0.0.9.28-py3-none-any.whl:

Publisher: release.yml on alexbeatnik/ManulEngine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page