2013年2月6日水曜日

Selenium 2.xで条件が満たされるまで待つ

Selenium 2.X (確認したのは2.28) で何か条件が満たされる(例えば、JavaScriptが実行されてロード時はクリックできない要素がクリックできるようになる)まで待つには、WebDriverWait#until(Predicate)を使う。
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("id")));
引数に渡しているインスタンスが条件を表している。よくある条件はExpectedConditionsクラスに用意されている。not(ExpectedCondition)もあるのでたいていの場合はこれで事足りる。

単に待つだけじゃなく、条件を満たした時のインスタンスを返してくれるのが便利。

独自の条件が必要な場合は、ExpectedCondition<T>インタフェースを実装して、applyメソッドをオーバーライドする。その返値の型は、インタフェースの型パラメータで指定する。nullでもfalseでもない値が返されると、条件を満たしたと判定される。

車輪の再発明だけれど、上記の例を無名クラスを使って実装すると、次のようになる。
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(new ExpectedCondition(){
      @Override
      public WebElement apply(WebDriver d) {
      return d.findElement(By.id("id"));
}});

References

Selenium 2.xでHTML要素の非存在をチェックする

Selenium 2.X (確認したのは2.28) で指定したHTML要素が存在しないことをチェックするには、WebDriver#findElements(By)を使う。返値 (List) の長さが0なら、存在しない。例えば、hogeクラスが指定されたdiv要素が存在しないことをチェックするなら、下記のようになる。

if (driver.findElements(By.cssSelector("div.hoge")).size() == 0) {
  // hogeクラスが指定されたdiv要素は存在しない
}
このことは、WebDriver#findElements(By)じゃなくてWebDriver#findElement(By)のJavadocに書いてある。ちょっと回りくどい上に、使うメソッドのJavadocには特に何も書いてなくて、よく見失う。
findElement should not be used to look for non-present elements, use findElements(By) and assert zero length response instead.
WebDriver#findElement(By)
1.XのAPIだと、Selenium#isElementPresent(String)というそのものズバリのメソッドがある。後方互換性があるから今でも動作するけれど、それを使うのにはパッと思いつくだけでデメリットが3つある。というわけで、なるべく2.XのAPIで統一していきたい。
  • 1.XのAPIは要素の指定で、バグを作りやすい ("locatorType=argument"フォーマットのStringで、"locatorType="の省略時動作に暗黙のルールがある)
  • APIのスタイルが混在すると、リーダビリティが下がる
  • 古いAPIはいつまでも使い続けられるとは限らない

References