Herokuでwkhtmltopdfを実行してみた

複数のBuildPackを利用できるようにする

Herokuで利用できる実行環境(コマンド)はBuildPackという単位で指定します。 デフォルトではアプリ作成時に選択した言語のものが利用されますが、カスタムのBuildPackを追加して、独自の実行環境(コマンド)をインストールすることも可能です。

heroku-buildpack-multiを利用する

アプリ作成時に以下のBuildPackを利用すると、複数のBuildPackを利用できるようになります。

heroku create %アプリ名% --buildpack git://github.com/ddollar/heroku-buildpack-multi.git

作成済みの場合は、以下のherokuコマンドで指定します。

heroku buildpacks:set https://github.com/ddollar/heroku-buildpack-multi.git

heroku-buildpack-multiの詳細は、GitHubで公開されています。

https://github.com/ddollar/heroku-buildpack-multi

上記だけだと、日本語が出力できなかったので、IPAフォントをインストールするBuildPackを作りました。 github.com

wkhtmltopdfのBuildPackを指定する

複数のBuildPackが利用できるようになったので、開発言語に加えてwkhtmltopdfのBuildPackを指定します。 指定は、ルートに.buildpacksという隠しファイルを作成して行います。

.buildpacksの指定

デフォルトのjavaに加えて、GitHubで公開されていたheroku-buildpack-wkhtmltopdfを利用します。

https://github.com/heroku/heroku-buildpack-java.git
https://github.com/momotaro-lucy/heroku-buildpack-wkhtmltopdf-ja.git

heroku-buildpack-wkhtmltopdfの詳細は、以下のGitHubで公開されています。 https://github.com/toncid/heroku-buildpack-wkhtmltopdf

ただ、issueを読んでるとバージョンが上がっていくごとに不安定そうにも見えたので、今後は自分でBuildPackを作成するのを検討中です。

インストール確認

インストールが成功したことを確認します。

確認コマンド

heroku run "wkhtmltopdf -V" --app %アプリ名%
wkhtmltopdf 0.12.2.1 (with patched qt)

Macのeclipseからwkhtmltopdfを実行する

  • どうやら、Maceclipseでは、/usr/local/binにPATHが通ってないようで、ちょっとはまりました。

eclipseからwkhtmltopdfを実行すると、エラーとなる。

  • 以下のようなjavaファイルを作成し、eclipseからJavaアプリケーションとして実行します。
package wkhtmltopdf_java;

public class WkhtmltopdfTest {

    public static void main(String[] args){
        try {
            ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf", 
                    "https://www.google.co.jp/webhp?hl=ja",
                    "/Users/test/output.pdf");
            Process process = pb.start();
            int ret = process.waitFor();
            System.out.println(ret);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
java.io.IOException: Cannot run program "wkhtmltopdf": error=2, No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
    at wkhtmltopdf_java.WkhtmltopdfTest.main(WkhtmltopdfTest.java:10)
Caused by: java.io.IOException: error=2, No such file or directory
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:248)
    at java.lang.ProcessImpl.start(ProcessImpl.java:134)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    ... 1 more

f:id:unagi_tabetai:20150812233306p:plain

wkhtmltopdfをインストールされた/usr/local/binをeclipseのPATHに通す

Javaアプリケーションの実行構成からEnvironmentタブを表示する

  • Javaファイルで右クリックして、「Run as」の「Run Configuration」を選択する。

  • 表示されたダイアログにて、選択したJavaクラスをダブルクリックする。

  • 右の領域が切り替わるので、Environmentタブを選択する。

Environmentタブにて/usr/local/binをPATHに追加する

  • Selectを押下する。

  • Select Environment Variablesダイアログが表示されるので、PATHにチェックを入れて、OKを押下する。

  • PATHを選択して、Editを押下する。

f:id:unagi_tabetai:20150812233311p:plain

  • valueに/usr/local/binを追加する。

  • Runを押下し、Javaアプリケーションを実行する。

Spring BootでBasic認証を利用する

  • 今回はSpring Bootで作ったWebアプリに対して簡易的な認証を行います。

参考

3章の5 「Spring Securityで認証・認可を追加」にSpring Securityの設定方法が色々と解説されています。

Spring Securityを依存関係に追加する

  • basic認証には、Spring Boot版のSpring Securityを利用します。
    • Spring Securityが追加されることで、すべてのURLに対してbasic認証が適用されます。
<dependency>
    <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Basic認証のアカウントを設定する

