chatbot-ui自动化测试:端到端测试用例编写
概述
在现代Web应用开发中,自动化测试已成为确保软件质量的关键环节。chatbot-ui作为一个开源的AI聊天界面项目,采用Playwright作为端到端测试框架,为开发者提供了完善的测试基础设施。本文将深入探讨如何为chatbot-ui编写高质量的端到端测试用例。
测试框架配置
Playwright基础配置
chatbot-ui使用Playwright进行端到端测试,配置文件位于__tests__/playwright-test/playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
测试脚本配置
package.json中定义了测试相关脚本:
{
"scripts": {
"integration": "playwright test",
"integration:open": "playwright test --ui",
"integration:codegen": "playwright codegen"
}
}
核心测试用例编写
登录功能测试
chatbot-ui的认证系统基于Supabase Auth,以下是完整的登录功能测试用例:
import { test, expect } from '@playwright/test';
// 测试用例:开始聊天按钮显示
test('start chatting is displayed', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page.getByRole('link', { name: 'Start Chatting' })).toBeVisible();
});
// 测试用例:无密码登录错误处理
test('No password error message', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.getByPlaceholder('you@example.com').fill('dummyemail@gmail.com');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForLoadState('networkidle');
await expect(page.getByText('Invalid login credentials')).toBeVisible();
});
// 测试用例:注册时缺少密码验证
test('No password for signup', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.getByPlaceholder('you@example.com').fill('dummyEmail@Gmail.com');
await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText('Signup requires a valid')).toBeVisible();
});
// 测试用例:无效用户名注册验证
test('invalid username for signup', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.getByPlaceholder('you@example.com').fill('dummyEmail');
await page.getByPlaceholder('••••••••').fill('dummypassword');
await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText('Unable to validate email')).toBeVisible();
});
// 测试用例:密码重置功能验证
test('password reset message', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.getByPlaceholder('you@example.com').fill('demo@gmail.com');
await page.getByRole('button', { name: 'Reset' }).click();
await expect(page.getByText('Check email to reset password')).toBeVisible();
});
测试用例设计模式
页面对象模式(Page Object Pattern)
为了提高测试代码的可维护性,建议使用页面对象模式:
// pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}
async navigate() {
await this.page.goto('http://localhost:3000/login');
}
async fillEmail(email: string) {
await this.page.getByPlaceholder('you@example.com').fill(email);
}
async fillPassword(password: string) {
await this.page.getByPlaceholder('••••••••').fill(password);
}
async clickLogin() {
await this.page.getByRole('button', { name: 'Login' }).click();
}
async clickSignUp() {
await this.page.getByRole('button', { name: 'Sign Up' }).click();
}
async clickReset() {
await this.page.getByRole('button', { name: 'Reset' }).click();
}
async getErrorMessage() {
return this.page.getByText('Invalid login credentials');
}
}
// 使用页面对象的测试用例
test('login with page object', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.fillEmail('test@example.com');
await loginPage.clickLogin();
await expect(loginPage.getErrorMessage()).toBeVisible();
});
数据驱动测试
使用数据驱动的方式提高测试覆盖率:
const loginTestData = [
{ email: '', password: 'password123', expectedError: 'Email is required' },
{ email: 'invalid-email', password: 'password123', expectedError: 'Invalid email format' },
{ email: 'test@example.com', password: '', expectedError: 'Password is required' },
];
loginTestData.forEach((data, index) => {
test(`login validation test ${index + 1}`, async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.getByPlaceholder('you@example.com').fill(data.email);
await page.getByPlaceholder('••••••••').fill(data.password);
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText(data.expectedError)).toBeVisible();
});
});
聊天功能测试用例
基础聊天交互测试
test('chat message sending and receiving', async ({ page }) => {
// 登录并进入聊天界面
await loginWithCredentials(page, 'test@example.com', 'password123');
// 输入消息并发送
await page.getByPlaceholder('Message Chatbot UI...').fill('Hello, how are you?');
await page.getByRole('button', { name: 'Send message' }).click();
// 验证消息发送成功
await expect(page.getByText('Hello, how are you?')).toBeVisible();
// 等待AI响应(模拟或真实API)
await expect(page.getByText(/I'm doing well/)).toBeVisible({ timeout: 10000 });
});
test('chat history persistence', async ({ page }) => {
// 发送多条消息
await loginWithCredentials(page, 'test@example.com', 'password123');
const messages = ['Message 1', 'Message 2', 'Message 3'];
for (const message of messages) {
await page.getByPlaceholder('Message Chatbot UI...').fill(message);
await page.getByRole('button', { name: 'Send message' }).click();
await expect(page.getByText(message)).toBeVisible();
}
// 刷新页面验证历史记录持久化
await page.reload();
for (const message of messages) {
await expect(page.getByText(message)).toBeVisible();
}
});
文件上传功能测试
test('file upload in chat', async ({ page }) => {
await loginWithCredentials(page, 'test@example.com', 'password123');
// 点击文件上传按钮
await page.getByRole('button', { name: 'Upload file' }).click();
// 选择文件(使用Playwright的文件上传功能)
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles('path/to/test-file.txt');
// 验证文件上传成功
await expect(page.getByText('test-file.txt')).toBeVisible();
await expect(page.getByText('File uploaded successfully')).toBeVisible();
});
高级测试场景
多浏览器兼容性测试
// 在playwright.config.ts中配置多浏览器项目
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
]
// 响应式设计测试
test('responsive design on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 }); // iPhone X尺寸
await page.goto('http://localhost:3000/login');
// 验证移动端布局
await expect(page.getByRole('button', { name: 'Menu' })).toBeVisible();
await expect(page.locator('.sidebar')).toBeHidden(); // 侧边栏应隐藏
});
性能测试集成
test('chat response time performance', async ({ page }) => {
await loginWithCredentials(page, 'test@example.com', 'password123');
const startTime = Date.now();
await page.getByPlaceholder('Message Chatbot UI...').fill('Test performance');
await page.getByRole('button', { name: 'Send message' }).click();
// 等待AI响应并测量时间
await page.waitForResponse(response =>
response.url().includes('/api/chat') && response.status() === 200
);
const responseTime = Date.now() - startTime;
// 性能断言:响应时间应小于2秒
expect(responseTime).toBeLessThan(2000);
console.log(`Chat response time: ${responseTime}ms`);
});
测试最佳实践
1. 测试数据管理
// test-data.ts
export const TestUsers = {
admin: { email: 'admin@example.com', password: 'admin123' },
user: { email: 'user@example.com', password: 'user123' },
guest: { email: 'guest@example.com', password: 'guest123' },
};
export const TestMessages = {
simple: 'Hello, world!',
long: 'This is a longer message to test message length handling and UI responsiveness.',
specialChars: 'Message with special chars: !@#$%^&*()_+{}|:"<>?[]\\;\',./`~'
};
2. 测试钩子和清理
// 全局设置和清理
test.beforeAll(async ({ browser }) => {
// 创建测试用户等准备工作
});
test.afterAll(async () => {
// 清理测试数据
});
test.beforeEach(async ({ page }) => {
// 每个测试前的准备工作
await page.context().clearCookies();
});
test.afterEach(async ({ page }, testInfo) => {
// 测试失败时截图
if (testInfo.status !== testInfo.expectedStatus) {
const screenshotPath = testInfo.outputPath(`failure-${testInfo.title}.png`);
await page.screenshot({ path: screenshotPath });
}
});
3. 环境变量配置
// 使用环境变量控制测试行为
const BASE_URL = process.env.TEST_BASE_URL || 'http://localhost:3000';
const IS_CI = process.env.CI === 'true';
test('environment aware test', async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
if (IS_CI) {
// CI环境特定的测试逻辑
test.slow(); // 标记为慢测试
}
// 通用测试逻辑
});
测试报告和CI集成
HTML测试报告
Playwright默认生成HTML报告,位于playwright-report目录。报告包含:
- 测试执行详情
- 失败测试的截图和录屏
- 性能指标和时间线
- 浏览器兼容性信息
CI/CD流水线集成
# GitHub Actions示例
name: Playwright Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
常见问题排查
1. 元素定位问题
// 使用更稳定的定位策略
// 不推荐:基于文本内容定位
await page.click('text=Login');
// 推荐:使用角色和名称定位
await page.getByRole('button', { name: 'Login' }).click();
// 或者使用测试ID
await page.getByTestId('login-button').click();
2. 异步操作处理
// 错误的等待方式
await page.waitForTimeout(5000); // 固定等待,不推荐
// 推荐的等待方式
await page.waitForLoadState('networkidle'); // 等待网络空闲
await page.waitForSelector('.chat-message'); // 等待特定元素
await page.waitForResponse('/api/chat'); // 等待API响应
3. 跨浏览器兼容性
// 处理浏览器差异
test('cross-browser compatibility', async ({ page, browserName }) => {
await page.goto('http://localhost:3000/login');
if (browserName === 'firefox') {
// Firefox特定的处理逻辑
await page.waitForTimeout(1000); // Firefox可能需要额外等待
}
// 通用测试逻辑
});
总结
通过本文的详细指导,您已经掌握了为chatbot-ui项目编写高质量端到端测试用例的核心技能。从基础配置到高级场景,从最佳实践到常见问题排查,这些知识将帮助您构建稳定可靠的自动化测试套件。
记住优秀的测试用例应该具备:
- 清晰的测试目的和预期结果
- 稳定的元素定位策略
- 适当的等待和异步处理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



