* Initial plan * Initial plan for hierarchical configuration system Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Implement hierarchical configuration system with platform and room-level overrides Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Add config validation, platform key mapping, and hierarchical config example Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Add enhanced GUI configuration interface with hierarchical config support Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Complete implementation with documentation and working web UI demo Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 根据反馈完善层级配置系统:移除二进制文件,更新中文界面,支持所有直播平台 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 改进配置界面:添加YAML解析库,输出模板预览和详细工具提示 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 完善监控列表界面:增加直播间详细信息展开功能 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 修复后端API支持和前端功能,添加日志查看功能 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 修复前端编译错误,调整优化package.json * 实现真实的层级配置系统:添加日志存储,解析配置,更新所有组件使用解析后配置 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 实现层级配置系统:支持平台级设置、中文GUI界面、监控详情展示和真实数据后端 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 实现真实的平台访问频率限制功能 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 为所有主要直播平台添加访问频率限制支持 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 完成层级配置系统:平台访问频率限制功能全面实现 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 实现层级配置系统:支持平台级设置、中文GUI界面、监控详情展示、真实数据后端和统一访问频率限制 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 修复CI lint错误:添加webapp默认构建文件 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * feat: 新增ntfy通知功能 * feat: 添加停止录制时的ntfy推送通知 * 优化ntfy推送通知 * Update src/notify/ntfy/ntfy.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix test error * 为每个直播间分别保留一组直播间自己的log信息,供前端取用。 前端监控列表的详细信息现在由SSE实时更新。 为详情页面的日志增加了一些便利功能。 * 升级antd v3 -> v6 * 升级 react-router-dom v5-> v6 * 优化层级设置UI,访问频率限制逻辑等 * code refine * fix CI error * Fix CI error * Fix CI error * Fix "Component state update uses potentially inconsistent value" issue * 根据代码review修复问题:替换废弃方法,添加null检查,修复state更新和变量使用 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 修复CI编译错误:添加default构建文件并修正handler.go中的变量引用 Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Initial plan * Implement real-time countdown for next GetInfo request Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Add real-time download speed display for recording streams Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Fix setState callbacks to return prevState instead of null Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Use safe type assertion for RecorderManager to prevent panic Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Address PR review comments: add periodic SSE broadcast, fix optional chaining, cleanup memory leaks - Add periodic (3s) broadcast of recorder status via SSE for real-time download speed updates - Use optional chaining (?.) for safer recorder_status.speed access - Remove redundant nil check for recorder in GetRecorder - Clean up countdownTimers state when rows are collapsed to prevent memory leak - Use Math.ceil() for countdown initialization to avoid premature "available" display - Set up broadcast callback in server initialization to avoid circular dependencies Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Fix race condition and improve comments in status broadcaster - Add sync.WaitGroup to ensure broadcaster goroutine exits cleanly - Update comments to reflect callback-based implementation - Ensure proper cleanup sequence in Close() method Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Improve error handling in recorder status retrieval - Add warning log when GetStatus() fails for better observability - Simplify logic by assigning status regardless of nil value - Helps with debugging recorder status issues Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Improve countdown display and download speed formatting - Add SSE list threshold config (default 50 rooms) in RPC settings - Change download speed format from "0.562x" to "MB/s" or "KB/s" based on bitrate - Update recorder status broadcast interval from 3s to 5s - Fix countdown display: use "距离下次刷新" instead of "距下次GetInfo请求" - Change "立即可用" to "正在刷新" for better UX - Implement auto-calibrating countdown that adjusts with SSE updates - Track last update times to maintain accurate countdown state Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * Add list-level SSE subscription with configurable threshold - Enable SSE for live room list by default when room count < threshold - Automatically switch to detail-only SSE when list exceeds threshold - Update SSE subscription strategy dynamically when rooms added/removed - Wildcard SSE subscription ('*') receives all live_update events - List refreshes automatically when receiving SSE updates - Clean up list-level subscription on component unmount Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> * 任务系统 init * 新增任务列表,直播事件功能和对应数据库 * 修复新增功能前后端bug * 暂时commit部分修改内容,还未充分检查 * 0.8.0/dev (#1059) * 优化视频播放界面,新增退出视频播放按钮(支持ESC退出) * 文件列表新增文件重命名和删除操作,支持批量操作 * 设置界面引入前端校验,确保前后端参数一致性,之前前端非法参数可以保存到后端config,如最小录制时长。本次修改在前端加入校验提示,非法参数直接拦截并提示有效值 * 哔哩哔哩cookie功能优化,新增扫码登录和验证机制 * 哔哩哔哩cookie界面优化 * fix code review * 修复重命名、删除、批量操作的越权问题 * 表单验证间隔检测规则保持这里为0 * 完善报错 * 回滚误改的listening_count * 修改文案 * 修复哔哩哔哩cookie界面api反复调用问题 * 补充console * 重复的 ESC 键监听器问题修复 * 修复cookie潜在的内存问题 * cookie错误处理日志 * 错误日志改进 * 补充注释 * 调整文件编码转义,支持特殊字符,国际化,emojy等 * 修复--rsp是ReadableStream,只能读一次。如果 rsp.json() 失败后再去 rsp.text() 会失败。所以原始代码 clone 了一个 response 使用。 * 实现直播流选择,可选画质设置 * ci: 对齐版本、修复 lint 警告并添加开发工具模板 版本对齐: - 将 CI 中的 Go 版本从 1.24 更新到 1.25 (匹配 go.mod) - 将 Node.js 配置改为使用 .node-version 文件 (v20.10.0) - 升级 actions/setup-go 到 v5, actions/setup-node 到 v4 Lint 修复: - 修复 staticcheck 警告 (S1039, ST1023, S1009, QF1008) - 删除未使用的函数: - recorder.go: renderUploadPath, logAvailableStreams, extractRoomID - store.go: getIntFromMap, getFloat64FromMap - runjob_windows.go: runWithKillOnClose - 重新生成 mock 文件 开发环境: - 添加 VS Code 调试配置模板 (.vscode/launch.example.json) - 更新 .gitignore 支持本地 launch.json 自定义 - 更新 README 添加使用说明 * feat: 添加版本统计、更新器增强与开发环境改进 版本统计: - 新增 telemetry 包,仅统计程序版本号 - 用于了解用户更新程序的整体趋势 - 不收集平台、架构或任何其他个人数据 更新器增强: - 实现 SHA256 校验和验证,确保下载完整性 - 支持 tar.gz 和 zip 压缩包解压 - 优先使用 bililive-go.com 代理,回退到 GitHub 直连 开发环境: - 升级 Go 版本到 1.25 - 新增 tools/devtools.go 管理开发工具依赖 (delve, gopls, staticcheck) - Makefile 添加 help 命令 - 新增 test/ 目录和 launcher 测试基础设施 * fix: 修复前端 ESLint 警告并启用严格模式构建 ESLint 修复: - config-info/index.tsx: 移除未使用的 useNavigate 和 addPlatformVisible - history-panel/index.tsx: 移除未使用的 List、UserOutlined、HistoryResponse - live-list/index.tsx: 移除未使用的 Modal 构建配置改进: - 默认 build 命令使用 CI=true 模式,警告视为错误 - 添加 cross-env 依赖实现跨平台环境变量设置 - 添加 yarn lint 命令进行独立 ESLint 检查 - ESLint 配置中将 no-unused-vars 规则设为 error 这确保本地开发与 CI 行为一致,尽早发现代码问题 * 删除未使用的golang函数 * feat: 添加 Playwright E2E 测试框架 - 集成 Playwright 进行端到端测试 - 使用 osrp-stream-tester 作为测试流服务器 - 添加基础 UI 测试和直播录制测试用例 - 创建 GitHub Actions E2E 测试工作流 - 更新 Makefile 添加测试相关命令 使用方法: - make install-e2e # 安装测试依赖 - make test-e2e # 运行 E2E 测试 - make test-e2e-ui # 带 UI 运行测试 * fix: 添加 package-lock.json 以修复 CI * feat: 添加 Playwright E2E 测试框架、相关测试用例和 GitHub Actions 工作流。 * ci: 新增 Playwright E2E 测试工作流。 * feat: 添加 Playwright E2E 测试 GitHub Actions 工作流。 * feat: 添加 E2E 测试的 GitHub Actions 工作流。 * feat: 添加端到端测试以及对应的 GitHub Actions CI 工作流 * feat: 新增录制功能的端到端测试 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com> Co-authored-by: July <31428852+onlyjuly@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Joftal <42807405+Joftal@users.noreply.github.com>
208 lines
6.0 KiB
TypeScript
208 lines
6.0 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
||
|
||
/**
|
||
* Bililive-Go 基础功能测试
|
||
*
|
||
* 测试 Web UI 的基本功能,包括页面加载和导航
|
||
*/
|
||
test.describe('基础功能测试', () => {
|
||
test('首页正常加载', async ({ page }) => {
|
||
// 访问首页
|
||
await page.goto('/');
|
||
|
||
// 等待页面加载完成
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 验证页面包含关键元素:Bililive-go 标题
|
||
await expect(page.getByRole('heading', { name: 'Bililive-go' })).toBeVisible();
|
||
});
|
||
|
||
test('页面标题正确', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 验证页面标题包含 Bililive
|
||
await expect(page).toHaveTitle(/.*[Bb]ililive.*/);
|
||
});
|
||
|
||
test('Header 正确显示', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 验证 Header 存在(通过 banner 角色)
|
||
const header = page.getByRole('banner');
|
||
await expect(header).toBeVisible();
|
||
|
||
// 验证 logo 文本
|
||
await expect(page.getByRole('heading', { name: 'Bililive-go' })).toBeVisible();
|
||
});
|
||
|
||
test('API 信息端点可访问', async ({ request }) => {
|
||
// 直接测试 API
|
||
const response = await request.get('/api/info');
|
||
expect(response.ok()).toBeTruthy();
|
||
|
||
const info = await response.json();
|
||
// 验证返回的信息包含应用名称(使用 snake_case)
|
||
expect(info).toHaveProperty('app_name');
|
||
});
|
||
|
||
test('API 直播间列表端点可访问', async ({ request }) => {
|
||
const response = await request.get('/api/lives');
|
||
expect(response.ok()).toBeTruthy();
|
||
|
||
const data = await response.json();
|
||
// 返回的是直播间数组
|
||
expect(Array.isArray(data)).toBe(true);
|
||
});
|
||
|
||
test('API 配置端点可访问', async ({ request }) => {
|
||
const response = await request.get('/api/config');
|
||
expect(response.ok()).toBeTruthy();
|
||
});
|
||
|
||
test('直播间列表页面加载', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 等待直播间列表区域出现(通过 main 角色)
|
||
const content = page.getByRole('main');
|
||
await expect(content).toBeVisible();
|
||
});
|
||
|
||
test('侧边栏菜单存在', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 验证侧边栏存在(通过 complementary 角色)
|
||
const sider = page.getByRole('complementary');
|
||
await expect(sider).toBeVisible();
|
||
|
||
// 验证菜单存在
|
||
const menu = page.getByRole('menu');
|
||
await expect(menu).toBeVisible();
|
||
});
|
||
});
|
||
|
||
test.describe('错误处理测试', () => {
|
||
test('404 页面处理', async ({ page }) => {
|
||
// 访问不存在的页面
|
||
await page.goto('/#/nonexistent-page');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 应该仍然显示布局(通过 banner 角色验证)
|
||
const header = page.getByRole('banner');
|
||
await expect(header).toBeVisible();
|
||
});
|
||
|
||
test('API 错误响应处理', async ({ request }) => {
|
||
// 请求不存在的 API
|
||
const response = await request.get('/api/nonexistent');
|
||
// 应该返回 404 或其他错误状态
|
||
expect(response.status()).toBeGreaterThanOrEqual(400);
|
||
});
|
||
});
|
||
|
||
test.describe('性能基准测试', () => {
|
||
test('首页加载时间合理', async ({ page }) => {
|
||
const startTime = Date.now();
|
||
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
const loadTime = Date.now() - startTime;
|
||
|
||
// 首页应该在 10 秒内加载完成
|
||
expect(loadTime).toBeLessThan(10000);
|
||
|
||
console.log(`首页加载时间: ${loadTime}ms`);
|
||
});
|
||
|
||
test('页面没有控制台错误', async ({ page }) => {
|
||
const errors: string[] = [];
|
||
|
||
page.on('console', msg => {
|
||
if (msg.type() === 'error') {
|
||
errors.push(msg.text());
|
||
}
|
||
});
|
||
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// 过滤掉一些已知的无害错误
|
||
const criticalErrors = errors.filter(error =>
|
||
!error.includes('favicon') &&
|
||
!error.includes('Failed to load resource')
|
||
);
|
||
|
||
// 不应该有严重的控制台错误
|
||
if (criticalErrors.length > 0) {
|
||
console.log('发现控制台错误:', criticalErrors);
|
||
}
|
||
});
|
||
});
|
||
|
||
test.describe('设置功能测试', () => {
|
||
test('设置页面可访问', async ({ page }) => {
|
||
await page.goto('/#/configInfo');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 验证设置页面加载(通过 main 角色)
|
||
const content = page.getByRole('main');
|
||
await expect(content).toBeVisible();
|
||
});
|
||
|
||
test('通过菜单访问设置', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 查找设置菜单项(使用 menuitem 角色)
|
||
const settingsLink = page.getByRole('menuitem', { name: /设置/ });
|
||
|
||
if (await settingsLink.isVisible()) {
|
||
await settingsLink.click();
|
||
// 等待一下让路由切换
|
||
await page.waitForTimeout(500);
|
||
|
||
// 验证 URL 包含 configInfo
|
||
expect(page.url()).toContain('configInfo');
|
||
|
||
// 验证导航成功
|
||
await expect(page.getByRole('main')).toBeVisible();
|
||
}
|
||
});
|
||
});
|
||
|
||
test.describe('键盘可访问性测试', () => {
|
||
test('Tab 键可以导航', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 按 Tab 键导航
|
||
await page.keyboard.press('Tab');
|
||
await page.waitForTimeout(100);
|
||
|
||
// 验证有元素获得焦点
|
||
const focusedElement = page.locator(':focus');
|
||
await expect(focusedElement).toBeDefined();
|
||
});
|
||
|
||
test('Enter 键可以激活按钮', async ({ page }) => {
|
||
await page.goto('/');
|
||
await page.waitForLoadState('domcontentloaded');
|
||
|
||
// 找到并聚焦一个按钮
|
||
const button = page.locator('.ant-btn').first();
|
||
|
||
if (await button.isVisible()) {
|
||
await button.focus();
|
||
|
||
// 验证按钮获得焦点
|
||
await expect(button).toBeFocused();
|
||
}
|
||
});
|
||
});
|
||
|