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を実行する
eclipseからwkhtmltopdfを実行すると、エラーとなる。
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
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を押下する。
valueに/usr/local/binを追加する。
Runを押下し、Javaアプリケーションを実行する。
Spring BootでBasic認証を利用する
- 今回はSpring Bootで作ったWebアプリに対して簡易的な認証を行います。
参考
3章の5 「Spring Securityで認証・認可を追加」にSpring Securityの設定方法が色々と解説されています。
はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槇俊明
- 出版社/メーカー: 工学社
- 発売日: 2014/11
- メディア: 単行本
- この商品を含むブログ (5件) を見る
Spring Securityを依存関係に追加する
<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
書籍
はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槇俊明
- 出版社/メーカー: 工学社
- 発売日: 2014/11
- メディア: 単行本
- この商品を含むブログ (8件) を見る
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
プロジェクトルートにrepoディレクトリを作成します。
以下のMavenコマンドを実行します。
mvn deploy:deploy-file -Durl=file:///%プロジェクトルートのパス%/repo -Dfile=%ローカルのjarファイルへのパス% -DgroupId=%GROUP_ID% -DartifactId=%ARTIFACT_ID%-Dpackaging=jar -Dversion=%VERSION%
- 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");
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のものを紹介していきます。
合わせて読みたい
- 作者: Satya Avasarala,Sky株式会社玉川竜司
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/09/18
- メディア: 大型本
- この商品を含むブログ (3件) を見る
ちょっと補足
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(); } }
参考
Google Chrome
ここからは、デフォルトのFirefoxDriver以外を紹介していきます。 まずは、Google Chromeを操作するためのChromeDriverです。
ChromeDriver
Google Chromeを動作させるためのライブラリです。org.openqa.selenium.chrome.ChromeDriverというJavaの実装自体は、seleniumに同梱されているので、プロジェクトに依存関係を追加する必要はありません。
1. 実行ファイルのインストール
Google ChromeのAPIを実行するための実行ファイルをこちらからダウンロードします。
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(); } }
参考
Internet Explorer
InternetExplorerDriver
Internet Explorerを動作させるためのライブラリです。org.openqa.selenium.ie.InternetExplorerDriverというJavaの実装自体は、seleniumに同梱されているので、プロジェクトに依存関係を追加する必要はありません。
1. 実行ファイルのインストール
Internet ExplorerのAPIを実行するための実行ファイルをこちらからダウンロードします。
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(); } }