Playwright

一个插件,用于在 Storybook 环境中,在多个浏览器中对 Story 进行视觉测试。

在 Github 上查看

storybook-addon-playwright

一个插件,用于在 Storybook 环境中,在多个浏览器中对 Story 进行视觉测试。

该插件在 Storybook 静态构建中无法工作,但可以针对静态构建文件测试截图。

此软件包仅在 React 框架中进行了测试,因此可能不适用于其他框架。

仅适用于 组件 Story 格式 (CSF)

addon-screenshot

兼容性

软件包 版本
storybook ~6.4
playwright ~1.17

动机

能够使组件在所有浏览器中都具有相同的感觉和外观一直是一个挑战,这要求开发人员不断在浏览器之间切换并直观地检查组件。跟踪更改并能够尽快检测更改也很重要。因此创建了此插件。借助 Playwright 和 Storybook,现在可以在一个地方直观地检查组件并收到更改通知。

示例

storybook-addon-playwright-example

入门

所需软件包

  • storybook-addon-playwright
  • @storybook/addon-knobs
yarn add storybook-addon-playwright @storybook/addon-knobs --dev

配置

.storybook/main.js

module.exports = {
  stories: ['../**/*.stories.[tj]sx'],
  addons: [
    '@storybook/addon-knobs/register',
    'storybook-addon-playwright/preset',
    'storybook-addon-playwright/register',
  ],
};

如果 Storybook 抱怨它无法在 @storybook/addon-knobs/register 路径中找到 register,则可能需要将其指向目标文件夹:@storybook/addon-knobs/dist/register

.storybook/main.js.storybook/middleware.js

const { setConfig } = require('storybook-addon-playwright/configs');
const playwright = require('playwright');

(async () => {
  let browser = {
    chromium: await playwright['chromium'].launch(),
    firefox: await playwright['firefox'].launch(),
    webkit: await playwright['webkit'].launch(),
  };
  setConfig({
    storybookEndpoint: `https://127.0.0.1:6006/`,
    getPage: async (browserType, options) => {
      const page = await browser[browserType].newPage(options);
      return page;
    },
    afterScreenshot: async (page) => {
      await page.close();
    },
  });
})();

.storybook/middleware.js

const middleware = require('storybook-addon-playwright/middleware');
module.exports = middleware;

setConfig 选项

  • storybookEndpoint
  • enableMigration
  • beforeScreenshot
  • afterScreenshot
  • beforeStoryImageDiff
  • afterStoryImageDiff
  • beforeFileImageDiff
  • afterFileImageDiff
  • beforeAllImageDiff
  • afterAllImageDiff
  • pageGotoOptions
  • afterUrlConstruction
  • afterNavigation
  • releaseModifierKey
  • screenshotOptions
  • theme

storybookEndpoint

storybookEndpoint 必须与 Storybook 的 ip/端口匹配。

对于 Docker 和非本地浏览器,需要 Storybook 的公网 IP 地址。

enableMigration

如果设置为 true,将对插件生成的 json 文件应用更改,在“迁移”部分了解更多信息。

beforeScreenshot

在截取屏幕截图之前调用,用于操作页面。

afterScreenshot

在截取屏幕截图后调用。

afterStoryImageDiff

在对整个应用程序屏幕截图运行图像差异测试之前/之后调用。

beforeStoryImageDiff/afterStoryImageDiff

在对特定 Story 运行图像差异测试之前/之后调用。

beforeFileImageDiff/afterFileImageDiff

在对特定文件运行图像差异测试之前/之后调用。

beforeAllImageDiff/afterAllImageDiff

在所有 Story 屏幕截图的 imageDiff 过程完成后调用。

pageGotoOptions

请参考 Playwright API page.goto 选项

afterUrlConstruction

在 page.goto 之前调用,可用于操作 url。

afterNavigation

页面导航到 Story 时调用。

releaseModifierKey

当设置为 true 时,将在截取屏幕截图后,对修饰键(Shift、Meta、Control 或 Alt)执行键盘.up 操作。

screenshotOptions

截取屏幕截图时应用的默认选项。

theme

它覆盖了插件的默认主题。它是一个类型为 Material-UI Theme 对象的 json

.
.
.
setConfig({
  theme: {
    palette: {
      primary: {
        main: '#0052cc',
      },
      secondary: {
        main: '#edf2ff',
      },
    },
  }
});
.
.
.

工作原理

此插件基本上是 Playwright 和 Storybook Story 之间的接口。插件在配置中提供的页面上执行用户指令。

用户创建的指令将保存在 Story 文件旁边的 json 文件中,因此在下一次加载时可用。

此插件由三个部分组成

  • 操作列表面板
  • 屏幕截图列表面板
  • 屏幕截图预览面板

操作列表面板

操作面板就像一个游乐场,它包含用户为特定 Story 创建的操作集列表,并在选中时在浏览器页面中执行。

一个操作集可以包含一个或多个操作。

操作指的是 Playwright 页面方法,例如 click、mouse move 等。

屏幕截图列表面板

此面板保存用户之前截取的屏幕截图,在这里您可以管理屏幕截图,例如删除、编辑或排序屏幕截图。

屏幕截图预览面板

预览面板显示 Playwright 最近截取的屏幕截图,它可以选择性地显示 Playwright 支持的所有或部分浏览器。

在这里,您可以保存和更改屏幕截图设置,例如宽度、高度等。

屏幕截图保存在 Story 文件夹下的名为 __screenshots__ 的文件夹中。

添加或扩展 Playwright 页面方法

