はじめに

Storybookとの統合は、Reactで現在利用可能な@nightwatch/storybookプラグインを介して、v2.4以降のNightwatchに含まれています。

Storybookは、Reactの安定したユーザーベースと、Vue、Svelte、Angular、その他のUIライブラリの成長するコミュニティを持つ、現在最も人気のあるコンポーネントライブラリと言えるでしょう。世界中の何千ものチームが、再利用可能なコンポーネントでUIを構築するために使用しています。

Storybookの人気の理由は、今日のフロントエンド開発者が持つ本質的なニーズの1つを特定したことにあります。それは、最新の複雑なUIを構築するためには、フロントエンドチームがUIコンポーネントを個別に設計、構築、テストできる必要があるということです。Storybookはこの手法をコンポーネント駆動開発(CDD)と呼んでいます。

コンポーネント駆動開発

Storybookは、ドキュメント化とテストのための魅力的なツールを提供することにより、大規模なコンポーネントライブラリを管理するための完全なソリューションを提供することを目指しています。各コンポーネントは、さまざまな状態と要件で個別にレンダリングされます。

さまざまなプロパティと引数を持つコンポーネントの各表現はストーリーと呼ばれ、特定のコンポーネントに属するすべてのストーリーは、.stories.js[x](または.ts[x])ファイルに収集されます。ストーリーは宣言的な構文で記述され、各ストーリーは特定のプロップとモックデータセットを使用してコンポーネントをレンダリングすることにより、特定のコンポーネントのバリエーションをシミュレートします。

基本的なコンポーネントストーリーの例を次に示します

// Histogram.stories.js|jsx

import React from 'react';

import { Histogram } from './Histogram';

export default {
  title: 'Histogram',
  component: Histogram,
};

const Template = (args) => <Histogram {...args} />;

export const Default = Template.bind({});
Default.args = {
  dataType: 'latency',
  showHistogramLabels: true,
  histogramAccentColor: '#1EA7FD',
  label: 'Latency distribution',
};

Storybookは、既存のストーリーを読み込んで、コンポーネントのドキュメント化とテストのための統合ツールを備えた洗練された管理インターフェースに表示するNode Webアプリケーションとして配布されます。

Storybookアプリはローカルで実行することも、Webサーバーにデプロイすることもできます。実際、多くの人気のあるコンポーネントライブラリは公開されています

コンポーネントストーリーをテストする方法

現在、Storybookは主に、フロントエンド開発者とデザイナーがUIコンポーネントやページ全体をドキュメント化するための優れたコラボレーションツールとして使用されています。

テストが登場してすべてが複雑になるまでは、すべて順調です。UIコンポーネントを効果的に開発するには、それらを簡単にテストするための効率的な方法も必要になるため、本当の楽しみはそこから始まります。

指定されたプロップとモックデータですべてのストーリーが正しくレンダリングされることを保証するには、コンポーネントの自動テストが必要です。さらに、より複雑なコンポーネントには、ユーザーアクションのシミュレーションと動作の検証が必要なストーリーが含まれる場合があります。

Storybookは、すぐに使えるコンポーネントのいくつかのテスト戦略をサポートしています

画像クレジット:https://storybook.js.org/docs/react/writing-tests/introduction

そのため、テストのための組み込みサポートといくつかのCLIテストランナーがありますが、これは内部でPlaywrightJestを使用しています。しかし、その強みは、Webアプリケーション開発全体のスペクトルの設計側にあります。

構築するコンポーネントのコード品質に強い自信を持つためには、コンポーネントテストの追加サポートでストーリーを拡張するか、ストーリーを他のテストにインポートする必要があります。

StorybookにNightwatchを統合する

Nightwatchは、UIコンポーネントの設計+開発とQA+テストのギャップを埋めるのに役立ちます。Storybook環境でコンポーネントの自動テストのサポートを追加するには、Nightwatchをインストールし、既存のストーリーにテスト機能を追加するだけです。

次のセクションでは、まさにそれを試みます。Githubで公開されている既存のStorybookを取得し、次の手順を実行します

  1. Nightwatchをインストールして構成する
  2. Nightwatchで既存のコンポーネントストーリーを実行する
  3. 複雑なストーリーの1つを拡張し、Nightwatchテスト機能を追加する

それでは始めましょう。世界食糧計画のUiキットを使用します。これは、Githubのhttps://github.com/wfp/designsystemにあります。

wfp.org/UIGuide

依存関係をインストールする