  • デフォルトの設定では、basic認証のアカウントは固定の"user"となります。パスワードは起動時に以下のようにログ出力されます。
Using default security password: %パスワード%
  • main/resources/application.propertiesに以下のように設定可能です。
security.user.name=%ユーザーコード%
security.user.password=%パスワード%

Basic認証の対象範囲を設定する

  • デフォルトの設定では、すべてのURLがbasic認証の対象となります。

  • basic認証の対象を制限するには、main/resources/application.propertiesに以下のように設定します。

security.basic.path=/admin/**

Spring BootのWebアプリをherokuで動かす

  • 最近、herokuで個人プロジェクトのWebアプリを作って遊んでます。
  • 今回は、その中でSpring Bootの形式で作ったWebアプリを動かすときにやったことを記録したので、メモ代わりにここに挙げておきます。

参考

Heroku公式解説「Deploying Spring Boot Applications to Heroku」

Deploying Spring Boot Applications to Heroku | Heroku Dev Center

GitHub - kissaten/spring-boot-heroku-demo: Demo project showing how to deploy a Spring Boot application to Heroku

書籍

spring-boot-maven-plugin

Spring Boot Maven Plugin – Usage

  • herokuのjava開発では、デフォルトではMavenを使ってビルドします。
  • ここでは、Mavenプラグイン「spring-boot-maven-plugin」を利用して、herokuのProfileファイルに指定する実行可能なjarファイルを作成します。

  • 下記の設定を追加することで、dependencyに指定したjarを含む実行可能なjarを作成してくれます。

<build>
  <plugins>
   <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
</build>

Profile

  • 作成した実行可能なjarファイルの実行コマンドを記述します。
web: java -Dserver.port=$PORT -jar  target/〜.jar

[注意]ローカルのjarファイルを利用する場合

セントラルリポジトリにないjarファイル(社内開発など)を利用する場合は、systemスコープではなくローカルリポジトリを利用します。 systemスコープのjarはspring-boot-maven-pluginで収集してもらえません。

ローカルのjarを配置したローカルリポジトリを作成し、それをpom.xmlにて指定してビルドすることで、spring-boot-maven-pluginがビルド時に実行可能なjarに含めてくれます。

Adding Unmanaged Dependencies to a Maven Project | Heroku Dev Center

  1. プロジェクトルートにrepoディレクトリを作成します。

  2. 以下のMavenコマンドを実行します。

mvn deploy:deploy-file -Durl=file:///%プロジェクトルートのパス%/repo -Dfile=%ローカルのjarファイルへのパス% -DgroupId=%GROUP_ID% -DartifactId=%ARTIFACT_ID%-Dpackaging=jar -Dversion=%VERSION%
  1. pom.xmlにrepoディレクトリを指定します。
<repositories>
    <!--other repositories if any-->
    <repository>
        <id>project.local</id>
        <name>project</name>
        <releases>
        <checksumPolicy>ignore</checksumPolicy>
    </releases>
        <url>file:${project.basedir}/repo</url>
    </repository>
</repositories>

FluentLeniumでSeleniumの使いにくさを解消。

今回は、Selenium2 WebDriverでの自動テストの開発をさらに楽にしてくれるラッパーフレームワーク FluentLeniumを紹介します。 ラッパーなので、純粋なSelenium2 WebDriverに比べると、制限されることや動作スピードが落ちたりはしますが、名前の通りさくさく流れるように開発することができるようになります。

FluentLeniumの特徴

jQueryライクなセレクタ

jQueryセレクタ記法をそのまま利用した操作対象の要素の特定が可能です。 jQueryを普段利用しているフロントエンドの開発者なら、簡単にテストコードを書くことができます。

  • id属性で指定
$("#login-button").click();
  • name属性で指定
$("input[name=password]").text("foo");
  • class属性で指定した要素の下の階層にある特定のタグ要素を操作
$(".sample-class").find("span").click();

流れるインターフェース

いわゆる「流れるインターフェース」が意識されたAPIとして設計されているので、各APIの細かい部分を覚えていなくてもIDEのコード補完で直感的に開発可能です。

// id
goTo("/login").await().until("#user_cd").areDisplayed()
    .find("#user_cd").text("foo");

セットアップ

Mavenプロジェクトの場合、下記のdependencyをpom.xmlに追加します。 注意点としては、これらのプロジェクトの依存関係にjunitは入っていないので、junitアノテーションを利用するにはjunit自体も依存関係に追加する必要があります。

<dependency>
    <groupId>org.fluentlenium</groupId>
    <artifactId>fluentlenium-core</artifactId>
    <version>0.10.3</version>
</dependency>
<dependency>
    <groupId>org.fluentlenium</groupId>
    <artifactId>fluentlenium-assertj</artifactId>
    <version>0.10.3</version>
</dependency>
<dependency>
    <groupId>org.fluentlenium</groupId>
    <artifactId>fluentlenium-testng</artifactId>
    <version>0.10.3</version>
</dependency>

基本的な利用法

org.fluentlenium.adapter.FluentTestの継承

テストコードとして記述していくには、org.fluentlenium.adapter.FluentTestクラスを継承します。

import org.fluentlenium.adapter.FluentTest;

public class SampleFluentLenium extends FluentTest {
    ・
    ・
    ・
}

ドライバーの指定

デフォルトではFirefoxDriverが読み込まれますが、getDefaultDriverをオーバーライドすることで独自にWebDriverのインスタンスを作ることができます。

    @Override
    public WebDriver getDefaultDriver() {
    System.setProperty("webdriver.chrome.driver",
        "/Users/xxxx/Downloads/chromedriver");
    return new ChromeDriver();
    }

JavaDoc FluentTest#getDefaultDriver())

ページ移動

FluentTest#goToにてアクセスしたいURLを指定します。 テストケースにおいてベースとなるURLが決まっている場合は、FluentTest#withDefaultUrlにベースURLを指定し、FluentTest#goToでは相対URLとして記述します。

  • 絶対URLでのアクセス
   goTo("https://www.google.co.jp/");
  • ベースURLからの相対URLでのアクセス
   withDefaultUrl("http://b.hatena.ne.jp/");
    goTo("/it");

JavaDoc

Fluent#goTo(java.lang.String))

Fluent#withDefaultUrl(java.lang.String))

要素の特定方法

色々とやり方がありますが、ここではFluentTest#$とFluentTest#findを紹介します。 両方ともjQuery記法がそのまま使えます。

FluentTest#$
    $("input[name=password]").text("foo");
FluentTest#find

$のセレクタで特定した要素の子階層から絞り込むために利用します。 jQueryのようにメソッドチェーンでどんどん指定していくことができます。要素を特定したら、アクションを発火します。

    $(".sample-class").find("span").click();

Wait処理

FluentTest#awaitを利用し、取得したFluentWaitのインスタンスにて細かい条件を付けていきます。

タイムアウトを設定した上で、要素を特定した条件を付けます。ここでも、要素の特定にはjQueryの記法が使えます。

await().atMost(5, TimeUnit.SECONDS).until(".small").hasSize(3);

また、FluentWait#untilが戻すFluentWaitMatcherのインスタンスにて様々な条件が設定可能です。

  • hasText("myTextValue")

  • isPresent()

  • isNotPresent()

  • hasId("myId")

  • hasName("myName")

  • containsText("myName")

  • areDisplayed()

  • areEnabled()

JavaDoc FluentTest#await FluentWait FluentWaitMatcher

PageObjectsパターン

Seleniumでは画面ごとにクラスを作成して、テストコードではそのクラスのインスタンスを操作するだけというPageObjectsのデザインパターンがよく利用されますが、FluentLeniumのAPIはPageObjectsパターンを想定して作られています。

1. PageObjectのクラスを作成します。

org.fluentlenium.core.FluentPageクラスを継承して作成します。 FluentPage#getUrlをオーバーライドし、画面のURLを返却させます。

import org.fluentlenium.core.FluentPage;

/**
 * ログイン画面を表すPage Objectクラス。
 */
public class LogicPage extends FluentPage {
    
