Files
15b0b12777 0.8.0/dev (#1061)
* 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>
2026-01-31 03:44:14 +09:00

208 lines
6.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
});
});