Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

GitHub Pagesをビジュアルリグレッションテストをしてみた

More than 1 year has passed since last update.

モチベーション

こんにちは@glassmonkeyです。
Github ActionといえばCI。
せっかくCIするなら画像使ったものをやってみたかったので、簡単にビジュアルリグレッションテストをやってみました。
突貫で作ったのでご意見ご感想あればありがたいです。

ビジュアルリグレッションテストとは

アスタミューゼ様のブログでの説明がわかりやすかったので引用させていただきます。

ビジュアルリグレッションテストとは視覚的な回帰テストのことで、具体的にはスクリーンショットを撮影して差分抽出して行うテストです。

つまりGithub Actionの環境でビジュアルリグレッションテストを行うことは
仮想環境上で以下の画像を生成することになります。
* 変更前と変更後のスクリーンショット
* 変更前と変更後の差分画像

この記事では
* クロール及びスクリーンショットはSelenium + Headless Chrome
* 差分画像生成(画像処理)には OpenCV
を扱いました。

どのようにしたか

サンプル

実際作ってみたものが下記にあたります。
https://github.com/glassmonkey/vue-sample/pull/3
私のVuejsの勉強のために作ったアプリケーションに追加してみました。

今回は下記のように変更点がわかりやすく矩形で囲ってみました。

元画像 変更後画像 差分画像
元画像 変更画像 差分画像

要件

https://github.com/glassmonkey/vue-sample/blob/master/.github/workflows/test.yml#L19-L20

BASE_URL: https://glassmonkey.github.io/vue-sample

DIFF_URL: http://localhost:8080

テストの内容

テストの流れ

テスト用のymlは下記になります。
https://github.com/glassmonkey/vue-sample/blob/master/.github/workflows/test.yml

name: test

on: pull_request
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1 # ①
      - name: develop run # ②
        run: |
          docker-compose up -d
      - name: run test # ③
        run: |
          cd tests && \
          docker-compose build && \
          docker-compose run app \
        env:
          WINDOW_SIZE: 1024,768
          BASE_URL: https://glassmonkey.github.io/vue-sample/
          DIFF_URL: http://localhost:8080
      - uses: jakejarvis/s3-sync-action@master # ④
        env:
          AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: 'ap-northeast-1'
          SOURCE_DIR: './tests/dist'
          DEST_DIR:  ${{github.repository}}/${{github.sha}}
      - name: post maessage # ⑤
        run: |
          cd tests && bash post.sh
        env:
          S3_PATH: https://${{ secrets.AWS_S3_BUCKET }}.s3-ap-northeast-1.amazonaws.com/${{github.repository}}/${{github.sha}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

認証情報はGithub上settings > secretsから下記の通り設定させる必要はあります。
スクリーンショット 2019-12-02 12.30.20.png

簡単に解説すると

  1. 初期化
  2. PRの内容で仮想環境を構築
  3. ビジュアルリグレッションテスト用コンテナの構築及び実行
  4. 生成した画像をs3にアップロード
  5. PRに画像をポスト

の流れになります。3の画像生成と5のPRにポストの内容に関して解説します。

画像生成

スクーンショットの撮影および、差分画像生成は下記で行っています。
https://github.com/glassmonkey/vue-sample/blob/master/tests/src/main.py

今回テスト用に実行するコンテナはローカルで構築したアプリとは独立させたので、localhostの名前解決ができないので下記で無理やりテスト側で書き換えました。

    if "localhost" in url: 
         host_addr = subprocess.run(["ip route | awk 'NR==1 {print $3}'"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).stdout.decode("utf8").rstrip('\n')
         url = url.replace("localhost", host_addr)

スクリーンショットについて

Docker上でSeleniumとHeadless ChromeとPython3を動かすの記事を参考にさせていただきました
特筆すべき点としては下記のオプション項目は調整必須な点です。
特にヘッドレスクロームで起動する場合は起動字のタイミングでウィンドウサイズ指定が必須な点は少しハマりました。

   options.add_argument('--headless')
   options.add_argument('--no-sandbox')
   options.add_argument('--disable-dev-shm-usage')
   options.add_argument('--hide-scrollbars')
   options.add_argument('--window-size={}'.format(os.environ['WINDOW_SIZE']))

今回は下記のようにURLを静的にスクリーンショットにしましたが、スクリーンショット対象のDom要素の指定などもできるようなので外部からカスタマイズできるようにしてもいいのかなとは思いました。

    driver.get(url)
    driver.save_screenshot(filename)

差分画像生成について

こちらの記事を参考にさせていただきました。 下記の関数でスクリーンショット画像の差分画像を生成するようにしています。

def diff_images(base_image_path, diff_image_path):
    """
    referer to https://www.pyimagesearch.com/2017/06/19/image-difference-with-opencv-and-python/
    :param base_image_path:
    :param diff_image_path:
    :return:
    """
    # load the two input images
    base_image = cv2.imread(base_image_path)
    diff_image = cv2.imread(diff_image_path)

    # convert the images to grayscale
    grayA = cv2.cvtColor(base_image, cv2.COLOR_BGR2GRAY)
    grayB = cv2.cvtColor(diff_image, cv2.COLOR_BGR2GRAY)

    (score, sub) = compare_ssim(grayA, grayB, full=True)
    sub = (sub * 255).astype("uint8")
    print("SSIM: {}".format(score))
    thresh = cv2.threshold(sub, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    for c in cnts:
        # compute the bounding box of the contour and then draw the
        # bounding box on both input images to represent where the two
        # images differ
        (x, y, w, h) = cv2.boundingRect(c)
        cv2.rectangle(base_image, (x, y), (x + w, y + h), (0, 0, 255), 2)
        cv2.rectangle(diff_image, (x, y), (x + w, y + h), (0, 0, 255), 2)
    cv2.imwrite("/app/dist/base.png", base_image)
    cv2.imwrite("/app/dist/diff.png", diff_image)
    cv2.imwrite("/app/dist/sub.png", sub)

PRへPOST

https://github.com/glassmonkey/vue-sample/blob/master/tests/post.sh

s3にアップロードするアクションを用いてs3にアップした画像をGithubのApi経由でアップさせた画像をPostしています。
処理にはjessfraz/shaking-finger-actionを参考にしました。

※ 今回使用したバケットの設定は適当に作ったので割愛とさせていただきます。
コミットの度に画像を再生成する仕様なのでコミットのSHAでディレクトリ別にはしています。

結果

このPRに無事に投下されました。
https://github.com/glassmonkey/vue-sample/pull/3
スクリーンショット 2019-12-02 12.49.26.png

まとめ

簡単なビジュアルリグレッションテストをおこなうことができた。
せっかかくなのでちゃんとactionとして別リポジトリにしてもいいかもしれない。
まともにGithubのApiを触ったことがなかったので画像のPostをどうしようかすごい悩んだ。
今回はs3にしましたが、別のアプローチもありかもしれない。
Opencvのインストールが煩雑になるので断念したんですが、遅いのでテスト用コンテナをalpineから諦めましたがalpineにしたいですね。

glassmonkey
とあるところのエンジニア。コンテナ・CI/CDが好き。 Flutterは趣味で開発しています。
binc
Eコマースプラットフォーム「BASE」、オンライン決済サービス「PAY.JP」、購入者向けID型決済サービス「PAY ID」の3つのサービスを運営しています。
https://binc.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away