xpath
npm
chrome-extension

スクリーンショットを自動で撮るchrome拡張を作った話 その1

autologというchrome拡張を作った。

自動スクリーンショットツールである。
あらかじめ設定しておいた種類のイベントが、設定しておいたセレクタ(XPath、CSS)に一致するDOMで発火されたとき、
自動でキャプチャを撮りタイムライン状にまとめてくれる。

screenshot
こんな感じのレポートが出せる。
軽いフィルタリングや時間などでのソートもできる

動機

  • マニュアルテストでいちいちエビデンスを撮るのと、エビデンスまとめの作業が面倒すぎる。
  • 結果だけスクショしても正直不安なので毎回よくわからずいっぱい撮っている。

目指したこと

自動スクリーンショット

テストそのものではなく、エビデンス取得を自動化する。
マニュアルテストではオペレーションをまわして結果を目視で確認するだけでOKということにしたかった。

タイムライン

このアクションして、次このアクションして・・・という時系列がまとまっていた方がありがたい。
エビデンスまとめが楽になるだけではなく、結果だけだと不安というのも解決できる。

フレキシブルな設定

インプットが多すぎると普段使いできないので、
設定で便利に使えるようにしたかった。

今回は、特に
「何をトリガーにしてスクリーンショットを撮るか」
を設定するというところを実現した。

手動でも撮れる

そもそも手動で撮っても便利ということにしたかったので、
手動で撮影しつつ、メモとともに時系列上でレポーティングできるようにした。

実装

環境

node.jsのモジュールを使ってChrome拡張機能を作る
https://qiita.com/abcang/items/10750e3fb8453d5e0944

を参考にしてまずは環境構築。

yeomanを使って一発でgulp + webpackで開発する環境を整えることができた。

パーツ

全体

構成としては、

  • command =(ショートカットキー)> backgroundがカレントタブを録画開始し、レポート出力用のタブを開く
  • contentscript =(イベントを通知)> background => レポートタブがスクリーンショットを撮る

というのが基本で、他にpopupから録画開始・停止できたり、popupからスクショ撮れたり、ショートカットキーでもスクショ撮れたりするようにしている。

Service

backgroundとcontentscriptとpopupとレポート出力用のタブと、
関与するページが多いため、その間はchrome.runtime.sendMessagechrome.tabs.sendMessage などを利用して通信する必要がある。
ビュー層と通信する層は分けたかったので、Serviceという名の普通のjsオブジェクトを用意して、通信系のメソッドを提供することにした。

そのうえで適度にモジュール化してrequireでとってきて、webpackでトランスパイルするという流れ。

Content ScriptはjQuery + XPath

リスナー

このchrome拡張の華であるcontentscriptのイベントリスナーだが、
XPathで指定できるようにしたかったので少々凝ったことをしている。
(CSS Selectorでも指定できるようにしているので、そちらが使いたい場合はそれでもOK)

まずxpath-dom というnpmパッケージを利用して、
あるコンテキストノードに対してXPathでDOMをセレクトできるような状態にしておき、
それをjQueryでラップした自作のモジュールを作成して使った。

記法としては

$(document).byXPath('//*button[contains(text(),"登録")]');

とかやると、そのXPathでセレクトしたDOMのjQueryオブジェクトが返ってくるという寸法。

続いて、content scriptがロードされた当初は対象のDOMがまだ描画されていない可能性を鑑みて、
jQueryのdelegate(というか、onの第二引数にセレクタを渡したもの)に似たものを実装する必要がある。

要は、
親要素に対してイベントをセットし、イベントが発火した時点で、
発火点がセレクタを満たす場合にイベントハンドラを叩くという実装が必要。

( delegateについてはこちら
jQueryのclickとbindとliveとdelegateとonの違い
https://qiita.com/smzk/items/5eed5a90c4b32ca8b23a

と、いうことでそれらしいものを実装してみた。

参考にしたのはこちら

Native JS equivalent to jquery delegation
https://stackoverflow.com/questions/25248286/native-js-equivalent-to-jquery-delegation

ここらへんの実装部分についてはもろもろあったので、
別の記事を書いたうえで、おそらくnpmでjQueryプラグインとして公開する予定。
ざっと調べたところnpmでモジュールとして使えるjQuery + XPathのサムシングは無かった。
(今回は使わなかったけど、SelenideライクにlinkWithTextとか使いたいよね!)

名前の取得

エビデンスまとめを自動でやってほしいという要件があるので、
ただ日時とスクショを淡々ととるのではなく、「どんなアクションをしたか」という情報を載せてやる必要がある。

一般的に、例えば<label>要素に「人間がわかる項目名」が書いてあり、それが以下のような構造だった場合、

<div class="input-group">
 <label for="fullname">氏名</label> 
 <input type="text" name="fullname" />
</div>

<input> 要素に対するイベントをキャッチした後は、要は「同じ親に対する<label>要素のテキスト」を取得すればよい。

なので、今回は「イベントをキャッチした要素をコンテキストノードとするXPath」を設定できるようにした。

なのでこのケースの場合、

{
 "selector" : "//input[@type='text']"
 "nameSelector" : "../label"
}

という感じで設定してやれば、簡単にlabel要素を取得できるようになる。
イメージとしては、イベントハンドラで

function(e) {
 var name = $(this).byXPath('../label').text();
}

とやれば、名前が取得できるというイメージ。

そしてたいていの場合、このような要素とlabelなどの「人間がわかる項目名」との関係は一定なので、
設定として持っておくとかなり強力。

(これがどうしてもXPathを使いたかった理由でもある)

UIがある部分はvue.js + vuetifyjs

screenshot2

レポートや

screenshot3

設定画面

などは、マテリアルデザインで手軽にUIが作れるvuetifyjsを利用。
vue.jsは取り回しがいいのでこれぐらいの開発には向いている。

vueファイルを作るとかまではしなかった(gulpをいじるのが面倒だったので)。

特に苦労もしなかったのでここの説明は割愛。

スクショを撮る部分はテンプレで実装

html5rocks
https://www.html5rocks.com/ja/tutorials/getusermedia/intro/#toc-screenshot

ほぼここの通りで実装。

録画を始めるときにbackgroundでtabcaptureを録画開始しておき、
そのままwindow.URL.toDataURL()でblobUrlを取得。

そのまま、新しいタブを開き、レポート用のHTMLを読み込んで、
そのタブに対してblobUrlを渡せば、あとは上の記事の通りに実装できた。

<video><canvas> をそのタブ上でdisplay:none; で表示しておき、
そこからじゃんじゃん画像を切り取っていく。

so that's it!

簡単でしょう?

反省、今後の展望

テスト書いてない。

chrome拡張のテストってどうやったらいいのかわからなさすぎてマニュアルのモンキーテストしかしてない。
jasmineやらkarmaやらを今後書いていきたい(ちょっと遅い。)

ログ対象を増やす

pluginみたいなのを実装できるようにするとかいろいろあると思うが、
とりあえずalertが出たら一発撮っとくとか、
特定のクラスを持ったDOMが出てきたら撮るとか?(***-dangerとか!)

自動でログ収集していくつくりにしたいので、
そこらへんを検討中

レポーティング機能の強化

現状、レポートといっても単なる表を描画しているHTMLを保存してくださいという仕様になっているので、
ノイズが除去しづらい(フィルタ機能があるので、一応なんとかきれいにすることは可能そうだが)。
できれば、一度作成したタイムラインに対して、
これは必要、これは不要と個別に編集して、
そのうえでメモを後から追記したりして、そのうえで保存できるというつくりにしたい。

以上です