3 * Base include file for SimpleTest
5 * @subpackage WebTester
10 * include other SimpleTest class files
12 require_once(dirname(__FILE__
) . '/simpletest.php');
13 require_once(dirname(__FILE__
) . '/http.php');
14 require_once(dirname(__FILE__
) . '/encoding.php');
15 require_once(dirname(__FILE__
) . '/page.php');
16 require_once(dirname(__FILE__
) . '/selector.php');
17 require_once(dirname(__FILE__
) . '/frames.php');
18 require_once(dirname(__FILE__
) . '/user_agent.php');
21 if (!defined('DEFAULT_MAX_NESTED_FRAMES')) {
22 define('DEFAULT_MAX_NESTED_FRAMES', 3);
26 * Browser history list.
28 * @subpackage WebTester
30 class SimpleBrowserHistory
{
38 function SimpleBrowserHistory() {
39 $this->_sequence
= array();
40 $this->_position
= -1;
44 * Test for no entries yet.
45 * @return boolean True if empty.
49 return ($this->_position
== -1);
53 * Test for being at the beginning.
54 * @return boolean True if first.
57 function _atBeginning() {
58 return ($this->_position
== 0) && ! $this->_isEmpty();
62 * Test for being at the last entry.
63 * @return boolean True if last.
67 return ($this->_position +
1 >= count($this->_sequence
)) && ! $this->_isEmpty();
71 * Adds a successfully fetched page to the history.
72 * @param SimpleUrl $url URL of fetch.
73 * @param SimpleEncoding $parameters Any post data with the fetch.
76 function recordEntry($url, $parameters) {
80 array('url' => $url, 'parameters' => $parameters));
85 * Last fully qualified URL for current history
87 * @return SimpleUrl URL for this position.
91 if ($this->_isEmpty()) {
94 return $this->_sequence
[$this->_position
]['url'];
98 * Parameters of last fetch from current history
100 * @return SimpleFormEncoding Post parameters.
103 function getParameters() {
104 if ($this->_isEmpty()) {
107 return $this->_sequence
[$this->_position
]['parameters'];
111 * Step back one place in the history. Stops at
113 * @return boolean True if any previous entries.
117 if ($this->_isEmpty() ||
$this->_atBeginning()) {
125 * Step forward one place. If already at the
126 * latest entry then nothing will happen.
127 * @return boolean True if any future entries.
131 if ($this->_isEmpty() ||
$this->_atEnd()) {
139 * Ditches all future entries beyond the current
143 function _dropFuture() {
144 if ($this->_isEmpty()) {
147 while (! $this->_atEnd()) {
148 array_pop($this->_sequence
);
154 * Simulated web browser. This is an aggregate of
155 * the user agent, the HTML parsing, request history
156 * and the last header set.
157 * @package SimpleTest
158 * @subpackage WebTester
160 class SimpleBrowser
{
165 var $_maximum_nested_frames;
168 * Starts with a fresh browser with no
169 * cookie or any other state information. The
170 * exception is that a default proxy will be
171 * set up if specified in the options.
174 function SimpleBrowser() {
175 $this->_user_agent
= &$this->_createUserAgent();
176 $this->_user_agent
->useProxy(
177 SimpleTest
::getDefaultProxy(),
178 SimpleTest
::getDefaultProxyUsername(),
179 SimpleTest
::getDefaultProxyPassword());
180 $this->_page
= &new SimplePage();
181 $this->_history
= &$this->_createHistory();
182 $this->_ignore_frames
= false;
183 $this->_maximum_nested_frames
= DEFAULT_MAX_NESTED_FRAMES
;
187 * Creates the underlying user agent.
188 * @return SimpleFetcher Content fetcher.
191 function &_createUserAgent() {
192 $user_agent = &new SimpleUserAgent();
197 * Creates a new empty history list.
198 * @return SimpleBrowserHistory New list.
201 function &_createHistory() {
202 $history = &new SimpleBrowserHistory();
207 * Disables frames support. Frames will not be fetched
208 * and the frameset page will be used instead.
211 function ignoreFrames() {
212 $this->_ignore_frames
= true;
216 * Enables frames support. Frames will be fetched from
220 function useFrames() {
221 $this->_ignore_frames
= false;
225 * Switches off cookie sending and recieving.
228 function ignoreCookies() {
229 $this->_user_agent
->ignoreCookies();
233 * Switches back on the cookie sending and recieving.
236 function useCookies() {
237 $this->_user_agent
->useCookies();
241 * Parses the raw content into a page. Will load further
242 * frame pages unless frames are disabled.
243 * @param SimpleHttpResponse $response Response from fetch.
244 * @param integer $depth Nested frameset depth.
245 * @return SimplePage Parsed HTML.
248 function &_parse($response, $depth = 0) {
249 $page = &$this->_buildPage($response);
250 if ($this->_ignore_frames ||
! $page->hasFrames() ||
($depth > $this->_maximum_nested_frames
)) {
253 $frameset = &new SimpleFrameset($page);
254 foreach ($page->getFrameset() as $key => $url) {
255 $frame = &$this->_fetch($url, new SimpleGetEncoding(), $depth +
1);
256 $frameset->addFrame($frame, $key);
262 * Assembles the parsing machinery and actually parses
263 * a single page. Frees all of the builder memory and so
264 * unjams the PHP memory management.
265 * @param SimpleHttpResponse $response Response from fetch.
266 * @return SimplePage Parsed top level page.
269 function &_buildPage($response) {
270 $builder = &new SimplePageBuilder();
271 $page = &$builder->parse($response);
278 * Fetches a page. Jointly recursive with the _parse()
279 * method as it descends a frameset.
280 * @param string/SimpleUrl $url Target to fetch.
281 * @param SimpleEncoding $encoding GET/POST parameters.
282 * @param integer $depth Nested frameset depth protection.
283 * @return SimplePage Parsed page.
286 function &_fetch($url, $encoding, $depth = 0) {
287 $response = &$this->_user_agent
->fetchResponse($url, $encoding);
288 if ($response->isError()) {
289 $page = &new SimplePage($response);
291 $page = &$this->_parse($response, $depth);
297 * Fetches a page or a single frame if that is the current
299 * @param SimpleUrl $url Target to fetch.
300 * @param SimpleEncoding $parameters GET/POST parameters.
301 * @return string Raw content of page.
304 function _load($url, $parameters) {
305 $frame = $url->getTarget();
306 if (! $frame ||
! $this->_page
->hasFrames() ||
(strtolower($frame) == '_top')) {
307 return $this->_loadPage($url, $parameters);
309 return $this->_loadFrame(array($frame), $url, $parameters);
313 * Fetches a page and makes it the current page/frame.
314 * @param string/SimpleUrl $url Target to fetch as string.
315 * @param SimplePostEncoding $parameters POST parameters.
316 * @return string Raw content of page.
319 function _loadPage($url, $parameters) {
320 $this->_page
= &$this->_fetch($url, $parameters);
321 $this->_history
->recordEntry(
322 $this->_page
->getUrl(),
323 $this->_page
->getRequestData());
324 return $this->_page
->getRaw();
328 * Fetches a frame into the existing frameset replacing the
330 * @param array $frames List of names to drill down.
331 * @param string/SimpleUrl $url Target to fetch as string.
332 * @param SimpleFormEncoding $parameters POST parameters.
333 * @return string Raw content of page.
336 function _loadFrame($frames, $url, $parameters) {
337 $page = &$this->_fetch($url, $parameters);
338 $this->_page
->setFrame($frames, $page);
342 * Removes expired and temporary cookies as if
343 * the browser was closed and re-opened.
344 * @param string/integer $date Time when session restarted.
345 * If omitted then all persistent
349 function restart($date = false) {
350 $this->_user_agent
->restart($date);
354 * Adds a header to every fetch.
355 * @param string $header Header line to add to every
356 * request until cleared.
359 function addHeader($header) {
360 $this->_user_agent
->addHeader($header);
364 * Ages the cookies by the specified time.
365 * @param integer $interval Amount in seconds.
368 function ageCookies($interval) {
369 $this->_user_agent
->ageCookies($interval);
373 * Sets an additional cookie. If a cookie has
374 * the same name and path it is replaced.
375 * @param string $name Cookie key.
376 * @param string $value Value of cookie.
377 * @param string $host Host upon which the cookie is valid.
378 * @param string $path Cookie path if not host wide.
379 * @param string $expiry Expiry date.
382 function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
383 $this->_user_agent
->setCookie($name, $value, $host, $path, $expiry);
387 * Reads the most specific cookie value from the
389 * @param string $host Host to search.
390 * @param string $path Applicable path.
391 * @param string $name Name of cookie to read.
392 * @return string False if not present, else the
396 function getCookieValue($host, $path, $name) {
397 return $this->_user_agent
->getCookieValue($host, $path, $name);
401 * Reads the current cookies for the current URL.
402 * @param string $name Key of cookie to find.
403 * @return string Null if there is no current URL, false
404 * if the cookie is not set.
407 function getCurrentCookieValue($name) {
408 return $this->_user_agent
->getBaseCookieValue($name, $this->_page
->getUrl());
412 * Sets the maximum number of redirects before
413 * a page will be loaded anyway.
414 * @param integer $max Most hops allowed.
417 function setMaximumRedirects($max) {
418 $this->_user_agent
->setMaximumRedirects($max);
422 * Sets the maximum number of nesting of framed pages
423 * within a framed page to prevent loops.
424 * @param integer $max Highest depth allowed.
427 function setMaximumNestedFrames($max) {
428 $this->_maximum_nested_frames
= $max;
432 * Sets the socket timeout for opening a connection.
433 * @param integer $timeout Maximum time in seconds.
436 function setConnectionTimeout($timeout) {
437 $this->_user_agent
->setConnectionTimeout($timeout);
441 * Sets proxy to use on all requests for when
442 * testing from behind a firewall. Set URL
443 * to false to disable.
444 * @param string $proxy Proxy URL.
445 * @param string $username Proxy username for authentication.
446 * @param string $password Proxy password for authentication.
449 function useProxy($proxy, $username = false, $password = false) {
450 $this->_user_agent
->useProxy($proxy, $username, $password);
454 * Fetches the page content with a HEAD request.
455 * Will affect cookies, but will not change the base URL.
456 * @param string/SimpleUrl $url Target to fetch as string.
457 * @param hash/SimpleHeadEncoding $parameters Additional parameters for
459 * @return boolean True if successful.
462 function head($url, $parameters = false) {
463 if (! is_object($url)) {
464 $url = new SimpleUrl($url);
466 if ($this->getUrl()) {
467 $url = $url->makeAbsolute($this->getUrl());
469 $response = &$this->_user_agent
->fetchResponse($url, new SimpleHeadEncoding($parameters));
470 return ! $response->isError();
474 * Fetches the page content with a simple GET request.
475 * @param string/SimpleUrl $url Target to fetch.
476 * @param hash/SimpleFormEncoding $parameters Additional parameters for
478 * @return string Content of page or false.
481 function get($url, $parameters = false) {
482 if (! is_object($url)) {
483 $url = new SimpleUrl($url);
485 if ($this->getUrl()) {
486 $url = $url->makeAbsolute($this->getUrl());
488 return $this->_load($url, new SimpleGetEncoding($parameters));
492 * Fetches the page content with a POST request.
493 * @param string/SimpleUrl $url Target to fetch as string.
494 * @param hash/SimpleFormEncoding $parameters POST parameters.
495 * @return string Content of page.
498 function post($url, $parameters = false) {
499 if (! is_object($url)) {
500 $url = new SimpleUrl($url);
502 if ($this->getUrl()) {
503 $url = $url->makeAbsolute($this->getUrl());
505 return $this->_load($url, new SimplePostEncoding($parameters));
509 * Equivalent to hitting the retry button on the
510 * browser. Will attempt to repeat the page fetch. If
511 * there is no history to repeat it will give false.
512 * @return string/boolean Content if fetch succeeded
517 $frames = $this->_page
->getFrameFocus();
518 if (count($frames) > 0) {
521 $this->_page
->getUrl(),
522 $this->_page
->getRequestData());
523 return $this->_page
->getRaw();
525 if ($url = $this->_history
->getUrl()) {
526 $this->_page
= &$this->_fetch($url, $this->_history
->getParameters());
527 return $this->_page
->getRaw();
533 * Equivalent to hitting the back button on the
534 * browser. The browser history is unchanged on
535 * failure. The page content is refetched as there
536 * is no concept of content caching in SimpleTest.
537 * @return boolean True if history entry and
542 if (! $this->_history
->back()) {
545 $content = $this->retry();
547 $this->_history
->forward();
553 * Equivalent to hitting the forward button on the
554 * browser. The browser history is unchanged on
555 * failure. The page content is refetched as there
556 * is no concept of content caching in SimpleTest.
557 * @return boolean True if history entry and
562 if (! $this->_history
->forward()) {
565 $content = $this->retry();
567 $this->_history
->back();
573 * Retries a request after setting the authentication
574 * for the current realm.
575 * @param string $username Username for realm.
576 * @param string $password Password for realm.
577 * @return boolean True if successful fetch. Note
578 * that authentication may still have
582 function authenticate($username, $password) {
583 if (! $this->_page
->getRealm()) {
586 $url = $this->_page
->getUrl();
590 $this->_user_agent
->setIdentity(
592 $this->_page
->getRealm(),
595 return $this->retry();
599 * Accessor for a breakdown of the frameset.
600 * @return array Hash tree of frames by name
601 * or index if no name.
604 function getFrames() {
605 return $this->_page
->getFrames();
609 * Accessor for current frame focus. Will be
610 * false if no frame has focus.
611 * @return integer/string/boolean Label if any, otherwise
612 * the position in the frameset
616 function getFrameFocus() {
617 return $this->_page
->getFrameFocus();
621 * Sets the focus by index. The integer index starts from 1.
622 * @param integer $choice Chosen frame.
623 * @return boolean True if frame exists.
626 function setFrameFocusByIndex($choice) {
627 return $this->_page
->setFrameFocusByIndex($choice);
631 * Sets the focus by name.
632 * @param string $name Chosen frame.
633 * @return boolean True if frame exists.
636 function setFrameFocus($name) {
637 return $this->_page
->setFrameFocus($name);
641 * Clears the frame focus. All frames will be searched
645 function clearFrameFocus() {
646 return $this->_page
->clearFrameFocus();
650 * Accessor for last error.
651 * @return string Error from last response.
654 function getTransportError() {
655 return $this->_page
->getTransportError();
659 * Accessor for current MIME type.
660 * @return string MIME type as string; e.g. 'text/html'
663 function getMimeType() {
664 return $this->_page
->getMimeType();
668 * Accessor for last response code.
669 * @return integer Last HTTP response code received.
672 function getResponseCode() {
673 return $this->_page
->getResponseCode();
677 * Accessor for last Authentication type. Only valid
678 * straight after a challenge (401).
679 * @return string Description of challenge type.
682 function getAuthentication() {
683 return $this->_page
->getAuthentication();
687 * Accessor for last Authentication realm. Only valid
688 * straight after a challenge (401).
689 * @return string Name of security realm.
692 function getRealm() {
693 return $this->_page
->getRealm();
697 * Accessor for current URL of page or frame if
699 * @return string Location of current page or frame as
703 $url = $this->_page
->getUrl();
704 return $url ?
$url->asString() : false;
708 * Accessor for raw bytes sent down the wire.
709 * @return string Original text sent.
712 function getRequest() {
713 return $this->_page
->getRequest();
717 * Accessor for raw header information.
718 * @return string Header block.
721 function getHeaders() {
722 return $this->_page
->getHeaders();
726 * Accessor for raw page information.
727 * @return string Original text content of web page.
730 function getContent() {
731 return $this->_page
->getRaw();
735 * Accessor for plain text version of the page.
736 * @return string Normalised text representation.
739 function getContentAsText() {
740 return $this->_page
->getText();
744 * Accessor for parsed title.
745 * @return string Title or false if no title is present.
748 function getTitle() {
749 return $this->_page
->getTitle();
753 * Accessor for a list of all fixed links in current page.
754 * @return array List of urls with scheme of
755 * http or https and hostname.
758 function getAbsoluteUrls() {
759 return $this->_page
->getAbsoluteUrls();
763 * Accessor for a list of all relative links.
764 * @return array List of urls without hostname.
767 function getRelativeUrls() {
768 return $this->_page
->getRelativeUrls();
772 * Sets all form fields with that name.
773 * @param string $label Name or label of field in forms.
774 * @param string $value New value of field.
775 * @return boolean True if field exists, otherwise false.
778 function setField($label, $value) {
779 return $this->_page
->setField(new SimpleByLabelOrName($label), $value);
783 * Sets all form fields with that name. Will use label if
784 * one is available (not yet implemented).
785 * @param string $name Name of field in forms.
786 * @param string $value New value of field.
787 * @return boolean True if field exists, otherwise false.
790 function setFieldByName($name, $value) {
791 return $this->_page
->setField(new SimpleByName($name), $value);
795 * Sets all form fields with that id attribute.
796 * @param string/integer $id Id of field in forms.
797 * @param string $value New value of field.
798 * @return boolean True if field exists, otherwise false.
801 function setFieldById($id, $value) {
802 return $this->_page
->setField(new SimpleById($id), $value);
806 * Accessor for a form element value within the page.
807 * Finds the first match.
808 * @param string $label Field label.
809 * @return string/boolean A value if the field is
810 * present, false if unchecked
811 * and null if missing.
814 function getField($label) {
815 return $this->_page
->getField(new SimpleByLabelOrName($label));
819 * Accessor for a form element value within the page.
820 * Finds the first match.
821 * @param string $name Field name.
822 * @return string/boolean A string if the field is
823 * present, false if unchecked
824 * and null if missing.
827 function getFieldByName($name) {
828 return $this->_page
->getField(new SimpleByName($name));
832 * Accessor for a form element value within the page.
833 * @param string/integer $id Id of field in forms.
834 * @return string/boolean A string if the field is
835 * present, false if unchecked
836 * and null if missing.
839 function getFieldById($id) {
840 return $this->_page
->getField(new SimpleById($id));
844 * Clicks the submit button by label. The owning
845 * form will be submitted by this.
846 * @param string $label Button label. An unlabeled
847 * button can be triggered by 'Submit'.
848 * @param hash $additional Additional form data.
849 * @return string/boolean Page on success.
852 function clickSubmit($label = 'Submit', $additional = false) {
853 if (! ($form = &$this->_page
->getFormBySubmit(new SimpleByLabel($label)))) {
856 $success = $this->_load(
858 $form->submitButton(new SimpleByLabel($label), $additional));
859 return ($success ?
$this->getContent() : $success);
863 * Clicks the submit button by name attribute. The owning
864 * form will be submitted by this.
865 * @param string $name Button name.
866 * @param hash $additional Additional form data.
867 * @return string/boolean Page on success.
870 function clickSubmitByName($name, $additional = false) {
871 if (! ($form = &$this->_page
->getFormBySubmit(new SimpleByName($name)))) {
874 $success = $this->_load(
876 $form->submitButton(new SimpleByName($name), $additional));
877 return ($success ?
$this->getContent() : $success);
881 * Clicks the submit button by ID attribute of the button
882 * itself. The owning form will be submitted by this.
883 * @param string $id Button ID.
884 * @param hash $additional Additional form data.
885 * @return string/boolean Page on success.
888 function clickSubmitById($id, $additional = false) {
889 if (! ($form = &$this->_page
->getFormBySubmit(new SimpleById($id)))) {
892 $success = $this->_load(
894 $form->submitButton(new SimpleById($id), $additional));
895 return ($success ?
$this->getContent() : $success);
899 * Tests to see if a submit button exists with this
901 * @param string $label Button label.
902 * @return boolean True if present.
905 function isSubmit($label) {
906 return (boolean
)$this->_page
->getFormBySubmit(new SimpleByLabel($label));
910 * Clicks the submit image by some kind of label. Usually
911 * the alt tag or the nearest equivalent. The owning
912 * form will be submitted by this. Clicking outside of
913 * the boundary of the coordinates will result in
915 * @param string $label ID attribute of button.
916 * @param integer $x X-coordinate of imaginary click.
917 * @param integer $y Y-coordinate of imaginary click.
918 * @param hash $additional Additional form data.
919 * @return string/boolean Page on success.
922 function clickImage($label, $x = 1, $y = 1, $additional = false) {
923 if (! ($form = &$this->_page
->getFormByImage(new SimpleByLabel($label)))) {
926 $success = $this->_load(
928 $form->submitImage(new SimpleByLabel($label), $x, $y, $additional));
929 return ($success ?
$this->getContent() : $success);
933 * Clicks the submit image by the name. Usually
934 * the alt tag or the nearest equivalent. The owning
935 * form will be submitted by this. Clicking outside of
936 * the boundary of the coordinates will result in
938 * @param string $name Name attribute of button.
939 * @param integer $x X-coordinate of imaginary click.
940 * @param integer $y Y-coordinate of imaginary click.
941 * @param hash $additional Additional form data.
942 * @return string/boolean Page on success.
945 function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
946 if (! ($form = &$this->_page
->getFormByImage(new SimpleByName($name)))) {
949 $success = $this->_load(
951 $form->submitImage(new SimpleByName($name), $x, $y, $additional));
952 return ($success ?
$this->getContent() : $success);
956 * Clicks the submit image by ID attribute. The owning
957 * form will be submitted by this. Clicking outside of
958 * the boundary of the coordinates will result in
960 * @param integer/string $id ID attribute of button.
961 * @param integer $x X-coordinate of imaginary click.
962 * @param integer $y Y-coordinate of imaginary click.
963 * @param hash $additional Additional form data.
964 * @return string/boolean Page on success.
967 function clickImageById($id, $x = 1, $y = 1, $additional = false) {
968 if (! ($form = &$this->_page
->getFormByImage(new SimpleById($id)))) {
971 $success = $this->_load(
973 $form->submitImage(new SimpleById($id), $x, $y, $additional));
974 return ($success ?
$this->getContent() : $success);
978 * Tests to see if an image exists with this
980 * @param string $label Image text.
981 * @return boolean True if present.
984 function isImage($label) {
985 return (boolean
)$this->_page
->getFormByImage(new SimpleByLabel($label));
989 * Submits a form by the ID.
990 * @param string $id The form ID. No submit button value
992 * @return string/boolean Page on success.
995 function submitFormById($id) {
996 if (! ($form = &$this->_page
->getFormById($id))) {
999 $success = $this->_load(
1002 return ($success ?
$this->getContent() : $success);
1006 * Finds a URL by label. Will find the first link
1007 * found with this link text by default, or a later
1008 * one if an index is given. The match ignores case and
1009 * white space issues.
1010 * @param string $label Text between the anchor tags.
1011 * @param integer $index Link position counting from zero.
1012 * @return string/boolean URL on success.
1015 function getLink($label, $index = 0) {
1016 $urls = $this->_page
->getUrlsByLabel($label);
1017 if (count($urls) == 0) {
1020 if (count($urls) < $index +
1) {
1023 return $urls[$index];
1027 * Follows a link by label. Will click the first link
1028 * found with this link text by default, or a later
1029 * one if an index is given. The match ignores case and
1030 * white space issues.
1031 * @param string $label Text between the anchor tags.
1032 * @param integer $index Link position counting from zero.
1033 * @return string/boolean Page on success.
1036 function clickLink($label, $index = 0) {
1037 $url = $this->getLink($label, $index);
1038 if ($url === false) {
1041 $this->_load($url, new SimpleGetEncoding());
1042 return $this->getContent();
1046 * Finds a link by id attribute.
1047 * @param string $id ID attribute value.
1048 * @return string/boolean URL on success.
1051 function getLinkById($id) {
1052 return $this->_page
->getUrlById($id);
1056 * Follows a link by id attribute.
1057 * @param string $id ID attribute value.
1058 * @return string/boolean Page on success.
1061 function clickLinkById($id) {
1062 if (! ($url = $this->getLinkById($id))) {
1065 $this->_load($url, new SimpleGetEncoding());
1066 return $this->getContent();
1070 * Clicks a visible text item. Will first try buttons,
1071 * then links and then images.
1072 * @param string $label Visible text or alt text.
1073 * @return string/boolean Raw page or false.
1076 function click($label) {
1077 $raw = $this->clickSubmit($label);
1079 $raw = $this->clickLink($label);
1082 $raw = $this->clickImage($label);
1088 * Tests to see if a click target exists.
1089 * @param string $label Visible text or alt text.
1090 * @return boolean True if target present.
1093 function isClickable($label) {
1094 return $this->isSubmit($label) ||
($this->getLink($label) !== false) ||
$this->isImage($label);