Storybook Test Runner 辅助函数与测试钩子实战指南:getStoryContext 与 waitForPageReady 的完整用法
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
Storybook 的官方 Test Runner(@storybook/test-runner)把每一个 story 都变成可在真实浏览器中运行的自动化测试,而要让这些测试具备读取 story 内部数据、等待页面资源完全加载等高级能力,就需要借助它导出的测试钩子(Test Hook API)与辅助函数(Helpers)。本文基于仓库中 test-runner-helper-function.md 配置片段,结合完整文档 test-runner.mdx,系统讲解setup/preVisit/postVisit钩子以及getStoryContext、waitForPageReady两个辅助函数的原理、配置与实战用法,读完即可在自己的项目中写出可复制的测试定制方案。
Test Runner 与辅助函数在 Storybook 测试体系中的位置
Storybook Test Runner 是一个框架无关、与 Storybook 并行运行的独立工具,底层由 Jest 和 Playwright 驱动:
- 对于没有 play function 的 story:它验证 story 是否能无错误地渲染;
- 对于带有 play function 的 story:它额外检查 play function 中的错误,并确认所有断言均通过。
这些测试在真实的浏览器中运行,可通过命令行(CLI)或 CI 服务器执行。文档明确指出,在基于 Vite 的 Storybook 框架中,官方推荐使用更快、更现代的 Vitest 插件(Vitest addon) 替代 Test Runner,但 Test Runner 仍适用于 Webpack 等场景,且其钩子与辅助函数的设计思路在测试扩展中通用。
# 安装 Test Runner(开发依赖) npm install @storybook/test-runner --save-dev # pnpm: pnpm add --save-dev @storybook/test-runner # yarn: yarn add --dev @storybook/test-runner安装后在package.json中添加脚本:
{ "scripts": { "test-storybook": "test-storybook" } }Test Runner 需要一个本地运行中或已发布的 Storybook 实例,先启动 Storybook,再在另一个终端窗口执行yarn test-storybook即可运行全部 story 测试。若需更细粒度的控制,可运行test-storybook --eject,它会在项目根目录生成可修改的test-runner-jest.config.js文件(Test Runner 底层使用 jest-playwright)。
Test Hook API:钩子与生命周期的完整清单
许多行为无法通过运行在浏览器内的 play function 实现——例如让 Test Runner 代为截取视觉快照,这类操作必须在 Node 进程中执行。为此 Test Runner 导出了可在全局覆写的测试钩子,让你能在 story 渲染的之前与之后接入测试生命周期。可用钩子如下:
| 钩子 | 说明 | 签名 |
|---|---|---|
prepare | 为测试准备浏览器 | async prepare({ page, browserContext, testRunnerConfig }) {} |
setup | 在所有测试运行前执行一次 | setup() {} |
preVisit | 在 story 首次被访问、渲染于浏览器之前执行 | async preVisit(page, context) {} |
postVisit | 在 story 被访问并完全渲染之后执行 | async postVisit(page, context) {} |
这些测试钩子目前属于实验性 API,可能发生破坏性变更,官方建议尽可能在 story 的 play function 内完成测试逻辑。
要启用钩子 API,需要在 Storybook 目录(默认为.storybook/)下新建配置文件test-runner.js或test-runner.ts。除setup外,其余钩子均为异步函数;preVisit与postVisit额外接收两个参数:一个 Playwright 的page对象,以及一个包含 story 的id、title、name的 context 对象。
Test Runner 执行时,测试会经历如下生命周期:
setup函数在所有测试运行前执行;- 生成包含必要信息的 context 对象;
- Playwright 导航到 story 页面;
- 执行
preVisit函数; - story 被渲染,存在的 play function 被执行;
- 执行
postVisit函数。
辅助函数全景:getStoryContext 与 waitForPageReady
Test Runner 导出了若干辅助函数(Helpers),用于访问 Storybook 内部数据(如args、parameters),让测试更可读、更易维护。核心配置片段 test-runner-helper-function.md 给出了完整的 JavaScript 与 TypeScript 两种写法,这里完整继承如下。
JavaScript 版本(.storybook/test-runner.js)
const { getStoryContext, waitForPageReady } = require('@storybook/test-runner'); module.exports = { // Hook that is executed before the test runner starts running tests setup() { // Add your configuration here. }, /* Hook to execute before a story is initially visited before being rendered in the browser. * The page argument is the Playwright's page object for the story. * The context argument is a Storybook object containing the story's id, title, and name. */ async preVisit(page, context) { // Add your configuration here. }, /* Hook to execute after a story is visited and fully rendered. * The page argument is the Playwright's page object for the story * The context argument is a Storybook object containing the story's id, title, and name. */ async postVisit(page, context) { // Get the entire context of a story, including parameters, args, argTypes, etc. const storyContext = await getStoryContext(page, context); // This utility function is designed for image snapshot testing. It will wait for the page to be fully loaded, including all the async items (e.g., images, fonts, etc.). await waitForPageReady(page); // Add your configuration here. }, };TypeScript 版本(.storybook/test-runner.ts)
import type { TestRunnerConfig } from '@storybook/test-runner'; import { getStoryContext, waitForPageReady } from '@storybook/test-runner'; const config: TestRunnerConfig = { // Hook that is executed before the test runner starts running tests setup() { // Add your configuration here. }, /* Hook to execute before a story is initially visited before being rendered in the browser. * The page argument is the Playwright's page object for the story. * The context argument is a Storybook object containing the story's id, title, and name. */ async preVisit(page, context) { // Add your configuration here. }, /* Hook to execute after a story is visited and fully rendered. * The page argument is the Playwright's page object for the story * The context argument is a Storybook object containing the story's id, title, and name. */ async postVisit(page, context) { // Get the entire context of a story, including parameters, args, argTypes, etc. const storyContext = await getStoryContext(page, context); // This utility function is designed for image snapshot testing. It will wait for the page to be fully loaded, including all the async items (e.g., images, fonts, etc.). await waitForPageReady(page); // Add your configuration here. }, }; export default config;两个辅助函数的分工如下:
getStoryContext(page, context):读取某个 story 的完整上下文,包括parameters、args、argTypes等全部信息,返回值为 Promise,需await。它接收两个参数:当前 story 对应的 Playwrightpage对象,以及钩子传入的 context 对象。它常用于在preVisit阶段根据 story 的参数调整测试环境,或在postVisit阶段按 story 元数据生成自定义断言。waitForPageReady(page):专为图像快照(image snapshot)测试设计。它会等待页面完全加载就绪,包括所有异步资源(如图片、字体等)。由于页面中字体、图片等资源加载完成前截图会得到不稳定的结果,该函数能显著提升快照测试的稳定性。
实战一:用 getStoryContext 让 Playwright 视口跟随 story 参数
在preVisit钩子中调用getStoryContext,即可在渲染前读取 story 的parameters.viewport.defaultViewport,并据此调整 Playwright 页面的视口尺寸。完整示例见 test-runner-custom-page-viewport.md:
const { getStoryContext } = require('@storybook/test-runner'); const { MINIMAL_VIEWPORTS } = require('storybook/viewport'); const DEFAULT_VIEWPORT_SIZE = { width: 1280, height: 720 }; module.exports = { async preVisit(page, story) { // Accesses the story's parameters and retrieves the viewport used to render it const context = await getStoryContext(page, story); const viewportName = context.parameters?.viewport?.defaultViewport; const viewportParameter = MINIMAL_VIEWPORTS[viewportName]; if (viewportParameter) { const viewportSize = Object.entries(viewportParameter.styles).reduce( (acc, [screen, size]) => ({ ...acc, // Converts the viewport size from percentages to numbers [screen]: parseInt(size), }), {}, ); // Configures the Playwright page to use the viewport size page.setViewportSize(viewportSize); } else { page.setViewportSize(DEFAULT_VIEWPORT_SIZE); } }, };这个示例的关键点在于:MINIMAL_VIEWPORTS中定义的 viewport 尺寸以百分比字符串形式存储,因此需要通过parseInt转换为数字后才能传给page.setViewportSize。如果该 story 未定义defaultViewport,则回退到默认的1280 × 720。同理,你也可以基于context.parameters中的其他配置(如主题、语言环境)在preVisit阶段做任意环境定制。
实战二:用 waitForPageReady 打造稳定的图像快照测试
waitForPageReady最常见的应用场景是图像快照测试。在setup钩子中通过expect.extend注册toMatchImageSnapshot匹配器,再在postVisit中等待页面资源就绪后截图。完整示例见 test-runner-waitpageready.md:
const { waitForPageReady } = require('@storybook/test-runner'); const { toMatchImageSnapshot } = require('jest-image-snapshot'); const customSnapshotsDir = `${process.cwd()}/__snapshots__`; module.exports = { setup() { expect.extend({ toMatchImageSnapshot }); }, async postVisit(page, context) { // Awaits for the page to be loaded and available including assets (e.g., fonts) await waitForPageReady(page); // Generates a snapshot file based on the story identifier const image = await page.screenshot(); expect(image).toMatchImageSnapshot({ customSnapshotsDir, customSnapshotIdentifier: context.id, }); }, };这里用context.id作为快照标识符,保证每个 story 生成唯一命名的快照文件。快照默认存放于项目根目录的__snapshots__目录;如需自定义快照目录,可编写自定义的snapshot-resolver.js并在test-runner-jest.config.js中启用snapshotResolver选项。若你的项目使用了 Emotion、Angular 的ng属性等会生成基于哈希的 CSS 类名的 CSS-in-JS 方案,还可通过snapshotSerializers配置自定义快照序列化器(默认使用jest-serializer-html),在快照前将动态生成的属性替换为稳定的静态值,确保跨测试运行的一致性。
延伸配置:钩子之外的高频能力
除钩子与辅助函数外,Test Runner 还支持通过.storybook/test-runner.js导出的其他配置函数扩展行为:
getHttpHeaders(url):对需要认证托管的 Storybook 设置 HTTP 请求头。该函数接收 fetch 请求与页面访问的 URL,返回需要附加的 headers 对象。完整示例见 test-runner-auth.md,例如根据 URL 是否包含prod返回不同的Authorization: Bearer <token>。- CLI 常用参数:
--url <地址>指定测试目标(默认本机 6006 端口,也可用TARGET_URL环境变量);--maxWorkers <数量>控制并行 worker 数;--failOnConsole令浏览器控制台报错时测试失败;--updateSnapshot/-u重录失败的快照;--eject生成本地配置文件。 - 标签过滤:通过
--includeTags、--excludeTags、--skipTags或配置文件中的include/exclude/skip选项,按 story 的 tags 精确控制测试范围(需 Test Runner 0.15 及以上)。CLI 标志优先于配置文件中的同名选项。 - index.json 模式:对远端 Storybook,Test Runner 使用其
index.json(原stories.json)静态索引运行测试,可通过--index-json强制开启、--no-index-json关闭(该模式与 watch 模式不兼容)。
常见问题与排障建议
- 测试超时:若出现
Timeout - Async callback was not invoked within the 15000 ms timeout,通常意味着 Playwright 无法并行处理过多 story,可在 CI 脚本中限制并行度,如yarn test-storybook --maxWorkers=2。 - CLI 错误输出过短:默认错误输出截断于 1000 字符,可通过
DEBUG_PRINT_LIMIT=5000 yarn test-storybook调整上限。 - Yarn PnP 兼容性:Test Runner 依赖社区维护的
jest-playwright-preset,尚不完全支持 Yarn Plug'n'Play。可切换nodeLinker为node-modules,或将 Playwright 作为直接依赖安装并执行playwright install下载浏览器二进制。 - 标签过滤冲突:若
include与exclude提供了相同 tags,Test Runner 将按exclude执行并忽略include,请确保两者 tags 不重叠。
总结
getStoryContext与waitForPageReady是扩展 Storybook Test Runner 时最核心的两个辅助函数:前者打通了 Node 测试进程与 Storybook 内部数据(parameters、args、argTypes)之间的桥梁,让测试可以依据每个 story 的元数据动态定制;后者为图像快照类测试提供了可靠的资源加载等待保障。配合setup/preVisit/postVisit钩子与getHttpHeaders等配置项,你可以将 Test Runner 扩展为覆盖交互、视口、图像快照、认证访问等各类场景的通用测试框架。相关完整配置片段与文档可继续查阅 test-runner.mdx 及 test-runner-custom-page-viewport.md、test-runner-waitpageready.md 等配套示例。
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考