ykq-playwright-ci 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +25 -0
- package/.nvmrc +1 -0
- package/README.md +553 -0
- package/bin/playwright-ci.mjs +116 -0
- package/cases/doctor-pc/auth/doctor-auth.setup.ts +15 -0
- package/cases/doctor-pc/prescription/submit-failure-keeps-drugs.spec.ts +10 -0
- package/cases/doctor-pc/smoke/authenticated-home.spec.ts +28 -0
- package/cases/doctor-pc/smoke/login-page.spec.ts +21 -0
- package/cases/doctor-pc/smoke/prescription-tab.spec.ts +30 -0
- package/cases/doctor-pc/test-env/doctor-master-basic.spec.ts +89 -0
- package/ci/github-actions/playwright.yml +61 -0
- package/docs/auth-runbook.md +44 -0
- package/docs/case-map.md +10 -0
- package/docs/ci.md +36 -0
- package/docs/runbook.md +39 -0
- package/docs/selector-convention.md +24 -0
- package/docs/test-data.md +23 -0
- package/eslint.config.mjs +28 -0
- package/package.json +64 -0
- package/playwright.config.ts +44 -0
- package/projects.config.json +33 -0
- package/scripts/configure-project.mjs +280 -0
- package/scripts/run-doctor-test.mjs +19 -0
- package/scripts/run-project.mjs +252 -0
- package/src/api/doctor.api.ts +25 -0
- package/src/api/test-data.api.ts +33 -0
- package/src/fixtures/auth.fixture.ts +26 -0
- package/src/fixtures/base.fixture.ts +14 -0
- package/src/fixtures/doctor.fixture.ts +39 -0
- package/src/flows/doctor-pc/chat.flow.ts +10 -0
- package/src/flows/doctor-pc/login.flow.ts +28 -0
- package/src/flows/doctor-pc/medicine.flow.ts +11 -0
- package/src/flows/doctor-pc/prescription.flow.ts +19 -0
- package/src/mocks/routes/prescription.routes.ts +25 -0
- package/src/mocks/websocket/.gitkeep +1 -0
- package/src/pages/doctor-pc/chat.page.ts +26 -0
- package/src/pages/doctor-pc/drug-dialog.page.ts +28 -0
- package/src/pages/doctor-pc/home.page.ts +30 -0
- package/src/pages/doctor-pc/login.page.ts +35 -0
- package/src/pages/doctor-pc/prescription.page.ts +29 -0
- package/src/pages/doctor-pc/worklist.page.ts +21 -0
- package/src/pages/shared/.gitkeep +1 -0
- package/src/utils/env.ts +78 -0
- package/src/utils/evidence.ts +34 -0
- package/src/utils/selectors.ts +9 -0
- package/src/utils/waiters.ts +11 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { test } from '../../../src/fixtures/doctor.fixture';
|
|
2
|
+
|
|
3
|
+
test.describe('prescription high-risk placeholders', () => {
|
|
4
|
+
test('PW-RX-004 @p0 @rx-high @mocked submit failure keeps selected drugs', async () => {
|
|
5
|
+
test.skip(
|
|
6
|
+
true,
|
|
7
|
+
'Blocked until prescription test data and stable selectors are confirmed. See docs/test-data.md.'
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { test, expect } from '../../../src/fixtures/base.fixture';
|
|
2
|
+
import { createDoctorAuthContext, hasDoctorAuthState } from '../../../src/fixtures/auth.fixture';
|
|
3
|
+
import { HomePage } from '../../../src/pages/doctor-pc/home.page';
|
|
4
|
+
import { annotateCase } from '../../../src/utils/evidence';
|
|
5
|
+
import { hasDoctorBaseURL } from '../../../src/utils/env';
|
|
6
|
+
|
|
7
|
+
test.skip(!hasDoctorBaseURL(), 'DOCTOR_BASE_URL is required for deployed smoke tests.');
|
|
8
|
+
|
|
9
|
+
test('PW-SMK-002 @smoke authenticated doctor home loads', async ({ browser }, testInfo) => {
|
|
10
|
+
test.skip(!hasDoctorAuthState(), 'Doctor storageState is missing; see docs/auth-runbook.md.');
|
|
11
|
+
|
|
12
|
+
annotateCase(testInfo, {
|
|
13
|
+
caseId: 'PW-SMK-002',
|
|
14
|
+
evidenceId: 'SMOKE-authenticated-home',
|
|
15
|
+
priority: 'Smoke'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const context = await createDoctorAuthContext(browser);
|
|
19
|
+
try {
|
|
20
|
+
const page = await context.newPage();
|
|
21
|
+
const homePage = new HomePage(page);
|
|
22
|
+
await homePage.goto();
|
|
23
|
+
await homePage.assertLoaded();
|
|
24
|
+
await expect(page.locator('body')).toBeVisible();
|
|
25
|
+
} finally {
|
|
26
|
+
await context.close();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { test, expect } from '../../../src/fixtures/doctor.fixture';
|
|
2
|
+
import { annotateCase } from '../../../src/utils/evidence';
|
|
3
|
+
import { hasDoctorBaseURL } from '../../../src/utils/env';
|
|
4
|
+
|
|
5
|
+
test.skip(!hasDoctorBaseURL(), 'DOCTOR_BASE_URL is required for deployed smoke tests.');
|
|
6
|
+
|
|
7
|
+
test('PW-SMK-001 @smoke login page renders without blank screen', async (
|
|
8
|
+
{ loginPage },
|
|
9
|
+
testInfo
|
|
10
|
+
) => {
|
|
11
|
+
annotateCase(testInfo, {
|
|
12
|
+
caseId: 'PW-SMK-001',
|
|
13
|
+
sourceId: 'SMK-01',
|
|
14
|
+
evidenceId: 'SMOKE-login-page-render',
|
|
15
|
+
priority: 'Smoke'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await loginPage.goto();
|
|
19
|
+
await loginPage.assertLoaded();
|
|
20
|
+
await expect(testInfo.status).toBe(testInfo.expectedStatus);
|
|
21
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test, expect } from '../../../src/fixtures/base.fixture';
|
|
2
|
+
import { createDoctorAuthContext, hasDoctorAuthState } from '../../../src/fixtures/auth.fixture';
|
|
3
|
+
import { HomePage } from '../../../src/pages/doctor-pc/home.page';
|
|
4
|
+
import { PrescriptionPage } from '../../../src/pages/doctor-pc/prescription.page';
|
|
5
|
+
import { annotateCase } from '../../../src/utils/evidence';
|
|
6
|
+
import { hasDoctorBaseURL } from '../../../src/utils/env';
|
|
7
|
+
|
|
8
|
+
test.skip(!hasDoctorBaseURL(), 'DOCTOR_BASE_URL is required for deployed smoke tests.');
|
|
9
|
+
|
|
10
|
+
test('PW-SMK-003 @smoke prescription tab can open', async ({ browser }, testInfo) => {
|
|
11
|
+
test.skip(!hasDoctorAuthState(), 'Doctor storageState is missing; see docs/auth-runbook.md.');
|
|
12
|
+
|
|
13
|
+
annotateCase(testInfo, {
|
|
14
|
+
caseId: 'PW-SMK-003',
|
|
15
|
+
evidenceId: 'SMOKE-prescription-tab-open',
|
|
16
|
+
priority: 'Smoke'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const context = await createDoctorAuthContext(browser);
|
|
20
|
+
try {
|
|
21
|
+
const page = await context.newPage();
|
|
22
|
+
const homePage = new HomePage(page);
|
|
23
|
+
await homePage.goto();
|
|
24
|
+
await homePage.openPrescriptionTab();
|
|
25
|
+
await new PrescriptionPage(page).assertVisible();
|
|
26
|
+
await expect(page.locator('body')).toBeVisible();
|
|
27
|
+
} finally {
|
|
28
|
+
await context.close();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { test, expect } from '../../../src/fixtures/base.fixture';
|
|
2
|
+
import { annotateCase } from '../../../src/utils/evidence';
|
|
3
|
+
import { appEnv, doctorURL, hasDoctorBaseURL } from '../../../src/utils/env';
|
|
4
|
+
|
|
5
|
+
async function gotoDoctor(path: string, page: import('@playwright/test').Page): Promise<void> {
|
|
6
|
+
await page.goto(doctorURL(path), { waitUntil: 'domcontentloaded' });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
test.describe('doctor-master test environment basics', () => {
|
|
10
|
+
test.beforeEach(async () => {
|
|
11
|
+
test.skip(!hasDoctorBaseURL(), 'DOCTOR_BASE_URL is required for doctor-master test environment tests.');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('PW-TEST-001 @smoke login page renders required login controls', async ({ page }, testInfo) => {
|
|
15
|
+
annotateCase(testInfo, {
|
|
16
|
+
caseId: 'PW-TEST-001',
|
|
17
|
+
evidenceId: 'TEST-login-page-render',
|
|
18
|
+
priority: 'Smoke'
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await gotoDoctor(appEnv.doctorLoginPath, page);
|
|
22
|
+
|
|
23
|
+
await expect(page).toHaveTitle('荷叶医生');
|
|
24
|
+
await expect(page.getByRole('heading', { name: '荷叶医生' })).toBeVisible();
|
|
25
|
+
await expect(page.getByPlaceholder('手机号')).toBeVisible();
|
|
26
|
+
await expect(page.getByPlaceholder('图形验证码')).toBeVisible();
|
|
27
|
+
await expect(page.getByPlaceholder('验证码', { exact: true })).toBeVisible();
|
|
28
|
+
await expect(page.locator('.verify-img')).toBeVisible();
|
|
29
|
+
await expect(page.getByText('发送验证码')).toBeVisible();
|
|
30
|
+
await expect(page.getByText('我已阅读并同意')).toBeVisible();
|
|
31
|
+
await expect(page.getByText('《荷叶健康用户服务协议》')).toBeVisible();
|
|
32
|
+
await expect(page.locator('.submit-btn', { hasText: '登录' })).toHaveClass(/gruyBtn/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('PW-TEST-002 @smoke unauthenticated home redirects to login by route guard', async ({
|
|
36
|
+
page
|
|
37
|
+
}, testInfo) => {
|
|
38
|
+
annotateCase(testInfo, {
|
|
39
|
+
caseId: 'PW-TEST-002',
|
|
40
|
+
evidenceId: 'TEST-home-redirect-login',
|
|
41
|
+
priority: 'Smoke'
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await gotoDoctor(`${appEnv.doctorPublicPath}home`, page);
|
|
45
|
+
|
|
46
|
+
await expect(page).toHaveURL(/\/physician-admin\/login(?:\?|#|$)/);
|
|
47
|
+
await expect(page.getByRole('heading', { name: '荷叶医生' })).toBeVisible();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('PW-TEST-003 @smoke service agreement is public by whitelist', async ({ page }, testInfo) => {
|
|
51
|
+
annotateCase(testInfo, {
|
|
52
|
+
caseId: 'PW-TEST-003',
|
|
53
|
+
evidenceId: 'TEST-service-agreement-render',
|
|
54
|
+
priority: 'Smoke'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await gotoDoctor(`${appEnv.doctorPublicPath}serviceAgreement`, page);
|
|
58
|
+
|
|
59
|
+
await expect(page).toHaveURL(/\/physician-admin\/serviceAgreement(?:\?|#|$)/);
|
|
60
|
+
await expect(page.locator('body')).toContainText('荷叶健康用户服务协议');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('PW-TEST-004 @smoke login validation follows doctor-master form rules', async ({
|
|
64
|
+
page
|
|
65
|
+
}, testInfo) => {
|
|
66
|
+
annotateCase(testInfo, {
|
|
67
|
+
caseId: 'PW-TEST-004',
|
|
68
|
+
evidenceId: 'TEST-login-validation',
|
|
69
|
+
priority: 'Smoke'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await gotoDoctor(appEnv.doctorLoginPath, page);
|
|
73
|
+
|
|
74
|
+
await page.locator('.submit-btn', { hasText: '登录' }).click();
|
|
75
|
+
await expect(page.getByText('手机号输入不能为空')).toBeVisible();
|
|
76
|
+
await expect(page.getByText('图形验证码不能为空')).toBeVisible();
|
|
77
|
+
await expect(page.getByText('验证码不能为空', { exact: true })).toBeVisible();
|
|
78
|
+
|
|
79
|
+
await page.getByPlaceholder('手机号').fill('12345');
|
|
80
|
+
await page.getByPlaceholder('图形验证码').fill('abcd');
|
|
81
|
+
await page.getByPlaceholder('验证码', { exact: true }).fill('123456');
|
|
82
|
+
await page.locator('.submit-btn', { hasText: '登录' }).click();
|
|
83
|
+
await expect(page.getByText('请输入正确的手机号')).toBeVisible();
|
|
84
|
+
|
|
85
|
+
await page.getByPlaceholder('手机号').fill('13800138000');
|
|
86
|
+
await page.locator('.submit-btn', { hasText: '登录' }).click();
|
|
87
|
+
await expect(page.getByText('请阅读并勾选《荷叶健康用户服务协议》')).toBeVisible();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: playwright
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
inputs:
|
|
7
|
+
suite:
|
|
8
|
+
description: "Suite to run: smoke, p0, or release"
|
|
9
|
+
default: "smoke"
|
|
10
|
+
schedule:
|
|
11
|
+
- cron: "0 18 * * *"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
playwright:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 30
|
|
17
|
+
env:
|
|
18
|
+
DOCTOR_BASE_URL: ${{ secrets.DOCTOR_BASE_URL }}
|
|
19
|
+
DOCTOR_USERNAME: ${{ secrets.DOCTOR_USERNAME }}
|
|
20
|
+
DOCTOR_PASSWORD: ${{ secrets.DOCTOR_PASSWORD }}
|
|
21
|
+
TEST_ENV: test
|
|
22
|
+
BUILD_SHA: ${{ github.sha }}
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version-file: .nvmrc
|
|
29
|
+
cache: npm
|
|
30
|
+
|
|
31
|
+
- run: npm ci
|
|
32
|
+
|
|
33
|
+
- run: npx playwright install --with-deps chromium
|
|
34
|
+
|
|
35
|
+
- name: Check target URL
|
|
36
|
+
run: |
|
|
37
|
+
if [ -z "$DOCTOR_BASE_URL" ]; then
|
|
38
|
+
echo "DOCTOR_BASE_URL is required in CI."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
- name: Generate auth state
|
|
43
|
+
if: ${{ env.DOCTOR_USERNAME != '' && env.DOCTOR_PASSWORD != '' }}
|
|
44
|
+
run: npm run auth:doctor
|
|
45
|
+
|
|
46
|
+
- name: Run selected suite
|
|
47
|
+
run: |
|
|
48
|
+
case "${{ github.event.inputs.suite || 'smoke' }}" in
|
|
49
|
+
p0) npm run test:p0 ;;
|
|
50
|
+
release) npm run test:release ;;
|
|
51
|
+
*) npm run test:smoke ;;
|
|
52
|
+
esac
|
|
53
|
+
|
|
54
|
+
- uses: actions/upload-artifact@v4
|
|
55
|
+
if: always()
|
|
56
|
+
with:
|
|
57
|
+
name: playwright-artifacts
|
|
58
|
+
path: |
|
|
59
|
+
playwright-report/
|
|
60
|
+
test-results/
|
|
61
|
+
if-no-files-found: ignore
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 登录态说明
|
|
2
|
+
|
|
3
|
+
## 自动生成
|
|
4
|
+
|
|
5
|
+
配置 `.env`:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
DOCTOR_BASE_URL=https://your-test-doctor-pc.example.com
|
|
9
|
+
DOCTOR_USERNAME=doctor-account
|
|
10
|
+
DOCTOR_PASSWORD=doctor-password
|
|
11
|
+
DOCTOR_OTP=optional-otp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
运行:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run auth:doctor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
成功后会生成:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
playwright/.auth/doctor.json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
该文件包含登录态,已被 `.gitignore` 排除。
|
|
27
|
+
|
|
28
|
+
## 手工生成
|
|
29
|
+
|
|
30
|
+
当验证码无法自动化时,可以先手工生成:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx playwright codegen --save-storage=playwright/.auth/doctor.json "$DOCTOR_BASE_URL"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
在打开的浏览器中完成登录,然后关闭窗口。之后再运行:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run test:smoke
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 当前阻塞
|
|
43
|
+
|
|
44
|
+
规划里 `WP-03 登录态与账号池` 仍是 `Blocked`。在后端测试登录接口、验证码绕过或稳定测试账号确认前,CI 只能跑不依赖登录态的检查,或者使用定期刷新的 storageState。
|
package/docs/case-map.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# 用例映射
|
|
2
|
+
|
|
3
|
+
| Playwright Case ID | Source QA ID | Evidence ID | 优先级 | 文件 |
|
|
4
|
+
| --- | --- | --- | --- | --- |
|
|
5
|
+
| `PW-SMK-001` | `SMK-01` | `SMOKE-login-page-render` | Smoke | `cases/doctor-pc/smoke/login-page.spec.ts` |
|
|
6
|
+
| `PW-SMK-002` | 待补 | `SMOKE-authenticated-home` | Smoke | `cases/doctor-pc/smoke/authenticated-home.spec.ts` |
|
|
7
|
+
| `PW-SMK-003` | 待补 | `SMOKE-prescription-tab-open` | Smoke | `cases/doctor-pc/smoke/prescription-tab.spec.ts` |
|
|
8
|
+
| `PW-RX-004` | `QA-RXHIGH-006` | `RX-submit-fail-keep-drugs` | P0 | `cases/doctor-pc/prescription/submit-failure-keeps-drugs.spec.ts` |
|
|
9
|
+
|
|
10
|
+
`PW-RX-004` 当前只保留跳过用例,用来固定后续实现位置。真实实现依赖处方测试数据、稳定选择器和 mock 策略确认。
|
package/docs/ci.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# CI 接入说明
|
|
2
|
+
|
|
3
|
+
## 流水线分层
|
|
4
|
+
|
|
5
|
+
| 流程 | 命令 | 触发 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| PR Smoke | `npm run test:smoke` | PR 或测试仓变更 |
|
|
8
|
+
| Nightly P0 | `npm run test:p0` | 每晚定时 |
|
|
9
|
+
| Release Regression | `npm run test:release` | 发版前手动 |
|
|
10
|
+
|
|
11
|
+
## 环境变量
|
|
12
|
+
|
|
13
|
+
CI 至少需要:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
DOCTOR_BASE_URL=https://your-test-doctor-pc.example.com
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
如果要跑已登录用例,还需要提供稳定登录态:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
DOCTOR_USERNAME=
|
|
23
|
+
DOCTOR_PASSWORD=
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
或在 CI secret 中提供 `playwright/.auth/doctor.json` 的安全还原方式。
|
|
27
|
+
|
|
28
|
+
## 产物
|
|
29
|
+
|
|
30
|
+
建议归档:
|
|
31
|
+
|
|
32
|
+
- `playwright-report/`
|
|
33
|
+
- `test-results/`
|
|
34
|
+
- `playwright/.auth/` 不归档
|
|
35
|
+
|
|
36
|
+
GitHub Actions 模板在 `ci/github-actions/playwright.yml`。确认公司实际 CI 平台后,再移动到对应平台配置位置。
|
package/docs/runbook.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 运行手册
|
|
2
|
+
|
|
3
|
+
## 本地运行
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install
|
|
7
|
+
npx playwright install chromium
|
|
8
|
+
cp .env.example .env
|
|
9
|
+
npm run test:smoke
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`DOCTOR_BASE_URL` 未配置时,真实页面用例会跳过。这样可以先验证工程、类型检查和 CI 脚本,不把未确认的测试环境伪装成可用。
|
|
13
|
+
|
|
14
|
+
## 登录态
|
|
15
|
+
|
|
16
|
+
优先使用测试登录接口或账号密码生成 storageState:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm run auth:doctor
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
如果验证码暂时无法自动化,按 `docs/auth-runbook.md` 手工生成 `playwright/.auth/doctor.json`。
|
|
23
|
+
|
|
24
|
+
## 报告和证据
|
|
25
|
+
|
|
26
|
+
失败时 Playwright 会保留:
|
|
27
|
+
|
|
28
|
+
- trace
|
|
29
|
+
- screenshot
|
|
30
|
+
- video
|
|
31
|
+
- HTML report
|
|
32
|
+
- JUnit report
|
|
33
|
+
- JSON report
|
|
34
|
+
|
|
35
|
+
报告命令:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run report
|
|
39
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 选择器规范
|
|
2
|
+
|
|
3
|
+
## 优先级
|
|
4
|
+
|
|
5
|
+
1. `getByRole`
|
|
6
|
+
2. `getByLabel`
|
|
7
|
+
3. `getByText`
|
|
8
|
+
4. `data-testid`
|
|
9
|
+
5. 其他稳定业务属性
|
|
10
|
+
|
|
11
|
+
禁止把样式类名、DOM 层级或 `nth-child` 作为主要定位方式。
|
|
12
|
+
|
|
13
|
+
## 建议补充的 `data-testid`
|
|
14
|
+
|
|
15
|
+
| 区域 | 建议标识 |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| 问诊列表 | `doctor-worklist`、`inquiry-card-{inquiryId}` |
|
|
18
|
+
| 聊天区 | `chat-panel`、`chat-input`、`chat-send-button` |
|
|
19
|
+
| 处方区 | `prescription-panel`、`prescription-submit-button` |
|
|
20
|
+
| 诊断搜索 | `diagnosis-search-input` |
|
|
21
|
+
| 药品弹窗 | `drug-dialog`、`drug-search-input`、`drug-row-{drugName}` |
|
|
22
|
+
| 自动关诊 | `auto-close-countdown`、`auto-close-confirm-button` |
|
|
23
|
+
|
|
24
|
+
列表项尽量暴露业务 ID,便于断言不串问诊、不串处方。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 测试数据
|
|
2
|
+
|
|
3
|
+
## 首批数据标签
|
|
4
|
+
|
|
5
|
+
| 标签 | 用途 | 当前状态 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `doctor-account` | 医生端登录和并发隔离 | `Blocked` |
|
|
8
|
+
| `patient-account` | 患者发消息、视频、接诊配合 | `Blocked` |
|
|
9
|
+
| `first-prescription-inquiry` | 首次开方链路 | `Blocked` |
|
|
10
|
+
| `withdrawable-prescription` | 撤回修改处方 | `Blocked` |
|
|
11
|
+
| `drug-sample` | 药品搜索、库存、互斥 | `Blocked` |
|
|
12
|
+
| `auto-close-inquiry` | 自动关诊倒计时和关诊 | `Blocked` |
|
|
13
|
+
|
|
14
|
+
## 数据原则
|
|
15
|
+
|
|
16
|
+
- 修改同一问诊或处方的用例必须串行或独占数据。
|
|
17
|
+
- P0 用例优先使用可重复准备的数据,不依赖人工临时造数。
|
|
18
|
+
- 真实 IM/WebSocket 链路放 nightly,异常分支优先通过 mock 或测试桩覆盖。
|
|
19
|
+
- 每条用例需要记录 `Playwright Case ID`、来源 QA ID、Evidence ID 和 Data Tag。
|
|
20
|
+
|
|
21
|
+
## 后续接口约定
|
|
22
|
+
|
|
23
|
+
`src/api/test-data.api.ts` 预留了 `lease` 调用,等测试数据服务确认后再对齐真实路径和返回结构。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{
|
|
6
|
+
ignores: [
|
|
7
|
+
'node_modules/**',
|
|
8
|
+
'playwright-report/**',
|
|
9
|
+
'test-results/**',
|
|
10
|
+
'blob-report/**'
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
...tseslint.configs.recommended,
|
|
15
|
+
{
|
|
16
|
+
files: ['**/*.ts'],
|
|
17
|
+
rules: {
|
|
18
|
+
'no-empty-pattern': 'off',
|
|
19
|
+
'@typescript-eslint/no-unused-vars': [
|
|
20
|
+
'error',
|
|
21
|
+
{
|
|
22
|
+
argsIgnorePattern: '^_',
|
|
23
|
+
varsIgnorePattern: '^_'
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ykq-playwright-ci",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Company-level Playwright E2E test project for doctor PC workflows.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"playwright-ci": "bin/playwright-ci.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"cases/",
|
|
11
|
+
"ci/",
|
|
12
|
+
"docs/",
|
|
13
|
+
"scripts/",
|
|
14
|
+
"src/",
|
|
15
|
+
".env.example",
|
|
16
|
+
".nvmrc",
|
|
17
|
+
"eslint.config.mjs",
|
|
18
|
+
"package.json",
|
|
19
|
+
"playwright.config.ts",
|
|
20
|
+
"projects.config.json",
|
|
21
|
+
"README.md",
|
|
22
|
+
"tsconfig.json"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "playwright test",
|
|
29
|
+
"test:smoke": "playwright test --grep @smoke",
|
|
30
|
+
"test:p0": "playwright test --grep @p0 --workers=2",
|
|
31
|
+
"test:release": "playwright test --grep \"@p0|@rx-high\"",
|
|
32
|
+
"project:list": "node scripts/configure-project.mjs list",
|
|
33
|
+
"project:show": "node scripts/configure-project.mjs show",
|
|
34
|
+
"project:add": "node scripts/configure-project.mjs add",
|
|
35
|
+
"project:update": "node scripts/configure-project.mjs update",
|
|
36
|
+
"project:delete": "node scripts/configure-project.mjs delete",
|
|
37
|
+
"project:remove": "node scripts/configure-project.mjs remove",
|
|
38
|
+
"project:test": "node scripts/run-project.mjs",
|
|
39
|
+
"test:doctor-test": "node scripts/run-doctor-test.mjs",
|
|
40
|
+
"doctor:test": "node scripts/run-project.mjs doctor-pc",
|
|
41
|
+
"doctor:test:headed": "node scripts/run-project.mjs doctor-pc --headed",
|
|
42
|
+
"doctor:test:debug": "node scripts/run-project.mjs doctor-pc --debug",
|
|
43
|
+
"doctor:test:ui": "node scripts/run-project.mjs doctor-pc --ui",
|
|
44
|
+
"doctor:test:local": "DOCTOR_BASE_URL=https://ys-test.heyejk.com DOCTOR_PUBLIC_PATH=/physician-admin/ DOCTOR_LOGIN_PATH=/physician-admin/login playwright test cases/doctor-pc/test-env --project=chromium --no-deps",
|
|
45
|
+
"patient:test": "node scripts/run-project.mjs patient-h5",
|
|
46
|
+
"admin:test": "node scripts/run-project.mjs admin",
|
|
47
|
+
"test:headed": "playwright test --headed",
|
|
48
|
+
"auth:doctor": "playwright test --project=setup",
|
|
49
|
+
"report": "playwright show-report",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"lint": "eslint ."
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@playwright/test": "^1.49.0",
|
|
55
|
+
"dotenv": "^16.4.7"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@eslint/js": "^9.17.0",
|
|
59
|
+
"@types/node": "^22.10.0",
|
|
60
|
+
"eslint": "^9.17.0",
|
|
61
|
+
"typescript": "^5.7.2",
|
|
62
|
+
"typescript-eslint": "^8.18.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
import { appEnv } from './src/utils/env';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
testDir: '.',
|
|
6
|
+
testMatch: ['cases/**/*.spec.ts', 'cases/**/*.setup.ts'],
|
|
7
|
+
outputDir: 'test-results',
|
|
8
|
+
timeout: 60_000,
|
|
9
|
+
expect: {
|
|
10
|
+
timeout: 10_000
|
|
11
|
+
},
|
|
12
|
+
fullyParallel: false,
|
|
13
|
+
forbidOnly: Boolean(process.env.CI),
|
|
14
|
+
retries: process.env.CI ? 2 : 0,
|
|
15
|
+
workers: process.env.CI ? 2 : undefined,
|
|
16
|
+
reporter: [
|
|
17
|
+
['list'],
|
|
18
|
+
['html', { outputFolder: 'playwright-report', open: 'never' }],
|
|
19
|
+
['junit', { outputFile: 'test-results/junit.xml' }],
|
|
20
|
+
['json', { outputFile: 'test-results/results.json' }]
|
|
21
|
+
],
|
|
22
|
+
use: {
|
|
23
|
+
baseURL: appEnv.doctorBaseURL,
|
|
24
|
+
actionTimeout: 15_000,
|
|
25
|
+
navigationTimeout: 30_000,
|
|
26
|
+
trace: 'retain-on-failure',
|
|
27
|
+
screenshot: 'only-on-failure',
|
|
28
|
+
video: 'retain-on-failure'
|
|
29
|
+
},
|
|
30
|
+
projects: [
|
|
31
|
+
{
|
|
32
|
+
name: 'setup',
|
|
33
|
+
testMatch: /.*\.setup\.ts/
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'chromium',
|
|
37
|
+
dependencies: ['setup'],
|
|
38
|
+
testIgnore: /.*\.setup\.ts/,
|
|
39
|
+
use: {
|
|
40
|
+
...devices['Desktop Chrome']
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"caseCacheDir": ".case-repos",
|
|
3
|
+
"projects": {
|
|
4
|
+
"doctor-pc": {
|
|
5
|
+
"description": "医生端 PC 测试环境基础用例",
|
|
6
|
+
"repo": "https://git.int.ybm100.com/ykq/doctor-pc-web-test-store",
|
|
7
|
+
"branch": "master",
|
|
8
|
+
"caseDir": ".case-repos/doctor-pc",
|
|
9
|
+
"env": {
|
|
10
|
+
"DOCTOR_BASE_URL": "https://ys-test.heyejk.com",
|
|
11
|
+
"DOCTOR_PUBLIC_PATH": "/physician-admin/",
|
|
12
|
+
"DOCTOR_LOGIN_PATH": "/physician-admin/login"
|
|
13
|
+
},
|
|
14
|
+
"playwrightArgs": ["--project=chromium", "--no-deps"]
|
|
15
|
+
},
|
|
16
|
+
"patient-h5": {
|
|
17
|
+
"description": "患者端 H5 用例库占位,配置 repo 后可运行",
|
|
18
|
+
"repo": "",
|
|
19
|
+
"branch": "main",
|
|
20
|
+
"caseDir": ".case-repos/patient-h5",
|
|
21
|
+
"env": {},
|
|
22
|
+
"playwrightArgs": ["--project=chromium"]
|
|
23
|
+
},
|
|
24
|
+
"admin": {
|
|
25
|
+
"description": "运营后台用例库占位,配置 repo 后可运行",
|
|
26
|
+
"repo": "",
|
|
27
|
+
"branch": "main",
|
|
28
|
+
"caseDir": ".case-repos/admin",
|
|
29
|
+
"env": {},
|
|
30
|
+
"playwrightArgs": ["--project=chromium"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|