jsharness 1.0.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.
Files changed (68) hide show
  1. package/.harness/README.md +199 -0
  2. package/.harness/agents/code-reviewer/contract.yaml +64 -0
  3. package/.harness/agents/developer/contract.yaml +72 -0
  4. package/.harness/agents/gate-controller/contract.yaml +64 -0
  5. package/.harness/agents/project-manager/contract.yaml +77 -0
  6. package/.harness/agents/prompt-templates.md +352 -0
  7. package/.harness/agents/requirements-analyst/contract.yaml +64 -0
  8. package/.harness/agents/solution-designer/contract.yaml +75 -0
  9. package/.harness/agents/tester/contract.yaml +92 -0
  10. package/.harness/config/models.yaml +67 -0
  11. package/.harness/dev-map/backend/api-definition.md +131 -0
  12. package/.harness/dev-map/backend/auth-security.md +131 -0
  13. package/.harness/dev-map/backend/conventions-java.md +471 -0
  14. package/.harness/dev-map/backend/conventions.md +192 -0
  15. package/.harness/dev-map/backend/database.md +106 -0
  16. package/.harness/dev-map/backend/structure.md +140 -0
  17. package/.harness/dev-map/decisions.md +275 -0
  18. package/.harness/dev-map/frontend/api-integration.md +139 -0
  19. package/.harness/dev-map/frontend/components.md +178 -0
  20. package/.harness/dev-map/frontend/conventions.md +416 -0
  21. package/.harness/dev-map/frontend/state-management.md +170 -0
  22. package/.harness/dev-map/frontend/structure.md +103 -0
  23. package/.harness/dev-map/overview.md +267 -0
  24. package/.harness/docs/integration-test-plan.md +248 -0
  25. package/.harness/docs/team-guidelines/README.md +161 -0
  26. package/.harness/docs/team-guidelines/arch-team.md +811 -0
  27. package/.harness/docs/team-guidelines/collaboration.md +556 -0
  28. package/.harness/docs/team-guidelines/pm-team.md +337 -0
  29. package/.harness/docs/team-guidelines/qa-team.md +562 -0
  30. package/.harness/docs/team-guidelines/rd-team.md +714 -0
  31. package/.harness/docs/training-materials.md +280 -0
  32. package/.harness/gate/baseline.js +220 -0
  33. package/.harness/gate/checks/build-gates-frontend.js +152 -0
  34. package/.harness/gate/checks/build-gates-java.js +155 -0
  35. package/.harness/gate/checks/build-gates.js +119 -0
  36. package/.harness/gate/checks/engineering-consistency.js +138 -0
  37. package/.harness/gate/checks/security-quality.js +129 -0
  38. package/.harness/gate/checks/static-compliance.js +313 -0
  39. package/.harness/gate/checks/test-compliance.js +114 -0
  40. package/.harness/gate/index.js +315 -0
  41. package/.harness/mcp/config.yaml +435 -0
  42. package/.harness/rules/global/coding-standard.md +232 -0
  43. package/.harness/rules/global/commit-convention.md +165 -0
  44. package/.harness/rules/global/process-discipline.md +192 -0
  45. package/.harness/rules/global/security-baseline.md +306 -0
  46. package/.harness/rules/project/frontend-vue3.md +293 -0
  47. package/.harness/rules/project/java-backend.md +460 -0
  48. package/.harness/rules/project/web-specific.md +231 -0
  49. package/.harness/skills/build.md +192 -0
  50. package/.harness/skills/code-review.md +251 -0
  51. package/.harness/skills/docker-build.md +227 -0
  52. package/.harness/skills/docs-update.md +164 -0
  53. package/.harness/skills/java-build.md +261 -0
  54. package/.harness/skills/lint-check.md +482 -0
  55. package/.harness/skills/task-board-maintenance.md +105 -0
  56. package/.harness/skills/test-api.md +461 -0
  57. package/.harness/skills/test-e2e.md +431 -0
  58. package/.harness/skills/test-unit.md +649 -0
  59. package/.harness/skills/vue-frontend-build.md +344 -0
  60. package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
  61. package/.harness/task-board.md +121 -0
  62. package/.harness/workflow/definition.yaml +504 -0
  63. package/.harness/workflow/validate.js +320 -0
  64. package/.harness/workflow/variants.yaml +253 -0
  65. package/README.md +237 -0
  66. package/bin/jsharness.js +53 -0
  67. package/lib/index.mjs +778 -0
  68. package/package.json +1 -0
