EasyRun UI Agent · 部署与使用文档
1. 快速开始(新机器一键初始化)
sh scripts/bootstrap.sh # 自动:venv / 依赖 / 浏览器内核 / Allure / .env / 环境检查
bootstrap 会按平台自动处理:macOS 12 及更早自动锁定 playwright 1.50(新版 chromium 不支持旧系统)、Allure CLI+JRE 下载到项目内 tools/(不碰系统)、生成 .env 模板。Windows 10/11 用:powershell -ExecutionPolicy Bypass -File scripts\bootstrap.ps1。
- 编辑
.env填入DEEPSEEK_API_KEY=sk-xxx source .venv/bin/activate && easyrun serve- 打开控制台
http://127.0.0.1:8001/app/ - 另开终端跑演示验证:
python scripts/demo.py
2. 部署指南
2.1 操作系统支持矩阵
| 系统 | 支持程度 | 初始化方式 | 说明 |
|---|---|---|---|
| Ubuntu / Debian(Linux x64/arm64) | ✅ 完整支持(推荐生产环境) | sh scripts/bootstrap.sh | Docker 形态即 Linux;多机集群推荐 Ubuntu 执行节点 |
| macOS(x64 / Apple Silicon) | ✅ 完整支持 | sh scripts/bootstrap.sh | macOS 12 及更早自动锁定 playwright 1.50 |
| Windows 10/11(x64) | ✅ 支持 | powershell -ExecutionPolicy Bypass -File scripts\bootstrap.ps1 | 核心代码全平台;Windows 初始化脚本已提供 |
三个平台可同时混合部署:不同系统的执行节点接入同一 Redis + PostgreSQL 集群。
2.2 数据库配置(重点)
方案一:SQLite(默认,零配置)——适合单机开发,什么都不用配:
# 留空即可:自动使用 data/easyrun.db # EASYRUN_DATABASE_URL=
方案二:Docker 启动 PostgreSQL(生产推荐):
docker run -d --name easyrun-pg --restart unless-stopped \ -e POSTGRES_USER=easyrun -e POSTGRES_PASSWORD=EasyRun@2026 -e POSTGRES_DB=easyrun \ -p 5432:5432 -v easyrun_pg:/var/lib/postgresql/data postgres:16-alpine
方案三:已有 PostgreSQL 实例——先建库建用户:
# psql 执行(或数据库管理工具): CREATE USER easyrun WITH PASSWORD 'EasyRun@2026'; CREATE DATABASE easyrun OWNER easyrun;
连接串格式:postgresql+asyncpg://用户名:密码@主机:端口/库名(需先 pip install -e ".[postgres]" 安装驱动):
# .env 示例 EASYRUN_DATABASE_URL=postgresql+asyncpg://easyrun:EasyRun@2026@192.168.1.10:5432/easyrun
@→%40、:→%3A、/→%2F、#→%23、%→%25。上例中 EasyRun@2026 实际是 EasyRun%402026——推荐密码避免 @ : / 以省去转义。验证连接:
# 容器内检查 docker exec -it easyrun-pg psql -U easyrun -d easyrun -c "SELECT 1;" # 平台侧:启动后看日志无报错,或访问 /api/health 正常返回
2.3 Redis 配置
单机开发留空即可(进程内队列);多机/多进程部署必配:
# Docker 启动(带密码) docker run -d --name easyrun-redis --restart unless-stopped \ -p 6379:6379 redis:7-alpine --requirepass EasyRunRedis # .env 示例 EASYRUN_REDIS_URL=redis://:EasyRunRedis@192.168.1.10:6379/0
Redis 连接串格式:redis://[:密码]@主机:端口/库号。所有节点必须配置同一个 Redis 地址。需先 pip install -e ".[redis]"。
验证:redis-cli -h 192.168.1.10 -a EasyRunRedis ping → PONG。
2.4 LLM(DeepSeek)配置
# .env 示例 DEEPSEEK_API_KEY=sk-你的密钥 # 可选:自定义端点(默认 https://api.deepseek.com) # EASYRUN_DEEPSEEK_BASE_URL=https://api.deepseek.com # 可选:换本地开源模型(OpenAI 兼容端点,如 Ollama/vLLM) # EASYRUN_DEEPSEEK_BASE_URL=http://127.0.0.1:11434/v1 # EASYRUN_DEEPSEEK_CHAT_MODEL=qwen3:32b # EASYRUN_DEEPSEEK_REASONER_MODEL=qwen3:32b
密钥只在服务端使用,页面与报告不会回显。
2.5 执行参数配置示例
# 生产集群推荐配置 EASYRUN_WORKERS=4 # 并发 Agent 数(扩容单元 = 浏览器实例;控制节点设 0) EASYRUN_BROWSER_HEADLESS=true EASYRUN_MAX_ATTEMPTS=1 # 失败不自动重试 EASYRUN_TASK_TIMEOUT_SECONDS=600 EASYRUN_REPLAY_STEP_DELAY_MS=3000 EASYRUN_DATA_DIR=/srv/easyrun-data # 多机共享时指向 NFS 挂载点
2.6 形态 A:单机开发(零外部依赖)
SQLite + 内存队列 + 内置 Worker。只配 DEEPSEEK_API_KEY 即可启动。
2.7 形态 B:单机 Docker(推荐生产起步)
export DEEPSEEK_API_KEY=sk-xxx docker compose up -d --build # 验证 docker compose ps curl http://127.0.0.1:8001/api/health
四服务:api(控制面)+ worker(执行面,内置 chromium)+ redis + postgres(compose 内已自动配好连接串,无需手改)。镜像构建时已内置 Allure CLI + JRE,报告生成开箱即用;运行时数据(截图 / Allure 结果与 HTML / PostgreSQL 数据)绑定挂载到项目根目录 ./data/。
docker compose logs -f worker # 看执行日志 docker compose up -d --build worker # 代码更新后重建 docker compose down # 停止(数据保留在 ./data/,不随 down 删除)
2.7.1 同机多 Worker(控制器 ×1 + 执行节点 ×N)
形态 B 默认只起 1 个 worker 容器(4 个并发 Agent)。机器资源充足时可用 --scale 把执行面扩到 N 个容器,控制面仍是 1 个 api 容器。
docker compose build docker compose up -d --scale worker=3 # api×1 + worker×3 + redis + postgres docker compose logs -f worker # 提交多用例计划后应看到多个容器同时执行
配置要点(compose 已用 YAML 锚点给 api 和 worker 配好同一份连接,无需手改):
| 配置 | 取值 | 为什么 |
|---|---|---|
EASYRUN_REDIS_URL | redis://redis:6379/0 | 所有节点连同一个队列,任务才分发得出去 |
EASYRUN_DATABASE_URL | compose 内 postgres 连接串 | 所有节点读写同一个元数据库(任务/事件/报告) |
EASYRUN_WORKERS | api=0 / worker=4 | 控制节点不跑浏览器;执行节点按资源设并发数 |
EASYRUN_DATA_DIR | /srv/data(绑定挂载项目根目录 ./data) | 共享工件目录:控制台可预览任意 worker 节点的截图 / Allure |
EASYRUN_ALLURE_BIN | 不设(自动探测) | 探测顺序:显式配置 → PATH → 项目根 tools/bin/allure;Docker 镜像已内置(容器内 /srv/tools/bin/allure),无需配置 |
EASYRUN_BROWSER_HEADLESS | true | 容器内无显示器,必须无头 |
在线扩缩容:docker compose up -d --scale worker=5(扩容)/ --scale worker=1(缩容,多余容器自动停止)。--scale 只能用于 worker:api 发布 8001 端口,多副本会冲突。
容量:1 个并发 Agent ≈ 1 个 chromium ≈ 400-600MB 内存。8G 内存建议 --scale worker=2,16G 建议 4,32G 建议 8。改每容器并发数(默认 4):加 docker-compose.override.yml 设 EASYRUN_WORKERS(compose 自动合并,不改主文件)。
注意:/api/health 的 workers 显示的是 api 容器自身的 Worker 数(0),不代表执行能力,以 docker compose logs worker 为准。
2.8 形态 C:多机集群(水平扩容)
| 节点 | 运行内容 | 关键配置 |
|---|---|---|
| 控制节点 ×1 | easyrun serve | EASYRUN_WORKERS=0 |
| 执行节点 ×N | easyrun worker | EASYRUN_WORKERS=4 |
| 基础设施 | Redis + PostgreSQL | 见 2.2 / 2.3 的连接串示例 |
| 共享工件 | 截图 / Allure | 把项目根目录 data/(或 EASYRUN_DATA_DIR)挂到 NFS |
控制节点 .env 完整示例:
DEEPSEEK_API_KEY=sk-xxx EASYRUN_DATABASE_URL=postgresql+asyncpg://easyrun:EasyRun@2026@192.168.1.10:5432/easyrun EASYRUN_REDIS_URL=redis://:EasyRunRedis@192.168.1.10:6379/0 EASYRUN_WORKERS=0 EASYRUN_DATA_DIR=/mnt/easyrun-share
执行节点 .env 完整示例:
DEEPSEEK_API_KEY=sk-xxx EASYRUN_DATABASE_URL=postgresql+asyncpg://easyrun:EasyRun@2026@192.168.1.10:5432/easyrun EASYRUN_REDIS_URL=redis://:EasyRunRedis@192.168.1.10:6379/0 EASYRUN_WORKERS=4 EASYRUN_DATA_DIR=/mnt/easyrun-share
部署顺序:① 基础设施(2.2/2.3)→ ② 控制节点 bootstrap + .env + easyrun serve → ③ 每台执行机 bootstrap + .env + easyrun worker → ④ NFS 挂载共享数据目录(完整配置见 2.8.1 数据路径配置详解)。
扩容 = 新机器执行第 ③ 步;节点下线直接停进程,崩溃任务由调度器超时回收。
注意:多机部署不是只在控制节点上执行命令——每台执行机都要独立完成部署(拷贝代码 → bootstrap → 配 .env → 启动)。控制节点无法远程下发部署;执行节点连上共享 Redis 即自动接入,无需控制节点审批(无中心注册表)。执行机需要本机浏览器内核(bootstrap 自动下载),这是必须逐台部署的根本原因。
执行节点也可用 Docker:docker compose up -d --no-deps --build worker,并用 override 把 EASYRUN_REDIS_URL / EASYRUN_DATABASE_URL 指向中心基础设施 IP。--no-deps 必须加,否则 compose 会连带把依赖的 redis/postgres 也在这台机器上各起一套。Docker 执行节点镜像已内置 Allure CLI + JRE,工件落在该机项目根目录 ./data/artifacts/(compose 绑定挂载 ./data:/srv/data)。
2.8.1 数据路径配置(共享工件)详解
谁写谁读:控制节点(api)生成 Allure 原始结果与 HTML(data/artifacts/allure/、allure-html/),并从共享目录读全部节点的截图打包附件;执行节点(worker)把步骤截图写入 data/artifacts/sessions/、视觉基线写入 data/artifacts/baselines/,基本只写不读。所以控制节点必须能读到各执行节点的 data/artifacts/。
默认路径(不改配置即如此):裸机节点 = <项目根目录>/data(EASYRUN_DATA_DIR 默认值);Docker 节点 = 宿主机 <项目根目录>/data ↔ 容器 /srv/data(compose 绑定挂载 ./data:/srv/data)。
方案 A(推荐):共享路径 = 控制节点项目根的 data/(控制节点当 NFS 服务器,所有节点数据都在各自项目 data/ 下,配置零改动):
# 控制节点(Linux): sudo apt install -y nfs-kernel-server echo '<项目根目录>/data 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)' | sudo tee -a /etc/exports sudo exportfs -ra && sudo systemctl enable --now nfs-server # 控制节点(macOS): # sudo nfsd enable # echo '<项目根目录>/data -network 192.168.1.0 -mask 255.255.255.0 -maproot=root:wheel' | sudo tee -a /etc/exports # sudo nfsd update # 每台执行节点: sudo apt install -y nfs-common sudo mkdir -p <项目根目录>/data sudo mount -t nfs <控制节点IP>:<控制节点项目根目录>/data <项目根目录>/data echo '<控制节点IP>:<控制节点项目根目录>/data <项目根目录>/data nfs defaults,nofail 0 0' | sudo tee -a /etc/fstab
挂好后执行节点零配置:裸机默认路径即该挂载点;Docker 的 ./data:/srv/data 自动指向它。先挂载再启动容器(或挂载后 docker compose restart)——容器运行后再挂 NFS,容器看不到新内容。验证:执行节点 touch data/nfs-check 控制节点应能看到;控制台提交多用例计划后应能预览全部截图;生成 Allure 后各节点 data/artifacts/allure-html/<run_id>/ 应一致。
方案 B:独立共享存储:每台机器(含控制节点)把共享挂到任意位置(如 /mnt/easyrun-share);裸机 .env 设 EASYRUN_DATA_DIR=/mnt/easyrun-share;Docker 用 docker-compose.override.yml 把 volume 改为 /mnt/easyrun-share:/srv/data。控制节点必须也挂同一共享——生成 Allure 报告的是控制节点。
关键坑:① exports 必须加 no_root_squash(Docker 容器以 root 写,否则 Permission denied);② 裸机节点写失败通常是各机 uid 不一致——统一 uid 或加 all_squash,anonuid=<uid>;③ fstab 加 nofail 防开机阻塞;④ 不共享时能跑,但跨节点截图无法预览、Allure 附件缺失——挂好共享后重新点「生成 Allure 报告」即可完整重打包;⑤ data/ 整树导出含 data/postgres/,postgres 只在基础设施机本机访问,勿在多机同时启动指向同一目录的 postgres。
2.9 配置排查速查
- 启动报数据库连接失败:确认 PostgreSQL 端口可达(
nc -zv 主机 5432)、密码转义正确、驱动已装(pip install -e ".[postgres]") - Worker 连不上队列:确认 Redis 密码/地址、驱动已装(
.[redis])、多节点配置的是同一 Redis - 报告看不到其他节点截图:
EASYRUN_DATA_DIR未指向共享存储 - 配置优先级:命令行环境变量 > .env 文件 > 代码默认值
3. 平台功能使用
用例管理
- 步骤写「做什么」,不写「怎么点」——Agent 负责翻译成具体操作;每条步骤一行自然语言
- 每个用例可设默认访问网址(运行时仍可覆盖);断言至少一条,覆盖业务结果
- 不会写断言?用「AI 生成断言」:输入「页面出现订单编号;订单金额大于 100」自动转换
- 完成条件(可选):页面出现某文本即停止操作(如「中性新闻 (」),防多余动作
- 一条用例一个业务场景,建议 ≤10 步;多场景用「计划」批量跑
执行与目标地址优先级
运行时填写的网址 > 用例默认网址 > 平台默认目标地址(配置页设置)
报告
- 时间轴:每步 LLM 决策理由 / 动作结果 / 截图 / 断言 / 完成条件命中
- 失败时自动 AI 归因(五类)+ 缺陷草稿;「重跑失败用例」只重跑失败的
- Allure 报告:报告页点「生成 Allure 报告」按需生成(带进度显示),生成后按钮变为「查看 Allure 报告」,点击在新窗口打开
/allure-html/<run_id>/;已生成过的执行直接显示查看按钮,无需重复生成 - 执行中可随时「取消执行」(排队任务直接跳过,执行中任务在下一步停止;多机/多容器部署同样生效);执行记录支持多选/全选批量删除(不可恢复,删除前有确认)
固化与导出(0 token 回归)
- 探索模式通过后自动记录动作 → 用例行出现「固化」按钮 → 点击后变回放模式(不耗 LLM)
- 「导出代码」生成独立 Playwright 脚本(语义定位器 + 断言),脱离平台可跑、可进 CI
- 回放动作间默认间隔 3 秒(等待动态渲染),断言前有沉降等待——适配平滑滚动类页面
4. 断言与完成条件参考
9 类断言(确定性校验,由代码执行):
| 类型 | 用途 | target / expected 示例 |
|---|---|---|
text_contains | 页面正文包含文本 | 订单编号 / — |
url_contains | URL 包含片段 | checkout / — |
element_exists | CSS 选择器存在 | .cart-button / — |
element_count | 选择器数量等于 | .product / 3 |
element_text | 存在文本元素 | 欢迎回来 / — |
text_in_view | 屏幕可见元素含文本(全 DOM,含普通 div/span 标签) | 中性新闻 ( / — |
text_near_top | 文本元素出现在窗口上方区域 | 中性新闻 ( / 0.4(阈值可选) |
value_compare | 标签后数值比较(支持同元素与相邻兄弟元素) | 订单金额 / >= 100 |
visual | 截图与基线一致(首次自动建基线) | checkout / — |
完成条件:每步动作前检查,全部满足立即停止操作进入断言。字段 min_steps 可设"执行满 N 个动作后条件才生效"——用于目标状态与初始状态相同的场景(如利好新闻标签页面加载即可见,但用例要求先点日期再点按钮)。
步骤后断言:断言可绑定步骤序号(断言行的「步骤序号」输入框,1-99)。探索模式下 Agent 每完成一个步骤调用 case_step_done(step=N) 标记,平台在该步骤动作后立即执行绑定断言(0 token 确定性校验;失败即止:有自愈配置先自愈,否则用例失败)。Agent 忘标记直接结束时,收尾兜底按步骤序补跑所有未标记步骤的绑定断言,再接无绑定断言。固化回放同样在标记点执行(标记随固化动作保存,deterministic 用例的步骤列表中可见 case_step_done JSON 行,属正常现象);导出代码时绑定断言就地生成在对应动作之后。
步骤序号怎么对应:填的序号 = 用例「步骤」列表的行号(从 1 开始,每行一步)。规则:
- 一个步骤可绑多条断言;一条断言只绑一个序号。
- 序号是步骤行号,不是动作数——一个步骤可能对应多个动作(找元素 + 点击 + 等待),动作由 Agent 自主决策。
- 提示词中步骤带编号列出,绑定断言的步骤行会追加「完成本步骤后调用
case_step_done(step=N)触发绑定断言」提示。 - Agent 漏标记、或序号超出步骤数:断言不会丢——收尾兜底按序号顺序补跑(执行时机退化为「结束时」,报告仍显示「步骤 N」标签)。
- 不填序号 = 全部步骤完成后统一执行;增删步骤行后序号不会自动跟随,需手动核对。
示例(断言「页面出现订单编号」填步骤序号 4):
| 步骤 | 绑定断言 | 执行时机 |
|---|---|---|
| 1. 打开商城首页 | url_contains /home(序号 1) | 步骤 1 动作完成后立即校验 |
| 2. 搜索机械键盘 | element_count 搜索结果 = 12(序号 2) | 步骤 2 动作完成后立即校验 |
| 3. 点击第一个搜索结果 | (无) | — |
| 4. 加入购物车并结算 | text_contains 订单编号(序号 4) | 步骤 4 动作完成后立即校验 |
| — | value_compare 订单金额 > 100(不填) | 全部步骤完成后统一校验 |
5. 执行策略
| 场景 | 行为 |
|---|---|
| 动作执行失败 | 立即终止,不重试不换方式 |
| 同一动作被重复请求 | 跳过并提示执行下一步(超过 2 次跳过才终止) |
| 用例失败 | 不自动重试(max_attempts=1) |
| 断言失败 | 不自愈重试,直接判失败 |
| 等不到资源锁 | 直接失败,不回队列 |
| 连续失败 3 次 | 隔离(quarantine,新执行不受影响) |
| 需要重试 | 报告页「重跑失败用例」手动触发 |
browser_wait / browser_get_text / 截图不参与"重复动作"判定,等待和读取可多次调用。6. 配置项参考(环境变量,前缀 EASYRUN_)
| 变量 | 默认 | 说明 |
|---|---|---|
EASYRUN_DATA_DIR | ./data | 运行时数据(数据库/截图/Allure/浏览器内核) |
EASYRUN_DATABASE_URL | data/easyrun.db | 生产用 postgresql+asyncpg://user:pass@host/db |
EASYRUN_REDIS_URL | 空(内存队列) | 多机必填 redis://host:6379/0 |
EASYRUN_WORKERS | 4 | 并发 Agent 数;纯 API 节点设 0 |
EASYRUN_MAX_ATTEMPTS | 1 | 失败自动重试次数(1 = 只执行一次);⚙ 可在控制台「配置」页运行时修改(1-10) |
EASYRUN_MAX_NOOP_REPEATS | 1 | 同一动作允许执行次数 |
EASYRUN_MAX_SKIPPED_REPEATS | 2 | 重复请求最多跳过次数 |
EASYRUN_MAX_STEPS_PER_CASE | 30 | 单用例动作步数上限;⚙ 可运行时修改(3-100,页面键名 max_steps) |
EASYRUN_HEAL_ATTEMPTS | 0 | 断言失败自愈轮数(0 = 不自愈);⚙ 可运行时修改(0-5) |
EASYRUN_REPLAY_STEP_DELAY_MS | 3000 | 固化回放动作间延迟 |
EASYRUN_TASK_TIMEOUT_SECONDS | 600 | 单任务执行上限 |
EASYRUN_BROWSER_HEADLESS | true | 浏览器无头模式 |
EASYRUN_ALLURE_BIN | 自动探测 | allure CLI 路径(PATH → tools/bin/allure) |
DEEPSEEK_API_KEY | — | DeepSeek API Key |
EASYRUN_DEEPSEEK_BASE_URL | api.deepseek.com | OpenAI 兼容端点,可换本地 vLLM/Ollama |
⚙ = 可在控制台「配置」页运行时修改(存于共享数据库,多机一致,保存即生效;输入框置空保存 = 清除覆盖、回落环境变量默认)。「失败归因」开关也在配置页(关闭后失败任务不再自动调 deepseek-reasoner,省 token)。
7. REST API 摘要
GET /api/cases 用例列表(含整数编号 case_no)
POST /api/cases 新建用例 PUT /api/cases/{id} 更新
DELETE /api/cases/{id} 删除用例 POST /api/cases/{id}/run 单用例执行
POST /api/cases/{id}/cure 启用固化回放 POST /api/cases/{id}/export-code 导出 Playwright 代码
POST /api/cases/assertions/parse 自然语言 → 断言
GET /api/plans 计划列表 POST /api/plans/{id}/run 计划执行
POST /api/runs 提交执行 GET /api/runs?page=&page_size= 分页列表
GET /api/runs/{id}/report 聚合报告(含归因) GET /api/runs/{id}/events 事件流
POST /api/runs/{id}/cancel 取消执行 POST /api/runs/{id}/rerun-failed 重跑失败用例
DELETE /api/runs/{id} 删除执行 POST /api/runs/batch-delete 批量删除
POST /api/runs/{id}/allure Allure 导出+HTML GET /api/trends 趋势
GET /api/locators 元素库 GET/PUT /api/settings 平台配置
交互式 API 文档:/docs(Swagger UI)
8. 常见问题
- 用例步骤 6 条,为什么执行了 20+ 个动作? 步骤是自然语言目标,动作是 LLM 的每步决策(导航/点击/等待/确认…),一条步骤可能对应多个动作。上限由
max_steps_per_case控制。 - 为什么有的用例没有「固化」按钮? 固化动作在探索模式通过后自动记录(≥1 个动作)。从未通过、或已固化的用例没有该按钮(已固化的保留「导出代码」)。
- 完成条件配置了但不生效? 检查页面真实文本(如全角/半角括号差异:「中性新闻(」vs「中性新闻 (」);若目标状态与初始状态相同,配合
min_steps使用。 - 点击新闻链接页面没反应? 用
browser_click_link按名称点击 class=sub-links 内的链接;站点平滑滚动需等待动画完成(平台已内置渲染轮询与沉降等待)。 - 回放(固化)失败但探索成功? 页面结构变了 → 回到探索模式重跑一次重新固化;或检查
EASYRUN_REPLAY_STEP_DELAY_MS是否足够。 - 时间显示差 8 小时? 平台统一存 UTC、展示层转本地时区。若仍有偏差,确认服务器与浏览器时区设置。
- macOS 12 装不了浏览器? 新版 chromium 不支持旧系统,bootstrap 会自动锁定 playwright 1.50。
- 删除执行记录能恢复吗? 不能。删除会清除任务/事件/截图/Allure 全部关联内容,操作前有确认提示。
9. 成本与省钱
- 探索模式:每步决策约 0.5~1.5k tokens(页面快照占大头);失败即终止 + 跳过重复 + 完成条件三重护栏防空转
- 固化回放 / 导出代码 = 0 token——高频回归用例务必固化
- 失败归因用
deepseek-reasoner(更贵),仅失败时触发一次 - 总览页可查看 Token 消耗趋势;单用例成本 ≈ 决策数 × 单步 tokens
EasyRun UI Agent · Deployment & User Guide
1. Quick Start (one-command bootstrap)
sh scripts/bootstrap.sh # venv / deps / browser / Allure / .env / env check
bootstrap handles platform specifics automatically: macOS 12 and older pin playwright 1.50; the Allure CLI+JRE download into project-local tools/; it generates an .env template. On Windows 10/11 use: powershell -ExecutionPolicy Bypass -File scripts\bootstrap.ps1.
- Edit
.envand setDEEPSEEK_API_KEY=sk-xxx source .venv/bin/activate && easyrun serve- Open the console at
http://127.0.0.1:8001/app/ - Verify with the demo:
python scripts/demo.py
2. Deployment Guide
2.1 OS support matrix
| OS | Support | Bootstrap | Notes |
|---|---|---|---|
| Ubuntu / Debian (Linux x64/arm64) | ✅ Full (recommended for production) | sh scripts/bootstrap.sh | Docker mode runs on Linux; Ubuntu recommended for worker nodes |
| macOS (x64 / Apple Silicon) | ✅ Full | sh scripts/bootstrap.sh | macOS 12 and older auto-pin playwright 1.50 |
| Windows 10/11 (x64) | ✅ Supported | powershell -ExecutionPolicy Bypass -File scripts\bootstrap.ps1 | Core code is cross-platform; Windows bootstrap scripts provided |
All three platforms can be mixed in one cluster: worker nodes on different OSes join the same Redis + PostgreSQL.
2.2 Database configuration
Option 1: SQLite (default, zero config) — for single-machine development:
# Leave empty: data/easyrun.db is used automatically # EASYRUN_DATABASE_URL=
Option 2: PostgreSQL via Docker (recommended for production):
docker run -d --name easyrun-pg --restart unless-stopped \ -e POSTGRES_USER=easyrun -e POSTGRES_PASSWORD=EasyRun@2026 -e POSTGRES_DB=easyrun \ -p 5432:5432 -v easyrun_pg:/var/lib/postgresql/data postgres:16-alpine
Option 3: existing PostgreSQL instance — create the database and user first:
# Run in psql (or any DB tool): CREATE USER easyrun WITH PASSWORD 'EasyRun@2026'; CREATE DATABASE easyrun OWNER easyrun;
Connection string format: postgresql+asyncpg://user:password@host:port/dbname (install the driver first with pip install -e ".[postgres]"):
# .env example EASYRUN_DATABASE_URL=postgresql+asyncpg://easyrun:EasyRun@2026@192.168.1.10:5432/easyrun
@→%40, :→%3A, /→%2F, #→%23, %→%25. In the example above EasyRun@2026 must actually be EasyRun%402026 — prefer passwords without @ : / to avoid escaping.Verify connectivity:
# From inside the container docker exec -it easyrun-pg psql -U easyrun -d easyrun -c "SELECT 1;" # Or start the platform and check /api/health
2.3 Redis configuration
Leave empty for single-machine development (in-process queue); required for multi-machine/multi-process:
# Start with Docker (with password) docker run -d --name easyrun-redis --restart unless-stopped \ -p 6379:6379 redis:7-alpine --requirepass EasyRunRedis # .env example EASYRUN_REDIS_URL=redis://:EasyRunRedis@192.168.1.10:6379/0
Format: redis://[:password]@host:port/db. All nodes must use the same Redis address. Install the driver first: pip install -e ".[redis]".
Verify: redis-cli -h 192.168.1.10 -a EasyRunRedis ping → PONG.
2.4 LLM (DeepSeek) configuration
# .env example DEEPSEEK_API_KEY=sk-your-key # Optional: custom endpoint (default https://api.deepseek.com) # EASYRUN_DEEPSEEK_BASE_URL=https://api.deepseek.com # Optional: switch to a local open model (OpenAI-compatible endpoint, e.g. Ollama/vLLM) # EASYRUN_DEEPSEEK_BASE_URL=http://127.0.0.1:11434/v1 # EASYRUN_DEEPSEEK_CHAT_MODEL=qwen3:32b # EASYRUN_DEEPSEEK_REASONER_MODEL=qwen3:32b
The API key is used server-side only; it is never echoed in the UI or reports.
2.5 Execution parameter examples
# Recommended production cluster settings EASYRUN_WORKERS=4 # Concurrent agents (scaling unit = browser instance; set 0 on control node) EASYRUN_BROWSER_HEADLESS=true EASYRUN_MAX_ATTEMPTS=1 # No automatic retry on failure EASYRUN_TASK_TIMEOUT_SECONDS=600 EASYRUN_REPLAY_STEP_DELAY_MS=3000 EASYRUN_DATA_DIR=/srv/easyrun-data # Point to an NFS mount when sharing across nodes
2.6 Mode A: single-machine dev (zero external deps)
SQLite + in-process queue + built-in workers. Only DEEPSEEK_API_KEY is required.
2.7 Mode B: single-machine Docker (recommended production start)
export DEEPSEEK_API_KEY=sk-xxx docker compose up -d --build # Verify docker compose ps curl http://127.0.0.1:8001/api/health
Four services: api (control plane) + worker (execution plane with chromium) + redis + postgres (connection strings preconfigured inside compose). The image bundles the Allure CLI + JRE at build time, so report generation works out of the box; runtime data (screenshots / Allure results & HTML / PostgreSQL data) is bind-mounted to the project root ./data/.
docker compose logs -f worker # follow worker logs docker compose up -d --build worker # rebuild after code changes docker compose down # stop (data stays in ./data/, not removed by down)
2.7.1 Same-machine multi-worker (control ×1 + execution nodes ×N)
Mode B starts only 1 worker container (4 concurrent agents) by default. With enough machine resources, use --scale to expand the execution plane to N containers; the control plane stays a single api container.
docker compose build docker compose up -d --scale worker=3 # api×1 + worker×3 + redis + postgres docker compose logs -f worker # after submitting a multi-case plan, several containers run concurrently
Config essentials (compose already shares one connection config between api and worker via a YAML anchor — no manual edits):
| Config | Value | Why |
|---|---|---|
EASYRUN_REDIS_URL | redis://redis:6379/0 | All nodes connect to the same queue for task distribution |
EASYRUN_DATABASE_URL | postgres connection string inside compose | All nodes read/write the same metadata DB (tasks/events/reports) |
EASYRUN_WORKERS | api=0 / worker=4 | Control node runs no browsers; execution nodes set concurrency by resources |
EASYRUN_DATA_DIR | /srv/data (bind-mounted to the project root ./data) | Shared artifacts: the console previews screenshots / Allure from any worker node |
EASYRUN_ALLURE_BIN | Unset (auto-detected) | Detection order: explicit config → PATH → project-root tools/bin/allure; Docker images already bundle it (container path /srv/tools/bin/allure), no configuration needed |
EASYRUN_BROWSER_HEADLESS | true | No display inside containers — headless is required |
Scale online: docker compose up -d --scale worker=5 (out) / --scale worker=1 (in; extra containers stop automatically). Use --scale only for worker: api publishes port 8001, so replicas would conflict.
Capacity: 1 concurrent agent ≈ 1 chromium ≈ 400-600MB RAM. Suggested: 8GB → --scale worker=2, 16GB → 4, 32GB → 8. To change per-container concurrency (default 4): add a docker-compose.override.yml setting EASYRUN_WORKERS (compose merges it automatically; the main file stays untouched).
Note: the workers field of /api/health reports the api container's own worker count (0) — it does not reflect execution capacity; check docker compose logs worker instead.
2.8 Mode C: multi-machine cluster (horizontal scale)
| Node | Runs | Key config |
|---|---|---|
| Control node ×1 | easyrun serve | EASYRUN_WORKERS=0 |
| Worker nodes ×N | easyrun worker | EASYRUN_WORKERS=4 |
| Infrastructure | Redis + PostgreSQL | See 2.2 / 2.3 connection strings |
| Shared artifacts | Screenshots / Allure | Mount the project-root data/ (or EASYRUN_DATA_DIR) onto NFS |
Control node .env example:
DEEPSEEK_API_KEY=sk-xxx EASYRUN_DATABASE_URL=postgresql+asyncpg://easyrun:EasyRun@2026@192.168.1.10:5432/easyrun EASYRUN_REDIS_URL=redis://:EasyRunRedis@192.168.1.10:6379/0 EASYRUN_WORKERS=0 EASYRUN_DATA_DIR=/mnt/easyrun-share
Worker node .env example:
DEEPSEEK_API_KEY=sk-xxx EASYRUN_DATABASE_URL=postgresql+asyncpg://easyrun:EasyRun@2026@192.168.1.10:5432/easyrun EASYRUN_REDIS_URL=redis://:EasyRunRedis@192.168.1.10:6379/0 EASYRUN_WORKERS=4 EASYRUN_DATA_DIR=/mnt/easyrun-share
Order: ① infrastructure (2.2/2.3) → ② control node bootstrap + .env + easyrun serve → ③ each worker machine bootstrap + .env + easyrun worker → ④ mount the shared data dir via NFS (full configuration in 2.8.1 Data path configuration).
Scale out = run step ③ on a new machine; take nodes offline by simply stopping the process (crashed tasks are recovered by the scheduler).
Note: multi-machine deployment is not "run commands only on the control node" — every worker machine must be deployed independently (copy code → bootstrap → configure .env → start). The control node cannot provision remote machines; a worker node joins automatically as soon as it connects to the shared Redis (no central registry, no control-node approval). Worker machines need their own browser binaries (downloaded by bootstrap), which is why each machine must be deployed separately.
Worker nodes can also run as Docker containers: docker compose up -d --no-deps --build worker, with an override pointing EASYRUN_REDIS_URL / EASYRUN_DATABASE_URL at the central infrastructure IPs. --no-deps is required, otherwise compose would also start a local redis/postgres pair on this machine. Docker worker images already bundle the Allure CLI + JRE; artifacts land in that machine's project root ./data/artifacts/ (compose bind-mounts ./data:/srv/data).
2.8.1 Data path configuration (shared artifacts) in detail
Who writes / reads what: the control node (api) generates raw Allure results and HTML (data/artifacts/allure/, allure-html/) and reads all nodes' screenshots from the shared directory to pack attachments; worker nodes write step screenshots to data/artifacts/sessions/ and visual baselines to data/artifacts/baselines/, effectively write-only. So the control node must be able to read every worker's data/artifacts/.
Default paths (when nothing is configured): bare-metal node = <project-root>/data (the EASYRUN_DATA_DIR default); Docker node = host <project-root>/data ↔ container /srv/data (compose bind mount ./data:/srv/data).
Option A (recommended): shared path = the control node's project-root data/ (the control node acts as the NFS server; every node's data stays under its own project data/, zero configuration changes):
# Control node (Linux): sudo apt install -y nfs-kernel-server echo '<project-root>/data 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)' | sudo tee -a /etc/exports sudo exportfs -ra && sudo systemctl enable --now nfs-server # Control node (macOS): # sudo nfsd enable # echo '<project-root>/data -network 192.168.1.0 -mask 255.255.255.0 -maproot=root:wheel' | sudo tee -a /etc/exports # sudo nfsd update # Each worker node: sudo apt install -y nfs-common sudo mkdir -p <project-root>/data sudo mount -t nfs <control-IP>:<control-project-root>/data <project-root>/data echo '<control-IP>:<control-project-root>/data <project-root>/data nfs defaults,nofail 0 0' | sudo tee -a /etc/fstab
Once mounted, worker nodes need zero configuration: the bare-metal default path is that mount point; Docker's ./data:/srv/data points at it automatically. Mount first, then start containers (or docker compose restart after mounting) — a container started before the NFS mount keeps seeing the old local directory. Verification: touch data/nfs-check on a worker should be visible on the control node; after submitting a multi-case plan the console should preview every screenshot; after generating Allure, data/artifacts/allure-html/<run_id>/ should be identical on every node.
Option B: separate shared storage: mount the share at any location (e.g. /mnt/easyrun-share) on every machine (including the control node); bare-metal sets EASYRUN_DATA_DIR=/mnt/easyrun-share in .env; Docker changes the volume to /mnt/easyrun-share:/srv/data in docker-compose.override.yml. The control node must mount the same share — it is the one generating the Allure report.
Key pitfalls: ① exports must include no_root_squash (Docker containers write as root, otherwise Permission denied); ② bare-metal write failures usually mean mismatched uids across machines — align uids or add all_squash,anonuid=<uid>; ③ add nofail to fstab so a missing NFS never blocks boot; ④ without a share the cluster still runs, but cross-node screenshots can't be previewed and Allure attachments are missing — after mounting the share, click "Generate Allure report" again to repack completely; ⑤ the exported data/ tree includes data/postgres/ — postgres accesses it only locally on the infrastructure machine; never start postgres on multiple machines pointing at the same directory.
2.9 Configuration troubleshooting
- DB connection fails at startup: check the PostgreSQL port is reachable (
nc -zv host 5432), password is URL-encoded, driver installed (pip install -e ".[postgres]") - Workers cannot reach the queue: check Redis password/address, driver installed (
.[redis]), and that all nodes point to the same Redis - Reports miss screenshots from other nodes:
EASYRUN_DATA_DIRis not on shared storage - Config precedence: command-line env vars > .env file > code defaults
3. Using the Platform
Case management
- Write steps as "what to do", not "how to click" — the agent translates them into actions; one natural-language step per line
- Each case can set a default target URL (overridable at run time); add at least one assertion covering the business outcome
- Not sure how to write assertions? Use "AI Generate": type "page shows Order No; amount greater than 100" and it converts automatically
- Completion conditions (optional): stop all actions once a text appears (e.g. "中性新闻 ("), preventing redundant actions
- One case = one business scenario, ideally ≤10 steps; batch multiple scenarios with a Plan
Target URL priority
run-time input > case default URL > platform default (Settings page)
Reports
- Timeline: per-step LLM decision reasons / action results / screenshots / assertions / goal hits
- Failures get automatic AI attribution (5 categories) + a defect draft; "Re-run Failed" re-runs only failed cases
- Allure: click "Generate Allure Report" on the report page to generate on demand (with progress status); once saved, the button becomes "View Allure Report" and opens
/allure-html/<run_id>/in a new tab. Runs already generated show the View button directly — no regeneration - Cancel a run anytime (queued tasks are skipped immediately, running tasks stop at the next step; works across machines/containers too); run records support multi-select / select-all batch delete (irreversible, with confirmation)
Cure & export (0-token regression)
- After an explore-mode pass, actions are recorded automatically → a "Cure" button appears → replay mode (no LLM cost)
- "Export code" generates a standalone Playwright script (semantic locators + assertions) that runs outside the platform and fits CI
- Replay waits 3s between actions (dynamic rendering) and settles before assertions — smooth-scroll pages included
4. Assertions & Completion Conditions
9 assertion types (deterministic, executed by code):
| Type | Purpose | target / expected example |
|---|---|---|
text_contains | Body contains text | Order No / — |
url_contains | URL contains fragment | checkout / — |
element_exists | CSS selector exists | .cart-button / — |
element_count | Selector count equals | .product / 3 |
element_text | Element with text exists | Welcome back / — |
text_in_view | Visible element contains text (full DOM, incl. plain div/span labels) | 中性新闻 ( / — |
text_near_top | Text element appears in the upper window area | 中性新闻 ( / 0.4 (threshold optional) |
value_compare | Compare the number after a label (same-element or sibling forms) | Order Amount / >= 100 |
visual | Screenshot matches baseline (baseline auto-created on first run) | checkout / — |
Completion conditions: checked before every action; when all are satisfied, actions stop and assertions run. The min_steps field makes a condition effective only after N executed actions — for cases where the goal state equals the initial state (e.g. the 利好新闻 label is visible on load, but the case must click the date and button first).
Step-bound assertions: an assertion can be bound to a step number (the "Step #" input on the assertion row, 1-99). In explore mode the agent calls case_step_done(step=N) after completing each step, and the platform runs the bound assertions immediately after that step's actions (0-token deterministic checks; fail-fast: self-heal first if configured, otherwise the case fails). If the agent finishes without marking a step, the wrap-up fallback runs all unmarked step assertions in step order, then the unbound ones. Cured replay also runs them at the marker points (markers are saved with the cured actions; seeing case_step_done JSON rows in a deterministic case's step list is normal); exported code generates the bound assertions inline right after the corresponding actions.
How the step number maps to your case: the number is the line number in the case's Steps list (1-based, one step per line). Rules:
- One step can bind several assertions; one assertion binds exactly one step number.
- The number is the step line number, not an action count — one step may correspond to several actions (find + click + wait); actions are decided by the agent.
- In the prompt, steps are listed with numbers, and lines with bound assertions get a "call
case_step_done(step=N)after completing this step" hint appended. - Agent misses a marker, or the number exceeds the step count: the assertion is never lost — the wrap-up fallback runs it in step-number order (it degrades to end-of-run timing, still labeled "step N" in the report).
- No number = runs after all steps complete; adding/removing step lines does not renumber bindings automatically — double-check after editing steps.
Example (the assertion "page shows Order No" is bound to step 4):
| Step | Bound assertion | When it runs |
|---|---|---|
| 1. Open the store homepage | url_contains /home (step 1) | Immediately after step 1's actions |
| 2. Search "mechanical keyboard" | element_count results = 12 (step 2) | Immediately after step 2's actions |
| 3. Click the first result | (none) | — |
| 4. Add to cart and check out | text_contains Order No (step 4) | Immediately after step 4's actions |
| — | value_compare order amount > 100 (no number) | After all steps complete |
5. Execution Policy
| Scenario | Behavior |
|---|---|
| Action execution fails | Terminate immediately — no retry, no alternative |
| Same action requested again | Skip it and prompt the next step (terminates after 2 skips) |
| Case fails | No automatic retry (max_attempts=1) |
| Assertion fails | No self-healing retry — fail directly |
| Resource lock timeout | Fail directly, not re-queued |
| 3 consecutive failures | Quarantined (new runs unaffected) |
| Retry needed | Manual "Re-run Failed" on the report page |
browser_wait / browser_get_text / screenshots are excluded from the repeat-action guard; waiting and reading may be called repeatedly.6. Configuration Reference (env vars, prefix EASYRUN_)
| Variable | Default | Description |
|---|---|---|
EASYRUN_DATA_DIR | ./data | Runtime data (DB/screenshots/Allure/browser binaries) |
EASYRUN_DATABASE_URL | data/easyrun.db | Production: postgresql+asyncpg://user:pass@host/db |
EASYRUN_REDIS_URL | empty (in-process) | Required for multi-machine: redis://host:6379/0 |
EASYRUN_WORKERS | 4 | Concurrent agents; set 0 on pure API nodes |
EASYRUN_MAX_ATTEMPTS | 1 | Automatic retry cap (1 = execute once); ⚙ changeable at runtime on the console Settings page (1-10) |
EASYRUN_MAX_NOOP_REPEATS | 1 | Allowed executions per identical action |
EASYRUN_MAX_SKIPPED_REPEATS | 2 | Max skips for repeated requests |
EASYRUN_MAX_STEPS_PER_CASE | 30 | Action-step cap per case; ⚙ changeable at runtime (3-100, page key max_steps) |
EASYRUN_HEAL_ATTEMPTS | 0 | Self-healing rounds on assertion failure (0 = off); ⚙ changeable at runtime (0-5) |
EASYRUN_REPLAY_STEP_DELAY_MS | 3000 | Delay between replayed actions |
EASYRUN_TASK_TIMEOUT_SECONDS | 600 | Per-task execution limit |
EASYRUN_BROWSER_HEADLESS | true | Headless browser |
EASYRUN_ALLURE_BIN | auto-detect | Allure CLI path (PATH → tools/bin/allure) |
DEEPSEEK_API_KEY | — | DeepSeek API key |
EASYRUN_DEEPSEEK_BASE_URL | api.deepseek.com | OpenAI-compatible endpoint; can point to local vLLM/Ollama |
⚙ = changeable at runtime on the console Settings page (stored in the shared database, consistent across machines, effective on save; saving an empty input clears the override and falls back to the environment default). The "Failure analysis" toggle is also on the Settings page (off = failed tasks skip the deepseek-reasoner call, saving tokens).
7. REST API Summary
GET /api/cases Case list (with integer case_no)
POST /api/cases Create case PUT /api/cases/{id} Update
DELETE /api/cases/{id} Delete case POST /api/cases/{id}/run Run single case
POST /api/cases/{id}/cure Enable replay POST /api/cases/{id}/export-code Export Playwright code
POST /api/cases/assertions/parse NL → assertions
GET /api/plans Plan list POST /api/plans/{id}/run Run plan
POST /api/runs Submit run GET /api/runs?page=&page_size= Paged list
GET /api/runs/{id}/report Aggregated report (with attribution)
POST /api/runs/{id}/cancel Cancel run POST /api/runs/{id}/rerun-failed Re-run failed
DELETE /api/runs/{id} Delete run POST /api/runs/batch-delete Batch delete
POST /api/runs/{id}/allure Allure export+HTML GET /api/trends Trends
GET /api/locators Element repo GET/PUT /api/settings Platform settings
Interactive API docs: /docs (Swagger UI)
8. FAQ
- The case has 6 steps but executed 20+ actions? Steps are natural-language goals; actions are the LLM's per-step decisions (navigate/click/wait/verify…). One step may take several actions, capped by
max_steps_per_case. - Why is there no "Cure" button on some cases? Cured actions are recorded automatically after an explore-mode pass (≥1 action). Cases that never passed, or already-cured cases, don't show it (cured cases keep "Export code").
- Completion condition configured but not firing? Check the actual page text (full-width vs half-width parentheses: 「中性新闻(」vs「中性新闻 (」); if the goal state equals the initial state, combine with
min_steps. - Clicking a news link does nothing? Use
browser_click_linkto click by name inside class=sub-links; smooth-scroll sites need the animation to finish (the platform already polls for render and settles before assertions). - Replay (cured) fails but explore succeeds? The page structure changed — re-run in explore mode to re-cure, or check
EASYRUN_REPLAY_STEP_DELAY_MS. - Times are off by 8 hours? The platform stores UTC and converts to local time for display. Check server/browser timezone settings if still off.
- Can't install the browser on macOS 12? Newer chromium dropped old macOS; bootstrap auto-pins playwright 1.50.
- Can deleted run records be restored? No — deletion removes tasks, events, screenshots and Allure artifacts, with a confirmation prompt first.
9. Cost & Saving Tips
- Explore mode: ~0.5–1.5k tokens per decision (page snapshot dominates); fail-fast + skip-repeats + completion conditions guard against token burn
- Replay / exported code = 0 tokens — always cure high-frequency regression cases
- Failure attribution uses
deepseek-reasoner(pricier), triggered once per failure only - Watch token trends on the Overview page; per-case cost ≈ decisions × tokens per step