DockerでVue2 開発環境・Jest + Puppeteerでテスト環境構築

いろいろエラー吐きまくりでゲロゲロだったけど、なんとか形になったので記録を残す。

ーローカル環境ー
macOS Big Sur 11.4
Docker version 20.10.5
docker-compose version 1.28.5


まずは作業ディレクトリ(任意の名前)を作成後、Dockerfileを作成。

$ mkdir vue-docker
$ cd vue-docker
$ touch Dockerfile

中身をテキストエディタ等で以下のように記述。

FROM node:14-slim

# appは任意の名前でOK。後のdocker-compose.ymlのvolumesと合わせること。
WORKDIR /app

# Puppeteerの起動に必要なChromeと関連パッケージ等のインストール。
# 公式参照(https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker)
# 上に加えて、Vueのイントールでエラーが出るためlibxtst6を、Puppeteerの実行でエラーが出るためprocpsを追加している。
RUN apt-get update \
  && apt-get install -y wget gnupg vim procps \
  && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
  && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
  && apt-get update \
  && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 libxtst6 \
  --no-install-recommends \
  && rm -rf /var/lib/apt/lists/*

RUN npm install -g @vue/cli

Nodeイメージの2021年5月時点最新の16では、Vueのインストールが上手くいかなかったので、node:14-slimを指定。
一つ目のRUNでは、Puppetter公式のトラブルシューティングの記述に加え、パッケージのインストールにprocpsを、chrome関連パッケージとしてlibxtst6を追加している。理由はコメントに書いているとおり。
また、Docker内でファイル編集する機会もあるかと予想したのでvimも追加した(試行錯誤しているときに無くて困った)。
ここではDocker内でChromeを起動するためにいろいろインストールしている( Running Puppeteer in Docker の翻訳とメモ - ベーコンになります。を参照 )わけだが、先の記事に書いているユーザー権限まわりは省略した。そのかわり--no-sandbox等の必要な設定は後で書く。


次に同ディレクトリにdocker-compose.ymlを作成。

$ touch docker-compose.yml

開いて中身を記述。.ymlファイルはインデントや文字間の空白数が厳密なので、気をつけて書こう。

version: "3"

# web のところは任意の名前でOK
services:
  web:
    build: .
    ports:
      - 8080:8080
    volumes:
      - .:/app
    init: true
    tty: true
    stdin_open: true
    command: npm run serve

buildやvolumesに出てくる . はカレントディレクトリの意味。volumesは . とdockerコンテナ内の/appディレクトリをマウントしている。


これで準備ができた。次はコンテナに入ってvueをインストールする。(ここから以下すべてコンテナ内で作業(# でコマンドライン表記))

$ docker-compose run web bash
# vue create .

インストール時の設定は以下。自分の好みややりたいことに合わせて設定を。テストランナーはJestを使うので始めから入れておく。

?  Your connection to the default yarn registry seems to be slow.
   Use https://registry.npm.taobao.org for faster installation? (Y/n)
> n

? Generate project in current directory? (Y/n)
> y

? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
  Default (Vue 3) ([Vue 3] babel, eslint) 
❯ Manually select features

? Check the features needed for your project: 
 ◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◯ Vuex
 ◉ CSS Pre-processors
 ◉ Linter / Formatter
❯◉ Unit Testing
 ◯ E2E Testing

? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
❯ 2.x 
  3.x

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)
> y

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
❯ Sass/SCSS (with dart-sass) 
  Sass/SCSS (with node-sass) 
  Less 
  Stylus 

? Pick a linter / formatter config: 
  ESLint with error prevention only 
  ESLint + Airbnb config 
  ESLint + Standard config 
❯ ESLint + Prettier

? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save
 ◯ Lint and fix on commit (requires Git)

? Pick a unit testing solution: 
  Mocha + Chai 
❯ Jest

? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files 
  In package.json

? Save this as a preset for future projects? (y/N)
> n

? Pick the package manager to use when installing dependencies: 
  Use Yarn 
❯ Use NPM


Vueの開発環境ができた。
ここでDockerfileに以下を追記しておく(npm install -g @vue/cliより前の部分)。

(省略)

COPY package.json .
RUN npm install \
  && npm install -g @vue/cli



次はテスト環境を構築する。 ここからインストールするものが多くなる。 まずはJestのunitテスト環境を整えよう。これはVue公式が参考になる。

Jest を使用した単一ファイルコンポーネントのテスト | Vue Test Utils

公式のとおりやればOK…とはいかなかったので、少し記述を変えたり追加したものがある。
最初のJestとtest-utilsはインストール済みなので省略。
次のpackage.jsonの記述は以下のようにした。"test"の名前はコマンドになるので、好みで変えてOK。

{
  (省略),
  "scripts": {
    (省略)",
    "test:unit": "jest",
    (省略)
  },
  (省略)
}


次のvue-jestのインストール〜設定までは公式のとおりやろう。
その次のbabel-jestインストール→package.jsonに記述追加後、.babelrcを設定とあるが、この環境では.babel.config.jsが替わりにあるので、こちらを設定する。jsファイルなので書き方が若干異なる。

module.exports = {
  presets: ["@vue/cli-plugin-babel/preset", ["env", { modules: false }]],
  env: {
    test: {
      presets: [["env", { targets: { node: "current" } }]],
    },
  },
};


これで設定完了。テストを走らせてみよう(デフォルトで/src/tests/unit/example.spec.jsがあるはず)。

# npm run test:unit


あれ?エラーが。
Error: Cannot find module 'babel-preset-env' とある。のでbabel-preset-envをインストール。

# npm install babel-preset-env

.babel.config.jsの"env" → "@babel/preset-env"に変更(2箇所とも)。

module.exports = {
  presets: [
    "@vue/cli-plugin-babel/preset",
    ["@babel/preset-env", { modules: false }],
  ],
  env: {
    test: {
      presets: [["@babel/preset-env", { targets: { node: "current" } }]],
    },
  },
};

これで先ほどのテストコマンドを走らせると...…通った!これでunitテストの設定は完了。


次はE2Eテスト環境に取り掛かる。
まずはPuppeteerと、それをJest上で扱いやすくjest-puppeteerをインストールする。

# npm install puppeteer jest-puppeteer


次に、必須ではないと思うが、お手軽に設定を追加してくれるvue-cli-plugin-jest-puppeteerを入れる。

# vue add jest-puppeteer


これで/test/e2eディレクトリが作成されるが、testsとtestの2つあるのは鬱陶しいので、1つ(tests)に統一する。

# mv ./test/e2e ./tests/.
# rmdir test

ディレクトリの変更をjest.e2e.config.jsに反映する。下記のようにtestMatch: の/test/ を/tests/に変更。

module.exports = {
  preset: "jest-puppeteer",
  moduleFileExtensions: ["js"],
  testMatch: ["**/tests/e2e/**/*.spec.(js|ts)"],
  watchPlugins: [],
};


