☰
Midscene.js + GitHub Actions:搭建无人值守的定时巡检系统
2026/10/20 17:11:23 网站建设 项目流程

1. 项目思路与场景拆解

1.1 定时巡检到底解决什么问题

先说个很现实的场景。我维护的一个小型搜索应用,平时看着挺正常,但总有那么几次是用户跑来跟我说“收藏列表打不开了”,我才知道线上出了问题。你回头查的时候,可能是前天发版把某个接口字段改了,也可能是某个活动下线后遗留了一段无效代码,前端页面没报错,但核心交互就是静默地挂了。这种问题最头疼,因为它不崩、不红、不报错,就是功能没反应。

人工巡检确实能发现一部分,但靠人肉每天早上点一遍页面,坚持一周就烦了,坚持一个月基本就变成“例行打开看一眼”,根本点不到深层交互。这个痛点正好是自动化巡检的用武之地:把核心用户路径固化成一堆脚本,让它在固定时间自己跑到线上走一圈,挂了就截图留证据、发通知。这次我把 Midscene.js 的自动化任务接进了 GitHub Actions,用定时调度实现了一套无人值守的巡检闭环,整个方案跑了一个多月,确实帮我提前抓住了几次问题。

这套方案适合谁?适合手上有线上业务、又不想维护一台专门跑巡检的服务器的人。特别是用 GitHub 管理代码的团队,前端、测试、独立开发者都可以直接照着搭。Midscene.js 负责“怎么测”,GitHub Actions 负责“什么时候测、测完怎么通知”,两者一组合,就是一个性价比很高的线上质量小卫兵。

1.2 为什么选 Midscene.js 而不是纯 Playwright

其实我最早做巡检用的是纯 Playwright 写脚本,选择器、等待、断言全手写。问题在于:页面一旦轻微改版,定位器就失效,脚本三天两头要改。我维护的页面虽然不大,但只要前端结构调整,巡检脚本的维护成本立刻压过来。

Midscene.js 的差异在于它允许你用近乎自然语言的方式描述测试意图,底层再通过视觉理解和模型能力去匹配页面元素。我写“在顶部的搜索框输入关键词,点击搜索按钮,断言结果列表出现”,脚本内部自己去找对应元素,而不是依赖死板的 xpath 或 text 选择器。页面上按钮位置挪了、文案变了,只要视觉语义没变,它大概率还能跑通。这一点对我这种小团队太重要了,因为没人愿意每周花半天去修巡检脚本。

另外 Midscene.js 支持生成可视化报告和截图,我会在后面的配置里详细说。GitHub Actions 则是天然契合:仓库就在上面,定时任务直接用 cron 语法配置,跑完的产物可以直接上传到 Actions 页面留存,还支持手动触发,排查问题非常方便。不需要额外服务器,不占用本地电脑,免费额度对个人项目足够用。

2. 环境准备与脚本改造

2.1 最小项目结构与依赖安装

要让整个流程跑起来,先把项目结构梳理清楚。我建议把巡检脚本独立成一个子目录,不要和你业务代码混在一起。我的典型结构长这样:

daily-inspect/ ├── .github/ │ └── workflows/ │ └── daily-inspect.yml ├── scripts/ │ ├── search-flow.ts │ └── utils.ts ├── reports/ # 本地跑的时候生成报告 ├── package.json ├── tsconfig.json └── .env.local # 本地用的环境变量,别提交到仓库

依赖安装方面,我用的是最常规的组合:

npm init -y npm install @midscene/cli playwright @midscene/playwright npm install -D typescript tsx npx playwright install chromium

这里有两个点需要解释一下。第一,浏览器我只装了 chromium,没有装全量的 chrome、firefox、webkit,因为巡检场景里 chromium 一个就够了,装多了只会拖慢 GitHub Actions 的执行时间。第二,本地调试时我用.env.local存模型服务的 API Key,GitHub Actions 里则用 Repository Secrets,两边互不干扰。

Midscene.js 的核心能力需要调用大模型做页面语义识别,所以要准备一个模型服务的 API Key。这类 Key 一定要走环境变量,绝不能硬编码进脚本。我在配置里统一命名为MIDSCENE_MODEL_API_KEY,本地和 CI 环境都读这个变量,省得脚本里写两套逻辑。

2.2 用自然语言写巡检用例