最初にプロジェクトをフォークする必要があります。私のフォークはgithub.com/pineviewlabs/wfp-designsystemにあります。プロジェクトをローカルにクローンし、既存の依存関係をインストールします

--legacy-peer-depsフラグは、古い依存関係をインストールするために必要です。npm auditメッセージが表示された場合は、これはチュートリアルなので、今のところ無視してください。

Storybookを実行する

すべてがインストールされたら、Storybookをローカルで実行してみてください。そのためには、次を実行します

npm run storybook

これにより、Storybookファイルがビルドされ、プロジェクトがローカルホストで実行されます。

このガイドを書いている時点では、WFPデザインキットで使用されているStorybookのバージョンは6.2です。少なくともv6.5にアップグレードしようとすることができますが、私はすでにそれを試してみて、たくさんのエラーが発生したので、今のところはすべてを一緒に動作させるために6.2を使い続けています。

Nightwatchをインストールする

Nightwatchは、1つのコマンドでインストールできます

npm init nightwatch@latest

これにより、ブラウザとテストファイルの場所をインストールするように求められます。Chrome、Firefox、Safari、Edgeのすべてを選択しました。Edgeは別途インストールする必要がありますが、initツールはNightwatchに必要な設定を生成します。

ソースフォルダーの場所には、次を入力します

src/components/**/*.stories.js

ベースURLには、次を入力します

http://localhost:9000/

また、Nightwatch用のStorybookプラグインもインストールする必要があります

npm install @nightwatch/storybook

新しいNPMバージョンを使用している場合は、再び--legacy-peer-depsを渡す必要がある場合があります。

最後の手順は、Nightwatchでプラグインを読み込んで構成することです。そのためには、nightwatch.conf.jsを編集し、src_foldersの直後に次を追加します

// nightwatch.conf.js
module.exports = {
  // .. other settings
  plugins: ['@nightwatch/storybook'],

  '@nightwatch/storybook': {
    start_storybook: false, 
    storybook_url: 'http://localhost:9000/'
  }
}

Nightwatchはテスト実行中にStorybookを自動的に起動/停止できますが、この特定のバージョンのStorybookの読み込みには少し時間がかかるため、start_storybookfalseのままにして、手動で実行します。

Nightwatchでストーリーを実行する

@nightwatch/storybookプラグインは、ユーザーがテストでストーリーをインポートすることを期待する代わりに、コンポーネントテストを既存のコンポーネントストーリーの上に追加できるように設計されています。NightwatchをStorybookで使用する場合、テストは.storiesファイル自体です。

この場合、既存のsrc/component/**/*.stories.jsファイルをテストとして実行します。ただし、それを行う前に、最後にもう1つ設定する必要があります。

esbuildをカスタマイズする

NightwatchはJSXファイルを解析するために内部でesbuildを使用しており、このプロジェクトにはesbuildがデフォルトでサポートしていない構文機能がいくつか含まれています。ただし、babelを使用したコンパイルを有効にすることで、それらをサポートできます。

そのためには、nightwatch.conf.jsを編集し、'@nightwatch/storybook'が含まれている行の直後に次を追加します

// nightwatch.conf.js
module.exports = {
  // other settings here...
  esbuild: {
    babel: {
      filter: /\.[cm]?js$/
    }
  }
}

Babelを追加する

esbuildが見つけられるように、次の内容のbabel.config.jsonファイルを追加します

{
  "exclude": ["node_modules/**"],
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-proposal-export-namespace-from",
    "@babel/plugin-proposal-export-default-from"
  ]
}

Nightwatchを実行する

いよいよコンポーネントストーリーを実行するときが来ました。このプロジェクトには、すでにJestで記述され、Enzymeを使用するユニットレベルのコンポーネントテストがいくつかあります。それらには触れずに、実際の.storiesファイルを操作します。

Chromeを使用して探索的なテストランを実行し、ブラウザを観察できるように--serialモードで実行します

npx nightwatch --env chrome --serial

プロジェクト内のすべてのストーリーをシリアルモードで実行するには、時間がかかる場合があります。しかし、並列で実行してみることもできます(インストールされているブラウザに応じて、「chrome」を「firefox」、「safari」、または「edge」に置き換えることができます)。

デフォルトでは、CPUコアの数に基づいてテストワーカーの数が選択されますが、ここでは4に設定します。また、ブラウザがポップアップして邪魔にならないように、--headlessモードで実行します(ヘッドレスモードはSafariでは使用できません)

npx nightwatch --env chrome --workers=4 --headless

