API CHANGE: Added Functional Test, a sub-class of SapphireTest, designed for testing end-user functionality such as page display and form submission

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@54633 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2008-05-15 07:15:33 +00:00
parent 4cb354b79a
commit ada3a51b4d
4 changed files with 279 additions and 1 deletions

View File

@ -0,0 +1,57 @@
<?php
/**
* CSSContentParser enables parsing & assertion running of HTML content via CSS selectors.
*
* It works by converting the content to XHTML using tidy, rewriting the CSS selectors as XPath queries, and executing
* those using SimpeXML.
*
* It was built to facilitate testing using PHPUnit and contains a number of assert methods that will throw PHPUnit
* assertion exception when applicable.
*/
class CSSContentParser extends Object {
protected $simpleXML = null;
function __construct($content) {
$CLI_content = escapeshellarg($content);
$tidy = `echo $CLI_content | tidy -n -q -utf8 -asxhtml`;
$tidy = str_replace('xmlns="http://www.w3.org/1999/xhtml"','',$tidy);
$tidy = str_replace('&#160;','',$tidy);
$this->simpleXML = new SimpleXMLElement($tidy);
}
/**
* Returns a number of SimpleXML elements that match the given CSS selector.
* Currently the selector engine only supports querying by tag, id, and classs
*/
function getBySelector($selector) {
$xpath = $this->selector2xpath($selector);
return $this->simpleXML->xpath($xpath);
}
/**
* Converts a CSS selector into an equivalent xpath expression.
* Currently the selector engine only supports querying by tag, id, and classs
*/
function selector2xpath($selector) {
$parts = preg_split('/\\s+/', $selector);
$xpath = "";
foreach($parts as $part) {
if(preg_match('/^([A-Za-z][A-Za-z0-9]*)/', $part, $matches)) {
$xpath .= "//$matches[1]";
} else {
$xpath .= "//*";
}
$xfilters = array();
if(preg_match('/#([^#.\[]+)/', $part, $matches)) {
$xfilters[] = "@id='$matches[1]'";
}
if(preg_match('/\.([^#.\[]+)/', $part, $matches)) {
$xfilters[] = "contains(@class,'$matches[1]')";
}
if($xfilters) $xpath .= '[' . implode(',', $xfilters) . ']';
}
return $xpath;
}
}

175
testing/FunctionalTest.php Normal file
View File

