"headful(non-headless)" chrome on docker

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に実行する必要があります。

developers.google.com

用途は限られてはいますが、どうしてもheadlessでないchromeを利用したいケースとして以下があります

  • chrome extensionそのものの操作テストを自動化したい
  • より正確にユーザの操作をシミュレートしたい

今回は、後述しますが上記2点のどちらでもなくextensionからしか操作できないChromeのAPIを利用してfile downloadの挙動を変えたいというのが動機となっています。

やり方

Xvfbサーバの起動

headless modeと異なり、headfulで起動する場合にはディスプレイが必要になりますが、当然自動化環境においては通常ディスプレイのハードウェアが存在しないため、in memoryの仮想ディスプレイ Xvfb を起動してハードウェアをエミュレートする必要があります。

www.x.org

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: ユーザに確認する

developer.chrome.com

執筆時点で確認したところ 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そのものを管理しておくのが良いと思います。

f:id:t_nakamura_ga:20200430103539p:plain
packaging crx

終わり

楽しい自動化ライフをお送りください。

その他参考リンク

github.com

developer.chrome.com

www.selenium.dev