Playwright 组件测试迁移指南:从 Testing Library 切换到 mount 组件测试模型
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
本文基于 Playwright 官方文档 Migrating from Testing Library,讲解如何将基于 DOM Testing Library、React Testing Library 或 Vue Testing Library 编写的组件测试,迁移到 Playwright Test 内置的组件测试模型(story + gallery +mountfixture)。读完本文,你将掌握完整的 API 对照表、逐行迁移方法,并能结合源码理解mount()在底层是如何通过一个#rootLocator 驱动真实浏览器渲染的。
一、迁移的总体思路
原 Testing Library 的写法是在测试里直接调用render()内联地把组件挂载到测试环境;Playwright 则把这份"设置"抽离到一个story(一个把组件嵌入特定场景的小包装器),由你自己的 dev server 以gallery(组件库页面)的形式对外提供,测试中只用 id 引用它。gallery 的完整搭建方法见 组件测试指南。
需要注意的一个边界情况:如果你是在浏览器中使用 DOM Testing Library(例如用 webpack 打包端到端测试),可以直接切换到 Playwright Test——原文档中的示例聚焦组件测试,但做端到端测试时,只需把await mount(...)换成await page.goto('http://localhost:3000/')打开被测页面即可。
二、API 对照速查表(Cheat Sheet)
下面是原文档给出的完整对照表,迁移时可直接照此替换:
| Testing Library | Playwright |
|---|---|
screen | page与component(Locator) |
queries(getBy.../findBy...) | locators |
async helpers(waitFor等) | assertions |
user events(@testing-library/user-event) | Locator 的 actions |
await user.click(screen.getByText('Click me')) | await component.getByText('Click me').click() |
await user.click(await screen.findByText('Click me')) | await component.getByText('Click me').click() |
await user.type(screen.getByLabelText('Password'), 'secret') | await component.getByLabel('Password').fill('secret') |
expect(screen.getByLabelText('Password')).toHaveValue('secret') | await expect(component.getByLabel('Password')).toHaveValue('secret') |
screen.getByRole('button', { pressed: true }) | component.getByRole('button', { pressed: true }) |
screen.getByLabelText('...') | component.getByLabel('...') |
screen.queryByPlaceholderText('...') | component.getByPlaceholder('...') |
screen.findByText('...') | component.getByText('...') |
screen.getByTestId('...') | component.getByTestId('...') |
render(<Component />); | 一个 story 导出 +await mount('Component/Default'); |
const { unmount } = render(<Component />); | const component = await mount('...'); await component.unmount(); |
const { rerender } = render(<Component />); | const component = await mount('...'); await component.update(props); |
三、完整示例:逐行迁移一个登录测试
Testing Library 原版:
import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; test('sign in', async () => { // Setup the page. const user = userEvent.setup(); render(<SignInPage />); // Perform actions. await user.type(screen.getByLabelText('Username'), 'John'); await user.type(screen.getByLabelText('Password'), 'secret'); await user.click(screen.getByRole('button', { name: 'Sign in' })); // Verify signed in state by waiting until "Welcome" message appears. expect(await screen.findByText('Welcome, John')).toBeInTheDocument(); });逐行迁移后,场景先从测试搬进组件旁边的 story 文件:
import { SignInPage } from './SignInPage'; export const Default = () => <SignInPage />; // 1然后测试按 id 挂载这个 story:
const { test, expect } = require('@playwright/test'); // 2 test('sign in', async ({ mount }) => { // 3 // Setup the page. const component = await mount('pages/SignInPage/Default'); // 4 // Perform actions. await component.getByLabel('Username').fill('John'); // 5 await component.getByLabel('Password').fill('secret'); await component.getByRole('button', { name: 'Sign in' }).click(); // Verify signed in state by waiting until "Welcome" message appears. await expect(component.getByText('Welcome, John')).toBeVisible(); // 6 });迁移要点(对应代码中的内联注释):
- 注释 1:过去
render()在测试里内联设置的一切——props、providers、mock 数据——都变成 story 的导出。story 运行在浏览器里,因此活的 JS 对象(回调、实例)不再需要跨越 Node.js/浏览器边界进入测试。 - 注释 2:组件测试和端到端测试都统一从
@playwright/test导入。 - 注释 3:测试函数拿到
page(与其他测试隔离)和mount(在该 page 中渲染 story)两个 fixtures。它们是 Playwright Test fixtures 体系的一部分。 - 注释 4:
render被mountfixture 替代,它接收 story id,返回一个作用域限定在 gallery 根元素上的 component locator。 - 注释 5:用
Locator.locator/Page.locator创建的 Locator 完成绝大多数交互操作。 - 注释 6:用 assertions 验证状态。
四、查询(Queries)如何迁移
Testing Library 的getBy...、findBy...、queryBy...及其多元素版本(getAllBy...)统一替换为component.getBy...Locator。由于 Locator始终自动等待并在必要时重试,你不必再纠结该选getBy、findBy还是queryBy哪个方法——findByText的"等待出现"语义已由自动等待覆盖。
当你需要做列表操作(例如断言一列文本),Playwright 会自动执行多元素操作,详见 Locators 的 Lists 章节。
五、用断言替换waitFor
Playwright 的断言会自动等待条件成立,因此通常不需要显式的waitFor/waitForElementToBeRemoved调用:
// Testing Library await waitFor(() => { expect(getByText('the lion king')).toBeInTheDocument(); }); await waitForElementToBeRemoved(() => queryByText('the mummy')); // Playwright await expect(page.getByText('the lion king')).toBeVisible(); await expect(page.getByText('the mummy')).toBeHidden();如果找不到合适的断言,使用expect.poll替代:
await expect.poll(async () => { const response = await page.request.get('https://api.example.com'); return response.status(); }).toBe(200);六、用嵌套 Locator 替换within
可以用Locator.locator方法在一个 Locator 内部再创建 Locator,作用即等价于within:
// Testing Library const messages = screen.getByTestId('messages'); const helloMessage = within(messages).getByText('hello'); // Playwright const messages = component.getByTestId('messages'); const helloMessage = messages.getByText('hello');七、源码视角:mountfixture 到底做了什么
原文档强调mount接收 story id 并返回"作用域限定在 gallery 根上的 component locator"。这一点可以在仓库源码中得到印证:mountfixture 的完整实现位于 packages/playwright/src/index.ts。从源码结构看,其行为可以拆解为四步:
- 强制要求
baseURL:若配置中没有设置baseURL,mount()会直接抛出mount() requires baseURL to point at the component gallery. Set it in your Playwright config.——这解释了为什么组件测试项目的配置里baseURL必须指向 gallery 页面(见 组件测试指南的配置示例)。 - 导航到 gallery:
await page.goto(baseURL),然后page.evaluate调用页面暴露的window.mount({ story: storyId, props });如果 gallery 页面没有定义window.mount(),也会抛出明确的错误。 - 返回作用域 Locator:
mount返回的是page.locator('#root')上附加了额外方法的对象——这就是"component locator 限定在 gallery 根"的实现本体,测试中的所有查询都从#root向下作用域。 - 附加
update与unmount:update(newProps)再次以新 props 调用window.mount(不重新导航);从源码注释看,若 gallery 复用了渲染根节点,框架会做 reconcile,组件状态得以保留——这正是对照表中rerender的替代方案。unmount()则调用window.unmount?.()。
此外源码中还有一个值得注意的细节:调用window.mount时使用了{ exposeFunctions: true }选项,意味着传入的 props 中如果包含函数,会被转换为真正可在浏览器内调用、并回调到测试进程的函数。
配套的 gallery 规范(window.mount/window.unmount契约、#root挂载点、根节点复用等)在仓库中以 agent skill 的形式提供,位于 playwright-component-testing skill,其中还附有 React 实现参考、Vue 实现参考 和 实验包迁移参考。
八、迁移后你获得的 Playwright Test 能力
一旦迁移到 Playwright Test,你将获得(原文档"Playwright Test Super Powers"):
- 完整的零配置 TypeScript 支持
- 在所有主流浏览器引擎(Chrome、Firefox、Safari)与所有主流操作系统(Windows、macOS、Ubuntu)上运行测试
- 对多源(multi-origin)、(i)frames、tabs 和 contexts 的完整支持(参见 pages)
- 在多个浏览器中并行、隔离地运行测试
- 内置测试产物(截图、视频、trace)收集,见 recording options
以及随 Playwright Test 捆绑的工具链:
- Visual Studio Code 集成
- UI Mode:带 watch mode 和"时间旅行"调试体验
- Playwright Inspector
- 测试代码生成
- Playwright Tracing:用于事后调试
九、延伸阅读
围绕 Playwright Test 运行器与组件测试的更多文档:
- Getting Started
- Component testing(gallery 搭建、story 约定、
page.route拦截网络等完整模式) - Locators
- Assertions
- Auto-waiting
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考