1<?php
2/**
3 *  Base include file for SimpleTest.
4 *  @package    SimpleTest
5 *  @subpackage WebTester
6 *  @version    $Id$
7 */
8
9/**#@+
10 *  include other SimpleTest class files
11 */
12require_once(dirname(__FILE__) . '/test_case.php');
13require_once(dirname(__FILE__) . '/browser.php');
14require_once(dirname(__FILE__) . '/page.php');
15require_once(dirname(__FILE__) . '/expectation.php');
16/**#@-*/
17
18/**
19 *    Test for an HTML widget value match.
20 *    @package SimpleTest
21 *    @subpackage WebTester
22 */
23class FieldExpectation extends SimpleExpectation {
24    private $value;
25
26    /**
27     *    Sets the field value to compare against.
28     *    @param mixed $value     Test value to match. Can be an
29     *                            expectation for say pattern matching.
30     *    @param string $message  Optiona message override. Can use %s as
31     *                            a placeholder for the original message.
32     *    @access public
33     */
34    function __construct($value, $message = '%s') {
35        parent::__construct($message);
36        if (is_array($value)) {
37            sort($value);
38        }
39        $this->value = $value;
40    }
41
42    /**
43     *    Tests the expectation. True if it matches
44     *    a string value or an array value in any order.
45     *    @param mixed $compare        Comparison value. False for
46     *                                 an unset field.
47     *    @return boolean              True if correct.
48     *    @access public
49     */
50    function test($compare) {
51        if ($this->value === false) {
52            return ($compare === false);
53        }
54        if ($this->isSingle($this->value)) {
55            return $this->testSingle($compare);
56        }
57        if (is_array($this->value)) {
58            return $this->testMultiple($compare);
59        }
60        return false;
61    }
62
63    /**
64     *    Tests for valid field comparisons with a single option.
65     *    @param mixed $value       Value to type check.
66     *    @return boolean           True if integer, string or float.
67     *    @access private
68     */
69    protected function isSingle($value) {
70        return is_string($value) || is_integer($value) || is_float($value);
71    }
72
73    /**
74     *    String comparison for simple field with a single option.
75     *    @param mixed $compare    String to test against.
76     *    @returns boolean         True if matching.
77     *    @access private
78     */
79    protected function testSingle($compare) {
80        if (is_array($compare) && count($compare) == 1) {
81            $compare = $compare[0];
82        }
83        if (! $this->isSingle($compare)) {
84            return false;
85        }
86        return ($this->value == $compare);
87    }
88
89    /**
90     *    List comparison for multivalue field.
91     *    @param mixed $compare    List in any order to test against.
92     *    @returns boolean         True if matching.
93     *    @access private
94     */
95    protected function testMultiple($compare) {
96        if (is_string($compare)) {
97            $compare = array($compare);
98        }
99        if (! is_array($compare)) {
100            return false;
101        }
102        sort($compare);
103        return ($this->value === $compare);
104    }
105
106    /**
107     *    Returns a human readable test message.
108     *    @param mixed $compare      Comparison value.
109     *    @return string             Description of success
110     *                               or failure.
111     *    @access public
112     */
113    function testMessage($compare) {
114        $dumper = $this->getDumper();
115        if (is_array($compare)) {
116            sort($compare);
117        }
118        if ($this->test($compare)) {
119            return "Field expectation [" . $dumper->describeValue($this->value) . "]";
120        } else {
121            return "Field expectation [" . $dumper->describeValue($this->value) .
122                    "] fails with [" .
123                    $dumper->describeValue($compare) . "] " .
124                    $dumper->describeDifference($this->value, $compare);
125        }
126    }
127}
128
129/**
130 *    Test for a specific HTTP header within a header block.
131 *    @package SimpleTest
132 *    @subpackage WebTester
133 */
134class HttpHeaderExpectation extends SimpleExpectation {
135    private $expected_header;
136    private $expected_value;
137
138    /**
139     *    Sets the field and value to compare against.
140     *    @param string $header   Case insenstive trimmed header name.
141     *    @param mixed $value     Optional value to compare. If not
142     *                            given then any value will match. If
143     *                            an expectation object then that will
144     *                            be used instead.
145     *    @param string $message  Optiona message override. Can use %s as
146     *                            a placeholder for the original message.
147     */
148    function __construct($header, $value = false, $message = '%s') {
149        parent::__construct($message);
150        $this->expected_header = $this->normaliseHeader($header);
151        $this->expected_value = $value;
152    }
153
154    /**
155     *    Accessor for aggregated object.
156     *    @return mixed        Expectation set in constructor.
157     *    @access protected
158     */
159    protected function getExpectation() {
160        return $this->expected_value;
161    }
162
163    /**
164     *    Removes whitespace at ends and case variations.
165     *    @param string $header    Name of header.
166     *    @param string            Trimmed and lowecased header
167     *                             name.
168     *    @access private
169     */
170    protected function normaliseHeader($header) {
171        return strtolower(trim($header));
172    }
173
174    /**
175     *    Tests the expectation. True if it matches
176     *    a string value or an array value in any order.
177     *    @param mixed $compare   Raw header block to search.
178     *    @return boolean         True if header present.
179     *    @access public
180     */
181    function test($compare) {
182        return is_string($this->findHeader($compare));
183    }
184
185    /**
186     *    Searches the incoming result. Will extract the matching
187     *    line as text.
188     *    @param mixed $compare   Raw header block to search.
189     *    @return string          Matching header line.
190     *    @access protected
191     */
192    protected function findHeader($compare) {
193        $lines = explode("\r\n", $compare);
194        foreach ($lines as $line) {
195            if ($this->testHeaderLine($line)) {
196                return $line;
197            }
198        }
199        return false;
200    }
201
202    /**
203     *    Compares a single header line against the expectation.
204     *    @param string $line      A single line to compare.
205     *    @return boolean          True if matched.
206     *    @access private
207     */
208    protected function testHeaderLine($line) {
209        if (count($parsed = explode(':', $line, 2)) < 2) {
210            return false;
211        }
212        list($header, $value) = $parsed;
213        if ($this->normaliseHeader($header) != $this->expected_header) {
214            return false;
215        }
216        return $this->testHeaderValue($value, $this->expected_value);
217    }
218
219    /**
220     *    Tests the value part of the header.
221     *    @param string $value        Value to test.
222     *    @param mixed $expected      Value to test against.
223     *    @return boolean             True if matched.
224     *    @access protected
225     */
226    protected function testHeaderValue($value, $expected) {
227        if ($expected === false) {
228            return true;
229        }
230        if (SimpleExpectation::isExpectation($expected)) {
231            return $expected->test(trim($value));
232        }
233        return (trim($value) == trim($expected));
234    }
235
236    /**
237     *    Returns a human readable test message.
238     *    @param mixed $compare      Raw header block to search.
239     *    @return string             Description of success
240     *                               or failure.
241     *    @access public
242     */
243    function testMessage($compare) {
244        if (SimpleExpectation::isExpectation($this->expected_value)) {
245            $message = $this->expected_value->overlayMessage($compare, $this->getDumper());
246        } else {
247            $message = $this->expected_header .
248                    ($this->expected_value ? ': ' . $this->expected_value : '');
249        }
250        if (is_string($line = $this->findHeader($compare))) {
251            return "Searching for header [$message] found [$line]";
252        } else {
253            return "Failed to find header [$message]";
254        }
255    }
256}
257
258/**
259 *    Test for a specific HTTP header within a header block that
260 *    should not be found.
261 *    @package SimpleTest
262 *    @subpackage WebTester
263 */
264class NoHttpHeaderExpectation extends HttpHeaderExpectation {
265    private $expected_header;
266    private $expected_value;
267
268    /**
269     *    Sets the field and value to compare against.
270     *    @param string $unwanted   Case insenstive trimmed header name.
271     *    @param string $message    Optiona message override. Can use %s as
272     *                              a placeholder for the original message.
273     */
274    function __construct($unwanted, $message = '%s') {
275        parent::__construct($unwanted, false, $message);
276    }
277
278    /**
279     *    Tests that the unwanted header is not found.
280     *    @param mixed $compare   Raw header block to search.
281     *    @return boolean         True if header present.
282     *    @access public
283     */
284    function test($compare) {
285        return ($this->findHeader($compare) === false);
286    }
287
288    /**
289     *    Returns a human readable test message.
290     *    @param mixed $compare      Raw header block to search.
291     *    @return string             Description of success
292     *                               or failure.
293     *    @access public
294     */
295    function testMessage($compare) {
296        $expectation = $this->getExpectation();
297        if (is_string($line = $this->findHeader($compare))) {
298            return "Found unwanted header [$expectation] with [$line]";
299        } else {
300            return "Did not find unwanted header [$expectation]";
301        }
302    }
303}
304
305/**
306 *    Test for a text substring.
307 *    @package SimpleTest
308 *    @subpackage UnitTester
309 */
310class TextExpectation extends SimpleExpectation {
311    private $substring;
312
313    /**
314     *    Sets the value to compare against.
315     *    @param string $substring  Text to search for.
316     *    @param string $message    Customised message on failure.
317     *    @access public
318     */
319    function __construct($substring, $message = '%s') {
320        parent::__construct($message);
321        $this->substring = $substring;
322    }
323
324    /**
325     *    Accessor for the substring.
326     *    @return string       Text to match.
327     *    @access protected
328     */
329    protected function getSubstring() {
330        return $this->substring;
331    }
332
333    /**
334     *    Tests the expectation. True if the text contains the
335     *    substring.
336     *    @param string $compare        Comparison value.
337     *    @return boolean               True if correct.
338     *    @access public
339     */
340    function test($compare) {
341        return (strpos($compare, $this->substring) !== false);
342    }
343
344    /**
345     *    Returns a human readable test message.
346     *    @param mixed $compare      Comparison value.
347     *    @return string             Description of success
348     *                               or failure.
349     *    @access public
350     */
351    function testMessage($compare) {
352        if ($this->test($compare)) {
353            return $this->describeTextMatch($this->getSubstring(), $compare);
354        } else {
355            $dumper = $this->getDumper();
356            return "Text [" . $this->getSubstring() .
357                    "] not detected in [" .
358                    $dumper->describeValue($compare) . "]";
359        }
360    }
361
362    /**
363     *    Describes a pattern match including the string
364     *    found and it's position.
365     *    @param string $substring      Text to search for.
366     *    @param string $subject        Subject to search.
367     *    @access protected
368     */
369    protected function describeTextMatch($substring, $subject) {
370        $position = strpos($subject, $substring);
371        $dumper = $this->getDumper();
372        return "Text [$substring] detected at character [$position] in [" .
373                $dumper->describeValue($subject) . "] in region [" .
374                $dumper->clipString($subject, 100, $position) . "]";
375    }
376}
377
378/**
379 *    Fail if a substring is detected within the
380 *    comparison text.
381 *    @package SimpleTest
382 *    @subpackage UnitTester
383 */
384class NoTextExpectation extends TextExpectation {
385
386    /**
387     *    Sets the reject pattern
388     *    @param string $substring  Text to search for.
389     *    @param string $message    Customised message on failure.
390     *    @access public
391     */
392    function __construct($substring, $message = '%s') {
393        parent::__construct($substring, $message);
394    }
395
396    /**
397     *    Tests the expectation. False if the substring appears
398     *    in the text.
399     *    @param string $compare        Comparison value.
400     *    @return boolean               True if correct.
401     *    @access public
402     */
403    function test($compare) {
404        return ! parent::test($compare);
405    }
406
407    /**
408     *    Returns a human readable test message.
409     *    @param string $compare      Comparison value.
410     *    @return string              Description of success
411     *                                or failure.
412     *    @access public
413     */
414    function testMessage($compare) {
415        if ($this->test($compare)) {
416            $dumper = $this->getDumper();
417            return "Text [" . $this->getSubstring() .
418                    "] not detected in [" .
419                    $dumper->describeValue($compare) . "]";
420        } else {
421            return $this->describeTextMatch($this->getSubstring(), $compare);
422        }
423    }
424}
425
426/**
427 *    Test case for testing of web pages. Allows
428 *    fetching of pages, parsing of HTML and
429 *    submitting forms.
430 *    @package SimpleTest
431 *    @subpackage WebTester
432 */
433class WebTestCase extends SimpleTestCase {
434    private $browser;
435    private $ignore_errors = false;
436
437    /**
438     *    Creates an empty test case. Should be subclassed
439     *    with test methods for a functional test case.
440     *    @param string $label     Name of test case. Will use
441     *                             the class name if none specified.
442     *    @access public
443     */
444    function __construct($label = false) {
445        parent::__construct($label);
446    }
447
448    /**
449     *    Announces the start of the test.
450     *    @param string $method    Test method just started.
451     *    @access public
452     */
453    function before($method) {
454        parent::before($method);
455        $this->setBrowser($this->createBrowser());
456    }
457
458    /**
459     *    Announces the end of the test. Includes private clean up.
460     *    @param string $method    Test method just finished.
461     *    @access public
462     */
463    function after($method) {
464        $this->unsetBrowser();
465        parent::after($method);
466    }
467
468    /**
469     *    Gets a current browser reference for setting
470     *    special expectations or for detailed
471     *    examination of page fetches.
472     *    @return SimpleBrowser     Current test browser object.
473     *    @access public
474     */
475    function getBrowser() {
476        return $this->browser;
477    }
478
479    /**
480     *    Gets a current browser reference for setting
481     *    special expectations or for detailed
482     *    examination of page fetches.
483     *    @param SimpleBrowser $browser    New test browser object.
484     *    @access public
485     */
486    function setBrowser($browser) {
487        return $this->browser = $browser;
488    }
489
490    /**
491     *    Sets the HTML parser to use within this browser.
492     *    @param object         The parser, one of SimplePHPPageBuilder or
493     *                          SimpleTidyPageBuilder.
494     */
495    function setParser($parser) {
496        $this->browser->setParser($parser);
497    }
498
499    /**
500     *    Clears the current browser reference to help the
501     *    PHP garbage collector.
502     *    @access public
503     */
504    function unsetBrowser() {
505        unset($this->browser);
506    }
507
508    /**
509     *    Creates a new default web browser object.
510     *    Will be cleared at the end of the test method.
511     *    @return TestBrowser           New browser.
512     *    @access public
513     */
514    function createBrowser() {
515        return new SimpleBrowser();
516    }
517
518    /**
519     *    Gets the last response error.
520     *    @return string    Last low level HTTP error.
521     *    @access public
522     */
523    function getTransportError() {
524        return $this->browser->getTransportError();
525    }
526
527    /**
528     *    Accessor for the currently selected URL.
529     *    @return string        Current location or false if
530     *                          no page yet fetched.
531     *    @access public
532     */
533    function getUrl() {
534        return $this->browser->getUrl();
535    }
536
537    /**
538     *    Dumps the current request for debugging.
539     *    @access public
540     */
541    function showRequest() {
542        $this->dump($this->browser->getRequest());
543    }
544
545    /**
546     *    Dumps the current HTTP headers for debugging.
547     *    @access public
548     */
549    function showHeaders() {
550        $this->dump($this->browser->getHeaders());
551    }
552
553    /**
554     *    Dumps the current HTML source for debugging.
555     *    @access public
556     */
557    function showSource() {
558        $this->dump($this->browser->getContent());
559    }
560
561    /**
562     *    Dumps the visible text only for debugging.
563     *    @access public
564     */
565    function showText() {
566        $this->dump(wordwrap($this->browser->getContentAsText(), 80));
567    }
568
569    /**
570     *    Simulates the closing and reopening of the browser.
571     *    Temporary cookies will be discarded and timed
572     *    cookies will be expired if later than the
573     *    specified time.
574     *    @param string/integer $date Time when session restarted.
575     *                                If ommitted then all persistent
576     *                                cookies are kept. Time is either
577     *                                Cookie format string or timestamp.
578     *    @access public
579     */
580    function restart($date = false) {
581        if ($date === false) {
582            $date = time();
583        }
584        $this->browser->restart($date);
585    }
586
587    /**
588     *    Moves cookie expiry times back into the past.
589     *    Useful for testing timeouts and expiries.
590     *    @param integer $interval    Amount to age in seconds.
591     *    @access public
592     */
593    function ageCookies($interval) {
594        $this->browser->ageCookies($interval);
595    }
596
597    /**
598     *    Disables frames support. Frames will not be fetched
599     *    and the frameset page will be used instead.
600     *    @access public
601     */
602    function ignoreFrames() {
603        $this->browser->ignoreFrames();
604    }
605
606    /**
607     *    Switches off cookie sending and recieving.
608     *    @access public
609     */
610    function ignoreCookies() {
611        $this->browser->ignoreCookies();
612    }
613
614    /**
615     *    Skips errors for the next request only. You might
616     *    want to confirm that a page is unreachable for
617     *    example.
618     *    @access public
619     */
620    function ignoreErrors() {
621        $this->ignore_errors = true;
622    }
623
624    /**
625     *    Issues a fail if there is a transport error anywhere
626     *    in the current frameset. Only one such error is
627     *    reported.
628     *    @param string/boolean $result   HTML or failure.
629     *    @return string/boolean $result  Passes through result.
630     *    @access private
631     */
632    protected function failOnError($result) {
633        if (! $this->ignore_errors) {
634            if ($error = $this->browser->getTransportError()) {
635                $this->fail($error);
636            }
637        }
638        $this->ignore_errors = false;
639        return $result;
640    }
641
642    /**
643     *    Adds a header to every fetch.
644     *    @param string $header       Header line to add to every
645     *                                request until cleared.
646     *    @access public
647     */
648    function addHeader($header) {
649        $this->browser->addHeader($header);
650    }
651
652    /**
653     *    Sets the maximum number of redirects before
654     *    the web page is loaded regardless.
655     *    @param integer $max        Maximum hops.
656     *    @access public
657     */
658    function setMaximumRedirects($max) {
659        if (! $this->browser) {
660            trigger_error(
661                    'Can only set maximum redirects in a test method, setUp() or tearDown()');
662        }
663        $this->browser->setMaximumRedirects($max);
664    }
665
666    /**
667     *    Sets the socket timeout for opening a connection and
668     *    receiving at least one byte of information.
669     *    @param integer $timeout      Maximum time in seconds.
670     *    @access public
671     */
672    function setConnectionTimeout($timeout) {
673        $this->browser->setConnectionTimeout($timeout);
674    }
675
676    /**
677     *    Sets proxy to use on all requests for when
678     *    testing from behind a firewall. Set URL
679     *    to false to disable.
680     *    @param string $proxy        Proxy URL.
681     *    @param string $username     Proxy username for authentication.
682     *    @param string $password     Proxy password for authentication.
683     *    @access public
684     */
685    function useProxy($proxy, $username = false, $password = false) {
686        $this->browser->useProxy($proxy, $username, $password);
687    }
688
689    /**
690     *    Fetches a page into the page buffer. If
691     *    there is no base for the URL then the
692     *    current base URL is used. After the fetch
693     *    the base URL reflects the new location.
694     *    @param string $url          URL to fetch.
695     *    @param hash $parameters     Optional additional GET data.
696     *    @return boolean/string      Raw page on success.
697     *    @access public
698     */
699    function get($url, $parameters = false) {
700        return $this->failOnError($this->browser->get($url, $parameters));
701    }
702
703    /**
704     *    Fetches a page by POST into the page buffer.
705     *    If there is no base for the URL then the
706     *    current base URL is used. After the fetch
707     *    the base URL reflects the new location.
708     *    @param string $url          URL to fetch.
709     *    @param mixed $parameters    Optional POST parameters or content body to send
710     *    @param string $content_type Content type of provided body
711     *    @return boolean/string      Raw page on success.
712     *    @access public
713     */
714    function post($url, $parameters = false, $content_type = false) {
715        return $this->failOnError($this->browser->post($url, $parameters, $content_type));
716    }
717
718    /**
719     *    Fetches a page by PUT into the page buffer.
720     *    If there is no base for the URL then the
721     *    current base URL is used. After the fetch
722     *    the base URL reflects the new location.
723     *    @param string $url          URL to fetch.
724     *    @param mixed $body          Optional content body to send
725     *    @param string $content_type Content type of provided body
726     *    @return boolean/string      Raw page on success.
727     *    @access public
728     */
729    function put($url, $body = false, $content_type = false) {
730        return $this->failOnError($this->browser->put($url, $body, $content_type));
731    }
732
733    /**
734     *    Fetches a page by a DELETE request
735     *    @param string $url          URL to fetch.
736     *    @param hash $parameters     Optional additional parameters.
737     *    @return boolean/string      Raw page on success.
738     *    @access public
739     */
740    function delete($url, $parameters = false) {
741        return $this->failOnError($this->browser->delete($url, $parameters));
742    }
743
744
745    /**
746     *    Does a HTTP HEAD fetch, fetching only the page
747     *    headers. The current base URL is unchanged by this.
748     *    @param string $url          URL to fetch.
749     *    @param hash $parameters     Optional additional GET data.
750     *    @return boolean             True on success.
751     *    @access public
752     */
753    function head($url, $parameters = false) {
754        return $this->failOnError($this->browser->head($url, $parameters));
755    }
756
757    /**
758     *    Equivalent to hitting the retry button on the
759     *    browser. Will attempt to repeat the page fetch.
760     *    @return boolean     True if fetch succeeded.
761     *    @access public
762     */
763    function retry() {
764        return $this->failOnError($this->browser->retry());
765    }
766
767    /**
768     *    Equivalent to hitting the back button on the
769     *    browser.
770     *    @return boolean     True if history entry and
771     *                        fetch succeeded.
772     *    @access public
773     */
774    function back() {
775        return $this->failOnError($this->browser->back());
776    }
777
778    /**
779     *    Equivalent to hitting the forward button on the
780     *    browser.
781     *    @return boolean     True if history entry and
782     *                        fetch succeeded.
783     *    @access public
784     */
785    function forward() {
786        return $this->failOnError($this->browser->forward());
787    }
788
789    /**
790     *    Retries a request after setting the authentication
791     *    for the current realm.
792     *    @param string $username    Username for realm.
793     *    @param string $password    Password for realm.
794     *    @return boolean/string     HTML on successful fetch. Note
795     *                               that authentication may still have
796     *                               failed.
797     *    @access public
798     */
799    function authenticate($username, $password) {
800        return $this->failOnError(
801                $this->browser->authenticate($username, $password));
802    }
803
804    /**
805     *    Gets the cookie value for the current browser context.
806     *    @param string $name          Name of cookie.
807     *    @return string               Value of cookie or false if unset.
808     *    @access public
809     */
810    function getCookie($name) {
811        return $this->browser->getCurrentCookieValue($name);
812    }
813
814    /**
815     *    Sets a cookie in the current browser.
816     *    @param string $name          Name of cookie.
817     *    @param string $value         Cookie value.
818     *    @param string $host          Host upon which the cookie is valid.
819     *    @param string $path          Cookie path if not host wide.
820     *    @param string $expiry        Expiry date.
821     *    @access public
822     */
823    function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
824        $this->browser->setCookie($name, $value, $host, $path, $expiry);
825    }
826
827    /**
828     *    Accessor for current frame focus. Will be
829     *    false if no frame has focus.
830     *    @return integer/string/boolean    Label if any, otherwise
831     *                                      the position in the frameset
832     *                                      or false if none.
833     *    @access public
834     */
835    function getFrameFocus() {
836        return $this->browser->getFrameFocus();
837    }
838
839    /**
840     *    Sets the focus by index. The integer index starts from 1.
841     *    @param integer $choice    Chosen frame.
842     *    @return boolean           True if frame exists.
843     *    @access public
844     */
845    function setFrameFocusByIndex($choice) {
846        return $this->browser->setFrameFocusByIndex($choice);
847    }
848
849    /**
850     *    Sets the focus by name.
851     *    @param string $name    Chosen frame.
852     *    @return boolean        True if frame exists.
853     *    @access public
854     */
855    function setFrameFocus($name) {
856        return $this->browser->setFrameFocus($name);
857    }
858
859    /**
860     *    Clears the frame focus. All frames will be searched
861     *    for content.
862     *    @access public
863     */
864    function clearFrameFocus() {
865        return $this->browser->clearFrameFocus();
866    }
867
868    /**
869     *    Clicks a visible text item. Will first try buttons,
870     *    then links and then images.
871     *    @param string $label        Visible text or alt text.
872     *    @return string/boolean      Raw page or false.
873     *    @access public
874     */
875    function click($label) {
876        return $this->failOnError($this->browser->click($label));
877    }
878
879    /**
880     *    Checks for a click target.
881     *    @param string $label        Visible text or alt text.
882     *    @return boolean             True if click target.
883     *    @access public
884     */
885    function assertClickable($label, $message = '%s') {
886        return $this->assertTrue(
887                $this->browser->isClickable($label),
888                sprintf($message, "Click target [$label] should exist"));
889    }
890
891    /**
892     *    Clicks the submit button by label. The owning
893     *    form will be submitted by this.
894     *    @param string $label    Button label. An unlabeled
895     *                            button can be triggered by 'Submit'.
896     *    @param hash $additional Additional form values.
897     *    @return boolean/string  Page on success, else false.
898     *    @access public
899     */
900    function clickSubmit($label = 'Submit', $additional = false) {
901        return $this->failOnError(
902                $this->browser->clickSubmit($label, $additional));
903    }
904
905    /**
906     *    Clicks the submit button by name attribute. The owning
907     *    form will be submitted by this.
908     *    @param string $name     Name attribute of button.
909     *    @param hash $additional Additional form values.
910     *    @return boolean/string  Page on success.
911     *    @access public
912     */
913    function clickSubmitByName($name, $additional = false) {
914        return $this->failOnError(
915                $this->browser->clickSubmitByName($name, $additional));
916    }
917
918    /**
919     *    Clicks the submit button by ID attribute. The owning
920     *    form will be submitted by this.
921     *    @param string $id       ID attribute of button.
922     *    @param hash $additional Additional form values.
923     *    @return boolean/string  Page on success.
924     *    @access public
925     */
926    function clickSubmitById($id, $additional = false) {
927        return $this->failOnError(
928                $this->browser->clickSubmitById($id, $additional));
929    }
930
931    /**
932     *    Checks for a valid button label.
933     *    @param string $label        Visible text.
934     *    @return boolean             True if click target.
935     *    @access public
936     */
937    function assertSubmit($label, $message = '%s') {
938        return $this->assertTrue(
939                $this->browser->isSubmit($label),
940                sprintf($message, "Submit button [$label] should exist"));
941    }
942
943    /**
944     *    Clicks the submit image by some kind of label. Usually
945     *    the alt tag or the nearest equivalent. The owning
946     *    form will be submitted by this. Clicking outside of
947     *    the boundary of the coordinates will result in
948     *    a failure.
949     *    @param string $label    Alt attribute of button.
950     *    @param integer $x       X-coordinate of imaginary click.
951     *    @param integer $y       Y-coordinate of imaginary click.
952     *    @param hash $additional Additional form values.
953     *    @return boolean/string  Page on success.
954     *    @access public
955     */
956    function clickImage($label, $x = 1, $y = 1, $additional = false) {
957        return $this->failOnError(
958                $this->browser->clickImage($label, $x, $y, $additional));
959    }
960
961    /**
962     *    Clicks the submit image by the name. Usually
963     *    the alt tag or the nearest equivalent. The owning
964     *    form will be submitted by this. Clicking outside of
965     *    the boundary of the coordinates will result in
966     *    a failure.
967     *    @param string $name     Name attribute of button.
968     *    @param integer $x       X-coordinate of imaginary click.
969     *    @param integer $y       Y-coordinate of imaginary click.
970     *    @param hash $additional Additional form values.
971     *    @return boolean/string  Page on success.
972     *    @access public
973     */
974    function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
975        return $this->failOnError(
976                $this->browser->clickImageByName($name, $x, $y, $additional));
977    }
978
979    /**
980     *    Clicks the submit image by ID attribute. The owning
981     *    form will be submitted by this. Clicking outside of
982     *    the boundary of the coordinates will result in
983     *    a failure.
984     *    @param integer/string $id   ID attribute of button.
985     *    @param integer $x           X-coordinate of imaginary click.
986     *    @param integer $y           Y-coordinate of imaginary click.
987     *    @param hash $additional     Additional form values.
988     *    @return boolean/string      Page on success.
989     *    @access public
990     */
991    function clickImageById($id, $x = 1, $y = 1, $additional = false) {
992        return $this->failOnError(
993                $this->browser->clickImageById($id, $x, $y, $additional));
994    }
995
996    /**
997     *    Checks for a valid image with atht alt text or title.
998     *    @param string $label        Visible text.
999     *    @return boolean             True if click target.
1000     *    @access public
1001     */
1002    function assertImage($label, $message = '%s') {
1003        return $this->assertTrue(
1004                $this->browser->isImage($label),
1005                sprintf($message, "Image with text [$label] should exist"));
1006    }
1007
1008    /**
1009     *    Submits a form by the ID.
1010     *    @param string $id       Form ID. No button information
1011     *                            is submitted this way.
1012     *    @return boolean/string  Page on success.
1013     *    @access public
1014     */
1015    function submitFormById($id, $additional = false) {
1016        return $this->failOnError($this->browser->submitFormById($id, $additional));
1017    }
1018
1019    /**
1020     *    Follows a link by name. Will click the first link
1021     *    found with this link text by default, or a later
1022     *    one if an index is given. Match is case insensitive
1023     *    with normalised space.
1024     *    @param string $label     Text between the anchor tags.
1025     *    @param integer $index    Link position counting from zero.
1026     *    @return boolean/string   Page on success.
1027     *    @access public
1028     */
1029    function clickLink($label, $index = 0) {
1030        return $this->failOnError($this->browser->clickLink($label, $index));
1031    }
1032
1033    /**
1034     *    Follows a link by id attribute.
1035     *    @param string $id        ID attribute value.
1036     *    @return boolean/string   Page on success.
1037     *    @access public
1038     */
1039    function clickLinkById($id) {
1040        return $this->failOnError($this->browser->clickLinkById($id));
1041    }
1042
1043    /**
1044     *    Tests for the presence of a link label. Match is
1045     *    case insensitive with normalised space.
1046     *    @param string $label     Text between the anchor tags.
1047     *    @param mixed $expected   Expected URL or expectation object.
1048     *    @param string $message   Message to display. Default
1049     *                             can be embedded with %s.
1050     *    @return boolean          True if link present.
1051     *    @access public
1052     */
1053    function assertLink($label, $expected = true, $message = '%s') {
1054        $url = $this->browser->getLink($label);
1055        if ($expected === true || ($expected !== true && $url === false)) {
1056            return $this->assertTrue($url !== false, sprintf($message, "Link [$label] should exist"));
1057        }
1058        if (! SimpleExpectation::isExpectation($expected)) {
1059            $expected = new IdenticalExpectation($expected);
1060        }
1061        return $this->assert($expected, $url->asString(), sprintf($message, "Link [$label] should match"));
1062    }
1063
1064    /**
1065     *    Tests for the non-presence of a link label. Match is
1066     *    case insensitive with normalised space.
1067     *    @param string/integer $label    Text between the anchor tags
1068     *                                    or ID attribute.
1069     *    @param string $message          Message to display. Default
1070     *                                    can be embedded with %s.
1071     *    @return boolean                 True if link missing.
1072     *    @access public
1073     */
1074    function assertNoLink($label, $message = '%s') {
1075        return $this->assertTrue(
1076                $this->browser->getLink($label) === false,
1077                sprintf($message, "Link [$label] should not exist"));
1078    }
1079
1080    /**
1081     *    Tests for the presence of a link id attribute.
1082     *    @param string $id        Id attribute value.
1083     *    @param mixed $expected   Expected URL or expectation object.
1084     *    @param string $message   Message to display. Default
1085     *                             can be embedded with %s.
1086     *    @return boolean          True if link present.
1087     *    @access public
1088     */
1089    function assertLinkById($id, $expected = true, $message = '%s') {
1090        $url = $this->browser->getLinkById($id);
1091        if ($expected === true) {
1092            return $this->assertTrue($url !== false, sprintf($message, "Link ID [$id] should exist"));
1093        }
1094        if (! SimpleExpectation::isExpectation($expected)) {
1095            $expected = new IdenticalExpectation($expected);
1096        }
1097        return $this->assert($expected, $url->asString(), sprintf($message, "Link ID [$id] should match"));
1098    }
1099
1100    /**
1101     *    Tests for the non-presence of a link label. Match is
1102     *    case insensitive with normalised space.
1103     *    @param string $id        Id attribute value.
1104     *    @param string $message   Message to display. Default
1105     *                             can be embedded with %s.
1106     *    @return boolean          True if link missing.
1107     *    @access public
1108     */
1109    function assertNoLinkById($id, $message = '%s') {
1110        return $this->assertTrue(
1111                $this->browser->getLinkById($id) === false,
1112                sprintf($message, "Link ID [$id] should not exist"));
1113    }
1114
1115    /**
1116     *    Sets all form fields with that label, or name if there
1117     *    is no label attached.
1118     *    @param string $name    Name of field in forms.
1119     *    @param string $value   New value of field.
1120     *    @return boolean        True if field exists, otherwise false.
1121     *    @access public
1122     */
1123    function setField($label, $value, $position=false) {
1124        return $this->browser->setField($label, $value, $position);
1125    }
1126
1127    /**
1128     *    Sets all form fields with that name.
1129     *    @param string $name    Name of field in forms.
1130     *    @param string $value   New value of field.
1131     *    @return boolean        True if field exists, otherwise false.
1132     *    @access public
1133     */
1134    function setFieldByName($name, $value, $position=false) {
1135        return $this->browser->setFieldByName($name, $value, $position);
1136    }
1137
1138    /**
1139     *    Sets all form fields with that id.
1140     *    @param string/integer $id   Id of field in forms.
1141     *    @param string $value        New value of field.
1142     *    @return boolean             True if field exists, otherwise false.
1143     *    @access public
1144     */
1145    function setFieldById($id, $value) {
1146        return $this->browser->setFieldById($id, $value);
1147    }
1148
1149    /**
1150     *    Confirms that the form element is currently set
1151     *    to the expected value. A missing form will always
1152     *    fail. If no value is given then only the existence
1153     *    of the field is checked.
1154     *    @param string $name       Name of field in forms.
1155     *    @param mixed $expected    Expected string/array value or
1156     *                              false for unset fields.
1157     *    @param string $message    Message to display. Default
1158     *                              can be embedded with %s.
1159     *    @return boolean           True if pass.
1160     *    @access public
1161     */
1162    function assertField($label, $expected = true, $message = '%s') {
1163        $value = $this->browser->getField($label);
1164        return $this->assertFieldValue($label, $value, $expected, $message);
1165    }
1166
1167    /**
1168     *    Confirms that the form element is currently set
1169     *    to the expected value. A missing form element will always
1170     *    fail. If no value is given then only the existence
1171     *    of the field is checked.
1172     *    @param string $name       Name of field in forms.
1173     *    @param mixed $expected    Expected string/array value or
1174     *                              false for unset fields.
1175     *    @param string $message    Message to display. Default
1176     *                              can be embedded with %s.
1177     *    @return boolean           True if pass.
1178     *    @access public
1179     */
1180    function assertFieldByName($name, $expected = true, $message = '%s') {
1181        $value = $this->browser->getFieldByName($name);
1182        return $this->assertFieldValue($name, $value, $expected, $message);
1183    }
1184
1185    /**
1186     *    Confirms that the form element is currently set
1187     *    to the expected value. A missing form will always
1188     *    fail. If no ID is given then only the existence
1189     *    of the field is checked.
1190     *    @param string/integer $id  Name of field in forms.
1191     *    @param mixed $expected     Expected string/array value or
1192     *                               false for unset fields.
1193     *    @param string $message     Message to display. Default
1194     *                               can be embedded with %s.
1195     *    @return boolean            True if pass.
1196     *    @access public
1197     */
1198    function assertFieldById($id, $expected = true, $message = '%s') {
1199        $value = $this->browser->getFieldById($id);
1200        return $this->assertFieldValue($id, $value, $expected, $message);
1201    }
1202
1203    /**
1204     *    Tests the field value against the expectation.
1205     *    @param string $identifier      Name, ID or label.
1206     *    @param mixed $value            Current field value.
1207     *    @param mixed $expected         Expected value to match.
1208     *    @param string $message         Failure message.
1209     *    @return boolean                True if pass
1210     *    @access protected
1211     */
1212    protected function assertFieldValue($identifier, $value, $expected, $message) {
1213        if ($expected === true) {
1214            return $this->assertTrue(
1215                    isset($value),
1216                    sprintf($message, "Field [$identifier] should exist"));
1217        }
1218        if (! SimpleExpectation::isExpectation($expected)) {
1219            $identifier = str_replace('%', '%%', $identifier);
1220            $expected = new FieldExpectation(
1221                    $expected,
1222                    "Field [$identifier] should match with [%s]");
1223        }
1224        return $this->assert($expected, $value, $message);
1225    }
1226
1227    /**
1228     *    Checks the response code against a list
1229     *    of possible values.
1230     *    @param array $responses    Possible responses for a pass.
1231     *    @param string $message     Message to display. Default
1232     *                               can be embedded with %s.
1233     *    @return boolean            True if pass.
1234     *    @access public
1235     */
1236    function assertResponse($responses, $message = '%s') {
1237        $responses = (is_array($responses) ? $responses : array($responses));
1238        $code = $this->browser->getResponseCode();
1239        $message = sprintf($message, "Expecting response in [" .
1240                implode(", ", $responses) . "] got [$code]");
1241        return $this->assertTrue(in_array($code, $responses), $message);
1242    }
1243
1244    /**
1245     *    Checks the mime type against a list
1246     *    of possible values.
1247     *    @param array $types      Possible mime types for a pass.
1248     *    @param string $message   Message to display.
1249     *    @return boolean          True if pass.
1250     *    @access public
1251     */
1252    function assertMime($types, $message = '%s') {
1253        $types = (is_array($types) ? $types : array($types));
1254        $type = $this->browser->getMimeType();
1255        $message = sprintf($message, "Expecting mime type in [" .
1256                implode(", ", $types) . "] got [$type]");
1257        return $this->assertTrue(in_array($type, $types), $message);
1258    }
1259
1260    /**
1261     *    Attempt to match the authentication type within
1262     *    the security realm we are currently matching.
1263     *    @param string $authentication   Usually basic.
1264     *    @param string $message          Message to display.
1265     *    @return boolean                 True if pass.
1266     *    @access public
1267     */
1268    function assertAuthentication($authentication = false, $message = '%s') {
1269        if (! $authentication) {
1270            $message = sprintf($message, "Expected any authentication type, got [" .
1271                    $this->browser->getAuthentication() . "]");
1272            return $this->assertTrue(
1273                    $this->browser->getAuthentication(),
1274                    $message);
1275        } else {
1276            $message = sprintf($message, "Expected authentication [$authentication] got [" .
1277                    $this->browser->getAuthentication() . "]");
1278            return $this->assertTrue(
1279                    strtolower($this->browser->getAuthentication()) == strtolower($authentication),
1280                    $message);
1281        }
1282    }
1283
1284    /**
1285     *    Checks that no authentication is necessary to view
1286     *    the desired page.
1287     *    @param string $message     Message to display.
1288     *    @return boolean            True if pass.
1289     *    @access public
1290     */
1291    function assertNoAuthentication($message = '%s') {
1292        $message = sprintf($message, "Expected no authentication type, got [" .
1293                $this->browser->getAuthentication() . "]");
1294        return $this->assertFalse($this->browser->getAuthentication(), $message);
1295    }
1296
1297    /**
1298     *    Attempts to match the current security realm.
1299     *    @param string $realm     Name of security realm.
1300     *    @param string $message   Message to display.
1301     *    @return boolean          True if pass.
1302     *    @access public
1303     */
1304    function assertRealm($realm, $message = '%s') {
1305        if (! SimpleExpectation::isExpectation($realm)) {
1306            $realm = new EqualExpectation($realm);
1307        }
1308        return $this->assert(
1309                $realm,
1310                $this->browser->getRealm(),
1311                "Expected realm -> $message");
1312    }
1313
1314    /**
1315     *    Checks each header line for the required value. If no
1316     *    value is given then only an existence check is made.
1317     *    @param string $header    Case insensitive header name.
1318     *    @param mixed $value      Case sensitive trimmed string to
1319     *                             match against. An expectation object
1320     *                             can be used for pattern matching.
1321     *    @return boolean          True if pass.
1322     *    @access public
1323     */
1324    function assertHeader($header, $value = false, $message = '%s') {
1325        return $this->assert(
1326                new HttpHeaderExpectation($header, $value),
1327                $this->browser->getHeaders(),
1328                $message);
1329    }
1330
1331    /**
1332     *    Confirms that the header type has not been received.
1333     *    Only the landing page is checked. If you want to check
1334     *    redirect pages, then you should limit redirects so
1335     *    as to capture the page you want.
1336     *    @param string $header    Case insensitive header name.
1337     *    @return boolean          True if pass.
1338     *    @access public
1339     */
1340    function assertNoHeader($header, $message = '%s') {
1341        return $this->assert(
1342                new NoHttpHeaderExpectation($header),
1343                $this->browser->getHeaders(),
1344                $message);
1345    }
1346
1347    /**
1348     *    Tests the text between the title tags.
1349     *    @param string/SimpleExpectation $title    Expected title.
1350     *    @param string $message                    Message to display.
1351     *    @return boolean                           True if pass.
1352     *    @access public
1353     */
1354    function assertTitle($title = false, $message = '%s') {
1355        if (! SimpleExpectation::isExpectation($title)) {
1356            $title = new EqualExpectation($title);
1357        }
1358        return $this->assert($title, $this->browser->getTitle(), $message);
1359    }
1360
1361    /**
1362     *    Will trigger a pass if the text is found in the plain
1363     *    text form of the page.
1364     *    @param string $text       Text to look for.
1365     *    @param string $message    Message to display.
1366     *    @return boolean           True if pass.
1367     *    @access public
1368     */
1369    function assertText($text, $message = '%s') {
1370        return $this->assert(
1371                new TextExpectation($text),
1372                $this->browser->getContentAsText(),
1373                $message);
1374    }
1375
1376    /**
1377     *    Will trigger a pass if the text is not found in the plain
1378     *    text form of the page.
1379     *    @param string $text       Text to look for.
1380     *    @param string $message    Message to display.
1381     *    @return boolean           True if pass.
1382     *    @access public
1383     */
1384    function assertNoText($text, $message = '%s') {
1385        return $this->assert(
1386                new NoTextExpectation($text),
1387                $this->browser->getContentAsText(),
1388                $message);
1389    }
1390
1391    /**
1392     *    Will trigger a pass if the Perl regex pattern
1393     *    is found in the raw content.
1394     *    @param string $pattern    Perl regex to look for including
1395     *                              the regex delimiters.
1396     *    @param string $message    Message to display.
1397     *    @return boolean           True if pass.
1398     *    @access public
1399     */
1400    function assertPattern($pattern, $message = '%s') {
1401        return $this->assert(
1402                new PatternExpectation($pattern),
1403                $this->browser->getContent(),
1404                $message);
1405    }
1406
1407    /**
1408     *    Will trigger a pass if the perl regex pattern
1409     *    is not present in raw content.
1410     *    @param string $pattern    Perl regex to look for including
1411     *                              the regex delimiters.
1412     *    @param string $message    Message to display.
1413     *    @return boolean           True if pass.
1414     *    @access public
1415     */
1416    function assertNoPattern($pattern, $message = '%s') {
1417        return $this->assert(
1418                new NoPatternExpectation($pattern),
1419                $this->browser->getContent(),
1420                $message);
1421    }
1422
1423    /**
1424     *    Checks that a cookie is set for the current page
1425     *    and optionally checks the value.
1426     *    @param string $name        Name of cookie to test.
1427     *    @param string $expected    Expected value as a string or
1428     *                               false if any value will do.
1429     *    @param string $message     Message to display.
1430     *    @return boolean            True if pass.
1431     *    @access public
1432     */
1433    function assertCookie($name, $expected = false, $message = '%s') {
1434        $value = $this->getCookie($name);
1435        if (! $expected) {
1436            return $this->assertTrue(
1437                    $value,
1438                    sprintf($message, "Expecting cookie [$name]"));
1439        }
1440        if (! SimpleExpectation::isExpectation($expected)) {
1441            $expected = new EqualExpectation($expected);
1442        }
1443        return $this->assert($expected, $value, "Expecting cookie [$name] -> $message");
1444    }
1445
1446    /**
1447     *    Checks that no cookie is present or that it has
1448     *    been successfully cleared.
1449     *    @param string $name        Name of cookie to test.
1450     *    @param string $message     Message to display.
1451     *    @return boolean            True if pass.
1452     *    @access public
1453     */
1454    function assertNoCookie($name, $message = '%s') {
1455        return $this->assertTrue(
1456                $this->getCookie($name) === null or $this->getCookie($name) === false,
1457                sprintf($message, "Not expecting cookie [$name]"));
1458    }
1459
1460    /**
1461     *    Called from within the test methods to register
1462     *    passes and failures.
1463     *    @param boolean $result    Pass on true.
1464     *    @param string $message    Message to display describing
1465     *                              the test state.
1466     *    @return boolean           True on pass
1467     *    @access public
1468     */
1469    function assertTrue($result, $message = '%s') {
1470        return $this->assert(new TrueExpectation(), $result, $message);
1471    }
1472
1473    /**
1474     *    Will be true on false and vice versa. False
1475     *    is the PHP definition of false, so that null,
1476     *    empty strings, zero and an empty array all count
1477     *    as false.
1478     *    @param boolean $result    Pass on false.
1479     *    @param string $message    Message to display.
1480     *    @return boolean           True on pass
1481     *    @access public
1482     */
1483    function assertFalse($result, $message = '%s') {
1484        return $this->assert(new FalseExpectation(), $result, $message);
1485    }
1486
1487    /**
1488     *    Will trigger a pass if the two parameters have
1489     *    the same value only. Otherwise a fail. This
1490     *    is for testing hand extracted text, etc.
1491     *    @param mixed $first          Value to compare.
1492     *    @param mixed $second         Value to compare.
1493     *    @param string $message       Message to display.
1494     *    @return boolean              True on pass
1495     *    @access public
1496     */
1497    function assertEqual($first, $second, $message = '%s') {
1498        return $this->assert(
1499                new EqualExpectation($first),
1500                $second,
1501                $message);
1502    }
1503
1504    /**
1505     *    Will trigger a pass if the two parameters have
1506     *    a different value. Otherwise a fail. This
1507     *    is for testing hand extracted text, etc.
1508     *    @param mixed $first           Value to compare.
1509     *    @param mixed $second          Value to compare.
1510     *    @param string $message        Message to display.
1511     *    @return boolean               True on pass
1512     *    @access public
1513     */
1514    function assertNotEqual($first, $second, $message = '%s') {
1515        return $this->assert(
1516                new NotEqualExpectation($first),
1517                $second,
1518                $message);
1519    }
1520
1521    /**
1522     *    Uses a stack trace to find the line of an assertion.
1523     *    @return string           Line number of first assert*
1524     *                             method embedded in format string.
1525     *    @access public
1526     */
1527    function getAssertionLine() {
1528        $trace = new SimpleStackTrace(array('assert', 'click', 'pass', 'fail'));
1529        return $trace->traceMethod();
1530    }
1531}
1532?>