Обеспечение того, чтобы ваше приложение выглядело так, как вы ожидаете на разных устройствах, является важной частью обеспечения качественного опыта для всех ваших пользователей. Визуальный снимок автоматизирует этот процесс, и, к счастью, его довольно легко настроить с помощью Storybook. Существует множество инструментов, которые предоставляют визуальные снимки прямо из коробки - некоторые из них имеют открытый исходный код, некоторые из них являются платными. В этом посте будет показано, как визуально делать снимки ваших историй с помощью портов просмотра различных устройств, используя внутренние addon-storyshots и addon-storyshots-puppeteer (это было сделано не так давно Thomas BERTET 💪). Последний пример можно найти в этом репо.

Здесь я предполагаю, что вы знакомы с Storybook и имеете базовые знания о его концепциях. Чтобы этот пост был понятен широкому кругу разработчиков внешнего интерфейса, я буду использовать пакет the@storybook/html, который поставляется с версией 4 (на момент написания это v4.0.0-alpha.18), так что там нет необходимости понимать фреймворки, такие как _4 _ / _ 5_ или другие.

У Storybook есть специальный аддон - addon-viewport, который позволяет отображать различные порты просмотра в пользовательском интерфейсе Storybook. Этот аддон «имитирует» порт просмотра реального устройства, изменяя размер области предварительного просмотра в Storybook, поскольку Storybook - это веб-приложение, которое запускается в браузере, мы немного ограничены в том, что мы можно изменить в браузере. С кукольником мы можем управлять конфигурацией браузера из среды тестирования.

Настройка сборника рассказов

Прежде всего, нам нужно установить все необходимые зависимости, чтобы показывать наши простые html-истории. Я буду использовать yarn, но, конечно, все достижимо и с npm:

yarn add -D @storybook/html@alpha babel-loader @babel/core

Обратите внимание, что мы устанавливаем babel-loader и babel/core, поскольку сборник рассказов зависит от них (для babel-loader v8 требуется babel/core)

Теперь давайте создадим config.js в каталоге .storybook:

import { configure } from '@storybook/html';

function loadStories() {
  require('../src/story');
}

configure(loadStories, module);

Наша конфигурация загрузит один файл истории с именем story.js из каталога src. Эта история будет содержать несколько простых примеров HTML:

import { storiesOf } from '@storybook/html';

import './style.css';

storiesOf('Example', module)
  .add('Div', () => {
    return '<div>Here is a first story in div</div>';
  })
  .add('Button', () => {
    return '<button>Button</button>';
  })
  .add('Table', () => {
    return `<table>
              <tr>
                <td>td</td>
                <td>td</td>
              </tr>
              <tr>
                <td>td</td>
                <td>td</td>
              </tr>
            </table>`;
  });

style.css, импортированный туда, будет использовать медиа-запросы, чтобы создать впечатление о разных стилях для разных портов просмотра (на основе https://css-tricks.com/snippets/css/media-queries-for-standard-devices ):

/* ----------- iPhone 5, 5S, 5C and 5SE ----------- */
@media only screen
  and (min-device-width: 320px)
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2) {

  body {
    background-color: #33800b;
  }
}

/* ----------- iPhone 6, 6S, 7 and 8 ----------- */
@media only screen
  and (min-device-width: 375px)
  and (max-device-width: 667px)
  and (-webkit-min-device-pixel-ratio: 2) {

  body {
    background-color: #408053;
  }
}

/* ----------- iPhone 6+, 7+ and 8+ ----------- */
@media only screen
  and (min-device-width: 414px)
  and (max-device-width: 736px)
  and (-webkit-min-device-pixel-ratio: 3) {

  body {
    background-color: #4c8004;
  }
}

/* ----------- iPad 1, 2, Mini and Air ----------- */
@media only screen
  and (min-device-width: 768px)
  and (max-device-width: 1024px)
  and (-webkit-min-device-pixel-ratio: 1) {

  body {
    background-color: #7a8023;
  }
}

Как видите, мы раскрасим фон тела в разные цвета в соответствии с согласованным медиа-запросом.

Теперь нам нужно добавить следующие сценарии в package.json, чтобы иметь возможность запускать Storybook в режимах разработки и производства:

"scripts": {
  "storybook": "start-storybook -p 8008",
  "build-storybook": "build-storybook -o ./public"
}

После выполнения описанных выше действий у вас должна получиться следующая структура файлов:

project/
  .storybook/
    config.js
  src/
    story.js
    style.js
  node_modules/
  package.json
  yarn.lock

