Asial Blog

Recruit! Asialで一緒に働きませんか?

簡単・便利、ブラウザの自動操作!~Selenium WebDriver~

カテゴリ :
バックエンド(プログラミング)
タグ :
Tech
HTML5
テスト
Android

はじめに



今回はSelenium WebDriverをご紹介します。ブラウザ操作を自動化する際には最適な仕組みです。Webシステムのend-to-endテストを自動化する際には、ブラウザ操作が必要になることがあります。そんな時にSelenium WebDriverはとても便利です。


Selenium



Seleniumとは、ブラウザをプログラムで動かすフレームワークです。この仕組みを使うことで、ユーザーテストなど、様々な処理を自動化できます。現在のところ、Seleniumは以下のWebブラウザを制御できます(公式サイト)。

  • Internet Explorer
  • Firefox
  • Chrome
  • Opera
  • Android標準Webブラウザ
  • Safari (iPhone標準Webブラウザ)

実際に使用する際には、以下の2つの仕組みのどちらかを使用します。

Selenium IDEはFirefoxのアドオンです。Firefox上でユーザー操作を記録します。この処理を再度実行したり、WebDriverのコードへ変換できます。バグを再現させる場合など、手軽に使えて非常に便利です。

Selenium WebDriverとは、ブラウザの拡張機能やOSネイティブの機能を使ってブラウザを操作するライブラリ群、もしくはその仕組みのことです。プログラムに記述した通りにブラウザを操作できます。Java、Python、Ruby、C#のいずれかを使用します(公式提供)。ブラウザを使用した自動テストを詳細に作成する場合などに便利です。

今回は、ブラウザ制御を全て自動化できるSelenium WebDriverを使ってみます。Javaを使って、以下の2つのことをしてみましょう。

  • PC上でのブラウザ操作
  • Android端末(実機)上でのブラウザ操作

画面遷移、JavaScriptのクリックイベント実行、JUnitとの組み合わせなども試してみます。


プロジェクト作成



まずは、Javaのパッケージを下記サイトからダウンロードします。2013年7月5日現在では、selenium-java-2.33.0.zipが最新版のようです。

http://docs.seleniumhq.org/download/

ダウンロードしたファイルを展開すると、無意味に深い階層が現れます。最後のselenium-2.33.0ディレクトリをそのままeclipseインストールディレクトリのdropinsディレクトリへ移動しておきます。
  1. selenium-2.33.0
  2.  selenium-2.33.0
  3.    └ selenium-2.33.0
  4.        ├ libs
  5.        ├ selenium-java-2.33.0.jar
  6.        └ selenium-java-2.33.0-srcs.jar
次に、プロジェクトを作成します。Javaプロジェクトをeclipseで作成し、SeleniumのJARファイルをプロジェクトへ追加します。JARの追加方法は以下の通りです。

  1. プロジェクトを右クリック
  2. Java Build Pathを選択
  3. Add External JARsをクリック
  4. eclipse/dropins内のselenium-java-2.33.0.jarとlibs内の全てのjarを追加

ここまで出来たら、後はコードを書いてブラウザを動かしてみるだけです!

[補足] プロジェクト自体にjarを追加することもできます。以下のようにすると、jarがプロジェクトに追加され、プロジェクト単体で処理を完結できます。

  1. プロジェクトディレクトリへselenium-2.33.0ディレクトリをコピー
  2. プロジェクトを右クリック
  3. Java Build Pathを選択
  4. Add JARsをクリック
  5. プロジェクト内のselenium-java-2.33.0.jarとlibs内の全てのjarを追加


PCブラウザ操作



ブラウザの起動・終了



まずは、ブラウザを単純に開いて閉じることをしてみましょう。ここではアシアルのサイトを見てみます。今回はFirefoxを使います。
  1. package jp.co.asial.test;
  2.  
  3. import org.openqa.selenium.WebDriver;
  4. import org.openqa.selenium.firefox.FirefoxDriver;
  5.  
  6. public class AsialHpTest {
  7.  
  8.     public static void main(String[] args) {
  9.         WebDriver driver = new FirefoxDriver();
  10.         driver.get("http://www.asial.co.jp");
  11.         driver.quit();
  12.     }
  13. }
WebDriverのget()メソッドで画面を開き、quite()で閉じます。後は、Java Applicationとして実行します。すると、しばらくしてFirefoxが立ち上がり、アシアルのサイトが表示され、すぐにブラウザが閉じると思います。起動するまでに若干時間がかかります。

画面遷移



