## e2e 测试基础设施 ### 测试辅助工具 - test/fake-ffmpeg/main.go: 极简 FFmpeg 替身程序,响应 -version 参数 - test/ffmpeg-mock-server/main.go: 模拟 FFmpeg 下载服务器 - 启动时自动编译 fake-ffmpeg 并打包成 zip(含 bin/ffmpeg.exe 路径) - GET /ffmpeg.zip: 支持 ?speed=<字节/秒> 限速和 ?fail=true 失败模拟 - POST /control: 运行时动态修改限速/失败行为 - GET /health: 等待 zip 就绪后返回 200(供 Playwright 做启动检查) ### 测试 Fixture - tests/e2e/fixtures/test-tools-config.json: remotetools 测试配置,将所有 平台的 FFmpeg 下载 URL 重定向至 127.0.0.1:8890/ffmpeg.zip - tests/e2e/fixtures/ffmpeg-test-config.template.yml: bgo 测试配置 - 端口 8085(避免与正常测试冲突) - ffmpeg_path 指向不存在路径(强制走 remotetools 下载流程) - 独立 app_data_path(避免与正常测试的缓存互相干扰) ### 后端调试注入端点(dev 构建专有) - src/servers/debug_routes_dev.go: POST /api/debug/ffmpeg/force-state 供 e2e 测试注入任意 FFmpeg 状态,无需实际触发下载 - src/servers/debug_routes_stub.go: 非 dev 构建的空实现 - src/servers/server.go: 注册 registerDevDebugRoutes(apiRoute) - src/tools/tools.go: 导出 ForceFFmpegStatus() 供调试端点调用 - src/tools/dev_config.go: 支持 REMOTETOOLS_CONFIG 环境变量覆盖 remotetools 配置路径,使下载流程测试可指向 mock server ### E2E 测试文件 - tests/e2e/ffmpeg-status.spec.ts: 9 条状态注入测试(在正常配置下运行) - 验证 ready/checking/downloading/not_found/error 各状态的 Banner 显示 - 验证状态切换后 Banner 正确消失 - 验证 GET /api/ffmpeg/status 返回格式和 Alert 类型 - tests/e2e/ffmpeg-download.spec.ts: 3 条完整下载流程测试(需 mock server) - 慢速下载时出现 downloading Banner,加速后 Banner 消失 - 下载失败时显示 error Banner - 页面刷新后正确恢复当前状态 - playwright.ffmpeg.config.ts: 下载流程测试专用配置 - 运行 ffmpeg-mock-server 和使用独立 bgo 实例(端口 8085) - 通过 REMOTETOOLS_CONFIG 环境变量注入测试用 remotetools 配置 - 每次运行前清空 external_tools 目录,确保测试从干净状态开始 - playwright.config.ts: 新增 testIgnore 排除 ffmpeg-download.spec.ts (该文件需要 mock server,仅在 playwright.ffmpeg.config.ts 下运行) ## Bug 修复(对应 PR #1150 code review) ### Fix 1: WaitForToolsInit 错误分支死代码(tools.go) 原代码将 WaitForToolsInit 返回的所有非 nil 错误都当作 ctx 取消处理, 但 WaitForToolsInit 在 Init() 失败时也返回该错误,导致: - FFmpeg 状态永久停留在 "checking" - not_found/error 状态永远不会被设置 修复:通过 ctx.Err() != nil 区分 ctx 取消和 Init 失败,失败时 正确调用 setFFmpegStatus({State: "not_found"}) ### Fix 2: 前端竞态条件(ffmpeg-banner/index.tsx) 原代码将 handleSSEMessage 定义在 useEffect 外部(useCallback), 导致:HTTP 初始化请求如果晚于 SSE 推送到达,会用旧状态覆盖新状态, 使 Banner 重新出现并永久卡在旧状态;且组件卸载后 HTTP 回调仍会 调用 setStatus 造成内存泄漏。 修复: - 将 handler 移入 useEffect,添加 active flag 防止卸载后更新 - 添加 sseReceived flag,HTTP 响应到达时若已收到 SSE 则忽略 - 移除不再需要的 useCallback import ### Fix 3: FFmpegAsyncInit 过早访问 remotetools(tools.go) 原注释称"先检查系统 PATH / 配置文件中的 FFmpeg,不需要 remotetools", 但实际调用 utils.IsFFmpegExist → utils.GetFFmpegPath → remotetools.Get().GetTool(), 在 Init() 完成前访问未配置好的 remotetools,可能产生误判。 修复:新增 isFFmpegAvailableWithoutRemotetools() 仅检查配置路径 (os.Stat)和系统 PATH(exec.LookPath),不触碰 remotetools。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
import { defineConfig, devices } from '@playwright/test';
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
import * as dotenv from 'dotenv';
|
||
|
||
// 加载本地环境变量配置(.env.local 不会被 git 跟踪)
|
||
dotenv.config({ path: path.join(__dirname, '.env.local') });
|
||
|
||
// 在测试前复制配置模板到 test-output 目录
|
||
// 这样配置文件的变化不会被 git 跟踪
|
||
const configTemplate = path.join(__dirname, 'tests/e2e/fixtures/test-config.template.yml');
|
||
const configRuntime = path.join(__dirname, 'test-output/test-config.yml');
|
||
|
||
// 确保 test-output 目录存在并复制配置
|
||
fs.mkdirSync(path.dirname(configRuntime), { recursive: true });
|
||
fs.copyFileSync(configTemplate, configRuntime);
|
||
|
||
/**
|
||
* Bililive-Go E2E 测试配置
|
||
*
|
||
* 使用 osrp-stream-tester 作为模拟直播流服务器
|
||
* 测试 Web UI 的各种功能
|
||
*/
|
||
export default defineConfig({
|
||
testDir: './tests/e2e',
|
||
// ffmpeg-download.spec.ts 需要独立的 mock server,仅在 playwright.ffmpeg.config.ts 中运行
|
||
testIgnore: ['**/ffmpeg-download.spec.ts'],
|
||
|
||
// 测试输出目录(被 .gitignore 忽略)
|
||
outputDir: 'test-results',
|
||
|
||
// 全局超时:30 秒(快速失败,节省 CI 时间)
|
||
timeout: 30 * 1000,
|
||
|
||
// 期望超时
|
||
expect: {
|
||
timeout: 5 * 1000,
|
||
},
|
||
|
||
// 由于需要共享服务器,不使用完全并行
|
||
fullyParallel: false,
|
||
|
||
// CI 环境检测
|
||
forbidOnly: !!process.env.CI,
|
||
|
||
// 重试策略(CI 中只重试 1 次)
|
||
retries: process.env.CI ? 1 : 0,
|
||
|
||
// 并发控制:由于需要共享服务器,使用单线程
|
||
workers: 1,
|
||
|
||
// 测试报告
|
||
reporter: [
|
||
['list'],
|
||
['html', { outputFolder: 'playwright-report', open: 'never' }],
|
||
],
|
||
|
||
// 共享配置
|
||
use: {
|
||
// 基础 URL(bgo Web UI)
|
||
baseURL: 'http://127.0.0.1:8080',
|
||
|
||
// 仅在失败时截图
|
||
screenshot: 'only-on-failure',
|
||
|
||
// 始终录制 trace,便于调试(可在 Trace Viewer 中查看源代码和调用栈)
|
||
trace: {
|
||
mode: 'on',
|
||
sources: true, // 在 trace 中包含源代码
|
||
snapshots: true, // 包含 DOM 快照
|
||
screenshots: true, // 包含截图用于时间线预览
|
||
},
|
||
|
||
// 仅在失败时录制视频
|
||
video: 'on-first-retry',
|
||
|
||
// 浏览器视口
|
||
viewport: { width: 1280, height: 720 },
|
||
},
|
||
|
||
// 只测试 Chromium(减少测试时间)
|
||
projects: [
|
||
{
|
||
name: 'chromium',
|
||
use: { ...devices['Desktop Chrome'] },
|
||
},
|
||
],
|
||
|
||
// Web 服务器配置
|
||
webServer: [
|
||
// update-mock-server 模拟版本 API 服务器
|
||
{
|
||
command: 'go run ./test/update-mock-server -version 99.0.0 -port 8889',
|
||
url: 'http://127.0.0.1:8889/health',
|
||
reuseExistingServer: !process.env.CI,
|
||
timeout: 60 * 1000,
|
||
stdout: 'pipe',
|
||
stderr: 'pipe',
|
||
},
|
||
// osrp-stream-tester 测试流服务器
|
||
{
|
||
// 本地开发可设置 OSRP_STREAM_TESTER_PATH 环境变量指向本地目录
|
||
// 例如: set OSRP_STREAM_TESTER_PATH=C:/path/to/osrp-stream-tester
|
||
// 如未设置(包括 CI),则从远程拉取最新版本
|
||
command: process.env.OSRP_STREAM_TESTER_PATH
|
||
? `powershell -Command "cd ${process.env.OSRP_STREAM_TESTER_PATH}; go run ./cmd/stream-tester serve --port 8888"`
|
||
: 'go run github.com/kira1928/osrp-stream-tester/cmd/stream-tester@latest serve --port 8888',
|
||
url: 'http://127.0.0.1:8888/health',
|
||
reuseExistingServer: !process.env.CI,
|
||
timeout: 30 * 1000,
|
||
stdout: 'pipe',
|
||
stderr: 'pipe',
|
||
},
|
||
// bililive-go 主程序(使用 dev 构建标签)
|
||
{
|
||
// 在 CI 中使用预构建的二进制,本地使用 go run
|
||
// 配置文件从模板复制到 test-output 目录(避免 git 跟踪动态变化)
|
||
command: process.env.CI
|
||
? './bin/bililive-dev --config test-output/test-config.yml'
|
||
: 'go run -tags dev ./src/cmd/bililive --config test-output/test-config.yml',
|
||
url: 'http://127.0.0.1:8080/api/info',
|
||
reuseExistingServer: !process.env.CI,
|
||
timeout: 60 * 1000,
|
||
stdout: 'pipe',
|
||
stderr: 'pipe',
|
||
},
|
||
],
|
||
});
|