sfTestBrowser.class.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. <?php
  2. require_once(dirname(__FILE__).'/../vendor/lime/lime.php');
  3. /*
  4. * This file is part of the symfony package.
  5. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * sfTestBrowser simulates a fake browser which can test a symfony application.
  12. *
  13. * @package symfony
  14. * @subpackage test
  15. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  16. * @version SVN: $Id: sfTestBrowser.class.php 16170 2009-03-11 08:02:42Z fabien $
  17. */
  18. class sfTestBrowser extends sfBrowser
  19. {
  20. protected static
  21. $test = null;
  22. /**
  23. * Initializes the browser tester instance.
  24. *
  25. * @param string $hostname Hostname
  26. * @param string $remote Remote IP address
  27. * @param array $options Options
  28. */
  29. public function initialize($hostname = null, $remote = null, $options = array())
  30. {
  31. parent::initialize($hostname, $remote, $options);
  32. $output = isset($options['output']) ? $options['output'] : new lime_output_color();
  33. if (is_null(self::$test))
  34. {
  35. self::$test = new lime_test(null, $output);
  36. }
  37. }
  38. /**
  39. * Retrieves the lime_test instance.
  40. *
  41. * @return lime_test The lime_test instance
  42. */
  43. public function test()
  44. {
  45. return self::$test;
  46. }
  47. /**
  48. * Retrieves and checks an action.
  49. *
  50. * @param string $module Module name
  51. * @param string $action Action name
  52. * @param string $url Url
  53. * @param string $code The expected return status code
  54. *
  55. * @return sfTestBrowser The current sfTestBrowser instance
  56. */
  57. public function getAndCheck($module, $action, $url = null, $code = 200)
  58. {
  59. return $this->
  60. get(null !== $url ? $url : sprintf('/%s/%s', $module, $action))->
  61. isStatusCode($code)->
  62. isRequestParameter('module', $module)->
  63. isRequestParameter('action', $action)
  64. ;
  65. }
  66. /**
  67. * Calls a request.
  68. *
  69. * @param string $uri URI to be invoked
  70. * @param string $method HTTP method used
  71. * @param array $parameters Additional paramaters
  72. * @param bool $changeStack If set to false ActionStack is not changed
  73. *
  74. * @return sfTestBrowser The current sfTestBrowser instance
  75. */
  76. public function call($uri, $method = 'get', $parameters = array(), $changeStack = true)
  77. {
  78. $uri = $this->fixUri($uri);
  79. $this->test()->comment(sprintf('%s %s', strtolower($method), $uri));
  80. return parent::call($uri, $method, $parameters, $changeStack);
  81. }
  82. /**
  83. * Simulates the browser back button.
  84. *
  85. * @return sfTestBrowser The current sfTestBrowser instance
  86. */
  87. public function back()
  88. {
  89. $this->test()->comment('back');
  90. return parent::back();
  91. }
  92. /**
  93. * Simulates the browser forward button.
  94. *
  95. * @return sfTestBrowser The current sfTestBrowser instance
  96. */
  97. public function forward()
  98. {
  99. $this->test()->comment('forward');
  100. return parent::forward();
  101. }
  102. /**
  103. * Tests if the current request has been redirected.
  104. *
  105. * @param bool $boolean Flag for redirection mode
  106. *
  107. * @return sfTestBrowser The current sfTestBrowser instance
  108. */
  109. public function isRedirected($boolean = true)
  110. {
  111. if ($location = $this->context->getResponse()->getHttpHeader('location'))
  112. {
  113. $boolean ? $this->test()->pass(sprintf('page redirected to "%s"', $location)) : $this->test()->fail(sprintf('page redirected to "%s"', $location));
  114. }
  115. else
  116. {
  117. $boolean ? $this->test()->fail('page redirected') : $this->test()->pass('page not redirected');
  118. }
  119. return $this;
  120. }
  121. /**
  122. * Checks that the current response contains a given text.
  123. *
  124. * @param string $uri Uniform resource identifier
  125. * @param string $text Text in the response
  126. *
  127. * @return sfTestBrowser The current sfTestBrowser instance
  128. */
  129. public function check($uri, $text = null)
  130. {
  131. $this->get($uri)->isStatusCode();
  132. if ($text !== null)
  133. {
  134. $this->responseContains($text);
  135. }
  136. return $this;
  137. }
  138. /**
  139. * Test an status code for the current test browser.
  140. *
  141. * @param string Status code to check, default 200
  142. *
  143. * @return sfTestBrowser The current sfTestBrowser instance
  144. */
  145. public function isStatusCode($statusCode = 200)
  146. {
  147. $this->test()->is($this->getResponse()->getStatusCode(), $statusCode, sprintf('status code is "%s"', $statusCode));
  148. return $this;
  149. }
  150. /**
  151. * Tests whether or not a given string is in the response.
  152. *
  153. * @param string Text to check
  154. *
  155. * @return sfTestBrowser The current sfTestBrowser instance
  156. */
  157. public function responseContains($text)
  158. {
  159. $this->test()->like($this->getResponse()->getContent(), '/'.preg_quote($text, '/').'/', sprintf('response contains "%s"', substr($text, 0, 40)));
  160. return $this;
  161. }
  162. /**
  163. * Tests whether or not a given key and value exists in the current request.
  164. *
  165. * @param string $key
  166. * @param string $value
  167. *
  168. * @return sfTestBrowser The current sfTestBrowser instance
  169. */
  170. public function isRequestParameter($key, $value)
  171. {
  172. $this->test()->is($this->getRequest()->getParameter($key), $value, sprintf('request parameter "%s" is "%s"', $key, $value));
  173. return $this;
  174. }
  175. /**
  176. * Checks that the request is forwarded to a given module/action.
  177. *
  178. * @param string $moduleName The module name
  179. * @param string $actionName The action name
  180. * @param mixed $position The position in the action stack (default to the last entry)
  181. *
  182. * @return sfTestBrowser The current sfTestBrowser instance
  183. */
  184. public function isForwardedTo($moduleName, $actionName, $position = 'last')
  185. {
  186. $actionStack = $this->context->getActionStack();
  187. switch ($position)
  188. {
  189. case 'first':
  190. $entry = $actionStack->getFirstEntry();
  191. break;
  192. case 'last':
  193. $entry = $actionStack->getLastEntry();
  194. break;
  195. default:
  196. $entry = $actionStack->getEntry($position);
  197. }
  198. $this->test()->is($entry->getModuleName(), $moduleName, sprintf('request is forwarded to the "%s" module (%s)', $moduleName, $position));
  199. $this->test()->is($entry->getActionName(), $actionName, sprintf('request is forwarded to the "%s" action (%s)', $actionName, $position));
  200. return $this;
  201. }
  202. /**
  203. * Tests for a response header.
  204. *
  205. * @param string $key
  206. * @param string $value
  207. *
  208. * @return sfTestBrowser The current sfTestBrowser instance
  209. */
  210. public function isResponseHeader($key, $value)
  211. {
  212. $headers = explode(', ', $this->getResponse()->getHttpHeader($key));
  213. $ok = false;
  214. foreach ($headers as $header)
  215. {
  216. if ($header == $value)
  217. {
  218. $ok = true;
  219. break;
  220. }
  221. }
  222. $this->test()->ok($ok, sprintf('response header "%s" is "%s" (%s)', $key, $value, $this->getResponse()->getHttpHeader($key)));
  223. return $this;
  224. }
  225. /**
  226. * Tests for the user culture.
  227. *
  228. * @param string $culture The user culture
  229. *
  230. * @return sfTestBrowser The current sfTestBrowser instance
  231. */
  232. public function isUserCulture($culture)
  233. {
  234. $this->test()->is($this->getContext()->getUser()->getCulture(), $culture, sprintf('user culture is "%s"', $culture));
  235. return $this;
  236. }
  237. /**
  238. * Tests for the request is in the given format.
  239. *
  240. * @param string $format The request format
  241. *
  242. * @return sfTestBrowser The current sfTestBrowser instance
  243. */
  244. public function isRequestFormat($format)
  245. {
  246. $this->test()->is($this->getContext()->getRequest()->getRequestFormat(), $format, sprintf('request format is "%s"', $format));
  247. return $this;
  248. }
  249. /**
  250. * Tests that the current response matches a given CSS selector.
  251. *
  252. * @param string $selector The response selector or a sfDomCssSelector object
  253. * @param mixed $value Flag for the selector
  254. * @param array $options Options for the current test
  255. *
  256. * @return sfTestBrowser The current sfTestBrowser instance
  257. */
  258. public function checkResponseElement($selector, $value = true, $options = array())
  259. {
  260. if (is_object($selector))
  261. {
  262. $values = $selector->getValues();
  263. }
  264. else
  265. {
  266. $values = $this->getResponseDomCssSelector()->matchAll($selector)->getValues();
  267. }
  268. if (false === $value)
  269. {
  270. $this->test()->is(count($values), 0, sprintf('response selector "%s" does not exist', $selector));
  271. }
  272. else if (true === $value)
  273. {
  274. $this->test()->cmp_ok(count($values), '>', 0, sprintf('response selector "%s" exists', $selector));
  275. }
  276. else if (is_int($value))
  277. {
  278. $this->test()->is(count($values), $value, sprintf('response selector "%s" matches "%s" times', $selector, $value));
  279. }
  280. else if (preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $value, $match))
  281. {
  282. $position = isset($options['position']) ? $options['position'] : 0;
  283. if ($match[1] == '!')
  284. {
  285. $this->test()->unlike(@$values[$position], substr($value, 1), sprintf('response selector "%s" does not match regex "%s"', $selector, substr($value, 1)));
  286. }
  287. else
  288. {
  289. $this->test()->like(@$values[$position], $value, sprintf('response selector "%s" matches regex "%s"', $selector, $value));
  290. }
  291. }
  292. else
  293. {
  294. $position = isset($options['position']) ? $options['position'] : 0;
  295. $this->test()->is(@$values[$position], $value, sprintf('response selector "%s" matches "%s"', $selector, $value));
  296. }
  297. if (isset($options['count']))
  298. {
  299. $this->test()->is(count($values), $options['count'], sprintf('response selector "%s" matches "%s" times', $selector, $options['count']));
  300. }
  301. return $this;
  302. }
  303. /**
  304. * Tests if an exception is thrown by the latest request.
  305. *
  306. * @param string $class Class name
  307. * @param string $message Message name
  308. *
  309. * @return sfTestBrowser The current sfTestBrowser instance
  310. */
  311. public function throwsException($class = null, $message = null)
  312. {
  313. $e = $this->getCurrentException();
  314. if (null === $e)
  315. {
  316. $this->test()->fail('response returns an exception');
  317. }
  318. else
  319. {
  320. if (null !== $class)
  321. {
  322. $this->test()->ok($e instanceof $class, sprintf('response returns an exception of class "%s"', $class));
  323. }
  324. else
  325. {
  326. $lime = null;
  327. }
  328. if (null !== $message && preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $message, $match))
  329. {
  330. if ($match[1] == '!')
  331. {
  332. $this->test()->unlike($e->getMessage(), substr($message, 1), sprintf('response exception message does not match regex "%s"', $message));
  333. }
  334. else
  335. {
  336. $this->test()->like($e->getMessage(), $message, sprintf('response exception message matches regex "%s"', $message));
  337. }
  338. }
  339. else if (null !== $message)
  340. {
  341. $this->test()->is($e->getMessage(), $message, sprintf('response exception message matches regex "%s"', $message));
  342. }
  343. }
  344. $this->resetCurrentException();
  345. return $this;
  346. }
  347. /**
  348. * Trigger a test failure if an uncaught exception is present.
  349. *
  350. * @return bool
  351. */
  352. public function checkCurrentExceptionIsEmpty()
  353. {
  354. if (false === ($empty = parent::checkCurrentExceptionIsEmpty()))
  355. {
  356. $this->test()->fail(sprintf('last request threw an uncaught exception "%s: %s"', get_class($this->getCurrentException()), $this->getCurrentException()->getMessage()));
  357. }
  358. return $empty;
  359. }
  360. /**
  361. * Tests if the given uri is cached.
  362. *
  363. * @param boolean $boolean Flag for checking the cache
  364. * @param boolean $with_layout If have or not layout
  365. *
  366. * @return sfTestBrowser The current sfTestBrowser instance
  367. */
  368. public function isCached($boolean, $with_layout = false)
  369. {
  370. return $this->isUriCached($this->context->getRouting()->getCurrentInternalUri(), $boolean, $with_layout);
  371. }
  372. /**
  373. * Tests if the given uri is cached.
  374. *
  375. * @param string $uri Uniform resource identifier
  376. * @param boolean $boolean Flag for checking the cache
  377. * @param boolean $with_layout If have or not layout
  378. *
  379. * @return sfTestBrowser The current sfTestBrowser instance
  380. */
  381. public function isUriCached($uri, $boolean, $with_layout = false)
  382. {
  383. $cacheManager = $this->context->getViewCacheManager();
  384. // check that cache is enabled
  385. if (!$cacheManager)
  386. {
  387. $this->test()->ok(!$boolean, 'cache is disabled');
  388. return $this;
  389. }
  390. if ($uri == $this->context->getRouting()->getCurrentInternalUri())
  391. {
  392. $main = true;
  393. $type = $with_layout ? 'page' : 'action';
  394. }
  395. else
  396. {
  397. $main = false;
  398. $type = $uri;
  399. }
  400. // check layout configuration
  401. if ($cacheManager->withLayout($uri) && !$with_layout)
  402. {
  403. $this->test()->fail('cache without layout');
  404. $this->test()->skip('cache is not configured properly', 2);
  405. }
  406. else if (!$cacheManager->withLayout($uri) && $with_layout)
  407. {
  408. $this->test()->fail('cache with layout');
  409. $this->test()->skip('cache is not configured properly', 2);
  410. }
  411. else
  412. {
  413. $this->test()->pass('cache is configured properly');
  414. // check page is cached
  415. $ret = $this->test()->is($cacheManager->has($uri), $boolean, sprintf('"%s" %s in cache', $type, $boolean ? 'is' : 'is not'));
  416. // check that the content is ok in cache
  417. if ($boolean)
  418. {
  419. if (!$ret)
  420. {
  421. $this->test()->fail('content in cache is ok');
  422. }
  423. else if ($with_layout)
  424. {
  425. $response = unserialize($cacheManager->get($uri));
  426. $content = $response->getContent();
  427. $this->test()->ok($content == $this->getResponse()->getContent(), 'content in cache is ok');
  428. }
  429. else
  430. {
  431. $ret = unserialize($cacheManager->get($uri));
  432. $content = $ret['content'];
  433. $this->test()->ok(false !== strpos($this->getResponse()->getContent(), $content), 'content in cache is ok');
  434. }
  435. }
  436. }
  437. return $this;
  438. }
  439. }
  440. if (!defined('E_RECOVERABLE_ERROR'))
  441. {
  442. define('E_RECOVERABLE_ERROR', 4096);
  443. }
  444. /**
  445. * Error handler for the current test browser instance.
  446. *
  447. * @param mixed $errno Error number
  448. * @param string $errstr Error message
  449. * @param string $errfile Error file
  450. * @param mixed $errline Error line
  451. */
  452. function sfTestBrowserErrorHandler($errno, $errstr, $errfile, $errline)
  453. {
  454. if (($errno & error_reporting()) == 0)
  455. {
  456. return false;
  457. }
  458. $msg = sprintf('PHP sent a "%%s" error at %s line %s (%s)', $errfile, $errline, $errstr);
  459. switch ($errno)
  460. {
  461. case E_WARNING:
  462. $msg = sprintf($msg, 'warning');
  463. throw new Exception($msg);
  464. break;
  465. case E_NOTICE:
  466. $msg = sprintf($msg, 'notice');
  467. throw new Exception($msg);
  468. break;
  469. case E_STRICT:
  470. $msg = sprintf($msg, 'strict');
  471. throw new Exception($msg);
  472. break;
  473. case E_RECOVERABLE_ERROR:
  474. $msg = sprintf($msg, 'catchable');
  475. throw new Exception($msg);
  476. break;
  477. }
  478. return false;
  479. }
  480. set_error_handler('sfTestBrowserErrorHandler');