見出し画像

空振りクリックに困ったときに便利なSelenideのClickOptions

こんにちは、週末のベーグルづくりがマイトレンドになっている自動テストアーキテクトの森川です。ツルツルもちもちのベーグルが焼けると一週間のモチベーションがあがります。

本日は自動テストフレームワークのSelenideに関するTipsについてお送りしたいと思います。


Selenideとは?

SelenideはJavaでSelenium WebDriverを使う場合に、パワフルな拡張をしてくれる自動テストフレームワークです。jQueryライクに直感的に記述ができ、チェック処理を簡単に書けるところや画面表示の待機処理が柔軟なのが特徴です。

生のSeleniumだとコード量が多く可読性も低いですし、待機は自前で組む必要があります。

// Seleniumの場合
driver = new ChromeDriver();
driver.get(URL);
driver.findElement(By.cssSelector("#submit-form")).click();
try  {
    new WebDriverWait(driver,30)
        .until(ExpectedConditions.textToBe(By.cssSelector("#message"),EXPECTED_MESSAGE));
} catch(Exception e) {
    Assertions.fail("メッセージの表示");
}

Selenideだとこんなに簡潔で直感的(待機もおまかせ)。

// Selenideの場合
open(URL);
$("#submit-form").click();
$("#message").shouldBe(text(EXPECTED_MESSAGE));

また、IDEのインテリジェンス機能を駆使すればAPIドキュメントを読まなくても、とりあえずは書けるのでとっつきが良いです。

国内ではQiitaさん自動化系のイベントログの紹介記事がヒットしますが、Selenideの特徴的なギミックである「遅延評価」やダウンロードなどの充実したユーティリティ機能群については、触れられている記事は多くないようです。Selenideは現在でもメンテナンスは続いて、おり新しい機能追加も時折あるのですが、最近は更新記事が少なく最新情報についてはあまり書かれていません。

ということで、本日はその中のひとつを紹介したいと思います。

余談ですが、Selenideについて詳しく触れている日本語書籍をほとんど見たことがありません。実践的なことも含めて詳しく書かれているのはこちらです。お薦めです。
【POD】エキスパートが教えるSelenium最前線 (CodeZine BOOKS) ※ Selenideについて第2章のみです。

クリック空振り問題に対処

Seleniumでクロスブラウザの自動テストを書いているとクリック空振り問題にぶち当たります。

要素は明らかに表示されているのになぜか特定のブラウザや特定のページ要素ではクリックに失敗するという事象で、原因はフロントエンドのフレームワークやブラウザ起因など様々なようです。また再現性もまちまちという厄介なやつです。

よくある対策はJavaScriptによるクリックです。
厳密にはSeleniumのJavaScript実行APIに対象要素のIDとスクリプトを渡して実行します。
https://www.w3.org/TR/webdriver/#execute-script

import static com.codeborne.selenide.Selenide.*;
// 何がなんでもJSでクリックしてこい(良い子はまねしないでください)
Selenide.executeJavaScript("arguments[0].click();",$("#my_locator"));

WebDriverの標準のクリックAPIではなく、JavaScriptを用いるので、対象要素が見えていようが、隠れていようが、DOMツリー上に存在する限りぶっ叩くことができます。

「だったら自動テストは全部JavaScriptでClickすればいいじゃない」
という意見が出てきそうですね。

さいわいSelenideには要素ClickをすべてJavaScript経由で行えるように設定するためのグローバルオプションが用意されています。

import com.codeborne.selenide.Configuration;
Configuration.clickViaJs = true

これで万事解決・・・
というわけにはいきません。

WebアプリのE2E自動テスト経験者の方はおわかりになると思いますが、JavaScriptでクリックすると思ったとおりにアプリが動いてくれないことがあります。

考えられる原因は要素にバインドされているイベントが通常のClick時と同じふるまいをしてくれないから・・・

まぁ、細かい話は置いといて
『すべてをJavaScriptクリックにするのではなく、クリックに失敗する要素だけでJavaScript経由でクリックしよう』
が良いプラクティスになりそうなことは明らかです。

そこで上のグローバルオプションclickViaJsを都度切り替えるというのもなんか無粋だよね、ということでSelenideのv5.15.0で実装されたClickOptionsなるAPIを使います。

Released Selenide 5.15.0

とてもシンプルにクリック方法を切り替えることができます。

