tl;dr
お疲れ様です. かっぱです. YAMAP に入社してもうすぐ一年が経とうとしています. あっと言う間の一年でした. この一年の振り返りを... と思いましたが, 最近の悩み事を技術で解決してみようと思って試験的に実装した内容を紹介させていただきます.
そして, この記事は, YAMAP エンジニア Advent Calendar 2019 の 25 日目の記事になる予定です.
悩み事
YAMAP では, yamap.com 以外にもいくつかの外部サイトを運営しています.
これらのサイトのデプロイ作業を含む各種運用に携わっていますが, これらのサイトがデプロイの度にレイアウトがデグレしてたり, 意図しないような状態で表示されたりすることが多く発生していました. 原因はいくつかありますが, コンテンツが CVS の管理外であったり, コンテンツをオンラインで更新していて CVS にマージされていなかったり人為的な手違いによるものが多く, さらに, その異常に気付くことが出来なかったりして胃が痛くなる日々が続いていました. ということで, ざっくりと悩み事をまとめると...
- 意図しないサイトのデグレが度々発生する
- ソースコードとコンテンツが別々に管理されている (WordPress で記事はデータベース, テーマのソースコードは CVS で管理) ので変更検知は 2 箇所になるので, 最後に信じられるのは実際に動いている環境になりがち
- コンテンツ管理者とテーマの管理者が異なる
- HTTP ステータスコードレベルでの監視では満たされてない
こんな感じです.
どんな風に解決しようと試みたか
下図のような実装を考えて実装しました. (雑に手書きですいません.)
流れとしては...
- CircleCI を実行基盤として...チェックを定期実行
- Puppeteer + テストフレームワークを使ってサイトにアクセスしてスクリーンショットを取得
- 前回のスクリーンショットと差分を比較
- 差分が N % 以上であれば, テストが Fail となる
こんな感じ. 上図では Puppeteer ってなっているところは, 実際には Jasmine から Puppeteer を操作してスクリーンショットを取得して, 従来のスクリーンショットとの差分を比較する処理が動きます.
作ったもの
リポジトリ
こだわり
出来るだけ手を動かすことなく CircleCI 上で動かすことを考えました.
また, Puppeteer でサイトにアクセスしてスクリーンショットを取得して比較するコアな部分は最小限の実装に スクリーンショットを S3 にアップロード, ダウンロードする部分はコアとは別に AWS CLI (実際には CircleCI の orbs) で実装しました. 以下は .circleci/config.yml の抜粋です.
version: 2.1 orbs: aws-s3: circleci/aws-s3@1.0.11 slack: circleci/slack@3.4.1 executors: default: docker: - image: circleci/node:12.4 - image: circleci/python:2.7 commands: check_prepare: steps: - aws-s3/sync: from: s3://${ROOT_PATH}/ to: /home/circleci/project/${ROOT_PATH}/ overwrite: true arguments: > --delete npm_install: steps: - run: name: Update npm command: sudo npm install -g npm@latest - restore_cache: name: Restore Dependencies keys: - dependencies-{{ checksum "package-lock.json" }} - dependencies - run: name: Install Dependencies command: npm install - save_cache: name: Save Dependencies key: dependencies-{{ checksum "package-lock.json" }} paths: - node_modules jobs: check: executor: name: default steps: - checkout - run: name: Install Headless Chrome dependencies command: | sudo apt-get install -yq \ ... 略 ... - npm_install - check_prepare - run: name: Run test command: ./node_modules/.bin/jasmine ... 略 ... - run: name: Store Images command: | aws s3 sync /home/circleci/project/${ROOT_PATH}/ s3://${ROOT_PATH}/ when: always - slack/status: fail_only: true mentions: ${SLACK_MEMBER_IDS} workflows: version: 2 byhand-webpage-check: jobs: - check triggerd-webpage-check: triggers: - schedule: cron: "3 * * * *" filters: branches: only: - master jobs: - check
また, CircleCI の triggers を利用して一時間に一回定期的に実行させるようにしています.
さらに, S3 へのアップロードと Slack への通知は orbs を利用しています.
ちょっと痒いところに手が届かないところ (run
で利用出来る when
的な定義が書けないとか) が残念でしたが, 基本的な使い方であれば全然 orbs でいけるので最高ですな.
苦労したところ
Puppeteer (1)
何よりも Puppeteer です. 全く触ったことなかったので, 見様見真似で写経 (コピペ) していました. しかし, 以下のように簡単にサイトのスクリーンショットを取得出来るのには感動しました.
// // https://github.com/puppeteer/puppeteer#usage より引用 // const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://yamap.com'); await page.screenshot({path: 'test.png'}); await browser.close(); })();
これを test.js あたりで保存して node test.js
とすると, 以下のようなスクリーンショットがシュッと撮れます.
しかし, 相手は JavaScript, 一筋縄ではいきません. async
と await
がよくわからないまま書き進めてしまい, 一応, 思ったような動作になりました... これでは, 成長が無いのでもう少し async
と await
をはじめ JavaScript について理解を深めていきたいと思います.
Puppeteer (2)
ページ全体のスクリーンショットを取得したい場合, 以下のように screenshot
メソッドの引数に fullPage: true
をつけてあげると取得出来ることは確認しました.
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://xxxx.xxxx.com/'); await page.screenshot({path: 'test.png', fullPage: true}); await browser.close(); })();
ところが, サイトによっては, 下図のように画像が一部読み込まれず悩みました.
現在も解決出来ていません...
差分抽出
差分抽出には Resemble.js を利用しています.
この Resemble.js も Puppeteer 同様に以下のように簡単に画像の差分を抽出することが出来ます.
resemble('after.png').compareTo('before.png') .ignoreColors() .onComplete(function(data) { fse.writeFileSync(DirName + 'diff.png', data.getBuffer()); });
動いている様子
CircleCi でのテスト結果.
上記のように, 対象ページに差分があり, 差分許容率がしきい値を超えるとテストが Fail となります.
Fail したら Slack に上図のように通知されます. 通知が届いたら, S3 バケットに保存されている画像を確認して, 意図した差分なので, そうでないかをデザイナーさん等に確認する流れになることを想定しています.
また, テストと合わせて差分抽出画像が生成されます. 差分抽出画像は上図のような画像になります. 前回のスクリーンショットとの差分がピンク色に着色された状態になります.
参考
以下の記事を大幅に参考にさせていただきました. Puppeteer のことを 1bit もわからない自分でもなんとなく利用することが出来ました. ありがとうございました.
ということで
まとめ
Web サイトの監視について, 対象のコンテンツ差分をチェックするというアプローチを検討して実装してみました. 意図したコンテンツの変更についても検知してアラートが飛んでくる状況ですが, これはこれで外形監視上の変更履歴として利用出来るのではと考えています.
実装にあたり, 使った技術要素を見てみると, チェックのフレームワークとして Jasmine というテストフレームワーク (と言っていいのかわかりませんが), 実際の基盤としては CircleCI と出来合いのフレームワークを組み合わせることでシュッと仮実装まで持ってくることが出来ました. 技術の進化というものは本当に素晴らしいものですね.
最後に
YAMAP に入社して一年が経とうとしていますが, エンジニアとして, 自分なりの「新しい山を作ろう」で頑張っていきたいと思います. これからも宜しくお願い致します.