接下来是核心部分:把你要巡检的业务流程翻译成 Midscene.js 能执行的脚本。我拿自己那个搜索应用举例,巡检目标是验证“用户搜索关键词后,结果列表能正确渲染,并且第一条结果的标题非空”。用代码写的思路大致长这样:

import { Agent } from '@midscene/core'; const agent = new Agent({ modelApiKey: process.env.MIDSCENE_MODEL_API_KEY!, browser: 'chromium', }); async function runSearchFlow() { await agent.openPage('https://example-test-site.com/search'); await agent.run(` 请在页面顶部的搜索框中输入"自动化测试工具", 然后点击旁边的"搜索"按钮; 等待结果列表加载完成; 断言页面上出现了至少一个带标题的结果项; 如果断言通过,把整个页面的截图保存为"搜索流程-成功.png"; 如果断言失败,截图保存为"搜索流程-失败.png"。 `); await agent.exportReport(); } runSearchFlow().catch((e) => { console.error('巡检失败: ', e); process.exit(1); });

这段代码的关键在于agent.run()里那段中文描述。Midscene.js 的模型能力会把这段描述拆解成一系列动作指令,自己完成“找输入框、点击、等待、断言”的操作。你不需要写page.locator('#search-input')这种代码,只要把用户视角的“行为意图”写清楚就行。

这里我踩过一个坑:指令描述尽量拆成短句,每一步都明确是“做什么”和“等待什么”。如果一句话写太长,模型拆解的稳定性会下降,偶尔会把“点击搜索”和“等待结果”合并成一步,导致断言时机太早。改成上面那种分步骤写法之后,稳定性明显上来了。

另外,我习惯在每个关键步骤都安排一次截图。机器的巡检和人工巡检一样,得有“证据”。截图不仅能确认结果,一旦巡检失败,你翻日志时能直接看到页面当时长什么样,而不是靠猜。Midscene.js 的导出报告会把所有动作、截图、耗时整理成一个 HTML 文件,我后面会让 Actions 把它作为构建产物保存下来。

2.3 本地先跑通这一关必须较真

接入 CI 之前,我强烈建议你先在本地把整套脚本跑通。因为 GitHub Actions 环境虽然和本地接近,但它是个干净的虚拟机,任何没在本地验证过的问题,到 CI 里都会被放大。常见如浏览器二进制缺失、依赖没装全、绝对路径写错、环境变量没传,都会让你多调试好几轮。

本地跑的命令很简单:

set -a && source .env.local && set +a npx tsx scripts/search-flow.ts

这里用tsx直接跑 TypeScript 脚本,不用先编译再执行,调试时改一行立刻就能重跑。如果本地能顺利生成报告和截图,再进入 Git 操作。

本地调试时我会开非无头模式,让浏览器窗口弹出来,肉眼看着它一步步操作。虽然 CI 里肯定是 headless,但本地调试阶段建议先关掉 headless,因为自然语言驱动的自动化在出错时,你光看日志不一定能理解模型为什么做出某个行为,眼看一遍最快。确认每一步动作符合预期后,再把 headless 打开跑一遍完整流程,确保无头模式下结果一致。

提示:本地和 CI 的 base URL 要区分。我习惯通过环境变量INSPECT_BASE_URL控制目标地址,而不是把域名硬编码进脚本。本地用测试站,CI 用线上真实地址,同一套脚本两端复用,减少维护量。

3. 接入 GitHub Actions 流水线

3.1 workflow 文件怎么写

现在到了重头戏:把巡检脚本接入 GitHub Actions,做成定时任务。核心就是一个 workflow 的 YAML 文件,放在.github/workflows/目录下。我用的配置如下:

name: daily-inspect on: schedule: - cron: '0 0 * * *' workflow_dispatch: jobs: inspect: runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 20 cache: npm - name: Download Playwright Browser run: npx playwright install --with-deps chromium - name: Install Dependencies run: npm ci - name: Run Inspection env: MIDSCENE_MODEL_API_KEY: ${{ secrets.MIDSCENE_MODEL_API_KEY }} INSPECT_BASE_URL: ${{ secrets.INSPECT_BASE_URL }} run: npx tsx scripts/search-flow.ts - name: Upload Reports if: always() uses: actions/upload-artifact@v4 with: name: inspect-reports path: reports/

