文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

xvfbとfirefoxでseleniumをヘッドレスに起動する手順

最近開発をしていて、入力フォームが沢山ある案件で、あれを修正するとあっちの画面にもこっちの画面に影響する、といった現象が連鎖してしまい、段々と影響範囲をテストできなくなってきました。これを何とかするため、ついにSelenium WebDriverを使うようになりました。


f:id:treeapps:20180418115102p:plain

このseleniumですが、ローカルで自分のマシンで起動する分にはブラウザがインストールされているので普通に起動できます。

しかしlinux等のXウインドウを起動していないコンソールのみのサーバだと、ブラウザはインストールされていないし、そもそも画面を表示できないためseleniumは起動しません。

本当ならwindowsサーバをAWS等で導入するのが最善ですが、お金がかかるのが嫌です。そこで前述のlinuxサーバでseleniumを起動する、xvfbを使ったヘッドレス(画面が無い)にseleniumを起動する方法を調べました。

手順

Xvfbについて

画面を起動できないCentOSで、Xvfbを使って仮想ディスプレイをエミュレートし、seleniumを実行します。

Xvfb はディスプレイのないサーバ機などで X Window System の画面入出力をシミュレートする X11 のサーバ・ソフトウェアである。 例えば,あるサイトのリンクにそのページ画面のサムネイル画像を掲げているのをよく目にするが,これをサーバで自動的にやろうとすると,JavaServlet などでサイトのページを取得し,ブラウザと同様にレンダリングし,画面イメージを生成するわけだ。 このとき Awt などの「ディスプレイ」の存在を仮定するライブラリを使うのが常である。 そこで,ディスプレイ・レスの UNIX サーバ機でもそれをエミュレートする,仮想フレームバッファなるインタフェースを提供するのが Xvfb である。

http://yasuda.homeip.net/insomnia/2010/04/xvfb-install.html

環境

今回はCentOS6.5で試しています。

インストールするもの

まずjdkをインストールします。

sudo yum -y install java-1.7.0-openjdk.x86_64 java-1.7.0-openjdk-devel.x86_64

続いてXvfbとfirefoxをインストールします。

sudo yum -y install xorg-x11-server-Xvfb firefox

このままだとブラウザが文字化けするので、日本語フォントをインストールします。

sudo yum -y groupinstall "Japanese Support"

設定

vi ~/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs
export DISPLAY=:1
PATH=$PATH:$HOME/bin

export PATH

「export DISPLAY=:1」が必要な設定となります。

Xvfbを起動

以下のようにダラダラコンソールが流れますが、これで無事起動します。
なお、&でバックグラウンド実行して下さい。

[vagrant@localhost selenium-test]$ Xvfb :1 -screen 0 1024x768x24 &
[vagrant@localhost selenium-test]$ _XSERVTransmkdir: Owner of /tmp/.X11-unix should be set to root
Initializing built-in extension Generic Event Extension
Initializing built-in extension SHAPE
Initializing built-in extension MIT-SHM
Initializing built-in extension XInputExtension
Initializing built-in extension XTEST
Initializing built-in extension BIG-REQUESTS
Initializing built-in extension SYNC
Initializing built-in extension XKEYBOARD
Initializing built-in extension XC-MISC
Initializing built-in extension SECURITY
Initializing built-in extension XINERAMA
Initializing built-in extension XFIXES
Initializing built-in extension RENDER
Initializing built-in extension RANDR
Initializing built-in extension COMPOSITE
Initializing built-in extension DAMAGE
Initializing built-in extension MIT-SCREEN-SAVER
Initializing built-in extension DOUBLE-BUFFER
Initializing built-in extension RECORD
Initializing built-in extension DPMS
Initializing built-in extension X-Resource
Initializing built-in extension XVideo
Initializing built-in extension XVideo-MotionCompensation
Initializing built-in extension SELinux
Initializing built-in extension GLX

firefoxを起動

firefoxも起動時にダラダラコンソールに流れますが、これで無事に起動しています。

