日本語ドキュメントがなく、えらい苦労したので記録しておく事にする

クラス定義

behat --initで生成されるクラス定義に何をextendするか。まずここで迷う

class FeatureContext implements Context, SnippetAcceptingContext

これは、Behat+Selenium Webdriverで受け入れテストの自動化をやってみたでのクラス定義

単純にPHPでアレするだけならこれで構わないのだけど、WebUIのテストでは必須とも言える、要素をクリックする、だとか、ドコドコにナニナニを入力する、といった記述をしたい

結果だけ言うと、behat/mink-extensionから、RasMinkContextをみんなextendしてたので、それをやったらやりたかった事が出来るようになった

<?php

use Behat\Behat\Context\Context;
use Behat\MinkExtension\Context\RawMinkContext;    ←追加
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;

/**
 * Defines application features from the specific context.
 */
class FeatureContext extends RawMinkContext implements Context, SnippetAcceptingContext    ←extends RawMinkContext追加
{

実行中セッションのDOMにアクセスしたい

実行中のセッションに対して何かする場合は、$this->getSession()->getPage()でDOMにアクセスすることが出来ます

var_dump(get_class_methods($this->getSession()->getPage()));

array(31) {
  [0]=>
  string(8) "getXpath"
  [1]=>
  string(10) "getContent"
  [2]=>
  string(10) "hasContent"
  [3]=>
  string(8) "findById"
  [4]=>
  string(7) "hasLink"
  [5]=>
  string(8) "findLink"
  [6]=>
  string(9) "clickLink"
  [7]=>
  string(9) "hasButton"
  [8]=>
  string(10) "findButton"
  [9]=>
  string(11) "pressButton"
  [10]=>
  string(8) "hasField"
  [11]=>
  string(9) "findField"
  [12]=>
  string(9) "fillField"
  [13]=>
  string(15) "hasCheckedField"
  [14]=>
  string(17) "hasUncheckedField"
  [15]=>
  string(10) "checkField"
  [16]=>
  string(12) "uncheckField"
  [17]=>
  string(9) "hasSelect"
  [18]=>
  string(17) "selectFieldOption"
  [19]=>
  string(8) "hasTable"
  [20]=>
  string(17) "attachFileToField"
  [21]=>
  string(11) "__construct"
  [22]=>
  string(10) "getSession"
  [23]=>
  string(3) "has"
  [24]=>
  string(7) "isValid"
  [25]=>
  string(7) "waitFor"
  [26]=>
  string(4) "find"
  [27]=>
  string(7) "findAll"
  [28]=>
  string(7) "getText"
  [29]=>
  string(7) "getHtml"
  [30]=>
  string(12) "getOuterHtml"
}

findAllを使って、DOM内の要素を検索します。(以下はログインボタンを探す例)

$submits = $this->getSession()->getPage()->findAll("css", "*[type='submit']");

$loginButton = false;

foreach($submits as $i => $row)
{
    if("ログイン" === $row->getValue())
    {
        $loginButton = $row;
        break;
    }
}

cssセレクタがそのまま使えるので、findAll('css', [セレクタ])が馴染みやすくてとても良いです

返ってきたオブジェクトには、DOM操作のためのメソッドが用意されています

var_dump(get_class_methods($loginButton));

array(55) {
  [0]=>
  string(11) "__construct"
  [1]=>
  string(8) "getXpath"
  [2]=>
  string(9) "getParent"
  [3]=>
  string(10) "getTagName"
  [4]=>
  string(8) "getValue"
  [5]=>
  string(8) "setValue"
  [6]=>
  string(12) "hasAttribute"
  [7]=>
  string(12) "getAttribute"
  [8]=>
  string(8) "hasClass"
  [9]=>
  string(5) "click"
  [10]=>
  string(5) "press"
  [11]=>
  string(11) "doubleClick"
  [12]=>
  string(10) "rightClick"
  [13]=>
  string(5) "check"
  [14]=>
  string(7) "uncheck"
  [15]=>
  string(9) "isChecked"
  [16]=>
  string(12) "selectOption"
  [17]=>
  string(10) "isSelected"
  [18]=>
  string(10) "attachFile"
  [19]=>
  string(9) "isVisible"
  [20]=>
  string(9) "mouseOver"
  [21]=>
  string(6) "dragTo"
  [22]=>
  string(5) "focus"
  [23]=>
  string(4) "blur"
  [24]=>
  string(8) "keyPress"
  [25]=>
  string(7) "keyDown"
  [26]=>
  string(5) "keyUp"
  [27]=>
  string(6) "submit"
  [28]=>
  string(8) "findById"
  [29]=>
  string(7) "hasLink"
  [30]=>
  string(8) "findLink"
  [31]=>
  string(9) "clickLink"
  [32]=>
  string(9) "hasButton"
  [33]=>
  string(10) "findButton"
  [34]=>
  string(11) "pressButton"
  [35]=>
  string(8) "hasField"
  [36]=>
  string(9) "findField"
  [37]=>
  string(9) "fillField"
  [38]=>
  string(15) "hasCheckedField"
  [39]=>
  string(17) "hasUncheckedField"
  [40]=>
  string(10) "checkField"
  [41]=>
  string(12) "uncheckField"
  [42]=>
  string(9) "hasSelect"
  [43]=>
  string(17) "selectFieldOption"
  [44]=>
  string(8) "hasTable"
  [45]=>
  string(17) "attachFileToField"
  [46]=>
  string(10) "getSession"
  [47]=>
  string(3) "has"
  [48]=>
  string(7) "isValid"
  [49]=>
  string(7) "waitFor"
  [50]=>
  string(4) "find"
  [51]=>
  string(7) "findAll"
  [52]=>
  string(7) "getText"
  [53]=>
  string(7) "getHtml"
  [54]=>
  string(12) "getOuterHtml"
}

このログインボタンをクリックするなら↓このようにclickメソッドをコールします

$loginButton->click();

今回作ったメソッドは↓こちら

/**
 * @When :acount でログインしている
 *
 * @param       string $acount
 */
public function adminLogin($acount)
{
    $this->getSession()->getPage()->fillField("login_id", $acount);

    switch($acount)
    {
        case "admin":
            $this->getSession()->getPage()->fillField("login_password", "xxxxxxxxxx");
            break;
        case "guest":
            $this->getSession()->getPage()->fillField("login_password", "xxxxxxxxxx");
            break;
    }

    $submits = $this->getSession()->getPage()->findAll("css", "*[type='submit']");

    $loginButton = false;

    foreach($submits as $i => $row)
    {
        if("ログイン" === $row->getValue())
        {
            $loginButton = $row;
            break;
        }
    }

    $loginButton->click();
}

これでfeature側に、「前提 ○○○でログインしている」という条件でログインが出来るようになりました

# language: ja
#
フィーチャ: アカウント新規登録

    @javascript
    シナリオ: アカウント作成
        前提 ユーザーは "/login/" を表示している
        前提 "admin" でログインしている