18
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Karateを利用したUIテスト(もしくはE2Eテスト)

Last updated at Posted at 2019-06-03

はじめに

UIテストにおける潮流

UIテストの自動化の代表格といえば、言わずとしれた Selenium でしょう。
最近では、Selenium をラップして、より使いやすくした Selenide も利用されているケースが増えているように思います。

自分自身も、Selenium のHTMLでテストシナリオを作成できる点や、Selenium IDE との組み合わせの良さに感激し、以前は積極的に利用していたものです。

ただ、上記の ノンプログラミングモデル だと、自由度が低く、ある程度の制限も出てくることから、最近では、WebDriverを利用した プログライミングモデル が主流になっているかと思います。
このモデルは、様々な言語に対応しており、多くの開発プロジェクトで利用できることは良いことなのですが、 プログラミングモデル の場合、個人的には、対応できるテストエンジニアに限りがあったり、メンテナンスコストの高さが課題となってくると感じています。

Karateの登場

そこで Karate の登場です。
Karateが何か、というのは、こちらを。

Karateは、CucumberというBDD(ビヘイビア駆動開発)を行うためのフレームワークをJavaVM上に移植したもので、Gherkinの文法を自然言語に近い形で記述できるユニットテストツールです。

Karateとは?

Karate は、WebアプリにおけるAPIベースのテストを自動化するツールですが、現在は、WebDriverを使ったUIテストも可能になっています。
私自身、APIテストだけでなく、UIテストにも、Karateを利用し出しています。

Gherkin 形式の DSL(Domain Specific Language) でテストシナリオを直感的に作成できる一方で、そのシナリオの中で、JavaScriptの関数を定義して実行したり(JS Functions)、Javaのクラスを呼び出したり(Java Interop)することも可能なので、例えば、以下のことをひとつのツールで実現することができます。

  • テストの事前処理として、DBを初期化する(サーバー)。
  • ブラウザから項目入力をして、データを登録する(画面)。
  • 登録された結果を、画面で確認する(画面)。
  • 画面には表示されないが、DBに必要な項目が登録されているかを確認する(サーバー)。

Selenium のノンプログラミングモデルの場合、画面側の確認はできても、サーバー側の処理や確認はできません。そのため、以前は、初期化やデータ確認用のツールやAPIを別途開発して、試験を実施したりしていました。
このようなところが、UIテストやE2Eテストを、効率よく作成/実行する上では、大きな差になってきます。

Karateでは、DSLによる直感的なテストシナリオの作成と、複雑な処理はJavaScriptやJavaのコード実行に委譲できる、ハイブリッド構成が取れる点が、利便性の高さだと感じています。

Karate Driver

Karate におけるUIテストは、 「Karate Driver」というWebDriverベースの機能を利用します。

ということで、まずは KarateDriver でのテスト内容について、実際のシナリオを以下に示します。

ブラウザ操作のシナリオ

サンプルとして、GitHubにおけるリポジトリ検索の操作を自動化してみました。

Feature: GitHubに対するUI操作

Background:
  * configure driver = { type: 'chrome' }

# -----------------------------------------------
# GitHubでのリポジトリ検索
# -----------------------------------------------
Scenario: Search intuit/karate in GitHub

    * def keyword = 'karate'

    Given driver 'https://github.com/search'
        And eval driver.waitUntil(driver.title == 'Code Search · GitHub')
        And driver.input('input[name=q]', keyword)
    When driver.submit('#search_form button.btn')
    Then eval driver.waitUntil(driver.location == 'https://github.com/search?utf8=%E2%9C%93&q=' + keyword + '&ref=simplesearch')
        And eval driver.title == 'Search · karate · GitHub'

    * print driver.text('li.repo-list-item h3:first-child a')
    * match driver.text('li.repo-list-item h3:first-child a') == 'intuit/karate'

    * def bytes = driver.screenshot()
    * eval karate.embed(bytes, 'image/png')

以下、ポイントとなる内容を見ていきます。

Background:
  * configure driver = { type: 'chrome' }

利用するブラウザ(WebDriver)の指定です。

type 説明
chrome “native” Chrome automation via the DevTools protocol
chromedriver W3C Chrome Driver
geckodriver W3C Gecko Driver (Firefox)
safaridriver W3C Safari Driver
mswebdriver Microsoft WebDriver
msedge Microsoft Edge
    Given driver 'https://github.com/search'
        And eval driver.waitUntil(driver.title == 'Code Search · GitHub')
        And driver.input('input[name=q]', keyword)

Given で、最初に遷移する画面と、その画面での入力を行っています。
driver.waitUntil() を利用して、画面が表示されてから、入力を行うようにしています。

driver.input() では、CSSセレクタを利用して、要素を特定することができます。
これは、他のコマンドでも同様で、CSSセレクタで要素を特定することができますし、他にも、XPathなども指定することができます。

    When driver.submit('#search_form button.btn')

formのsubmit処理です。
上記は、検索ボタンを押下する内容になります。

    Then eval driver.waitUntil(driver.location == 'https://github.com/search?utf8=%E2%9C%93&q=' + keyword + '&ref=simplesearch')
        And eval driver.title == 'Search · karate · GitHub'

検索後の内容の確認です。
driver.waitUntil() で、検索後の画面の表示を待ってから、処理をしています。

    * match driver.text('li.repo-list-item h3:first-child a') == 'intuit/karate'

ここは、表示されている要素の確認です。
driver.text() で、表示されている内容を確認しています。

    * def bytes = driver.screenshot()
    * eval karate.embed(bytes, 'image/png')

