How to test file download in Behat
Asked Answered
D

4

8

There is this new Export functionality developed on this application and I'm trying to test it using Behat/Mink. The issue here is when I click on the export link, the data on the page gets exported in to a CSV and gets saved under /Downloads but I don't see any response code or anything on the page.

Is there a way I can export the CSV and navigate to the /Downloads folder to verify the file?

Debris answered 24/7, 2014 at 9:42 Comment(1)
any justification for the downvote?? that is an absolute joke isn't it..daft really..Debris
H
3

Assuming you are using the Selenium driver you could "click" on the link and $this->getSession()->wait(30) until the download is finished and then check the Downloads folder for the file.

That would be the simplest solution. Alternatively you can use a proxy, like BrowserMob, to watch all requests and then verify the response code. But that would be a really painful path for that alone.

The simplest way to check that the file is downloaded would be to define another step with a basic assertion.

/**
 * @Then /^the file ".+" should be downloaded$/
 */
public function assertFileDownloaded($filename) 
{
    if (!file_exists('/download/dir/' . $filename)) {
        throw new Exception();
    }
}

This might be problematic in situations when you download a file with the same name and the browser saves it under a different name. As a solution you can add a @BeforeScenario hook to clear the list of the know files.

Another issue would be the download dir itself – it might be different for other users / machines. To fix that you could pass the download dir in your behat.yml as a argument to the context constructor, read the docs for that.

But the best approach would be to pass the configuration to the Selenium specifying the download dir to ensure it's always clear and you know exactly where to search. I am not certain how to do that, but from the quick googling it seems to be possible.

Hindustani answered 24/7, 2014 at 10:46 Comment(2)
Hi Ian, how do you check the download folder. I'm using the headless browser (phantomJS)Debris
I've updated the answer. I don't think you'll be able to do this without an actual browser. I would assume that following the link using the headless browser would just return the file contents, I might be wrong though – never tried that. You can use more than one session with different drivers. Check if the page content after following the link is your CSV (then downloading is working), or setup different sessions and do it the proper way.Hindustani
C
2

Checkout this blog: https://www.jverdeyen.be/php/behat-file-downloads/

The basic idea is to copy the current session and do the request with Guzzle. After that you can check the response any way you like.

class FeatureContext extends \Behat\Behat\Context\BehatContext {

   /**
    * @When /^I try to download "([^"]*)"$/
    */
    public function iTryToDownload($url)
    {
        $cookies = $this->getSession()->getDriver()->getWebDriverSession()->getCookie('PHPSESSID');
        $cookie = new \Guzzle\Plugin\Cookie\Cookie();
        $cookie->setName($cookies[0]['name']);
        $cookie->setValue($cookies[0]['value']);
        $cookie->setDomain($cookies[0]['domain']);

        $jar = new \Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar();
        $jar->add($cookie);

        $client = new \Guzzle\Http\Client($this->getSession()->getCurrentUrl());
        $client->addSubscriber(new \Guzzle\Plugin\Cookie\CookiePlugin($jar));

        $request = $client->get($url);
        $this->response = $request->send();
    }

    /**
    * @Then /^I should see response status code "([^"]*)"$/
    */
    public function iShouldSeeResponseStatusCode($statusCode)
    {
        $responseStatusCode = $this->response->getStatusCode();

        if (!$responseStatusCode == intval($statusCode)) {
            throw new \Exception(sprintf("Did not see response status code %s, but %s.", $statusCode, $responseStatusCode));
        }
    }

    /**
    * @Then /^I should see in the header "([^"]*)":"([^"]*)"$/
    */
    public function iShouldSeeInTheHeader($header, $value)
    {
        $headers = $this->response->getHeaders();
        if ($headers->get($header) != $value) {
            throw new \Exception(sprintf("Did not see %s with value %s.", $header, $value));
        }
    }
}
Cacie answered 25/1, 2015 at 18:54 Comment(2)
Thanks very much for your response tscheepers. I will try that soon.Debris
what driver are you using? I remember reading it somewhere that the status codes are not supported using PhanomJS driver.Debris
F
0

Little modified iTryToDownload() function with using all cookies:

public function iTryToDownload($link) {
$elt = $this->getSession()->getPage()->findLink($link);
if($elt) {
  $value = $elt->getAttribute('href');
  $driver = $this->getSession()->getDriver();
  if ($driver instanceof \Behat\Mink\Driver\Selenium2Driver) {
    $ds = $driver->getWebDriverSession();
    $cookies = $ds->getAllCookies();
  } else {
    throw new \InvalidArgumentException('Not Selenium2Driver');
  }

  $jar = new \Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar();
  for ($i = 0; $i < count($cookies); $i++) {
    $cookie = new \Guzzle\Plugin\Cookie\Cookie();
    $cookie->setName($cookies[$i]['name']);
    $cookie->setValue($cookies[$i]['value']);
    $cookie->setDomain($cookies[$i]['domain']);
    $jar->add($cookie);
  }
  $client = new \Guzzle\Http\Client($this->getSession()->getCurrentUrl());
  $client->addSubscriber(new \Guzzle\Plugin\Cookie\CookiePlugin($jar));

  $request = $client->get($value);
  $this->response = $request->send();
} else {
  throw new \InvalidArgumentException(sprintf('Could not evaluate: "%s"', $link));
}
}
Forepart answered 10/7, 2015 at 15:14 Comment(0)
M
0

In project we have problem that we have two servers: one with web drivers and browsers and second with selenium hub. As result we decide to use curl request for fetching headers. So I wrote function which would called in step definition. Below you can find a function which use a standard php functions: curl_init()

/**
 * @param $request_url
 * @param $userToken
 * @return bool
 * @throws Exception
 */
private function makeCurlRequestForDownloadCSV($request_url, $userToken)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $request_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $headers = [
        'Content-Type: application/json',
        "Authorization: Bearer {$userToken}"
    ];
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    $output = curl_exec($ch);
    $info = curl_getinfo($ch);
    $output .= "\n" . curl_error($ch);
    curl_close($ch);

    if ($output === false || $info['http_code'] != 200 || $info['content_type'] != "text/csv; charset=UTF-8") {
        $output = "No cURL data returned for $request_url [" . $info['http_code'] . "]";
        throw new Exception($output);
    } else {
        return true;
    }
}

How you can see I have authorization by token. If you want to understand what headers you should use you should download file manual and look request and response in browser's tab network

Multiflorous answered 21/9, 2017 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.