import static com.codeborne.selenide.ClickOptions.*;
$("#locator").click(usingJavaScript());```

これを使ってブラウザ(IE)を判別してClickOptionsを切り替えるstaticなサブクラスを書いてみました。

BaseTestに仕込んでおきシナリオクラスから呼び出せば切り替えは簡潔になりそうです。

// BaseTest.java

static class ClickOptionsHelper {
    static ClickOptions clickViaJSIfIE() {
        if (Configuration.browser.equals(BrowserType.IE)) {
            return usingJavaScript();
        } else {
            return usingDefaultMethod();
        }
    }
}
// ScnearioTest.java

// 普段使い
$("#stable_locator").click();

// クリック失敗する要素
$("#flaky_locator").click(clickViaJSIfIE());

おまけですが、ClickOptionsでは座標ポインタを指定できます。

$("#locator").click(usingJavaScript().offset(123, 222));
$("#locator").click(usingJavaScript().offsetY(222));

なのですが、座標指定のおかげで「ひゃっほーい!」となるシチュエーションについて正直私はピンと来ていません。詳しい方いたらコメントください。

以上、クリック空振りで困ったときにSelenideの新機能ClickOptionsでサクッと回避する方法でした。

クリック空振りにお悩みのお忙しい自動テストエンジニアの皆様はここで作業にお戻りください。少しお時間のある方は、Selenideの実装を一緒に覗いてみましょう。

Selenideの実装を覗く

Selenideのclickのコアコードを見てみましょう。
selenide/Click.java at master · selenide/selenide
クリック時のWebDriverのAPIを叩き方は3種類

・elementクリック
・Actionsでクリック
・JavaScriptでクリック

clickメソッドに与える引数によって次のように分岐されます。

画像1

それぞれの叩き方を見てみましょう。

elementクリック

element.click();

いわゆるWebDriverの標準のクリック処理です。

W3Cのドキュメントにも記載されているとおり、原則見えている要素を対象としてmouseoverからclickイベントまでを順に呼び出してからクリックを行います。要素にバインドされたイベントまでケアしてくれることで、手動によるクリック操作に近い処理が行われていると思います。

上でとりあげた「全部JavaScriptクリックにしたらうまくいかない」との関連が大いに予想されますね。
https://www.w3.org/TR/webdriver/#element-click

Actionsでクリック

private void defaultClick(Driver driver, WebElement element, int offsetX, int offsetY) {
  driver.actions()
    .moveToElement(element, offsetX, offsetY)
    .click()
    .perform();
}

SeleniumのActions APIを使います。

Actionsを使用している理由は、コミットログを読む限りではオフセット座標指定が目的のようです。
add click command with relative position · selenide/selenide@b92d5c5

ClickOptionsで非JavaScriptを指定した場合に、強制的にActions経由でクリックされ、element.click()は使わなくなるところが若干気になります。

W3C/WebDriver賢者ではないので詳しいことはわからないのですが、Actionsでクリックした場合はMouse.pointerDownイベントが発火されますので、WebDriver標準のClickイベントとは厳密には同じではないようです。Webアプリによってどのような副作用をもたらすのかはケースByケースだと思いますが、個人的にはClickOptionsの乱用は控えたほうが良い気がしました。

JavaScriptでクリック
Seleniumのexecute APIでJavaScriptを実行しています。

private void clickViaJS(Driver driver, WebElement element, int offsetX, int offsetY) {
  driver.executeJavaScript(jsSource.content(), element, offsetX, offsetY);
}

実行スクリプトのclick.jsを見ると単純に要素をクリックしているだけではないことがわかります。
selenide/click.js at master · selenide/selenide

(function (element, offsetX, offsetY) {
  const rect = element.getBoundingClientRect();

  function mouseEvent() {
    if (typeof (Event) === 'function') {
      return new MouseEvent('click', {
        'view': window,
        'bubbles': true,
        'cancelable': true,
        'clientX': rect.left + rect.width / 2 + offsetX,
        'clientY': rect.top + rect.height / 2 + offsetY
      });
    }
    else {
      const event = document.createEvent('MouseEvent');
      event.initEvent('click', true, true);
      event.type = 'click'
      event.view = window;
      event.clientX = rect.left + rect.width / 2 + offsetX
      event.clientY = rect.top + rect.height / 2 + offsetY
      return event;
    }
  }
  element.dispatchEvent(mouseEvent());
})(arguments[0], arguments[1], arguments[2]);

要素の位置とサイズを採ってセンター位置でマウスクリックしています。分岐のtypeof (Event) === 'function''はIE対応のために入れているようです。この実装もオフセット座標指定が目的のようです。
#1406 fix JS code for `$.click()` in InternetExplorer · selenide/selenide

気になる点があるとすれば、要素位置とサイズが実画面とずれる場合があるかな?というところですが、自前でarguments[0].click();などと書くよりも、ヘルシーな気がします。

まとめと宣伝

SelenideのClickOptionsについてご紹介しました。

Selenideはファイルダウンロードやプロキシ機能など便利なユーティリティを持っています。また遅延評価やPageObjectパターンについても面白いところがありますので、次回はそちらについてもご紹介できればと思います。
最後までお読みいただきありがとうございました。

少し宣伝です。
弊社で最も実績のあるE2Eテストツール「Racine」では、Seleniumの拡張F/WとしてSelenideを採用しています。

SHIFTのGUIテストツールRacineとその開発プロセス

自動テストコードをSelenideと同様に書けるため直感的で学習コストが低くすみますし、ネット上に多くある情報を参考にできるためツール独自の文法が方言化・陳腐化しないというエコシステム的にも優れた点がございます。
そしてRacineはライセンス料なしですので無償でご利用いただけます。
(導入費用、サポート費用は別途必要となります。)

SeleniumベースのE2Eテストの導入・維持に関するご相談につきましては、是非とも弊社の窓口にご相談ください。

テスト自動化サービス | ソフトウェアテストのSHIFT
https://service.shiftinc.jp/service/consulting/test-automation/

_________________________________

執筆者プロフィール:森川 知雄
中堅SIerでテスト管理と業務ツール、テスト自動化ツール開発を12年経験。
SHIFTでは、GUIテストの自動化ツールRacine(ラシーヌ)の開発を担当。
GUIテストに限らず、なんでも自動化することを好むが、ルンバが掃除しているところを眺めるのは好まないタイプ。
さまざま案件で自動化、効率化によるお客様への価値創出を日々模索している。

画像2

★3分でわかるSHIFTについて

★SHIFTの導入事例はコチラ

★SHIFTの最新イベント情報はコチラ

★SHIFTの最新コラムはコチラ

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/