Selenide Java Library：簡単なことは簡単に，Web UI テスト自動化の敷居を下げ，生産性をあげる魔法の Wrapper

プログラム上からブラウザの操作を支援する Selenium Library を利用して，jUnit / TestNG の文脈でより使いやすい機能を追加した Selenium Library を紹介します．

# Selenium (WebDriver) のちょっといけていない所

最初に Selenium WebDriver を触った瞬間に「これはすごい技術だ！」と感動したことを覚えています．

当時は Firefox Driver だけを利用していましたが，その後， Chrome Driver にもほとんどコードを書き換えることなく移行でき，その力を更に感じました．

しかし現在，WebDriver を使うにあたっていくつか気になる所がでてきてしまいます．

自分が特に気になっていたことは，以下の4つでした．

* 自分で **WebDriver のライフタイムを管理しなくてはいけない．**おまじないという名の `driver.quit()`．テストの並列化も考えると頭の痛い課題．
* 要素選択がめんどう．ただ**『次へ』リンクをクリックするコードを書くだけ**のために，要素の ID を調べるために開発者ツールを開いて……そして固有のID・クラス・属性がなかったら，**マジカルな XPath か CSS Selector でなんとかするしかない．**
* 遅延ロードするページのためにわざわざ Sleep しないといけない．Ajax 等で遅延ロードする場合，ロード直後のページは要素が存在しない事もあります．その場合，一定時間 Sleep するのがまず第一の選択肢ですが，保守的な Sleep をするとテストに時間がかかりすぎることになります．めんどうだが仕方なくループさせることに．**そうしているうちにマジカルなテストのできあがりです．**

こういった理由で，ともかくマジカルなコードが増えていきます．「なぜ driver からメソッドが生えているのか」，「存在を直接テストしている部分と，ループしてから次に進む部分があるのか」「Sleep が間に入っている行と，そうでない行の違いは何か」，テストケースを書いた人間以外にわからなくなったらおしまいです．

Web 自動化テストを書いたり読んだりするために必要な知識は少なければ少ないほどいい訳です．最低限必要なのは HTML/CSS の知識だけだと Good です．だって，実際にやっているのは，**「要素をクリックする」「結果を見る」それだけのこと**ですし．

上記の問題の解決策の一つが，ブラウザ操作ツールである WebDriver を，自動化テストの為に Wrap した，Selenide ライブラリです．

# Selenide ライブラリとは何か？

Selenide ライブラリは，Webテストの自動化ツールである，Selenium WebDriver のラッパーです．直接 Selenium （の WebDriver）を使うのに比べて，以下の特徴を持ちます．

## テストに便利な API
Selenium Web Driver のメソッドは，Web・ブラウザ操作の為の API です．
Selenide では，「テストのため」のAPI設計がされています．

例えば，Google のトップページを開き，検索ボタン（`name="btnK"`）に『Google 検索』という単語が含まれているかどうかをテストするコードは，それぞれ以下の用に書けます．

```java:SeleniumVsSelenideTest.java
package holdings.ozaki.selenide_basic;

import static com.codeborne.selenide.Condition.value;
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Selenide.open;
import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;

public class SeleniumVsSelenideTest {
  /**
   * Selenium を直接利用したコード
   */
  @Test
  public void bySelenium() {
    // 自分で WebDriver を作る必要あり．作ったら必ず自分で閉じる
    WebDriver driver = new FirefoxDriver();
    try {
      driver.get("http://google.com");
      WebElement searchButton = driver.findElement(By.name("btnK"));

      // Selenium がやるのはテキストの取得までで，改めてそれを assertEquals する必要あり
      assertEquals("Google 検索", searchButton.getAttribute("value"));
    } finally {
      // quiet しないと死ぬ．というか（同僚に）殺される．
      driver.quit();
    }
  }

  /**
   * Selenide ライブラリを利用したコード
   */
  @Test
  public void bySelenide() {
    open("http://google.com"); // 1. open したウィンドウはライブラリが閉じてくれる

    $("[name=btnK]")    // 2. jQuery のように CSS Selector が利用可能
        .should(value("Google 検索"));
    // 3. 要素が "Google 検索" を value 属性に持っていなかったら，テスト失敗．
    // 失敗時したテストは，スクリーンショットを取得して終了する（自動）
  }
}
```

