Skip to main content

AI automation bolt-on for any framework

Project description

Qirabot Python SDK

AI-powered automation SDK that bolts onto your existing browser/mobile automation framework. Let AI see the screen, click, type, extract data, and verify results — with any framework you already use.

Use it two ways: as a Python library (let qirabot launch a Playwright browser for you via bot.open(), or bolt onto a Playwright / Selenium / Appium / Airtest / pyautogui session you already drive), or inside your pytest suite.

Installation

pip install qirabot

Requires Python 3.10+.

The core package has no automation engine of its own — install the extra for the framework you'll drive:

pip install "qirabot[browser]"   # Playwright (needed for bot.open())
pip install "qirabot[desktop]"   # pyautogui (native desktop apps)
pip install "qirabot[appium]"    # Appium (Android / iOS)
pip install "qirabot[airtest]"   # Airtest (Android / iOS / Windows, image-based)

pip install selenium             # Selenium is not an extra — bring your own driver

The airtest extra pulls in Airtest, which pins numpy<2.0 and opencv-contrib-python 4.4–4.6. Installing it into an env that already has numpy>=2 may downgrade or conflict — prefer a dedicated virtualenv.

The Quick Start below uses bot.open(), so it needs qirabot[browser] plus a one-time playwright install chromium. With Selenium you create the driver yourself and pass it to qirabot — see examples/selenium/.

Configuration

export QIRA_API_KEY="qk_your_api_key"
from qirabot import Qirabot

bot = Qirabot()  # reads QIRA_API_KEY from environment

Constructor options:

Parameter Env Variable Default Description
api_key QIRA_API_KEY API key for authentication
base_url QIRA_BASE_URL https://app.qirabot.com API server URL
timeout 120.0 HTTP request timeout (seconds)
model_alias "" Default model alias for all operations
language "" Default response language
task_name "" Optional name for the task (visible in dashboard)
report True Write an HTML run report (+ screenshots) on close
report_dir QIRA_REPORT_DIR "" Output root; default ./qira_runs/<date>/<time-id>/
screenshot_annotate True Draw a red crosshair at click/type coordinates
screenshot_format "jpeg" Saved screenshot format ("jpeg" or "png")
screenshot_quality 80 JPEG quality, 1–100
retry 1 Retries per action on transient failures
retry_delay 1.0 Seconds between retries

Model & language

model_alias selects which model backs every operation. The built-in aliases are fast, balanced (the default), and high_quality — trading cost for quality. Check your dashboard for the live list your account can use, then pass the name as model_alias; leave it empty for the default:

bot = Qirabot(model_alias="high_quality")        # applies to all actions
bot.click(page, "Login", model_alias="fast")     # or override per call

language sets the language of AI responses (extracted text, reasoning). It's a short language tag like "zh" or "en" — empty means the server default:

bot = Qirabot(language="zh")                      # extract/ai answers in Chinese
text = bot.extract(page, "获取主标题", language="zh")

Quick Start

This uses bot.open(), so install the browser extra and Chromium first:

pip install "qirabot[browser]"
playwright install chromium
from qirabot import Qirabot

bot = Qirabot()
page = bot.open("https://google.com")

bot.type_text(page, "Search input", "SpaceX", press_enter=True)

summary = bot.extract(page, "Get the first search result title")
print(f"Result: {summary}")

bot.close()

Bind a target (optional)

Every action takes the framework object (page / driver / device / module) as its first argument: bot.click(target, "Login"). When you drive a single, stable target for the whole session, call bot.bind(target) once to get a drop-in proxy that drops the repeated first argument:

bot = Qirabot().bind(driver)     # Selenium/Appium driver, pyautogui, Airtest G/device
bot.click("Login")
bot.type_text("Email", "a@b.com")
with Qirabot().bind(driver) as bot:   # works as a context manager too
    ...

