9-7-stability-validation.md 23 KB

Story 9.7: 稳定性验证(10 次连续运行)

Status: in-progress

Story

作为测试开发者, 我想要验证所有测试的稳定性, 以便确保测试可以可靠地运行。

Acceptance Criteria

Given 所有业务测试已完成并隔离 When 连续运行测试 10 次 Then 验收标准如下:

  1. 测试通过率 100%

    • 10/10 次运行全部通过
    • 无 flaky 失败
    • 无超时失败
  2. 性能指标达标

    • 平均执行时间 ≤ 10 分钟/次
    • 无明显性能衰减
  3. 并行执行稳定

    • 并行模式(4 workers)10 次运行 100% 通过
    • 串行模式 10 次运行 100% 通过

Tasks / Subtasks

  • [x] Task 1: 创建稳定性测试脚本 (AC: #1)

    • Subtask 1.1: 编写 bash 脚本实现 10 次连续运行
    • Subtask 1.2: 添加通过/失败统计
    • Subtask 1.3: 添加执行时间记录和平均时间计算
  • [ ] Task 2: 执行并行模式稳定性测试 (AC: #1, #2, #3) - ⚠️ 第一次运行失败,需修复后重试

    • Subtask 2.1: 使用 4 workers 运行测试(实际运行 1 次,失败后停止)
    • Subtask 2.2: 记录运行的通过率和执行时间
    • Subtask 2.3: 分析失败的测试(已识别 10 个失败测试)
  • [ ] Task 3: 执行串行模式稳定性测试 (AC: #1, #2, #3)

    • Subtask 3.1: 使用 1 worker 运行 10 次测试
    • Subtask 3.2: 记录每次运行的通过率和执行时间
    • Subtask 3.3: 对比并行和串行模式的性能差异
  • [ ] Task 4: 分析结果并生成报告 (AC: #1, #2, #3)

    • Subtask 4.1: 统计总体通过率
    • Subtask 4.2: 计算平均执行时间和性能衰减
    • Subtask 4.3: 记录 flaky 失败(如有)并分析原因
    • Subtask 4.4: 更新 Story 文档和 sprint-status

Dev Notes

Epic 9 背景与目标

Epic 9: 残疾人管理完整 E2E 测试覆盖(含并行隔离)

为残疾人管理功能编写完整的、真正验证业务功能的 E2E 测试,并确保测试可以与未来的区域管理测试并行运行。

Epic 9 Story 依赖关系:

  • Story 9.1:照片上传功能测试 ✅ Done
  • Story 9.2:银行卡管理功能测试 ✅ Done
  • Story 9.3:备注管理功能测试 ✅ Done
  • Story 9.4:回访记录管理测试 ✅ Done
  • Story 9.5:完整流程测试(CRUD)✅ Done
  • Story 9.6:测试隔离与并行执行验证 ✅ Done
  • Story 9.7(本故事):稳定性验证(10次连续运行)🔄 当前

本 Story 的核心目标

稳定性验证的意义:

  1. Flaky 测试检测:10 次连续运行可以暴露偶发性失败的测试
  2. 性能基准建立:建立测试执行时间的基准线
  3. 并行执行信心:验证并行模式在多次运行中的稳定性
  4. Epic 9 完成条件:100% 通过率是 Epic 9 完成的必要条件

成功标准:

  • 10/10 通过 = Epic 9 完成 ✅
  • < 10/10 通过 = 分析失败原因并修复

待验证的测试文件清单

Epic 9 已完成的所有测试文件:

测试文件 测试数量 预计时间 验证项
disability-person-photo.spec.ts 8 ~1.1m 照片上传、格式支持、删除
disability-person-bankcard.spec.ts 5 ~0.8m 银行卡 CRUD、多张管理
disability-person-note.spec.ts 4 ~0.6m 备注 CRUD
disability-person-visit.spec.ts 4 ~0.7m 回访记录 CRUD
disability-person-crud.spec.ts 16 ~2.0m 完整 CRUD 流程

总计:37 个测试,预计执行时间 ~5.2 分钟(4 workers)

稳定性测试脚本

脚本位置: web/tests/e2e/scripts/run-stability-test.sh

#!/bin/bash
# Epic 9 稳定性测试脚本
# 运行所有残疾人管理测试 10 次,验证稳定性

set -e  # 遇到错误立即退出

# 配置
RUNS=10
PROJECT_ROOT="/mnt/code/188-179-template-6"
WEB_DIR="${PROJECT_ROOT}/web"
PASSED=0
FAILED=0
TIMES=()

# 切换到 web 目录
cd "${WEB_DIR}" || exit 1

echo "========================================="
echo "Epic 9 稳定性验证测试"
echo "========================================="
echo "运行次数: ${RUNS}"
echo "测试范围: 残疾人管理所有测试 (37 个测试)"
echo "开始时间: $(date)"
echo ""
echo "========================================="
echo ""

# 运行测试
for i in $(seq 1 ${RUNS}); do
  echo "=== 运行 #${i}/${RUNS} ==="
  START=$(date +%s)

  # 运行测试(4 workers 并行)
  if pnpm test:e2e:chromium --workers=4; then
    PASSED=$((PASSED + 1))
    echo "✅ 运行 #${i} 通过"
  else
    FAILED=$((FAILED + 1))
    echo "❌ 运行 #${i} 失败"
  fi

  END=$(date +%s)
  DURATION=$((END - START))
  TIMES+=($DURATION)
  echo "⏱️  耗时: ${DURATION}s"
  echo ""
done

# 计算统计数据
TOTAL=$((PASSED + FAILED))
PASS_RATE=$((PASSED * 100 / TOTAL))
AVG_TIME=$(awk '{sum+=$1} END {print sum/NR}' <<< "${TIMES[@]}")
MIN_TIME=$(printf "%s\n" "${TIMES[@]}" | sort -n | head -1)
MAX_TIME=$(printf "%s\n" "${TIMES[@]}" | sort -n | tail -1)

# 输出结果
echo "========================================="
echo "稳定性测试结果"
echo "========================================="
echo "通过: ${PASSED}/${TOTAL} (${PASS_RATE}%)"
echo "失败: ${FAILED}/${TOTAL}"
echo ""
echo "执行时间统计:"
echo "  平均: ${AVG_TIME}s"
echo "  最快: ${MIN_TIME}s"
echo "  最慢: ${MAX_TIME}s"
echo ""
echo "结束时间: $(date)"
echo "========================================="

# 判断结果
if [ $PASSED -eq $TOTAL ]; then
  echo ""
  echo "🎉 Epic 9 稳定性验证通过!100% 成功率!"
  echo "✅ Epic 9 可以标记为完成"
  exit 0
else
  echo ""
  echo "⚠️  稳定性不足,需要修复失败的测试"
  echo "❌ 请分析失败原因并修复后重新运行"
  exit 1
fi

串行模式稳定性测试脚本

#!/bin/bash
# Epic 9 串行模式稳定性测试脚本
# 用于对比并行和串行模式的性能差异

RUNS=10
PROJECT_ROOT="/mnt/code/188-179-template-6"
WEB_DIR="${PROJECT_ROOT}/web"
PASSED=0
FAILED=0
TIMES=()

cd "${WEB_DIR}" || exit 1

echo "========================================="
echo "Epic 9 串行模式稳定性验证"
echo "========================================="

for i in $(seq 1 ${RUNS}); do
  echo "=== 运行 #${i}/${RUNS} (串行模式) ==="
  START=$(date +%s)

  # 运行测试(1 worker 串行)
  if pnpm test:e2e:chromium --workers=1; then
    PASSED=$((PASSED + 1))
    echo "✅ 运行 #${i} 通过"
  else
    FAILED=$((FAILED + 1))
    echo "❌ 运行 #${i} 失败"
  fi

  END=$(date +%s)
  DURATION=$((END - START))
  TIMES+=($DURATION)
  echo "⏱️  耗时: ${DURATION}s"
  echo ""
done

# 输出结果(同上)
# ...

单文件稳定性测试(调试用)

#!/bin/bash
# 单文件稳定性测试 - 用于调试特定测试文件

TEST_FILE="${1:-disability-person-crud.spec.ts}"
RUNS=10

echo "测试文件: ${TEST_FILE}"
echo "运行次数: ${RUNS}"

for i in $(seq 1 ${RUNS}); do
  echo "=== 运行 #${i} ==="
  pnpm test:e2e:chromium "${TEST_FILE}" --workers=2
done

Flaky 失败分析指南

如果出现偶发性失败,按以下步骤分析:

  1. 查看失败截图

    # 查看 test-results 目录
    ls -la web/tests/e2e/test-results/
    
  2. 查看错误上下文

    # 查看自动生成的错误上下文
    cat web/tests/e2e/test-results/**/error-context.md
    
  3. 分析失败类型

    • 超时失败:增加超时时间或优化测试逻辑
    • 元素未找到:添加等待或改进选择器
    • 数据冲突:检查唯一性策略
    • 网络问题:重试或使用 mock 数据
  4. 修复后重新验证

    # 单文件验证
    cd web
    pnpm test:e2e:chromium disability-person-xxx.spec.ts --workers=2 --repeat=10
    

Project Structure Notes

E2E 测试目录结构:

web/tests/e2e/
├── fixtures/          # 测试文件(图片、文档)
├── pages/            # Page Object
│   └── admin/
│       └── disability-person.page.ts
├── specs/            # 测试用例
│   └── admin/
│       ├── disability-person-photo.spec.ts
│       ├── disability-person-bankcard.spec.ts
│       ├── disability-person-note.spec.ts
│       ├── disability-person-visit.spec.ts
│       └── disability-person-crud.spec.ts
├── scripts/          # 测试脚本(新增)
│   ├── run-stability-test.sh
│   ├── run-stability-serial.sh
│   └── run-stability-single.sh
└── playwright.config.ts

Previous Story Intelligence

Story 9.6 (测试隔离与并行执行验证) 的经验:

主要改进:

  1. ✅ 所有测试移除了 test.describe.serial
  2. ✅ 添加了 TEST_TIMESTAMP/TEST_PREFIX 模式
  3. ✅ 实现了完整的 test.afterEach 清理机制
  4. ✅ 验证了并行执行稳定性(2 workers,8/8 通过)
  5. ✅ 性能提升:4 workers 下速度提升 3 倍(3.3m → 1.1m)

并行执行验证结果(来自 Story 9.6):

photo.spec.ts:  8 passed (2.2m) with 2 workers  ✅
note.spec.ts:    8 passed (1.1m) with 2 workers  ✅
photo.spec.ts:   8 passed (3.3m) with 1 worker   ✅
photo.spec.ts:   8 passed (1.1m) with 4 workers  ✅ (速度提升 3x)

关键模式(Story 9.6 建立):

  1. TEST_TIMESTAMP 在 test.describe 级别声明
  2. createdTestData 数组用于跟踪创建的记录
  3. test.afterEach 统一清理机制
  4. 使用不同的省市组合避免并发冲突

Story 9.1-9.5 的模式总结:

  • ✅ 所有测试使用时间戳生成唯一 ID(正确模式)
  • ✅ 文件上传使用固定的测试文件路径
  • ✅ 备注和回访记录使用唯一内容
  • ⚠️ 需要验证 10 次运行的稳定性

Git Intelligence Summary

Recent Commits:

  • 9e3c2dcb - test(e2e): 完成 Story 9.6 代码审查 - 测试隔离与并行执行
  • 81ba705a - docs(e2e): 创建 Story 10.5 - 编辑订单测试
  • 241c2491 - test(e2e): 完成 Story 10.4 - 创建订单测试

Epic 9 完成状态:

  • Story 9.1-9.5:所有业务测试已完成 ✅
  • Story 9.6:并行隔离验证已完成 ✅
  • Story 9.7:稳定性验证(当前)🔄

测试文件模式:

  • 测试文件命名:disability-person-{feature}.spec.ts
  • Page Object 方法命名:动词+名词
  • 选择器策略:data-testid 优先

References

源文档引用:

  • [Source: _bmad-output/planning-artifacts/epics.md#Epic-9-Story-9.7] - 完整业务需求
  • [Source: _bmad-output/planning-artifacts/architecture.md#Testing-Standards] - 测试稳定性标准
  • [Source: web/tests/e2e/playwright.config.ts] - Playwright 配置文件

前置 Story 参考:

  • [Source: _bmad-output/implementation-artifacts/9-1-photo-upload-tests.md] - 照片上传测试
  • [Source: _bmad-output/implementation-artifacts/9-2-bankcard-tests.md] - 银行卡测试
  • [Source: _bmad-output/implementation-artifacts/9-3-note-tests.md] - 备注测试
  • [Source: _bmad-output/implementation-artifacts/9-4-visit-tests.md] - 回访记录测试
  • [Source: _bmad-output/implementation-artifacts/9-5-crud-tests.md] - CRUD 测试
  • [Source: _bmad-output/implementation-artifacts/9-6-parallel-isolation.md] - 并行隔离验证

Playwright 文档:


稳定性测试结果分析

第一次运行结果(并行模式 - 4 workers)

执行时间: 2026-01-11 09:20:33 UTC

测试结果:

  • ✅ 41 passed
  • ❌ 10 failed
  • ⏭️ 2 skipped
  • ⏱️ 执行时间:5.7 分钟

通过率: 41/53 = 77.4% ❌ 未达到 100% 要求

失败测试详情

1. disability-person-bankcard.spec.ts(1 个失败)

  • 完整流程:添加多张银行卡并提交 - 可能原因:表单提交或数据验证问题

2. disability-person-crud.spec.ts(8 个失败)

  • 应该成功完成新增残疾人完整流程(基本信息 + 备注 + 银行卡)
  • 应该成功完成新增残疾人完整流程(基本信息)
  • 应该成功编辑并添加备注
  • 应该正确显示残疾人详情
  • 应该能在详情中查看完整信息
  • 应该支持按姓名搜索残疾人
  • 应该支持按残疾类型筛选
  • 应该完成完整的 CRUD 生命周期

主要错误类型:

  • personExists 返回 false - 表单提交后记录未创建或未能找到
  • 超时错误 - page.waitForLoadState: Timeout 10000ms exceeded

3. disability-person-visit.spec.ts(1 个失败)

  • 应该成功编辑回访记录内容
  • 错误:Test timeout of 60000ms exceeded while running "afterEach" hook
  • 原因:清理钩子耗时过长

根本原因分析

1. 表单提交时机问题(HIGH 优先级)

  • 多个 CRUD 测试失败,显示 personExists 返回 false
  • 可能原因:
    • 表单提交后立即查询,但后端处理未完成
    • 缺少足够的等待时间让数据持久化
    • 成功提示显示但实际数据未保存

建议修复:

// 在 submitForm 后添加更长的等待时间
await this.page.waitForTimeout(3000); // 增加到 3 秒
// 或等待列表刷新完成
await this.page.waitForSelector('[data-testid="person-list"]');

2. 清理钩子超时问题(HIGH 优先级)

  • afterEach 钩子超时(60秒)
  • 可能原因:
    • 删除操作缓慢
    • 并发测试时多个删除操作冲突
    • 网络请求卡住

建议修复:

test.afterEach(async ({ disabilityPersonPage, page }) => {
  // 为每个清理操作设置独立超时
  for (const data of createdTestData) {
    try {
      await page.waitForTimeout(1000); // 添加间隔
      await disabilityPersonPage.deletePersonByName(data.name, { timeout: 30000 });
    } catch (error) {
      console.log(`清理失败: ${data.name}`, error);
    }
  }
});

3. 测试数据隔离问题(MEDIUM 优先级)

  • 并行运行时可能出现数据冲突
  • 建议增强数据隔离策略

下一步行动计划

选项 A:修复现有测试后重新验证(推荐)

  1. 修复 CRUD 测试的表单提交等待问题
  2. 优化清理钩子的超时处理
  3. 单个测试文件验证后再运行完整稳定性测试
  4. 预计修复时间:1-2 小时

选项 B:降低验收标准(不推荐)

  • 将稳定性要求从 10/10 降低到 8/10
  • 这违背了 Epic 9 的质量目标

选项 C:标记为已知问题,继续其他 Epic

  • 记录当前测试状态为"部分稳定"
  • 在后续 Sprint 中修复这些测试

性能指标

第一次运行:

  • 执行时间:5.7 分钟(53 个测试)
  • 平均每个测试:~6.5 秒
  • 满足 ≤10 分钟/次的要求 ✅

第二次稳定性测试结果(应用修复后)

应用的修复(2026-01-12)

修复 1:增加表单提交后等待时间

  • 位置: disability-person-crud.spec.ts, disability-person-bankcard.spec.ts
  • 修改: 所有 searchByName 后的等待时间从 1 秒增加到 3 秒
  • 原因: 确保数据已持久化后再查询

    await disabilityPersonPage.searchByName(testData.name);
    // 增加等待时间以确保数据已持久化(修复稳定性问题)
    await page.waitForTimeout(3000);
    

修复 2:改进 submitForm 方法的超时处理

  • 位置: disability-person.page.ts
  • 修改: 增加 networkidle 超时到 30 秒,添加 domcontentloaded 降级方案

    // 等待网络请求完成(增加超时并添加容错处理)
    try {
    await this.page.waitForLoadState('networkidle', { timeout: 30000 });
    } catch (e) {
    // networkidle 可能因为长轮询或后台请求而失败,使用 domcontentloaded 作为降级方案
    console.debug('  ⚠ networkidle 超时,使用 domcontentloaded 作为降级方案');
    await this.page.waitForLoadState('domcontentloaded', { timeout: 10000 });
    }
    

修复 3:银行卡完整流程测试添加数据验证

  • 位置: disability-person-bankcard.spec.ts
  • 修改: 在提交表单后添加数据创建验证

    // 刷新页面并验证数据创建成功(修复稳定性问题)
    await page.reload();
    await page.waitForLoadState('networkidle');
    await disabilityPersonPage.goto();
    await disabilityPersonPage.searchByName(testData.name);
    await page.waitForTimeout(3000);
    
    const personExists = await disabilityPersonPage.personExists(testData.name);
    expect(personExists).toBe(true);
    

第二次运行结果(并行模式 - 4 workers)

执行时间: 2026-01-12

测试结果:

  • ✅ 45 passed
  • ❌ 6 failed
  • ⏭️ 2 skipped
  • ⏱️ 执行时间:5.4 分钟

通过率: 45/53 = 85% ⚠️ 仍未达到 100% 要求

进步: 通过率从 77.4% 提升到 85%(+7.6%)

剩余失败测试详情

1. 应该成功完成新增残疾人完整流程(基本信息 + 备注 + 银行卡)

  • 错误: Test timeout of 60000ms exceeded - waiting for "借记卡" option
  • 问题: 银行卡类型下拉选项异步加载,需要等待选项出现
  • 优先级: HIGH

2. 应该成功编辑并添加备注

  • 错误: Timeout 5000ms exceeded - waiting for row to be visible
  • 问题: 搜索后未找到记录,数据未持久化或搜索未返回结果
  • 优先级: HIGH

3. 应该成功删除残疾人记录

  • 错误: expect(personExists).toBe(true) - received false
  • 问题: 表单提交后数据未持久化,即使等待 3 秒仍未找到
  • 优先级: HIGH

4-6. afterEach 超时失败(3 个测试)

  • 错误: Test timeout of 60000ms exceeded while running "afterEach" hook
  • 问题: 清理钩子超过 60 秒
  • 影响测试:
    • 应该成功添加长文本备注
    • 应该成功上传单张照片 - 身份证正面
    • 超大文件应该有合理处理
  • 优先级: MEDIUM(测试功能正常,清理问题)

根本原因分析(更新)

1. 数据持久化问题仍然存在(HIGH 优先级)

  • 即使增加 3 秒等待,某些记录仍未能在搜索时找到
  • 可能原因:
    • 后端数据库写入延迟
    • 搜索索引更新延迟
    • 并发测试时的数据竞争
  • 需要进一步修复:

    // 考虑使用重试机制而非固定等待
    await expect(async () => {
    await disabilityPersonPage.searchByName(testData.name);
    const exists = await disabilityPersonPage.personExists(testData.name);
    expect(exists).toBe(true);
    }).toPass({ timeout: 10000 });
    

2. 银行卡类型选择器异步加载(HIGH 优先级)

  • "借记卡" 选项可能在点击后延迟出现
  • 建议修复:

    const cardTypeTrigger = this.page.locator(`[data-testid="card-type-select-${cardIndex}"]`);
    await cardTypeTrigger.click();
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
    // 等待选项出现
    await this.page.getByRole('option', { name: bankCard.cardType }).waitFor({ state: 'visible' });
    await this.page.getByRole('option', { name: bankCard.cardType }).click();
    

3. afterEach 清理超时(MEDIUM 优先级)

  • 清理钩子在某些情况下超过 60 秒
  • 建议优化:
    • 限制清理操作的超时时间
    • 添加清理操作的并发控制
    • 考虑使用测试数据库事务回滚而非逐条删除

下一步建议

选项 A:继续修复剩余问题以达到 100%

  1. 修复银行卡类型选择器的异步加载问题
  2. 实现数据持久化的重试机制
  3. 优化 afterEach 清理钩子性能
  4. 预计时间:2-3 小时

选项 B:记录已知问题,标记为部分完成

  • 当前通过率 85% 已显著改善
  • 功能测试核心逻辑正常
  • 剩余问题属于稳定性/时序问题
  • 可在后续 Sprint 中优化

选项 C:降低测试复杂度

  • 移除一些边缘情况测试
  • 聚焦核心业务流程验证

性能指标(更新)

第二次运行:

  • 执行时间:5.4 分钟(53 个测试)
  • 平均每个测试:~6.1 秒
  • 满足 ≤10 分钟/次的要求 ✅

Dev Agent Record

Agent Model Used

Created by create-story workflow based on epics.md and previous stories

Debug Log References

Completion Notes List

完成时间: 2026-01-11

完成内容:

  1. ✅ 创建了三个稳定性测试脚本:

    • run-stability-test.sh - 并行模式(4 workers)
    • run-stability-serial.sh - 串行模式(1 worker)
    • run-stability-single.sh - 单文件调试
  2. ✅ 执行了第一次并行模式稳定性测试:

    • 运行次数:1/10(测试失败后停止)
    • 通过率:77.4% (41/53)
    • 执行时间:5.7 分钟
  3. ⚠️ 发现并记录了测试稳定性问题:

    • 10 个测试失败(主要在 CRUD 和 Visit 测试)
    • 根本原因:表单提交时机、清理钩子超时

代码审查修复(AI Code Review - 2026-01-11):

  1. ✅ 修复了 run-stability-test.shset -e 问题,确保完成所有 10 次运行
  2. ✅ 统一所有残疾人测试文件的日志输出:console.logconsole.debug(220 处)
  3. ✅ 修正了 Story 任务完成状态,Task 2 正确标记为未完成
  4. ✅ 更新了 File List 以准确反映 git 变更

未完成内容:

  • ❌ 未达到 100% 通过率要求(需要修复测试稳定性问题后重新运行)
  • ⏭️ 串行模式测试未执行(等待修复后再执行)
  • ⏭️ 10 次连续运行未完成

技术发现:

  1. 现有测试(Story 9.1-9.6)存在稳定性问题
  2. 需要修复表单提交等待时间
  3. 需要优化 afterEach 清理钩子
  4. E2E 测试应统一使用 console.debug 以保持一致性

File List

创建的文件:

  • web/tests/e2e/scripts/run-stability-test.sh - 并行模式稳定性测试脚本
  • web/tests/e2e/scripts/run-stability-serial.sh - 串行模式稳定性测试脚本
  • web/tests/e2e/scripts/run-stability-single.sh - 单文件测试脚本

修改的文件(2026-01-12 第二轮修复):

  • _bmad-output/implementation-artifacts/9-7-stability-validation.md - 更新稳定性测试结果和修复记录
  • web/tests/e2e/pages/admin/disability-person.page.ts - 改进 submitForm 方法超时处理
  • web/tests/e2e/specs/admin/disability-person-crud.spec.ts - 增加表单提交后等待时间到 3 秒
  • web/tests/e2e/specs/admin/disability-person-bankcard.spec.ts - 增加等待时间,添加数据验证
  • web/tests/e2e/specs/admin/disability-person-visit.spec.ts - 添加 afterEach 超时保护注释

修改的文件(之前的代码审查修复):

  • web/tests/e2e/scripts/run-stability-test.sh - 修复 set -e 导致提前退出的问题
  • web/tests/e2e/specs/admin/disability-person-photo.spec.ts - console.log → console.debug
  • web/tests/e2e/specs/admin/disability-person-note.spec.ts - console.log → console.debug

注意: 上述测试文件的修改来自 Story 9.6(移除 .serial、添加 TEST_TIMESTAMP),在本 Story 中进行了 console.log → console.debug 的修复。