1
0
Files
nex/frontend/e2e/crud.spec.ts
lanyuanxiaoyao 5dd26d29a7 test: 修复单元测试并补充完善 E2E 测试用例
- 修复 5 个单元测试失败:ProviderForm 表单提交超时、ModelForm 初始值检查、
  StatsTable Select 交互、ProviderTable 删除确认超时
- 适配 Ant Design 6 的 DOM 结构变化和异步行为
- 补充 E2E 测试从 6 个扩展到 32 个,覆盖供应商 CRUD、模型管理、
  表单验证、错误处理、边界情况、用量统计筛选等完整用户流程
- 发现并处理 Ant Design 6 按钮文本渲染带空格的兼容性问题
2026-04-16 16:27:09 +08:00

257 lines
9.1 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.
import { test, expect } from '@playwright/test';
// 辅助:在对话框内定位输入框
function formInputs(page: import('@playwright/test').Page) {
const dialog = page.getByRole('dialog');
return {
id: dialog.getByRole('textbox', { name: /ID/ }),
name: dialog.getByRole('textbox', { name: /名称/ }),
apiKey: dialog.locator('input[type="password"]'),
baseUrl: dialog.getByRole('textbox', { name: /Base URL/ }),
saveBtn: dialog.locator('.ant-modal-footer').getByRole('button').last(),
cancelBtn: dialog.locator('.ant-modal-footer').getByRole('button').first(),
};
}
// 辅助:在模型对话框内定位输入框
function modelFormInputs(page: import('@playwright/test').Page) {
const dialog = page.getByRole('dialog');
return {
id: dialog.locator('input[placeholder="例如: gpt-4o"]').first(),
modelName: dialog.locator('input[placeholder="例如: gpt-4o"]').nth(1),
saveBtn: dialog.locator('.ant-modal-footer').getByRole('button').last(),
cancelBtn: dialog.locator('.ant-modal-footer').getByRole('button').first(),
};
}
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.getByRole('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 inputs.saveBtn.click();
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
});
test('供应商创建后编辑流程', async ({ page }) => {
const editBtns = page.locator('.ant-table-tbody button:has-text("编辑")');
const count = await editBtns.count();
if (count > 0) {
await editBtns.first().click();
await expect(page.getByRole('dialog')).toBeVisible();
const inputs = formInputs(page);
await inputs.name.clear();
await inputs.name.fill('Updated Provider Name');
await inputs.saveBtn.click();
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
} else {
test.skip();
}
});
test('供应商删除流程', async ({ page }) => {
const deleteBtns = page.locator('.ant-table-tbody button:has-text("删除")');
const count = await deleteBtns.count();
if (count > 0) {
await deleteBtns.first().click();
await expect(page.getByText('确定要删除这个供应商吗?')).toBeVisible();
// 点击确认Popconfirm 按钮最后一个 = "确 定"
await page.locator('.ant-popconfirm-buttons').getByRole('button').last().click();
await expect(page.getByText('确定要删除这个供应商吗?')).not.toBeVisible({ timeout: 3000 });
} else {
test.skip();
}
});
test('展开供应商并添加模型', async ({ page }) => {
const expandBtns = page.locator('.ant-table-row-expand-icon');
const expandCount = await expandBtns.count();
if (expandCount > 0) {
await expandBtns.first().click();
await expect(page.locator('.ant-table-expanded-row').first()).toBeVisible();
const addModelBtn = page.locator('.ant-table-expanded-row button:has-text("添加模型")');
const addCount = await addModelBtn.count();
if (addCount > 0) {
await addModelBtn.first().click();
const dialog = page.getByRole('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.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
} else {
test.skip();
}
} else {
test.skip();
}
});
test('编辑已有模型', async ({ page }) => {
const expandBtns = page.locator('.ant-table-row-expand-icon');
const expandCount = await expandBtns.count();
if (expandCount > 0) {
await expandBtns.first().click();
await expect(page.locator('.ant-table-expanded-row').first()).toBeVisible();
const modelEditBtns = page.locator('.ant-table-expanded-row button:has-text("编辑")');
const editCount = await modelEditBtns.count();
if (editCount > 0) {
await modelEditBtns.first().click();
const dialog = page.getByRole('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.getByRole('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.getByRole('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.getByRole('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.getByRole('dialog')).toBeVisible();
expect(await page.locator('[role="dialog"]').count()).toBe(1);
await formInputs(page).cancelBtn.click();
await expect(page.getByRole('dialog')).not.toBeVisible();
});
test('取消后表单应重置', async ({ page }) => {
// 打开并填写表单
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.getByRole('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.getByRole('dialog')).not.toBeVisible();
// 重新打开
await page.getByRole('button', { name: '添加供应商' }).click();
await expect(page.getByRole('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('.ant-select').first().click();
await page.waitForSelector('.ant-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('');
});
});