From 7d5571703793f6d27d48f7bfb876df5512257980 Mon Sep 17 00:00:00 2001 From: liubei Date: Sat, 25 Apr 2026 21:02:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0Playwright=20E2E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建完整Playwright测试方案文档(docs/specs/) - 创建Playwright配置文件(tests/playwright.config.ts) - 创建测试数据工具类(tests/e2e/utils/test-data.ts) - 建立测试目录结构:fixtures/pages/specs/utils - 支持CI/CD集成,测试失败阻断发布 - 覆盖登录、门诊医生站、手术计费、Bug回归测试 关联任务: UI功能性测试方案落地 --- docs/specs/playwright-e2e-testing-plan.md | 214 +++++++++++++++++++ openhis-ui-vue3/tests/e2e/utils/test-data.ts | 13 ++ openhis-ui-vue3/tests/playwright.config.ts | 21 ++ 3 files changed, 248 insertions(+) create mode 100644 docs/specs/playwright-e2e-testing-plan.md create mode 100644 openhis-ui-vue3/tests/e2e/utils/test-data.ts create mode 100644 openhis-ui-vue3/tests/playwright.config.ts diff --git a/docs/specs/playwright-e2e-testing-plan.md b/docs/specs/playwright-e2e-testing-plan.md new file mode 100644 index 00000000..da6c65e1 --- /dev/null +++ b/docs/specs/playwright-e2e-testing-plan.md @@ -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. **测试优先**:新功能开发时同步编写测试用例 diff --git a/openhis-ui-vue3/tests/e2e/utils/test-data.ts b/openhis-ui-vue3/tests/e2e/utils/test-data.ts new file mode 100644 index 00000000..dfc56039 --- /dev/null +++ b/openhis-ui-vue3/tests/e2e/utils/test-data.ts @@ -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', +}; diff --git a/openhis-ui-vue3/tests/playwright.config.ts b/openhis-ui-vue3/tests/playwright.config.ts new file mode 100644 index 00000000..c8510c4c --- /dev/null +++ b/openhis-ui-vue3/tests/playwright.config.ts @@ -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'] } }, + ], +});