この例での特徴は以下の2点です．

* Selenide ライブラリを使う場合，テスト終了時に WebDriver が起動したウィンドウが自動的に閉じられます．
* 作成された WebDriver はスレッド毎に管理されていますので，テストを並列実行した場合でも，それぞれ別のウィンドウが自動的に作成され，テストは各ブラウザプロセスに対して行われます．
* 要素の選択は，`com.codeborne.selenide.Selenide` クラスにある static メソッド，`public static SelenideElement $(String cssSelector)` を使うことによって行えます．
引数は CSS Selector で，返値は，`WebElement` をラップした，`SelenideElement` クラスです．
* `SelenideElement` クラスは，テストの用のメソッドを持っています．`should(value("xxx"))` で value 属性のテストが行えます．

WebDriver はブラウザの自動操作に特化したインタフェースのみを持ち，実際のテストケースに落とし込む為には，別に assertion （assertEquals とか） を書く必要がありました．これは，アセンブリのような低級言語でプログラミングするような状態です．

一方で，Selenide は，jUnit 等のフレームワークと組み合わせた自動化テストに特化して，driver のインスタンスをライブラリ内で管理し，ユーザーには driver を意識させずに，テスト用の static method を提供するという大胆な手法をとっています．

どのような WebDriver を使うかは，カスタマイズできます．ChromeDriver を使いたい場合，
初めの `open` メソッドを呼ぶ前に，`com.codeborne.selenide.Configuration.browser` 環境変数に，`org.openqa.selenium.remote.BrowserType.CHROME` を代入しておくと，`open()` が呼ばれたタイミングで ChromeDriver が作成されます．

```java
import org.openqa.selenium.remote.BrowserType;
import com.codeborne.selenide.Configuration;

public class SimpleTest {
  @BeforeClass
  public static void beforeClass() {
    Configuration.browser = BrowserType.CHROME;
  }

  // 以下テストケース．Chrome で実行される．
```

環境変数から指定することもできるので，同じテストケースを IE, Chrome, Safari で実行することもできます．


## Ajax support

正確には非同期操作のサポートです．

最近では，First View （ロード直後のページ表示）に Ajax を取り入れることも増えてきました．

このような場合，Selenium でページロード直後に要素の存在チェックなどを行っても失敗するため，コード側で Sleep したり，条件が満たされるまでループする必要がありました．マジカルですね．

Selenide は，特に指定せずとも，ライブラリ側で Sleep & ループを行います．

先ほどの以下の例では，`[name=btnK]` 要素が "Google 検索" を値に持っていなかった場合，4秒間リトライします．

```java
$("[name=btnK]")
  .should(value("Google 検索"));
```

## Page Object サポート

Selenium 等の Web テストツールや，Appnium 等のネイティブアプリケーションテストツールでは，ページをオブジェクトとして記述することでテストコードの可読性を上げたり，再利用性を高める手法があります．

Page Object Model に関しては，興味があれば "Page Object" で検索すると，いいサンプルがあると思いますので省略します．

