この記事は Retty Advent Calendar 16日目です。
昨日はEisukeさんのデザイナー向け - コンポーネント化について考えるでした。
スクレイピング大好きおじさんです。おはようございます。
Rettyではいろいろな技術が使われています。実際にフロントエンドでサービスを動かしているPHP/Zend Frameworkを始めとしてみんな大好き機械学習までさまざまなものを利用しています。
私はバックエンドを主に担当しています。利用されているユーザーの方々が見ていない裏側のプロセスを色々ゴニョゴニョしています。
尖った技術に関しては他の方々にお任せして、今回は私が普段使っている PhantomJS を軽くご紹介したいと思います。
Docker初心者・PhantomJS初心者向けの記事です。
兎角テストとはめんどくさいものです。
私はコードを書くのは好きなのですが、テストをするのはあまり好きではありません。
テストにはご存知のようにいくつかのフェーズがあり、単体テスト・結合テスト・システムテストなどがあります。
今から10年位前のSIer時代の話です。
テスト項目を作成し、テスト項目を上長にレビューしてもらい、テスト項目を元に画面の操作を行い、1ステップごとにキャプチャソフトで1枚ずつスクリーンショットを撮り、プリントアウトして上長に確認してもらっていた…という忌まわしい記憶が蘇ります…。
今はそんなアコギな作業をしなくてもテストを行うSolutionが! Test Frameworkがあるのです! なんと良い時代になったのでしょうか! Viva Innovation !!
導入は既に他社の方が公開されているものを参考にしました。
(主な参考元: Java+PhantomJSでサイトレスポンスを計測する : アジャイル株式会社 )
私が実行しているのはオンプレマシンに必要な環境を用意して、都度必要なタイミングで処理をまとめたスクリプトを実行しています。
上述の参考元でもJavaを使っていますが、私のPhantomJSもJavaから呼び出しています。
元々Rubyで書いてあったコードがあったのですが、チェックする対象のページが膨大で直列で実行すると1日近くかかってしまっていたということもあり、マルチスレッドで実行可能かつ自分の書きやすい言語と言うことでJavaを選びました。
そういった経緯があり、Java, PhantomJSが最低限必要なものとして上がって来ました。
実行環境はUbuntu 14.04.5なので基本 apt-get
コマンドで殆ど必要なソフトウェアはインストールすることが出来ました。
実際に私がインストール作業を行ったもの、行っていないものとあるので、Dockerで同じ環境を作ってみよう!と言うことで進めていきたいと思います!
PhantomJSの実行環境を作ろう!
Docker を手に入れよう!
公式サイトからダウンロードしてください。
英語サイトですが、別に難しいことが書いてあるわけではないのでサクッと必要なページからダウンロードしましょう!
Macの場合は Topページ → 左のメニューの Docker for Mac → Getting Started
インストールして、次はubuntuのDockerイメージを手に入れましょう。
ただ、Java8のコードはubuntuの標準パッケージとして用意されていないようで、インストールがとても煩雑で骨が折れます。
ここは横着をしてJava8がインストール済みのDokcerイメージを利用しようと思います。
$ docker pull openjdk:8
8: Pulling from library/openjdk
75a822cd7888: Pull complete
57de64c72267: Pull complete
4306be1e8943: Pull complete
1e6944bfb718: Pull complete
3521f2f45ed2: Pull complete
9c2f0d9b5f90: Pull complete
8cedcf6d2527: Pull complete
31f99da7583d: Pull complete
Digest: sha256:6bfae6cf8902a28d3f0209f5cdbb269032762a94835d331811df33498970aa7e
Status: Downloaded newer image for openjdk:8
Dockerの立ち上げ
Docker pullしてダウンロードしたDockerイメージを確認します。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
openjdk 8 a001fc27db5a 30 hours ago 643 MB
ubuntu latest 4ca3a192ff2a 2 weeks ago 128 MB
harisekhon/centos-java latest d2f082b5ad2d 3 months ago 397 MB
centos latest 970633036444 4 months ago 197 MB
tracker/cron-resource latest 8210be87b158 5 months ago 20.8 MB
redis latest 4465e4bcad80 6 months ago 186 MB
hello-world latest 693bce725149 6 months ago 967 B
nginx latest 0d409d33b27e 6 months ago 183 MB
behance/docker-gocron-logrotate latest 3b514e40da91 18 months ago 223 MB
dockerbase/cron latest df6071e77600 2 years ago 312 MB
では早速起動
$ docker run -it a001fc27db5a /bin/bash
$ java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
ついでにOSの確認
$ cat /etc/issue
Debian GNU/Linux 8 \n \l
Mavenのインストール
私はJavaのMavenというフレームワーク上でPhantomJSを実行しています。
Apache Maven(アパッチ メイヴン/メイヴェン)は、Java用プロジェクト管理ツールである。Apache Antに代わるものとして作られた。Apacheライセンスにて配布されているオープンソースソフトウェアである。
Mavenについては主題と離れてしまうので、この記事では割愛します。
apt-getコマンドからも取得できるようなのですが、パッケージの依存関係でインストールが中断されることが多いので
動作実績があるファイルをダウンロード・展開して利用します。
$ wget http://ftp.riken.jp/net/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
$ tar xzvf apache-maven-3.3.9-bin.tar.gz
PhantomJSのインストール
Maven同様、こちらも動作実績のあるファイルをダウンロード・展開して利用します。
$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
$ bzip2 -dc phantomjs-2.1.1-linux-x86_64.tar.bz2 | tar xvf -
実行してみよう!
Mavenで実行するためのライブラリの設定ファイル(pom.xml)を用意します。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>PhantomJSSampleOnMaven</groupId>
<artifactId>PhantomJS</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.github.detro.ghostdriver</groupId>
<artifactId>phantomjsdriver</artifactId>
<version>1.1.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.arnx</groupId>
<artifactId>jsonic</artifactId>
<version>1.3.10</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<mainClass>Main</mainClass>
<addClasspath>true</addClasspath>
<addExtensions>false</addExtensions>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>install</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/lib
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
そして実際にPhantomJSをJavaから呼び出すためのクラスです。前述の参考元のソースを元に若干いじったものです。
こちらはYahoo! Japanのページ内に含まれる特定のDivタグを確認するサンプルになります。
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.util.Optional;
public class Main {
private static final String PHANTOMJS_PATH = System.getenv("PHANTOM_JS_PATH");
private static final String[] PHANTOM_ARGS =
new String[]{
"--webdriver-loglevel=NONE",
"--load-images=no"
};
private static final int TIMEOUT_SECONDS = 30;
public static void main(String[] args) {
Main testEngine = new Main();
testEngine.check("http://www.yahoo.co.jp");
}
private PhantomJSDriver initDriver() {
// set Capabilities
DesiredCapabilities capabilities = DesiredCapabilities.phantomjs();
capabilities.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, PHANTOM_ARGS);
capabilities.setCapability("phantomjs.page.settings.resourceTimeout", TIMEOUT_SECONDS);
capabilities.setJavascriptEnabled(true);
System.setProperty("phantomjs.binary.path", PHANTOMJS_PATH);
PhantomJSDriver driver = new PhantomJSDriver(capabilities);
return driver;
}
public void check(String url) {
long processStartTime = System.currentTimeMillis();
PhantomJSDriver driver = null;
try {
driver = this.initDriver();
driver.get(url); // access to specified URL
waitForLoad(driver);
// Get values of Navigation Timing
long startTime = (Long) driver.executeScript("return window.performance.timing.navigationStart");
long loadEndTime = (Long) driver.executeScript("return window.performance.timing.loadEventEnd");
long responseEndTime = (Long) driver.executeScript("return window.performance.timing.responseEnd");
Optional<WebElement> tag = this.findTag(driver);
if (tag.isPresent()) {
System.out.println("Find !!");
} else {
System.out.println("Could not find ...");
}
System.out.format("Response Time : %d\n", responseEndTime - startTime);
System.out.format("PageLoad Time : %d\n", loadEndTime - startTime);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (driver != null) {
driver.quit();
}
}
if (driver != null) {
System.out.println("Destroy phantomJsDriver.");
driver = null;
}
}
private void waitForLoad(PhantomJSDriver driver) throws Exception {
ExpectedCondition<Boolean> pageLoadCondition = new
ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver driver) {
return ((JavascriptExecutor) driver).executeScript("return document.readyState").equals("complete");
}
};
try {
WebDriverWait wait = new WebDriverWait(driver, TIMEOUT_SECONDS);
wait.until(pageLoadCondition);
} catch(TimeoutException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private Optional<WebElement> findTag(PhantomJSDriver driver) {
if (driver == null) {
System.out.println("Driver is not set...");
return Optional.empty();
}
try {
WebElement element = driver.findElement(By.id("masthead"));
return Optional.ofNullable(element);
} catch (Exception e) {
return Optional.empty();
}
}
}
あとは実行するためのシェルを用意。
今回はLibディレクトリにPhantomJSとMavenのディレクトリを移動せず、解凍した場所から移動していないので環境変数にセットする必要があります。
それから mvn package
、 mvn exec:java -e
で実行することが出来ます。
#!/bin/bash
export PHANTOM_JS_PATH=/phantomjs-2.1.1-linux-x86_64/bin/phantomjs
# maven
export M3_HOME=/apache-maven-3.3.9
export M3=$M3_HOME/bin
export PATH=$M3:$PATH
mvn package
mvn exec:java -e
実行結果
$ ./run.sh
[INFO] ------------------------------------------------------------------------
[INFO] Building PhantomJS 1.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.5.0:java (default-cli) @ PhantomJS ---
Dec 15, 2016 9:16:11 AM org.openqa.selenium.phantomjs.PhantomJSDriverService <init>
INFO: executable: /phantomjs-2.1.1-linux-x86_64/bin/phantomjs
Dec 15, 2016 9:16:11 AM org.openqa.selenium.phantomjs.PhantomJSDriverService <init>
INFO: port: 1511
Dec 15, 2016 9:16:11 AM org.openqa.selenium.phantomjs.PhantomJSDriverService <init>
INFO: arguments: [--webdriver-loglevel=NONE, --load-images=no, --webdriver=1511, --webdriver-logfile=/PhantomJSSample-master/phantomjsdriver.log]
Dec 15, 2016 9:16:11 AM org.openqa.selenium.phantomjs.PhantomJSDriverService <init>
INFO: environment: {}
Find !!
Response Time : 654
PageLoad Time : 2101
Destroy phantomJsDriver.
このサンプルでは特定のDIVタグがあるかどうかを確認するだけですが、画面のキャプチャの取得も行うことも出来る上、プログラムでページごとに異なるチェックを行うなど色々広げていくことが出来ます。
私はRettyで提供しているページに特定のDivブロックが出ているか、間違ったページにお店の情報が出ていないかのチェックとして利用しています。
今回はJavaでの実行環境の作成から実施までを紹介しましたが、もちろん個人で使用しているMacにインストールして実行することも可能です。
さようなら人力テスト! こんにちはPhantomJS!
これであなたのテスト工数も激減☆(ゝω・)v