Version: current

      1 <?php
      2
      3 /**
      4  * Sapphire-specific testing object designed to support functional testing of your web app.  It simulates get/post
      5  * requests, form submission, and can validate resulting HTML, looking up content by CSS selector.
      6  *
      7  * The example below shows how it works.
      8  *
      9  * <code>
     10  *   function testMyForm() {
     11  *     // Visit a URL
     12  *     $this->get("your/url");
     13  *
     14  *     // Submit a form on the page that you get in response
     15  *     $this->submitForm("MyForm_ID",  array("Email" => "invalid email ^&*&^"));
     16  *
     17  *     // Validate the content that is returned
     18  *     $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid."));
     19  *  }
     20  * </code>
     21  *
     22  * @package sapphire
     23  * @subpackage testing
     24  */
     25 class FunctionalTest extends SapphireTest {
     26     /**
     27      * Set this to true on your sub-class to disable the use of themes in this test.
     28      * This can be handy for functional testing of modules without having to worry about whether a user has changed
     29      * behaviour by replacing the theme.
     30      */
     31     static $disable_themes = false;
     32
     33     /**
     34      * Set this to true on your sub-class to use the draft site by default for every test in this class.
     35      */
     36     static $use_draft_site = false;
     37
     38     protected $mainSession = null;
     39
     40     /**
     41      * CSSContentParser for the most recently requested page.
     42      */
     43     protected $cssParser = null;
     44
     45     /**
     46      * If this is true, then 30x Location headers will be automatically followed.
     47      * If not, then you will have to manaully call $this->mainSession->followRedirection() to follow them.  However, this will let you inspect
     48      * the intermediary headers
     49      */
     50     protected $autoFollowRedirection = true;
     51
     52     /**
     53      * Returns the {@link Session} object for this test
     54      */
     55     function session() {
     56         return $this->mainSession->session();
     57     }
     58
     59     function setUp() {
     60         parent::setUp();
     61         $this->mainSession = new TestSession();
     62
     63         // Disable theme, if necessary
     64         if($this->stat('disable_themes')) SSViewer::set_theme(null);
     65
     66         // Switch to draft site, if necessary
     67         if($this->stat('use_draft_site')) {
     68             $this->useDraftSite();
     69         }
     70     }
     71
     72     function tearDown() {
     73         parent::tearDown();
     74         unset($this->mainSession);
     75     }
     76
     77     /**
     78      * Submit a get request
     79      * @uses Director::test()
     80      */
     81     function get($url, $session = null, $headers = null, $cookies = null) {
     82         $this->cssParser = null;
     83         $response = $this->mainSession->get($url, $session, $headers, $cookies);
     84         if($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) $response = $this->mainSession->followRedirection();
     85         return $response;
     86     }
     87
     88     /**
     89      * Submit a post request
     90      * @uses Director::test()
     91      */
     92     function post($url, $data, $headers = null, $session = null, $body = null, $cookies = null) {
     93         $this->cssParser = null;
     94         $response = $this->mainSession->post($url, $data, $headers, $session, $body, $cookies);
     95         if($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) $response = $this->mainSession->followRedirection();
     96         return $response;
     97     }
     98
     99     /**
    100      * Submit the form with the given HTML ID, filling it out with the given data.
    101      * Acts on the most recent response
    102      */
    103     function submitForm($formID, $button = null, $data = array()) {
    104         $this->cssParser = null;
    105         $response = $this->mainSession->submitForm($formID, $button, $data);
    106         if($this->autoFollowRedirection && is_object($response) && $response->getHeader('Location')) $response = $this->mainSession->followRedirection();
    107         return $response;
    108     }
    109
    110     /**
    111      * Return the most recent content
    112      */
    113     function content() {
    114         return $this->mainSession->lastContent();
    115     }
    116
    117     /**
    118      * Find an attribute in a SimpleXMLElement object by name.
    119      * @param SimpleXMLElement object
    120      * @param string $attribute Name of attribute to find
    121      * @return SimpleXMLElement object of the attribute
    122      */
    123     function findAttribute($object, $attribute) {
    124         $found = false;
    125         foreach($object->attributes() as $a => $b) {
    126             if($a == $attribute) {
    127                 $found = $b;
    128             }
    129         }
    130         return $found;
    131     }
    132
    133     /**
    134      * Return a CSSContentParser for the most recent content.
    135      */
    136     function cssParser() {
    137         if(!$this->cssParser) $this->cssParser = new CSSContentParser($this->mainSession->lastContent());
    138         return $this->cssParser;
    139     }
    140
    141     /**
    142      * Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
    143      * The given CSS selector will be applied to the HTML of the most recent page.  The content of every matching tag
    144      * will be examined. The assertion fails if one of the expectedMatches fails to appear.
    145      *
    146      * Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
    147      *
    148      * @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
    149      * @param array|string $expectedMatches The content of at least one of the matched tags
    150      * @throws PHPUnit_Framework_AssertionFailedError
    151      * @return boolean
    152      */
    153     function assertPartialMatchBySelector($selector, $expectedMatches) {
    154         if(is_string($expectedMatches)) $expectedMatches = array($expectedMatches);
    155
    156         $items = $this->cssParser()->getBySelector($selector);
    157
    158         $actuals = array();
    159         if($items) foreach($items as $item) $actuals[trim(preg_replace("/[ \n\r\t]+/", " ", $item. ''))] = true;
    160
    161         foreach($expectedMatches as $match) {
    162             if(!isset($actuals[$match])) {
    163                 throw new PHPUnit_Framework_AssertionFailedError(
    164                     "Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "'\n\n"
    165                     . "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'"
    166                 );
    167                 return false;
    168             }
    169         }
    170
    171         return true;
    172     }
    173
    174     /**
    175      * Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
    176      * The given CSS selector will be applied to the HTML of the most recent page.  The full HTML of every matching tag
    177      * will be examined. The assertion fails if one of the expectedMatches fails to appear.
    178      *
    179      * Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
    180      *
    181      * @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
    182      * @param array|string $expectedMatches The content of *all* matching tags as an array
    183      * @throws PHPUnit_Framework_AssertionFailedError
    184      * @return boolean
    185      */
    186     function assertExactMatchBySelector($selector, $expectedMatches) {
    187         if(is_string($expectedMatches)) $expectedMatches = array($expectedMatches);
    188
    189         $items = $this->cssParser()->getBySelector($selector);
    190
    191         $actuals = array();
    192         if($items) foreach($items as $item) $actuals[] = trim(preg_replace("/[ \n\r\t]+/", " ", $item. ''));
    193
    194         if($expectedMatches != $actuals) {
    195             throw new PHPUnit_Framework_AssertionFailedError(
    196                 "Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "'\n\n"
    197                 . "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'"
    198             );
    199             return false;
    200         }
    201
    202         return true;
    203     }
    204
    205     /**
    206      * Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
    207      * The given CSS selector will be applied to the HTML of the most recent page.  The content of every matching tag
    208      * will be examined. The assertion fails if one of the expectedMatches fails to appear.
    209      *
    210      * Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
    211      *
    212      * @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
    213      * @param array|string $expectedMatches The content of at least one of the matched tags
    214      * @throws PHPUnit_Framework_AssertionFailedError
    215      * @return boolean
    216      */
    217     function assertPartialHTMLMatchBySelector($selector, $expectedMatches) {
    218         if(is_string($expectedMatches)) $expectedMatches = array($expectedMatches);
    219
    220         $items = $this->cssParser()->getBySelector($selector);
    221
    222         $actuals = array();
    223         if($items) foreach($items as $item) $actuals[$item->asXML()] = true;
    224
    225         foreach($expectedMatches as $match) {
    226             if(!isset($actuals[$match])) {
    227                 throw new PHPUnit_Framework_AssertionFailedError(
    228                     "Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "'\n\n"
    229                     . "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'"
    230                 );
    231                 return false;
    232             }
    233         }
    234
    235         return true;
    236     }
    237
    238     /**
    239      * Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
    240      * The given CSS selector will be applied to the HTML of the most recent page.  The full HTML of every matching tag
    241      * will be examined. The assertion fails if one of the expectedMatches fails to appear.
    242      *
    243      * Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
    244      *
    245      * @param string $selector A basic CSS selector, e.g. 'li.jobs h3'
    246      * @param array|string $expectedMatches The content of *all* matched tags as an array
    247      * @throws PHPUnit_Framework_AssertionFailedError
    248      * @return boolean
    249      */
    250     function assertExactHTMLMatchBySelector($selector, $expectedMatches) {
    251         $items = $this->cssParser()->getBySelector($selector);
    252
    253         $actuals = array();
    254         if($items) foreach($items as $item) $actuals[] = $item->asXML();
    255
    256         if($expectedMatches != $actuals) {
    257             throw new PHPUnit_Framework_AssertionFailedError(
    258                 "Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "'\n\n"
    259                 . "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'"
    260             );
    261         }
    262     }
    263
    264     /**
    265      * Log in as the given member
    266      * @param $member The ID, fixture codename, or Member object of the member that you want to log in
    267      */
    268     function logInAs($member) {
    269         if(is_object($member)) $memberID = $member->ID;
    270         elseif(is_numeric($member)) $memberID = $member;
    271         else $memberID = $this->idFromFixture('Member', $member);
    272
    273         $this->session()->inst_set('loggedInAs', $memberID);
    274     }
    275
    276     /**
    277      * Use the draft (stage) site for testing.
    278      * This is helpful if you're not testing publication functionality and don't want "stage management" cluttering your test.
    279      */
    280     function useDraftSite() {
    281         $this->session()->inst_set('readingMode', 'Stage.Stage');
    282         $this->session()->inst_set('unsecuredDraftSite', true);
    283     }
    284
    285     /**
    286      * Return a static variable from this class.
    287      * Gets around PHP's lack of late static binding.
    288      */
    289     function stat($varName) {
    290         $className = get_class($this);
    291         return eval("return {$className}::\$$varName;");
    292     }
    293 }
    294

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -