前の記事:Behat+Selenium Webdriverで受け入れテストの自動化をやってみた

要素のクリック動作とかを自動でやりたかったので、CakePHPで学ぶ 継続的インテグレーションに載ってた、behatch/contextsを入れてみた

composer.json

composerで入れます

{
    "require-dev": {
        "behat/behat": "3.*@stable",
        "behat/mink": "1.*@stable",
        "behat/mink-extension": "*",
        "behat/mink-goutte-driver": "*",
        "behat/mink-sahi-driver": "*",
        "behat/mink-selenium2-driver": "*",
        "behat/mink-zombie-driver": "*",
        "behatch/contexts": "*",    ←追加
    },
    "config": {
        "bin-dir": "bin/"
    }
}

インストール

$ php composer.phar update

behat.yml 編集

基本的には、ここ(Github)に載ってる通りです

$ cat behat.yml
    default:
      suites:
        default:
          path: %paths.base%/features
          contexts:
              - FeatureContext
              - Behat\MinkExtension\Context\MinkContext
              - behatch:browser    ←追加
              - behatch:debug    ←追加
              - behatch:system    ←追加
              - behatch:json    ←追加
              - behatch:table    ←追加
              - behatch:rest    ←追加
              - behatch:xml    ←追加
      extensions:
        Behat\MinkExtension:
          browser_name: firefox
          base_url: http://example.com/
          sessions:
            default:
              selenium2:
                capabilities: {"browser":"firefox","version":"*"}
        Sanpi\Behatch\Extension: ~    ←追加
    chrome-via-webdriver:
      extensions:
        Behat\MinkExtension:
          browser_name: chrome
          base_url: http://example.com/
          sessions:
            default:
              selenium2:
                capabilities: {"browser":"chrome","version":"*"}
    ie-via-webdriver:
      extensions:
        Behat\MinkExtension:
          browser_name: internet explorer
          base_url: http://example.com/
          sessions:
            default:
              selenium2:
                capabilities: {"browser":"internet explorer","version":"*"}

確認

たくさん増えてるか確認

