Ask your Symfony questions! Pay money and get answers fast! (more info)

How to loop over every link on a page in functional tests? Symfony

In a functional test, is it possible to get all the links on the page, and then loop over them? I'd like to click on each link and look for 404 errors. In pseudo code, I'd like something like this:

$browser->get('/login')->
click('.signin_login', array('signin' => array('username' => $username, 'password' => $password)))->
with('form')->
hasErrors(false)->
with('user')->begin()->
isAuthenticated(true)->
hasCredential(array('tastingnotes.user'))->
end()->
get('restaurant_award/index')->
click('a', array(), array('position' => $browser->incrementPosition('a')))->
with('response')->begin()->
isStatusCode(200)->
end()
;


What I'm not clear how to do is the magic incrementPosition() method.

I could do something very ugly, like use debug() to get the HTML and then use regex to count the number of links and then go into a loop and in the loop call all of the above code, incrementing a counter that is handled by the loop. But I'd like to believe there is a more graceful way to handle this.

Answers (1)

2011-03-23

Jakub Zalas answers:

You can quite easily implement your own testers:


class zTesterResponse extends sfTesterResponse
{
/**
* @return sfTestFunctionalBase|sfTester
*/
public function assertNo404Links()
{
// do magic things here
// ...
// you can use $this->browser->getResponse() or $this->response

return $this->getObjectToReturn();
}
}


And than use them:


$browser = new sfTestFunctional(new sfBrowser());
$browser->setTester('response', 'zTesterResponse');

$browser->
[...]
with('response')->assertNo404s()


If you extend sfTesterResponse you'l have content in the $this->response property. You can also access it with $this->browser->getResponse();

I'm not sure however, if it's a good practice to manipulate browser in your tester (getters are fine).

Maybe it's better to extend sfTestFunctional and add your method there. If you decide to extend sfTestFunctional you'll be also able to access your browser with protected $this->browser property.



Lawrence Krubner comments:

But how do I reference links on a page via sfBrowser? That is what I am asking. I'd like to click() each link, test for 404 (or for 200) then hit the back() method, then click() the next link.


Jakub Zalas comments:

Didn't test it but code below should do the job:

class zTestFunctional extends sfTestFuncional
{
public function assertNo404s()
{
$nodes = $this->browser->getResponseDomCssSelector()->matchAll('a')->getNodes();
foreach ($nodes as $node)
{
$this->click($node);
$this->with('response')->isStatusCode(200);
$this->back();
}

return $this;
}
}


Example usage:


$browser->get('/login')->
click('.signin_login', array('signin' => array('username' => $username, 'password' => $password)))->
with('form')->
hasErrors(false)->
with('user')->begin()->
isAuthenticated(true)->
hasCredential(array('tastingnotes.user'))->
end()->
get('restaurant_award/index')->
assertNo404s()
;


Jakub Zalas comments:

Of course you should use zTestFunctional instead of sfTestFunctional.


Lawrence Krubner comments:

Hmm, might work. Except now, using your code, I get "functional no controller found".

I used "php -l" on all the files to be sure there were no syntax errors anywhere.

I'm calling:

./symfony test:functional tastingnotes home_page_test

and:

$browser->get('/login')->
click('.signin_login', array('signin' => array('username' => $username, 'password' => $password)))->
with('form')->
hasErrors(false)->
with('user')->begin()->
isAuthenticated(true)->
hasCredential(array('tastingnotes.user'))->
end()->
get('wine/index')->
assertNo404s()
end()
;



Lawrence Krubner comments:

Strange. If I run:

./symfony test:functional tastingnotes

The other 23 files run, and their tests are ok, but the home_page_test never runs. I thought maybe this was a naming convention, so changed the file name to homePageTest.php and ran :

./symfony test:functional tastingnotes homePageTest

but I still get:


functional no controller found


Lawrence Krubner comments:

Changed the code to:

class msTestFunctional extends sfTestFuncional
{

public function assertNo404s()
{
$nodes = $this->browser->getResponseDomCssSelector()->matchAll('a')->getNodes();

print_r($nodes);

foreach ($nodes as $node)
{
$this->click($node);
$this->with('response')->isStatusCode(200);
$this->back();
}

return $this;
}


but I never get to the print_r statement.


Test file is:


<?php

include(dirname(__FILE__).'/../../bootstrap/functional.php');

$browser = new msTestFunctional(new sfBrowser());

$username = 'xxxx';
$password = 'xxxx';

$browser->get('/login')->
click('.signin_login', array('signin' => array('username' => $username, 'password' => $password)))->
with('form')->
hasErrors(false)->
with('user')->begin()->
isAuthenticated(true)->
hasCredential(array('tastingnotes.user'))->
end()->
get('wine/index')->
assertNo404s()
end()
;


Lawrence Krubner comments:

Okay, that was a naming convention, regarding the test file. I figured it out and your code works great. Thanks.

Sad to say I get "Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 311296 bytes) in /home/dev/tastingnotes/cache/tastingnotes/test/config/config_routing.yml.php on line 208
"

I'll try to figure out a workaround for that.


Jakub Zalas comments:

Where's your msTestFunctional class located? Is it autoloaded? If not did you require it with require or require_once?

In jobeet there's an example of creating custom functional test class: http://www.symfony-project.org/jobeet/1_2/Propel/en/09#chapter_09_test_data

I think you're close.

While running a test you shouldn't include a "Test" word. It should be:

./symfony test:functional tastingnotes homePage