要添加或扩展 Playwright 方法,setConfig 方法中提供了以下属性

  • customActionSchema

customActionSchema

此属性使开发人员能够向 Playwright 页面添加新方法。customActionSchema 属性中的每个条目都将出现在“操作”面板下的“添加操作”菜单中。

此属性遵循 json-schema 规则,并添加了一个名为 parameters 的属性,因此需要清楚地了解 json-schema

以下是如何向页面添加框的示例

//async function addBox(this: Page, position: { x: number; y: number })
async function addBox(position) {
  await this.evaluate((pos) => {
    if (!pos) return;
    const div = document.createElement('div');
    div.style.backgroundColor = '#009EEA';
    div.style.width = '200px';
    div.style.height = '200px';
    div.style.position = 'absolute';
    div.style.top = pos.y + 'px';
    div.style.left = pos.x + 'px';
    document.body.append(div);
  }, position);
}

(async () => {
  let browser = {
    chromium: await playwright['chromium'].launch(),
    firefox: await playwright['firefox'].launch(),
    webkit: await playwright['webkit'].launch(),
  };
  setConfig({
    storybookEndpoint: `https://127.0.0.1:6006/`,
    getPage: async (browserType, options) => {
      const page = await browser[browserType].newPage(options);
      page.addBox = addBox;
      return page;
    },
    afterScreenshot: async (page) => {
      await page.close();
    },
    customActionSchema: {
      addBox: {
        type: 'promise',
        parameters: {
          position: {
            type: 'object',
            properties: {
              x: {
                type: 'number',
              },
              y: {
                type: 'number',
              },
            },
            required: ['x', 'y'],
          },
        },
      },
    },
  });
})();

其他页面方法

以下自定义方法已添加到 Playwright 页面

  • clearInput,
  • mouseDownOnSelector
  • mouseMoveToSelector
  • setSelectorSize
  • scrollSelector
  • dragDropSelector
  • takeScreenshot
  • takeScreenshotOptions
  • selectorMouseWheel
  • mouseFromTo

clearInput

此方法使用 selector 获取元素,等待可操作性检查,聚焦元素,清除它并触发输入事件。

mouseDownOnSelector

此方法使用 selector 获取元素,并在选择器中心执行 mousedown 操作。

mouseMoveToSelector

此方法使用 selector 获取元素,并将鼠标移动到选择器中心。

setSelectorSize

此方法使用 selector 获取元素,并设置选择器的宽度或高度。

scrollSelector

此方法使用 selector 获取元素,并设置选择器的 scrollLeft 和 scrollTop。

dragDropSelector

此方法使用 selector 获取元素,并将其移动到用户指定的位置。

takeScreenshot

此方法将在操作之间截取屏幕截图,这对于按顺序截取事件/操作的屏幕截图很有用。最后,屏幕截图将与最终屏幕截图合并。

takeScreenshotOptions

此操作的目的是为所有屏幕截图提供集中选项。此操作只能与 takeScreenshot 操作结合使用。只能使用一个实例。

selectorMouseWheel

此方法使用 selector 获取元素,并分派 WheelEvent。

mouseFromTo

此方法将执行鼠标按下、移动和从到选定位置抬起操作。

测试

使用插件保存的屏幕截图也可以使用 Jest 等测试框架进行测试。为此,请按如下方式配置 Jest

将设置文件添加到 jest.config.js

module.exports = {
  setupFilesAfterEnv: ['./jest.setup.js'],
};

jest.setup.js

const playwright = require('playwright');
const { setConfig } = require('storybook-addon-playwright/configs');
const { toMatchScreenshots } = require('storybook-addon-playwright');

expect.extend({ toMatchScreenshots });

let browser = {};

beforeAll(async () => {
  browser = {
    chromium: await playwright['chromium'].launch(),
    firefox: await playwright['firefox'].launch(),
    webkit: await playwright['webkit'].launch(),
  };
  setConfig({
    storybookEndpoint: `https://127.0.0.1:6006/`, // or  `./storybook-static`
    getPage: async (browserType, options) => {
      const page = await browser[browserType].newPage(options);
      return page;
    },
    afterScreenshot: async (page) => {
      await page.close();
    },
  });
});

afterAll(async () => {
  const promises = Object.keys(browser).map((browserType) =>
    browser[browserType].close(),
  );
  await Promise.resolve(promises);
});

并在测试文件中

describe('test screenshots', () => {
  it('should pass image diff', async () => {
    await expect('*').toMatchScreenshots();
  }, 10000);
});

或使用 toMatchImageSnapshot

const { getScreenshots } = require('storybook-addon-playwright');

describe('test screenshots manually', () => {
  it('should pass image diff', async () => {
    await getScreenshots({
      onScreenshotReady: (screenshotBuffer, baselineScreenshotPath) => {
        expect(screenshotBuffer).toMatchImageSnapshot({
          customSnapshotIdentifier: baselineScreenshotPath.screenshotIdentifier,
          customSnapshotsDir: baselineScreenshotPath.screenshotsDir,
        });
      },
    });
  }, 10000);
});

确保为您的测试设置了适当的超时时间。

TypeScript

如果您的编辑器无法识别 toMatchScreenshots 匹配器,请在您的项目中添加一个 global.d.ts 文件,其中包含

import 'storybook-addon-playwright';

迁移

由于新功能,插件生成的 json 文件的结构可能会发生变化,要修复并应用更改,请在 setConfig 中将 enableMigration 设置为 true 并运行 Storybook。

确保迁移后将 enableMigration 设置为 false