我先重点讲一下on里面的配置逻辑。schedule.cron定义定时触发的时间点,GitHub Actions 使用的 cron 表达式是 UTC 时区的标准五段格式:分、时、日、月、周。0 0 * * *表示每天 UTC 零点,换算成北京时间就是早上 8 点。如果你不关心必须几点跑,其实定时时间的精度要求并不高,关键是理解时区换算,否则你会发现自己配置的“晚上 8 点”实际是在“凌晨 4 点”跑的。

workflow_dispatch一定要加。它允许你在 Actions 页面上手动点击“Run workflow”按钮,调试时不用干等定时触发,一次手动执行就能验证整条链路是否通。我第一次接好后就是用这个按钮直接触发的,省了整整一天的等待时间。

3.2 运行环境里的坑

automation 任务在云端虚拟机跑,很多东西和本地不完全一样。我总结几个一定会遇到的点,提前说能帮你少走弯路。

第一,浏览器必须提前装好。我用了npx playwright install --with-deps chromium,重点在--with-deps。它不只是下载 chromium 二进制,还会安装一堆系统级依赖库。GitHub Actions 的 ubuntu 镜像虽然比较全,但偶尔缺一两个库就会导致启动浏览器时崩溃,报错信息又不太直观。加上--with-deps之后问题少很多。

第二,依赖安装顺序有讲究。我先执行playwright install再执行npm ci。因为 Midscene.js 或 Playwright 相关的 postinstall 脚本可能会触发浏览器下载,如果包都没装好就直接下载,容易遇到找不到目标目录的问题。反过来,如果先npm ci再playwright install,逻辑上也通,但 install 必须发生在启动脚本之前,这一点顺序别搞反就行。

第三,timeout-minutes: 30不是随便写的。GitHub Actions 的 job 默认超时是 6 小时,听着很宽松,但如果脚本真的卡住,你会白白等上很久才算失败。巡检任务通常是轻量的,正常情况 3 分钟之内就能跑完。我设成 30 分钟,既留足余量,又能保证一旦挂起不会拖太久。

第四,Upload Reports里的if: always()不能省。默认情况下,只有任务成功才会执行后续步骤。但巡检失败时恰恰是最需要报告的时候,if: always()保证不管脚本退出码是什么,截图和报告都会上传到 Actions 的 Artifact 区,你可以随时下载查看当时的页面状态。

注意:GitHub Actions 对schedule任务有个限制——最短触发间隔是 5 分钟,而且不保证严格准点执行,特别是免费账号,排队延迟几秒到几分钟都正常。巡检任务没必要追求秒级精度,能容忍这个不确定性就完全没问题。

3.3 结果反馈要做闭环

定时任务跑完不能只是“日志里显示失败”就算完,得有主动通知。这里我做了两层闭环,分享出来供你参考。

第一层是构建产物留档。每次跑完,报告和截图上传到 Artifacts,哪怕用户没看,数据也沉淀在那里。后续想回溯某天的巡检情况,直接按日期翻 Artifacts 就行,非常方便。这一层相当于“留痕”。

第二层是失败通知。我在 workflow 里加了一个专门的通知步骤,照样写在同一个 YAML 文件里:

- name: Notify on Failure if: failure() run: | curl -s -X POST "${{ secrets.INSPECT_WEBHOOK_URL }}" \ -H 'Content-Type: application/json' \ -d '{"text":"线上巡检失败,请查看 Actions 日志确认问题。"}'

这个 webhook 可以指向任何支持入站消息的群机器人地址。我目前用的是工作群里接的一个机器人,巡检失败时会弹一条消息,团队成员都能看到。设置好后,我基本不需要主动去刷 Actions 页面,有问题系统自己来找我。

顺带一提,如果有天你希望“失败后自动提一个 Issue”,也可以接一个创建 Issue 的 action,但要注意防止重复提:跑之前先搜索是否存在未关闭的同名 Issue,存在就跳过。我的经验是,对个人项目来说群通知够了,Issue 自动化更适合多人团队的任务管理。

4. 实际运行与问题排查

4.1 定时任务没触发,先查这四件事

接入一个月来,我遇到过几次“到点了没跑”的情况,排查思路基本固定在四件事上。

第一,先看 Actions 页面左侧有没有这个 workflow,再点进去看有没有 Schedule 标签页。如果页面压根没有,说明 YAML 语法有错,仓库默认分支上可能也存在问题。GitHub 解析 workflow 时遇到语法错误会静默忽略,不给你明显的报错提示,很容易漏掉。