    @Override
    public String getUrl(){
        return "http://xxx.xxxx/login";
    }
    
    /**
     * 指定された認証情報でログインを行います。
     * @param userCd
     * @param password
     */
    public void login(String userCd, String password){
    await().until("#user_cd").areDisplayed()
    .find("#user_cd").text(userCd);
    
        $("#password").text(password);
        $(".btn-login").click();
    }
    

}
2. テストコードにてPageObjectのクラスを呼び出し、操作します。

@Pageアノテーションを指定したインスタンス変数を宣言することで、FluentTest側で適切にPageObjectのインスタンスを生成してくれます。 PageObjectが提供する画面にアクセスするには、FluentTest#goTo(P page)を利用します。

import org.fluentlenium.adapter.FluentTest;
import org.fluentlenium.core.annotation.Page;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class SampleFluentLenium extends FluentTest {
    
    @Page
    public LogicPage loginPage;
            
    @Test
    public void testExecute() {
    
    goTo(loginPage);
    loginPage.login("user_cd", "password");
    }
}
3. 注意事項

テストコード側のクラスにてwithDefaultUrlを使ってベースURLをセットしても、PageObjectのgetUrlのURLにアクセスする際には適用されません。

参考

【Selenium】色んなブラウザを動かす(PC編)

Seleniumの各ブラウザ対応

Selenium 1.0ではクライアントサイドJavaScriptの実行によりブラウザを操作していましたが、Selenium 2.0 WebDriverでは各ブラウザのAPIを呼び出すことで操作を行います。そのため、SeleniumがリリースしているWebDriverのライブラリだけではフルブラウザ対応できません。

org.openqa.selenium.WebDriverというインターフェースに関して各ベンダーもしくはどこぞの親切な人が実装を提供しているので、そちらをセットアップする必要が有ります。今回はいくつか提供されているその実装のうち、Google Chrome, Internet Explorerのものを紹介していきます。

合わせて読みたい

実践 Selenium WebDriver

実践 Selenium WebDriver

ちょっと補足

Selenium以外に必要なライブラリは下記の2パターンあります。

org.openqa.selenium.WebDriverインターフェースの実装

FirefoxDriverやChromeDriverのように実装自体がSelenium WebDriverに含まれているものもありますが、ios-driverのように3rdパーティーライブラリもあります。その場合は、Javaのビルドパスの解決からしなければいけません。

各ブラウザのAPIを実行するための実行ファイル

Javaのビルドパスの解決に加えて、各プラットフォーム向けの実行ファイルをセットアップする必要があります。

Selenium WebDriverおさらい

ここで、ちょっとWebDriver自体のおさらいをします。

セットアップ

Mavenプロジェクトの場合、下記のdependencyをpom.xmlに追加します。

<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>2.45.0</version>
</dependency>

Jarで直接指定する場合は、ダウンロードページからselenium-javaのzipをダウンロードし、selenium-java-x.x.x.jarを利用します。libs配下には、selenium-javaが依存するjarが同梱されています。

FirefoxDriver

実際にブラウザを動かすには、下記のようにFirefoxDriverを利用してWebDriverのインスタンスを作ります。FirefoxDriverはプラットフォーム内にFirefoxがインストールされているだけで利用可能です。このコードでは、Firefoxが起動してGoogleのトップページを表示します。

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

public class FirefoxTest {
    
