Deterministic DSL-first browser automation platform powered by Playwright heuristics with optional local AI self-healing (Ollama)
Project description
ManulEngine
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, andclickall work. The canonical form used in documentation and generated files is ALL UPPERCASE.Element type hints are optional. Words like
button,link,field,dropdownplaced after the target outside quotes are not required, but they provide a strong heuristic signal that boosts scoring accuracy.CLICK the 'Login' buttonandCLICK 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 ==.
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 & loops —
IF/ELIF/ELSEfor adaptive flows;REPEAT,FOR EACH,WHILEfor iterating data, retrying actions, and polling dynamic state. Full nesting support. - Explainability —
--explainprints 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
--debugpause, typewto 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_pathin the config and useOPEN APPinstead ofNAVIGATEto 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_groupinmanul_hooks.pyfor suite-wide setup and teardown. - HTML reports —
--html-reportgenerates a self-contained dark-themed report with accordions, screenshots, tag filters, and run-session merging across CLI invocations. - Docker CI runner —
ghcr.io/alexbeatnik/manul-engine:0.0.9.29runs headless in CI withdumb-init, non-root user, andMANUL_*env overrides.
Quickstart
Install
pip install manul-engine==0.0.9.29
playwright install
Optional local AI fallback (not required):
pip install "manul-engine[ai]==0.0.9.29"
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.29 \
--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:
- Try it:
pip install manul-engine==0.0.9.29 && playwright install - File issues: github.com/alexbeatnik/ManulEngine/issues
What's New in v0.0.9.29
- Loop constructs (
REPEAT/FOR EACH/WHILE): Iterative execution blocks in.huntfiles.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), empty body validation. 129-assertion test suite. - Complete user guide — new
docs/folder with structured documentation: overview, installation, getting started, full DSL syntax reference, reports & explainability, and integration guides.
v0.0.9.28
- Conditional branching (
IF/ELIF/ELSE): Block-style branching in.huntfiles based on element presence, visible text, or variable state. Indentation-based body detection, nesting support, andConditionalSyntaxErrorfor malformed blocks. 97-assertion test suite. - What-If Analysis REPL (
ExplainNextDebugger): Interactive debug REPL for hypothetical step evaluation. During a debug pause, typew(terminal) to enter the REPL ore/ sendexplain-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 passesstrategic_contextandstep_idxby keyword. Injected What-If steps run throughsubstitute_memory()so{var}placeholders resolve before execution. - LLM JSON fence-stripping:
_parse_llm_json()now strips markdown code fences before JSON parsing.
License
Version: 0.0.9.29
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file manul_engine-0.0.9.29.tar.gz.
File metadata
- Download URL: manul_engine-0.0.9.29.tar.gz
- Upload date:
- Size: 176.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57e0c921afd15e23a3834397f97355a33abb9067d9a9f5674033ef03f7426362
|
|
| MD5 |
49c0cd552258d3494c6671145c9c2252
|
|
| BLAKE2b-256 |
23fff341ec367357c0c6aceeafaaa0ff53b260e10f0cf48609c78791a680e83c
|
Provenance
The following attestation bundles were made for manul_engine-0.0.9.29.tar.gz:
Publisher:
release.yml on alexbeatnik/ManulEngine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
manul_engine-0.0.9.29.tar.gz -
Subject digest:
57e0c921afd15e23a3834397f97355a33abb9067d9a9f5674033ef03f7426362 - Sigstore transparency entry: 1298860283
- Sigstore integration time:
-
Permalink:
alexbeatnik/ManulEngine@2ee68ebc601e1c77e35c4c24b094b4fd8deb73f1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alexbeatnik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2ee68ebc601e1c77e35c4c24b094b4fd8deb73f1 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file manul_engine-0.0.9.29-py3-none-any.whl.
File metadata
- Download URL: manul_engine-0.0.9.29-py3-none-any.whl
- Upload date:
- Size: 183.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bca27c7b937850f4a8efdd3e916a00adb37de6838be145e4253f5aa478d37f0
|
|
| MD5 |
4be293650eda6c44e832e50af7b6c911
|
|
| BLAKE2b-256 |
841bcf70f8980cab4edaf9fc588a3e7422f21fe25cc1f0171b9bea34fbbc3463
|
Provenance
The following attestation bundles were made for manul_engine-0.0.9.29-py3-none-any.whl:
Publisher:
release.yml on alexbeatnik/ManulEngine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
manul_engine-0.0.9.29-py3-none-any.whl -
Subject digest:
3bca27c7b937850f4a8efdd3e916a00adb37de6838be145e4253f5aa478d37f0 - Sigstore transparency entry: 1298860360
- Sigstore integration time:
-
Permalink:
alexbeatnik/ManulEngine@2ee68ebc601e1c77e35c4c24b094b4fd8deb73f1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alexbeatnik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2ee68ebc601e1c77e35c4c24b094b4fd8deb73f1 -
Trigger Event:
workflow_dispatch
-
Statement type: