TL; DR
Xvfbで仮想ディスプレイ有りのnon-headless GoogleChromeをdocker上で実行した
これは何
GA technologies / ITANDI, inc. の中村です。 本ドキュメントは、Selenium webdriver経由でのchrome操作を、主として自動化を目的に
- docker上で
- headless でない(=GUIを持つ)modeで
- ruby selenium-webdriverで
動作させるためのノウハウを記すものです。 ただし、基本的な仕組みはselenium driverのbinding languageに関わらず共通なので、他の言語でも転用できるかと思います。
一体なぜそんな事を
webサイトのクローリングや自動化テストなど通常は Chrome Headless を用いればそれで事足りるのですが、自動化の対象にChrome Extensionを利用したい場合などは執筆現在、headless modeがexntensionに対応していないためHeadfulに実行する必要があります。
用途は限られてはいますが、どうしてもheadlessでないchromeを利用したいケースとして以下があります
- chrome extensionそのものの操作テストを自動化したい
- より正確にユーザの操作をシミュレートしたい
今回は、後述しますが上記2点のどちらでもなくextensionからしか操作できないChromeのAPIを利用してfile downloadの挙動を変えたいというのが動機となっています。
やり方
Xvfbサーバの起動
headless modeと異なり、headfulで起動する場合にはディスプレイが必要になりますが、当然自動化環境においては通常ディスプレイのハードウェアが存在しないため、in memoryの仮想ディスプレイ Xvfb を起動してハードウェアをエミュレートする必要があります。
RUN apt-get install xvfb
entrypoint.sh
# entrypoint.sh Xvfb :99 -screen 0 1024x768x16 & export DISPLAY=:99.0
Google Chrome install
実際のuser操作をsimulateするため、chromiumではなくGoogle Chromeそのものをinstallします。
Dockerfile
RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN apt-get install google-chrome-stable
selenium(capybara)の設定
ポイントはheadless chromeで通常設定される --headless
optionを指定しない点です。
ChromeはRAM領域 /dev/shm
のリソースを大きく消費するのですが、docker containerの /dev/shm
領域の割り当てはdefault 64MBと非常に小さいため hostと /dev/shm:/dev/shm
のようにvolume mountするか、 --disable-dev-shm-usage
を指定します。dockerの --shm-size
で変更する方法もあります。
Capybara.register_driver :driver do |app| options = Selenium::WebDriver::Chrome::Options.new options.add_argument '--window-size=1024,768' options.add_argument '--no-sandbox' options.add_argument '--disable-setuid-sandbox' options.add_argument '--disable-gpu' options.add_argument '--incognito' # enable this if run on docker without /dev/shm volumn mount # options.add_argument '--disable-dev-shm-usage' options.add_preference(:download, { prompt_for_download: false, default_directory: download_directory || download_default_directory }) options.add_extension('chrome_extensions/package.crx') Capybara::Selenium::Driver.new( app, browser: :chrome, options: options ) end @session = Capybara::Session.new(:driver)
optional
上記のseleniumの設定に options.add_extension('chrome_extensions/package.crx')
という記述がありましたが、下記手順で作成したchrome extensionのpackageを指定してください。
downloadの挙動を変えるchrome extension
余談として、今回実装したchrome extensionのコードを記しておきます。
ブラウザで、同じファイル名のファイルをダウンロードする場合の挙動はいくつかのパターンが存在します。
- overwrite: 上書きする
- uniquify: {originalfilename}(1)のようにversioningする
- prompt: ユーザに確認する
執筆時点で確認したところ google-chromeはdefaultでuniquify、chromiumはoverwriteと挙動が異なるので念の為、chrome downloads api を通じて動作を確実に指定します。
下記の通り、file download時の名前を決める onDeterminingFilename
でファイル名決定のpolicyを指定する事で動作を保証できます。
chrome.runtime.onInstalled.addListener(function() { chrome.downloads.onDeterminingFilename.addListener(function(downloadItem, suggest) { suggest({ filename: downloadItem.filename, conflictAction: 'uniquify' }); }); });
extensionのpackage化
address barに chrome://extensions/
を入力した後、「拡張機能をパッケージ化」を選択してください。
執筆時点で、command lineでパッケージ化する方法はないようなので、source codeとセットでrepositoryにpackageそのものを管理しておくのが良いと思います。
終わり
楽しい自動化ライフをお送りください。