- 替换 antd 为 tdesign-react 作为主要 UI 组件库 - 引入 Recharts 替代 @ant-design/charts 实现图表功能 - 移除主题系统相关代码(ThemeContext、themes 目录) - 更新所有组件以适配 TDesign 组件 API - 更新测试用例以匹配新的组件实现 - 新增 TDesign 和 Recharts 集成规范文档
265 lines
9.5 KiB
TypeScript
265 lines
9.5 KiB
TypeScript
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('');
|
||
});
|
||
});
|