1
0
Files
nex/frontend/e2e/crud.spec.ts
lanyuanxiaoyao 2b1c5e96c3 refactor: 迁移 UI 组件库从 Ant Design 至 TDesign
- 替换 antd 为 tdesign-react 作为主要 UI 组件库
- 引入 Recharts 替代 @ant-design/charts 实现图表功能
- 移除主题系统相关代码(ThemeContext、themes 目录)
- 更新所有组件以适配 TDesign 组件 API
- 更新测试用例以匹配新的组件实现
- 新增 TDesign 和 Recharts 集成规范文档
2026-04-17 18:22:13 +08:00

265 lines
9.5 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';
// 辅助:在对话框内定位输入框
function formInputs(page: import('@playwright/test').Page) {
const dialog = page.locator('.t-dialog:visible');
return {
id: dialog.locator('input[placeholder="例如: openai"]'),
name: dialog.locator('input[placeholder="例如: OpenAI"]'),
apiKey: dialog.locator('input[type="password"]'),
baseUrl: dialog.locator('input[placeholder="例如: https://api.openai.com/v1"]'),
saveBtn: dialog.locator('.t-dialog__footer').getByRole('button', { name: '保存' }),
cancelBtn: dialog.locator('.t-dialog__footer').getByRole('button', { name: '取消' }),
};
}
// 辅助:在模型对话框内定位输入框
function modelFormInputs(page: import('@playwright/test').Page) {
const dialog = page.locator('.t-dialog:visible');
return {
id: dialog.locator('input[placeholder="例如: gpt-4o"]').first(),
modelName: dialog.locator('input[placeholder="例如: gpt-4o"]').nth(1),
saveBtn: dialog.locator('.t-dialog__footer').getByRole('button', { name: '保存' }),
cancelBtn: dialog.locator('.t-dialog__footer').getByRole('button', { name: '取消' }),
};
}
test.describe('供应商和模型完整CRUD流程', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/providers');
await expect(page.getByRole('heading', { name: '供应商管理' })).toBeVisible();
});
test('完整的供应商创建流程', async ({ page }) => {
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.locator('.t-dialog')).toBeVisible();
const inputs = formInputs(page);
const testId = `e2e-${Date.now()}`;
await inputs.id.fill(testId);
await inputs.name.fill('E2E Test Provider');
await inputs.apiKey.fill('sk-e2e-test-key');
await inputs.baseUrl.fill('https://api.e2e-test.com/v1');
// 验证所有字段填写正确
await expect(inputs.id).toHaveValue(testId);
await expect(inputs.name).toHaveValue('E2E Test Provider');
await expect(inputs.baseUrl).toHaveValue('https://api.e2e-test.com/v1');
await inputs.saveBtn.click();
// 注意:对话框关闭依赖后端 API 响应成功,此处仅验证提交按钮可点击
});
test('供应商创建后编辑流程', async ({ page }) => {
const editBtns = page.locator('.t-table__body button:has-text("编辑")');
const count = await editBtns.count();
if (count > 0) {
await editBtns.first().click();
await expect(page.locator('.t-dialog:visible')).toBeVisible();
const inputs = formInputs(page);
await inputs.name.clear();
await inputs.name.fill('Updated Provider Name');
// 验证名称字段已更新
await expect(inputs.name).toHaveValue('Updated Provider Name');
await inputs.saveBtn.click();
// 注意:对话框关闭依赖后端 API 响应成功,此处仅验证提交按钮可点击
} else {
test.skip();
}
});
test('供应商删除流程', async ({ page }) => {
const deleteBtns = page.locator('.t-table__body button:has-text("删除")');
const count = await deleteBtns.count();
if (count > 0) {
await deleteBtns.first().click();
await expect(page.getByText('确定要删除这个供应商吗?')).toBeVisible();
// 点击确认TDesign Popconfirm 确定按钮)
await page.locator('.t-popconfirm').getByRole('button', { name: '确定' }).click();
await expect(page.getByText('确定要删除这个供应商吗?')).not.toBeVisible({ timeout: 3000 });
} else {
test.skip();
}
});
test('展开供应商并添加模型', async ({ page }) => {
const expandBtns = page.locator('.t-table__expandable-icon');
const expandCount = await expandBtns.count();
if (expandCount > 0) {
await expandBtns.first().click();
await expect(page.locator('.t-table__expanded-row').first()).toBeVisible();
const addModelBtn = page.locator('.t-table__expanded-row button:has-text("添加模型")');
const addCount = await addModelBtn.count();
if (addCount > 0) {
await addModelBtn.first().click();
const dialog = page.locator('.t-dialog');
await expect(dialog).toBeVisible();
await expect(dialog.getByText('添加模型')).toBeVisible();
const modelInputs = modelFormInputs(page);
await modelInputs.id.fill(`model-${Date.now()}`);
await modelInputs.modelName.fill('gpt-4-turbo');
await modelInputs.saveBtn.click();
await expect(page.locator('.t-dialog')).not.toBeVisible({ timeout: 5000 });
} else {
test.skip();
}
} else {
test.skip();
}
});
test('编辑已有模型', async ({ page }) => {
const expandBtns = page.locator('.t-table__expandable-icon');
const expandCount = await expandBtns.count();
if (expandCount > 0) {
await expandBtns.first().click();
await expect(page.locator('.t-table__expanded-row').first()).toBeVisible();
const modelEditBtns = page.locator('.t-table__expanded-row button:has-text("编辑")');
const editCount = await modelEditBtns.count();
if (editCount > 0) {
await modelEditBtns.first().click();
const dialog = page.locator('.t-dialog');
await expect(dialog).toBeVisible();
await expect(dialog.getByText('编辑模型')).toBeVisible();
// ID 字段应被禁用
await expect(modelFormInputs(page).id).toBeDisabled();
await modelFormInputs(page).cancelBtn.click();
} else {
test.skip();
}
} else {
test.skip();
}
});
});
test.describe('错误处理和边界情况', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/providers');
await expect(page.getByRole('heading', { name: '供应商管理' })).toBeVisible();
});
test('应显示必填字段验证', async ({ page }) => {
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.locator('.t-dialog')).toBeVisible();
await formInputs(page).saveBtn.click();
await expect(page.getByText('请输入供应商 ID')).toBeVisible();
await expect(page.getByText('请输入名称')).toBeVisible();
await expect(page.getByText('请输入 API Key')).toBeVisible();
await expect(page.getByText('请输入 Base URL')).toBeVisible();
});
test('应验证URL格式', async ({ page }) => {
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.locator('.t-dialog')).toBeVisible();
const inputs = formInputs(page);
await inputs.id.fill('test-url');
await inputs.name.fill('Test');
await inputs.apiKey.fill('sk-test');
await inputs.baseUrl.fill('not-a-url');
await inputs.saveBtn.click();
await expect(page.getByText('请输入有效的 URL')).toBeVisible();
});
test('超长输入处理', async ({ page }) => {
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.locator('.t-dialog')).toBeVisible();
const inputs = formInputs(page);
await inputs.id.fill('test-long');
await inputs.name.fill('a'.repeat(500));
await inputs.apiKey.fill('sk-test');
await inputs.baseUrl.fill('https://api.test.com/v1');
await inputs.saveBtn.click();
// 等待 API 响应或验证错误
await page.waitForTimeout(2000);
});
test('快速连续点击只打开一个对话框', async ({ page }) => {
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.locator('.t-dialog')).toBeVisible();
expect(await page.locator('.t-dialog').count()).toBe(1);
await formInputs(page).cancelBtn.click();
await expect(page.locator('.t-dialog')).not.toBeVisible();
});
test('取消后表单应重置', async ({ page }) => {
// 打开并填写表单
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.locator('.t-dialog')).toBeVisible();
let inputs = formInputs(page);
await inputs.id.fill('should-be-reset');
await inputs.name.fill('Should Be Reset');
await inputs.cancelBtn.click();
await expect(page.locator('.t-dialog')).not.toBeVisible();
// 重新打开
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.locator('.t-dialog')).toBeVisible();
// 验证表单已重置
inputs = formInputs(page);
await expect(inputs.id).toHaveValue('');
await expect(inputs.name).toHaveValue('');
});
});
test.describe('用量统计筛选功能', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/stats');
await expect(page.getByRole('heading', { name: '用量统计' })).toBeVisible();
});
test('组合筛选条件', async ({ page }) => {
await page.locator('.t-select').first().click();
await page.waitForSelector('.t-select__dropdown', { timeout: 3000 });
await page.keyboard.press('Escape');
await page.getByPlaceholder('模型名称').fill('gpt-4');
await expect(page.getByPlaceholder('模型名称')).toHaveValue('gpt-4');
});
test('清空筛选条件', async ({ page }) => {
const modelInput = page.getByPlaceholder('模型名称');
await modelInput.fill('gpt-4');
await expect(modelInput).toHaveValue('gpt-4');
await modelInput.clear();
await expect(modelInput).toHaveValue('');
});
test('刷新页面后筛选状态丢失', async ({ page }) => {
await page.getByPlaceholder('模型名称').fill('gpt-4');
await page.reload();
await expect(page.getByRole('heading', { name: '用量统计' })).toBeVisible();
await expect(page.getByPlaceholder('模型名称')).toHaveValue('');
});
});