Development

/branches/1.3/lib/test/sfTestFunctionalBase.class.php

You must first sign up to be able to contribute.

root/branches/1.3/lib/test/sfTestFunctionalBase.class.php

Revision 22945, 15.8 kB (checked in by Kris.Wallsmith, 4 years ago)

[1.3] fixed using CSS selector in sfTestFunctional::click()

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 require_once(dirname(__FILE__).'/../vendor/lime/lime.php');
4
5 /*
6  * This file is part of the symfony package.
7  * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
8  *
9  * For the full copyright and license information, please view the LICENSE
10  * file that was distributed with this source code.
11  */
12
13 /**
14  * sfTestFunctional tests an application by using a browser simulator.
15  *
16  * @package    symfony
17  * @subpackage test
18  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
19  * @version    SVN: $Id$
20  */
21 abstract class sfTestFunctionalBase
22 {
23   protected
24     $testers       = array(),
25     $blockTester   = null,
26     $currentTester = null,
27     $browser       = null;
28
29   protected static
30     $test = null;
31
32   /**
33    * Initializes the browser tester instance.
34    *
35    * @param sfBrowserBase $browser A sfBrowserBase instance
36    * @param lime_test     $lime    A lime instance
37    */
38   public function __construct(sfBrowserBase $browser, lime_test $lime = null, $testers = array())
39   {
40     $this->browser = $browser;
41
42     if (null === self::$test)
43     {
44       self::$test = null !== $lime ? $lime : new lime_test();
45     }
46
47     $this->setTesters(array_merge(array(
48       'request'  => 'sfTesterRequest',
49       'response' => 'sfTesterResponse',
50       'user'     => 'sfTesterUser',
51       'mailer'   => 'sfTesterMailer',
52     ), $testers));
53
54     // register our shutdown function
55     register_shutdown_function(array($this, 'shutdown'));
56
57     // register our error/exception handlers
58     set_error_handler(array($this, 'handlePhpError'));
59     set_exception_handler(array($this, 'handleException'));
60   }
61
62   /**
63    * Returns the tester associated with the given name.
64    *
65    * @param string   $name The tester name
66    *
67    * @param sfTester A sfTester instance
68    */
69   public function with($name)
70   {
71     if (!isset($this->testers[$name]))
72     {
73       throw new InvalidArgumentException(sprintf('The "%s" tester does not exist.', $name));
74     }
75
76     if ($this->blockTester)
77     {
78       throw new LogicException(sprintf('You cannot nest tester blocks.'));
79     }
80
81     $this->currentTester = $this->testers[$name];
82     $this->currentTester->initialize();
83
84     return $this->currentTester;
85   }
86
87   /**
88    * Begins a block of test for the current tester.
89    *
90    * @return sfTester The current sfTester instance
91    */
92   public function begin()
93   {
94     if (!$this->currentTester)
95     {
96       throw new LogicException(sprintf('You must call with() before beginning a tester block.'));
97     }
98
99     return $this->blockTester = $this->currentTester;
100   }
101
102   /**
103    * End a block of test for the current tester.
104    *
105    * @return sfTestFunctionalBase
106    */
107   public function end()
108   {
109     if (null === $this->blockTester)
110     {
111       throw new LogicException(sprintf('There is not current tester block to end.'));
112     }
113
114     $this->blockTester = null;
115
116     return $this;
117   }
118
119   /**
120    * Sets the testers.
121    *
122    * @param array $testers An array of named testers
123    */
124   public function setTesters($testers)
125   {
126     foreach ($testers as $name => $tester)
127     {
128       $this->setTester($name, $tester);
129     }
130   }
131
132   /**
133    * Sets a tester.
134    *
135    * @param string          $name   The tester name
136    * @param sfTester|string $tester A sfTester instance or a tester class name
137    */
138   public function setTester($name, $tester)
139   {
140     if (is_string($tester))
141     {
142       $tester = new $tester($this, self::$test);
143     }
144
145     if (!$tester instanceof sfTester)
146     {
147       throw new InvalidArgumentException(sprintf('The tester "%s" is not of class sfTester.', $name));
148     }
149
150     $this->testers[$name] = $tester;
151   }
152
153   /**
154    * Shutdown function.
155    *
156    * @return void
157    */
158   public function shutdown()
159   {
160     $this->checkCurrentExceptionIsEmpty();
161   }
162
163   /**
164    * Retrieves the lime_test instance.
165    *
166    * @return lime_test The lime_test instance
167    */
168   public function test()
169   {
170     return self::$test;
171   }
172
173   /**
174    * Gets a uri.
175    *
176    * @param string $uri         The URI to fetch
177    * @param array  $parameters  The Request parameters
178    * @param bool   $changeStack  Change the browser history stack?
179    *
180    * @return sfTestFunctionalBase
181    */
182   public function get($uri, $parameters = array(), $changeStack = true)
183   {
184     return $this->call($uri, 'get', $parameters, $changeStack);
185   }
186
187   /**
188    * Retrieves and checks an action.
189    *
190    * @param  string $module  Module name
191    * @param  string $action  Action name
192    * @param  string $url     Url
193    * @param  string $code    The expected return status code
194    *
195    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
196    */
197   public function getAndCheck($module, $action, $url = null, $code = 200)
198   {
199     return $this->
200       get(null !== $url ? $url : sprintf('/%s/%s', $module, $action))->
201       with('request')->begin()->
202         isParameter('module', $module)->
203         isParameter('action', $action)->
204       end()->
205       with('response')->isStatusCode($code)
206     ;
207   }
208
209   /**
210    * Posts a uri.
211    *
212    * @param string $uri         The URI to fetch
213    * @param array  $parameters  The Request parameters
214    * @param bool   $changeStack  Change the browser history stack?
215    *
216    * @return sfTestFunctionalBase
217    */
218   public function post($uri, $parameters = array(), $changeStack = true)
219   {
220     return $this->call($uri, 'post', $parameters, $changeStack);
221   }
222
223   /**
224    * Calls a request.
225    *
226    * @param  string $uri          URI to be invoked
227    * @param  string $method       HTTP method used
228    * @param  array  $parameters   Additional paramaters
229    * @param  bool   $changeStack  If set to false ActionStack is not changed
230    *
231    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
232    */
233   public function call($uri, $method = 'get', $parameters = array(), $changeStack = true)
234   {
235     $this->checkCurrentExceptionIsEmpty();
236
237     $uri = $this->browser->fixUri($uri);
238
239     $this->test()->comment(sprintf('%s %s', strtolower($method), $uri));
240
241     foreach ($this->testers as $tester)
242     {
243       $tester->prepare();
244     }
245
246     $this->browser->call($uri, $method, $parameters, $changeStack);
247
248     return $this;
249   }
250
251   /**
252    * Simulates deselecting a checkbox or radiobutton.
253    *
254    * @param string  $name       The checkbox or radiobutton id, name or text
255    *
256    * @return sfTestFunctionalBase
257    */
258   public function deselect($name)
259   {
260     $this->browser->doSelect($name, false);
261
262     return $this;
263   }
264
265   /**
266    * Simulates selecting a checkbox or radiobutton.
267    *
268    * @param string  $name       The checkbox or radiobutton id, name or text
269    *
270    * @return sfTestFunctionalBase
271    */
272   public function select($name)
273   {
274     $this->browser->doSelect($name, true);
275
276     return $this;
277   }
278
279   /**
280    * Simulates a click on a link or button.
281    *
282    * @param string  $name       The link or button text
283    * @param array   $arguments  The arguments to pass to the link
284    * @param array   $options    An array of options
285    *
286    * @return sfTestFunctionalBase
287    */
288   public function click($name, $arguments = array(), $options = array())
289   {
290     if ($name instanceof DOMElement)
291     {
292       list($uri, $method, $parameters) = $this->doClickElement($name, $arguments, $options);
293     }
294     else
295     {
296       try
297       {
298         list($uri, $method, $parameters) = $this->doClick($name, $arguments, $options);
299       }
300       catch (InvalidArgumentException $e)
301       {
302         list($uri, $method, $parameters) = $this->doClickCssSelector($name, $arguments, $options);
303       }
304     }
305
306     return $this->call($uri, $method, $parameters);
307   }
308
309   /**
310    * Simulates the browser back button.
311    *
312    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
313    */
314   public function back()
315   {
316     $this->test()->comment('back');
317
318     $this->browser->back();
319
320     return $this;
321   }
322
323   /**
324    * Simulates the browser forward button.
325    *
326    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
327    */
328   public function forward()
329   {
330     $this->test()->comment('forward');
331
332     $this->browser->forward();
333
334     return $this;
335   }
336
337   /**
338    * Outputs an information message.
339    *
340    * @param string $message A message
341    *
342    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
343    */
344   public function info($message)
345   {
346     $this->test()->info($message);
347
348     return $this;
349   }
350
351   /**
352    * Tests if the current request has been redirected.
353    *
354    * @deprecated since 1.2
355    *
356    * @param  bool $boolean  Flag for redirection mode
357    *
358    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
359    */
360   public function isRedirected($boolean = true)
361   {
362     return $this->with('response')->isRedirected($boolean);
363   }
364
365   /**
366    * Checks that the current response contains a given text.
367    *
368    * @param  string $uri   Uniform resource identifier
369    * @param  string $text  Text in the response
370    *
371    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
372    */
373   public function check($uri, $text = null)
374   {
375     $this->get($uri)->with('response')->isStatusCode();
376
377     if ($text !== null)
378     {
379       $this->with('response')->contains($text);
380     }
381
382     return $this;
383   }
384
385   /**
386    * Test an status code for the current test browser.
387    *
388    * @deprecated since 1.2
389    *
390    * @param string Status code to check, default 200
391    *
392    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
393    */
394   public function isStatusCode($statusCode = 200)
395   {
396     return $this->with('response')->isStatusCode($statusCode);
397   }
398
399   /**
400    * Tests whether or not a given string is in the response.
401    *
402    * @deprecated since 1.2
403    *
404    * @param string Text to check
405    *
406    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
407    */
408   public function responseContains($text)
409   {
410     return $this->with('response')->contains($text);
411   }
412
413   /**
414    * Tests whether or not a given key and value exists in the current request.
415    *
416    * @deprecated since 1.2
417    *
418    * @param string $key
419    * @param string $value
420    *
421    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
422    */
423   public function isRequestParameter($key, $value)
424   {
425     return $this->with('request')->isParameter($key, $value);
426   }
427
428   /**
429    * Tests for a response header.
430    *
431    * @deprecated since 1.2
432    *
433    * @param  string $key
434    * @param  string $value
435    *
436    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
437    */
438   public function isResponseHeader($key, $value)
439   {
440     return $this->with('response')->isHeader($key, $value);
441   }
442
443   /**
444    * Tests for the user culture.
445    *
446    * @deprecated since 1.2
447    *
448    * @param  string $culture  The user culture
449    *
450    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
451    */
452   public function isUserCulture($culture)
453   {
454     return $this->with('user')->isCulture($culture);
455   }
456
457   /**
458    * Tests for the request is in the given format.
459    *
460    * @deprecated since 1.2
461    *
462    * @param  string $format  The request format
463    *
464    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
465    */
466   public function isRequestFormat($format)
467   {
468     return $this->with('request')->isFormat($format);
469   }
470
471   /**
472    * Tests that the current response matches a given CSS selector.
473    *
474    * @deprecated since 1.2
475    *
476    * @param  string $selector  The response selector or a sfDomCssSelector object
477    * @param  mixed  $value     Flag for the selector
478    * @param  array  $options   Options for the current test
479    *
480    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
481    */
482   public function checkResponseElement($selector, $value = true, $options = array())
483   {
484     return $this->with('response')->checkElement($selector, $value, $options);
485   }
486
487   /**
488    * Tests if an exception is thrown by the latest request.
489    *
490    * @param  string $class    Class name
491    * @param  string $message  Message name
492    *
493    * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
494    */
495   public function throwsException($class = null, $message = null)
496   {
497     $e = $this->browser->getCurrentException();
498
499     if (null === $e)
500     {
501       $this->test()->fail('response returns an exception');
502     }
503     else
504     {
505       if (null !== $class)
506       {
507         $this->test()->ok($e instanceof $class, sprintf('response returns an exception of class "%s"', $class));
508       }
509
510       if (null !== $message && preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $message, $match))
511       {
512         if ($match[1] == '!')
513         {
514           $this->test()->unlike($e->getMessage(), substr($message, 1), sprintf('response exception message does not match regex "%s"', $message));
515         }
516         else
517         {
518           $this->test()->like($e->getMessage(), $message, sprintf('response exception message matches regex "%s"', $message));
519         }
520       }
521       else if (null !== $message)
522       {
523         $this->test()->is($e->getMessage(), $message, sprintf('response exception message is "%s"', $message));
524       }
525     }
526
527     $this->resetCurrentException();
528
529     return $this;
530   }
531
532   /**
533    * Triggers a test failure if an uncaught exception is present.
534    *
535    * @return  bool
536    */
537   public function checkCurrentExceptionIsEmpty()
538   {
539     if (false === ($empty = $this->browser->checkCurrentExceptionIsEmpty()))
540     {
541       $this->test()->fail(sprintf('last request threw an uncaught exception "%s: %s"', get_class($this->browser->getCurrentException()), $this->browser->getCurrentException()->getMessage()));
542     }
543
544     return $empty;
545   }
546
547   public function __call($method, $arguments)
548   {
549     $retval = call_user_func_array(array($this->browser, $method), $arguments);
550
551     // fix the fluent interface
552     return $retval === $this->browser ? $this : $retval;
553   }
554
555   /**
556    * Error handler for the current test browser instance.
557    *
558    * @param mixed  $errno    Error number
559    * @param string $errstr   Error message
560    * @param string $errfile  Error file
561    * @param mixed  $errline  Error line
562    */
563   static public function handlePhpError($errno, $errstr, $errfile, $errline)
564   {
565     if (($errno & error_reporting()) == 0)
566     {
567       return false;
568     }
569
570     $msg = sprintf('PHP sent a "%%s" error at %s line %s (%s)', $errfile, $errline, $errstr);
571     switch ($errno)
572     {
573       case E_WARNING:
574         $msg = sprintf($msg, 'warning');
575         throw new RuntimeException($msg);
576         break;
577       case E_NOTICE:
578         $msg = sprintf($msg, 'notice');
579         throw new RuntimeException($msg);
580         break;
581       case E_STRICT:
582         $msg = sprintf($msg, 'strict');
583         throw new RuntimeException($msg);
584         break;
585       case E_RECOVERABLE_ERROR:
586         $msg = sprintf($msg, 'catchable');
587         throw new RuntimeException($msg);
588         break;
589     }
590
591     return false;
592   }
593
594   /**
595    * Exception handler for the current test browser instance.
596    *
597    * @param Exception $exception The exception
598    */
599   function handleException(Exception $exception)
600   {
601     $this->test()->error(sprintf('%s: %s', get_class($exception), $exception->getMessage()));
602
603     $traceData = $exception->getTrace();
604     array_unshift($traceData, array(
605       'function' => '',
606       'file'     => $exception->getFile() != null ? $exception->getFile() : 'n/a',
607       'line'     => $exception->getLine() != null ? $exception->getLine() : 'n/a',
608       'args'     => array(),
609     ));
610
611     $traces = array();
612     $lineFormat = '  at %s%s%s() in %s line %s';
613     for ($i = 0, $count = count($traceData); $i < $count; $i++)
614     {
615       $line = isset($traceData[$i]['line']) ? $traceData[$i]['line'] : 'n/a';
616       $file = isset($traceData[$i]['file']) ? $traceData[$i]['file'] : 'n/a';
617       $args = isset($traceData[$i]['args']) ? $traceData[$i]['args'] : array();
618       $this->test()->error(sprintf($lineFormat,
619         (isset($traceData[$i]['class']) ? $traceData[$i]['class'] : ''),
620         (isset($traceData[$i]['type']) ? $traceData[$i]['type'] : ''),
621         $traceData[$i]['function'],
622         $file,
623         $line
624       ));
625     }
626
627     $this->test()->fail('An uncaught exception has been thrown.');
628   }
629 }
630
Note: See TracBrowser for help on using the browser.