Bytebase verify 技能实战:构建并驱动 Playwright E2E 测试验证运行中的应用
【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase
当一次代码变更需要"对着运行中的应用"做最终确认时,Bytebase 仓库内置了名为verify的 Agent 技能(SKILL.md):它定义了一条标准操作路径——先用embed_frontend标签构建出内嵌前端的后端二进制,再用 Playwright E2E 测试框架拉起一次性 Bytebase 服务器,运行与变更相关的场景,并以"用户可见结果"作为证据。读完本篇,你可以完整复现这条"构建—运行—取证"验证链路,并理解测试框架如何自动完成服务器启动、管理员注册、样例实例供给和清理,以及企业 License、浏览器选择等前置条件背后的实现细节。
技能定位:什么情况下应该执行 verify
verify技能的描述是:"Build and drive Bytebase with the Playwright E2E harness when a change needs verification against the running app"(当变更需要对着运行中的应用做验证时,构建并用 Playwright E2E 框架驱动 Bytebase)。它把验证过程收敛为三个动作:构建(Build)、运行受影响场景(Run the affected scenario)、取证(Evidence)。
技能文档明确了两条前置阅读约定,两者都是仓库根目录下的相对路径:
- 前置条件、License 配置、浏览器选择与故障排查,见 E2E README;
- 编写或修改测试之前,先读 E2E 编写规范。
技能要求所有命令默认在仓库根目录执行(除非命令本身切换了目录),这也决定了下文所有命令的起始位置。
构建:embed_frontend 标签是 UI 可被服务的必要条件
构建分两步:
pnpm --dir frontend release go build -tags embed_frontend -ldflags "-w -s" -p=16 -o ./bytebase-build/bytebase ./backend/bin/server/main.go两条命令各司其职:
pnpm --dir frontend release:在frontend目录下执行前端发布构建,产出静态资源包;go build -tags embed_frontend ...:以embed_frontend构建标签编译后端服务器入口 backend/bin/server/main.go,把前端打包进 Go 二进制,输出到./bytebase-build/bytebase。-ldflags "-w -s"去掉调试信息与符号表以缩小体积,-p=16控制并行编译数。
embed_frontend标签不是可有可无的选项。从源码结构看,后端按构建标签提供了两个互斥的实现:
- server_frontend_embed.go 首行声明
//go:build embed_frontend,仅在该标签下参与编译,负责把前端页面服务出去; - server_frontend_not_embed.go 声明
//go:build !embed_frontend,没有标签时编译,只输出一行日志:"Skip embedding frontend, build with 'embed_frontend' tag if you want embedded frontend."
E2E README 的故障排查表也印证了这一点:如果启动后报 "This Bytebase build does not bundle frontend",说明二进制没有带embed_frontend标签编译,需要按上述命令重新构建。
另一条同样重要的纪律:代码变更后必须重新构建受影响的前端 bundle / 后端二进制,让测试真正覆盖到修复点,而不是在旧产物上跑一遍得出假阳性结论。
前置条件:License、浏览器与 psql
企业 License 是硬性要求(BYTEBASE_E2E_LICENSE)
E2E 套件覆盖的是企业版功能(数据脱敏 masking、JIT 抽屉、审批规则、查询数据策略门控如disableExport/disableCopyData、数据库分组等),因此License 是必需的:没有BYTEBASE_E2E_LICENSE时,global setup 阶段会快速失败(fail fast),不存在免费计划的回退路径。
实现层面,mode-start-new-bytebase.ts 的startServer()在拉起服务器进程之前就先校验 License 是否存在:
const license = process.env.BYTEBASE_E2E_LICENSE?.trim(); if (!license) { throw new Error( "BYTEBASE_E2E_LICENSE is required to run the e2e suite. Set it to a valid " + "enterprise license JWT (signed by Bytebase's license key) and re-run. " + "The suite does not run on the free plan.", ); }这个"先校验、后启动"的顺序是有意的:License 缺失时绝不会留下孤儿服务器进程。校验通过后,登录管理员 API 会话,再通过PATCH /v1/subscription/license(api.uploadLicense(license))安装 License。
License JWT 由 Bytebase 的 License RSA 密钥签名,需向 Bytebase 运维申请开发/测试用 License,且绝不能提交进仓库或粘贴到聊天、issue 中。README 给出的本地存放推荐做法(三选一):
# (a) 仓库外文件 + 按需注入(推荐) mkdir -p ~/.config/bytebase chmod 700 ~/.config/bytebase echo '<your-jwt>' > ~/.config/bytebase/e2e-license chmod 600 ~/.config/bytebase/e2e-license # 运行套件时: BYTEBASE_E2E_LICENSE=$(cat ~/.config/bytebase/e2e-license) \ pnpm exec playwright test # (b) direnv(cd 进仓库自动加载) echo 'export BYTEBASE_E2E_LICENSE=$(cat ~/.config/bytebase/e2e-license)' >> .envrc direnv allow # (c) Shell rc / 登录配置(gitignored 文件中持久导出) export BYTEBASE_E2E_LICENSE='<your-jwt>'README 特别警告:不要把 JWT 放进frontend/.env*或仓库内任何文件(即使已 gitignore)——.env*文件极易被 IDE 打开、意外提交或出现在截图中。
浏览器选择:BYTEBASE_BROWSER_CHANNEL 作为离线逃生通道
Playwright Chromium 默认由globalSetup自动安装。从源码结构看,global-setup.ts 的ensureBrowser()会在启动服务器前执行pnpm exec playwright install chromium;但当BYTEBASE_BROWSER_CHANNEL被设置时会自动跳过——该模式驱动本机已安装的浏览器(如 Chrome),不需要任何下载:
if (process.env.BYTEBASE_BROWSER_CHANNEL) { return; // channel 模式驱动本地浏览器,跳过 Chromium 自动安装 } execFileSync("pnpm", ["exec", "playwright", "install", "chromium"], { stdio: "inherit" });浏览器缓存与@playwright/test的版本绑定(存放在全局缓存而非node_modules),因此版本升级会使缓存失效,而自动安装机制会在下次运行时自愈。playwright.config.ts中对应的配置是channel: process.env.BYTEBASE_BROWSER_CHANNEL || undefined(playwright.config.ts)。
技能文档的表述是:当浏览器下载不可用时,设置BYTEBASE_BROWSER_CHANNEL=chrome使用本地安装的 Chrome。
psql 客户端
DDL 准备需要psql在 PATH 上:Bytebase 的查询 API 是只读的,测试通过 Unix socket 直连样例 Postgres 执行 DDL/DML。缺失时表现为spawn psql ENOENT,安装 Postgres 客户端即可(brew install postgresql或apt install postgresql-client)。
完整环境参数表
以下参数完整继承自 E2E README:
| 变量 | 是否必需 | 默认值 | 说明 |
|---|---|---|---|
BYTEBASE_BIN | 否 | ./bytebase-build/bytebase | 预构建二进制路径 |
BYTEBASE_STARTUP_TIMEOUT | 否 | 300000(5 分钟) | 服务器启动超时(毫秒) |
BYTEBASE_HEADED | 否 | — | 设为1使用有头浏览器 |
BYTEBASE_BROWSER_CHANNEL | 否 | — | 驱动本地安装的浏览器渠道(如chrome)替代下载版 Chromium,跳过 Chromium 自动安装;下载不可用(离线)时有用 |
BYTEBASE_E2E_LICENSE | 是 | — | 企业 License JWT(见上文) |
CI | 否 | — | 启用 CI 专用 reporter |
运行受影响场景
在BYTEBASE_E2E_LICENSE配置好后,从frontend目录运行与变更相关的场景:
cd frontend pnpm exec playwright test sql-editor/sql-editor-lsp.spec.ts --reporter=list技能文档以sql-editor-lsp.spec.ts作为示例 spec,要求把它替换为与本次变更实际相关的场景——验证的粒度是"受影响的路径",而不是全量回归。
运行方式上还可以按需选择:
# 有头模式,肉眼观察浏览器执行过程 BYTEBASE_HEADED=1 pnpm exec playwright test # 只跑某个功能目录 pnpm exec playwright test masking-exemption配置层面(playwright.config.ts)值得注意几个决定执行语义的项:fullyParallel: false加workers: 1意味着测试完全串行;testDir为./tests/e2e,两个 project 分别是名为setup的 setup-project.ts(完成认证与发现)和chromium主项目(依赖setup,加载.auth/state.json的 storageState);CI环境下 retries 为 2 且 reporter 切换为github。
测试框架托管一次性服务器:不要手工搭建 bootstrap
技能文档强调:harness 拥有(owns)一次性服务器启动、认证、样例实例供给和 teardown,应使用其现有实现,而不是手工拼 bootstrap。从 mode-start-new-bytebase.ts 的startServer()可以看到这条托管链路的全貌:
- 清理孤儿进程(
cleanupOrphans):读取/tmp/bytebase-e2e-pid中记录的 PID 与临时目录,先验证该 PID 确实属于 bytebase 进程(防止 OS 复用 PID 后误杀无关进程),再按进程组 SIGTERM 并删除临时数据目录; - 端口分配:Bytebase 基于主
--port值分配三个端口——PORT主 HTTP 服务、PORT + 2内嵌元数据 Postgres、PORT + 3项目级样例 Postgres。findAvailablePort()从默认端口 18234 起,每轮检查[0, 2, 3]三个偏移是否全部空闲,否则步进 4 重试,最多 100 轮; - 启动二进制:以
detached: truespawnbytebase --port <port> --data <tempDir>,并强制注入LANG=en_US.UTF-8(内嵌 Postgres 的 initdb 需要有效 UTF-8 locale,否则在 macOS 上 Postgres 17 会报 "postmaster became multithreaded during startup"),同时置空PG_URL; - 分阶段就绪轮询(统一走
pollUntil,500ms 间隔、以BYTEBASE_STARTUP_TIMEOUT为截止):- Phase 1:轮询
/healthz直到 HTTP 200; - Phase 2:重试注册首个管理员
demo@example.com/12345678——服务器完成元数据 schema 迁移前 signup 会返回 4xx,轮询天然覆盖这个窗口;首个注册用户自动成为工作区管理员; - Phase 3:登录换取 bearer token,随后安装 License(Phase 3b),再供给 DBA 测试用户
dba1@example.com并授予roles/workspaceDBA(Phase 3c,供 plan-detail 审批场景用作第二审批人); - Phase 4:创建
project-sample项目并调用PrepareSampleProjectInstance供给其实例,轮询listInstances直到实例可见; - Phase 5:探测
PORT+3的样例 Postgres 是否接受连接。这里有个实现细节:样例 Postgres 以-h ""(不监听 TCP)加-k /tmp(Unix socket 目录)方式启动,所以 TCP 探测永远失败,isPortListening先探测 Unix socket/tmp/.s.PGSQL.<port>,探测失败才回退 TCP。
- Phase 1:轮询
global-setup.ts 还处理了一个 Playwright 自身不兜底的场景:globalSetup抛错时 Playwright 不会执行globalTeardown,所以一旦startServer失败(例如/healthz超时),必须当场调用stopServer()拆掉半启动的服务器,否则孤儿进程组与临时目录会让每一次后续运行都难以启动。stopServer()本身也是精细的:SIGTERM 后异步等待最多 15 秒,未退出则 SIGKILL 进程组,再删临时目录与 PID 文件——注释解释了原因:在内嵌 Postgres 尚未退出时删除数据目录,会使 checkpointer PANIC("could not fsync pg_xact/0000")并残留 socket 文件,进而干扰下一次启动的端口探测。
所有关键上下文(baseURL、管理员凭据、样例项目与实例资源名)通过 env.ts 写入frontend/.e2e-env.json;各 spec 通过loadTestEnv()取回环境数据并获得BytebaseApiClient实例(v1 REST API 封装,401 时自动刷新 token)。
取证:验证用户可见的结果,包括失败路径
技能文档的 Evidence 一节给出了验证的判定标准:
- 验证用户可见的结果,并且必须包含与本次变更相关的失败路径——只验证 happy path 不算完成验证;
- 对于 LSP 相关的工作,参考现有 LSP spec 的 buffer 辅助函数与 websocket 断言;连接状态是 tooltip(悬停提示),不是页面上的持久文本——这是一个极易踩中的断言陷阱:把连接状态当成持久 DOM 文本来断言会写出不稳定的测试;
- 汇报内容:场景(scenario)、结果(result),以及任何缺失的前置条件或未能验证的行为(missing prerequisite / unverified behavior)。
以 sql-editor-lsp.spec.ts 为例,它是一条"经由/lspwebsocket 的补全冒烟测试":监听page.on("websocket")捕获 LSP 通道,用 fixture 表中一个"名字永远不会出现在编辑器 buffer 或样例数据里"的表来证明补全确实来自 LSP 响应而非本地 buffer 拼接;清理 buffer 时也是通过测量字符数后逐次退格,而不是依赖全局清空快捷键。这类细节正是技能要求"先读现有 LSP spec 再动手"的原因。
AGENTS.md 中"Assertions and regression coverage"部分对取证提出了更高要求:对复现出的回归,要证明测试在错误行为上失败、在修复后通过(必要时可用临时变异验证);对已知未修复的回归用test.fail()让"意外通过"可见;权限敏感流程要分别以有权限和被限制的用户各测一遍。
常见故障速查
继承自 E2E README 的 Troubleshooting 表:
| 症状 | 修复 |
|---|---|
Bytebase binary not found | 执行上文构建命令,或设置BYTEBASE_BIN |
spawn psql ENOENT | 安装 Postgres 客户端:brew install postgresql或apt install postgresql-client |
| 服务器无法启动 / 端口被占用 | lsof -ti:18234 \| xargs kill,并检查残留的bytebase-e2e孤儿进程 |
| 过期 PID 文件 | rm /tmp/bytebase-e2e-pid |
| 过期认证状态 | rm -rf frontend/.auth/ frontend/tests/.auth/ |
postmaster became multithreaded during startup(macOS) | shell 缺少有效 UTF-8LANG;harness 启动时会默认设置,手工运行二进制时导出LANG=en_US.UTF-8 |
安装 License 报token signature is invalid | License 是用生产密钥签名的,需要以-tags embed_frontend,release构建 |
| "This Bytebase build does not bundle frontend" | 二进制未带embed_frontend标签编译,按前置构建命令重构建 |
小结
verify技能把"对着运行中的应用验证变更"压缩成三步可执行清单:带embed_frontend标签构建内嵌前端的二进制(pnpm --dir frontend release+go build -tags embed_frontend ...);配置BYTEBASE_E2E_LICENSE(必要时BYTEBASE_BROWSER_CHANNEL=chrome)后在frontend下用pnpm exec playwright test <scenario>运行受影响场景,服务器生命周期完全交给 harness 托管;最后以用户可见结果(含失败路径)为证据汇报场景、结果与未验证项。配合 E2E README 的前置条件与 AGENTS.md 的编写规范,以及 framework 目录 中global-setup.ts、mode-start-new-bytebase.ts、setup-project.ts等托管实现,构成了 Bytebase 仓库内一条从"改了代码"到"验证修复"的完整、可复现的工程链路。
【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考