[vagrant@localhost selenium-test]$ firefox &
[vagrant@localhost selenium-test]$ 5 XSELINUXs still allocated at reset
SCREEN: 0 objects of 232 bytes = 0 total bytes 0 private allocs
DEVICE: 0 objects of 96 bytes = 0 total bytes 0 private allocs
CLIENT: 0 objects of 152 bytes = 0 total bytes 0 private allocs
WINDOW: 0 objects of 40 bytes = 0 total bytes 0 private allocs
PIXMAP: 1 objects of 16 bytes = 16 total bytes 0 private allocs
GC: 4 objects of 16 bytes = 64 total bytes 0 private allocs
CURSOR: 0 objects of 8 bytes = 0 total bytes 0 private allocs
DBE_WINDOW: 0 objects of 24 bytes = 0 total bytes 0 private allocs
TOTAL: 5 objects, 80 bytes, 0 allocs
1 PIXMAPs still allocated at reset
PIXMAP: 1 objects of 16 bytes = 16 total bytes 0 private allocs
GC: 4 objects of 16 bytes = 64 total bytes 0 private allocs
CURSOR: 0 objects of 8 bytes = 0 total bytes 0 private allocs
DBE_WINDOW: 0 objects of 24 bytes = 0 total bytes 0 private allocs
TOTAL: 5 objects, 80 bytes, 0 allocs
4 GCs still allocated at reset
GC: 4 objects of 16 bytes = 64 total bytes 0 private allocs
CURSOR: 0 objects of 8 bytes = 0 total bytes 0 private allocs
DBE_WINDOW: 0 objects of 24 bytes = 0 total bytes 0 private allocs
TOTAL: 4 objects, 64 bytes, 0 allocs
6 XSELINUXs still allocated at reset
SCREEN: 0 objects of 232 bytes = 0 total bytes 0 private allocs
DEVICE: 0 objects of 96 bytes = 0 total bytes 0 private allocs
CLIENT: 0 objects of 152 bytes = 0 total bytes 0 private allocs
WINDOW: 0 objects of 40 bytes = 0 total bytes 0 private allocs
PIXMAP: 2 objects of 16 bytes = 32 total bytes 0 private allocs
GC: 4 objects of 16 bytes = 64 total bytes 0 private allocs
CURSOR: 0 objects of 8 bytes = 0 total bytes 0 private allocs
DBE_WINDOW: 0 objects of 24 bytes = 0 total bytes 0 private allocs
TOTAL: 6 objects, 96 bytes, 0 allocs
2 PIXMAPs still allocated at reset
PIXMAP: 2 objects of 16 bytes = 32 total bytes 0 private allocs
GC: 4 objects of 16 bytes = 64 total bytes 0 private allocs
CURSOR: 0 objects of 8 bytes = 0 total bytes 0 private allocs
DBE_WINDOW: 0 objects of 24 bytes = 0 total bytes 0 private allocs
TOTAL: 6 objects, 96 bytes, 0 allocs
4 GCs still allocated at reset
GC: 4 objects of 16 bytes = 64 total bytes 0 private allocs
CURSOR: 0 objects of 8 bytes = 0 total bytes 0 private allocs
DBE_WINDOW: 0 objects of 24 bytes = 0 total bytes 0 private allocs
TOTAL: 4 objects, 64 bytes, 0 allocs

seleniumを起動する

これでこのサーバに対してseleniumを実行すると、画面が無いのにseleniumが起動でき、スクリーンショットを取ることもできます。

制限

今のところFirefxしか起動しません。
IEはそもそもWindowsOSでしか起動しません。
chromeは・・・できるんでしょうかね。未調査です。

おまけ

不要かと思いますが、seleniumのソースコードのサンプルも載せてみます。

build.gradle

gradleを使ってユニットテストを実行します。SLF4jは余計ですが入れてます。

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.7
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

repositories {
    mavenCentral()
}

dependencies {
    // util
    compile group: 'com.google.guava', name: 'guava', version: '17.0'
    compile group: 'joda-time', name: 'joda-time', version: '2.3'
    // log
    compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
    compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.7'
    compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.7'
    compile group: 'org.slf4j', name: 'jul-to-slf4j', version: '1.7.7'
    compile group: 'org.slf4j', name: 'log4j-over-slf4j', version: '1.7.7'

    // junit
    testCompile group: 'junit', name: 'junit', version: '4.11'
    // selenium
    testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '2.43.1'
    testCompile group: 'com.vividsolutions', name: 'jts', version: '1.13'
}

テストクラス

普通にJUnitからseleniumを実行し、yahooとトップのスクリーンキャプチャを保存して終了します。

package test.selenium;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import org.joda.time.DateTime;
import org.junit.Test;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

import com.google.common.io.Files;

public class SeleniumTest {

    @Test
    public void testSelenium() {
        // ドライバを生成
        WebDriver driver = new FirefoxDriver();
        // タイムアウトを設定
        driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
        // ウインドウを最大化
        driver.manage().window().maximize();
        // 画面遷移
        driver.get("http://www.yahoo.co.jp");
        // yahooのスクリーンショットを/tmpに保存
        File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        File destFile = new File("/tmp/" + DateTime.now().toString("yyyyMMddHHmmss") + ".png");
        try {
            Files.move(srcFile, destFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // ドライバを終了
        driver.quit();
    }
}

gradleからテストしてみる

vagrantで適当に環境を作ったので、パスは適当ですが、以下のように実行すると、/tmpにスクリーンショットが保存されます!

[vagrant@localhost selenium-test]$ /tmp/gradle-2.1/bin/gradle clean test
:clean
:compileJava UP-TO-DATE
:processResources
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
> Bu:test

BUILD SUCCESSFUL

Total time: 2 mins 17.79 secs

雑感


selenium、動かすまでが大変だと思ったけど、案外簡単だなー


これでseleniumによる自動結合テストなんかができるようになるぞ。

jenkinsとかCircleCIとかからseleniumを呼ぶといいぞ。


しかしいざ結合テストクラス作ってみると、粒度に迷うんだよな〜

網羅するようなテスト書くとメンテできなくなるからな〜


そうだな。網羅系のテストにせず、重要な画面の疎通確認程度の粒度から始めるといいかもな。


ちなみにseleniumからhtmlタグも取得できるから、noindexになってるかとか、SEOのテストも一緒にできるぞ!


ふむふむ。凄いなあ。

あ、でもたまにdevelop環境の画面に本番環境のリンクがあって本番環境に遷移しちゃって、誤って本番環境でお問い合わせして、実際のお店にメールが送られて怒られる事もありそう・・・


お、いいところに気づいたな。その通りだ。

そういう事故が起きる可能性を考慮して、画面のURLを取得して、テストしていいURLでない場合はテスト失敗、等と保険をうっておいた方がいいな。


やるお「これでバグが減るといいな」

やらないお「これってテスターさんの存在意義が薄れるよな。テスターさん大ピンチだ」