@ -0,0 +1,175 @@
<?php
/**
* Sapphire-specific testing object designed to support functional testing of your web app. It simulates get/post
* requests, form submission, and can validate resulting HTML, looking up content by CSS selector.
*
* The example below shows how it works.
*
* <code>
* function testMyForm() {
* // Visit a URL
* $this->get("your/url");
*
* // Submit a form on the page that you get in response
* $this->submitForm("MyForm_ID", array("Email" => "invalid email ^&*&^"));
*
* // Validate the content that is returned
* $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid."));
* }
* </code>
*/
class FunctionalTest extends SapphireTest {
protected $mainSession = null;
/**
* CSSContentParser for the most recently requested page.
*/
protected $cssParser = null;
function setUp() {
parent::setUp();
$this->mainSession = new TestSession();
}
function tearDown() {
parent::tearDown();
$this->mainSession = null;
}
/**
* Submit a get request
*/
function get($url) {
$this->cssParser = null;
return $this->mainSession->get($url);
}
/**
* Submit a post request
*/
function post($url, $data) {
$this->cssParser = null;
return $this->mainSession->post($url, $data);
}
/**
* Submit the form with the given HTML ID, filling it out with the given data.
* Acts on the most recent response
*/
function submitForm($formID, $button = null, $data = array()) {
$this->cssParser = null;
return $this->mainSession->submitForm($formID, $button, $data);
}
/**
* Return the most recent content
*/
function content() {
return $this->mainSession->lastContent();
}
/**
* Return a CSSContentParser for the most recent content.
*/
function cssParser() {
if(!$this->cssParser) $this->cssParser = new CSSContentParser($this->mainSession->lastContent());
return $this->cssParser;
}
/**
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
*
* The given CSS selector will be applied to the HTML of the most recent page. The content of every matching tag
* will be examined.
*
* The assertion fails if one of the expectedMatches fails to appear.
*
* Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
*/
function assertPartialMatchBySelector($selector, $expectedMatches) {
$items = $this->cssParser()->getBySelector($selector);
foreach($items as $item) $actuals[$item . ''] = true;
foreach($expectedMatches as $match) {
if(!isset($actuals[$match])) {
throw new PHPUnit_Framework_AssertionFailedError(
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "\n\n"
. "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'"
);
return;
}
}
}
/**
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
*
* The given CSS selector will be applied to the HTML of the most recent page. The full HTML of every matching tag
* will be examined.
*
* The assertion fails if one of the expectedMatches fails to appear.
*
* Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
*/
function assertExactMatchBySelector($selector, $expectedMatches) {
$items = $this->cssParser()->getBySelector($selector);
foreach($items as $item) $actuals[] = $item . '';
if($expectedMatches != $actuals) {
throw new PHPUnit_Framework_AssertionFailedError(
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "\n\n"
. "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'"
);
}
}
/**
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
*
* The given CSS selector will be applied to the HTML of the most recent page. The content of every matching tag
* will be examined.
*
* The assertion fails if one of the expectedMatches fails to appear.
*
* Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
*/
function assertPartialHTMLMatchBySelector($selector, $expectedMatches) {
$items = $this->cssParser()->getBySelector($selector);
foreach($items as $item) $actuals[$item->asXML()] = true;
foreach($expectedMatches as $match) {
if(!isset($actuals[$match])) {
throw new PHPUnit_Framework_AssertionFailedError(
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "\n\n"
. "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'"
);
return;
}
}
}
/**
* Assert that the most recently queried page contains a number of content tags specified by a CSS selector.
*
* The given CSS selector will be applied to the HTML of the most recent page. The full HTML of every matching tag
* will be examined.
*
* The assertion fails if one of the expectedMatches fails to appear.
*
* Note: &nbsp; characters are stripped from the content; make sure that your assertions take this into account.
*/
function assertExactHTMLMatchBySelector($selector, $expectedMatches) {
$items = $this->cssParser()->getBySelector($selector);
foreach($items as $item) $actuals[] = $item->asXML();
if($expectedMatches != $actuals) {
throw new PHPUnit_Framework_AssertionFailedError(
"Failed asserting the CSS selector '$selector' has an exact match to the expected elements:\n'" . implode("'\n'", $expectedMatches) . "\n\n"
. "Instead the following elements were found:\n'" . implode("'\n'", $actuals) . "'"
);
}
}
}

View File

@ -47,7 +47,6 @@ class TestSession {
$postVars = array();
parse_str($submission->_encode(), $postVars);
Debug::show($postVars);
return $this->post($url, $postVars);
}
@ -84,6 +83,11 @@ class TestSession {
return $this->lastResponse->getBody();
}
function cssParser() {
return new CSSContentParser($this->lastContent());
}
/**
* Get the last response as a SimplePage object
*/

View File

@ -0,0 +1,42 @@
<?php
class CSSContentParserTest extends SapphireTest {
function testSelector2xpath() {
$parser = new CSSContentParser("<html><head><title>test</title></head><body><p>test</p></body></html>");
$this->assertEquals("//div[@id='UserProfile']//label", $parser->selector2xpath("div#UserProfile label"));
$this->assertEquals("//div", $parser->selector2xpath("div"));
$this->assertEquals("//div[contains(@class,'test')]", $parser->selector2xpath("div.test"));
$this->assertEquals("//*[@id='UserProfile']//div[contains(@class,'test')]//*[contains(@class,'other')]//div[@id='Item']",
$parser->selector2xpath("#UserProfile div.test .other div#Item"));
}
function testGetBySelector() {
$parser = new CSSContentParser(<<<HTML
<html>
<head>
<title>test</title>
</head>
<body>
<div id="A" class="one two three">
<p class="other">result</p>
</div>
<p>test</p>
</body>
</html>"
HTML
);
$result = $parser->getBySelector('div.one');
$this->assertEquals("A", $result[0]['id'].'');
$result = $parser->getBySelector('div.two');
$this->assertEquals("A", $result[0]['id'].'');
$result = $parser->getBySelector('div.three');
$this->assertEquals("A", $result[0]['id'].'');
$result = $parser->getBySelector('div#A p.other');
$this->assertEquals("result", $result[0] . '');
$result = $parser->getBySelector('#A .other');
$this->assertEquals("result", $result[0] . '');
}
}