さらに、上記のようにすると、画面キャプチャを取得し、Karateのレポートに挿入してくれます。

karate-driver-report.jpg

**これは、 シナリオの内容と、キャプチャが一体化してくれて、便利!!** さらに、printコマンドを使って、コメントなどを適宜出力しておけば、どのような試験で、どのような状態になったか、一目瞭然ですね。

JavaScriptによる制御を行うシナリオ

上記では、ボタンを押下する処理を

    When driver.submit('#search_form button.btn')

として行っていましたが、この部分を、JavaScriptによる制御に置き換えてみます。

# -----------------------------------------------
# GitHubでのリポジトリ検索
#   ・ブラウザ内でのJavaScript実行あり
# -----------------------------------------------
Scenario: Search2 intuit/karate in GitHub

    * def keyword = 'karate'

    # ブラウザで動作させるJSは、Karate自体の JS Function とは定義方法が異なる。
    # 関数自体は、文字列として定義し、それを driver.eval で実行させる。
    # 以下は、JS Function を文字列として定義し、変数部分は replace を使って置換している。
    * text formSubmitWebFn =
        """
        var formElem = document.querySelector('${selector}');
        formElem.submit();
        """
    * replace formSubmitWebFn.${selector} = '#search_form'

    Given driver 'https://github.com/search'
        And eval driver.waitUntil(driver.title == 'Code Search · GitHub')
        And driver.input('input[name=q]', keyword)
    When eval driver.eval(formSubmitWebFn)
    Then eval driver.waitUntil(driver.location == 'https://github.com/search?utf8=%E2%9C%93&q=' + keyword + '&ref=simplesearch')
        And match driver.title == 'Search · karate · GitHub'

    * print driver.text('li.repo-list-item h3:first-child a')
    * match driver.text('li.repo-list-item h3:first-child a') == 'intuit/karate'

    * def bytes = driver.screenshot()
    * eval karate.embed(bytes, 'image/png')

これも、最初のシナリオと同様の動作をします。

    * text formSubmitWebFn =
        """
        var formElem = document.querySelector('${selector}');
        formElem.submit();
        """
    * replace formSubmitWebFn.${selector} = '#search_form'

ここでは、上記のように、「text」を使って、ブロックテキストとしてJavaScriptを定義した後、「replace」で変数部分を置換しています。

    * eval driver.eval(formSubmitWebFn)

定義したJavaScriptの関数は、上記のようにすることで、ブラウザ上で実行することができます。
このようにして、標準のコマンドだけではできない内容も、JavaScriptの関数を組み合わせることで、複雑な操作も実現可能になります。

サンプルコード

今回作成したサンプルの内容は、以下に登録しています。

また、本記事では、Javaコードによる処理は行っていませんが、シナリオ中にJavaコードを呼び出しての処理をすることも可能です。
これについては、以下を参照してください。

注意点

Given-When-Then の部分利用

今回は、Given-When-Thenの内容をセットで利用していますが、実は、部分的に実行することも可能です。

# 画面遷移を確認
Given driver baseUrl + '/path/to'
    And driver.waitUntil(driver.title == '画面 | アプリケーション')
Then match driver.text('#title') == '画面タイトル'
# ダイアログ表示を確認
When driver.click('#dialog-open')
Then eval driver.text('div.modal-mask div.header') == 'ダイアログ'

UIの操作では、必ずしもフォーム入力をしてsubmitを行うような内容だけではないので、上記のように、部分的な利用もできることが分かっていると、シナリオ作成の幅が広がります。

driver.waitUntil() の利用方法

今回、以下のように、waitUntil の中で、KarateDriverの関数である driver.title を利用していますが、ここは、任意のコマンドを実行できるわけではありません。

driver.waitUntil(driver.title == 'Code Search · GitHub')

Karateのドキュメントを見ると、本来は、以下のように、JavaScriptの式を指定する内容になっています。
タグの内容やCSSをの内容を見て、状態が更新されるのを待つ、など、そのような使い方をします。

driver.waitUntil("document.readyState == 'complete'")

そこで、なぜ driver.title を使えるか、というと、今回のサンプルの内容は、実は以下のようにKarate内部で変換されて実行されています(driver = document ということではありません)。

driver.waitUntil(driver.title == 'Code Search · GitHub')
 ↓
driver.waitUntil(document.title == 'Code Search · GitHub')

補足

今回示した内容以外に、Karate Driver では、以下のようなことも可能です。

  • Windowsアプリの操作(Example
  • Cookie に対する操作
  • ブラウザダイアログに対する処理

その他、本記事で利用した以外のコマンドも、多く用意されており、その辺りは、以下のあたりの内容を参照してください。

よくUIテストで課題になる、ファイルアップロード/ダウンロードなどの処理は、現状では、工夫しないと実現できないのですが、その辺りは、今後の拡張に期待です(画面操作ではなく、APIに対する処理であれば可能です)。

まとめ

今回は、Karate Driver を利用した、UIテスト(E2Eテスト)の内容を紹介しました。
まだまだ発展途上の機能ですが、簡単にシナリオを作成して、UI操作を自動化できるのは、テストだけではなく、通常の業務自動化にも利用できるのではないか、と別の期待も持てる内容でした。

特に、シナリオの実行内容のレポートに、自動で画面キャプチャを埋め込めるのは、私もお気に入りの機能です。
これで、スクリーンショットを撮ってExcelに貼り付ける、なんてことをやって人生の時間を浪費している人を、救うこともできそうです(笑)。

18
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?