curlで覚えるWebDriver (2/2)

    curl で覚える WebDriver、第二段です。 前回の記事はこちら。

    前回に引き続き、Selenium のコア機能である WebDriver を curl で叩いてみます。 この記事では要素の取得やクリックの送信など、より実践的な API を使ってみます。 この記事の最後に、シェルスクリプトによる簡単な E2E テストの例を紹介します。

    サンプルアプリケーション

    まずこの記事で使用するサンプルアプリケーションを作ります。 以下の HTML はボタンを押すと画面上の数値が+1 されるシンプルなアプリケーションです。 この HTML を適当な名前で保存します。

    <!DOCTYPE html>
    <html lang="en">
      <body>
        <button id="button">++</button>
        <span id="counter">0</span>
    
        <script>
          document.querySelector("#button").onclick = () => {
            let span = document.querySelector("#counter")
            let num = parseInt(span.textContent, 10)
            if (isNaN(num)) {
              num = 0
            }
            span.textContent = (num + 1).toString()
          }
        </script>
      </body>
    </html>

    curl でブラウザの E2E テストを実行する

    このアプリケーションのテスト仕様を考えてみます。 以下のような手順でテストをするとします。

    1. 現在表示されている数値を N とする。
    2. ボタンをクリックする。
    3. 画面上に表示される数値が N+1 になっている。

    これを WebDriver で自動テストするには、以下のステップで実行します。

    1. WebDriver のセッションを作成する。
    2. ページを開く
    3. 画面上の#counter要素を取得して、それを数値 N とする。
    4. 画面上の#button要素をクリックする
    5. 画面上の#counter要素を取得して、手順 2.で取得した値 N+1 になってることを確認。

    テストをスタートする前に予め geckodriver または chromedriver を起動しておきます。 この記事では 4444 ポートで起動してると仮定します。

    セッションを作成

    まずブラウザのセションを作成します。 セッションの作成はPOST /sesionを使います。

    curl -sSL -XPOST -H'Content-Type: application/json' -d '{ "capabilities": {} }' localhost:4444/session

    レスポンスにセッション ID が返ってきます。

    {
      "value": {
        "sessionId": "2c4a9b35-52b5-42d7-a27c-029582d6131e",
        "capabilities": {
          // ......
        }
      }
    }

    ページを開く

    作成されたセッションは空のページが開かれたブラウザが起動します。 任意の URL を開くにはPOST /session/{session id}/urlを使います。

    session idはセッション作成時に返されたセッション ID です。 URL は JSON ボディの"url"フィールドに指定します。 "url"フィールドにはhttp:// (https://) で始まる URL や、ローカルファイルなら file:// でパスを指定できます。 先程保存した HTML へのパスを指定します。

    curl -XPOST -H'Content-Type: application/json' \
      -d'{ "url": "file:///path/to/index.html" }' \
      localhost:4444/session/2c4a9b35-52b5-42d7-a27c-029582d6131e/url

    現在のカウンタの値を取得する

    要素が含むテキストコンテンツを取得するにはGET /session/{session id}/element/{element id}/textを使用します。 この API を呼び出すには予め要素の ID を知る必要があります。

    要素の ID を取得するには、POST /session/{session id}/elementPOST /session/{session id}/elementsを呼び出します。 これらの API は、CSS セレクタやタグ名、XPath などから要素を探して、その要素 ID を返します。

    CSS セレクタで要素を探すには、以下のようなリクエストを投げます。 リクエストボディの JSON に、"using": "css selector" を指定して、 "value" に CSS セレクタを指定します。

    curl -XPOST -H'Content-Type: application/json' \
      -d'{ "using": "css selector", "value": "#counter" }' \
      localhost:4444/session/2c4a9b35-52b5-42d7-a27c-029582d6131e/element
    {
      "value": {
        "element-6066-11e4-a52e-4f735466cecf": "ae2ae378-45c6-438a-9a54-d258ea124690"
      }
    }

    "value"のオブジェクトのキーは web element identifier と呼ばれ、 "element-6066-11e4-a52e-4f735466cecf" という固定値です。 オブジェクトのバリューが web element reference と呼ばれ、一意な ID (UUID) が割り当てらます。 要素を操作する API はこの UUID を指定します(API の element idweb element reference を指定しして、 web element identifier は使わない)。

    取得したweb element reference の値から、GET /session/{session id}/element/{element id}/text でテキストコンテンツを取得するには以下のようなリクエストを投げます。

    curl localhost:4444/session/0395c9e9-4582-4a3f-965d-15b26eb0b68d/element/85eed002-2d05-4a52-815f-95b9af9d628d/text
    {
      "value": "0"
    }

    ボタンをクリックする

    要素ののクリックは POST /session/{session id}/element/{element id}/click を使用します。 element id は先ほどと同じ方法で取得した要素の UUID です。

    まずはボタン要素の UUID を取得します。

    curl -XPOST -H'Content-Type: application/json' \
      -d'{ "using": "css selector", "value": "#button" }' \
      localhost:4444/session/2c4a9b35-52b5-42d7-a27c-029582d6131e/element
    {
      "value": {
        "element-6066-11e4-a52e-4f735466cecf": "0a812a33-b041-4627-b5ae-54ed4bc5c918"
      }
    }

    そして取得した要素の UUID を使い、click API を使います。 リクエストボディは空 JSON です。

    curl -XPOST -H'Content-Type: application/json' \
      -d'{}' \
      localhost:4444/session/2c4a9b35-52b5-42d7-a27c-029582d6131e/element/0a812a33-b041-4627-b5ae-54ed4bc5c918/click

    Web の画面上でカウンタが+1 されるはずです。

    再びカウンタの値を取得して、それが期待する結果 (1) になってればテストはパスします。

    テストを自動化する

    ここまでの手順を簡単なシェルスクリプトを使って実装してみます。 以下のシェルスクリプトはボタンを 1 回クリックして、カウンタの値が+1 されてるかをチェックします。

    geckodriver と chromedriver の両方で動作確認はしてあります。 ポート 4444 固定なので、chromedriver の場合は--port=4444を指定してください。

    #!/bin/sh
    
    PORT=4444
    TARGET_HTML="file:///path/to/index.html"
    
    create_session() {
      curl -sSL -XPOST -H'Content-Type: application/json' \
          -d '{ "capabilities": {} }' "localhost:${PORT}/session" \
        | jq -r '.value.sessionId'
    }
    
    close_session() {
      session_id=$1
      curl -sSL -o/dev/null -XDELETE "localhost:${PORT}/session/${session_id}"
    }
    
    navigate_to() {
      session_id=$1
      url=$2
      jq -n --arg url "$url" '{ "url": $url }' \
        | curl -sSL -o/dev/null -XPOST -H'Content-Type: application/json' \
              -d@- "localhost:${PORT}/session/${session_id}/url"
    }
    
    get_element_by_css() {
      session_id=$1
      selector=$2
      jq -n --arg selector "$selector" '{ "using": "css selector", "value": $selector }' \
        | curl -sSL -XPOST -H'Content-Type: application/json' -d@- \
              "localhost:${PORT}/session/${session_id}/element" \
        | jq -r '.value["element-6066-11e4-a52e-4f735466cecf"]'
    }
    
    click_element() {
      session_id=$1
      element_id=$2
      curl -sSL -o/dev/null -XPOST -H'Content-Type: application/json' \
          -d'{}' "localhost:${PORT}/session/${session_id}/element/${element_id}/click"
    }
    
    get_element_text() {
      session_id=$1
      element_id=$2
      curl -sSL "localhost:${PORT}/session/${session_id}/element/${element_id}/text" \
        | jq -r '.value'
    }
    
    session_id=$(create_session)
    
    navigate_to "$session_id" "$TARGET_HTML"
    counter_element=$(get_element_by_css "$session_id" "#counter")
    button_element=$(get_element_by_css "$session_id" "#button")
    
    current_text=$(get_element_text "$session_id" "$counter_element")
    click_element "$session_id" "$button_element"
    new_text=$(get_element_text "$session_id" "$counter_element")
    
    close_session "$session_id"
    
    if [ "$new_text" -ne "$((current_text + 1))" ]; then
      echo >&2 "Assertion failed: $new_text != $current_text + 1"
    fi
    echo >&2 "PASS"
    

    まとめ

    前回に引き続き「curl で覚える WebDriver」という記事を書きました。 今回はより実践的な Web アプリケーションのテスト方法を記述してます。 これを各言語のクライアントライブラリとして実装したのが Selenium になります。

    Vim Vixen も E2E テストの自動化に WebDriver をりようしてます。 アドオンのロードは Firefox の Capability を設定してます。 その仕組みを現在 1 つのライブラリとして書き直してるので、また(機会があれば)紹介したいと思います。

    参考


    Profile picture

    Shin'ya Ueoka

    B2B向けSaaSを提供する会社の、元Webエンジニア。今はエンジニアリング組織のマネジメントをしている。