Selenium2 PageFactory の紹介

PageObject パターンを楽に実現するために、PageFactory というクラスがあります。
PageFactory - selenium - Description of the PageFactory in the WebDriver support library. - Browser automation framework - Google Project Hosting
PageObject クラスのフィールドに WebElement をマッピングしてくれます。
上記のページを見ると分かりますが、フィールド名か、@FindBy アノテーションで紐付けます。


こんな感じに実装していたのが、

import static org.openqa.selenium.support.ui.ExpectedConditions.*;
...

public class Login {
  private final WebDriver driver;
  public Login(WebDriver driver) {
    this.driver = driver;
  }
  WebElement userId() {
    return driver.findElement(By.name("userId"));
  }
  WebElement password() {
    return driver.findElement(By.name("password"));
  }
  WebElement login() {
    return driver.findElement(By.xpath("//input[@type='submit' and @value='login']"));
  }
  public Menu ログインしてメニューに遷移(String ユーザー, String パスワード) {
    userId().sendKeys(ユーザー);
    password().sendKeys(パスワード);
    login().click();
    Wait<WebDriver> wait = new WebDriverWait(driver, 10);
    wait.until(titleIs("メニュー"));
    return new Menu(driver);
  }
}

こんな感じに書きかえれます。

import static org.openqa.selenium.support.ui.ExpectedConditions.*;
...

public class Login {
  private final WebDriver driver;
  public Login(WebDriver driver) {
    this.driver = driver;
    PageFactory.initElements(driver, this);
  }
  WebElement userId;
  WebElement password;
  @FindBy(how = How.XPATH, xpath = "//input[@type='submit' and @value='login']")
  WebElement login;

  public Menu ログインしてメニューに遷移(String ユーザー, String パスワード) {
    userId.sendKeys(ユーザー);
    password.sendKeys(パスワード);
    login.click();
    Wait<WebDriver> wait = new WebDriverWait(driver, 10);
    wait.until(titleIs("メニュー"));
    return PageFacroty.initElements(driver, Menu.class);
  }
}

この各フィールドは、参照した時に findElement が実行され値を取得しているようです。(遅延評価)
PageFactory.initElements の中では、渡されたクラスの WebElement 型のフィールドに java.lang.reflect.Proxy を設定して遅延評価を実現しているようです。


このままだと、フィールドにアクセスする度に findElement されて効率が悪い!という場合には、@CacheLookup マーカーアノテーションをフィールドに指定していると、初回の findElement した値をキャッシュして次回以降はそれを返します。

@FindBy(how = How.XPATH, xpath = "//input[@type='radio' and @value='0']")
WebElement none;
@FindBy(how = How.XPATH, xpath = "//input[@type='radio' and @value='1']")
@CacheLookup
WebElement all;

public void ラジオボタン切替() {
  all.click(); // driver.findElement(By.xpath("..."); が実行される。
  assert none.isSelected() == false; // driver.findElement(By.xpath("..."); が実行される。
  assert all.isSelected() == true; // CacheLookup 指定しているので、findElement は実行されない。
  none.click(); // driver.findElement(By.xpath("..."); が実行される。
  assert none.isSelected() == true; // driver.findElement(By.xpath("..."); が実行される。
  assert all.isSelected() == false; // CacheLookup 指定しているので、findElement は実行されない。
}

CacheLookup の注意点は findElement し直さないので、例えばサーバーに投げて HTML が変更されるるような処理が間にあると上手く動かなかったりします。