bind() is recommended for Airtest, pyautogui, Appium, Selenium. For Playwright keep the explicit form page = bot.click(page, ...) so new-tab follows stay visible (a click can open a new tab; the returned page is the one your native page.fill(...) calls should use). With a bound proxy, reach the live page via bot.current_page().

Examples

Runnable examples live in examples/, in two styles:

See examples/README.md for which to pick.

API Reference

Simple Actions

These actions use lightweight vision-based element location — fast and low-cost:

# Click on an element by description
bot.click(page, "Login button")

# Auto-wait: poll until the element looks present (up to timeout) before
# clicking, else raise QirabotTimeoutError. Works on every framework.
# `wait` overrides the auto-derived assertion. (Also on type_text/double_click.)
bot.click(page, "Login button", timeout=15.0, interval=2.0)

# Type text into an input field
bot.type_text(page, "Email input", "user@example.com")

# Extract data from the screen
text = bot.extract(page, "Get the main heading")

# Verify a visual assertion (returns True/False, never raises)
ok = bot.verify(page, "The success message is visible")

# Wait for a condition (acts as a gate): returns when met, else raises
# QirabotTimeoutError. Use verify() for a non-raising bool check.
bot.wait_for(page, "Page has finished loading", timeout=15.0, interval=2.0)

click, type_text, and double_click return the current target (the same kind you passed in). When an action opens a link in a new tab, the return value is that new tab, so reassign it to keep operating on the active page:

page = bot.click(page, "Open the first video")  # may switch to a new tab

Multi-Step AI (bot.ai())

Let AI autonomously complete a complex task using the full decision engine:

from qirabot import Qirabot, StepResult

bot = Qirabot()
page = bot.open("https://www.google.com")

def on_step(step: StepResult) -> None:
    status = "done" if step.finished else step.action_type
    print(f"  Step {step.step}: {status} {step.params}")

result = bot.ai(
    page,
    "Search for 'best python libraries 2026', click the first result, and extract the main content",
    max_steps=10,
    on_step=on_step,
)

print(f"Success: {result.success}")
print(f"Output: {result.output}")
bot.close()

Screenshot (No AI)

Saves to report_dir/screenshots/ and returns the saved path (or None when report=False):

path = bot.screenshot(page)
print(f"saved to {path}")

Navigation & Scrolling (No AI)

Direct, non-billed actions that don't need AI element location. go_back, navigate, and close_tab return the current page/target (may differ after the navigation); scroll returns None.

bot.navigate(page, "example.com")   # scheme optional; "https://" prepended
bot.go_back(page)                   # back to the previous page (smart, see below)
page = bot.close_tab(page)          # close current tab, return to previous tab
bot.scroll(page, "down", 3)         # scroll at viewport center
bot.scroll(page, "up", distance=5, x=640, y=400)  # scroll at a point

Smart go_back (Playwright): if the current page has back history it goes back in place; if it doesn't — e.g. a click opened a link in a new tab, which starts with no history — and another tab is open, it closes the current tab and returns to the previous one. So the common "click opens a video in a new tab, then go back to the list" loop just works:

for i in range(4):
    page = bot.click(page, locate=f"open video {i + 1}")  # opens a new tab
    bot.screenshot(page)
    page = bot.go_back(page)                               # closes it, back to the list

Reach for close_tab directly when you want to force-close the current tab regardless of history.

Platform support (all actions):

Action Playwright Selenium Appium (mobile) pyautogui (desktop) Airtest
click
double_click ✅ ᵃ ✅ ᵃ
right_click = tap ᵇ Windows / = tap ᵇ
hover no-op ᶜ no-op ᶜ
type_text
clear_text Android ᵈ
press_key ✅ ᵉ
scroll
drag
navigate
go_back Android
close_tab
screenshot

