📝

VRT(storycap, reg-suit, Next.js)を安定させるためのTips

2023/11/06に公開

はじめに

社のフロントエンド開発で、品質担保施策の一環としてVRTを実施しています。
単純にコンポーネントをマウントした結果だけではなく、interaction test や msw-storybook-addon を合わせて活用し、pageやmodal単位の振る舞いの変化の結果の担保として、integration test の役割も一部担わせているため、これを安定化させ、オオカミ少年にしないことが重要です。

そのために、ドキュメントされていない点で改善を試行錯誤した部分があるのでまとめておきます。

改善事項

storycapは厳密には play function に対応していないことを意識する

https://github.com/reg-viz/storycap/issues/584
https://github.com/reg-viz/storycap/issues/518

storycapは、正式なサポートとして、play関数のPromiseが解消されたことを確認してから撮影しているわけではありません。storybookにこれが導入される前から、撮影の安定化のために導入されていたMetricsWatcher これによって、16msごとにdomとstyleの変化を計測しつづけ、これが変化し続ける = レンダリングが終わっていない。と判断し撮影をしないだけです。
なので、play関数の中で長時間 deplaysetTimeoutするような挙動を記述していても、これの解決を待たずに撮影されてしまうこともあり、不安定な撮影タイミングにつながります。

この制約を意識して、play関数の内容を調整したり、どうしても複雑な挙動を担保したいものは通常のintegration testを記述すること。
または、以下のように waitFor を活用して期待するUI要素の出現を待たせることで解決しました。

import { screen } from '@storybook/testing-library';

parameters: {
  screenshot: {
    waitFor: async () => {
      await screen.findByText('このテキストが出てる状態で撮影する');
    },
  },
}

CSSアニメーションのキャンセル

https://github.com/reg-viz/storycap/issues/766
このIssueにあげたように、storycapのdefaultでdisableCssAnimationが効いているはずなのですが、アニメーションの途中のタイミングで撮影されてしまうことが多発していました。
そのため、撮影前のタイミングでstorybookのiframe内に確実にstyleが付くように処理を加えることで解決しました。

export const decorators = [
  // ..., other decorators
  (Story) => {
    // https://github.com/reg-viz/storycap/issues/464
    if (isScreenshot()) {
      function addStyle(styleString) {
        const style = document.createElement('style');
        style.textContent = styleString;
        document.head.append(style);
      }
      addStyle(`
        *,
        *::before,
        *::after {
          transition: none !important;
          animation: none !important;
        }
        input {
          caret-color: transparent !important;
        }
      `);
    }
    return <Story />;
  },
];

Infinite なアニメーションを使用しない

https://github.com/reg-viz/storycap/issues/327

上の項目に関連すると思いますが、css/js問わずアニメーション期限を無限に設定したものが存在すると撮影が開始されずにtimeoutすることがあり、skipをしても該当コンポーネントでタイムアウトする挙動が残ります。
そのため、storybook上ではアニメーション意図的に切れるように設定するか、storybook上から削除することで解決しました。

Infinite なmsw のリクエストを設定しない

https://github.com/mswjs/msw/issues/778

Issueに関連して、delay('infinite')なqueryがひとつでも存在すると、同一のqueryを使ったstoryが存在し、かつ、撮影タイミングがdelay('infinite')なものよりも後であった時、全てこれとと同じ状態で撮影されてしまう挙動がありました。
そのため、infiniteなstoryを削除することで解決しました。

iframeを使用しない

https://github.com/reg-viz/storycap/issues/563
こちらのIssueにあるように、iframeを使用したstoryが存在するとそれが理由で落ちる時がありました。
iframeの内部の表示についてvisualを担保する必要はないので、skipし通常のtestを書くことで解決しました。

Next/Imageの挙動安定化

特に、Intersection Observerを使用して、srcを実際のimgタグに渡す/渡さないを制御する形でlazy loadingを実現していた next/legacy/image の方で顕著でしたが
Next/Imageは内部でdefaultに lazy loading など読み込みを最小化するような処理がされています。
これによって、画像が空の状態で撮影されることが多発していました。

このため、このようなworkaroundを使用するなどして、storybookの起動中はoptimizeが効かないようにすることで解決しました。
https://github.com/storybookjs/storybook/issues/23684#issuecomment-1682143610

画像差分検知の安定化

一部の画像において、全く同じ画像URLかつ、storycapのdefaultの機能で画像読み込み終了まで撮影をしないように設定されているはずだが、画像の内部で差分が発生してしまうことが起きました。


画像の表示が担保されていればよく、特定の画像でテストする必要はなかったため、mock用のdata uriを用意し、これを各所で使用するように変更することで収めることができました。

mainImage: {
-  imageUrl: 'https://storage.googleapis.com/hoge/fuga.jpg',
+  imageUrl: 'data:image/png;base64,iVBORw0KGg ... ',

おわりに

諸々調整したことによって、現状安定した状態でVRTを運用できています。
設定する側としては一定労力が掛かりますが、開発時の体験としてはstorybook駆動にUI開発をしていればそれ自体がテストになってしまうため、コスパ高く開発できるものと考えており、引き続き運用してみようと考えています。

(Next.jsの major version 更新のたびにstorybookが壊れて、それもそれで大変ですなぁ)

Discussion