开发者讲述如何用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 枚曳光弹,每一枚都针对一个具体的切片:
dependencies: ['Setup']。ownerOrg → ownerProject)。globalSetup。关键洞察在于:依赖关系图告诉了我,哪些曳光弹可以并行执行。曳光弹 1 和 2 彼此独立,曳光弹 4 也是独立的,而曳光弹 3 依赖曳光弹 1。后来,当我并行运行多个 AI session 时,这一点变得非常重要。
曳光弹 1 只针对一个包含 5 个测试的文件。具体步骤如下:
currentUser → sharedOrg → project)projects-settings-general.spec.ts,让它使用这些 fixture有了计划之后,我把全部 33 项任务分阶段组织了起来。接下来,我需要一种能够稳定推进这些任务的机制——每次都采用相同的流程、相同的质量标准和相同的基准测试方式。因此,我构建了一个 Skill:pw-test-improvement。
每次修改都必须遵循一套严格的七步流程:
这个 Skill 内置了相关知识:Playwright locator 的优先级(getByRole > getByLabel > getByText > ...)、需要避免的反模式清单(waitForTimeout、无实际作用的 assertion、CSS class selector、没有合理理由的强制点击),以及用 Playwright 原生调用替换 Actions wrapper 的迁移模式。
它使用 Playwright CLI 直接运行测试并采集结果。
最大的一项改动,是把重复的 beforeAll/afterAll 代码块迁移到 Playwright fixture。修改前,5 个测试文件分别调用 getUser()、createOrg() 和 createProject(),总计发起 15 次 API 调用。修改后,文件之间共享 worker-scoped fixture,总调用次数降至 7 次,减少了 53%。
关键在于区分 worker-scoped 和 test-scoped:
{ scope: 'worker' })——只创建一次,由该 worker 中的所有测试共享。适合组织和项目这类成本较高的 setup。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 修好它”就结束了,而是一套协作过程:
我最多会同时运行 4 个 AI session,具体取决于哪些曳光弹彼此独立。实现计划中的依赖关系图告诉我,哪些任务可以安全地同时执行。
我会在不同 session 之间切换,检查进度、审阅正在进行的修改,并在某些内容需要验证时介入。AI 负责机械性的工作:应用模式、运行测试、采集基准数据。我负责监督:决定下一步修复什么、发现看起来不合理的建议,并对照真实的 Playwright 文档进行验证。
任何时候都不会超过 4 个。我希望阅读并理解正在发生的一切。
CI 是绿色的,并不意味着测试能在本地运行。
在 serial mode 下,一个真实的失败可能级联成几十个虚假的失败。
Web-first assertion(expect(locator))能够发现手动检查遗漏的时序问题。
Fixture 并不总是正确答案,有些 setup 就应该放在 beforeAll 中。
关于与 AI 协作:
要为这类任务腾出时间。它们一开始确实需要投入不少时间,但随后你将获得大得多的产出。
使用 AI 时,就把它当成一位你还不太熟悉的新同事:他从不打开摄像头,所以很难真正了解他,你也无法完全信任他;但你知道他有不错的见解,工作能力也很好。只是你必须确认,他确实把问题考虑周全了,而不是在偷懒并做出糟糕的决定。
部分评论可能仅对已登录的访客可见。登录后即可查看全部评论。
如果需要采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。