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 Manul Engine Extension (Open VSX) MCP Server MCP Server (Open VSX) ManulAI Local Agent 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.

📖 Full Documentation: Overview · Installation · Getting Started · DSL Syntax · Reports & Explainability · Integration


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.

Loops

Repeat actions with REPEAT, iterate data with FOR EACH, or poll dynamic state with WHILE. Loops nest freely with conditionals.

@var: {products} = Laptop, Headphones, Mouse

STEP 1: Add products to cart
    FOR EACH {product} IN {products}:
        FILL 'Search' field with '{product}'
        PRESS Enter
        CLICK the 'Add to Cart' button NEAR '{product}'
        VERIFY that 'Added to cart' is present

STEP 2: Load all reviews
    WHILE button 'Load More' exists:
        CLICK the 'Load More' button
        WAIT 2

STEP 3: Retry checkout
    REPEAT 3 TIMES:
        CLICK the 'Place Order' button
        IF text 'Success' is present:
            VERIFY that 'Order confirmed' is present

REPEAT N TIMES: runs a fixed count. FOR EACH {var} IN {collection}: iterates comma-separated values. WHILE <condition>: repeats until the condition is false (safety limit: 100 iterations). {i} counter is auto-set on every iteration.

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 ==.

WAIT FOR SELECTOR 'css' waits for a CSS selector to appear (useful for custom elements like ytd-video-renderer that have no stable visible text). The WAIT FOR 'target' TO BE VISIBLE form also accepts CSS selectors — if the quoted target looks like a CSS selector it routes to page.wait_for_selector() automatically.

Page scanning for LLM agents

FULL SCAN

FULL SCAN groups every interactive control on the page by its nearest semantic landmark (form, nav, header, dialog, section …) and prints a Markdown table per group. Designed for LLM-driven automation — an LLM can paste the output directly into its context window to decide which element to interact with next. Shadow DOM trees are traversed recursively, so controls inside custom elements (e.g. <ytd-*>, <mwc-*>) appear under a [shadow]-suffixed group.

Example output:

## Form: Login
| role       | label                            | locator                              | tag      | editable |
|------------|----------------------------------|--------------------------------------|----------|----------|
| textbox    | Email                            | #email                               | input    | yes      |
| textbox    | Password                         | #password                            | input    | yes      |
| button     | Sign In                          | text=Sign In                         | button   | no       |

## Navigation
| role       | label                            | locator                              | tag      | editable |
|------------|----------------------------------|--------------------------------------|----------|----------|
| link       | Home                             | text=Home                            | a        | no       |
| link       | About                            | text=About                           | a        | no       |

SCAN PAGE remains available for generating draft .hunt files from a live page.

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

  • Conditional branching & loopsIF / ELIF / ELSE for adaptive flows; REPEAT, FOR EACH, WHILE for iterating data, retrying actions, and polling dynamic state. Full nesting support.
  • 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. Handlers receive a typed ControlContext (ctx.page / ctx.action / ctx.value / ctx.target / ctx.page_name / ctx.url / ctx.step); manul controls list shows the registry; misses against a sibling page print a one-line hint.
  • 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.32 runs headless in CI with dumb-init, non-root user, and MANUL_* env overrides.

Quickstart

Install

pip install manul-engine==0.0.9.32
playwright install

Optional local AI fallback (not required):

pip install "manul-engine[ai]==0.0.9.32"
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.32 \
  --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

Component Role Links
ManulEngine Deterministic automation runtime (Python). Heuristic element resolver, .hunt DSL, CLI runner. PyPI · GitHub
Manul Engine Extension VS Code extension for ManulEngine with debug panel, explain mode, and Test Explorer integration. Marketplace · Open VSX · GitHub
ManulMcpServer MCP bridge that gives Copilot Chat and other agents access to ManulEngine. Marketplace · Open VSX · GitHub
ManulAI Local Agent Autonomous AI agent for browser automation, powered by ManulEngine. Marketplace · Open VSX · GitHub

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.32

  • FULL SCAN DSL step: groups every interactive control on the page by its nearest semantic landmark ancestor (<form>, <nav>, <header>, <footer>, <dialog>, <section>, ARIA roles) and prints a compact Markdown table per group. Designed for LLM-driven automation — paste the output into an LLM context window to decide what to interact with next. Shadow DOM trees are traversed recursively; controls inside custom elements appear under a [shadow]-suffixed group.
  • WAIT FOR SELECTOR '<css>' DSL step: explicit CSS-selector wait via page.wait_for_selector(). Solves the SPA / YouTube use case where there is no stable visible text, only a DOM tag (ytd-video-renderer, mwc-button, etc.).
  • CSS-aware WAIT FOR '…' TO BE VISIBLE: the existing step now auto-detects CSS selectors (starts with #, ., [, contains -, >, or :) and routes to page.wait_for_selector() instead of get_by_text(). Plain-text targets are unchanged.
v0.0.9.31
  • Page registry split into pages/ directory (BREAKING): page mappings now live as one JSON fragment per site under <project>/pages/<safe_netloc>.json. Run manul pages migrate once to split any pre-existing pages.json.
  • ControlContext API for @custom_control (BREAKING): handlers now accept a single ControlContext argument exposing page, action, value, target, page_name, url, and step. Replace async def fn(page, action_type, value) with async def fn(ctx: ControlContext).
  • manul pages list / manul pages migrate / manul controls list CLI commands.
  • Custom Controls miss-diagnostics and visible dispatch log without --debug.
v0.0.9.30
  • Loop constructs (REPEAT / FOR EACH / WHILE): iterative execution blocks. REPEAT N TIMES: for fixed counts, FOR EACH {var} IN {collection}: for data iteration, WHILE <condition>: for dynamic polling. Full nesting with conditionals, {i} auto-counter, WHILE safety limit (100 iterations). 129-assertion test suite.
  • Complete user guide — new docs/ folder with structured documentation.

License

Version: 0.0.9.32

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.32.tar.gz (184.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.32-py3-none-any.whl (190.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: manul_engine-0.0.9.32.tar.gz
  • Upload date:
  • Size: 184.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.32.tar.gz
Algorithm Hash digest
SHA256 b6a62baad4710a9d12f9871aba22ee6d286bc6267345d93ce9777f4e0dee2026
MD5 c5e1d78c6594a55ca9eb05495a01f41f
BLAKE2b-256 66bb86aeb8e136428561f56a678588927974d5f5392ca3e0a316d349935ba0f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for manul_engine-0.0.9.32.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.32-py3-none-any.whl.

File metadata

  • Download URL: manul_engine-0.0.9.32-py3-none-any.whl
  • Upload date:
  • Size: 190.5 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.32-py3-none-any.whl
Algorithm Hash digest
SHA256 699f5becf182da4c38c4ca49d8062866925d0764abd81f68682462baad87dc2c
MD5 308222ee6a07322c36e323efde12c7c0
BLAKE2b-256 706bc3524a66931a09dcda37ee320ebcbd7b2d0d304438ff7cab222fdf3c1d1f

See more details on using hashes here.

Provenance

The following attestation bundles were made for manul_engine-0.0.9.32-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