はじめに
UIテストにおける潮流
UIテストの自動化の代表格といえば、言わずとしれた Selenium でしょう。
最近では、Selenium をラップして、より使いやすくした Selenide も利用されているケースが増えているように思います。
自分自身も、Selenium のHTMLでテストシナリオを作成できる点や、Selenium IDE との組み合わせの良さに感激し、以前は積極的に利用していたものです。
ただ、上記の ノンプログラミングモデル だと、自由度が低く、ある程度の制限も出てくることから、最近では、WebDriverを利用した プログライミングモデル が主流になっているかと思います。
このモデルは、様々な言語に対応しており、多くの開発プロジェクトで利用できることは良いことなのですが、 プログラミングモデル の場合、個人的には、対応できるテストエンジニアに限りがあったり、メンテナンスコストの高さが課題となってくると感じています。
Karateの登場
そこで Karate の登場です。
Karateが何か、というのは、こちらを。
Karateは、CucumberというBDD(ビヘイビア駆動開発)を行うためのフレームワークをJavaVM上に移植したもので、Gherkinの文法を自然言語に近い形で記述できるユニットテストツールです。
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ベースの機能を利用します。
- https://intuit.github.io/karate/karate-core/
- https://github.com/intuit/karate/tree/master/karate-demo/src/test/java/driver/demo
ということで、まずは 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のレポートに挿入してくれます。
**これは、 シナリオの内容と、キャプチャが一体化してくれて、便利!!** さらに、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コードを呼び出しての処理をすることも可能です。
これについては、以下を参照してください。
- https://intuit.github.io/karate/#calling-java
- https://qiita.com/takanorig/items/70f14fa6ef024850ba25#%E6%8B%A1%E5%BC%B5
注意点
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に貼り付ける、なんてことをやって人生の時間を浪費している人を、救うこともできそうです(笑)。