第二,确认 cron 表达式的时间基准。如果你按北京时间写30 8 * * *,实际执行点却是北京时间下午 4 点,那就是时区换算的锅。GitHub Actions 的 cron 严格按 UTC 处理,没有时区配置项,只有自己换算。

第三,看触发记录里对应时间点有没有“跳过”或“失败”的标记。有时候定时任务确实触发了,但之前的 job 排队长,实际运行时间偏移了不少。Actions 页面的 “Run history” 列能按时间筛选,仔细看一眼就明白。

第四,检查仓库的 actions 权限设置。仓库 Settings 里的 Actions 权限如果被限制,第三方 action 和部分系统行为会被阻断,表现就是 job 一启动就失败。这个默认是正常的,但如果你从某个模板仓库复制了一个带奇怪权限配置的 workflow,就要留意。

4.2 跑起来但秒失败,怎么定位

定时任务触发了,job 也启动了,但脚本几秒内就报错退出。这种“秒失败”大多集中在三个原因。

最常见的是浏览器没装好。报错信息通常是类似browserType.launch: Executable doesn't exist at ...这样,解决方案就是回到 workflow 里确认npx playwright install --with-deps chromium这步执行成功。有一次我发现这步没有输出“download complete”,原因是 npm 缓存命中了旧包,install 命令被跳过了。后来我给 install 步骤加了npx playwright install --force chromium,强制重新下载,问题解决。

其次是 API Key 没传对。自然语言驱动依赖模型服务,如果MIDSCENE_MODEL_API_KEY为空或者过期的 Key,脚本会在第一次调用模型时直接抛认证错误。排查方法是把 Key 重新设置一遍,然后在本地先验证 Key 的有效性,再推到 GitHub Secrets。注意 Secrets 的值更新后不会立刻全局缓存,可能几分钟后再跑才生效。

然后是超时问题。Midscene.js 默认会等待元素出现,但如果页面上某个东西一直加载不出来,会卡到超时才失败。我之前在脚本里写了一个“等待结果列表出现”的步骤,结果某次线上接口响应特别慢,整个巡检任务跑了 20 多分钟才失败。后来我把关键等待步骤拆开,每一步单独设置超时,整体失败速度大幅提升,定位也更容易。

4.3 常见问题速查表

症状可能原因解决方案
定时任务完全不触发YAML 语法错误 / 分支不对检查 workflow 文件是否在默认分支,尝试手动 dispatch 触发
触发时间和你预期不一致UTC 与时区换算错误统一按 UTC 计算,或改成需求不敏感的宽松时段
job 启动后瞬间失败浏览器二进制缺失加上--force重新 install chromium
脚本第一次调模型就报错API Key 无效或未传在本地验证 Key,更新到 GitHub Secrets
页面元素加载慢导致超时未拆分布骤超时对关键步骤设置独立 timeout 参数
失败时拿不到报告上传步骤未设always()给 upload-artifact 步骤加if: always()
免费额度被跑满定时频率过高降低 cron 频率,去掉不必要的运行

这张表我直接贴在项目 README 里了,每次出问题先查一遍,大多数都能快速定位。

5. 经验总结与进阶建议

这套“Midscene.js + GitHub Actions”的定时巡检方案落地之后,我最大的体会是:脚本本身只占三成功劳,另外七成在任务调度和结果可达性上。GitHub Actions 提供了免费的 cron 调度,Midscene.js 降低了脚本维护成本,但真正让你“睡得着觉”的,是失败通知和报告留档这两条链路。通知能让你第一时间知道,报告能让你知道当时发生了什么,这两者缺一不可。

最后再分享一个小技巧:我后来在脚本开头加了一行执行日志,记录“开始巡检 + 时间 + 目标环境”,脚本结尾再打一行“巡检结束 + 通过/失败 + 总耗时”。这样每次跑完,Actions 日志第一眼就能看到整体状态,不用翻到最后才知道结论。配合通知机器人,整个巡检体系基本做到了无人值守。

如果你想在这个基础上继续扩展,可以做的事还挺多的:多环境轮询(预发、正式环境同一套脚本)、全链路场景拆分(不只是一个搜索流程,把注册、登录、下单都拆成独立脚本分别调度)、失败自动重跑一次确认是否偶发等。把这些任务都接进同一套 workflow,巡检会从“单点检查”变成一张质量防护网,价值会大很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询