Skip to main content

Optics Framework

Self-healing test automation for mobile, web, TV — and AI agents.

One keyword engine. Six ways to drive it: CSV/YAML files, a Python SDK, Robot Framework, a REST API, an interactive terminal, or an MCP server your AI agent talks to.

PyPI Python License PyPI Downloads Quality Gate Status Coverage OpenSSF Best Practices

Optics demo

Documentation · Install · Getting Started · Keywords · Architecture


Most frameworks assume a UI element has one true locator, and a test breaks the moment that locator changes. Optics assumes an element has several plausible identities — its XPath, its visible text, what it looks like on screen — and tries all of them before giving up.

That idea runs through the whole framework: locators fall back, drivers fall back, element values fall back. Tests are data (CSV or YAML), so non-coders can write them, and the same keywords are reachable from Python, Robot Framework, HTTP, and MCP.

Why Optics

A locator ladder, not a locator. Every element-based keyword walks a priority-ordered chain until one strategy succeeds:

# Strategy How it finds the element
1 XPathStrategy Native XPath query through the driver's accessibility tree
2 TextElementStrategy Direct text / CSS / class lookup through the element source
3 TextDetectionStrategy Screenshot → OCR (EasyOCR, Pytesseract, Google Vision, remote OCR)
4 ImageDetectionStrategy Screenshot → template matching against a reference PNG
5 AI self-heal (opt-in) All four failed → an LLM reads the screen and recovers

Cheap strategies run first, so vision only costs you time when the tree can't help. Steps 1–4 are LocatorStrategy registrations; step 5 is a separate recovery layer, bounded to five turns and a six-keyword allowlist so it re-enters the ladder rather than tapping blind coordinates. Two more fallback axes sit alongside: multiple values per element name, and multiple enabled drivers or element sources, each tried in config order.

Beyond the ladder:

  • Tests are data — elements, modules and test cases as plain CSV or YAML. No IDE, no programming.
  • Targets — Android, iOS, web (Selenium/Playwright), Android TV, Samsung Tizen, LG webOS.
  • Non-intrusive — the ble driver drives production devices as a Bluetooth HID mouse/keyboard where debugging and screenshots are blocked. Coordinate-only, so pair it with camera_screenshot and the vision strategies.
  • Agent-ready — optics mcp exposes every keyword as a typed MCP tool and device state as MCP resources.

Install

Optics needs Python 3.12+. The core install ships no drivers, OCR, or LLM backends — you add only what you need:

python3 -m venv venv && source venv/bin/activate
pip install "optics-framework[appium,easyocr]"

