Storycap
Storycap 會爬取你的 Storybook 並擷取螢幕截圖。它主要負責產生視覺測試(例如 reg-suit)所需的影像。
功能
- :camera:擷取每個 story 的螢幕截圖。透過 Puppeteer。
- :zap:極快。
- :package:零配置。
- :rocket:提供彈性的螢幕截圖拍攝選項。
- :tada:獨立於任何 UI 框架(React、Angular、Vue 等...)
安裝
$ npm install storycap
或者
$ npm install storycap puppeteer
安裝 puppeteer 是選用的。請參閱Chromium 版本以取得更多詳細資訊。
開始使用
Storycap 以 2 種模式執行。一種是「簡單」模式,另一種是「託管」模式。
使用簡單模式,您不需要設定您的 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
如果您想要控制如何擷取 stories(時機或大小等等),請使用託管模式。
首先,將 storycap
加入您的 Storybook 設定檔
/* .storybook/main.js */
module.exports = {
stories: ['../src/**/*.stories.@(js|mdx)'],
addons: [
'@storybook/addon-actions',
'@storybook/addon-links',
'storycap', // <-- Add storycap
],
};
接下來,使用 withScreenshot
裝飾器來告知 Storycap 如何擷取您的 stories。
/* .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({/* 一些選項 */})
。但使用裝飾器作為函式已不建議使用。如果您想要更多詳細資訊,請參閱 Storybook 的遷移指南。
設定你的 stories(選用)
您也可以透過 parameters
覆寫特定 stories 檔案中的全域螢幕截圖選項。
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 擷取 stories。
注意: 將 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 會等到 story 所請求的所有資源(例如<img>
或 CSS 背景圖片)完成後才會擷取。waitFor
:如果您設定一個函式傳回Promise
,Storycap 會等待 promise 解析。您也可以設定一個傳回Promise
的全域函式名稱。fullPage
:如果設定為 true,Storycap 會擷取整個 story 頁面。focus
:如果設定有效的 CSS 選取器字串,Storycap 會在聚焦與選取器相符的元素後擷取。hover
:如果設定有效的 CSS 選取器字串,Storycap 會在將滑鼠游標懸停在與選取器相符的元素後擷取。click
:如果設定有效的 CSS 選取器字串,Storycap 會在點擊與選取器相符的元素後擷取。skip
:如果設定為 true,Storycap 會取消擷取對應的 stories。viewport
、viewports
:請參閱下方的Viewport
類型章節。variants
:請參閱下方的Variants
類型章節。waitImages
:已棄用。請使用waitAssets
。如果設定為 true,Storycap 會等到 story 中的<img>
載入完成。omitBackground
:如果設定為 true,Storycap 會省略頁面的背景,以便產生透明的螢幕截圖。請注意,storybook 主題也需要是透明的。captureBeyondViewport
:如果設定為 true,Storycap 會擷取超出視窗範圍的螢幕截圖。另請參閱 Puppeteer API 文件。clip
:如果設定,Storycap 只會擷取螢幕上由 x/y/width/height 界定的部分。
類型 Variants
Variants
用於從 1 個 story 產生多個 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
:如果設定其他 variant 的名稱(或其名稱的陣列),此 variant 會擴充其他 variant 選項。而且此 variant 會產生具有尾碼(例如_${parentVariantName}_${thisVariantName}
)的 PNG 檔案。
類型 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 中變更您的 stories 行為(例如停用 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 個 story 產生多個 PNG
依預設,storycap 會從 1 個 story 產生 1 個螢幕截圖。如果您想要針對 1 個 story 產生多個 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
在上述範例中,variant 鍵 hovered
用作產生之 PNG 檔案名稱的尾碼。而且幾乎所有 ScreenshotOptions
欄位都可作為 variant 值的欄位使用。
注意: variants
本身和 viewports
皆禁止作為 variant 的欄位。
Variants 組合
您可以透過 extends
欄位組合多個 variant。
normal.story = {
parameters: {
screenshot: {
variants: {
small: {
viewport: 'iPhone 5',
},
hovered: {
extends: 'small',
hover: 'button.my-button',
},
},
},
},
};
上述範例會產生以下內容
MyButton/normal.png
(預設)MyButton/normal_small.png
(衍生自small
variant)MyButton/normal_hovered.png
(衍生自hovered
variant)MyButton/normal_small_hovered.png
(衍生自hovered
和small
variant)
注意: 您可以使用 viewports
選項的鍵擴充一些視窗範圍,因為 viewports
欄位會在內部展開為 variants。
跨多台電腦平行處理
若要在多台電腦上平行處理更多 stories,可以使用 shard
引數。
shard
引數是一個格式為:<shardNumber>/<totalShards>
的字串。<shardNumber>
是介於 1 和 <totalShards>
之間的數字(含 1 和 <totalShards>
)。<totalShards>
是執行此操作的電腦總數。
例如,使用 --shard 1/1
的執行會被視為單一電腦上的預設行為。兩台電腦分別執行 --shard 1/2
和 --shard 2/2
會將 stories 分散到兩台電腦上。
當 stories 依其 ID 排序時,會以循環配置的方式將 stories 分散到各個分片上。如果一系列「靠近」的 stories 的螢幕截圖速度比其他 stories 慢,則應該平均分配這些 stories。
提示
使用 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。