Если до этого момента все шло хорошо, yarn storybook покажет нам этот простой пример (переход по адресу http: // localhost: 8008 в Chrome):

Попробуем сымитировать устройства с помощью Chrome и посмотреть, какие цвета появятся. Откройте DevTools и используйте инструмент Device Emulation для эмуляции различных устройств. Соответствующий медиа-запрос запускается, чтобы раскрасить тело запрошенным цветом. И это поведение также может быть достигнуто с помощью API кукольника.

Настройка Jest + Storyshots

Теперь давайте настроим jest, чтобы иметь возможность запускать наши визуальные снимки. Прежде всего, нам нужно добавить следующие пакеты:

  • jest - тестовый раннер от Facebook
  • babel-jest, @babel/preset-env, [email protected] - вся эта вавилонская штука, чтобы можно было запускать ES6 в шутку
  • @storybook/addon-storyshots - аддон, объединяющий сборник рассказов для шуток
  • @storybook/addon-storyshots-puppeteer — интеграция сборника рассказов в puppeteer + jest-image-snapshot

И все это вместе:

yarn add -D jest [email protected] babel-jest @babel/preset-env @storybook/addon-storyshots@alpha @storybook/addon-storyshots-puppeteer@alpha

Теперь нам нужно добавить jest.confg.js:

module.exports = {
  cacheDirectory: '.cache/jest',
  clearMocks: true,
  moduleNameMapper: {
    '\\.(css|scss)$': '<rootDir>/styleMock.js',
  },
  roots: [
    '<rootDir>/src',
  ],
  transform: {
    '^.+\\.jsx?$': 'babel-jest',
  },
  testEnvironment: 'jsdom',
  moduleFileExtensions: ['js', 'jsx', 'json'],
};

Обратите внимание, здесь я высмеиваю файлы css - это не очень важно, поскольку мы не используем jsdom в этом примере, поэтому для его работы просто добавьте пустой styleMock.js файл на корневой уровень.

А вот и простой файл .babelrc:

{
  "presets": [
    "@babel/preset-env"
  ]
}

На последнем этапе тестирования нам нужно добавить скрипт в package.json (честно говоря, он в основном нужен пользователям npm):

"scripts": {
  "storybook": "start-storybook -p 8008",
  "build-storybook": "build-storybook -o ./public",
  "test": "jest"
}

Моментальные снимки изображений можно настроить как для Storybook, работающего в режиме разработки, так и для статической сборки (режим prod). Мы уже настроили сценарий build-storybook для создания статической версии Storybook в каталоге public, поэтому мы будем использовать этот режим для нашего теста (я также считаю, что это лучшее решение для CI, вместо того, чтобы запускать сервер разработки во время сборки, но используйте -корпуса могут отличаться). Чтобы создать статическую сборку, выполните следующую команду:

yarn build-storybook

Теперь давайте создадим наш imageSnapshots.test.js в каталоге src. Шаги выше должны были быть организованы в следующую структуру файлов:

project/
  .storybook/
    config.js
  public/
  src/
    imageSnapshots.test.js
    story.js
    style.js
  node_modules/
  .bablerc
  jest.config.js
  styleMock.js
  package.json
  yarn.lock

Для логики начального теста мы добавим imageSnapshot тестовый метод в storyshots:

import path from 'path';
import initStoryshots from '@storybook/addon-storyshots';
import {imageSnapshot} from '@storybook/addon-storyshots-puppeteer';
const storybookUrl = path.resolve('public');
initStoryshots({
  framework: 'html',
  suite: 'Image storyshots',
  test: imageSnapshot({
    storybookUrl,
  })
});

Как вы можете видеть выше, мы добавили storybookUrl в расположение файла нашей статически экспортированной Storybook. Кроме того, framework настроен на html, поскольку мы используем пример html, но для React / Angular / Whatever-supported следует использовать соответствующую альтернативу.

В настоящее время запущенная команда test с yarn test будет генерировать снимки изображений для стандартного режима предварительного просмотра:

Мы хотим автоматизировать создание визуальных снимков для разных устройств. Для этого мы можем использовать DeviceDescriptors модуль, который поставляется с кукольником. Этот файл поставляется с огромным количеством предварительно настроенных портов просмотра, которые мы можем использовать для эмуляции устройства (как мы сделали это с помощью Chrome devtools). В этом примере мы ограничим список устройств следующим: iPad, iPhone 5, iPhone 6 и iPhone 7 Plus. Чтобы сымитировать Chrome без головы на устройстве, мы будем использовать параметр customizePage в imageSnapshot.

import path from 'path';
import pupDevices from 'puppeteer/DeviceDescriptors';
import initStoryshots from '@storybook/addon-storyshots';
import {imageSnapshot} from '@storybook/addon-storyshots-puppeteer';

const storybookUrl = path.resolve('public');
const supportedDevices = new Set(['iPad', 'iPhone 5', 'iPhone 6', 'iPhone 7 Plus']);

function createCustomizePage(pupDevice) {
  return function(page) {
    return page.emulate(pupDevice);
  }
}

for (let supportedDevice of supportedDevices) {
  const pupDevice = pupDevices[supportedDevice];

  if (!pupDevice) {
    continue;
  }

  const customizePage = createCustomizePage(pupDevice);

  initStoryshots({
    framework: 'html',
    suite: `Image storyshots: ${pupDevice.name}`,
    test: imageSnapshot({
      storybookUrl,
      customizePage,
    })
  });
}

Как вы можете видеть в изменениях выше, мы перебираем определение разрешенных устройств и инициализируем сторишоты на каждой итерации с разными экземплярами устройства.

Давайте теперь снова yarn test, чтобы увидеть разницу:

Заглянув в сгенерированные изображения, мы можем увидеть разницу в цветах и ​​размерах 😎:

Для финального проекта вы можете заглянуть в репо это. Он также включает изменения, необходимые для группировки каждого устройства в его собственный каталог моментальных снимков, но для целей этой публикации это менее актуально.

Обобщая сказанное выше, я надеюсь, что мне удалось открыть вам еще одну мощную сторону Storybook, которая укрепит вашу кодовую базу. Как обычно, наши ссылки: