LoginSignup
16
20

More than 3 years have passed since last update.

ゼロからVue.jsでビジュアルリグレッションテストするまでpart1/3

Last updated at Posted at 2019-12-05

Part1 ここ
Part2 https://qiita.com/senku/items/20e21033edd512be1d4d
Part3 https://qiita.com/senku/items/08d547eda2c6ff818108

Vue.jsでビジュアルリグレッションテストをするためにいろいろやったナレッジを公開しまーす。

やりたいこと

  • Vue.jsのコンポーネントのビジュアルリグレッションテスト(画像回帰テスト)をしたい。
  • viewsレベルのテストもしたい。(Vuex/Vue Routerが絡む)
  • 多言語対応もしたい。(Vue I18nが絡む)
  • テストはなるべくローカルで完結したい。サーバ立てたりしたくない。

Summary

Storybook(ビジュアルの元ネタ) + storycap(画像生成) + reg-suit(画像比較&レポート) でやる。
Storyを作るために悪戦苦闘する

  • Vue.js開発環境の構築
  • StorybookでコンポーネントのStoryをつくろう
    • Vue I18nと戦う
    • Vue Routerはかんたん(ここまで)
    • Vuexはモックする
  • Storycapで画像を生成
  • reg-suitで比較&レポート

Vue.jsのセットアップ

ゼロからやりますと言ったものの、みんな流石にNode.jsは持ってるよね。

Vue CLI いれる

公式のインストールに従います。

$ npm install -g @vue/cli
()
+ @vue/cli@4.1.1
$ vue --version
@vue/cli 4.1.1

Vue CLI 4だ!

プロジェクトをつくる

公式のプロジェクトの作成に従う。はろーわーるど!

$ vue create hello-world

とりあえずデフォルトでいきましょう。babelとESLintだけ。

? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint)

言われるがままにcdしてnpm run serveします。

$ cd hello-world
$ npm run serve

http://localhost:8080 ではろーわーるどできましたね。

Storybook導入

Storybookセットアップ

Storybook for Vueは見なかったことにして、Vue CLI plugin for Storybookの手順で入れます。Vue CLI Pluginとしてセットアップしないとvue.config.jsを参照してくれないので後で詰みます(1敗)。Vue CLIに逆らってはならぬ。

$ vue add storybook

Initial Frameworkには無言でEnterを押します。

インストール後、言われるがままにnpm run storybook:serveします。

$ npm run storybook:serve

自動でブラウザが起動して、http://localhost:6006/でStorybookが表示されますね。

かなり後で使うので、ブラウザが起動しないコマンドもここで作っておきましょう。--ciオプションが使えます。
オプションにはStorybookのCLI Optionがそのまま渡せるので覚えておくといつか役に立つかもしれません。

diff --git a/package.json b/package.json
index dfba14f..533efd0 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,8 @@
     "build": "vue-cli-service build",
     "lint": "vue-cli-service lint",
     "storybook:build": "vue-cli-service storybook:build -c config/storybook",
