Storycap
Storycap 会爬取您的 Storybook 并进行截图。它主要负责为视觉测试(如 reg-suit)生成图像。
功能
- :camera: 通过 Puppeteer 为每个故事拍摄屏幕截图。
- :zap: 速度极快。
- :package: 零配置。
- :rocket: 提供灵活的屏幕截图拍摄选项。
- :tada: 独立于任何 UI 框架(React、Angular、Vue 等)。
安装
$ npm install storycap
或者
$ npm install storycap puppeteer
安装 Puppeteer 是可选的。请参阅 Chromium 版本 获取更多详细信息。
入门
Storycap 以两种模式运行。一种是“简单”模式,另一种是“托管”模式。
在简单模式下,您无需配置 Storybook。您只需要提供 Storybook 的 URL,例如
$ npx storycap https://127.0.0.1:9001
您可以通过 `--serverCmd` 选项启动服务器。
$ storycap --serverCmd "start-storybook -p 9001" https://127.0.0.1:9001
当然,您可以使用预构建的 Storybook
$ build-storybook -o dist-storybook
$ storycap --serverCmd "npx http-server dist-storybook -p 9001" https://127.0.0.1:9001
此外,Storycap 可以爬取构建后的托管 Storybook 页面
$ storycap https://next--storybookjs.netlify.app/vue-kitchen-sink/
托管模式
设置 Storybook
如果您想控制故事的捕获方式(时间、大小等),请使用托管模式。
首先,将 `storycap` 添加到您的 Storybook 配置文件中
/* .storybook/main.js */
module.exports = {
stories: ['../src/**/*.stories.@(js|mdx)'],
addons: [
'@storybook/addon-actions',
'@storybook/addon-links',
'storycap', // <-- Add storycap
],
};
接下来,使用 `withScreenshot` 装饰器来告诉 Storycap 如何捕获您的故事。
/* .storybook/preview.js */
import { withScreenshot } from 'storycap';
export const decorators = [
withScreenshot, // Registration the decorator is required
];
export const parameters = {
// Global parameter is optional.
screenshot: {
// Put global screenshot parameters(e.g. viewport)
},
};
**注意:** 您可以使用 `addParameters` 和 `screenshot` 键来设置屏幕截图的配置。
**注意:** Storycap 还支持旧版 Storybook 装饰器的符号,例如 `addDecorator(withScreenshot({/* some options */}))`。但是,将装饰器用作函数已过时,不建议使用。如果您想了解更多信息,请参阅 Storybook 迁移指南。
设置您的故事(可选)
您可以在特定故事文件中通过 `parameters` 覆盖全局屏幕截图选项。
import React from 'react';
import MyComponent from './MyComponent';
export default {
title: 'MyComponent',
parameters: {
screenshot: {
delay: 200,
},
},
};
export const normal = () => <MyComponent />;
export const small = () => <MyComponent text="small" />;
small.story = {
parameters: {
screenshot: {
viewport: 'iPhone 5',
},
},
};
当然,Storycap 与 CSF 3.0 符号配合良好。
import React from 'react';
import MyComponent from './MyComponent';
export default {
title: 'MyComponent',
component: MyComponent,
parameters: {
screenshot: {
delay: 200,
},
},
};
export const Normal = {};
export const Small = {
args: {
text: 'small',
},
parameters: {
screenshot: {
viewport: 'iPhone 5',
},
},
};
运行 `storycap` 命令
$ npx start-storybook -p 9009
$ npx storycap https://127.0.0.1:9009
或者,您可以通过 `--serverCmd` 选项使用单行命令执行
$ npx storycap https://127.0.0.1:9009 --serverCmd "start-storybook -p 9009"
API
withScreenshot
withScreenshot(opt?: ScreenshotOptions): Function;
一个 Storybook 装饰器,用于通知 Storycap 捕获故事。
**注意:** 将 `withScreenshot` 用作函数已过时。如果您要提供屏幕截图选项,请使用 `addParameters`。
类型 `ScreenshotOptions`
`ScreenshotOptions` 对象可作为 `addParameters` 参数或 `withScreenshot` 参数的 `screenshot` 键的值使用。
interface ScreenshotOptions {
delay?: number; // default 0 msec
waitAssets?: boolean; // default true
waitFor?: string | () => Promise<void>; // default ""
fullPage?: boolean; // default true
hover?: string; // default ""
focus?: string; // default ""
click?: string; // default ""
skip?: boolean; // default false
viewport?: Viewport;
viewports?: string[] | { [variantName]: Viewport };
variants?: Variants;
waitImages?: boolean; // default true
omitBackground?: boolean; // default false
captureBeyondViewport?: boolean; // default true
clip?: { x: number; y: number; width: number; height: number } | null; // default null
}
- `delay`:捕获之前的等待时间 [毫秒]。
- `waitAssets`:如果设置为 `true`,Storycap 会等待故事请求的所有资源(例如 `` 或 CSS 背景图像)完成加载。
- `waitFor`:如果您设置一个返回 `Promise` 的函数,Storycap 会等待 Promise 解决。您也可以设置一个返回 `Promise` 的全局函数的名称。
- `fullPage`:如果设置为 `true`,Storycap 会捕获故事的整个页面。
- `focus`:如果设置有效的 CSS 选择器字符串,Storycap 会在将焦点设置到与选择器匹配的元素后捕获。
- `hover`:如果设置有效的 CSS 选择器字符串,Storycap 会在将鼠标悬停在与选择器匹配的元素上后捕获。
- `click`:如果设置有效的 CSS 选择器字符串,Storycap 会在单击与选择器匹配的元素后捕获。
- `skip`:如果设置为 `true`,Storycap 会取消捕获相应的的故事。
- `viewport`、`viewports`:请参阅下文中的 `Viewport` 类型部分。
- `variants`:请参阅下文中的 `Variants` 类型部分。
- `waitImages`:已过时。请使用 `waitAssets`。如果设置为 `true`,Storycap 会等待故事中的 `` 加载完成。
- `omitBackground`:如果设置为 `true`,Storycap 会省略页面的背景,从而生成透明的屏幕截图。请注意,Storybook 主题也需要是透明的。
- `captureBeyondViewport`:如果设置为 `true`,Storycap 会捕获视窗范围之外的屏幕截图。另请参阅 Puppeteer API 文档。
- `clip`:如果设置,Storycap 只会捕获由 x/y/width/height 范围内的屏幕部分。
类型 `Variants`
`Variants` 用于从 1 个故事生成 多个 PNG。
type Variants = {
[variantName: string]: {
extends?: string | string[]; // default: ""
delay?: number;
waitAssets?: boolean;
waitFor?: string | () => Promise<void>;
fullPage?: boolean;
hover?: string;
focus?: string;
click?: string;
skip?: boolean;
viewport?: Viewport;
waitImages?: boolean;
omitBackground?: boolean;
captureBeyondViewport?: boolean;
clip?: { x: number; y: number; width: number; height: number } | null;
};
};
- `extends`:如果设置了其他变体的名称(或其名称的数组),则该变体会扩展其他变体的选项。该变体会生成一个带有后缀的 PNG 文件,例如 `_${parentVariantName}_${thisVariantName}`。
类型 `Viewport`
`Viewport` 与 Puppeteer 视窗接口 兼容。
type Viewport =
| string
| {
width: number; // default: 800
height: number; // default: 600
deviceScaleFactor: ?number; // default: 1,
isMobile?: boolean; // default: false,
hasTouch?: boolean; // default: false,
isLandscape?: boolean; // default: false,
};
**注意:** 如果设置字符串,则应选择有效的 设备名称。
`Viewport` 值在 `viewports` 字段中可用,例如
addParameters({
screenshot: {
viewports: {
large: {
width: 1024,
height: 768,
},
small: {
width: 375,
height: 668,
},
xsmall: {
width: 320,
height: 568,
},
},
},
});
函数 `isScreenshot`
function isScreenshot(): boolean;
返回当前进程是否在 Storycap 浏览器中运行。这对于仅在 Storycap 中更改故事的行为(例如禁用 JavaScript 动画)非常有用。
命令行选项
usage: storycap [options] storybook_url
Options:
--help Show help [boolean]
--version Show version number [boolean]
-o, --outDir Output directory. [string] [default: "__screenshots__"]
-p, --parallel Number of browsers to screenshot. [number] [default: 4]
-f, --flat Flatten output filename. [boolean] [default: false]
-i, --include Including stories name rule. [array] [default: []]
-e, --exclude Excluding stories name rule. [array] [default: []]
--delay Waiting time [msec] before screenshot for each story. [number] [default: 0]
-V, --viewport Viewport. [array] [default: ["800x600"]]
--disableCssAnimation Disable CSS animation and transition. [boolean] [default: true]
--disableWaitAssets Disable waiting for requested assets [boolean] [default: false]
--trace Emit Chromium trace files per screenshot. [boolean] [default: false]
--silent [boolean] [default: false]
--verbose [boolean] [default: false]
--forwardConsoleLogs Forward in-page console logs to the user's console. [boolean] [default: false]
--serverCmd Command line to launch Storybook server. [string] [default: ""]
--serverTimeout Timeout [msec] for starting Storybook server. [number] [default: 60000]
--shard The sharding options for this run. In the format <shardNumber>/<totalShards>.
<shardNumber> is a number between 1 and <totalShards>. <totalShards> is the total
number of computers working. [string] [default: "1/1"]
--captureTimeout Timeout [msec] for capture a story. [number] [default: 5000]
--captureMaxRetryCount Number of count to retry to capture. [number] [default: 3]
--metricsWatchRetryCount Number of count to retry until browser metrics stable. [number] [default: 1000]
--viewportDelay Delay time [msec] between changing viewport and capturing. [number] [default: 300]
--reloadAfterChangeViewport Whether to reload after viewport changed. [boolean] [default: false]
--stateChangeDelay Delay time [msec] after changing element's state. [number] [default: 0]
--listDevices List available device descriptors. [boolean] [default: false]
-C, --chromiumChannel Channel to search local Chromium. One of "puppeteer", "canary", "stable", "*"
[string] [default: "*"]
--chromiumPath Executable Chromium path. [string] [default: ""]
--puppeteerLaunchConfig JSON string of launch config for Puppeteer.
[string] [default: "{ "args": ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] }"]
Examples:
storycap https://127.0.0.1:9009
storycap https://127.0.0.1:9009 -V 1024x768 -V 320x568
storycap https://127.0.0.1:9009 -i "some-kind/a-story"
storycap http://example.com/your-storybook -e "**/default" -V iPad
storycap --serverCmd "start-storybook -p 3000" https://127.0.0.1:3000
从 1 个故事生成多个 PNG
默认情况下,storycap 从 1 个故事生成 1 个屏幕截图图像。如果您想为 1 个故事生成多个 PNG(例如视窗、元素状态变化等),请使用 `variants`。
基本用法
例如
import React from 'react';
import MyComponent from './MyButton';
export default {
title: 'MyButton',
};
export const normal = () => <MyButton />;
normal.story = {
parameters: {
screenshot: {
variants: {
hovered: {
hover: 'button.my-button',
},
},
},
},
};
上面的配置会生成 2 个 PNG
MyButton/normal.png
MyButton/normal_hovered.png
上面的示例中的变体键 `hovered` 用作生成 PNG 文件名的后缀。几乎所有 `ScreenshotOptions` 字段都可以在变体值的字段中使用。
**注意:** `variants` 本身和 `viewports` 不能用作变体的字段。
变体组合
您可以通过 `extends` 字段组合多个变体。
normal.story = {
parameters: {
screenshot: {
variants: {
small: {
viewport: 'iPhone 5',
},
hovered: {
extends: 'small',
hover: 'button.my-button',
},
},
},
},
};
上面的示例会生成以下文件
- `MyButton/normal.png`(默认)
- `MyButton/normal_small.png`(从 `small` 变体派生)
- `MyButton/normal_hovered.png`(从 `hovered` 变体派生)
- `MyButton/normal_small_hovered.png`(从 `hovered` 和 `small` 变体派生)
**注意:** 您可以通过 `viewports` 选项的键扩展某些视窗,因为 `viewports` 字段会在内部扩展为变体。
跨多台计算机并行化
要跨多台计算机并行处理更多故事,可以使用 `shard` 参数。
`shard` 参数是以下格式的字符串:`<shardNumber>/<totalShards>`。`<shardNumber>` 是 1 到 `<totalShards>`(含)之间的数字。`<totalShards>` 是运行执行的计算机总数。
例如,在单台计算机上运行 `--shard 1/1` 将被视为默认行为。两台计算机分别运行 `--shard 1/2` 和 `--shard 2/2` 会将故事拆分到这两台计算机上。
故事会根据其 ID 按循环方式分配到各个分片中。如果一系列“相邻的”故事比其他故事的屏幕截图速度慢,则应将它们均匀地分配。
技巧
使用 Docker 运行
使用 regviz/node-xcb。
或者创建您自己的 Docker 基础映像,例如
FROM node:12
RUN apt-get update -y
RUN apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
完全控制截图时间
有时您可能希望完全管理执行屏幕截图的时间。如果您认为需要,请使用 `waitFor` 选项。此字符串参数应指向一个返回 `Promise` 的全局函数。
例如,以下设置告诉 storycap 等待 `fontLoading` 解决
<!-- ./storybook/preview-head.html -->
<link rel="preload" href="/some-heavy-asset.woff" as="font" onload="this.setAttribute('loaded', 'loaded')" />
<script>
function fontLoading() {
const loaded = () => !!document.querySelector('link[rel="preload"][loaded="loaded"]');
if (loaded()) return Promise.resolve();
return new Promise((resolve, reject) => {
const id = setInterval(() => {
if (!loaded()) return;
clearInterval(id);
resolve();
}, 50);
});
}
</script>
/* .storybook/config.js */
import { addParameters, addDecorator } from '@storybook/react';
import { withScreenshot } from 'storycap';
addDecorator(withScreenshot);
addParameters({
screenshot: {
waitFor: 'fontLoading',
},
});
Chromium 版本
从 v3.0.0 开始,Storycap 不再直接使用 Puppeteer。相反,Storycap 会按照以下顺序搜索 Chromium 二进制文件
- 已安装的 Puppeteer 包(如果您明确安装了)
- 本地安装的 Canary Chrome
- 本地安装的稳定版 Chrome
您可以使用 `--chromiumChannel` 选项更改搜索通道,或使用 `--chromiumPath` 选项设置可执行的 Chromium 文件路径。
Storybook 兼容性
Storybook 版本
Storycap 已针对以下版本进行了测试
- 简单模式
- Storybook v5.x
- Storybook v6.x
- 托管模式
- Storybook v5.x
- Storybook v6.x
另请参阅 `examples` 目录中的包。
UI 框架
Storycap(使用简单模式和托管模式)对特定 UI 框架(例如 React、Angular、Vue.js 等)是不可知的。因此,您可以将它与您喜欢的框架一起用于 Storybook :smile:。
迁移
如果您已经使用 storybook-chrome-screenshot 或 zisui,请参阅 迁移指南。
工作原理
Storycap 使用 Puppeteer 访问已启动的页面。
待办事项
以下任务尚待完成。欢迎贡献 :smiley
- 升级 v2
- 将爬虫提取为 NPM 包。
- 更多单元测试。
- 使用 JS/CSS 覆盖率进行捕获。
贡献
请参阅 CONTRIBUTING.md。