一個 Storybook 擴充功能,透過 puppeteer 儲存你的 stories 的螢幕截圖!

在 Github 上檢視

Storycap

DEMO

npm

一個 Storybook 擴充功能,透過 Puppeteer 儲存你的 stories 的螢幕截圖:camera:。

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)
  },
};

注意: 您可以使用 addParametersscreenshot 鍵設定螢幕截圖的配置。

注意: 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。
  • viewportviewports:請參閱下方的 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

ViewportPuppeteer 視窗範圍介面 相容。

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 (衍生自 hoveredsmall 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 二進位檔

  1. 已安裝的 Puppeteer 套件(如果您已明確安裝)
  2. 本機安裝的 Canary Chrome
  3. 本機安裝的穩定版 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-screenshotzisui,請參閱 遷移指南

運作方式

Storycap 會使用 Puppeteer 存取已啟動的頁面。

待辦事項

以下工作仍在進行中。歡迎貢獻:smiley

  • 升級 v2
  • 將爬取工具提取為 NPM 套件。
  • 更多單元測試。
  • 使用 JS/CSS 覆蓋率進行擷取。

貢獻

請參閱 CONTRIBUTING.md

授權

MIT © reg-viz