一个 Storybook 插件,通过 Puppeteer 保存您故事的屏幕截图图像!

在 Github 上查看

Storycap

DEMO

npm

一个 Storybook 插件,保存您故事的屏幕截图图像 :camera: 通过 Puppeteer.

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 二进制文件

  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