feat: 添加Playwright E2E自动化测试完整方案
- 创建完整Playwright测试方案文档(docs/specs/) - 创建Playwright配置文件(tests/playwright.config.ts) - 创建测试数据工具类(tests/e2e/utils/test-data.ts) - 建立测试目录结构:fixtures/pages/specs/utils - 支持CI/CD集成,测试失败阻断发布 - 覆盖登录、门诊医生站、手术计费、Bug回归测试 关联任务: UI功能性测试方案落地
This commit is contained in:
214
docs/specs/playwright-e2e-testing-plan.md
Normal file
214
docs/specs/playwright-e2e-testing-plan.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# HIS项目 Playwright E2E 自动化测试方案 v1.0
|
||||||
|
|
||||||
|
## 一、方案概述
|
||||||
|
|
||||||
|
### 1.1 选型理由
|
||||||
|
- **Playwright** 是微软开源的端到端测试框架,完美适配 Vue 3 + Vite 技术栈
|
||||||
|
- 自动等待机制适合HIS系统复杂交互场景(异步加载、动态渲染)
|
||||||
|
- 支持多浏览器(Chromium/Firefox/WebKit),CI/CD集成成熟
|
||||||
|
- 已有 `@playwright/test ^1.58.2` 依赖 installed
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
1. 核心业务流程自动化覆盖率达到 80%+
|
||||||
|
2. 已修复Bug 100% 回归测试覆盖
|
||||||
|
3. 每次代码推送自动触发测试,失败阻断发布
|
||||||
|
|
||||||
|
## 二、项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
openhis-ui-vue3/
|
||||||
|
├── tests/
|
||||||
|
│ ├── e2e/
|
||||||
|
│ │ ├── fixtures/ # 测试夹具
|
||||||
|
│ │ │ └── auth.ts # 登录认证fixture
|
||||||
|
│ │ ├── pages/ # 页面对象模型(POM)
|
||||||
|
│ │ │ ├── LoginPage.ts
|
||||||
|
│ │ │ ├── DoctorStationPage.ts
|
||||||
|
│ │ │ └── SurgeryBillingPage.ts
|
||||||
|
│ │ ├── specs/ # 测试用例
|
||||||
|
│ │ │ ├── login.spec.ts
|
||||||
|
│ │ │ ├── doctor-station.spec.ts
|
||||||
|
│ │ │ ├── surgery-billing.spec.ts
|
||||||
|
│ │ │ └── bug-regression.spec.ts # Bug回归测试
|
||||||
|
│ │ └── utils/
|
||||||
|
│ │ └── test-data.ts # 测试数据
|
||||||
|
│ └── playwright.config.ts # Playwright配置
|
||||||
|
├── .env.test # 测试环境变量
|
||||||
|
└── package.json # 已有playwright依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三、环境配置
|
||||||
|
|
||||||
|
### 3.1 环境变量(.env.test)
|
||||||
|
```bash
|
||||||
|
# 测试环境配置
|
||||||
|
VITE_APP_BASE_API=http://192.168.110.253:8080
|
||||||
|
TEST_USERNAME=test_admin
|
||||||
|
TEST_PASSWORD=test123456
|
||||||
|
TEST_BASE_URL=http://localhost:80
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Playwright配置(playwright.config.ts)
|
||||||
|
```typescript
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests/e2e/specs',
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
expect: { timeout: 10000 },
|
||||||
|
fullyParallel: false,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: 1,
|
||||||
|
reporter: [['html', { outputFolder: 'playwright-report' }], ['list']],
|
||||||
|
use: {
|
||||||
|
baseURL: process.env.TEST_BASE_URL || 'http://localhost:80',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
video: 'retain-on-failure',
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、核心测试用例
|
||||||
|
|
||||||
|
### 4.1 登录测试(login.spec.ts)
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('用户登录成功', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', process.env.TEST_USERNAME || 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', process.env.TEST_PASSWORD || '123456');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await expect(page).toHaveURL(/.*dashboard.*/);
|
||||||
|
await expect(page.locator('.user-avatar')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('登录失败-错误密码', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', 'wrongpassword');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await expect(page.locator('.el-message--error')).toBeVisible();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 门诊医生站测试(doctor-station.spec.ts)
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('门诊医生站', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// 登录
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', process.env.TEST_USERNAME || 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', process.env.TEST_PASSWORD || '123456');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await page.waitForURL(/.*dashboard.*/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#427 检查项目分类手风琴展开', async ({ page }) => {
|
||||||
|
await page.goto('/doctorstation');
|
||||||
|
// 点击第一个分类
|
||||||
|
await page.click('.category-item >> nth=0');
|
||||||
|
await expect(page.locator('.category-content >> nth=0')).toBeVisible();
|
||||||
|
// 点击第二个分类,第一个应收起
|
||||||
|
await page.click('.category-item >> nth=1');
|
||||||
|
await expect(page.locator('.category-content >> nth=0')).not.toBeVisible();
|
||||||
|
await expect(page.locator('.category-content >> nth=1')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 手术计费回归测试(bug-regression.spec.ts)
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Bug回归测试', () => {
|
||||||
|
test('#437 手术计费防重复提交', async ({ page }) => {
|
||||||
|
// 登录并导航到手术计费
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', process.env.TEST_USERNAME || 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', process.env.TEST_PASSWORD || '123456');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await page.waitForURL(/.*dashboard.*/);
|
||||||
|
await page.goto('/surgery-billing');
|
||||||
|
|
||||||
|
// 快速连续点击新增按钮(测试防重复锁)
|
||||||
|
const addBtn = page.locator('button:has-text("新增")');
|
||||||
|
await addBtn.click();
|
||||||
|
await addBtn.click(); // 第二次应被阻止
|
||||||
|
await addBtn.click(); // 第三次应被阻止
|
||||||
|
|
||||||
|
// 验证只弹出一个表单
|
||||||
|
await expect(page.locator('.el-dialog')).toHaveCount(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、执行命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装浏览器
|
||||||
|
npx playwright install chromium
|
||||||
|
|
||||||
|
# 运行所有测试
|
||||||
|
npm run test:e2e
|
||||||
|
|
||||||
|
# 运行单个测试文件
|
||||||
|
npx playwright test login.spec.ts
|
||||||
|
|
||||||
|
# 生成HTML报告
|
||||||
|
npx playwright show-report
|
||||||
|
|
||||||
|
# UI模式(调试用)
|
||||||
|
npx playwright test --ui
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、CI/CD集成
|
||||||
|
|
||||||
|
### 6.1 package.json脚本
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"test:e2e:report": "playwright show-report"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Spug流水线集成
|
||||||
|
```yaml
|
||||||
|
# Spug 构建后阶段添加
|
||||||
|
- name: E2E Testing
|
||||||
|
script: |
|
||||||
|
cd openhis-ui-vue3
|
||||||
|
npx playwright install --with-deps chromium
|
||||||
|
npm run test:e2e -- --reporter=html
|
||||||
|
# 测试失败则阻断发布
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "E2E测试失败,阻断发布!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 七、实施计划
|
||||||
|
|
||||||
|
| 阶段 | 时间 | 内容 | 负责人 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| Phase 1 | 第1周 | 登录+核心页面冒烟测试 | 张飞+赵云 |
|
||||||
|
| Phase 2 | 第2-3周 | 门诊医生站+手术计费全流程 | 张飞 |
|
||||||
|
| Phase 3 | 第4周 | Bug回归测试全覆盖 | 张飞 |
|
||||||
|
| Phase 4 | 第5周 | CI/CD流水线集成 | 赵云+运维 |
|
||||||
|
|
||||||
|
## 八、注意事项
|
||||||
|
|
||||||
|
1. **测试数据隔离**:使用独立的测试数据库,不污染生产数据
|
||||||
|
2. **环境变量**:敏感信息通过 `.env.test` 管理,不提交到git
|
||||||
|
3. **截图留痕**:失败时自动截图,便于排查
|
||||||
|
4. **测试优先**:新功能开发时同步编写测试用例
|
||||||
13
openhis-ui-vue3/tests/e2e/utils/test-data.ts
Normal file
13
openhis-ui-vue3/tests/e2e/utils/test-data.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const TEST_USERS = {
|
||||||
|
admin: {
|
||||||
|
username: process.env.TEST_USERNAME || 'admin',
|
||||||
|
password: process.env.TEST_PASSWORD || '123456',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_URLS = {
|
||||||
|
login: '/',
|
||||||
|
dashboard: '/dashboard',
|
||||||
|
doctorStation: '/doctorstation',
|
||||||
|
surgeryBilling: '/surgery-billing',
|
||||||
|
};
|
||||||
21
openhis-ui-vue3/tests/playwright.config.ts
Normal file
21
openhis-ui-vue3/tests/playwright.config.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests/e2e/specs',
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
expect: { timeout: 10000 },
|
||||||
|
fullyParallel: false,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: 1,
|
||||||
|
reporter: [['html', { outputFolder: 'playwright-report' }], ['list']],
|
||||||
|
use: {
|
||||||
|
baseURL: process.env.TEST_BASE_URL || 'http://localhost:80',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
video: 'retain-on-failure',
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user