@@ -0,0 +1,431 @@
1
+ # E2E 测试技能 (test-e2e)
2
+
3
+ > **执行角色**: 测试验证 Agent / CI Pipeline
4
+ > **触发时机**: 关键功能开发完成、预发布前、版本发布前
5
+
6
+ ---
7
+
8
+ ## E2E 测试环境准备
9
+
10
+ ### 环境启动
11
+
12
+ ```bash
13
+ # 启动 E2E 测试环境(Docker Compose)
14
+ docker compose -f docker-compose.e2e.yml up -d
15
+
16
+ # 等待所有服务就绪
17
+ # 等待顺序:数据库 → 后端 API → 前端应用
18
+
19
+ # 验证服务状态
20
+ curl -f http://localhost:3000/api/health || echo "Backend not ready"
21
+ curl -f httplocalhost:3000 || echo "Frontend not ready"
22
+ ```
23
+
24
+ ### 环境配置要点
25
+
26
+ | 配置项 | E2E 环境 | 注意事项 |
27
+ |--------|----------|----------|
28
+ | 数据库 | 使用独立测试数据库 | 每次测试前重置 |
29
+ | API 地址 | `http://localhost:3000/api` | 非 production URL |
30
+ | 认证方式 | 使用测试专用 Token | 不过期或长有效期 |
31
+ | 外部服务 | 使用 Mock 服务 | 支付/邮件/短信等 |
32
+
33
+ ---
34
+
35
+ ## 关键路径测试执行
36
+
37
+ ### 执行命令
38
+
39
+ ```bash
40
+ # 运行全部 E2E 测试
41
+ npm run test:e2e
42
+
43
+ # 运行特定 spec 文件
44
+ npx playwright test login.spec.ts
45
+
46
+ # UI 模式(调试用)
47
+ npx playwright test --ui
48
+
49
+ # headed 模式(显示浏览器)
50
+ npx playwright test --headed
51
+ ```
52
+
53
+ ### 必测的关键路径
54
+
55
+ 每个 Web 应用必须维护以下核心 E2E 场景:
56
+
57
+ #### 用户认证流程
58
+ ```
59
+ 注册 → 邮箱验证 → 登录 → 密码修改 → 退出登录
60
+ 登录态过期 → 自动跳转登录页 → 重新登录 → 回到原页面
61
+ ```
62
+
63
+ #### 核心业务流程(按产品定制)
64
+
65
+ ```
66
+ 示例:电商场景
67
+ 浏览商品 → 加入购物车 → 结算 → 选择支付方式 → 支付成功 → 订单详情
68
+ 示例:管理后台
69
+ 登录 → 数据看板 → CRUD 操作 → 导出数据 → 操作日志审计
70
+ ```
71
+
72
+ #### 关键功能回归
73
+ ```
74
+ 每次发版前必须跑通的 TOP10 页面/功能
75
+ (由 PM 和 QA 共同确定优先级列表)
76
+ ```
77
+
78
+ ### E2E 用例结构(Playwright 示例)
79
+
80
+ ```typescript
81
+ import { test, expect } from '@playwright/test';
82
+
83
+ test.describe('用户认证流程', () => {
84
+ test.beforeEach(async ({ page }) => {
85
+ await page.goto('/login');
86
+ });
87
+
88
+ test('正确邮箱密码可以成功登录', async ({ page }) => {
89
+ // Arrange
90
+ await page.fill('[data-testid="email"]', 'test@example.com');
91
+ await page.fill('[data-testid="password"]', 'ValidPass123!');
92
+
93
+ // Act
94
+ await page.click('[data-testid="submit-btn"]');
95
+
96
+ // Assert
97
+ await expect(page).toHaveURL(/\/dashboard/);
98
+ await expect(page.locator('[data-testid="user-avatar"]')).toBeVisible();
99
+ });
100
+
101
+ test('错误密码显示提示信息', async ({ page }) => {
102
+ await page.fill('[data-testid="email"]', 'test@example.com');
103
+ await page.fill('[data-testid="password"]', 'WrongPassword');
104
+ await page.click('[data-testid="submit-btn"]');
105
+
106
+ await expect(page.locator('[data-testid="error-message"]'))
107
+ .toHaveText('邮箱或密码错误');
108
+ await expect(page).toHaveURL(/\/login/); // 未跳转
109
+ });
110
+ });
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 截图与日志收集
116
+
117
+ ### 失败自动截图
118
+
119
+ ```typescript
120
+ // playwright.config.ts
121
+ export default defineConfig({
122
+ use: {
123
+ screenshot: 'only-on-failure', // 仅失败时截图
124
+ video: 'retain-on-failure', // 仅失败时保留视频
125
+ trace: 'on-first-retry', // 重试时记录 trace
126
+ },
127
+ outputDir: 'test-results/',
128
+ });
129
+ ```
130
+
131
+ ### 日志收集策略
132
+
133
+ | 日志类型 | 收集方式 | 用途 |
134
+ |----------|----------|------|
135
+ | **浏览器 Console** | Playwright `console` 事件 | 捕获前端运行时错误 |
136
+ | **网络请求** | Playwright `route` / HAR | 检查 API 调用是否正常 |
137
+ | **后端日志** | Docker logs / 文件 | 服务端错误排查 |
138
+ | **性能指标** | Performance Timing | 页面加载时间分析 |
139
+
140
+ ```typescript
141
+ // 收集控制台日志
142
+ test('页面无 JS 错误', async ({ page }) => {
143
+ const logs: string[] = [];
144
+ page.on('console', msg => {
145
+ if (msg.type() === 'error') logs.push(msg.text());
146
+ });
147
+
148
+ await page.goto('/');
149
+
150
+ expect(loges).toEqual([]); // 不应有 console.error
151
+ });
152
+ ```
153
+
154
+ ## E2E 测试结果输出模板
155
+
156
+ ```yaml
157
+ test_result_e2e:
158
+ timestamp: "2026-05-20T22:00:00Z"
159
+ framework: "playwright"
160
+ browser: "chromium" | "firefox" | "webkit"
161
+ status: pass | fail
162
+
163
+ environment:
164
+ backend_url: "http://localhost:3000"
165
+ database: "postgresql-test"
166
+ external_services: mocked
167
+
168
+ summary:
169
+ total_specs: 12
170
+ total_tests: 48
171
+ passed: 47
172
+ failed: 1
173
+ skipped: 2
174
+ duration_ms: 120000
175
+
176
+ failed_tests:
177
+ - name: "购物车结算流程 > 应正确计算总价"
178
+ spec: "checkout.spec.ts"
179
+ error: "期望 ¥99.00 但实际为 ¥99.50"
180
+ screenshot: "test-results/checkout-failure-1.png"
181
+ trace: "test-results/trace.zip"
182
+
183
+ performance:
184
+ page_load_avg_ms: 1200
185
+ api_response_avg_ms: 230
186
+
187
+ baseline_comparison:
188
+ regression: false
189
+ new_failures: 1
190
+ ```
191
+
192
+ ---
193
+
194
+ # ════════════════════════════════════════════════════════
195
+ # Java 后端 E2E 测试(Testcontainers + Spring Profiles)
196
+ # ════════════════════════════════════════════════════════
197
+
198
+ > **适用技术栈**: Spring Boot / JDK21 / Testcontainers
199
+ > **参考规则**: `rules/project/java-backend.md` §13 测试规范
200
+
201
+ ---
202
+
203
+ ## Spring Boot Profile 配置
204
+
205
+ ### application-test.yml(单元/集成测试)
206
+
207
+ ```yaml
208
+ # src/test/resources/application-test.yml
209
+ spring:
210
+ datasource:
211
+ url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
212
+ driver-class-name: org.h2.Driver
213
+ username: sa
214
+ password:
215
+ jpa:
216
+ hibernate:
217
+ ddl-auto: create-drop # 每次测试自动建表/销毁
218
+ show-sql: false # 生产关闭 SQL 日志
219
+ redis:
220
+ host: localhost # 需要 Redis 在本地运行,或用 Testcontainers
221
+ port: 6379
222
+
223
+ logging:
224
+ level:
225
+ root: WARN # 减少日志噪音
226
+ com.jieshun: DEBUG # 项目包开启 DEBUG
227
+
228
+ # 测试专用配置
229
+ app:
230
+ security:
231
+ sm4-enabled: false # 测试环境关闭 SM4 加密
232
+ external:
233
+ sms-mock: true # Mock 短信发送
234
+ email-mock: true # Mock 邮件发送
235
+ ```
236
+
237
+ ### application-e2e.yml(端到端测试)
238
+
239
+ ```yaml
240
+ # src/test/resources/application-e2e.yml
241
+ spring:
242
+ datasource:
243
+ # 由 Testcontainers 动态注入 URL/用户名/密码
244
+ redis:
245
+ # 由 Testcontainers 启动 GenericContainer
246
+
247
+ # E2E 专用:模拟真实生产配置
248
+ app:
249
+ security:
250
+ sm4-enabled: true # E2E 需要完整安全链路
251
+ rate-limit:
252
+ enabled: false # E2E 禁用限流避免干扰
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Testcontainers — 容器化测试基础设施
258
+
259
+ ### Maven 依赖
260
+
261
+ ```xml
262
+ <!-- pom.xml (test scope) -->
263
+ <dependency>
264
+ <org.testcontainers>testcontainers</groupId>
265
+ <artifactId>testcontainers</artifactId>
266
+ <version>1.19.7</version>
267
+ <scope>test</scope>
268
+ </dependency>
269
+ <dependency>
270
+ <org.testcontainers>junit-jupiter</groupId>
271
+ <artifactId>testcontainers</artifactId>
272
+ <version>1.19.7</version>
273
+ <scope>test</scope>
274
+ </dependency>
275
+ <dependency>
276
+ <org.testcontainers>mysql</groupId>
277
+ <artifactId>mysql</artifactId>
278
+ <version>1.19.7</version>
279
+ <scope>test</scope>
280
+ </dependency>
281
+ <dependency>
282
+ <org.testcontainers>generic</groupId>
283
+ <artifactId>testcontainers</artifactId>
284
+ <version>1.19.7</version>
285
+ <scope>test</scope>
286
+ </dependency>
287
+ ```
288
+
289
+ ### 常用容器模板
290
+
291
+ ```java
292
+ @Testcontainers // 自动管理容器生命周期
293
+ @SpringBootTest
294
+ @ActiveProfiles("e2e")
295
+ class OrderE2ETest {
296
+
297
+ @Container
298
+ static MySQLContainer mysql = new MySQLContainer<>("mysql:8.0")
299
+ .withDatabaseName("jieshun_e2e")
300
+ .withUsername("e2e_user")
301
+ .withPassword("e2e_pass")
302
+ .withInitScript("sql/init-test-data.sql"); // 初始化种子数据
303
+
304
+ @Container
305
+ static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
306
+ .withExposedPorts(6379)
307
+ .waitingFor(Wait.forListeningPort());
308
+
309
+ // 动态注入连接信息到 Spring 配置
310
+ @DynamicPropertySource
311
+ static void configureProperties(DynamicPropertyRegistry registry) {
312
+ registry.add("spring.datasource.url", () -> {
313
+ // Testcontainers 提供的 JDBC URL
314
+ String jdbcUrl = mysql.getJdbcUrl() +
315
+ "?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
316
+ return jdbcUrl;
317
+ });
318
+ registry.add("spring.datasource.username", mysql::getUsername);
319
+ registry.add("spring.datasource.password", mysql::getPassword);
320
+ registry.add("spring.data.redis.host", redis::getHost);
321
+ registry.add("spring.data.redis.port", redis::getFirstMappedPort);
322
+ }
323
+
324
+ @Autowired
325
+ private OrderService orderService;
326
+
327
+ @Autowired
328
+ private ProductService productService;
329
+
330
+ @Test
331
+ @DisplayName("E2E: 下单 → 支付 → 发货 全流程")
332
+ void shouldCompleteFullOrderLifecycle() {
333
+ // 1. 创建订单
334
+ OrderReqVO orderReq = OrderReqVO.builder()
335
+ .userId("user-001").productId("prod-100").quantity(2).build();
336
+ RespVO<OrderVO> createResult = orderService.createOrder(orderReq);
337
+ assertThat(createResult.getCode()).isEqualTo(0);
338
+
339
+ // 2. 支付订单
340
+ PayReqVO payReq = PayReqVO.builder()
341
+ .orderId(createResult.getData().getId()).amount(new BigDecimal("199.00")).build();
342
+ RespVO payResult = orderService.pay(payReq);
343
+ assertThat(payResult.getCode()).isEqualTo(0);
344
+
345
+ // 3. 查询确认状态
346
+ OrderVO updated = orderService.getOrderById(createResult.getData().getId());
347
+ assertThat(updated.getStatus()).isEqualTo(OrderStatus.PAID);
348
+ }
349
+ }
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Maven E2E 执行命令
355
+
356
+ ```bash
357
+ # === 运行所有 E2E 测试 ===
358
+ mvn verify -P e2e # 通过 profile 触发
359
+
360
+ # === 仅运行 E2E 测试类(带标签)===
361
+ mvn test -Dgroups="e2e" -P e2e # 使用 JUnit5 Tag
362
+
363
+ # === 跳过 E2E(日常开发)===
364
+ mvn test -DexcludedGroups="e2e" # 排除 E2E 标签的测试
365
+
366
+ # === 带覆盖率报告 ===
367
+ mvn verify -P e2e jacoco:report # 包含 JaCoCo
368
+
369
+ # === 并行加速(多模块项目)===
370
+ mvn verify -P e2e -T 4 # 4 线程并行
371
+ ```
372
+
373
+ ### pom.xml Profile 定义
374
+
375
+ ```xml
376
+ <profiles>
377
+ <profile>
378
+ <id>e2e</id>
379
+ <properties>
380
+ <!-- 覆盖 Surefire 的 groups 参数 -->
381
+ <surefire.groups>e2e</surefire.groups>
382
+ <!-- 或使用 include/exclude 文件 -->
383
+ </properties>
384
+ </profile>
385
+ </profiles>
386
+ ```
387
+
388
+ ---
389
+
390
+ ## Java E2E 测试结果输出模板
391
+
392
+ ```yaml
393
+ test_result_e2e_java:
394
+ timestamp: "2026-05-20T22:00:00Z"
395
+ framework: "JUnit5 + Testcontainers + Spring Boot"
396
+ containers_used:
397
+ - type: "MySQLContainer"
398
+ image: "mysql:8.0"
399
+ status: running
400
+ - type: "GenericContainer (Redis)"
401
+ image: "redis:7-alpine"
402
+ status: running
403
+
404
+ status: pass | fail
405
+
406
+ summary:
407
+ total_tests: 18
408
+ passed: 17
409
+ failed: 1
410
+ skipped: 0
411
+ duration_seconds: 245
412
+
413
+ failed_tests:
414
+ - name: "OrderE2ETest > shouldCompleteFullOrderLifecycle > step_3_query_status"
415
+ file: "src/test/java/com/jieshun/e2e/OrderE2ETest.java:78"
416
+ error: "Expected status PAID but was CREATED — payment callback not received"
417
+
418
+ container_logs:
419
+ mysql_init_success: true
420
+ redis_connected: true
421
+
422
+ performance:
423
+ avg_test_duration_ms: 13600
424
+ slowest_test:
425
+ name: "shouldHandleConcurrentOrders"
426
+ duration_ms: 45000
427
+
428
+ baseline_comparison:
429
+ regression: false
430
+ new_failures: 1
431
+ ```