Storybook Play function とは
この記事では、Storybook の Play function について話をします。
Play function とは
Play functions are small snippets of code executed after the story renders. Enabling you to interact with your components and test scenarios that otherwise required user intervention.
https://storybook.js.org/docs/writing-stories/play-function
Play function は ユーザ操作を含む Story の定義を可能とする機能 と言えると思います。
Story は何かというと、
A story captures the rendered state of a UI component. Developers write multiple stories per component that describe all the “interesting” states a component can support.
https://storybook.js.org/docs/get-started/whats-a-story
コンポーネントがサポートできる ”興味深い状態” のキャプチャです。
これらを合わせて整理すると、Play function は
「コンポーネントがサポートしている、ユーザ操作を含む興味深い状態 の定義」を可能にする機能です。
例
Only Available という Story にて Play function が使われています。
ここでは、「ユーザがチェックボックスをクリックした後の、ストックのある商品が表示された状態」を Story として定義しています。
(画像のキャプチャ元: https://www.youtube.com/clip/Ugkx9Yn1tl4Nm33eAD6kTD2svqYLq8QA0YP7 3:46時点)
テストで再利用
Play function は、ユーザ操作を含む興味深い状態 を定義できる機能だとすると、
Play function によって定義される状態は、テストにて動作と結果が保証されるべき状態であり、テストケースとしての再利用 に適したものであると言えると思います。
テストの実装上でも、Story を簡単に再利用する仕組みが用意されています。
以下の例では、 React のテストにて Play function を再利用しています。
// テキストボックスに入力をする Story を定義
export const InputFieldFilled: Story<InputFieldProps> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByRole('textbox'), 'Hello world!');
},
};
const { InputFieldFilled } = composeStories(stories);
test('renders with play function', async () => {
// 👇 上記 Story を再利用
const { container } = render(<InputFieldFilled />);
await InputFieldFilled.play({ canvasElement: container });
const input = screen.getByRole('textbox') as HTMLInputElement;
expect(input.value).toEqual('Hello world!');
});
Play function のテストでの再利用のメリットとしては、視覚的なフィードバックを受けながらテストを書く事が可能になる ことが挙げられます。通常、testing-library 等で記述するテストは、視覚的なフィードバックなしに、トライ & エラーを繰り返しながら実装していく大変な作業です。
実装の詳細は、以下をご参照ください。(上記コードも以下リンク先より引用しています)
まとめ
- Play function は ユーザ操作を含む興味深い状態 を Story として定義することを可能にした
- テストに再利用可能 であり、Storybook でも、テストでもメリットを享受できる機能
- 実装は簡単
この記事を読んでいただきありがとうございました!
Happy coding! 🚀
おまけ
Play function を試す中ででつまずいた点を共有します。
Interactions タブに interactions が表示されない
// 🙅🏻: `@testing-library/react` の `within` と `userEvent` を使っていた
import { userEvent, within } from '@testing-library/react';
// 🙆: `@storybook/testing-library` を使う
import { userEvent, within } from '@storybook/testing-library';
参考:
Cannot invoke an object which is possibly 'undefined'.
export const CombinedStories: Story = {
play: async (context) => {
// 🙅🏻: Error: Cannot invoke an object which is possibly 'undefined'.
await FirstStory.play(context);
await SecondStory.play(context);
// 🙆
await FirstStory.play?.(context);
await SecondStory.play?.(context);
},
};
参考:
Pass a context when invoking play function of another story
export const CombinedStories: Story = {
// 🙅🏻
play: async ({canvasElement}) => {
// Error: `Pass a context when invoking play function of another story`
await FirstStory.play?.(canvasElement);
}
// 🙆: context をそのまま渡す必要あり
play: async (context) => {
await FirstStory.play?.(context);
}
};
参考:
If you want to read this article in English, here is the link: https://dev.to/takuyakikuchi/what-is-storybook-play-function-2bmb
Discussion