关于 test-migration
test-migration 是帮助团队将测试套件逐步迁移到新框架的技能,适用于需要保留现有覆盖率与 CI 安全网的项目。它建议新旧套件并行运行,达到覆盖率和稳定性要求后再移除旧框架。
主要功能
- 01按优先级逐步迁移测试
- 02新旧测试套件在 CI 并行运行
- 03先迁移共享测试基础设施
- 04提供框架间模式映射指南
- 05使用转换工具进行初步迁移
- 06以覆盖率和稳定性判断退役
适用场景
- 01将 Selenium 测试迁移至 Playwright
- 02将 Cypress 测试迁移至 Playwright
- 03将 Jest 或 Mocha 迁移至 Vitest
- 04在大型测试迁移中维持 CI 安全网
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill test-migration安装后在 Claude Code 中输入/test-migration确认已加载。
附带文件references/framework-guides.mdreferences/parallel-ci.md
详细介绍
快速导航
| 你要从 → 迁移到 | 跳转至 | 首轮工具 |
|---|---|---|
| Selenium → Playwright | 翻译模式 + references/framework-guides.md | 手动编写;没有值得信赖的代码转换工具 |
| Cypress → Playwright | 翻译模式 + references/framework-guides.md | cy2pw 网页转换器/社区 CLI(参见工具表) |
| Protractor → Playwright | references/framework-guides.md(紧急——2023 年终止支持) | 手动编写;映射 by.model/by.binding |
| Jest → Vitest | references/framework-guides.md | 先用 sed 将 jest.替换为vi.,再验证 |
| Mocha → Vitest | references/framework-guides.md | 先用 chai-matcher 代码转换工具,再验证 |
| 任意迁移路径,测试数量超过 100 个 | 迁移工作流 + 并行运行策略 | 始终在 CI 中并行运行 |
调研问题
先检查 .agents/qa-project-context.md。如果该文件存在,请将其作为上下文,并跳过其中已回答的问题。
当前状态:
- 你要从什么框架迁移到什么框架?(这决定了要使用的翻译表以及适用的指南章节)
- 有多少个测试,目前的不稳定率/跳过率是多少?(迁移不稳定的测试会复现其不稳定性;可能不值得迁移被跳过的测试)
- 目前是否有覆盖率数据?(作为对等迁移的基线)
测试基础设施:
- 有哪些配套基础设施?(页面对象、自定义命令、测试夹具、数据工厂——这些都要先于测试迁移)
- 如何处理身份验证/会话?(登录流程是最常见的隐性故障点——参见故障模式)
- 哪条 CI 流水线负责运行测试?两种框架能否同时运行?(并行运行不可妥协)
约束条件:
- 时间安排有多紧?(紧急情况=框架终止支持,例如 Protractor;时间充裕=现代化改造)
- 迁移期间同时运行两套测试套件的预算是多少?(CI 成本暂时翻倍)
- 团队对目标框架的熟悉程度如何?(培训差距决定研讨会/结对计划)
核心原则
1. 增量迁移优于大爆炸式重写
一次性重写所有测试,是风险最高的做法。它会让测试开发停滞数周,一次性提交大量未经验证的测试,并在替代方案尚未就绪前移除已验证的安全网。务必一次迁移一个测试、一个模块。
2. 并行运行,直到覆盖率一致
在 CI 中同时运行两套测试套件,直到新套件的覆盖率至少达到旧套件的水平。旧套件是你的安全网;让它继续存在,但不阻塞流程。只有当新套件在多个迭代周期中捕获到真实回归问题后,才将旧套件退役。
3. 优先迁移价值最高的测试
从关键用户旅程、频繁失败的测试(这些测试最能受益于更好的框架)以及高风险领域入手。低价值测试留到最后——有些根本不值得迁移,这也是合理且应有记录的决定。
4. 迁移时改进模式,而不只是翻译
把糟糕的 Selenium 测试翻译成糟糕的 Playwright 测试,就是浪费机会。对于每个测试,都要问:“如果从头开始用目标框架编写,我会怎么写?”——使用面向用户的定位器、自动等待和测试夹具。对于 AI 代码转换工具的输出,这一点更是尤为重要(参见反模式)。
迁移工作流
典型的多 Sprint 迁移分为六个阶段。请直接使用内嵌清单;复杂代码请参阅参考资料。
阶段 1 — 审查现有测试套件(第 1 周)。 统计各类别的测试数量、跳过/不稳定测试的占比、覆盖率(如有测量)、运行时间、页面对象/工具文件、自定义插件和 CI 阶段。然后为每项测试分类:关键(营收流程)→ 高优先级(核心旅程)→ 中优先级(次要流程)→ 低优先级(管理/边缘场景)→ 跳过(已禁用、重复、过时)。此优先级顺序将决定后续所有工作。
阶段 2 — 设置目标框架(第 1–2 周)。 与旧框架并行安装。创建配置文件(playwright.config.ts / vitest.config.ts)、独立测试目录、非阻塞 CI 阶段、一个用于验证配置的冒烟测试、与现有格式匹配的报告器,以及共享环境变量/密钥。
阶段 3 — 迁移共享基础设施(第 2–3 周)。 先迁移基础设施,再迁移测试,顺序如下:基础页面对象/测试基类 → 身份验证辅助工具 → API 客户端辅助工具 → 通用页面对象(导航栏/页眉/页脚)→ 数据工厂 → 自定义断言 → 功能专属页面对象(及其测试)。在此阶段捕获 storageState,以便迁移后的测试跳过登录流程。
阶段 4 — 按优先级迁移测试(第 3–8 周及以后)。 对每项测试:阅读旧测试并理解它实际验证的内容 → 使用现代模式从头编写新测试(不要逐行翻译)→ 在本地运行并通过 → 在 CI 中运行并通过 → 将旧测试标记为“已迁移”(不要删除)→ 并行运行一个 Sprint 且均通过后,删除旧测试。
阶段 5 — 在 CI 中并行运行(全程)。 每个流水线都会运行两个测试套件。新测试套件达到同等水平前,旧测试套件保持非阻塞(continue-on-error: true);达到后,让新测试套件成为阻塞项,并移除旧任务。完整的 GitHub Actions 工作流请参阅 references/parallel-ci.md。
阶段 6 — 停用旧框架(最后)。 仅在达到同等水平且运行稳定后执行:所有关键/高优先级测试均已迁移,新测试套件在 CI 中连续 2 个以上 Sprint 通过,新测试套件的不稳定程度不高于旧测试套件,覆盖率对比未显示退步,团队已连续 2 个以上 Sprint 使用新框架编写新测试。然后从 package.json 中移除旧依赖,删除旧测试文件(不能只禁用),更新 CI,使其仅运行新测试套件,并更新文档。
自动化迁移工具
没有什么 AI 魔法按钮。请根据迁移路径选择合适的首轮工具,然后参照 references/framework-guides.md 手动完善。
| 工具 | 适用场景 | 不要依赖它处理 |
|---|---|---|
| cy2pw web converter (demo.playwright.dev/cy2pw) | Cypress→Playwright,适用于简单的测试规格;官方、确定性强,提供浏览器 UI | 自定义命令、POM 规范、测试夹具设置 |
@11joselu/cypress-to-playwright(社区 CLI,npx @11joselu/cypress-to-playwright <dir>) | 对目录中的 Cypress→Playwright 测试进行批量首轮转换 | 任何依赖时序的内容——请逐个检查文件 |
| AI agents(Claude Code、Cursor) | 自定义命令和测试夹具转换,以及转换器无法处理的部分 | 已完成的测试——只把输出当作首轮结果 |
Playwright 1.59+ agentic CLI(npx playwright trace、--debug=cli、经过 AI 优化的无障碍快照) | 迁移之后,排查迁移测试失败的原因 | 迁移本身——这些是调试工具,不是转换器 |
避免使用:
npx playwright migrate——Playwright CLI 并没有这个内置命令;在终端执行该指令会失败(已于 2026 年 6 月验证)。请使用上面的 cy2pw 或社区 CLI。
黄金参考范例方案。 在让任何 AI 或代码转换工具批量处理测试套件前,先手动端到端迁移一项有代表性的测试。将其提交为团队的规范模式(例如 e2e/_golden/login.spec.ts)。将此测试作为少量样例参考提供给 AI,并让人工审查者参照它。之后的每次迁移都以此文件为准——这是确保 AI 输出和审查者保持一致、效果最显著的做法。
转换模式
定位器映射
| 旧模式 | 新模式(Playwright) | 说明 |
|---|---|---|
By.id('submit-btn') | page.getByRole('button', { name: 'Submit' }) | 优先使用基于角色的定位 |
By.css('.nav-item.active') | page.getByRole('link', { name: 'Dashboard' }) | 使用用户可见文本 |
By.xpath('//div[@class="modal"]') | page.getByRole('dialog') | ARIA 角色更稳定 |
By.css('[data-testid="user-menu"]') | page.getByTestId('user-menu') | 将 testid 作为备用方案 |
cy.get('.product-card').first() | page.getByRole('article').first() | 优先使用语义化元素 |
cy.contains('Add to cart') | page.getByRole('button', { name: 'Add to cart' }) | 使用具体角色更好 |
element(by.model('username')) | page.getByLabel('Username') | Angular 模型 → 标签 |
以上角色和标签名称仅供示例,请替换为应用实际呈现的无障碍名称。
等待策略映射
| 旧模式 | 新模式(Playwright) | 说明 |
|---|---|---|
Thread.sleep(3000) | (完全移除) | Playwright 会自动等待 |
WebDriverWait(driver, 10).until(visible) | (完全移除) | 操作时自动等待 |
cy.wait(2000) | (完全移除) | 断言时自动等待 |
cy.wait('@apiCall') | page.waitForResponse(/\/api\/data/) | 显式等待网络响应(在执行操作前启动 Promise) |
browser.wait(EC.presenceOf(...)) | await expect(locator).toBeVisible() | 以网页为先的断言 |
implicitlyWait(10, SECONDS) | (移除 — 在配置中设置) | 在配置中使用 actionTimeout |
使用轮询的 FluentWait | await expect(locator).toHaveText('Done') | 以网页为先的断言会重试 |
断言映射
| 旧模式 | 新模式(Playwright) | 说明 |
|---|---|---|
assert element.is_displayed() | await expect(locator).toBeVisible() | 自动重试 |
cy.get('.msg').should('have.text', 'Done') | await expect(locator).toHaveText('Done') | 自动重试 |
expect(element.getText()).toBe('Done') | await expect(locator).toHaveText('Done') | 自动重试 |
cy.url().should('include', '/dashboard') | await expect(page).toHaveURL(/dashboard/) | 自动重试 |
assert len(elements) == 5 | await expect(locator).toHaveCount(5) | 自动重试 |
配置映射(Cypress → Playwright)
| 旧版(Cypress) | 新版(Playwright) |
|---|---|
baseUrl | use.baseURL |
defaultCommandTimeout: 10000 | use.actionTimeout: 10000 |
pageLoadTimeout: 30000 | use.navigationTimeout: 30000 |
retries: { runMode: 2 } | retries: 2 |
video: true | use.video: 'on' |
screenshotOnRunFailure: true | use.screenshot: 'only-on-failure' |
特定迁移指南
每种迁移路径都有各自的关键差异、迁移前后代码,以及 references/framework-guides.md 中的迁移说明检查清单。快速了解:
- Selenium → Playwright: 删除所有显式等待(Playwright 会自动等待),将字符串定位器替换为
getByRole/getByLabel/getByTestId,并用BrowserContext替换 WebDriver 会话。捕获storageState,而不是重新实现登录逻辑。 - Jest → Vitest(目标版本为 Vitest 4.x):API 基本兼容——将
jest.替换为vi.,将jest.config.js转换为vitest.config.ts,移除 Babel/ts-jest 转换(但如果使用了 emotion/styled-components 宏等自定义 Babel 插件,请保留对应的 esbuild/SWC 方案)。运行速度预计可提升 2-10 倍。使用 Vitest 4.1 的测试tags实现渐进式切换。 - Cypress → Playwright(目标版本为 PW ≥ 1.50):最大的变化是从命令队列转向 async/await。
cy.intercept()+cy.wait()改为page.route()+page.waitForResponse();自定义命令改为 fixtures。注意 1.52 版本中page.route()的 glob 和 Cookie 标头的破坏性变更(请通过browserContext.addCookies()设置 cookies,而不要覆盖标头)。 - Mocha → Vitest: 几乎可以一对一迁移。
describe/it/钩子保持不变;将 chai 匹配器(to.equal→toBe、to.deep.equal→toEqual、to.contain→toContain)和sinon→vi.fn()/vi.spyOn()转换即可。 - Protractor → Playwright: 自 2023 年起已停止维护,需尽快迁移。移除
waitForAngular,将by.model/by.binding映射为getByLabel/getByText/getByTestId,并将onPrepare改为globalSetup。
并行运行策略
迁移期间的覆盖率对比
在电子表格中跟踪新旧测试套件之间的覆盖率一致性,确保没有遗漏。该文档是停用旧测试套件时进行客观检查的依据。
Feature Area | Old Suite Tests | New Suite Tests | Parity | Notes
Login/Auth | 8 | 8 | 100% | Complete
Dashboard | 12 | 7 | 58% | In progress
Search | 6 | 0 | 0% | Not started
Checkout | 15 | 15 | 100% | Complete
User Settings | 4 | 4 | 100% | Complete
Admin Panel | 20 | 0 | 0% | Low priority
--- | --- | --- | --- |
Total | 65 | 34 | 52% | On track for Q2
渐进式切换时间表
对于包含 200 个测试的套件,计划约 10 个迭代:设置和基础设施(迭代 1-2)、关键路径迁移(迭代 3-5,约 50 个测试)、批量迁移(迭代 6-8)、清理和停用(迭代 9-10)。随着一致性逐步提高,旧测试套件会从阻塞变为非阻塞。从迭代 3 开始,所有新测试都只使用新框架编写。
何时删除旧测试
先将旧测试标记为“已迁移”,不要立即删除。只有当新的对应测试已在 CI 中通过 2 周以上,并且人工审核确认没有丢失独有断言时,才删除旧测试。如果旧测试能捕获新测试遗漏的回归问题,请先完善新测试。
反模式
一次性全面迁移
停止所有开发 3 个月,一次性重写所有测试。团队在重写期间无法发布新测试,覆盖率停滞,而且新测试套件直到一次性全部上线后才会在 CI 中得到验证。
解决方法: 采用渐进式迁移并行运行。每个迭代迁移一个模块。在整个迁移期间,两个测试套件都在 CI 中运行。从第一天起,就用新框架编写新测试。
只翻译,不现代化
逐行将 Selenium 测试转换为 Playwright 测试,同时保留显式等待、CSS 选择器和脆弱模式。测试虽然换成了新框架,却保留了所有旧问题。
解决方法: 按目标框架的最佳实践重写每个测试——使用 getByRole 而非 CSS,不使用显式等待,并用 fixtures 替代 beforeEach。迁移是改进每个测试的好机会。
把 AI 代码迁移工具的结果当作最终答案
Cursor、Aider、Continue、Claude Code 和 cy2pw/community 转换器都会进行机械式转换,但质量参差不齐,而且“只翻译,不现代化”的陷阱在它们的输出中会严重十倍。
解决方法: 先在一个文件上运行工具,并与源文件进行差异对比。如果现代化得当,再批量处理其余文件。如果只是翻译,就编写自己的黄金参考文件,并将其作为少样本示例提供给工具。每轮处理后,都要与原测试套件并行运行——如果结果不一致,说明工具出错了;在推广之前先调查原因。
不并行运行
在新套件得到验证之前,就停用旧套件。由于新套件缺少旧套件中已有的一项测试,导致回归问题漏了过去。
修复方法: 达到覆盖一致后,至少在 2 个冲刺周期内于 CI 中运行两个套件。旧套件是低成本的保险。只有在新套件独立捕获到回归问题后,才停用旧套件。
迁移期间丢失覆盖范围
20 个旧测试变成了 15 个新测试,因为“有些测试重复了”——但没人核实被删掉的测试是否覆盖了独有场景。
修复方法: 在覆盖率电子表格中,明确将每个旧测试对应到新测试。如果有旧测试被有意不迁移,请记录原因,并确认其覆盖范围已由其他测试提供。
照搬不稳定的测试
如果 Selenium 中的测试不稳定,而根本原因是测试设计(共享状态、时间假设、非确定性数据),那么它在 Playwright 中也会不稳定。忠实迁移只会复现这种不稳定性。
修复方法: 先诊断不稳定的原因并修复,然后编写包含修复方案的新测试。迁移是修复不稳定问题的最佳时机,因为无论如何都要重写测试。有关运行时逐项测试排查,请参阅 test-reliability。
团队没有接受培训
团队从未使用过 Playwright 就迁移过去,意味着所有工作都会落到骨干成员身上,而其他团队成员无法维护。
修复方法: 开始前组织一场 2 小时的研讨会。为最初迁移的 10 个测试进行结对编程(骨干成员 + 团队成员)。为新框架编写团队风格指南。每个迁移后的测试必须有 2 位或以上审阅者。
故障模式
| 症状 | 可能原因 | 修复或检查 |
|---|---|---|
| 每次运行时,迁移后的测试都会重新登录/在测试中途丢失会话 | 没有 storageState——登录流程未迁移过来 | 在 globalSetup 中捕获 storageState;每个测试重复使用。cy.setCookie/driver.add_cookie → browserContext.addCookies() |
npx playwright migrate 报错“unknown command” | 没有这样的内置 CLI | 使用 cy2pw 网页转换器或 @11joselu/cypress-to-playwright |
| 迁移后的拦截规则始终不匹配 | 1.52 的 glob 变更移除了 ?/[] | 转义这些字符,或将路由改写为正则表达式 |
route.continue() 上设置的 Cookie 被忽略 | route.continue() 上的 Cookie 标头从 Cookie 存储区加载,而非使用你的覆盖值 | 通过 browserContext.addCookies() 设置 Cookie |
| 新测试在本地通过,在 CI 中却不稳定 | 照搬了旧的时间假设 | 使用先检查网页状态的 expect(...) 断言替换 waitForTimeout/显式等待 |
| codemod 运行后测试数量减少 | codemod 静默跳过了不支持的语法 | 提交前,按功能领域将测试数量与覆盖率对照表进行比对 |
验证
证明迁移后的产物可正常运行——先做最小检查:
- 迁移后的测试通过:
npx playwright test <migrated-file>(或npx vitest run <migrated-file>)以状态码 0 退出。绿色运行是最低要求,不是终点。 - 没有测试数量遗漏: 按功能领域将新旧测试数量与覆盖率电子表格进行比对。“新套件测试”列中的数字应与实际运行的数量一致(每个领域运行
npx playwright test --list | wc -l)。 - 两个套件都在 CI 中运行: 并行运行工作流将旧版任务设为
continue-on-error: true,并将新版任务设为阻断;流水线运行中应同时显示这两个任务。 - 没有引入回归问题: 在删除任何旧测试之前,新套件须在 CI 中连续至少 2 个冲刺周期保持全绿。
完成标准
- 已定义迁移范围:哪些测试优先迁移(关键路径)、哪些最后迁移(低优先级),以及哪些测试有意不迁移(每项都需在覆盖率电子表格中记录理由)。
- 覆盖率对等电子表格中,每个关键/高优先级功能领域的覆盖率均为 100%,且测试数量不低于映射后的旧测试。
- 两个框架已在 CI 中并行运行至少连续 2 个冲刺,新测试套件全部通过。
- 已创建迁移回顾文档,记录不稳定性的根本原因、模式改进和团队培训方面的不足。
- 已彻底移除旧框架:从
package.json中删除依赖项,删除旧测试文件(不能仅禁用),并更新 CI 配置,使其只运行新测试套件。
参考文件(位于 references/ 中)
- framework-guides.md — 五种迁移路径的完整迁移前后代码和迁移注意事项检查清单:Selenium→Playwright、Jest→Vitest、Cypress→Playwright、Mocha→Vitest、Protractor→Playwright。
- parallel-ci.md — GitHub Actions 并行测试套件工作流,用于在迁移期间并行运行新旧框架。
相关技能
- playwright-automation — Selenium/Cypress/Protractor 迁移的目标框架最佳实践;翻译完成后,可参阅此技能编写符合 Playwright 惯例的代码。
- cypress-automation — 如果要迁移到 Cypress,或需要了解 Cypress 源框架的详细信息。
- unit-testing — 深入了解 Jest→Vitest 模式、模拟对象和覆盖率配置。
- test-reliability — 在运行时修复单个不稳定测试;如果迁移时发现必须分诊处理而非重写的不稳定测试,可使用此技能。
- selector-drift-recovery — 在同一框架内进行 UI 重构后,批量重新生成选择器;不适用于框架变更。
- ci-cd-integration — 并行 CI 配置,用于在迁移期间同时运行两个测试套件。
- qa-metrics — 跟踪迁移进度:测试数量对等情况、不稳定性对比、覆盖率变化。
- test-strategy — 迁移决策应与整体的跨季度测试策略保持一致。