AI-located actions (click, type_text, double_click) and the AI operations (extract, verify, wait_for, ai) work on every framework — the matrix shows how each underlying action maps per platform.

  • ᵃ Appium/Airtest emulate double_click as two quick taps.
  • ᵇ Mobile has no right-click: Appium taps; Airtest right-clicks on Windows only, taps elsewhere.
  • ᶜ Touch targets have no hover: Appium/Airtest treat hover as a no-op.
  • ᵈ Airtest has no element model; clear_text is best-effort on Android (caret-to-end + repeated delete).
  • ᵉ Airtest key names are Android-first (adb); Windows (pywinauto) / iOS use different names.

navigate/go_back raise NotImplementedError where unsupported (pyautogui has no browser-style navigation; Airtest has no URL concept). close_tab is Playwright-only (other targets raise NotImplementedError); the new-tab fallback inside go_back therefore applies to Playwright only — on Selenium/Appium go_back is always history-back, and on Airtest it maps to keyevent("BACK") (Android only; iOS/Windows raise).

Launch a Desktop App (No AI)

pyautogui can drive the mouse and keyboard but cannot open an application. launch_app shells out to the OS so desktop runs can start from a known app:

import pyautogui
from qirabot import Qirabot, launch_app

bot = Qirabot(task_name="wechat")

bot.launch_app("WeChat")              # macOS app name (or bundle id "com.tencent.xinWeChat")
# launch_app("notepad")              # Windows: exe path, registered name, or UWP AppUserModelID
# launch_app("/path/to/app", wait=3) # wait seconds for the window to appear (default 2)

bot.ai(pyautogui, "Send 'hello' to honey in WeChat")

launch_app is also available standalone (from qirabot import launch_app). On macOS it uses open -a/open -b (activating an already-running app), on Windows os.startfile/start/explorer.exe shell:AppsFolder, on Linux the executable directly.

Reports

By default every run writes a self-contained HTML report (with per-step screenshots) when the bot closes — including on error or Ctrl+C, so you can see where it stopped. No model calls, no network; it's built from data captured during the run.

# Default: report on, written to ./qira_runs/<date>/<time-id>/
bot = Qirabot(task_name="checkout")

# Custom output root (date/run subdirs are still added automatically)
bot = Qirabot(report_dir="./artifacts")        # or export QIRA_REPORT_DIR=./artifacts

# Turn it off entirely (nothing written to disk) — e.g. CI / library use
bot = Qirabot(report=False)

Output layout per run:

qira_runs/2026-06-07/192335-3f9ab2c1/
  report.html          # self-contained: embedded thumbnails + PASS/FAIL per ai() task
  screenshots/         # full-resolution frames (click a thumbnail to open)
    001_click.jpg
    002_type_text.jpg
    ...
  recording.mp4        # embedded in the report if you record into report_dir

screenshot_annotate=True (default) draws a red crosshair at the resolved click/type coordinates. To embed a screen recording, point your recorder at bot.report_dir:

dev.start_recording(output=os.path.join(bot.report_dir, "recording.mp4"))

Call bot.report("path.html") to also write the report to a custom location on demand. Use bot.screenshot(target) for a one-off frame (saved under report_dir/screenshots/).

Bolt-On to Any Framework

Qirabot works with your existing automation setup — just pass your page/driver/device object:

Playwright

from playwright.sync_api import sync_playwright
from qirabot import Qirabot

bot = Qirabot()

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://github.com/trending")

    # Mix playwright selectors with AI
    repos = bot.extract(page, "Get the top 5 trending repo names")
    print(repos)

    browser.close()
bot.close()

Selenium

from selenium import webdriver
from qirabot import Qirabot

driver = webdriver.Chrome()
driver.get("https://www.wikipedia.org")
bot = Qirabot().bind(driver)   # bind once; the driver is stable for the session

summary = bot.extract("Get the first paragraph of the article")
print(summary)

driver.quit()
bot.close()

Android (Appium)

from appium import webdriver
from appium.options.android import UiAutomator2Options
from qirabot import Qirabot