これで、うまくいけば、WFP StorybookプロジェクトにNightwatchがインストールされているはずです。現時点では、ストーリーはレンダリングされるだけで、Nightwatchはすべてが正常であることを確認するための可視性チェックを実行します。.

次のステップは、ストーリーの1つを拡張し、Nightwatch固有の機能を追加することです。

Nightwatchテストでストーリーを拡張する

プロジェクトの大規模なストーリーの1つを選択し、追加のテスト機能を追加し始めます。src/components/Form/Form.stories.jsは複雑さの点で適切な候補のようですので、それを使用します。

最初に個別に実行してみましょう

npx nightwatch src/components/Form/Form.stories.js -e chrome

ファイルには4つの異なるストーリーがあり、出力は次のようになります

[Form.stories.js component] Test Suite
───────────────────────────────────────────────────────────────────────
  Using: chrome (107.0.5304.110) on MAC OS X.


  Running "Default" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--default.Default" story was rendered successfully.

  ✨ PASSED. 1 assertions. (1.078s)

  Running "Detailed Form" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--detailed-form.DetailedForm" story was rendered successfully.

  ✨ PASSED. 1 assertions. (583ms)

  Running "Login" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--login.Login" story was rendered successfully.

  ✨ PASSED. 1 assertions. (411ms)

  Running "Contact" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--contact.Contact" story was rendered successfully.

  ✨ PASSED. 1 assertions. (513ms)

  ✨ PASSED. 4 total assertions (5.768s)

Nightwatchファイルアップロードテストを追加する

Nightwatchは、Storybookのインタラクションテストアクセシビリティテストを実行でき、hooks apiもサポートしています。ただし、この記事では、Nightwatchがコンポーネントストーリーフォーマットに追加するtest()関数のみを検討します。Storybookの統合については、Nightwatchのドキュメントをご覧ください。

DetailedFormストーリーを拡張するので、src/components/Form/Form.stories.jsを編集し、export const Loginの行の直前に次を追加します(400行目付近)。

💡
test()関数はNodeコンテキストで実行されるため、ブラウザサンドボックスによって制限されるplay()関数とは異なり、ファイルシステムにアクセスできます。

test()関数は、Nightwatch browser API(コマンドの発行とアサーションの実行に使用できる)と、ストーリーのルート要素を指し、Nightwatchコマンドとアサーションと互換性のあるcomponentオブジェクトの両方を受け取ります。

この例では、Nightwatch uploadFile() apiコマンドを使用してファイルのアップロードをシミュレートし、ファイルドロップゾーン要素が更新されたかどうかを確認します。

// src/components/Form/Form.stories.js
DetailedForm.test = async (browser, {component}) => {
  await browser.uploadFile('input[type="file"]', require.resolve('./README.mdx'));

  const fileElementContainer = await browser.findElement(`.${settings.prefix}--file-container`);
  const elementHasDescendants = await browser.hasDescendants(fileElementContainer);
  
  await browser.strictEqual(
    elementHasDescendants, true, 'The file dropzone element has been populated.'
  );
};

また、ファイルの先頭にこのインポートを追加します

// src/components/Form/Form.stories.js
import settings from '../../globals/js/settings';

これで、DetailedFormストーリーのみを実行できます

npx nightwatch src/components/Form/Form.stories.js -e chrome --story=DetailedForm

うまくいけばすべて順調で、出力は次のようになります(上部にいくつかの警告が表示される場合がありますが、それらは重要ではありません)

[Form.stories.js component] Test Suite
───────────────────────────────────────────────────────────────────────
  Using: chrome (107.0.5304.110) on MAC OS X.


  Running "Detailed Form" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--detailed-form.DetailedForm" story was rendered successfully.
  ✔ Passed [strictEqual]: The file dropzone element has been populated.

  ✨ PASSED. 2 assertions. (1.217s)

結論

今のところこれでほぼ終わりです。別の公開 Storybook ライブラリを使用して、同様の投稿をさらに書くかもしれません。あるいは、皆さんが寄稿したいと思うかもしれません。Storybook の素晴らしい人たちは最近、コンポーネント百科事典に公開 Storybook ライブラリのコレクションをまとめ始めたので、実験したり、オープンソースに貢献したりする機会がたくさんあります。

チュートリアルをここまで進めた方は、敬礼いたします!あなたは今、国連で使用されているオープンソースプロジェクトに貢献する準備ができています。これは素晴らしいことです。

読んでいただきありがとうございます。屋内外を問わず、お好きなように帽子をかぶることをお忘れなく。