モチベーション
こんにちは@glassmonkeyです。
Github ActionといえばCI。
せっかくCIするなら画像使ったものをやってみたかったので、簡単にビジュアルリグレッションテストをやってみました。
突貫で作ったのでご意見ご感想あればありがたいです。
ビジュアルリグレッションテストとは
アスタミューゼ様のブログでの説明がわかりやすかったので引用させていただきます。
ビジュアルリグレッションテストとは視覚的な回帰テストのことで、具体的にはスクリーンショットを撮影して差分抽出して行うテストです。
つまりGithub Actionの環境でビジュアルリグレッションテストを行うことは
仮想環境上で以下の画像を生成することになります。
- 変更前と変更後のスクリーンショット
- 変更前と変更後の差分画像
この記事では
- クロール及びスクリーンショットはSelenium + Headless Chrome
- 差分画像生成(画像処理)には OpenCV
を扱いました。
どのようにしたか
サンプル
実際作ってみたものが下記にあたります。
https://github.com/glassmonkey/vue-sample/pull/3
私のVuejsの勉強のために作ったアプリケーションに追加してみました。
今回は下記のように変更点がわかりやすく矩形で囲ってみました。
元画像 | 変更後画像 | 差分画像 |
---|---|---|
要件
- 変更前は本番環境(github pagesでデプロイする想定)
- デプロイは @peaceiris さんのGitHub Actions による GitHub Pages への自動デプロイを使用させていただきました。
- 変更後はPRの内容(CI上の仮想環境でdocker-composeで起動させる)
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
から下記の通り設定させる必要はあります。
簡単に解説すると
- 初期化
- PRの内容で仮想環境を構築
- ビジュアルリグレッションテスト用コンテナの構築及び実行
- 生成した画像をs3にアップロード
- 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
s3にアップロードするアクションを用いてs3にアップした画像をGithubのApi経由でアップさせた画像をPostしています。
処理にはjessfraz/shaking-finger-actionを参考にしました。
※ 今回使用したバケットの設定は適当に作ったので割愛とさせていただきます。
コミットの度に画像を再生成する仕様なのでコミットのSHAでディレクトリ別にはしています。
結果
このPRに無事に投下されました。
https://github.com/glassmonkey/vue-sample/pull/3
まとめ
簡単なビジュアルリグレッションテストをおこなうことができた。
せっかかくなのでちゃんとactionとして別リポジトリにしてもいいかもしれない。
まともにGithubのApiを触ったことがなかったので画像のPostをどうしようかすごい悩んだ。
今回はs3にしましたが、別のアプローチもありかもしれない。
Opencvのインストールが煩雑になるので断念したんですが、遅いのでテスト用コンテナをalpineから諦めましたがalpineにしたいですね。