PHPUnit + php-webdriver でWebUIのテストを書く


WebUIのテストは[webdriverjs + jasmine-node](http://qiita.com/zaburo/items/55ebd5c4ea5b9e1f0416)で書いていましたが、PHPでも書いてみます。

WebAPIのテストは[こちら](http://qiita.com/zaburo/private/13bfdc349e1421421c98)。


##前提

* composerがインストールされている
* Mac(El capitan)

ホームディレクトリ内にtestというフォルダを作ってその中で作業するものとします。

##準備

###ライブラリのダウンロード

```bash
#ディレクトリ作成
cd ~/
mkdir test
cd test

#phpunitとwebdriverのインストール
composer require phpunit/phpunit
composer require facebook/webdriver
```

###Selenium standaloneサーバのダウンロードと起動

[ここ](http://selenium-release.storage.googleapis.com/index.html)から最新のものをダウンロードします。

```bash
wget http://selenium-release.storage.googleapis.com/2.48/selenium-server-standalone-2.48.2.jar
```

ダウンロードしたら、起動しておきます。

```bash
java -jar selenium-server-standalone-2.48.2.jar
```

標準では4444ポートで起動するようです。2.48.2の部分は、ダウンロードしたファイルに合わせて変更して下さい。

###chromedriverのダウンロード

[ここ](https://sites.google.com/a/chromium.org/chromedriver/downloads)から、最新のchromedriverをダウンロードして、PATHの通ったディレクトリにコピーします。

```bash
wget http://chromedriver.storage.googleapis.com/2.20/chromedriver_mac32.zip
unzip chromedriver_mac32.zip
mv chromedriver /usr/local/bin/
```

##テストを書く（テスト対象）

###制御用のサイトを準備する

localhost/test/index.php(html)を用意して、下記のようなHTMLを書きました。
とりあえずForm要素の制御をしたいので、主要要素を書きます。デザインは無視です。

ポイントとしては

* 主要なForm要素の制御を調べる
* Confirm等のアラートの制御を調べる

という感じ。

```html
<!doctype html>
<html>
<head>
<title>top</title>
</head>
<body>
<h3>input</h3>
<form action="res.php" method="post">
<!-- text -->
name:<input type="text" name="name" id="name"><br>
email:<input type="text" name="email" id="email"></br>

<!-- radio -->
gender:
 男：<input type="radio" name="gender" value="男" id="gender1">
 女：<input type="radio" name="gender" value="女" id="gender2"><br>
<!-- checkbox -->
media:
 Web:<input type="checkbox" name="web" value="Web" id="media1">
 TV:<input type="checkbox" name="TV" value="TV" id="media2"><br>

<!-- select -->
<select name="area" id="area">
	<option value="選択して下さい">選択して下さい</option>
	<option value="関東">関東</option>
	<option value="関西">関西</option>
</select><br>

<!-- textarea -->
ご意見:
<textarea id="note"></textarea><br>

<!-- hidden 対応できない -->
<input type="hidden" name="key" value="" id="key">

<!-- submit -->
<input type="submit" value="push" id="btn">

</form>
<!--script-->
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<script>
$(function(){
	$("#btn").click(function(){
		if(confirm('いいですか？')){
			//何もしない（action先へ）
		}else{
			return false;
		}
	});
});
</script>
</body>
</html>
```
res.phpというやつに飛ばしていますので、そのコードも。

```php
<?php

	//受け取ったPOSTを表示
	print_r($_POST);
	
	//初期化（別にいらない）
	$name = "xxx";
	$email = "xxx@xxx.com";

	//受け取り
	if(isset($_POST['name'])) $name = $_POST['name'];
	if(isset($_POST['email'])) $email = $_POST['email'];
?>
<!doctype html>
<html>
<head>
<title>res</title>
</head>
<body>
name:<span id="name2"><?php echo $name; ?></span><br>
email:<span id="email2"><?php echo $email; ?></span>
</body>
</html>
```

値がきているのを調べるのがめんどくさいのでprint_r($_POST)で表示させています。
あと、受け取った値を調べたいのでnameとemailだけタグに入れてidを振ってみました。

##テストを書く（テストコード）
testフォルダの中にさらにtestsフォルダを作り、その中にsampleTest.phpというファイルを作成し、テストを書いて行きます。

###最小限のコード

まずは、環境がきちんと動いているかチェックしてみましょう。

```bash
cd ~/test
mkdir tests
vi tests/sampleTest.php
```
sampleTest.phpを編集します。

```php
<?php

class sampleTest extends PHPUnit_Framework_TestCase
{
	public function testFunction()
	{
		//test assertion
		$this->assertEquals("hoge","hoge");
	}
}
```

###実行

実行してみます。

```bash
cd ~/test
./vendor/bin/phpinit --colors tsets/

OK (1 test, 1 assertion)
```
グリーンでOK (1 test, 1 assertion)とでればOKです。

###Chrome制御のテストを書いてみる

```php
<?php

//必要クラスの読み込み（テストコードが少しでも短くなうるよう指定しておく）
use Facebook\WebDriver;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Remote;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;

class sampleTest extends PHPUnit_Framework_TestCase
{
	public function testWebUI()
	{

		//準備 --------------------------------------------------------
		
		//（java -jar java -jar selenium-server-standalone-2.48.2.jar済である必要あり）
		$host = 'http://localhost:4444/wd/hub';
		//chromedriverを指定（chromedriverをダウンロードして/user/local/bin等に入れておく。
		$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome());

		// //テストするサイトに移動
		$driver->get('http://localhost/test/index.php');

		//最後の要素が読み込まれるまで待つ(sec,millisec)
		//とにかく待つならsleep(3)とかでも可？（未確認）
		$driver->wait(10,500)->until(
			//WebDriverExpectedCondition::titleIs('top')
			WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::cssSelector('#btn'))
		);
		
		//Form制御 -----------------------------------------------------

		//text:name選択
		$driver->findElement(WebDriverBy::cssSelector('#name'))->click();
		//入力
		$driver->getKeyboard()->sendKeys("hoge");

		//text:email選択
		$driver->findElement(WebDriverBy::cssSelector('#email'))->click();
		//入力
		$driver->getKeyboard()->sendKeys("hoge@hoge.com");

		//radio（clickだけでよい）
		$driver->findElement(WebDriverBy::cssSelector('#gender2'))->click();

		//checkbox（clickだけでよい）
		$driver->findElement(WebDriverBy::cssSelector('#media1'))->click();

		//select
		$driver->findElement(WebDriverBy::cssSelector('#area'))->click();
		//選択（sendKeyでよい）
		$driver->getKeyboard()->sendKeys("関西");

		//textarea
		$driver->findElement(WebDriverBy::cssSelector('#note'))->click();
		//入力
		$driver->getKeyboard()->sendKeys("何もありません。");

		//submit click
		$driver->findElement(WebDriverBy::cssSelector('#btn'))->click();

		//confirm ok
		$driver->switchTo()->alert()->accept();

		//confirm cancel
		// $driver->switchTo()->alert()->dismiss();

		//ページ移動 ======

		//移動先で要素取得
		$email = $driver->findElement(WebDriverBy::cssSelector('#email2'))->getText();

		//assert ------------------------------------------------------
		
		//評価
		$this->assertEquals("hoge@hoge.com",$email);
		$this->assertRegExp('/^h.+@.+com$/',$email);

		//ブラウザを閉じる
		$driver->close();
	}
}
```

こんな感じです。
少し複雑なのですが、一度パターンを書いてしまえば、それほど難しくはありません。

実行してみます。

```bash
./vendor/bin/phpinit --colors tsets/
```

```bash
Facebook\WebDriver\Exception\WebDriverCurlException: Curl error thrown for http POST to /session with params: {"desiredCapabilities":{"browserName":"chrome","platform":"ANY"}}

Failed to connect to localhost port 4444: Connection refused
```
このようなエラーが出た場合は、selenium serverが起動していません。起動させましょう。

```bash
java -jar selenium-server-standalone-2.48.2.jar
```

##その他

###覚えてると便利そうなwebdriverメソッド

githubの[サンプルページ](https://github.com/facebook/php-webdriver/blob/community/example.php)や[Wiki](https://github.com/facebook/php-webdriver/wiki/Example-command-reference)などが参考になります。が、その他にもいろいろ。

* WebDriverExpectedCondition::presenceOfAllElementsLocatedBy(WebDriverBy::cssSelector('#hoge'))
* WebDriverExpectedCondition::titleIs('top')
* \$driver->getPageSource()
* \$driver->getTitle()
* \$driver->findElement(WebDriverBy::linkText('click here'))->click()
* \$driver->takeScreenshot('test.png')

###最低限のassertメソッド

* assertEquals(\$expect,\$var) $expectと$varが等しいか？
* assertRegExp(\$ptn, \$var) 正規表現にマッチするかどうか？
* assertNull(\$var)
* assertTrue(\$var)
* assertFalse(\$var)
* assertCount(\$num,\$array)
* assertEmpty(\$array)

Laravelを利用している場合は標準以外に拡張されたassertも使えます。
詳しくは[ここ](http://laravel.com/docs/5.1/testing)。

###LaravelのQueryBuilderやEloquentを使う

UIやAPIのテストを実行した結果、DBに正常に値が書き込まれているかをチェックしたくなります。もちろん普通にPDOでコードを書いてもいいですが、Laravelに慣れてる場合、QueryBuilderやEloquentが使いたくなります。その場合は、[こちら](http://qiita.com/zaburo/items/9b8aa05c975677669142)を参考にしてください。