せっかくブラウザを開いたのだから、何らかの操作をしてみます。画面のリンクをクリックし、別の画面へ遷移してみます。下記のコードでは、各処理の間に1秒間処理を止めています。これを入れないと、処理が速すぎて何をしているかよく分からないためです。もちろん、テストを運用する時には外します。
  1. package jp.co.asial.test;
  2.  
  3. import org.openqa.selenium.By;
  4. import org.openqa.selenium.WebDriver;
  5. import org.openqa.selenium.WebElement;
  6. import org.openqa.selenium.firefox.FirefoxDriver;
  7.  
  8. public class AsialHpTest {
  9.     public static void main(String[] args) {
  10.         WebDriver driver = new FirefoxDriver();
  11.         driver.get("http://www.asial.co.jp");
  12.         sleep(1000);
  13.         driver.findElement(By.cssSelector("ul.globalMenuNormal li:first-child")).click();
  14.         sleep(1000);
  15.         driver.quit();
  16.     }
  17.     
  18.     private static void sleep(int microtime) {
  19.         try {
  20.             Thread.sleep(microtime);
  21.         } catch (InterruptedException e) {
  22.             e.printStackTrace();
  23.         }
  24.     }
  25. }
ここでは、WebDriverのfindElement()メソッドとBy.cssSelector()メソッドを使ってCSSセレクタから対象要素(WebElement)を取得しています。そして、その要素(Aタグ)をクリックしています。WebDriverがこのクリックをブラウザ上で実行することで、実際に画面遷移が行われます。JavaScriptを使えるエンジニアであれば、サクサク記述できると思います。

ちなみに、WebDriverのfindElement()メソッドでは、WebElementインタフェースを実装したオブジェクトを取得できます。そのオブジェクトを使えば、要素が現在見えているのか否か、どこに表示されているか、要素の表示サイズ、などを取得できます。例えば以下のように記述できます。

  1. WebElement element = driver.findElement(By.cssSelector("ul.globalMenuNormal li:last-child"));
  2. System.out.println("displayed = " + String.valueOf(element.isDisplayed()));
  3. System.out.println("enabled   = " + String.valueOf(element.isEnabled()));
  4. System.out.println("position  = " + element.getLocation());
  5. System.out.println("dimension = " + element.getSize());
  6. System.out.println("font-size = " + element.getCssValue("font-size"));

JavaScript実行



次に、JavaScriptのクリックイベントを実行してみます。アシアルのサイトでは、サイト上部の画像の両脇のボタンを押すことで、画像を左右に移動して変更できます。プログラムからこの操作をしてみたいと思います。
  1. package jp.co.asial.test;
  2.  
  3. import org.openqa.selenium.By;
  4. import org.openqa.selenium.WebElement;
  5. import org.openqa.selenium.firefox.FirefoxDriver;
  6. import org.openqa.selenium.remote.RemoteWebDriver;
  7.  
  8. public class AsialHpTest {
  9.     public static void main(String[] args) {
  10.         RemoteWebDriver driver = new FirefoxDriver();
  11.         
  12.         driver.get("http://www.asial.co.jp");
  13.         sleep(1000);
  14.         WebElement element = driver.findElement(By.cssSelector("ul.flex-direction-nav a.prev"));
  15.         
  16.         for (int i=0; i<5; i++) {
  17.             driver.executeScript("arguments[0].click();", element);
  18.             sleep(1000);
  19.         }
  20.         
  21.         driver.quit();
  22.         
  23.     }
  24.     
  25.     private static void sleep(int microtime)
  26.     {
  27.         // 省略
  28.     }
  29. }
ポイントは、RemoteWebDriverのexecuteScriptメソッドを使用している点です。RemoteWebDriverはWebDriverインタフェースを実装したクラスです。さらに、RemoteWebDriverはJavascriptExecutorインタフェースも実装しており、ここからJavaScriptを処理できます。FirefoxDriverはRemoteWebViewのサブクラスです。

上記コードにて、element(WebElementオブジェクト)のclick()メソッドを実行するとエラーとなります。

もちろん、WebDriverインタフェースの形でも同様のことをできます。
  1. public class AsialHpTest {
  2.     public static void main(String[] args) {
  3.         WebDriver driver = new FirefoxDriver();
  4.         JavascriptExecutor executor = (JavascriptExecutor)driver;
  5.         
  6.         ...
  7.         
  8.         for (int i=0; i<5; i++) {
  9.             executor.executeScript(...);
  10.         }
  11.         
  12.         ...
  13.     }
  14.     
  15.     ...
  16. }

