Files
kira1928 4c0d2fa6b6 完善自动更新功能 (#1073)
* 完善自动更新功能

* 修复 CI error

* 根据review意见更新代码

* 修复e2e CI
2026-02-18 02:19:53 +09:00

355 lines
12 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* Bililive-Go 自动更新功能测试
*
* 测试更新系统的 API 接口和前端组件
*/
test.describe('更新系统 API 测试', () => {
test('更新检查 API 可访问', async ({ request }) => {
// 请求更新检查 API
const response = await request.get('/api/update/check');
// API 应该返回成功(即使没有可用更新)
expect(response.ok()).toBeTruthy();
const data = await response.json();
// 验证返回包含必要字段
expect(data).toHaveProperty('available');
expect(data).toHaveProperty('current_version');
});
test('更新状态 API 可访问', async ({ request }) => {
const response = await request.get('/api/update/status');
expect(response.ok()).toBeTruthy();
const data = await response.json();
// 验证返回包含状态字段
expect(data).toHaveProperty('state');
});
test('启动器状态 API 可访问', async ({ request }) => {
const response = await request.get('/api/update/launcher');
expect(response.ok()).toBeTruthy();
const data = await response.json();
// 验证返回包含连接状态
expect(data).toHaveProperty('connected');
// 在 E2E 测试环境中,没有启动器连接
expect(data.connected).toBe(false);
});
test('最新版本 API 可访问', async ({ request }) => {
// 请求最新版本 API
const response = await request.get('/api/update/latest');
// API 应该返回成功、404(如果没有可用版本)或 500(服务器错误)
expect([200, 404, 500]).toContain(response.status());
if (response.status() === 200) {
const data = await response.json();
// 如果有数据,验证包含版本信息
if (data) {
expect(data).toHaveProperty('version');
}
}
});
test('更新通道 API 可访问', async ({ request }) => {
// 设置更新通道
const response = await request.put('/api/update/channel', {
data: { channel: 'stable' }
});
// 验证响应成功
expect(response.ok()).toBeTruthy();
});
test('更新取消 API 可访问', async ({ request }) => {
// 测试取消更新 API(即使没有进行中的更新也应该成功)
const response = await request.post('/api/update/cancel', {
data: {}
});
expect(response.ok()).toBeTruthy();
});
});
test.describe('更新系统状态测试', () => {
test.beforeEach(async ({ request }) => {
// 确保测试前状态被重置
await request.post('/api/update/cancel', { data: {} });
});
test('初始状态为 idle', async ({ request }) => {
const response = await request.get('/api/update/status');
expect(response.ok()).toBeTruthy();
const data = await response.json();
// 状态应该是合法的状态之一
expect(['idle', 'available', 'ready', 'checking', 'downloading', 'failed', 'applying']).toContain(data.state);
});
test('版本信息格式正确', async ({ request }) => {
// 通过更新检查获取版本信息
const response = await request.get('/api/update/check');
expect(response.ok()).toBeTruthy();
const data = await response.json();
// 当前版本应该是 semver 格式或 dev 版本
expect(data.current_version).toBeDefined();
// 版本号应该是字符串
expect(typeof data.current_version).toBe('string');
});
});
test.describe('更新配置 API 测试', () => {
test('配置包含更新选项', async ({ request }) => {
const response = await request.get('/api/config');
expect(response.ok()).toBeTruthy();
const config = await response.json();
// 验证配置包含更新相关字段
expect(config).toHaveProperty('update');
expect(config.update).toHaveProperty('auto_check');
expect(config.update).toHaveProperty('auto_download');
expect(config.update).toHaveProperty('check_interval_hours');
expect(config.update).toHaveProperty('include_prerelease');
});
test('更新配置值正确', async ({ request }) => {
const response = await request.get('/api/config');
expect(response.ok()).toBeTruthy();
const config = await response.json();
// 验证测试配置文件中的值
expect(config.update.auto_check).toBe(false); // 测试配置中禁用
expect(config.update.auto_download).toBe(false); // 测试配置中禁用
expect(config.update.check_interval_hours).toBe(6);
expect(config.update.include_prerelease).toBe(false);
});
test('可以更新配置中的更新选项', async ({ request }) => {
// 获取当前配置
const getResponse = await request.get('/api/config');
expect(getResponse.ok()).toBeTruthy();
const originalConfig = await getResponse.json();
// 尝试更新配置(但不实际改变值,避免影响其他测试)
const updateResponse = await request.put('/api/config', {
data: {
update: {
auto_check: false,
auto_download: false,
check_interval_hours: 6,
include_prerelease: false
}
}
});
expect(updateResponse.ok()).toBeTruthy();
// 验证配置未变(因为我们设置了相同的值)
const verifyResponse = await request.get('/api/config');
expect(verifyResponse.ok()).toBeTruthy();
const verifyConfig = await verifyResponse.json();
expect(verifyConfig.update.auto_check).toBe(originalConfig.update.auto_check);
});
});
test.describe('SSE 更新事件测试', () => {
test('SSE 端点可连接', async ({ page }) => {
// 访问页面以建立 SSE 连接
await page.goto('/');
await page.waitForLoadState('domcontentloaded');
// 验证页面加载正常(SSE 连接在后台建立)
await expect(page.getByRole('heading', { name: 'Bililive-go' })).toBeVisible();
});
test('SSE 连接建立后可接收事件', async ({ page }) => {
// 监听控制台日志以验证 SSE 连接
const sseMessages: string[] = [];
page.on('console', msg => {
if (msg.text().includes('[SSE]')) {
sseMessages.push(msg.text());
}
});
await page.goto('/');
await page.waitForLoadState('domcontentloaded');
// 等待 SSE 连接建立
await page.waitForTimeout(2000);
// 验证 SSE 连接日志
const hasConnected = sseMessages.some(msg =>
msg.includes('Connected') || msg.includes('Subscribed')
);
// SSE 应该已连接或订阅
if (sseMessages.length > 0) {
console.log('SSE 消息:', sseMessages);
}
});
});
test.describe('更新 UI 组件测试', () => {
test('初始状态下不显示更新横幅', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('domcontentloaded');
// 更新横幅在没有可用更新时不应该显示
const updateBanner = page.locator('.update-banner');
// 等待一下确保组件有机会渲染
await page.waitForTimeout(1000);
// 由于测试环境没有真正的更新,横幅不应该显示
// 使用 count() 而不是 toBeHidden() 因为元素可能根本不存在
const count = await updateBanner.count();
expect(count).toBe(0);
});
test('页面不应有更新相关的错误提示', async ({ page }) => {
const errors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error' && msg.text().toLowerCase().includes('update')) {
errors.push(msg.text());
}
});
await page.goto('/');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(2000);
// 不应该有更新相关的错误
expect(errors.length).toBe(0);
});
});
test.describe('更新工作流测试', () => {
test('完整的更新检查流程', async ({ request }) => {
// 1. 检查初始状态
const statusBefore = await request.get('/api/update/status');
expect(statusBefore.ok()).toBeTruthy();
const beforeData = await statusBefore.json();
expect(beforeData.state).toBeDefined();
// 2. 触发更新检查
const checkResponse = await request.get('/api/update/check');
expect(checkResponse.ok()).toBeTruthy();
const checkData = await checkResponse.json();
expect(checkData).toHaveProperty('available');
expect(checkData).toHaveProperty('current_version');
// 3. 检查状态(可能已更新或保持 idle)
const statusAfter = await request.get('/api/update/status');
expect(statusAfter.ok()).toBeTruthy();
const afterData = await statusAfter.json();
expect(afterData.state).toBeDefined();
console.log('更新检查结果:', {
available: checkData.available,
currentVersion: checkData.current_version,
stateBefore: beforeData.state,
stateAfter: afterData.state
});
});
test('预发布版本检查', async ({ request }) => {
// 测试包含预发布版本的检查
const response = await request.get('/api/update/check?prerelease=true');
expect(response.ok()).toBeTruthy();
const data = await response.json();
expect(data).toHaveProperty('available');
expect(data).toHaveProperty('current_version');
});
test('取消更新不会引发错误', async ({ request }) => {
// 即使没有进行中的更新,取消操作也应该成功
const cancelResponse = await request.post('/api/update/cancel', { data: {} });
expect(cancelResponse.ok()).toBeTruthy();
// 验证状态正常
const statusResponse = await request.get('/api/update/status');
expect(statusResponse.ok()).toBeTruthy();
});
});
test.describe('更新错误处理测试', () => {
test('无效的更新通道被拒绝', async ({ request }) => {
// 发送无效的通道值
const response = await request.put('/api/update/channel', {
data: { channel: 'invalid_channel' }
});
// 应该返回错误(400 Bad Request)或者接受并忽略
// 根据实际实现可能有不同行为
const status = response.status();
expect([200, 400]).toContain(status);
});
test('应用更新时无可用更新返回错误', async ({ request }) => {
// 在没有下载更新的情况下尝试应用
const response = await request.post('/api/update/apply', {
data: { graceful_wait: true }
});
// 应该返回错误,因为没有可用的更新
// 状态码可能是 400、409 或其他错误码
const status = response.status();
expect(status).toBeGreaterThanOrEqual(400);
});
test('下载更新时无可用更新返回错误', async ({ request }) => {
// 确保状态是 idle
await request.post('/api/update/cancel', { data: {} });
// 尝试下载(在没有检查到可用更新的情况下)
const response = await request.post('/api/update/download', {
data: {}
});
// 可能返回错误或成功开始检查
// 根据实现,可能需要先检查更新
const status = response.status();
console.log('下载响应状态:', status);
});
});
test.describe('更新与录制集成测试', () => {
test.beforeEach(async ({ request }) => {
// 确保测试环境干净
await request.post('/api/update/cancel', { data: {} });
});
test('启动器未连接时更新状态正确', async ({ request }) => {
const response = await request.get('/api/update/launcher');
expect(response.ok()).toBeTruthy();
const data = await response.json();
// E2E 测试环境中没有启动器
expect(data.connected).toBe(false);
// 验证这不影响更新检查
const checkResponse = await request.get('/api/update/check');
expect(checkResponse.ok()).toBeTruthy();
});
test('更新状态不影响直播间列表', async ({ request }) => {
// 触发更新检查
await request.get('/api/update/check');
// 验证直播间列表 API 仍然正常工作
const livesResponse = await request.get('/api/lives');
expect(livesResponse.ok()).toBeTruthy();
const lives = await livesResponse.json();
expect(Array.isArray(lives)).toBe(true);
});
});