Perform tests on a downloaded file with Behat and Guzzle
— by Andrea Salini, Web Developer at Caendra Inc.
The issue
As part of the requirements of our Symfony application, a user should be able to download a server-side generated PDF file. In our acceptance tests we wanted to verify that the file downloaded by the user had the expected content type.
The platform we use for acceptance testing is based on the battle-tested combination of Selenium browser automation software and Behat/Mink testing suite, all on top of the PHP Symfony Framework. Unfortunately, this combination doesn’t provide a straightforward solution to perform assertions on downloaded files.
The solution
Lucky for us, Behat can be extended with the use of custom step definitions. Our solution performs the HTTP file download request programmatically in a new Behat custom step, which is written using good old PHP code. The request will be performed using Guzzle, a nice library wrapping around cURL. This gives us the power to perform any kind of assertions on the response. For a detailed explanation on what you need to do in order to achieve this result, follow us in this journey, otherwise you can skip to the next section.
In your Behat context file start by defining a new custom step.
/**
* @Then /^I download a proper PDF file$/
*/
public function iDownloadAPDFFile()
{
// the custom code will go here..
}
Inside this step we need to get the session name, its id and our test server domain. As you will see later, this information will be necessary in order to build a valid session cookie and perform an authenticated HTTP request.
There is no single universal method for this task. In our case, we conveniently use the Symfony container to access the Session object details, as we forge a new Session for every test scenario directly from Behat (in order to bypass login screens and reduce the execution time of our tests). Another approach would be to get the cookie details from the Selenium session.
// access the Symfony Session object from the container
$container = $this->kernel->getContainer();
$session = $container->get('session');$cookieName = $session->getName();
$sessionId = $session->getId();$baseUrl = $this->getMinkParameter('base_url');
$domain = parse_url($baseUrl)['host'];
Notice that we can get an instance of the Symfony container as our context class implements the Behat\Symfony2Extension\Context\KernelAwareContext
interface. This may not be your case.
Then we use those parameters to manually set up a CookieJar object which will store our session cookie.
$cookieJar = \GuzzleHttp\Cookie\CookieJar::fromArray(array(
$cookieName => $sessionId,
), $domain);
We can now create an HTTP client object with a reference to the newly created CookieJar.
$guzzle = new \GuzzleHttp\Client(array(
'timeout' => 10,
'cookies' => $cookieJar,
));
At this point, we can use the Guzzle client to send the HTTP GET request. In our example the URL is a dummy constant value but you will probably need some logic to generate the proper URL.
$url = '...'; // here you will provide the proper URL
$response = $guzzle->get($url);
Finally we can perform the required checks on the HTTP response object. For example, we can check that the Content-Type header matches a PDF file.
$driver = $this->getSession()->getDriver();
$contentType = $response->getHeader('Content-Type')[0];
if ($contentType !== 'application/pdf') {
throw new \Behat\Mink\Exception\ExpectationException('The content type of the downloaded file is not correct.', $driver);
}
You can then use this new step in your scenarios, as in the next example.
Scenario: Properly download a PDF file
Given some conditions
Then I download a proper PDF file
If the downloaded file is not a PDF, an exception will be raised and the step will fail, otherwise the step will complete with success.
For your convenience here we publish also the complete custom step definition. Feel free to use it in your projects too!
/**
* @Given /^I download a proper PDF file$/
*/
public function iDownloadAPDFFile()
{
// Get the container
$container = $this->kernel->getContainer();
$session = $container->get('session'); // Get the session information from Symfony
$cookieName = $session->getName();
$sessionId = $session->getId(); // Find your cookie domain
$baseUrl = $this->getMinkParameter('base_url');
$domain = parse_url($baseUrl)['host']; $cookieJar = \GuzzleHttp\Cookie\CookieJar::fromArray(array(
$cookieName => $sessionId,
), $domain); $guzzle = new \GuzzleHttp\Client(array(
'timeout' => 10,
'cookies' => $cookieJar,
));
// Provide here the proper download URL
$url = '...'; $response = $guzzle->get($url); $driver = $this->getSession()->getDriver();
$contentType = $response->getHeader('Content-Type')[0]; if ($contentType !== 'application/pdf') {
throw new \Behat\Mink\Exception\ExpectationException('The content type of the downloaded file is not correct.', $driver);
}
}
Software used
As future reference, here is the list of the software packages that we used to build this solution and their corresponding version.
- symfony/symfony: 2.8
- behat/behat:3.3
- behat/symfony2-extension: 2.1
- behat/mink-extension: 1.7
- behat/mink-selenium2-driver: 1.3
- guzzlehttp/guzzle: 6.3
References
We wanted to say thank you to Joeri Verdeyen for is very helpful article which inspired our research, though his work is based on older versions of the Behat testing suite.
Thanks for reading! Feel free to drop a comment down here and let us know what you think.