[Selenide で Page Object を実装する場合のサンプルが公式ドキュメントにもあります．](http://selenide.org/documentation/page-objects.html)

## CSS Style (jQuery-style) のセレクタ

もう既にサンプルで示しましたが，Selenide で一番使う `$(String)` は，CSS Style のセレクタを引数に取ります．

ちなみに，Selenium 標準の By を使うこともできます．

```java
$("[name=btnK]"); // CSS Selector での書き方
$(By.name("btnK")); // $ の中身は， Selenium 標準の By でもいい．この場合も返値は SelenideElement なので
$(By.name("btnK")).should(value("Google 検索")); // .should(have("")); が使える
```

# その他テストの為の機能
## テキストを元に要素を探す

WebDriver を直接使うと XPath で頑張るしかありませんが，`com.codeborne.selenide.Selectors.byText(String)` という要素選択のための Utility があります．

これを使って次の用に書けます

```java
import static com.codeborne.selenide.Selectors.withText;

//--

$(withText("ストック")).click();
// 記事をストックするっ！
```

## 正規表現でテキストマッチ

matchText メソッドの中身に正規表現を書いて should の中に入れます．

```java
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Condition.matchText;

$("#userName").should(matchText("ユーザー さん"));
```

## 要素が存在しないことを確認したい

WebDriver を直接使う場合，色々やり方はあります．次の例では，存在しない要素の Lookup で例外が発生することを利用しています．

### WebDriver を直接使う場合

```java
try {
    WebElement element = driver.findElement(By.id("FacebookShare"));
    fail("このページにはソーシャルシェアボタンは置いちゃだめ");
}
catch (WebDriverException ignored) {
    // ここに流れてきたら OK
}
```

### Selenide の場合
Selenide は，`exists` 条件を持っています．また，SelenideElement には "shouldNot" メソッドがあり

```java
$("#FacebookShare")
  .shouldNot(exist.because("このページにはソーシャルシェアボタンは置いちゃだめ"));
```

## 親要素を指定してその中で探す

jQuery と同じで，`find` でできます

```java
$("table#items").find("tr");
```

## n番目の要素を選択

2引数目に数字を渡します

```java
$("tr", 2).should(hasClass("even")); // 2行目は even クラスあるよね？
```

## デバッグ用の表示

### Selenide の場合
toString() がオーバーライドされており，そのままログや System.out に出してやると（あるいは実行中に Eclipse でホバーすると） HTML が見えます．便利．

```java
System.out.println($("[name=btnK]"));
```
```text
<input aria-label="Google 検索" jsaction="sf.chk" name="btnK" type="submit" value="Google 検索"></input>
```

### WebDriver を直接使う場合

同じことを WebDriver の要素に直接やろうとしても，選択条件しか見えません

```java
System.out.println(driver.findElement(By.name("btnK")));
```
```text
[[FirefoxDriver: firefox on WINDOWS (c2b08c01-65bd-4af7-96f2-c659b646aa89)] -> name: btnK]
```

## スクリーンショットを取る

手作業で取りたいときは，`takeScreenShot` で取れます．

```java
takeScreenShot("my-test-case");
```

[実際には，何もしなくても jUnit テストケースが失敗すると其のタイミングでスクリーンショットをとっておいてくれます．便利．](http://selenide.org/documentation/screenshots.html)


# その他のリファレンス

Java で Selenium テストを単純明快に記述出来るライブラリ，"Selenide" の機能の一部を紹介させていただきました．

この記事を書くにあたって参考にした情報は以下の通りです．いずれも英語です……が，英語が読めなくてもコードサンプルを見ると雰囲気は解ると思います．

* [selenide.org - 公式サイト](http://selenide.org/)
* [Selenide vs Selenium - コードサンプル多し．非常に参考になります](https://github.com/codeborne/selenide/wiki/Selenide-vs-Selenium)
* [How to write UI Test in 10 minutes](https://vimeo.com/107647158)
* [Selenide and Selenium comparison](http://selenide.org/documentation/selenide-vs-selenium.html) 

# 終わりに

Web UI テストの自動化は，1人で全画面分書いていくというよりも，機能拡張が入る毎に開発担当者やテスト担当者が書いていく場合の方が多いと思います．そういった場合に，いきなり `WebDriver インスタンスが……` から説明が始まると，開発者でも大変ですし，テスト担当者にはそもそも自動化テストが書けない，といったことになりがちです．結局，Selenium を導入してみたはいいけど，個人の趣味レベルのもので，テストケースのメンテナンスができずに，製品に追いつけなくなってしまうという経験は多いのではないでしょうか？

Web UI Test の自動化を担当しているうちは，あるいは担当者にとっては，WebDriver のライフサイクル管理や，決まりに従った（悪い言い方をすると，マジカルな）要素選択はあたりまえの記述になっていても，新しくテストを書く人や，後からメンテナンスしようと思った人にはそうではありません．

本来は，Web UI テストの自動化は，関数の単体テストや結合テストなんかと同じか，それより広い層が可能な冪です．なぜならやっていることが，「ボタン・リンクを探す」「クリック」「期待値チェック」だけなのですから．それには複雑なインスタンス作成も，Sync・Async の違いもありません．

簡単なことは簡単に，Selenide ライブラリを使うことで，「自動化テストを書けるメンバ」「テストを書いてもいいと思ってくれるメンバー」を増やしてみるのはどうでしょうか？