Selenium WebDriver を利用して Webアプリケーションのテストをしてみる

以前から気になっていた Selenium WebDriver を使ってWebアプリの自動テストを試してみたので忘れないうちにメモ。

WebDriverに惹かれたのは以下の理由

  • ブラウザの操作がJavaで簡単に書ける(学習コストが低い)
  • 記述するコードが簡潔で分かりやすい(コードのメンテナンスがしやすい)
  • JUnitからも実行できる(Jenkinsから実行して自動化したり)
  • ブラウザのスクリーンショットが撮れる(エビデンス作成)

導入準備

ここから Selenium Client Drivers(Java) をダウンロードしてjarにクラスパスを通すだけ。
Mavenを利用する場合、pom.xmlselenium-java を追加するだけでOKです。
ブラウザ操作の機能のみを利用するだけなら selenium-htmlunit-driver は不要なのでexclusionを指定しておくと余計な依存jarを減らせます。

    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>2.5.0</version>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-htmlunit-driver</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

さっそくWebDriverを利用してブラウザを操作する

Seleniumの本家ドキュメントに載っているサンプルです。お手軽。コードを見れば処理の内容もなんとなくわかります。
実行するとFirefoxが起動し、ブラウザの操作が自動でおこなわれます。

public class Main {

    public static void main(String[] args) {

        // ブラウザ(Firefox)を起動
        WebDriver driver = new FirefoxDriver();

        // URLを開く
        driver.get("http://www.google.co.jp");

        // name が "q" の要素を取得
        WebElement element = driver.findElement(By.name("q"));

        // 文字をタイプ
        element.sendKeys("selenium");

        // submit
        element.submit();

        // ブラウザを閉じる
        driver.quit();
    }
}

JUnitからWebDriverを利用する

WebDriverはJUnit上でも問題なく動作します。
以下のコードはJUnitのBeforeClassを利用しテスト前処理にブラウザの起動処理を入れています。
その後テストメソッドの中で Yahoo!のTOP画面を開き、検索フィールドに『selenium』とタイプし、検索ボタンを押下しています。
テスト後の後処理はAfterClassを利用しブラウザを閉じています。
Googleで検索をおこなうと検索フィールドにタイプした瞬間に検索結果画面が表示されてしまうので動作が分かりにくいと思ったので…。)

public class YahooSearchTest {

    private static WebDriver driver;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        driver = new FirefoxDriver();
//        driver = new InternetExplorerDriver();
//        driver = new ChromeDriver(
//                new ChromeDriverService.Builder()
//                .usingChromeDriverExecutable(new File("resources\\chromedriver.exe"))
//                .usingAnyFreePort()
//                .build());
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        driver.quit();
    }

    @Test
    public void Yahoo検索() throws Exception {
        driver.get("http://www.yahoo.co.jp");
        driver.findElement(By.name("p")).sendKeys("selenium");
        driver.findElement(By.id("srchbtn")).click();
        assertThat(driver.getTitle(), is("「selenium」の検索結果 - Yahoo!検索"));
    }

起動するブラウザを変更するには最初に生成しているDriverクラスを変更するだけです。簡単。
ただしChromeを利用する場合は以下のURLから chromedriver.exe をダウンロードし、上記サンプルのようにusingChromeDriverExecutableメソッドでexeファイルを指定する必要があります。

スクリーンショットの保存

スクリーンショットを保存するには TakesScreenshotインターフェース のgetScreenshotAsメソッドを実行すれば良いみたいです。
InternetExplolerDriverもFirefoxDriver、ChromeDriverもTakesScreenshotの実装クラスになっているためスクリーンショットの保存が可能です。

    private void saveScreenshot(File saveFile){
        try {
            if(driver instanceof TakesScreenshot) {
                File tmpFile =
                    ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
                FileHandler.copy(tmpFile, saveFile);
            }
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

要素を読み込むまで待機する処理

Ajaxなどの処理でJavaScriptからDOMを更新している場合は待機処理を入れないとうまく動作しない場合があったりしたので
指定したエレメントが表示されるまでの待機処理メソッドも作っておいたほうが良いです。

    private void waitForElementToLoad(final By locator, int timeOutInSeconds) {
        new WebDriverWait(driver, timeOutInSeconds).until(new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver driver) {
                return driver.findElement(locator).isDisplayed();
            }
        });
    }

ブラウザの操作処理 - クリック処理、文字のタイプ処理、プルダウンの選択処理

本家サイトにも載っているのですがよく使う操作をメソッドにまとめておくと便利。

    /**
     * クリック処理
     *
     * @param locator
     */
    public void click(By locator) {
        driver.findElement(locator).click();
    }

    /**
     * フォームのサブミット処理
     *
     * @param locator
     */
    public void submit(By locator) {
        driver.findElement(locator).submit();
    }

    /**
     * テキストフィールドの入力処理
     *
     * @param locator
     * @param text
     */
    public void type(By locator, String text) {
        WebElement element = driver.findElement(locator);
        element.sendKeys(text);
    }

    /**
     * プルダウンの選択処理
     *
     * @param locator
     * @param label
     */
    public void select(By locator, String label) {
        Select element = new Select(driver.findElement(locator));
        element.selectByVisibleText(label);
    }

    /**
     * sleep処理
     *
     * @param millis
     */
    public void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

感想

WebDriverを利用するコード自体は理解しやすくお手軽なので導入はしやすいですね。
ただ実際の現場で運用しようとすると以下のような考えるべきことが多いので注意が必要だと思いました。

  • テストクラス、テストメソッドの単位、粒度をどうするか
  • 分かりやすくDRYなコードをどのように保つか
  • クロスブラウザの対応はどう管理するか
  • 各画面の入力パラメータの管理方法をどうするか

このあたりの問題を解決する『ルール』や『仕組み作り』を運用しながらノウハウを蓄積する必要があると思います。
単体テストとは違った観点だと思うのでもうちょっと現場で使ってみながら考えてみることにします。