将 Midscene.js AI 驱动的 UI 自动化能力桥接到 Python 测试框架。
无需自行安装 Node.js;无需维护 UI 选择器——用自然语言描述操作,AI 负责定位和执行。
单一 midscene 包,工程代码按模块划分,同时支持:
| 模块 | 能力 | 入口 |
|---|---|---|
agent_android |
Android 自动化(ADB + @midscene/android) |
from midscene import MidsceneAndroidAgent |
agent_web / drivers |
网页自动化(Puppeteer + @midscene/web,预留 Playwright/Bridge) |
from midscene import MidsceneWebAgent |
| 共享底层 | 配置、异常、Node 运行时桥接、RPC 服务管理、BaseAgent |
from midscene import MidsceneConfig, BaseAgent |
本 README 以 Android 为主线说明;网页用法见 网页自动化。
Python 测试代码
└── agent.ai_action("点击登录按钮")
│
│ JSON-RPC 2.0(本地回环,无网络开销)
▼
midscene(共享底层)
├── Node 运行时(首次使用自动下载到 ~/.midscene/node_runtime/,android/web 共享)
├── NodeServiceManager(按平台 ServiceSpec 多实例:android / web 各一个 Node 进程)
└── BaseAgent(跨平台 ai_action / ai_tap / ai_query / ai_assert …)
│
├── MidsceneAndroidAgent → @midscene/android → ADB → Android 设备 / 模拟器
└── MidsceneWebAgent → @midscene/web → Puppeteer → Chromium 浏览器
关键特性:
- 每个平台的 Python 进程与其 Node 进程 1:1;同平台多个 Agent 共享一个 Node 进程(通过 sessionId 隔离)
- android 与 web 各自拥有独立的 Node 服务与缓存命名空间,可并存
- Python 进程退出时 Node 子进程自动清理
- 无需系统已安装
node或npm
pip install midscene首次使用:会按需自动执行
npm install(Android 用@midscene/android,Web 用@midscene/web+ puppeteer),需要访问 npm registry。只有实际使用的平台才会触发对应安装。
系统要求:
| 条件 | 说明 |
|---|---|
| Python | 3.9 及以上 |
| ADB | 已安装并在 PATH 中(adb devices 能看到目标设备) |
| AI API | 支持 OpenAI 兼容接口的视觉模型(如 qwen-vl-max、GPT-4o) |
| Node.js | 无需预装;首次使用自动下载到 ~/.midscene/node_runtime/(wheel 不内置 Node) |
推荐使用 .env 文件(存放项目根目录,自动加载,无需 export;请勿将含密钥的 .env 提交到版本库):
# .env
MIDSCENE_MODEL_BASE_URL=https://ark.cn-beijing.volces.com/api/v3/
MIDSCENE_MODEL_API_KEY=ark-your-api-key
MIDSCENE_MODEL_NAME=doubao-seed-1-6-vision-250815
MIDSCENE_MODEL_FAMILY=doubao-seed首次使用:还会自动下载 Node.js / npm 到
~/.midscene/node_runtime/(需访问 nodejs.org;与系统是否已安装 Node 无关)。
from midscene import MidsceneAndroidAgent
# 从 .env 或环境变量自动读取配置
agent = MidsceneAndroidAgent("emulator-5556")
with MidsceneAndroidAgent("emulator-5556") as agent:
agent.ai_action("等待应用首页加载完成")
agent.ai_tap("登录按钮")
agent.ai_input("用户名输入框", "testuser")
agent.ai_input("密码输入框", "Test@123456")
agent.ai_tap("确认登录")
agent.ai_wait_for("登录成功,显示用户首页", timeout_ms=10000)
agent.ai_assert("当前页面是用户首页")# conftest.py
import pytest
from midscene import MidsceneAndroidAgent
@pytest.fixture
def agent():
ag = MidsceneAndroidAgent("emulator-5556")
yield ag
ag.destroy()
# test_login.py
def test_login(agent: MidsceneAndroidAgent):
agent.ai_action("打开登录页面")
agent.ai_input("用户名", "testuser")
agent.ai_input("密码", "password")
agent.ai_tap("登录")
agent.ai_assert("登录成功,显示用户名 testuser")网页自动化由 MidsceneWebAgent 提供,默认使用 Puppeteer 驱动(首次使用会自动安装 @midscene/web 与 puppeteer,并下载 Chromium)。
from midscene import MidsceneWebAgent
agent = MidsceneWebAgent("https://example.com")
agent.ai_action("在搜索框输入 midscene 并回车")
agent.ai_assert("搜索结果已展示")
agent.destroy()选择 / 配置驱动:
from midscene import MidsceneWebAgent, PuppeteerDriver
# 有头模式 + 指定视口
agent = MidsceneWebAgent(
"https://example.com",
driver=PuppeteerDriver(headless=False, viewport={"width": 1440, "height": 900}),
)
# 连接已运行的 Chrome(chrome --remote-debugging-port=9222)
agent = MidsceneWebAgent(driver=PuppeteerDriver(cdp_endpoint="http://127.0.0.1:9222"))网页专有方法:goto / back / forward / reload / current_url / current_title、多标签(new_tab / list_tabs / switch_tab / close_tab)、set_viewport、wait_for_selector / wait_for_navigation、run_script、ai_hover / ai_right_click,以及全部跨平台 ai_* 方法。PlaywrightDriver / BridgeDriver 为占位,后续接入。
安装 midscene 后,pytest 会自动加载内置插件,无需额外配置。提供 midscene_agent(Android)与 midscene_web_agent(Web)两个 fixture。
无需在 conftest.py 中自行声明 fixture,直接在测试函数参数中使用即可:
# test_my_app.py
import pytest
@pytest.mark.device
def test_login(midscene_agent):
midscene_agent.ai_action("点击登录按钮")
midscene_agent.ai_input("用户名", "testuser")
midscene_agent.ai_input("密码", "Test@123456")
midscene_agent.ai_assert("登录成功,显示用户首页")设备 ID 解析顺序:
| 优先级 | 来源 |
|---|---|
| 1 | --midscene-device CLI 参数 |
| 2 | MIDSCENE_DEVICE_ID 环境变量 |
| 3 | ANDROID_DEVICE_ID 环境变量 |
| 4 | 自动选取第一台已连接设备 |
使用 midscene_agent 的测试用例失败时,插件会自动:
- 调用
get_screenshot()将当前屏幕保存为 PNG。 - 调用
get_report_file()获取 Midscene HTML 报告路径。 - 将上述路径附加到 pytest 报告的 sections(终端
-v输出和 pytest-html 均可见)。
截图保存路径示例:
midscene_artifacts/tests__test_login__test_login.png
# 指定设备
pytest --midscene-device emulator-5556 tests/ -m device
# 自定义截图保存目录
pytest --midscene-artifact-dir /tmp/ci_artifacts tests/ -m device| 选项 | 默认值 | 说明 |
|---|---|---|
--midscene-device |
自动检测 | midscene_agent 使用的 Android 设备 ID |
--midscene-url |
无 | midscene_web_agent 的起始页面 URL(或 MIDSCENE_WEB_URL) |
--midscene-headed |
关闭 | midscene_web_agent 以有头模式启动浏览器 |
--midscene-artifact-dir |
midscene_artifacts/ |
失败截图与报告的保存目录 |
midscene_web_agent 用法:
def test_search(midscene_web_agent):
midscene_web_agent.goto("https://example.com")
midscene_web_agent.ai_action("点击更多信息链接")
midscene_web_agent.ai_assert("页面已跳转")可以通过环境变量、.env 文件或代码直接传入配置:
from midscene import MidsceneAndroidAgent, MidsceneConfig
# 方式一:从 .env / 环境变量自动读取(推荐)
agent = MidsceneAndroidAgent("emulator-5556")
# 方式二:代码直接传入
config = MidsceneConfig(
base_url="https://ark.cn-beijing.volces.com/api/v3/",
api_key="ark-your-api-key",
model_name="doubao-seed-1-6-vision-250815",
model_family="doubao-seed",
)
agent = MidsceneAndroidAgent("emulator-5556", config)model_family |
适用模型 | 示例 model_name |
|---|---|---|
openai |
OpenAI GPT 系列 | gpt-4o |
qwen |
阿里通义千问 | qwen-vl-max |
doubao |
字节豆包 | doubao-vision-pro-32k |
gemini |
Google Gemini | gemini-2.0-flash |
claude |
Anthropic Claude | claude-opus-4-5 |
| 变量 | 必填 | 说明 |
|---|---|---|
MIDSCENE_MODEL_BASE_URL |
✅ | AI API 的 base URL |
MIDSCENE_MODEL_API_KEY |
✅ | API Key |
MIDSCENE_MODEL_NAME |
✅ | 模型名称 |
MIDSCENE_MODEL_FAMILY |
❌ | 模型家族 |
所有方法均为同步调用,内部通过 JSON-RPC 与 Node.js 微服务通信。
agent = MidsceneAndroidAgent(device_id, config=None)
# device_id : ADB 设备 ID,如 "emulator-5556" 或 "192.168.1.100:5555"
# config : MidsceneConfig 实例,可选,默认从环境变量读取
agent.destroy() # 释放 session,进程退出时自动调用
agent.is_closed() # 返回 True/False弃用说明:
MidsceneAgent已更名为MidsceneAndroidAgent(与MidsceneWebAgent对称)。旧名仍可from midscene import MidsceneAgent导入,但实例化时会触发DeprecationWarning,将在后续版本移除。
agent.ai_action(prompt: str) -> NoneAI 自动规划并执行多步操作。适合描述复合目标:
agent.ai_action("打开设置,找到蓝牙选项并开启")
agent.ai_action("滑动到页面底部,点击'加载更多'")比 ai_action 更快、更稳定,适合已知元素的直接操作:
agent.ai_tap(locate: str) -> None
# 点击元素
agent.ai_tap("屏幕右上角的关闭按钮")
agent.ai_tap("文字为'立即购买'的按钮")
agent.ai_input(locate: str, value: str) -> None
# 在指定输入框中输入文本(先清空再输入)
agent.ai_input("搜索框", "midscene python")
agent.ai_clear_input(locate: str) -> None
# 清空输入框内容
agent.ai_clear_input("用户名输入框")
agent.ai_scroll(
locate: str = None,
direction: str = "down", # "up" | "down" | "left" | "right"
scroll_type: str = None,
distance: int = None,
) -> None
# 滚动操作
agent.ai_scroll("商品列表", direction="down", distance=3)
agent.ai_long_press(locate: str, duration: int = None) -> None
# 长按,duration 单位毫秒
agent.ai_long_press("消息列表第一条")
agent.ai_double_click(locate: str) -> None
# 双击
agent.ai_double_click("图片预览区域")
agent.ai_keyboard_press(key_name: str, locate: str = None) -> None
# 模拟按键,如 "Enter"、"Back"、"Home"
agent.ai_keyboard_press("Enter")
agent.ai_keyboard_press("Back")
agent.ai_pinch(
direction: str, # "in"(缩小)| "out"(放大)
locate: str = None,
distance: int = None,
duration: int = None,
) -> None
# 捏合/张开手势
agent.ai_pinch("out", locate="地图区域")agent.ai_assert(assertion: str) -> None
# AI 视觉断言,失败时抛出 AssertionError
agent.ai_assert("当前页面显示用户名 testuser")
agent.ai_assert("购物车商品数量为 3")
agent.ai_wait_for(assertion: str, timeout_ms: int = 15000) -> None
# 等待条件满足,超时抛出异常
agent.ai_wait_for("加载动画消失", timeout_ms=10000)
agent.ai_wait_for("弹窗出现", timeout_ms=5000)
agent.ai_query(data_demand) -> Any
# 从当前屏幕提取结构化数据
products = agent.ai_query({"name": "string", "price": "number", "in_stock": "boolean"})
title = agent.ai_query("页面标题文字")
agent.ai_ask(prompt: str) -> Any
# 自由问答,返回 AI 对当前屏幕的理解
answer = agent.ai_ask("当前页面的主要功能是什么?")
agent.ai_boolean(prompt: str) -> bool
# 返回布尔值
is_logged_in = agent.ai_boolean("用户是否已登录?")
agent.ai_number(prompt: str) -> Any
# 返回数字
count = agent.ai_number("购物车中的商品数量")
agent.ai_string(prompt: str) -> str
# 返回字符串
username = agent.ai_string("当前登录的用户名")
agent.ai_locate(locate_prompt: str) -> Any
# 定位元素,返回位置信息(坐标等)
pos = agent.ai_locate("确认按钮")agent.run_adb_shell(command: str, timeout_ms: int = None) -> str
# 执行 adb shell 命令,返回输出文本;timeout_ms 单位为毫秒
output = agent.run_adb_shell("dumpsys activity top | grep 'ACTIVITY'")
output = agent.run_adb_shell("pm list packages | grep com.example", timeout_ms=5000)from midscene import MidsceneRPCError
try:
agent.ai_assert("某个不存在的条件")
except AssertionError as e:
# AI 断言失败(pass=False),包含失败原因
print(f"断言失败: {e}")
try:
agent.ai_action("点击某个按钮")
except MidsceneRPCError as e:
# Node.js 侧报错(如设备断开、ADB 错误)
print(f"RPC 错误 [code={e.code}]: {e}")| 异常 | 触发场景 |
|---|---|
AssertionError |
ai_assert() 条件不满足 |
MidsceneRPCError |
Node.js 侧业务错误(ADB 断开、元素找不到等) |
MidsceneConfigError |
必填配置缺失,或同一平台复用 Node 服务时传入了不同的 MidsceneConfig |
MidsceneSetupError |
Node 二进制缺失、npm install 失败等初始化问题 |
MidsceneNodeServiceError |
Node.js 服务启动失败或意外退出 |
MidsceneError |
使用已 destroy() 的 Agent |
git clone https://github.com/zypdominate/midscene-python
cd midscene_python
# 以可编辑方式安装(含开发工具)
pip install -e ".[dev]"
# 首次运行测试会自动下载 Node 到 ~/.midscene/node_runtime/根目录 pyproject.toml 同时承载打包元数据与 ruff / pytest / mypy 配置,并把 src 加入 pythonpath,因此在根目录直接执行 pytest / ruff check . / mypy 即可。
# 全部非真机/非浏览器测试(首次需联网下载 Node + npm 依赖)
pytest -m "not device and not web" -v
# 需要真实 Android 设备的测试(需配置好 .env 并连接设备)
pytest tests/ -m device -v -s
# 需要真实浏览器 + AI Key 的网页测试
pytest tests/ -m web -v -s# 构建 py3-none-any wheel + sdist
python tools/build_wheel.py --clean
# 检查并上传 PyPI
python tools/upload_pypi.py --dry-run
python tools/upload_pypi.py --require-all --yessrc/midscene/
├── __init__.py # 顶层导出(MidsceneAndroidAgent / MidsceneWebAgent / 驱动 / 异常 …)
├── config.py # MidsceneConfig(环境变量 / .env 支持)
├── exceptions.py # 公开异常体系
├── agent_android.py # MidsceneAndroidAgent(BaseAgent) + 设备/系统方法
├── agent_web.py # MidsceneWebAgent(BaseAgent) + 网页导航/等待/脚本执行
├── drivers.py # PuppeteerDriver(实现)+ Playwright/Bridge(占位)
├── py.typed
├── _internal/ # 私有实现(非公共 API,可能随版本变动)
│ ├── node_bootstrap.py # 首次运行从 nodejs.org 下载 Node/npm
│ ├── runtime.py # ServiceSpec + 按平台参数化的 npm install / 缓存
│ ├── node_service.py # NodeServiceManager(按 spec 名称多实例)
│ ├── base_agent.py # BaseAgent(跨平台 ai_* 方法 + RPC)
│ ├── _pytest_plugin.py # midscene_agent / midscene_web_agent fixture + pytest11 入口
│ └── _pytest_support.py # pytest 插件共享逻辑(失败截图/报告)
└── _node_driver/
├── android/ # package.json(@midscene/android) + service.js
└── web/ # package.json(@midscene/web + puppeteer) + service.js
# 运行时缓存(不在 pip 包内,android/web 共享 Node 运行时):
# ~/.midscene/node_runtime/ ← Node + npm
# ~/.midscene/android/node_service/ ← npm install @midscene/android
# ~/.midscene/web/node_service/ ← npm install @midscene/web + puppeteer
tests/ # Android + Web 测试集中在根目录
tools/
├── build_wheel.py # 构建 py3-none-any wheel + sdist
└── upload_pypi.py # 检查 dist/ 并上传 PyPI
MIT