    public static void main(String[] args){
         WebDriver driver = new FirefoxDriver();
         driver.get("https://www.google.co.jp/");
         driver.quit();
    }
}

RemoteWebDriver

ブラウザがインストールされているプラットフォームとテストコードを実行するプラットフォームが分かれている場合は、RemoteWebDriverを利用します。仕組みとしては、ブラウザがインストールされているプラットフォームにSelenium Serverを立て、テストコードからRemoteWebDriverを通してHTTP通信にてブラウザをリモート操作します。

1. Selenium Server

ブラウザがインストールされているプラットフォームにSelenium Serverを立てます。ダウンロードページからselenium-server-standalone-x.x.x.jarをダウンロードし、コマンドラインから起動します。

java -jar selenium-server-standalone-x.x.x.jar

2. RemoteWebDriver

org.openqa.selenium.remote.RemoteWebDriverクラスを通して、WebDriverのインスタンスを作ります。RemoteWebDriverのコンストラクタ第一引数には、Selenium ServerのURLを、第二引数にはブラウザの種類を指定します。

import java.net.MalformedURLException;
import java.net.URL;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

public class FirefoxRemoteTest {
    
    public static void main(String args[]) throws MalformedURLException{
        DesiredCapabilities firefox = DesiredCapabilities.firefox();
        WebDriver driver = new RemoteWebDriver(new URL("http://x.x.x.x:4444/wd/hub"), firefox);
        driver.get("https://www.google.co.jp/");
        driver.quit();
    }
}

参考

WebDriverのJavaDoc

RemoteWebDriverServer

Google Chrome

ここからは、デフォルトのFirefoxDriver以外を紹介していきます。 まずは、Google Chromeを操作するためのChromeDriverです。

ChromeDriver

Google Chromeを動作させるためのライブラリです。org.openqa.selenium.chrome.ChromeDriverというJavaの実装自体は、seleniumに同梱されているので、プロジェクトに依存関係を追加する必要はありません。

1. 実行ファイルのインストール

Google ChromeAPIを実行するための実行ファイルをこちらからダウンロードします。

2. システムプロパティ webdriver.chrome.driverの指定

実行ファイルのインストールパスをシステムプロパティ webdriver.chrome.driverで指定します。指定できていれば、あとはGoogle Chromeがプラットフォームにインストールされていれば、利用可能です。

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class ChromeTest {
    
    public static void main(String args[]){
          System.setProperty("webdriver.chrome.driver", "/Users/xxxx/Downloads/chromedriver");

          WebDriver driver = new ChromeDriver();
          driver.get("https://www.google.co.jp/");
          driver.quit();
    }
}

ブラウザバージョンとの組み合わせに関して

ChromeDriverの実行ファイルは、ご利用のGoogle Chromeのバージョンに合わせて適切な組み合わせを選ぶ必要が有ります。また、Google Chrome自体は自動アップデートしていくので、開発環境のChromeDriverも追随していく必要が有ります。

Google Chromeのバージョンとの組み合わせは、実行ファイルのダウンロードページnotes.txtに記載されています。最新のGoogle Chrome 43をサポートするChromeDriver実行ファイルは2.15です。

リモート実行

RemoteWebDriverを利用してリモートで実行するには、Selenium Serverの起動時にChromeDriverの実行ファイルパスを指定する必要があります。

java -jar selenium-server-standalone-x.x.x.jar -Dwebdriver.chrome.driver=/Users/xxxx/Downloads/chromedriver

サンプルコード

import java.net.MalformedURLException;
import java.net.URL;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

public class ChromeTest {
    