スクリーンショット



スクリーンショットも軽々と撮れます。テスト結果の検証物件として納品する場合に便利そうです。
  1. package jp.co.asial.test;
  2.  
  3. import java.io.File;
  4. import java.io.IOException;
  5.  
  6. import org.apache.commons.io.FileUtils;
  7.  
  8. import org.openqa.selenium.OutputType;
  9. import org.openqa.selenium.TakesScreenshot;
  10. import org.openqa.selenium.WebDriver;
  11. import org.openqa.selenium.WebDriverException;
  12. import org.openqa.selenium.firefox.FirefoxDriver;
  13.  
  14. public class AsialScreenShot {
  15.     public static void main(String[] args) {
  16.         WebDriver driver = new FirefoxDriver();
  17.         driver.get("http://www.asial.co.jp");
  18.         
  19.         try {
  20.             FileUtils.copyFile(
  21.                     ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE),
  22.                     new File("path/to/file"));
  23.         } catch (WebDriverException e) {
  24.             e.printStackTrace();
  25.         } catch (IOException e) {
  26.             e.printStackTrace();
  27.         }
  28.         
  29.         driver.quit();
  30.     }
  31. }

+JUnit



PCブラウザ操作の最後に、JUnitと組み合わせて使用してみます。下記コードをRun As => JUnit Testから実行します(Eclipse JUnit Launcher)。JUnitがテストケースを順番に実行してくれます。また、Assertを使用して画面URLや各要素を確認することもできます。ここまで来ると、Selenium WebDriverを使えばテスト自動化もサクサクできそうな感じがします。

  1. package jp.co.asial.test;
  2.  
  3. import org.junit.After;
  4. import org.junit.Assert;
  5. import org.junit.Before;
  6. import org.junit.Test;
  7. import org.openqa.selenium.By;
  8. import org.openqa.selenium.WebDriver;
  9. import org.openqa.selenium.firefox.FirefoxDriver;
  10.  
  11. public class AsialHpJUnitTest {
  12.     private WebDriver driver;
  13.  
  14.     @Before
  15.     public void beforeTest() {
  16.         driver = new FirefoxDriver();
  17.     }
  18.  
  19.     @After
  20.     public void afterTest() {
  21.         sleep(1000); // 1秒後にブラウザを閉じる(動きを確認するため)
  22.         driver.quit();
  23.     }
  24.     
  25.     // トップ画面を開くだけ
  26.     @Test
  27.     public void open() {
  28.         driver.get("http://www.asial.co.jp");
  29.     }
  30.     
  31.     // スクール画面へ遷移する
  32.     @Test
  33.     public void moveToSchoolPage()
  34.     {
  35.         driver.get("http://www.asial.co.jp");
  36.         driver.findElement(By.cssSelector("ul.globalMenuNormal li:last-child")).click();
  37.         Assert.assertEquals("http://www.asial.co.jp/school/", driver.getCurrentUrl());
  38.     }
  39.     
  40.     private void sleep(int microtime)
  41.     {
  42.         try {
  43.             Thread.sleep(microtime);
  44.         } catch (InterruptedException e) {
  45.             e.printStackTrace();
  46.         }
  47.     }
  48. }


Androidブラウザ操作



最後にAndroid端末上でブラウザを操作してみます。AndroidDriverを使うことで、自分達のサイトがAndroidブラウザ上で正しく動いているか、自動化されたend-to-endテストを実行できます。多種多様なAndroid端末が出回っているため、これが出来るととても楽になりそうです。

Androidでのブラウザ操作もしくはテストには2つの方法があります(参考)。

  • リモートサーバを使用する方法
  • Androidテストフレームワークを使用する方法

前者は、様々な端末で同じテストを実行したい場合に使うと便利です。後者は、既にAndroidテストフレームワークを使用ていて、かつ特定の端末で素早いテストを実行したい場合に便利です。今回は前者の方法でブラウザ操作を実行してみたいと思います。

PC・端末の設定



リモートサーバといっても、server=Android端末、client=自分のPCという構成です。Android端末とPCをそれぞれ設定していきます。端末の設定はとても簡単で、3つのことをするだけです。

  • 端末をデバッグ可能モードでPCへUSB接続
  • Androidアプリ「WebDriver」をAndroid端末へインストール
  • WebDriverアプリを起動

Androidアプリ「WebDriver」がリモートサーバーとなるアプリケーションです。このアプリを使用してブラウザ操作を行います(正確にはWebView)。以下のサイトから、android-server-2.32.0.apkをダウンロードしてインストールします。

