AI助力重构百级测试套件的实战指南
开发者讲述如何用AI快速改造38个spec文件、165个Playwright E2E测试的架构。
完整中文译文
AI 主导分析本地测试通过率为何只有 6%
我加入了一个已有 Playwright E2E 测试套件的项目,其中包含 38 个 spec 文件、约 165 个测试,以及大约 14,000 行测试基础设施代码。我的第一步很简单:在本地运行测试。
在 130 个未跳过的测试中,只有 8 个通过。通过率仅为 6%。
令人困惑的是,CI 却是绿色的。后来发现,CI 使用 workers: 1 运行所有测试,而多个 worker 再加上开发环境的限制,意味着这些测试根本无法在本地正常运行。
第一步:分析——提出那些我不知道答案的问题
我对这个代码库的领域知识为零。我不知道为什么测试要以某种方式编写,不清楚自定义 wrapper 做了什么,也不知道真正的问题在哪里。因此,我开始让 AI 分析所有内容:Playwright 配置、Page Object、spec 文件以及 CI workflow。我不断提问,以帮助自己理解代码库,并找出怎样才能让测试在本地运行。
几天下来,我们产出了 18 份分析文档,涵盖架构、根因、反模式、静默 bug 和测试隔离。
分析阶段的目标,是为一个我完全不了解的代码库绘制地图。每一份文档,都是对一个问题的回答。
第二步:曳光弹计划
完成分析后,我已经清楚地知道需要改变什么。但问题在于:应该按照什么顺序进行?又该如何避免一场可能破坏一切的大规模重构?
答案是曳光弹(tracer bullet),这个概念来自《程序员修炼之道》。它的核心思想是:先构建一个贯穿所有层的轻量级端到端切片,证明这套架构可行,然后再以此为基础逐步扩展。
我设计了 8 枚曳光弹,每一枚都针对一个具体的切片:
- UI fixture 链——使用 worker-scoped 和 test-scoped fixture。验证目标:fixture 正常工作、teardown 正常工作、测试能够在 CI 中通过。
- API fixture 链——为 API 测试使用相同模式。验证目标:可组合 fixture 能够用于 API 场景。
- 扩展 UI 迁移——把已经验证过的 UI 模式应用到更多文件。
- MFE 级别的 project——按照 MFE 文件夹,将一个 Playwright project 拆分成 7 个 project(Applications、Organizations、Projects 等),每个 project 都配置
dependencies: ['Setup']。 - Teardown project——使用 Playwright 的 project dependencies 添加一个清理 project。
- 扩展 API fixture——构建可组合的 API fixture(
ownerOrg → ownerProject)。 - 大规模迁移 UI——迁移剩余的 UI spec 文件。
- API setup project——使用真正的 setup project 替换没有执行任何操作的
globalSetup。
关键洞察在于:依赖关系图告诉了我,哪些曳光弹可以并行执行。曳光弹 1 和 2 彼此独立,曳光弹 4 也是独立的,而曳光弹 3 依赖曳光弹 1。后来,当我并行运行多个 AI session 时,这一点变得非常重要。
曳光弹在实践中是什么样的
曳光弹 1 只针对一个包含 5 个测试的文件。具体步骤如下:
- 添加 fixture 基础设施(
currentUser → sharedOrg → project) - 迁移
projects-settings-general.spec.ts,让它使用这些 fixture - 在本地运行,确认测试通过
- 推送代码,确认 CI 保持绿色
第三步:我创建了一个 Skill 来执行工作
有了计划之后,我把全部 33 项任务分阶段组织了起来。接下来,我需要一种能够稳定推进这些任务的机制——每次都采用相同的流程、相同的质量标准和相同的基准测试方式。因此,我构建了一个 Skill:pw-test-improvement。
每次修改都必须遵循一套严格的七步流程:
- 识别(Identify)——从实现跟踪器中选择一个任务。
- 建立基线(Baseline)——修改前将受影响的测试运行 3 次,记录通过率和耗时。
- 修复(Fix)——按照 Skill 中内置的 Playwright 最佳实践进行修改。
- 测试(Test)——修改后运行 3 次,必须全部通过。
- 对比(Compare)——记录修改前后的基准测试结果。
- 更新(Update)——将跟踪器中的任务标记为已完成。
- 提交(Commit)——仅在被要求时提交,并附上结构化的 PR 描述。
这个 Skill 内置了相关知识:Playwright locator 的优先级(getByRole > getByLabel > getByText > ...)、需要避免的反模式清单(waitForTimeout、无实际作用的 assertion、CSS class selector、没有合理理由的强制点击),以及用 Playwright 原生调用替换 Actions wrapper 的迁移模式。
它使用 Playwright CLI 直接运行测试并采集结果。
架构调整
用 fixture 替换样板代码
最大的一项改动,是把重复的 beforeAll/afterAll 代码块迁移到 Playwright fixture。修改前,5 个测试文件分别调用 getUser()、createOrg() 和 createProject(),总计发起 15 次 API 调用。修改后,文件之间共享 worker-scoped fixture,总调用次数降至 7 次,减少了 53%。
关键在于区分 worker-scoped 和 test-scoped:
- Worker-scoped(
{ scope: 'worker' })——只创建一次,由该 worker 中的所有测试共享。适合组织和项目这类成本较高的 setup。 - Test-scoped(默认)——为每个测试重新创建。适合会被测试修改的数据。
Playwright 配置从由一个 project 运行全部 38 个 spec 文件,变为 7 个 project,每个 project 分别指向对应的 MFE 文件夹:
{ name: 'Applications', testDir: 'apps/ui/applications/e2e', dependencies: ['Setup'] },
{ name: 'Organizations', testDir: 'apps/ui/organizations/e2e', dependencies: ['Setup'] },
{ name: 'Projects', testDir: 'apps/ui/projects/e2e', dependencies: ['Setup'] },
// ... Subscriptions, Host, User Profile
这意味着,你可以使用 --project=Applications 只运行自己需要的测试;HTML 报告能够按业务区域分组;负载较重的 spec 也可以拥有独立的并行设置。
修复串行级联问题
实际上只有 4 个测试失败,看起来却像有 57 个失败。Application 测试使用了 serial mode,因此第一个测试失败后,同一个 describe 块中的所有后续测试都会被标记为“did not run”。
解决方案是:把负载较重的 spec 拆分到专用 project 中;增加 timeout(beforeAll 从 30 秒提高到 60 秒);限制 worker 数量,防止 API 过载;并使用 worker-scoped fixture 共享成本较高的 setup。
并不是所有方案第一次尝试都能成功。
Cleanup project 破坏了 CI。我们使用 Playwright 的 project dependencies 添加了一个 teardown project,用于在测试运行后清理测试数据。它在本地运行正常,但在 CI 中引发了失败——清理操作运行在共享环境中,干扰了其他 pipeline。最后只能将其回滚。
也并非所有东西都应该变成 fixture。我们曾尝试把所有内容都转换为 fixture。查阅 Playwright 文档后,我们在实际动手前否决了其中一个 fixture:worker-scoped fixture 会跨文件共享,从而污染那些使用不同选项、需要文件级隔离的 serial 测试。
这并不是一句“让 AI 修好它”就结束了,而是一套协作过程:
- 不停地提问——“这个方法做了什么?”“为什么这个测试不稳定?”“Playwright 文档说我们可以做 X,你能根据文档验证你的建议吗?”在持续数天的分析阶段,我问了几百个问题。
- 不停地提问——“这个方法做了什么?”“为什么这个测试不稳定?”“Playwright 文档说我们可以做 X,你能根据文档验证你的建议吗?”在持续数天的分析阶段,我问了几百个问题。
- 质疑每一条建议——“你确定吗?边界情况 X 怎么办?”如果 AI 建议采用某种模式,我会要求它解释原因,并确认这是否真的是一种好的做法。
- 质疑每一条建议——“你确定吗?边界情况 X 怎么办?”如果 AI 建议采用某种模式,我会要求它解释原因,并确认这是否真的是一种好的做法。
- 以文档作为事实依据——我会提供 Playwright 文档,并询问:“这与文档中的内容一致吗?”AI 的训练数据可能已经过时,而文档才是最新的。
- 以文档作为事实依据——我会提供 Playwright 文档,并询问:“这与文档中的内容一致吗?”AI 的训练数据可能已经过时,而文档才是最新的。
- 使用多种工具进行验证——我使用了 Goose、Claude Code 和 GitHub Copilot。不同的工具会发现不同的盲点,也会有不同的观点,就像与不同的团队成员合作一样。
- 使用多种工具进行验证——我使用了 Goose、Claude Code 和 GitHub Copilot。不同的工具会发现不同的盲点,也会有不同的观点,就像与不同的团队成员合作一样。
- 明确检查置信度——“你对此的置信度是多少?为什么只有 7 分?怎样才能达到 10 分?”这样的问题能够暴露 AI 可能不会主动说明的不确定性,也能帮助我们更深入地理解还有哪些问题尚未考虑,以及还能如何改进。
- 明确检查置信度——“你对此的置信度是多少?为什么只有 7 分?怎样才能达到 10 分?”这样的问题能够暴露 AI 可能不会主动说明的不确定性,也能帮助我们更深入地理解还有哪些问题尚未考虑,以及还能如何改进。
实际如何运行
我最多会同时运行 4 个 AI session,具体取决于哪些曳光弹彼此独立。实现计划中的依赖关系图告诉我,哪些任务可以安全地同时执行。
我会在不同 session 之间切换,检查进度、审阅正在进行的修改,并在某些内容需要验证时介入。AI 负责机械性的工作:应用模式、运行测试、采集基准数据。我负责监督:决定下一步修复什么、发现看起来不合理的建议,并对照真实的 Playwright 文档进行验证。
任何时候都不会超过 4 个。我希望阅读并理解正在发生的一切。
我们在这个过程中产出了什么
- 18 份分析文档
- 5 份实施指南
- 33 项带有验证命令的任务
- 1 个 Skill(测试改进)
CI 是绿色的,并不意味着测试能在本地运行。
在 serial mode 下,一个真实的失败可能级联成几十个虚假的失败。
Web-first assertion(expect(locator))能够发现手动检查遗漏的时序问题。
Fixture 并不总是正确答案,有些 setup 就应该放在 beforeAll 中。
关于与 AI 协作:
- AI 更擅长应用已知模式,而不是发明新模式;应该给它一套清晰的流程。
- 分析阶段是使用 AI 杠杆效应最高的环节,它发现了许多原本可能需要我几周才能注意到的问题。
- 多种工具优于单一工具;交叉检查可以发现 hallucination,并增强我们对方案的信心。
- Skill 让整个过程具备了可扩展性;如果没有它,每次修复都需要重复相同的指令。
- 让人始终参与其中:最多并行运行 4 个 session,绝不无人看管。
要为这类任务腾出时间。它们一开始确实需要投入不少时间,但随后你将获得大得多的产出。
使用 AI 时,就把它当成一位你还不太熟悉的新同事:他从不打开摄像头,所以很难真正了解他,你也无法完全信任他;但你知道他有不错的见解,工作能力也很好。只是你必须确认,他确实把问题考虑周全了,而不是在偷懒并做出糟糕的决定。
部分评论可能仅对已登录的访客可见。登录后即可查看全部评论。
如果需要采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。