    public static void main(String args[]) throws MalformedURLException{
        DesiredCapabilities chrome = DesiredCapabilities.chrome();
        WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), chrome);
        driver.get("https://www.google.co.jp/");
        driver.quit();
    }
}

参考

ChromeDriver RemoteWebDriver

Internet Explorer

InternetExplorerDriver

Internet Explorerを動作させるためのライブラリです。org.openqa.selenium.ie.InternetExplorerDriverというJavaの実装自体は、seleniumに同梱されているので、プロジェクトに依存関係を追加する必要はありません。

1. 実行ファイルのインストール

Internet ExplorerAPIを実行するための実行ファイルをこちらからダウンロードします。

2. システムプロパティ webdriver.chrome.driverの指定

実行ファイルのインストールパスをシステムプロパティ webdriver.ie.driverで指定します。指定できていれば、あとはInternet Explorerがプラットフォームにインストールされていれば、利用可能です。

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;

public class IETest {

    public static void main(final String[] args) {
        System.setProperty("webdriver.ie.driver", "C://Users/xxxx/Downloads/IEDriverServer_x64_2.45.0/IEDriverServer.exe");

        final WebDriver driver = new InternetExplorerDriver();
        driver.get("https://www.google.co.jp/webhp?hl=ja");
        driver.quit();
    }
}

リモート実行

RemoteWebDriverを利用してリモートで実行するには、Selenium Serverの起動時にInternetExplorerDriverの実行ファイルパスを指定する必要があります。

java -jar selenium-server-standalone-x.x.x.jar -Dwebdriver.ie.driver=C://Users/xxxx/Downloads/IEDriverServer_x64_2.45.0/IEDriverServer.exe

サンプルコード

import java.net.MalformedURLException;
import java.net.URL;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

public class IETest {

    public static void main(final String[] args) throws MalformedURLException {
        final DesiredCapabilities internetExplorer = DesiredCapabilities.internetExplorer();
        final WebDriver driver = new RemoteWebDriver(new URL("http://x.x.x.x:4444/wd/hub"), internetExplorer);
        driver.get("https://www.google.co.jp/webhp?hl=ja");
        driver.quit();
    }
}

【wkhtmltopdf】マニュアル

バージョン マニュアル 備考
wkhtmltopdf 0.9.6 マニュアルあり Linux版でPOSTパラメータオプションが無効・1ページに縮小されて印刷される。
wkhtmltopdf 0.12.0 マニュアルあり 改ページされるようになった。
  • もっとマイナーバージョンのマニュアルは、こちら