https://code.google.com/p/selenium/downloads/list

現在のところ、Android2.3.x、3.x、4.0.x以上で使用可能です。インストールが終わったら、アプリを起動します。後はUSBでPCへ接続し、eclipseから認識できていれば大丈夫です。

次にPCの設定を行います。PCの設定は以下の2つです。

  • Android SDK のインストール
  • ABD用USBドライバのインストール
  • ポートフォワード設定

Android SDKとUSBドライバのインストールはSDK Managerから実行できます。ポートフォワード設定を行うには、端末のデバイスIDが必要です。上記のように端末をPCに接続した状態で、eclipseのDDMSからデバイスIDを確認できます。もしくはadbコマンドを以下のように実行すると、PCに接続している端末のデバイスID(16桁)を取得できます。
  1. $ adb devices
ポートフォワードの設定は次の通りです。[DeviceId]の部分に端末のデバイスIDを入れます。
  1. $ adb -s [DeviceId] forward tcp:8080 tcp:8080
これで準備は終わりです。

ブラウザ操作



では、Android端末のブラウザ操作を行ってみます。アシアルのサイトを表示し、上部リンクの位置等をコンソールに出力します。その後、200px上部へスクロールを実施。先のリンクの情報を再度コンソールに出力(位置情報が変わっている)。最後に「セミナー&スクール」の画面へ遷移します。
  1. package jp.co.asial.test;
  2.  
  3. import org.openqa.selenium.By;
  4. import org.openqa.selenium.WebDriver;
  5. import org.openqa.selenium.WebElement;
  6. import org.openqa.selenium.android.AndroidDriver;
  7.  
  8. public class AsialHpAndroidTest {
  9.     public static void main(String[] args) {
  10.         AndroidDriver driver = new AndroidDriver();
  11.         
  12.         driver.get("http://www.asial.co.jp");
  13.         check(driver, "ul.globalMenuNormal li:last-child");
  14.         sleep(1000);
  15.         
  16.         driver.getTouch().scroll(0, 200);
  17.         check(driver, "ul.globalMenuNormal li:last-child");
  18.         sleep(1000);
  19.         
  20.         driver.findElementByCssSelector("ul.globalMenuNormal li:last-child").click();
  21.         
  22.         System.out.println("Page title: " + driver.getTitle());
  23.         System.out.println("Page URL: " + driver.getCurrentUrl());
  24.     }
  25.     
  26.     private static void check(final WebDriver driver, final String selector){
  27.         final WebElement element = driver.findElement(By.cssSelector(selector));
  28.         System.out.println(selector);
  29.         System.out.println("displayed = " + String.valueOf(element.isDisplayed()));
  30.         System.out.println("enabled   = " + String.valueOf(element.isEnabled()));
  31.         System.out.println("position  = " + element.getLocation());
  32.         System.out.println("dimension = " + element.getSize());
  33.         System.out.println("font-size = " + element.getCssValue("font-size"));
  34.         System.out.println("");
  35.     }
  36.     
  37.     private static void sleep(int microtime)
  38.     {
  39.         try {
  40.             Thread.sleep(microtime);
  41.         } catch (InterruptedException e) {
  42.             e.printStackTrace();
  43.         }
  44.     }
  45. }
コンソールへの出力結果は次のようになります。positionのy座標が200減っていることがわかります。
  1. ul.globalMenuNormal li:last-child
  2. displayed = true
  3. enabled   = true
  4. position  = (168, 80)  <== スクロール前
  5. dimension = (136, 32)
  6. font-size = 13px
  7.  
  8. ul.globalMenuNormal li:last-child
  9. displayed = true
  10. enabled   = true
  11. position  = (168, -120)  <== スクロール後
  12. dimension = (136, 32)
  13. font-size = 13px
  14.  
  15. Page title: PHP, HTML, Linux 講座 : セミナー&スクール | アシアル株式会社
  16. Page URL: http://www.asial.co.jp/school/
また、端末情報も次のように取得できます。
  1. driver.getCapabilities().getBrowserName(); // android
  2. driver.getCapabilities().getVersion(); // API Level


おわりに



Selenium WebDriverをPCブラウザとAndroidブラウザの両方に対して使ってみました。簡単に使用でき、ブラウザ内の操作や要素の情報も容易に取得できます。たまにブラウザ操作が失敗することが玉に瑕ですが、テストの自動化には最適だと思います。軽くでも試してみると、開発の幅が広がると思います。