Most extra names match the config.yaml source keys, so the word you install is usually the word you enable (exceptions: llm enables the gemini engine, google-vision enables google_vision, and the bundles and mcp aren't config keys):

Drivers appium · selenium · playwright · ble
OCR easyocr · pytesseract · google-vision
AI llm (natural-language mode + self-heal) · mcp (MCP server)
Bundles mobile · web · vision · all

optics setup installs the drivers, OCR and LLM engines above (and the bundles) by name — pinned to your installed Optics version — and bare optics setup opens a TUI picker. optics quickstart goes one step further and offers the right extras for the platform you pick:

optics setup --list
optics setup --install appium easyocr

Quickstart

From a fresh install to a running test with one guided command:

pip install optics-framework
optics quickstart

optics quickstart asks what you want to automate, installs the matching engine, scaffolds the project, writes a platform-correct config.yaml, and runs an optics doctor check — then prints your next steps. Every prompt has a default, so pressing Enter throughout yields a runnable project.

Prefer to drive each step yourself?

optics init my_test_project --template contact
# point my_test_project/config.yaml at your device/app, start the Appium server, then:
optics dry_run my_test_project    # validate keywords, elements and module refs — no device (but the config's engines must be installed)
optics execute my_test_project

--template scaffolds a working project from a bundled sample. Each needs the matching extras installed (see Install); the headline [appium,easyocr] above covers the first three:

  • contact · calendar · youtube — Appium/Android, easyocr → [appium,easyocr]
  • clock — Appium/Android, image templates + Tesseract OCR → [appium,pytesseract] (plus the Tesseract binary)
  • gmail_web — Selenium → [selenium,easyocr]
  • playwright — Playwright → [playwright] (then playwright install for the browsers)

Omit --template for an empty scaffold with a commented starter config.yaml. Need only a config for an existing folder? optics configure <folder> writes one from a few questions, and optics doctor [folder] [--check] verifies engines, tooling, and project config.

Write a test as data

my_test_project/
├── config.yaml
├── test_cases/test_cases.csv
├── modules/modules.csv
└── test_data/
    ├── elements.csv
    ├── error_definitions.csv      # optional
    └── input_templates/*.png      # optional, for image matching

Optics discovers these files by their content (CSV headers / YAML top-level keys), not their path — the folder names above are a convention, so elements.csv works equally under test_data/ or elements/ (the folder optics live's /save writes to).

test_data/elements.csv — names mapped to locators. Doubles as a general variable store; repeating a name builds a fallback list.

Element_Name,Element_ID
Add_Contact_Button,//android.widget.Button[@content-desc="Create contact"]
First_Name_element,//android.widget.EditText[@text="First name"]
Save_Button,Save
First_Name,John

A locator can be an XPath, text=…, css=…, a plain string, an image filename from input_templates/, or TEXT_ONLY:… to force a vision-based search.

modules/modules.csv — a reusable sequence of keywords; ${name} resolves against elements.csv.

module_name,module_step,param_1,param_2
Add Contact,Press Element,${Add_Contact_Button}
Add Contact,Enter Text,${First_Name_element},${First_Name}
Add Contact,Press Element,${Save_Button}

test_cases/test_cases.csv — modules sequenced into scenarios. A test case whose name contains suite + setup (or teardown) is hoisted to run around the whole suite.

test_case,test_step
Suite Setup,Launch Contact Application
Add Contact with Contact App,Add Contact
Add Contact with Contact App,Verify Contact is Added

Six ways to run the same keywords

Surface Command / import Best for
CLI runner optics execute <project> CI suites written as CSV/YAML
Interactive TUI optics live [project] Building a test by doing it — recording is always on, Ctrl-N toggles natural-language mode
Python SDK from optics_framework import Optics Custom logic, embedding in existing suites
Robot Framework Library optics_framework.optics.Optics Teams already on Robot
REST API optics serve Remote/orchestrated execution, live workspace streaming over SSE
MCP server optics mcp Letting an AI agent drive a real device
optics live — turning a session into a reusable module

Every successful keyword is buffered as you work. To persist the buffer:

/save <test_case> <module_name>

That appends the recorded keywords to modules/modules.csv as <module_name>, adds a (<test_case>, <module_name>) row to test_cases/test_cases.csv, creates a header-only elements/elements.csv stub if none exists, and copies the session's screenshots to execution_output/<module_name>/. The buffer then clears, so the next actions become the next module. If either name already exists, re-run the identical /save to confirm the append.

Other commands: /device [id], /elements, /screenshot, /help, /quit. Full reference: Live Usage.

Python SDK example
from optics_framework import Optics

optics = Optics()
optics.setup(
    driver_sources=[{"appium": {"enabled": True, "url": "http://localhost:4723"}}],
    elements_sources=[{"appium_find_element": {"enabled": True}}],
)

optics.launch_app("com.example.app")
optics.enter_text("username_field", "testuser")
optics.press_element("submit_button")
optics.validate_element("welcome_message")
optics.quit()
MCP client config
{ "mcpServers": { "optics": { "command": "optics", "args": ["mcp"] } } }

Then: start_session → observe (screenshot, optics://session/{id}/source) → act (press_element, enter_text, …) → terminate_session. For networked use: optics mcp --transport http --port 8090. Sessions are not shared with optics serve — each is its own process.

Keywords

Every public method on the four API classes is automatically a keyword, on every surface above. CSV/YAML uses Title Case (Press Element → press_element).

Category Keywords
Actions Press Element · Press By Percentage · Press By Coordinates · Detect And Press · Select Dropdown Option · Swipe · Swipe By Percentage · Swipe From Element · Swipe Until Element Appears · Scroll · Scroll From Element · Scroll Until Element Appears · Enter Text · Enter Text Direct · Enter Text Using Keyboard · Enter Number · Clear Element Text · Press Keycode · Get Text · Sleep · Execute Script
Verification Assert Presence · Assert Visibility · Assert Equality · Validate Element · Validate Screen · Is Element · Get Interactive Elements · Get Screen Elements · Capture Screenshot · Capture Pagesource
App lifecycle Launch App · Launch Other App · Start Appium Session · Get Driver Session Id · Close And Terminate App · Force Terminate App · Get App Version
Flow control Run Loop · Condition · Read Data · Evaluate · Date Evaluate · Invoke API

Run optics list for the live catalogue with signatures, or read the Keyword Usage guide for parameters and examples. Location keywords accept percentage-based Area-of-Interest bounds (aoi_x/y/width/height, 0–100) to scope a vision search to part of the screen.

Configure once, in config.yaml

Every section is a priority-ordered list and every entry has an enabled flag. Enable a second driver and it becomes a fallback. Prefer not to hand-write it? optics configure <folder> writes one from a few questions, or optics quickstart builds the whole project including the config.

driver_sources:
  - appium:
      enabled: true
      url: "http://localhost:4723"
      capabilities:
        platformName: Android
        automationName: UiAutomator2
        deviceName: emulator-5554
        appPackage: com.google.android.contacts
        appActivity: com.android.contacts.activities.PeopleActivity

elements_sources:
  - appium_find_element: { enabled: true }
  - appium_page_source:  { enabled: true }
  - appium_screenshot:   { enabled: true }

text_detection:
  - easyocr: { enabled: true }

image_detection:
  - templatematch: { enabled: false }

log_level: INFO
Layer Available engines
Drivers appium (Android, iOS, Android TV, Tizen, webOS) · selenium · playwright · ble
Element sources appium_find_element · appium_page_source · appium_screenshot · selenium_* · playwright_* · camera_screenshot
Text detection easyocr · pytesseract · google_vision · remote_ocr
Image detection templatematch · remote_oir
LLM gemini

Enabling the LLM features

The llm_models block powers both natural-language mode in optics live (Ctrl-N) and AI self-heal. Install the extra (pip install "optics-framework[llm]"), then add:

llm_models:
  - gemini:
      enabled: true
      capabilities:
        model: gemini-2.5-flash    # optional; this is the default
        # use_vertexai: true       # optional; else read from the environment
        # project: my-gcp-project  # optional (Vertex)
        # location: us-east4       # optional (Vertex)

ai_self_heal: true                 # opt into the LLM backstop; default false

Credentials are read from the environment by the google-genai SDK — GEMINI_API_KEY (or GOOGLE_API_KEY) for the Gemini Developer API, or GOOGLE_GENAI_USE_VERTEXAI + GOOGLE_CLOUD_PROJECT / GOOGLE_CLOUD_LOCATION / GOOGLE_APPLICATION_CREDENTIALS for Vertex AI. Never commit keys to config.yaml. With every capabilities key omitted the SDK auto-detects the backend. google-genai is imported only when gemini is enabled, and a misconfigured LLM degrades to "no self-heal" rather than a hard failure.

Full reference: Configuration. Adding your own engine is a file drop plus an interface — see Extending the Framework.

Results

An optics execute run writes to <project>/execution_output/:

  • junit_output.xml — written incrementally, so CI sees progress as it happens
  • logs.json — structured logs when json_log: true
  • screenshots — pre/post action frames, plus strategy-annotated and AOI overlays
  • detected_errors_<session_id>.json — on-screen error detection

Drop an error_definitions.csv into test_data/ and Optics scans visible text for crash dialogs, Session expired, network errors, and the like — no assertions required. Matches also land in the JUnit XML as a synthetic failing testcase, so CI fails a build on "the app crashed mid-test" the same way it fails a normal assertion. See Error Detection.

CLI reference

optics init        Scaffold a new project (positional NAME; --template, --path, --force, --git-init)
optics quickstart  Guided walkthrough: engines + project + config + doctor check
optics configure   Write/edit a project's config.yaml (guided prompts; --edit opens a TUI)
optics doctor      Diagnose engines, tooling and a project's config (--check for CI)
optics setup       Install engine backends (--list, --install); bare command opens a TUI
optics dry_run     Validate a project without touching a device
optics execute     Run a project (--runner test_runner|pytest)
optics live        Interactive keyword session against a live target
optics generate    Emit pytest or Robot Framework code from a project
optics list        Print every discoverable keyword
optics serve       Start the REST API server (--host, --port, --workers)
optics mcp         Start the MCP server (--transport stdio|http)
optics completion  Install shell autocompletion
optics --version   Print the installed version

Details in the CLI guide.

Contributing

Contributions welcome — bug reports, docs, and code are all first-class.

The contribution guide covers development setup, step-by-step recipes (adding a keyword or engine backend), commit conventions, and the pull-request format reviewers expect. Tooling details live in the Developer Guide; behaviour expectations in our Code of Conduct.

git clone git@github.com:mozarkai/optics-framework.git
cd optics-framework
pipx install poetry
poetry install --with dev,test,docs

poetry run pytest                    # tests + coverage
poetry run ruff check --fix .        # lint
poetry run pre-commit run --all-files
poetry run mkdocs serve              # docs preview

Commits follow Conventional Commits, enforced by commitizen in the commit-msg hook.

Security issues follow SECURITY.md rather than public issues.

License & support

Apache 2.0 — see LICENSE.

Questions and bugs: GitHub Issues. Anything else: lalit@mozark.ai.

Built by Mozark AI.

Metadata

Release files for optics-framework 1.10.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for optics-framework 1.10.2
File Size Uploaded
optics_framework-1.10.2.tar.gz 447.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for optics-framework 1.10.2
File Interpreter ABI Platform
optics_framework-1.10.2-py3-none-any.whl Python 3 none any Details

Total release size: 1.0 MB

Release files / optics_framework-1.10.2.tar.gz

Download URL optics_framework-1.10.2.tar.gz
Size 447.7 kB
Tags Source
SHA-256 checksum
How to use checksums
39c41004591cc67506a30fb5ca17c3d1e94e98a52a0ac3d19ce7fc611716edc6
BLAKE2b-256 checksum
How to use checksums
976a3208ad37c55f284901fbe44c4b02c0bf76e29cd97714c9b6ae5be70ad006
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.

Transparency log

Release files / optics_framework-1.10.2-py3-none-any.whl

Download URL optics_framework-1.10.2-py3-none-any.whl
Size 582.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bd15b6249bbde78c29438c523f6d0d65df23480b738c3dd47729435b2966c0ba
BLAKE2b-256 checksum
How to use checksums
4491dcd2f6f15e8934cde65382bef38e91de1a7e30d9f2ed470926c070dd4d90
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.

Transparency log

Release history Release notifications | RSS feed

1.11.0

2 release files

1.10.6

2 release files

1.10.4

2 release files

This release

1.10.2 This release

2 release files

1.10.0

2 release files

1.9.3

2 release files

1.9.2

2 release files

1.9.1

2 release files

1.9.0

2 release files

1.8.6

2 release files

1.8.5

2 release files

1.8.4

2 release files

1.8.3

2 release files

1.8.2

2 release files

1.8.1

2 release files

1.8.0

2 release files

1.7.16

2 release files

1.7.15

2 release files

1.7.14

2 release files

1.7.13

2 release files

1.7.10

2 release files

1.7.9

2 release files

1.7.8

2 release files

1.7.7

2 release files

1.7.6

2 release files

1.7.5

2 release files

1.7.4

2 release files

1.7.3

2 release files

1.7.2

2 release files

1.7.1

2 release files

1.7.0

2 release files

1.6.6

2 release files

1.6.5

2 release files

1.6.4

2 release files

1.6.3

2 release files

1.6.2

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.6

2 release files

1.5.4

2 release files

1.5.3

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.38

2 release files

1.3.36

2 release files

1.3.35

2 release files

1.3.34

2 release files

1.3.33

2 release files

1.3.32

2 release files

1.3.31

2 release files

1.3.30

2 release files

1.3.29

2 release files

1.3.28

2 release files

1.3.27

2 release files

1.3.26

2 release files

1.3.25

2 release files

1.3.24

2 release files

1.3.23

2 release files

1.3.19

2 release files

1.3.18

2 release files

1.3.17

2 release files

1.3.16

2 release files

1.3.15

2 release files

1.3.14

2 release files

1.3.13

2 release files

1.3.12

2 release files

1.3.11

2 release files

1.3.10

2 release files

1.3.9

2 release files

1.3.8

2 release files

1.3.7

2 release files

1.3.6

2 release files

1.3.5

2 release files

1.3.4

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page