StorybookのInteractionテストでcreatePortalした要素にアクセスできない時の対処法
最終更新日: 2024-04-08記事投稿日: 2024-04-08
概要
storybookのplay関数を使用したり
- @storybook/addon-interaction
- @storybook/test-runner
- @storybook/test
などのaddonを導入することで、実際のブラウザ上での挙動のテストを実行することができます。(このセットアップや運用方法については、本記事では解説しません。)
今回は以下のようなテストを書いていました。
import { within, userEvent, expect } from '@storybook/test'
export default {
title: 'Component/Review',
component: Review
}
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await delay(1000)
await userEvent.click(canvas.getByRole('button', { name: 'すべてのレビュー' })) // この操作でレビューが見れるモーダルがオープンする。
await delay(1000)
await userEvent.click(canvas.getByRole('button', { name: '不正なレビューを報告する' })) // ここで押下するボタンはモーダル内に存在する。今回はこの操作がうまく動かなかった。
}
}
原因
今回の原因は、userEvent.click(canvas.getByRole('button', { name: 'すべてのレビュー' }))
この処理で開かれるモーダルがReactのcreatePortalを使用していたことでした。
とはいえ、createPortal
を使用していると必ず、この不具合が起きるのかというとそういうわけではありません。
この不具合が起きる条件を理解するにはまず、storyがどのように描画されているのかを知る必要があります。Storyは以下のようにiframeを利用して描画されています。その中にhtml
, head
, body
があり、<div id='storybook-root'>
の中に設定したStoryの内容が描画されている形になります。
次に、const canvas = within(canvasElement)
この部分に注目してみます。この部分が何をしているのかというと、#storybook-rootの要素を取得しています。つまり、canvasに入ってくるのは<div id='storybook-root'>
になり、これに対してgetByRole
をしたり、userEvent.click
をしていることになります。
お気づきの方もいらっしゃるかもですが、createPortal(<Modal />, document.body)
で描画されるModalはこのcanvasの外の要素になります。それゆえにモーダル内の要素にアクセスできなかったようです👀
つまり<div id='storybook-root'>
外の要素にはアクセスできないというのが正しそうです。
解決方法
この問題はすでにissueが上がっているようでした。(2024年4月8日現在未解決)
その中のコメントにある方法を使用して以下のように修正したところ正常にPASSするテストになりました!
import { within, userEvent, expect }
export default {
title: 'Component/Review',
component: Review
}
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await delay(1000)
await userEvent.click(canvas.getByRole('button', { name: 'すべてのレビュー' }))
const body = within(canvasElement.ownerDocument.body)
await delay(1000)
await userEvent.click(body.getByRole('button', { name: '不正なレビューを報告する' }))
}
}