$ ./bin/behat -dl
default |  When :time 秒待つ
default | Given /^(?:|ユーザーは )ホームページを表示している$/
default |  When /^(?:|ユーザーは )ホームページへ移動する$/
default | Given /^(?:|ユーザーは )"(?P<page>[^\s]+)" を表示している$/u
default |  When /^(?:|ユーザーが )"(?P<page>[^\s]+)" へ移動する$/u
default |  When /^(?:|ユーザーが )ページをリロードする$/u
default |  When /^(?:|ユーザーが )履歴の前のページに戻る$/u
default |  When /^(?:|ユーザーが )履歴の次のページヘ進む$/u
default |  When /^(?:|ユーザーが )"(?P<button>(?:[^"]|\\")*)" ボタンをクリックする$/u
default |  When /^(?:|ユーザーが )"(?P<link>(?:[^"]|\\")*)" のリンク先へ移動する$/u
default |  When /^(?:|ユーザーが )"(?P<field>(?:[^"]|\\")*)" フィールドに "(?P<value>(?:[^"]|\\")*)" と入力する$/u
default |  When /^(?:|ユーザーが )"(?P<field>(?:[^"]|\\")*)" フィールドに以下の値を入力する:$/u
default |  When /^(?:|ユーザーが )"(?P<value>(?:[^"]|\\")*)" という値を "(?P<field>(?:[^"]|\\")*)" に入力する$/u
default |  When /^(?:|ユーザーが)次のように入力する:$/u
default |  When /^(?:|ユーザーが )"(?P<option>(?:[^"]|\\")*)" という値を "(?P<select>(?:[^"]|\\")*)" から選択する$/u
default |  When /^(?:|ユーザーが )"(?P<option>(?:[^"]|\\")*)" という値を "(?P<select>(?:[^"]|\\")*)" から追加で選択する$/u
default |  When /^(?:|ユーザーが )"(?P<option>(?:[^"]|\\")*)" にチェックをつける$/u
default |  When /^(?:|ユーザーが )"(?P<option>(?:[^"]|\\")*)" のチェックをはずす$/u
default |  When /^(?:|ユーザーが)パス "(?P<path>[^"]*)" にあるファイルを "(?P<field>(?:[^"]|\\")*)" に添付する$/u
default |  Then /^(?:|ユーザーが )(?P<page>[^\s]+) を表示していること$/u
default |  Then /^(?:|ユーザーが )ホームページを表示していること$/u
default |  Then /^(?i)url(?-i)が (?P<pattern>"(?:[^"]|\\")*") にマッチすること$/u
default |  Then /^レスポンスコードが (?P<code>\d+) であること$/u
default |  Then /^レスポンスコードが (?P<code>\d+) ではないこと$/u
default |  Then /^(?:|画面に )"(?P<text>(?:[^"]|\\")*)" と表示されていること$/u
default |  Then /^(?:|画面に )"(?P<text>(?:[^"]|\\")*)" と表示されていないこと$/u
default |  Then /^(?:|画面に )"(?P<pattern>"(?:[^"]|\\")*")" にマッチするテキストが表示されていること$/u
default |  Then /^(?:|画面に )"(?P<pattern>"(?:[^"]|\\")*")" にマッチするテキストが表示されていないこと$/u
default |  Then /^レスポンスに "(?P<text>(?:[^"]|\\")*)" が含まれていること$/u
default |  Then /^レスポンスに "(?P<text>(?:[^"]|\\")*)" が含まれていないこと$/u
default |  Then /^"(?P<element>[^"]*)" エレメントに "(?P<text>(?:[^"]|\\")*)" と表示されていること$/u
default |  Then /^"(?P<element>[^"]*)" エレメントに "(?P<text>(?:[^"]|\\")*)" と表示されていないこと$/u
default |  Then /^"(?P<element>[^"]*)" エレメントに "(?P<value>(?:[^"]|\\")*)" という値が含まれていること$/u
default |  Then /^"(?P<element>[^"]*)" エレメントに "(?P<value>(?:[^"]|\\")*)" という値が含まれていないこと$/u
default |  Then /^(?:|画面に )"(?P<element>[^"]*)" エレメントが表示されていること$/u
default |  Then /^(?:|画面に )"(?P<element>[^"]*)" エレメントが表示されていないこと$/u
default |  Then /^"(?P<field>(?:[^"]|\\")*)" フィールドに "(?P<value>(?:[^"]|\\")*)" が含まれていること$/u
default |  Then /^"(?P<field>(?:[^"]|\\")*)" フィールドに "(?P<value>(?:[^"]|\\")*)" が含まれていないこと$/u
default |  Then /^チェックボックス "(?P<checkbox>(?:[^"]|\\")*)" のチェックがついていること$/u
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/
default |  Then /^チェックボックス "(?P<checkbox>(?:[^"]|\\")*)" のチェックがはずれていること$/u
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" should (?:be unchecked|not be checked)$/
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" is (?:unchecked|not checked)$/
default |  Then /^(?:|画面に )(?P<num>\d+) 個の "(?P<element>[^"]*)" エレメントが表示されていること$/u
default |  Then /^現在のURLを表示$/
default |  Then /^最後のレスポンスを表示$/u
default |  Then /^最後のレスポンスをブラウザで表示$/u
default |  When /^Basic認証を"(?P<user>[^"]*)"と"(?P<password>[^"]*)"で設定する$/u
default | Given /^(?:|私が)下記から構成されるURLに遷移する:$/u
default |  When (私が ):index 番目の :element 要素をクリックする
default |  When (私が ):index 番目の :link に遷移する
default |  When /^(?:|私が)"(?P<field>[^"]*)"に現在の日付を入力する$/u
default |  When /^(?:|私が)"(?P<field>[^"]*)"に現在の日付を"(?P<modifier>[^"]*)"で入力する$/u
default |  When /^(?:|私が)"(?P<element>[^"]*)"にhoverする$/u
default |  When /^(?:|私が)"(?P<field>[^"]*)"の値を"(?P<parameter>[^"]*)"パラメーターに保存する$/u
default |  Then /^(?:|私が)"(?P<text>[^"]*)"を見るまで(?P<seconds>\d+)秒間?待つ$/u
default |  Then /^(?:|私が)"(?P<text>[^"]*)"を見るまで待つ$/u
default |  Then /^(?:|私が)"(?P<element>[^"]*)"要素(?:で|に)"(?P<text>[^"]*)"を見るまで(?P<seconds>\d+)秒間?待つ$/u
default |  Then /^(?:|私が)(?P<seconds>\d+)秒間?待つ$/u
default |  Then /^(?:|私が)"(?P<element>[^"]*)"要素(?:で|に)"(?P<text>[^"]*)"を見るまで待つ$/u
default |  Then (私が):element要素を見るまで待つ
default |  Then (私が ):element 要素を見るまで :count 秒(間)待つ
default |  Then /^(?P<index>\d+)番目の"(?P<parent>[^"]*)"(?:|要素)が(?P<count>\d+)個の"(?P<element>[^"]*)"(?:|要素)を持つこと$/u
default |  Then /^(?P<index>\d+)番目の"(?P<parent>[^"]*)"(?:|要素)が(?P<nth>\d+)個以下の"(?P<element>[^"]*)"(?:|要素)を持つこと$/u
default |  Then /^(?P<index>\d+)番目の"(?P<parent>[^"]*)"(?:|要素)が(?P<nth>\d+)個以上の"(?P<element>[^"]*)"(?:|要素)を持つこと$/u
default |  Then /^"(?P<element>[^"]*)"(?:|要素)が有効であること$/u
default |  Then /^"(?P<element>[^"]*)"(?:|要素)が無効であること$/u
default |  Then /^セレクトボックス"(?P<select>[^"]*)"は"(?P<option>[^"]*)"を含むこと$/u
default |  Then /^セレクトボックス"(?P<select>[^"]*)"は"(?P<option>[^"]*)"を含まないこと$/u
default |  Then /^要素"(?P<element>[^"]*)"は可視であること$/u
default |  Then /^要素"(?P<element>[^"]*)"は不可視であること$/u
default |  When (私が ):name iframeにフォーカスする
default |  When (私が ):name frameにフォーカスする
default |  When (私が )メインフレームにフォーカスする
default |  Then /^(?:|私が)ブレークポイントを設置する$/u
default |  When /^(?:|私が)スクリーンショットを"(?P<filename>[^"]*)"に保存する$/u
default |  When /^(?:|私が)ファイル"(?P<file>[^"]*)"を"(?P<field>(?:[^"]|\\")*)"に設定する$/u
default | Given /^(?:|私が)"(?P<command>[^"]*)"を実行する$/u
default | Given /^(?:|私が)"(?P<command>[^"]*)"をプロジェクトルートから実行する$/u
default | Given (私が):filenameというファイルを下記のテキストで作成する:
default | Given (I )create the file :filename contening:
default |  Then :filenameというファイルのテキストを表示する
default |  Then /^レスポンスがJSON(?:|形式)であること$/u
default |  Then /^レスポンスがJSON(?:|形式)でないこと$/u
default |  Then /^JSONのノード"(?P<node>[^"]*)"が"(?P<text>[^"]*)"と等しいこと$/u
default |  Then /^JSONのノード"(?P<node>[^"]*)"が(?P<nth>\d+)個の要素を持つこと$/u
default |  Then /^JSONのノード"(?P<node>[^"]*)"が"(?P<text>[^"]*)"を含むこと$/u
default |  Then /^JSONのノード"(?P<node>[^"]*)"が"(?P<text>[^"]*)"を含まないこと$/u
default | Given /^JSONにノード"(?P<name>[^"]*)"が存在すること$/u
default | Given /^JSONにノード"(?P<name>[^"]*)"が存在しないこと$/u
default |  Then /^JSONが下記のスキーマに従っていること:$/u
default |  Then /^JSONがスキーマファイル"(?P<filename>[^"]*)"に従っていること$/u
default |  Then /^JSONが下記と一致すること:$/u
default |  Then 最後のJSONレスポンスを表示する
default |  Then /^テーブル"(?P<table>[^"]*)"のカラムスキーマが下記と一致すること:$/u
default |  Then /^テーブル"(?P<table>[^"]*)"が(?P<nth>\d+)個のカラムを持つこと$/u
default |  Then /^(?P<index>\d+)番目のテーブル"(?P<table>[^"]*)"が(?P<nth>\d+)行持つこと$/u
default |  Then /^テーブル"(?P<table>[^"]*)"が(?P<nth>\d+)行持つこと$/u
default |  Then /^テーブル"(?P<table>[^"]*)"の(?P<nth>\d+)行目のデータが下記と一致すること:$/u
default |  Then /^テーブル"(?P<table>[^"]*)"の(?P<rowIndex>\d+)行目(?P<colIndes>\d+)列が"(?P<text>[^"]*)"を含むこと$/u
default | Given /^(?:|私が)(?P<method>[A-Z]+)メソッドで"(?P<url>[^"]*)"へリクエストを送る$/u
default | Given /^(?:|私が)(?P<method>[A-Z]+)メソッドで"(?P<url>[^"]*)"へ下記のパラメーターを伴ったリクエストを送る:$/u
default | Given /^(?:|私が)(?P<method>[A-Z]+)メソッドで"(?P<url>[^"]*)"へ下記のボディを持ったリクエストを送る:$/u
default |  Then /^レスポンスが下記と一致すること:$/u
default |  Then /^レスポンスが空であること$/u
default |  Then /^"(?P<name>[^"]*)"ヘッダが"(?P<value>[^"]*)"と一致すること$/u
default |  Then /^"(?P<name>[^"]*)"ヘッダが"(?P<value>[^"]*)"を含むこと$/u
default |  Then /^"(?P<name>[^"]*)"ヘッダが"(?P<value>[^"]*)"を含まないこと$/u
default |  Then /^"(?P<name>[^"]*)"ヘッダが存在しないこと$/u
default |  Then /^レスポンスが将来期限切れになること$/u
default |  Then /^(?:|私が)"(?P<name>[^"]*)"ヘッダに"(?P<value>[^"]*)"を追加する$/u
default |  Then /^レスポンスが"(?P<encoding>[^"]*)"でエンコードされていること$/u
default |  Then /^最後のレスポンスヘッダを表示する$/u
default |  Then /^curlコマンドを表示する$/u
default |  Then レスポンスがXML(形式)であること
default |  Then レスポンスがXML(形式)でないこと
default |  Then (この)XML(に)は :element 要素が存在していること
default |  Then (この)XML(に)は :element 要素が存在していないこと
default |  Then (この)XMLの :element 要素は :text と一致していること
default |  Then (この)XMLの :element 要素は :text と一致していないこと
default |  Then (この)XMLの :element 要素(に)は :attribute 属性が存在していること
default |  Then (この)XMLの :element 要素(に)は :attribute 属性が存在していないこと
default |  Then (この)XMLの :element 要素の :attribute 属性は :text と一致していること
default |  Then (この)XMLの :element 要素の :attribute 属性は :text と一致していないこと
default |  Then (この)XML(に)は :element 要素を :count 個含んでいること
default |  Then (この)XMLの :element 要素は :text を含んでいること
default |  Then (この)XMLの :element 要素は :text を含んでいないこと
default |  Then (この)XMLは名前空間 :namespace を使っていること
default |  Then (この)XMLは名前空間 :namespace を使っていないこと
default |  Then 最後のXMLレスポンスを表示する
default |  Then /^XMLフィードが自身のDTDに従っていること$/u
default |  Then /^XMLフィードがXSDファイル"(?P<filename>[^"]*)"に従っていること$/u
default |  Then /^XMLフィードが下記のXSDに従っていること:$/u
default |  Then /^XMLフィードがrelax NG schemaファイル"(?P<filename>[^"]*)"に従っていること$/u
default |  Then /^XMLフィードが下記のrelax NG schemaに従っていること:$/u
default |  Then /^atomフィードが妥当であること$/u
default |  Then /^RSS2フィードが妥当であること$/u

これで、目的のフィーチャが記述できるようになりました

シナリオ: ログイン失敗[存在しないアカウント]
    前提 ユーザーは "/auth/login/" を表示している
    ならば "ログイン" と表示されていること
        かつ "login_id" フィールドに "admi" と入力する
        かつ "login_password" フィールドに "xxxxxx" と入力する
        もし 私が 1 番目の "input[type='submit']" 要素をクリックする    ←これと
    ならば "認証情報が一致しませんでした。" と表示されていること
        かつ スクリーンショットを"login_failed.jpg"に保存する    ←これ