-    "storybook:serve": "vue-cli-service storybook:serve -p 6006 -c config/storybook"
+    "storybook:serve": "vue-cli-service storybook:serve -p 6006 -c config/storybook",
+    "storybook:ci": "vue-cli-service storybook:serve -p 6006 -c config/storybook --ci"
   },
   "dependencies": {
     "core-js": "^3.4.3",

HelloWorldコンポーネントのStoryを書いてみる

Storyの書き方はいろいろありますが、とりあえずこんな感じで作ってみます。

src/stories/HelloWorld.stories.js
import { storiesOf } from "@storybook/vue";
import HelloWorld from "@/components/HelloWorld.vue";

storiesOf("HelloWorld", module)
  .add("test", () => {
  return {
    components: { HelloWorld },
    template: `
    <hello-world msg="from storybook" />
    `
  };
});

Storybookに表示されますね。msgで渡したfrom storybookもちゃんと表示されています。

image.png

ここから先しばらくは、Storybookと他のプラグインとの兼ね合いの話をします。

Vue I18nとのたたかい

Storybook で Vue-I18nを使う場合はちょっと細工が必要です。

まずは vue-i18nをセットアップします。公式のInstallation参照。
Vue CLI 3.x向けの手順ですがたぶん問題ありません。

$ vue add i18n

とりあえず全部デフォルトで。基本のロケールがenになります。

? The locale of project localization. en
? The fallback locale of project localization. en
? The directory where store localization messages of project. It's stored under `src` directory. locales
? Enable locale messages in Single file components ? No

src/main.jsからsrc/i18n.jsがロードされるようになりました。
localeのファイルがsrc/locales/en.jsonにできています。

src/locales/en.json
{
  "message": "hello i18n !!"
}

ちょっとsrc/components/HelloWorld.vueから読み込ませてみます。

src/components/HelloWorld.vue
diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue
index 879051a..e77721a 100644
--- a/src/components/HelloWorld.vue
+++ b/src/components/HelloWorld.vue
@@ -1,6 +1,7 @@
 <template>
   <div class="hello">
     <h1>{{ msg }}</h1>
+    <p>{{ $t('message') }}</p>
     <p>
       For a guide and recipes on how to configure / customize this project,<br>
       check out the

npm run serve して http://localhost:8080/ を見てみます。
hello i18n !! が表示されてますね。

image.png

多言語の対応はここでは置いておいて、とりあえずenで話を進めます。

Vue I18n × Storybook

StorybookでHelloWorldのStoryを見てみましょう。npm run storybook:serveで再ロードさせます。

残念なことにエラーが出ます。Vue I18nがセットするプロパティの$tがないからですね。

image.png

Storybookの起動は src/main.js を経由していないため、別途コンポーネントにVueI18nを紐付けてやる必要があります。

やり方はいろいろあるんですが、とりあえずデコレータで解決しています。Vue.useはいらない。

src/stories/defaultDecorator.js
import i18n from "@/i18n";

export default function defaultDecorator() {
  return {
    template: "<div><story /></div>",
    i18n
  };
}
src/stories/HelloWorld.stories.js
diff --git a/src/stories/HelloWorld.stories.js b/src/stories/HelloWorld.stories.js
index 93b4b7e..d8e8605 100644
--- a/src/stories/HelloWorld.stories.js
+++ b/src/stories/HelloWorld.stories.js
@@ -1,7 +1,9 @@
 import { storiesOf } from "@storybook/vue";
+import defaultDecorator from "@/stories/defaultDecorator";
 import HelloWorld from "@/components/HelloWorld.vue";

 storiesOf("HelloWorld", module)
+  .addDecorator(defaultDecorator)
   .add("test", () => {
   return {
     components: { HelloWorld },

エラーが解消して、hello i18n !!が表示されます。

image.png

vue-i18n-loaderとのたたかい

Vue I18nには、SFCの中に定義を書けるvue-i18n-loaderがあります。これを使う場合は更にひと手間が必要です。

とりあえずvue invoke i18nで、プラグインの設定をvue-i18n-loaderが使えるように更新します。

$ vue invoke i18n
? The locale of project localization. en
? The fallback locale of project localization. en
? The directory where store localization messages of project. It's stored under `src` directory. locales
? Enable locale messages in Single file components ? Yes // ここだけy!

vue-i18n-loaderが追加され、vue.config.jsenableInSFCがtrueになり、src/components/HelloI18n.vueが作成されます。

App.vueから読めるようにしましょう。

src/App.vue
diff --git a/src/App.vue b/src/App.vue
index fcc5662..0660f5b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,16 +1,19 @@
 <template>
   <div id="app">
     <img alt="Vue logo" src="./assets/logo.png">
+    <HelloI18n/>
     <HelloWorld msg="Welcome to Your Vue.js App"/>
   </div>
 </template>

 <script>
+import HelloI18n from './components/HelloI18n.vue'
 import HelloWorld from './components/HelloWorld.vue'

 export default {
   name: 'app',
   components: {
+    HelloI18n,
     HelloWorld
   }
 }

npm run serveを再実行すると、Hello i18n in SFC!が表示されます。(画像はテキスト選択しちゃった)

image.png

vue-i18n-loader × Storybook

src/components/HelloI18n.vueのStoryを作ります。

src/stories/HelloI18n.stories.js
import { storiesOf } from "@storybook/vue";
import defaultDecorator from "@/stories/defaultDecorator";
import HelloI18n from "@/components/HelloI18n.vue";

storiesOf("HelloI18n", module)
  .addDecorator(defaultDecorator)
  .add("test", () => {
  return {
    components: { HelloI18n },
    template: `
    <hello-i18n />
    `
  };
});

この時点ではStorybookは正しく動作しません。keyであるhelloがそのまま表示されてしまいます。

image.png

Storybookとvue-i18n-loaderの間には複雑な問題があります。

いろいろと見守ってはいるんですが、decoratorでthis.$root._i18nをセットするのが現状可能な解決策の一つです。(via. dlucian/vuejs-storybook-i18n #1)ただし強引な解決策なので、いつ動かなくなるかわかんないです。

src/stories/defaultDecorator.js
diff --git a/src/stories/defaultDecorator.js b/src/stories/defaultDecorator.js
index 4e5acdf..c50bf20 100644
--- a/src/stories/defaultDecorator.js
+++ b/src/stories/defaultDecorator.js
@@ -3,6 +3,9 @@ import i18n from "@/i18n";
 export default function defaultDecorator() {
   return {
     template: "<div><story /></div>",
-    i18n
+    i18n,
+    beforeCreate: function() {
+      this.$root._i18n = this.$i18n;
+    }
   };
 }

Hello i18n in SFC!が表示されるようになりました。

image.png

Vue Routerとのたたかい

Routerを入れ忘れていたので入れます。

$ vue add router

Use history mode for router? (Requires proper server setup for index fallback in production) には無言でEnter(Yes)を押していきます。

Vue Routerをインストールすると、src/App.vueからsrc/views/About.vuesrc/views/Home.vueへのリンクが生成されます。というかファイルごと上書きされます。さようならHelloI18n。

src/App.vueのStoryを書いてみます。

src/stories/App.stories.js
import { storiesOf } from "@storybook/vue";
import defaultDecorator from "@/stories/defaultDecorator";
import App from "@/App.vue";

storiesOf("App", module)
  .addDecorator(defaultDecorator)
  .add("test", () => {
  return {
    components: { App },
    template: `
    <app />
    `
  };
});

StorybookでこのStoryを表示するとエラーが起こるので、decoratorで解決します。
Storybookでページ遷移させる予定はないので、ルーティング情報は空にしておきます。すまんな。

src/stories/defaultDecorator.js
diff --git a/src/stories/defaultDecorator.js b/src/stories/defaultDecorator.js
index c50bf20..746aef7 100644
--- a/src/stories/defaultDecorator.js
+++ b/src/stories/defaultDecorator.js
@@ -1,8 +1,10 @@
+import Router from 'vue-router'
 import i18n from "@/i18n";

 export default function defaultDecorator() {
   return {
     template: "<div><story /></div>",
+    router: new Router({}),
     i18n,
     beforeCreate: function() {
       this.$root._i18n = this.$i18n;

config/storybook/config.jsVue.useしておく必要もあります。

config/storybook/config.js
diff --git a/config/storybook/config.js b/config/storybook/config.js
index fe20bab..d8dcd31 100644
--- a/config/storybook/config.js
+++ b/config/storybook/config.js
@@ -1,5 +1,9 @@
 /* eslint-disable import/no-extraneous-dependencies */
 import { configure } from '@storybook/vue'
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)

 const req = require.context('../../src/stories', true, /.stories.js$/)

ここまでやれば、Vue Routerを使うコンポーネントのStoryが表示できるようになります。
リンクを押しても何も起こりません。

image.png

つづく

16
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
20