Changeset 11116
- Timestamp:
- 08/25/08 17:20:48 (10 months ago)
- Files:
-
- branches/1.2/UPGRADE_TO_1_2 (modified) (1 diff)
- branches/1.2/lib/autoload/sfCoreAutoload.class.php (modified) (1 diff)
- branches/1.2/lib/test/sfTestBrowser.class.php (modified) (2 diffs)
- branches/1.2/lib/test/sfTestFunctional.class.php (added)
- branches/1.2/lib/test/sfTestFunctionalBase.class.php (added)
- branches/1.2/lib/util/sfBrowser.class.php (modified) (6 diffs)
- branches/1.2/lib/util/sfBrowserBase.class.php (added)
- branches/1.2/test/functional/sfTestBrowserTest.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/1.2/UPGRADE_TO_1_2
r11102 r11116 327 327 (we used to compute it with `sfConfig::get('sf_time_start')`, which does not exist anymore). 328 328 This means that the time displayed will be much larger than in symfony 1.0 and 1.1. 329 330 Browser 331 ------- 332 333 The `sfBrowser` and `sfTestBrowser` classes have been refactored in four classes: 334 335 * `sfBrowserBase`: The base browser class. It knows nothing about symfony, 336 except classes from the symfony platform. 337 338 * `sfBrowser`: It inherits from `sfBrowserBase` and implements the 339 methods specific to symfony. 340 341 * `sfTestFunctionalBase`: The base functional test class. It implements test 342 methods that are independant from symfony. 343 344 * `sfTestFunctional`: It inherits from `sfTestFunctionalBase` and implements 345 the test methods specific to symfony. 346 347 * `sfTestBrowser`: A BC class which is the same as the `sfTestFunctional` 348 class with a constructor signature compatible with symfony 1.1 349 350 The idea behind the refactor is that the `sfTestFunctional` class is a test class, 351 not a browser. So, it takes a browser and a test object as its arguments: 352 353 [php] 354 $testBrowser = new sfTestBrowser('localhost'); 355 356 $tester = new sfTestFunctional(new sfBrowser('localhost'), new lime_test()); 357 358 The `sfTestFunctional` class acts as a proxy for the browser class which means 359 that all methods from the browser are accessible directly from the tester object. 360 361 This refactor must not introduce backward incompatibility with symfony 1.1. branches/1.2/lib/autoload/sfCoreAutoload.class.php
r11041 r11116 363 363 'sfTestUnitTask' => 'task/test', 364 364 'sfTestBrowser' => 'test', 365 'sfTestFunctional' => 'test', 366 'sfTestFunctionalBase' => 'test', 365 367 'sfBasicSecurityUser' => 'user', 366 368 'sfSecurityUser' => 'user', 367 369 'sfUser' => 'user', 368 370 'sfBrowser' => 'util', 371 'sfBrowserBase' => 'util', 369 372 'sfCallable' => 'util', 370 373 'sfContext' => 'util', branches/1.2/lib/test/sfTestBrowser.class.php
r10830 r11116 12 12 13 13 /** 14 * sfTestBrowser simulates a fake browser which can test a symfony application. 14 * sfTestBrowser simulates a browser which can test a symfony application. 15 * 16 * sfTestFunctional is backward compatible class for symfony 1.0, and 1.1. 17 * For new code, you can use the sfTestFunctional class directly. 15 18 * 16 19 * @package symfony … … 19 22 * @version SVN: $Id$ 20 23 */ 21 class sfTestBrowser extends sf Browser24 class sfTestBrowser extends sfTestFunctional 22 25 { 23 protected static24 $test = null;25 26 26 /** 27 27 * Initializes the browser tester instance. 28 28 * 29 * @param string $hostname Hostname 30 * @param string $remote Remote IP address31 * @param array $options Options 29 * @param string $hostname Hostname to browse 30 * @param string $remote Remote address to spook 31 * @param array $options Options for sfBrowser 32 32 */ 33 public function initialize($hostname = null, $remote = null, $options = array())33 public function __construct($hostname = null, $remote = null, $options = array()) 34 34 { 35 parent::initialize($hostname, $remote, $options); 36 37 $output = isset($options['output']) ? $options['output'] : new lime_output_color(); 38 39 if (is_null(self::$test)) 35 if (is_object($hostname)) 40 36 { 41 self::$test = new lime_test(null, $output); 42 } 43 } 44 45 /** 46 * Retrieves the lime_test instance. 47 * 48 * @return lime_test The lime_test instance 49 */ 50 public function test() 51 { 52 return self::$test; 53 } 54 55 /** 56 * Retrieves and checks an action. 57 * 58 * @param string $module Module name 59 * @param string $action Action name 60 * @param string $url Url 61 * @param string $code The expected return status code 62 * 63 * @return sfTestBrowser The current sfTestBrowser instance 64 */ 65 public function getAndCheck($module, $action, $url = null, $code = 200) 66 { 67 return $this-> 68 get(null !== $url ? $url : sprintf('/%s/%s', $module, $action))-> 69 isStatusCode($code)-> 70 isRequestParameter('module', $module)-> 71 isRequestParameter('action', $action) 72 ; 73 } 74 75 /** 76 * Calls a request. 77 * 78 * @param string $uri URI to be invoked 79 * @param string $method HTTP method used 80 * @param array $parameters Additional paramaters 81 * @param bool $changeStack If set to false ActionStack is not changed 82 * 83 * @return sfTestBrowser The current sfTestBrowser instance 84 */ 85 public function call($uri, $method = 'get', $parameters = array(), $changeStack = true) 86 { 87 $uri = $this->fixUri($uri); 88 89 $this->test()->comment(sprintf('%s %s', strtolower($method), $uri)); 90 91 return parent::call($uri, $method, $parameters, $changeStack); 92 } 93 94 /** 95 * Simulates the browser back button. 96 * 97 * @return sfTestBrowser The current sfTestBrowser instance 98 */ 99 public function back() 100 { 101 $this->test()->comment('back'); 102 103 return parent::back(); 104 } 105 106 /** 107 * Simulates the browser forward button. 108 * 109 * @return sfTestBrowser The current sfTestBrowser instance 110 */ 111 public function forward() 112 { 113 $this->test()->comment('forward'); 114 115 return parent::forward(); 116 } 117 118 /** 119 * Tests if the current request has been redirected. 120 * 121 * @param bool $boolean Flag for redirection mode 122 * 123 * @return sfTestBrowser The current sfTestBrowser instance 124 */ 125 public function isRedirected($boolean = true) 126 { 127 if ($location = $this->context->getResponse()->getHttpHeader('location')) 128 { 129 $boolean ? $this->test()->pass(sprintf('page redirected to "%s"', $location)) : $this->test()->fail(sprintf('page redirected to "%s"', $location)); 37 // new signature 38 parent::__construct($hostname, $remote); 130 39 } 131 40 else 132 41 { 133 $boolean ? $this->test()->fail('page redirected') : $this->test()->pass('page not redirected'); 134 } 42 $browser = new sfBrowser($hostname, $remote, $options); 135 43 136 return $this; 137 } 138 139 /** 140 * Checks that the current response contains a given text. 141 * 142 * @param string $uri Uniform resource identifier 143 * @param string $text Text in the response 144 * 145 * @return sfTestBrowser The current sfTestBrowser instance 146 */ 147 public function check($uri, $text = null) 148 { 149 $this->get($uri)->isStatusCode(); 150 151 if ($text !== null) 152 { 153 $this->responseContains($text); 154 } 155 156 return $this; 157 } 158 159 /** 160 * Test an status code for the current test browser. 161 * 162 * @param string Status code to check, default 200 163 * 164 * @return sfTestBrowser The current sfTestBrowser instance 165 */ 166 public function isStatusCode($statusCode = 200) 167 { 168 $this->test()->is($this->getResponse()->getStatusCode(), $statusCode, sprintf('status code is "%s"', $statusCode)); 169 170 return $this; 171 } 172 173 /** 174 * Tests whether or not a given string is in the response. 175 * 176 * @param string Text to check 177 * 178 * @return sfTestBrowser The current sfTestBrowser instance 179 */ 180 public function responseContains($text) 181 { 182 $this->test()->like($this->getResponse()->getContent(), '/'.preg_quote($text, '/').'/', sprintf('response contains "%s"', substr($text, 0, 40))); 183 184 return $this; 185 } 186 187 /** 188 * Tests whether or not a given key and value exists in the current request. 189 * 190 * @param string $key 191 * @param string $value 192 * 193 * @return sfTestBrowser The current sfTestBrowser instance 194 */ 195 public function isRequestParameter($key, $value) 196 { 197 $this->test()->is($this->getRequest()->getParameter($key), $value, sprintf('request parameter "%s" is "%s"', $key, $value)); 198 199 return $this; 200 } 201 202 /** 203 * Checks that the request is forwarded to a given module/action. 204 * 205 * @param string $moduleName The module name 206 * @param string $actionName The action name 207 * @param mixed $position The position in the action stack (default to the last entry) 208 * 209 * @return sfTestBrowser The current sfTestBrowser instance 210 */ 211 public function isForwardedTo($moduleName, $actionName, $position = 'last') 212 { 213 $actionStack = $this->context->getActionStack(); 214 215 switch ($position) 216 { 217 case 'first': 218 $entry = $actionStack->getFirstEntry(); 219 break; 220 case 'last': 221 $entry = $actionStack->getLastEntry(); 222 break; 223 default: 224 $entry = $actionStack->getEntry($position); 225 } 226 227 $this->test()->is($entry->getModuleName(), $moduleName, sprintf('request is forwarded to the "%s" module (%s)', $moduleName, $position)); 228 $this->test()->is($entry->getActionName(), $actionName, sprintf('request is forwarded to the "%s" action (%s)', $actionName, $position)); 229 230 return $this; 231 } 232 233 /** 234 * Tests for a response header. 235 * 236 * @param string $key 237 * @param string $value 238 * 239 * @return sfTestBrowser The current sfTestBrowser instance 240 */ 241 public function isResponseHeader($key, $value) 242 { 243 $headers = explode(', ', $this->getResponse()->getHttpHeader($key)); 244 245 $ok = false; 246 247 foreach ($headers as $header) 248 { 249 if ($header == $value) 44 if (is_null(self::$test)) 250 45 { 251 $ok = true; 252 break; 253 } 254 } 255 256 $this->test()->ok($ok, sprintf('response header "%s" is "%s" (%s)', $key, $value, $this->getResponse()->getHttpHeader($key))); 257 258 return $this; 259 } 260 261 /** 262 * Tests for the user culture. 263 * 264 * @param string $culture The user culture 265 * 266 * @return sfTestBrowser The current sfTestBrowser instance 267 */ 268 public function isUserCulture($culture) 269 { 270 $this->test()->is($this->getContext()->getUser()->getCulture(), $culture, sprintf('user culture is "%s"', $culture)); 271 272 return $this; 273 } 274 275 /** 276 * Tests for the request is in the given format. 277 * 278 * @param string $format The request format 279 * 280 * @return sfTestBrowser The current sfTestBrowser instance 281 */ 282 public function isRequestFormat($format) 283 { 284 $this->test()->is($this->getContext()->getRequest()->getRequestFormat(), $format, sprintf('request format is "%s"', $format)); 285 286 return $this; 287 } 288 289 /** 290 * Tests that the current response matches a given CSS selector. 291 * 292 * @param string $selector The response selector or a sfDomCssSelector object 293 * @param mixed $value Flag for the selector 294 * @param array $options Options for the current test 295 * 296 * @return sfTestBrowser The current sfTestBrowser instance 297 */ 298 public function checkResponseElement($selector, $value = true, $options = array()) 299 { 300 if (is_object($selector)) 301 { 302 $values = $selector->getValues(); 303 } 304 else 305 { 306 $values = $this->getResponseDomCssSelector()->matchAll($selector)->getValues(); 307 } 308 309 if (false === $value) 310 { 311 $this->test()->is(count($values), 0, sprintf('response selector "%s" does not exist', $selector)); 312 } 313 else if (true === $value) 314 { 315 $this->test()->cmp_ok(count($values), '>', 0, sprintf('response selector "%s" exists', $selector)); 316 } 317 else if (is_int($value)) 318 { 319 $this->test()->is(count($values), $value, sprintf('response selector "%s" matches "%s" times', $selector, $value)); 320 } 321 else if (preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $value, $match)) 322 { 323 $position = isset($options['position']) ? $options['position'] : 0; 324 if ($match[1] == '!') 325 { 326 $this->test()->unlike(@$values[$position], substr($value, 1), sprintf('response selector "%s" does not match regex "%s"', $selector, substr($value, 1))); 327 } 328 else 329 { 330 $this->test()->like(@$values[$position], $value, sprintf('response selector "%s" matches regex "%s"', $selector, $value)); 331 } 332 } 333 else 334 { 335 $position = isset($options['position']) ? $options['position'] : 0; 336 $this->test()->is(@$values[$position], $value, sprintf('response selector "%s" matches "%s"', $selector, $value)); 337 } 338 339 if (isset($options['count'])) 340 { 341 $this->test()->is(count($values), $options['count'], sprintf('response selector "%s" matches "%s" times', $selector, $options['count'])); 342 } 343 344 return $this; 345 } 346 347 /** 348 * Tests if an exception is thrown by the latest request. 349 * 350 * @param string $class Class name 351 * @param string $message Message name 352 * 353 * @return sfTestBrowser The current sfTestBrowser instance 354 */ 355 public function throwsException($class = null, $message = null) 356 { 357 $e = $this->getCurrentException(); 358 359 if (null === $e) 360 { 361 $this->test()->fail('response returns an exception'); 362 } 363 else 364 { 365 if (null !== $class) 366 { 367 $this->test()->ok($e instanceof $class, sprintf('response returns an exception of class "%s"', $class)); 46 $lime = new lime_test(null, isset($options['output']) ? $options['output'] : new lime_output_color()); 368 47 } 369 48 370 if (null !== $message && preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $message, $match)) 371 { 372 if ($match[1] == '!') 373 { 374 $this->test()->unlike($e->getMessage(), substr($message, 1), sprintf('response exception message does not match regex "%s"', $message)); 375 } 376 else 377 { 378 $this->test()->like($e->getMessage(), $message, sprintf('response exception message matches regex "%s"', $message)); 379 } 380 } 381 else if (null !== $message) 382 { 383 $this->test()->is($e->getMessage(), $message, sprintf('response exception message is "%s"', $message)); 384 } 49 parent::__construct($browser, $lime); 385 50 } 386 387 $this->resetCurrentException();388 389 return $this;390 }391 392 /**393 * Triggers a test failure if an uncaught exception is present.394 *395 * @return bool396 */397 public function checkCurrentExceptionIsEmpty()398 {399 if (false === ($empty = parent::checkCurrentExceptionIsEmpty()))400 {401 $this->test()->fail(sprintf('last request threw an uncaught exception "%s: %s"', get_class($this->getCurrentException()), $this->getCurrentException()->getMessage()));402 }403 404 return $empty;405 }406 407 /**408 * Checks if a cookie exists.409 *410 * @param string $name The cookie name411 * @param Boolean $exists Whether the cookie must exist or not412 *413 * @return sfTestBrowser The current sfTestBrowser instance414 */415 public function hasCookie($name, $exists = true)416 {417 if (!array_key_exists($name, $_COOKIE))418 {419 if ($exists)420 {421 $this->test()->fail(sprintf('cookie "%s" exist.', $name));422 }423 else424 {425 $this->test()->pass(sprintf('cookie "%s" does not exist.', $name));426 }427 428 return $this;429 }430 431 if ($exists)432 {433 $this->test()->pass(sprintf('cookie "%s" exists.', $name));434 }435 else436 {437 $this->test()->fail(sprintf('cookie "%s" does not exist.', $name));438 }439 440 return $this;441 }442 443 /**444 * Checks the value of a cookie.445 *446 * @param string $name The cookie name447 * @param mixed $value The expected value448 *449 * @return sfTestBrowser The current sfTestBrowser instance450 */451 public function isCookie($name, $value)452 {453 if (!array_key_exists($name, $_COOKIE))454 {455 $this->test()->fail(sprintf('cookie "%s" does not exist.', $name));456 457 return $this;458 }459 460 if (preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $value, $match))461 {462 if ($match[1] == '!')463 {464 $this->test()->unlike($_COOKIE[$name], substr($value, 1), sprintf('cookie "%s" content does not match regex "%s"', $name, $value));465 }466 else467 {468 $this->test()->like($_COOKIE[$name], $value, sprintf('cookie "%s" content matches regex "%s"', $name, $value));469 }470 }471 else if (null !== $message)472 {473 $this->test()->is($_COOKIE[$name], $value, sprintf('cookie "%s" content is ok', $name));474 }475 476 return $this;477 }478 479 /**480 * Tests if the given uri is cached.481 *482 * @param boolean $boolean Flag for checking the cache483 * @param boolean $with_layout If have or not layout484 *485 * @return sfTestBrowser The current sfTestBrowser instance486 */487 public function isCached($boolean, $with_layout = false)488 {489 return $this->isUriCached($this->context->getRouting()->getCurrentInternalUri(), $boolean, $with_layout);490 }491 492 /**493 * Tests if the given uri is cached.494 *495 * @param string $uri Uniform resource identifier496 * @param boolean $boolean Flag for checking the cache497 * @param boolean $with_layout If have or not layout498 *499 * @return sfTestBrowser The current sfTestBrowser instance500 */501 public function isUriCached($uri, $boolean, $with_layout = false)502 {503 $cacheManager = $this->context->getViewCacheManager();504 505 // check that cache is enabled506 if (!$cacheManager)507 {508 $this->test()->ok(!$boolean, 'cache is disabled');509 510 return $this;511 }512 513 if ($uri == $this->context->getRouting()->getCurrentInternalUri())514 {515 $main = true;516 $type = $with_layout ? 'page' : 'action';517 }518 else519 {520 $main = false;521 $type = $uri;522 }523 524 // check layout configuration525 if ($cacheManager->withLayout($uri) && !$with_layout)526 {527 $this->test()->fail('cache without layout');528 $this->test()->skip('cache is not configured properly', 2);529 }530 else if (!$cacheManager->withLayout($uri) && $with_layout)531 {532 $this->test()->fail('cache with layout');533 $this->test()->skip('cache is not configured properly', 2);534 }535 else536 {537 $this->test()->pass('cache is configured properly');538 539 // check page is cached540 $ret = $this->test()->is($cacheManager->has($uri), $boolean, sprintf('"%s" %s in cache', $type, $boolean ? 'is' : 'is not'));541 542 // check that the content is ok in cache543 if ($boolean)544 {545 if (!$ret)546 {547 $this->test()->fail('content in cache is ok');548 }549 else if ($with_layout)550 {551 $response = unserialize($cacheManager->get($uri));552 $content = $response->getContent();553 $this->test()->ok($content == $this->getResponse()->getContent(), 'content in cache is ok');554 }555 else556 {557 $ret = unserialize($cacheManager->get($uri));558 $content = $ret['content'];559 $this->test()->ok(false !== strpos($this->getResponse()->getContent(), $content), 'content in cache is ok');560 }561 }562 }563 564 return $this;565 51 } 566 52 } 567 568 if (!defined('E_RECOVERABLE_ERROR'))569 {570 define('E_RECOVERABLE_ERROR', 4096);571 }572 573 /**574 * Error handler for the current test browser instance.575 *576 * @param mixed $errno Error number577 * @param string $errstr Error message578 * @param string $errfile Error file579 * @param mixed $errline Error line580 */581 function sfTestBrowserErrorHandler($errno, $errstr, $errfile, $errline)582 {583 if (($errno & error_reporting()) == 0)584 {585 return;586 }587 588 $msg = sprintf('PHP send a "%%s" error at %s line %s (%s)', $errfile, $errline, $errstr);589 switch ($errno)590 {591 case E_WARNING:592 throw new Exception(sprintf($msg, 'warning'));593 break;594 case E_NOTICE:595 throw new Exception(sprintf($msg, 'notice'));596 break;597 case E_STRICT:598 throw new Exception(sprintf($msg, 'strict'));599 break;600 case E_RECOVERABLE_ERROR:601 throw new Exception(sprintf($msg, 'catchable'));602 break;603 }604 }605 606 set_error_handler('sfTestBrowserErrorHandler');branches/1.2/lib/util/sfBrowser.class.php
r11020 r11116 3 3 /* 4 4 * This file is part of the symfony package. 5 * (c) 2004-2006Fabien Potencier <fabien.potencier@symfony-project.com>5 * (c) Fabien Potencier <fabien.potencier@symfony-project.com> 6 6 * 7 7 * For the full copyright and license information, please view the LICENSE … … 10 10 11 11 /** 12 * sfBrowser simulates a fakebrowser which can surf a symfony application.12 * sfBrowser simulates a browser which can surf a symfony application. 13 13 * 14 14 * @package symfony … … 17 17 * @version SVN: $Id$ 18 18 */ 19 class sfBrowser 19 class sfBrowser extends sfBrowserBase 20 20 { 21 21 protected 22 $context = null, 23 $hostname = null, 24 $remote = null, 25 $dom = null, 26 $stack = array(), 27 $stackPosition = -1, 28 $cookieJar = array(), 29 $fields = array(), 30 $files = array(), 31 $vars = array(), 32 $defaultServerArray = array(), 33 $headers = array(), 34 $currentException = null; 35 36 /** 37 * Class constructor. 38 * 39 * @param string $hostname Hostname to browse 40 * @param string $remote Remote address to spook 41 * @param array $options Options for sfBrowser 42 * 43 * @return void 44 */ 45 public function __construct($hostname = null, $remote = null, $options = array()) 46 { 47 $this->initialize($hostname, $remote, $options); 48 } 49 50 /** 51 * Initializes sfBrowser - sets up environment 52 * 53 * @param string $hostname Hostname to browse 54 * @param string $remote Remote address to spook 55 * @param array $options Options for sfBrowser 56 * 57 * @return void 58 */ 59 public function initialize($hostname = null, $remote = null, $options = array()) 60 { 61 unset($_SERVER['argv']); 62 unset($_SERVER['argc']); 63 64 // setup our fake environment 65 $this->hostname = $hostname; 66 $this->remote = $remote; 67 68 sfConfig::set('sf_test', true); 69 70 // we set a session id (fake cookie / persistence) 71 $this->newSession(); 72 73 // store default global $_SERVER array 74 $this->defaultServerArray = $_SERVER; 75 76 // register our shutdown function 77 register_shutdown_function(array($this, 'shutdown')); 78 } 79 80 /** 81 * Sets variable name 82 * 83 * @param string $name The variable name 84 * @param mixed $value The value 85 * 86 * @return sfBrowser 87 */ 88 public function setVar($name, $value) 89 { 90 $this->vars[$name] = $value; 91 92 return $this; 93 } 94 95 /** 96 * Sets a HTTP header for the very next request. 97 * 98 * @param string $header The header name 99 * @param string $value The header value 100 */ 101 public function setHttpHeader($header, $value) 102 { 103 $this->headers[$header] = $value; 104 105 return $this; 106 } 107 108 /** 109 * Sets a cookie. 110 * 111 * @param string $name The cookie name 112 * @param string $name HTTP header name 113 * @param string $value Value for the cookie 114 * @param string $expire Cookie expiration period 115 * @param string $path Path 116 * @param string $domain Domain name 117 * @param bool $secure If secure 118 * @param bool $httpOnly If uses only HTTP 119 * 120 * @return sfBrowser This sfBrowser instance 121 */ 122 public function setCookie($name, $value, $expire = null, $path = '/', $domain = '', $secure = false, $httpOnly = false) 123 { 124 $this->cookieJar[$name] = array( 125 'name' => $name, 126 'value' => $value, 127 'expire' => $expire, 128 'path' => $path, 129 'domain' => $domain, 130 'secure' => (Boolean) $secure, 131 'httpOnly' => $httpOnly, 132 ); 133 134 return $this; 135 } 136 137 /** 138 * Removes a cookie by name. 139 * 140 * @param string $name The cookie name 141 * 142 * @return sfBrowser This sfBrowser instance 143 */ 144 public function removeCookie($name) 145 { 146 unset($this->cookieJar[$name]); 147 148 return $this; 149 } 150 151 /** 152 * Clears all cookies. 153 * 154 * @return sfBrowser This sfBrowser instance 155 */ 156 public function clearCookies() 157 { 158 $this->cookieJar = array(); 159 160 return $this; 161 } 162 163 /** 164 * Sets username and password for simulating http authentication. 165 * 166 * @param string $username The username 167 * @param string $password The password 168 * 169 * @return sfBrowser 170 */ 171 public function setAuth($username, $password) 172 { 173 $this->vars['PHP_AUTH_USER'] = $username; 174 $this->vars['PHP_AUTH_PW'] = $password; 175 176 return $this; 177 } 178 179 /** 180 * Gets a uri. 181 * 182 * @param string $uri The URI to fetch 183 * @param array $parameters The Request parameters 184 * 185 * @return sfBrowser 186 */ 187 public function get($uri, $parameters = array()) 188 { 189 return $this->call($uri, 'get', $parameters); 190 } 191 192 /** 193 * Posts a uri. 194 * 195 * @param string $uri The URI to fetch 196 * @param array $parameters The Request parameters 197 * 198 * @return sfBrowser 199 */ 200 public function post($uri, $parameters = array()) 201 { 202 return $this->call($uri, 'post', $parameters); 203 } 22 $context = null; 204 23 205 24 /** 206 25 * Calls a request to a uri. 207 *208 * @param string $uri The URI to fetch209 * @param string $method The request method210 * @param array $parameters The Request parameters211 * @param bool $changeStack Change the browser history stack?212 *213 * @return sfBrowser214 26 */ 215 p ublic function call($uri, $method = 'get', $parameters = array(), $changeStack = true)27 protected function doCall() 216 28 { 217 // check that the previous call() hasn't returned an uncatched exception218 $this->checkCurrentExceptionIsEmpty();219 220 $uri = $this->fixUri($uri);221 222 // add uri to the stack223 if ($changeStack)224 {225 $this->stack = array_slice($this->stack, 0, $this->stackPosition + 1);226 $this->stack[] = array(227 'uri' => $uri,228 'method' => $method,229 'parameters' => $parameters,230 );231 $this->stackPosition = count($this->stack) - 1;232 }233 234 list($path, $query_string) = false !== ($pos = strpos($uri, '?')) ? array(substr($uri, 0, $pos), substr($uri, $pos + 1)) : array($uri, '');235 $query_string = html_entity_decode($query_string);236 237 // remove anchor238 $path = preg_replace('/#.*/', '', $path);239 240 // removes all fields from previous request241 $this->fields = array();242 243 // prepare the request object244 $_SERVER = $this->defaultServerArray;245 $_SERVER['HTTP_HOST'] = $this->hostname ? $this->hostname : sfConfig::get('sf_app').'-'.sfConfig::get('sf_environment');246 $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];247 $_SERVER['SERVER_PORT'] = 80;248 $_SERVER['HTTP_USER_AGENT'] = 'PHP5/CLI';249 $_SERVER['REMOTE_ADDR'] = $this->remote ? $this->remote : '127.0.0.1';250 $_SERVER['REQUEST_METHOD'] = strtoupper($method);251 $_SERVER['PATH_INFO'] = $path;252 $_SERVER['REQUEST_URI'] = '/index.php'.$uri;253 $_SERVER['SCRIPT_NAME'] = '/index.php';254 $_SERVER['SCRIPT_FILENAME'] = '/index.php';255 $_SERVER['QUERY_STRING'] = $query_string;256 foreach ($this->vars as $key => $value)257 {258 $_SERVER[strtoupper($key)] = $value;259 }260 261 foreach ($this->headers as $header => $value)262 {263 $_SERVER['HTTP_'.strtoupper(str_replace('-', '_', $header))] = $value;264 }265 $this->headers = array();266 267 // request parameters268 $_GET = $_POST = array();269 if (strtoupper($method) == 'POST')270 {271 $_POST = $parameters;272 }273 if (strtoupper($method) == 'GET')274 {275 $_GET = $parameters;276 }277 278 // handle input type="file" fields279 if (count($this->files))280 {281 $_FILES = $this->files;282 }283 $this->files = array();284 285 parse_str($query_string, $qs);286 if (is_array($qs))287 {288 $_GET = array_merge($qs, $_GET);289 }290 291 // expire cookies292 $cookies = $this->cookieJar;293 foreach ($cookies as $name => $cookie)294 {295 if ($cookie['expire'] && $cookie['expire'] < time())296 {297 unset($this->cookieJar[$name]);298 }299 }300 301 // restore cookies302 $_COOKIE = array();303 foreach ($this->cookieJar as $name => $cookie)304 {305 $_COOKIE[$name] = $cookie['value'];306 }307 308 ob_start();309 310 29 // recycle our context object 311 30 $this->context = $this->getContext(true); 312 31 313 // launch request via controller 314 $controller = $this->context->getController(); 315 $request = $this->context->getRequest(); 316 $response = $this->context->getResponse(); 32 sfConfig::set('sf_test', true); 317 33 318 34 // we register a fake rendering filter 319 35 sfConfig::set('sf_rendering_filter', array('sfFakeRenderingFilter', null)); 320 36 321 $this-> currentException = null;37 $this->resetCurrentException(); 322 38 323 39 // dispatch our request 324 $controller->dispatch();325 40 ob_start(); 41 $this->context->getController()->dispatch(); 326 42 $retval = ob_get_clean(); 327 43 328 44 // append retval to the response content 329 $ response->setContent($retval);45 $this->context->getResponse()->setContent($retval); 330 46 331 47 // manually shutdown user to save current session data 332 48 $this->context->getUser()->shutdown(); 333 49 $this->context->getStorage()->shutdown(); 334 335 // save cookies336 foreach ($response->getCookies() as $name => $cookie)337 {338 // FIXME: deal with path, secure, ...339 $this->cookieJar[$name] = $cookie;340 }341 342 // support for the ETag header343 if ($etag = $this->context->getResponse()->getHttpHeader('Etag'))344 {345 $this->vars['HTTP_IF_NONE_MATCH'] = $etag;346 }347 else348 {349 unset($this->vars['HTTP_IF_NONE_MATCH']);350 }351 352 // support for the last modified header353 if ($lastModified = $this->context->getResponse()->getHttpHeader('Last-Modified'))354 {355 $this->vars['HTTP_IF_MODIFIED_SINCE'] = $lastModified;356 }357 else358 {359 unset($this->vars['HTTP_IF_MODIFIED_SINCE']);360 }361 362 // for HTML/XML content, create a DOM and sfDomCssSelector objects for the response content363 if (preg_match('/(x|ht)ml/i', $response->getContentType(), $matches))364 {365 $this->dom = new DomDocument('1.0', sfConfig::get('sf_charset'));366 $this->dom->validateOnParse = true;367 if ('x' == $matches[1])368 {369 @$this->dom->loadXML($response->getContent());370 }371 else372 {373 @$this->dom->loadHTML($response->getContent());374 }375 $this->domCssSelector = new sfDomCssSelector($this->dom);376 }377 else378 {379 $this->dom = null;380 $this->domCssSelector = null;381 }382 383 return $this;384 }385 386 /**387 * Go back in the browser history stack.388 *389 * @return sfBrowser390 */391 public function back()392 {393 if ($this->stackPosition < 1)394 {395 throw new sfException('You are already on the first page.');396 }397 398 --$this->stackPosition;399 return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);400 }401 402 /**403 * Go forward in the browser history stack.404 *405 * @return sfBrowser406 */407 public function forward()408 {409 if ($this->stackPosition > count($this->stack) - 2)410 {411 throw new sfException('You are already on the last page.');412 }413 414 ++$this->stackPosition;415 return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);416 }417 418 /**419 * Reload the current browser.420 *421 * @return sfBrowser422 */423 public function reload()424 {425 if (-1 == $this->stackPosition)426 {427 throw new sfException('No page to reload.');428 }429 430 return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);431 }432 433 /**434 * Get response dom css selector.435 *436 * @return sfDomCssSelector437 */438 public function getResponseDomCssSelector()439 {440 if (is_null($this->dom))441 {442 throw new sfException('The DOM is not accessible because the browser response content type is not HTML.');443 }444 445 return $this->domCssSelector;446 }447 448 /**449 * Get response dom.450 *451 * @return sfDomCssSelector452 */453 public function getResponseDom()454 {455 if (is_null($this->dom))456 {457 throw new sfException('The DOM is not accessible because the browser response content type is not HTML.');458 }459 460 return $this->dom;461 50 } 462 51 … … 512 101 513 102 /** 514 * Gets current exception.103 * Gets user. 515 104 * 516 * @return sf Exception105 * @return sfUser 517 106 */ 518 public function get CurrentException()107 public function getUser() 519 108 { 520 return $this->currentException; 521 } 522 523 /** 524 * Resets the current exception. 525 */ 526 public function resetCurrentException() 527 { 528 $this->currentException = null; 529 } 530 531 /** 532 * Test for an uncaught exception. 533 * 534 * @return boolean 535 */ 536 public function checkCurrentExceptionIsEmpty() 537 { 538 return is_null($this->getCurrentException()) || $this->getCurrentException() instanceof sfError404Exception; 539 } 540 541 /** 542 * Follow redirects? 543 * 544 * @throws sfException If request was not a redirect 545 * 546 * @return sfBrowser 547 */ 548 public function followRedirect() 549 { 550 if (null === $this->context->getResponse()->getHttpHeader('Location')) 551 { 552 throw new sfException('The request was not redirected.'); 553 } 554 555 return $this->get($this->context->getResponse()->getHttpHeader('Location')); 556 } 557 558 /** 559 * Sets a form field in the browser. 560 * 561 * @param string $name The field name 562 * @param string $value The field value 563 * 564 * @return sfBrowser 565 */ 566 public function setField($name, $value) 567 { 568 // as we don't know yet the form, just store name/value pairs 569 $this->parseArgumentAsArray($name, $value, $this->fields); 570 571 return $this; 572 } 573 574 /** 575 * Simulates a click on a link or button. 576 * 577 * @param string $name The link or button text 578 * @param array $arguments The arguments to pass to the link 579 * @param integer $position The position of the linked to link if several ones have the same name 580 * 581 * @return sfBrowser 582 */ 583 public function click($name, $arguments = array(), $position = 0) 584 { 585 $dom = $this->getResponseDom(); 586 587 if (!$dom) 588 { 589 throw new sfException('Cannot click because there is no current page in the browser.'); 590 } 591 592 $xpath = new DomXpath($dom); 593 594 // text link 595 if ($link = $xpath->query(sprintf('//a[.="%s"]', $name))->item($position)) 596 { 597 return $this->get($link->getAttribute('href')); 598 } 599 600 // image link 601 if ($link = $xpath->query(sprintf('//a/img[@alt="%s"]/ancestor::a', $name))->item($position)) 602 { 603 return $this->get($link->getAttribute('href')); 604 } 605 606 // form 607 if (!$form = $xpath->query(sprintf('//input[((@type="submit" or @type="button") and @value="%s") or (@type="image" and @alt="%s")]/ancestor::form', $name, $name))->item($position)) 608 { 609 if (!$form = $xpath->query(sprintf('//button[.="%s" or @id="%s" or @name="%s"]/ancestor::form', $name, $name, $name))->item($position)) 610 { 611 throw new sfException(sprintf('Cannot find the "%s" link or button.', $name)); 612 } 613 } 614 615 // form attributes 616 $url = $form->getAttribute('action'); 617 $method = $form->getAttribute('method') ? strtolower($form->getAttribute('method')) : 'get'; 618 619 // merge form default values and arguments 620 $defaults = array(); 621 $arguments = sfToolkit::arrayDeepMerge($this->fields, $arguments); 622 623 foreach ($xpath->query('descendant::input | descendant::textarea | descendant::select', $form) as $element) 624 { 625 $elementName = $element->getAttribute('name'); 626 $nodeName = $element->nodeName; 627 $value = null; 628 629 if ($nodeName == 'input' && ($element->getAttribute('type') == 'checkbox' || $element->getAttribute('type') == 'radio')) 630 { 631 if ($element->getAttribute('checked')) 632 { 633 $value = $element->hasAttribute('value') ? $element->getAttribute('value') : '1'; 634 } 635 } 636 else if ($nodeName == 'input' && $element->getAttribute('type') == 'file') 637 { 638 $ph = new sfParameterHolder(); 639 $ph->add($arguments); 640 641 $filename = $ph->get($elementName, ''); 642 643 if (is_readable($filename)) 644 { 645 $fileError = UPLOAD_ERR_OK; 646 $fileSize = filesize($filename); 647 } 648 else 649 { 650 $fileError = UPLOAD_ERR_NO_FILE; 651 $fileSize = 0; 652 } 653 654 $ph->remove($elementName); 655 $arguments = $ph->getAll(); 656 657 $this->parseArgumentAsArray($elementName, array('name' => basename($filename), 'type' => '', 'tmp_name' => $filename, 'error' => $fileError, 'size' => $fileSize), $this->files); 658 } 659 else if ( 660 $nodeName == 'input' 661 && 662 (($element->getAttribute('type') != 'submit' && $element->getAttribute('type') != 'button') || $element->getAttribute('value') == $name) 663 && 664 ($element->getAttribute('type') != 'image' || $element->getAttribute('alt') == $name) 665 ) 666 { 667 $value = $element->getAttribute('value'); 668 } 669 else if ($nodeName == 'textarea') 670 { 671 $value = ''; 672 foreach ($element->childNodes as $el) 673 { 674 $value .= $dom->saveXML($el); 675 } 676 } 677 else if ($nodeName == 'select') 678 { 679 if ($multiple = $element->hasAttribute('multiple')) 680 { 681 $elementName = str_replace('[]', '', $elementName); 682 $value = array(); 683 } 684 else 685 { 686 $value = null; 687 } 688 689 $found = false; 690 foreach ($xpath->query('descendant::option', $element) as $option) 691 { 692 if ($option->getAttribute('selected')) 693 { 694 $found = true; 695 if ($multiple) 696 { 697 $value[] = $option->getAttribute('value'); 698 } 699 else 700 { 701 $value = $option->getAttribute('value'); 702 } 703 } 704 } 705 706 // if no option is selected and if it is a simple select box, take the first option as the value 707 $option = $xpath->query('descendant::option', $element)->item(0); 708 if (!$found && !$multiple && $option instanceof DOMElement) 709 { 710 $value = $option->getAttribute('value'); 711 } 712 } 713 714 if (null !== $value) 715 { 716 $this->parseArgumentAsArray($elementName, $value, $defaults); 717 } 718 } 719 720 // create request parameters 721 $arguments = sfToolkit::arrayDeepMerge($defaults, $arguments); 722 if ('post' == $method) 723 { 724 return $this->post($url, $arguments); 725 } 726 else 727 { 728 $query_string = http_build_query($arguments, null, '&'); 729 $sep = false === strpos($url, '?') ? '?' : '&'; 730 731 return $this->get($url.($query_string ? $sep.$query_string : '')); 732 } 733 } 734 735 /** 736 * Parses arguments as array 737 * 738 * @param string $name The argument name 739 * @param string $value The argument value 740 * @param array $vars 741 */ 742 protected function parseArgumentAsArray($name, $value, &$vars) 743 { 744 if (false !== $pos = strpos($name, '[')) 745 { 746 $var = &$vars; 747 $tmps = array_filter(preg_split('/(\[ | \[\] | \])/x', $name), create_function('$s', 'return $s !== "";')); 748 foreach ($tmps as $tmp) 749 { 750 $var = &$var[$tmp]; 751 } 752 if ($var) 753 { 754 if (!is_array($var)) 755 { 756 $var = array($var); 757 } 758 $var[] = $value; 759 } 760 else 761 { 762 $var = $value; 763 } 764 } 765 else 766 { 767 $vars[$name] = $value; 768 } 769 } 770 771 /** 772 * Reset browser to original state 773 * 774 * @return sfBrowser 775 */ 776 public function restart() 777 { 778 $this->newSession(); 779 $this->cookieJar = array(); 780 $this->stack = array(); 781 $this->fields = array(); 782 $this->vars = array(); 783 $this->dom = null; 784 $this->stackPosition = -1; 785 786 return $this; 109 return $this->context->getUser(); 787 110 } 788 111 … … 794 117 public function shutdown() 795 118 { 796 $this->checkCurrentExceptionIsEmpty();119 parent::shutdown(); 797 120 798 121 // we remove all session data 799 122 sfToolkit::clearDirectory(sfConfig::get('sf_test_cache_dir').'/sessions'); 800 }801 802 /**803 * Fixes uri removing # declarations and front controller.804 *805 * @param string $uri The URI to fix806 * @return string The fixed uri807 */808 protected function fixUri($uri)809 {810 // remove absolute information if needed (to be able to do follow redirects, click on links, ...)811 if (0 === strpos($uri, 'http'))812 {813 // detect secure request814 if (0 === strpos($uri, 'https'))815 {816 $this->defaultServerArray['HTTPS'] = 'on';817 }818 else819 {820 unset($this->defaultServerArray['HTTPS']);821 }822 823 $uri = substr($uri, strpos($uri, 'index.php') + strlen('index.php'));824 }825 $uri = str_replace('/index.php', '', $uri);826 827 // # as a uri828 if ($uri && '#' == $uri[0])829 {830 $uri = $this->stack[$this->stackPosition]['uri'].$uri;831 }832 833 return $uri;834 }835 836 /**837 * Creates a new session in the browser.838 *839 * @return void840 */841 protected function newSession()842 {843 $this->defaultServerArray['session_id'] = $_SERVER['session_id'] = md5(uniqid(rand(), true));844 123 } 845 124 … … 853 132 public function listenToException(sfEvent $event) 854 133 { 855 $this-> currentException = $event->getSubject();134 $this->setCurrentException($event->getSubject()); 856 135 } 857 136 } branches/1.2/test/functional/sfTestBrowserTest.php
r10830 r11116 76 76 $b->test()->fail('The DOM is not accessible if the response content type is not HTML'); 77 77 } 78 catch ( sfException $e)78 catch (LogicException $e) 79 79 { 80 80 $b->test()->pass('The DOM is not accessible if the response content type is not HTML');