options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "emulator-5554"
options.app_package = "com.android.settings"
options.app_activity = ".Settings"
driver = webdriver.Remote("http://localhost:4723", options=options)
bot = Qirabot().bind(driver)

bot.click("Wi-Fi settings")
result = bot.ai("Open Display settings and change font size to Large")
print(f"Success: {result.success}")
bot.close()
driver.quit()

Android / iOS / Windows (Airtest)

Airtest connects to the device itself (no Appium server). G resolves the current device, so bind(G) keeps your usual Airtest style and adds AI on top.

from airtest.core.api import *       # your usual Airtest imports
from qirabot import Qirabot

auto_setup(__file__)                 # your usual Airtest setup, unchanged
bot = Qirabot().bind(G)

bot.click("Login button")            # AI-located — replaces brittle Template images
result = bot.ai("Open Settings and turn on dark mode")
print(f"Success: {result.success}")
touch(Template("native.png"))        # native Airtest still works side by side
bot.close()

Trade-offs and capability notes (e.g. navigate unsupported, go_back Android-only) are in examples/airtest/. You can also pass G, the airtest.core.api module, or an explicit connect_device(...) handle directly without bind().

Error Handling

from qirabot import (
    Qirabot,
    QirabotError,
    AuthenticationError,
    InsufficientBalanceError,
    QirabotTimeoutError,
)

try:
    bot = Qirabot()
    page = bot.open("https://example.com")
    bot.click(page, "Login button")
except AuthenticationError:
    print("Invalid API key.")
except InsufficientBalanceError:
    print("No credits left.")
except QirabotTimeoutError:
    print("Operation timed out.")
except QirabotError as e:
    print(f"Error: {e}")
finally:
    bot.close()

Task Lifecycle

Each Qirabot instance manages a server-side task that tracks all operations:

  • Task creation: created when the Qirabot instance is constructed (pass an existing task_id to attach to one instead)
  • Step recording: each click(), extract(), ai() call is recorded as a step on the server
  • Task completion: call bot.close() or use a context manager — the task is marked as completed
  • Auto-cleanup: if close() is not called, atexit ensures cleanup on script exit. The server also has a 30-minute timeout for orphaned SDK tasks.
bot = Qirabot(task_name="my automation")
# ... operations are recorded as steps ...
bot.close()  # task marked as completed

Context Manager

with Qirabot(task_name="my automation") as bot:
    page = bot.open("https://example.com")
    heading = bot.extract(page, "Get the main heading")
    print(heading)
# bot.close() is called automatically

License

MIT

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

qirabot-1.4.0.tar.gz (61.5 kB view details)

Uploaded Source

Built Distribution

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

qirabot-1.4.0-py3-none-any.whl (49.6 kB view details)

Uploaded Python 3

File details

Details for the file qirabot-1.4.0.tar.gz.

File metadata

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

File hashes

Hashes for qirabot-1.4.0.tar.gz
Algorithm Hash digest
SHA256 e3d3c529b9daa33f519413eb5e76bb6af35929c45047b3b517008bce56848fe2
MD5 f0c27d6371b892edd377a6ab350c0c92
BLAKE2b-256 bb639f2f5d14ba5c4d651b875d152c5b9472c42b23f69322a79d2f8078e72a81

See more details on using hashes here.

Provenance

The following attestation bundles were made for qirabot-1.4.0.tar.gz:

Publisher: publish.yml on qirabot/qirabot-python

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

File details

Details for the file qirabot-1.4.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for qirabot-1.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2418ab9081e2e74280ceb410ed4df70659b3066f654d843c4ed24380ba96c550
MD5 ed1881bd1befd9b2cebcbab3481f170a
BLAKE2b-256 e9cd3ef4e2ce3c27511bdd483a6ab9f2dfa53e81399d2aaa3e4e7e758b8b9738

See more details on using hashes here.

Provenance

The following attestation bundles were made for qirabot-1.4.0-py3-none-any.whl:

Publisher: publish.yml on qirabot/qirabot-python

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