package.jsonに以下が自動で追加されている。コマンドは好みで変更してもOK。

{
  (省略),
  "scripts": {
      (省略),
      "test:e2e": "jest --config=jest.e2e.config.js --runInBand",
      (省略),
    },
  (省略)
}


この状態でテストを走らせると、

# npm run test:e2e

Error: Failed to launch the browser process! Running as root without --no-sandbox is not supported.
となるので、jest-puppeteer.config.jsに設定を追加する。

module.exports = {
  launch: {
    headless: true,
    args: ["--no-sandbox", "--disable-setuid-sandbox"],
  },
  (省略),
};

デフォルトで用意されているtests/e2e/basic.spe.jsを走らせてみたいが、そのままでは内容がVueの初期画面と合致しないのでちょっと修正(HelloWorld → Welcome to Your Vue.js App)。

describe("your Vue app", () => {
  beforeAll(async () => {
    await page.goto("http://localhost:8080");
  });

  it("can be tested with jest and puppeteer", async () => {
    await expect(page).toMatchElement("h1", "Welcome to Your Vue.js App");
  });
});


あとは/src/views/Home.vueと/src/components/HelloWorld.vueの要らない記述(name: ~の行)を削除(TypeErrorになるので)。
これでテストコマンドを走らせれば、

# npm run test:e2e

PASS!!

お疲れ様でした。