Files
zclaw_openfang/admin-v2/tests/e2e/smoke_admin.spec.ts
iven 2e70e1a3f8
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
test: add 30 smoke tests for break detection across SaaS/Admin/Desktop
Layer 1 断裂探测矩阵:
- S1-S6: SaaS API 端到端 (auth/lockout/relay/permissions/billing/knowledge)
- A1-A6: Admin V2 连通性 (login/dashboard/CRUD/knowledge/roles/models)
- D1-D6: Desktop 聊天流 (gateway/kernel/relay/cancel/offline/error)
- F1-F6: Desktop 功能闭环 (agent/hands/pipeline/memory/butler/skills)
- X1-X6: 跨系统闭环 (provider→desktop/disabled user/knowledge/stats/totp/billing)

Also adds: admin-v2 Playwright config, updated spec doc with cross-reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 09:47:35 +08:00

183 lines
6.8 KiB
TypeScript
Raw 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.

/**
* Smoke Tests — Admin V2 连通性断裂探测
*
* 6 个冒烟测试验证 Admin V2 页面与 SaaS 后端的完整连通性。
* 所有测试使用真实浏览器 + 真实 SaaS Server。
*
* 前提条件:
* - SaaS Server 运行在 http://localhost:8080
* - Admin V2 dev server 运行在 http://localhost:5173
* - 种子用户: testadmin / Admin123456 (super_admin)
*
* 运行: cd admin-v2 && npx playwright test smoke_admin
*/
import { test, expect, type Page } from '@playwright/test';
const SaaS_BASE = 'http://localhost:8080/api/v1';
const ADMIN_USER = 'testadmin';
const ADMIN_PASS = 'Admin123456';
// Helper: 通过 API 登录获取 HttpOnly cookie避免 UI 登录的复杂性
async function apiLogin(page: Page) {
await page.request.post(`${SaaS_BASE}/auth/login`, {
data: { username: ADMIN_USER, password: ADMIN_PASS },
});
}
// Helper: 通过 API 登录 + 导航到指定路径
async function loginAndGo(page: Page, path: string) {
await apiLogin(page);
await page.goto(path);
// 等待主内容区加载
await page.waitForSelector('#main-content', { timeout: 15000 });
}
// ── A1: 登录→Dashboard ────────────────────────────────────────────
test('A1: 登录→Dashboard 5个统计卡片', async ({ page }) => {
// 导航到登录页
await page.goto('/login');
await expect(page.getByPlaceholder('请输入用户名')).toBeVisible({ timeout: 10000 });
// 填写表单
await page.getByPlaceholder('请输入用户名').fill(ADMIN_USER);
await page.getByPlaceholder('请输入密码').fill(ADMIN_PASS);
// 提交
await page.getByRole('button', { name: /登录/ }).click();
// 验证跳转到 Dashboard
await expect(page).toHaveURL(/\/$/, { timeout: 15000 });
// 验证 5 个统计卡片
await expect(page.getByText('总账号')).toBeVisible({ timeout: 10000 });
await expect(page.getByText('活跃服务商')).toBeVisible();
await expect(page.getByText('活跃模型')).toBeVisible();
await expect(page.getByText('今日请求')).toBeVisible();
await expect(page.getByText('今日 Token')).toBeVisible();
// 验证统计卡片有数值 (不是 loading 状态)
const statCards = page.locator('.ant-statistic-content-value');
await expect(statCards.first()).not.toBeEmpty({ timeout: 10000 });
});
// ── A2: Provider CRUD ──────────────────────────────────────────────
test('A2: Provider 创建→列表可见→禁用', async ({ page }) => {
// 通过 API 创建 Provider
await apiLogin(page);
const createRes = await page.request.post(`${SaaS_BASE}/providers`, {
data: {
name: `smoke_provider_${Date.now()}`,
provider_type: 'openai',
base_url: 'https://api.smoke.test/v1',
enabled: true,
},
});
expect(createRes.ok()).toBeTruthy();
// 导航到 Model Services 页面
await page.goto('/model-services');
await page.waitForSelector('#main-content', { timeout: 15000 });
// 切换到 Provider tab (如果存在 tab 切换)
const providerTab = page.getByRole('tab', { name: /服务商|Provider/i });
if (await providerTab.isVisible()) {
await providerTab.click();
}
// 验证 Provider 列表非空
const tableRows = page.locator('.ant-table-row');
await expect(tableRows.first()).toBeVisible({ timeout: 10000 });
expect(await tableRows.count()).toBeGreaterThan(0);
});
// ── A3: Account 管理 ───────────────────────────────────────────────
test('A3: Account 列表加载→角色可见', async ({ page }) => {
await loginAndGo(page, '/accounts');
// 验证表格加载
const tableRows = page.locator('.ant-table-row');
await expect(tableRows.first()).toBeVisible({ timeout: 10000 });
// 至少有 testadmin 自己
expect(await tableRows.count()).toBeGreaterThanOrEqual(1);
// 验证有角色列
const roleText = await page.locator('.ant-table').textContent();
expect(roleText).toMatch(/super_admin|admin|user/);
});
// ── A4: 知识管理 ───────────────────────────────────────────────────
test('A4: 知识分类→条目→搜索', async ({ page }) => {
// 通过 API 创建分类和条目
await apiLogin(page);
const catRes = await page.request.post(`${SaaS_BASE}/knowledge/categories`, {
data: { name: `smoke_cat_${Date.now()}`, description: 'Smoke test category' },
});
expect(catRes.ok()).toBeTruthy();
const catJson = await catRes.json();
const itemRes = await page.request.post(`${SaaS_BASE}/knowledge/items`, {
data: {
title: 'Smoke Test Knowledge Item',
content: 'This is a smoke test knowledge entry for E2E testing.',
category_id: catJson.id,
tags: ['smoke', 'test'],
},
});
expect(itemRes.ok()).toBeTruthy();
// 导航到知识库页面
await page.goto('/knowledge');
await page.waitForSelector('#main-content', { timeout: 15000 });
// 验证页面加载 (有内容)
const content = await page.locator('#main-content').textContent();
expect(content!.length).toBeGreaterThan(0);
});
// ── A5: 角色权限 ───────────────────────────────────────────────────
test('A5: 角色页面加载→角色列表非空', async ({ page }) => {
await loginAndGo(page, '/roles');
// 验证角色内容加载
await page.waitForTimeout(1000);
// 检查页面有角色相关内容 (可能是表格或卡片)
const content = await page.locator('#main-content').textContent();
expect(content!.length).toBeGreaterThan(0);
// 通过 API 验证角色存在
const rolesRes = await page.request.get(`${SaaS_BASE}/roles`);
expect(rolesRes.ok()).toBeTruthy();
const rolesJson = await rolesRes.json();
expect(Array.isArray(rolesJson) || rolesJson.roles).toBeTruthy();
});
// ── A6: 模型+Key池 ────────────────────────────────────────────────
test('A6: 模型服务页面加载→Provider和Model tab可见', async ({ page }) => {
await loginAndGo(page, '/model-services');
// 验证页面标题或内容
const content = await page.locator('#main-content').textContent();
expect(content!.length).toBeGreaterThan(0);
// 检查是否有 Tab 切换 (服务商/模型/API Key)
const tabs = page.locator('.ant-tabs-tab');
if (await tabs.first().isVisible()) {
const tabCount = await tabs.count();
expect(tabCount).toBeGreaterThanOrEqual(1);
}
// 通过 API 验证能列出 Provider
const provRes = await page.request.get(`${SaaS_BASE}/providers`);
expect(provRes.ok()).toBeTruthy();
});