How to test file download by Selenium

In principle it’s a bit tricky to test file download by using PHPUnit and Selenium, because you can’t control the browser windows (such as the file download dialog) by Javascript. There have been other approaches to overcome this limitation, such as using AutoIT or AWT Robot/JInvoke (but it restricts your tests to Windows only or installing some new tools). This generic approach works in other languages such as Java, Ruby and Python, too.

This approach has also some limitations, it works only in Firefox (in theory it can be used in any browser that can be configured to auto-download to a specific directory without popping up the file download dialog). This requires also some detailed configuration and modifications to the both selenium-server and browser settings, but you don’t need to install any extra libraries or tools. I figured out this test case when I was required to automatically test downloading of a CSV Excel file (or in particular test that an empty file is not downloaded).

1) Create a Custom Firefox Profile

I found this instruction from here (you might need some alternations if you use for example Seamonkey or Firefox 2), but this worked at least on Firefox 7.0.1. First create a directory in your project root called for example Test/BrowserProfiles/firefox3.selenium/ and create a file called mimeTypes.rdf with the following contents:

<?xml version="1.0"?>
<RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#"
         xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <RDF:Description RDF:about="urn:root"
                   NC:en-US_defaultHandlersVersion="3" />
  <RDF:Description RDF:about="urn:mimetype:application/vnd.ms-excel"
                   NC:value="application/vnd.ms-excel"
                   NC:editable="true"
                   NC:description="CSV document"
                   NC:alwaysAsk="false"
                   NC:saveToDisk="true">
    <NC:fileExtensions>csv</NC:fileExtensions>
    <NC:fileExtensions>xls</NC:fileExtensions>
    <NC:fileExtensions>xlb</NC:fileExtensions>
    <NC:fileExtensions>xlt</NC:fileExtensions>
    <NC:handlerProp RDF:resource="urn:mimetype:handler:application/vnd.ms-excel"/>
  </RDF:Description>
  <RDF:Description RDF:about="urn:mimetype:handler:text/csv"
                   NC:saveToDisk="true"
                   NC:alwaysAsk="false" />
  <RDF:Description RDF:about="urn:mimetype:text/csv"
                   NC:fileExtensions="csv"
                   NC:description="CSV document"
                   NC:value="text/csv"
                   NC:editable="true"
                   NC:alwaysAsk="false"
                   NC:saveToDisk="true">
      <NC:handlerProp RDF:resource="urn:mimetype:handler:text/csv"/>
  </RDF:Description>
</RDF:RDF>

The key thing here is to set the two properties NC:alwaysAsk=”false” and NC:savetoDisk=”true” to get the file downloads to start automatically without popping the file download dialog. You can also change and set the NC:fileExtensions and mime types (NC:Value) to other than text/csv, too, to suit your needs.

The original approach was to copy your current whole Firefox profile directory and then edit the files, but this incorporated hundreds of unnecessary files to the project. Since we have a quite strict code review process and nobody wanted to line-by-line inspect binary cache files, I figured out that you don’t actually need any other files than the mimeTypes.rdf, and that you can delete most of the contents of that file without any harm.

2) Customize Selenium-Server to use the custom profile

Start the selenium-server by the following command line:

java -jar selenium-server-standalone-2.6.0.jar -firefoxProfileTemplate ~/workspace/myproject/test/selenium/browserProfiles/firefox3.selenium

Please edit the paths and jar versions according to your own setup.

3) Write the test case

This is a bit simplified version of the real test case I made, for clarity. The approach is to first check from the (Firefox) Downloads/ folder that the file does not exist there yet. Then the download-button is clicked, and assuming everything is set up as described above, Firefox should download the file without asking any questions. Then you can check again the Downloads/ folder for the filename and check if it has appeared.

<?php
require_once('PHPUnit_Extensions_SeleniumTestCase');
class DownloadCSVTest extends PHPUnit_Extensions_SeleniumTestCase
{
    /**
     * Test Case - Try load a CSV
     * Expected: CSV file was downloaded
     * Note! This works only in Firefox and if you use the custom browser profile
     * in Test/Selenium/BrowserProfiles/firefox.selenium to download the file
     * This test assumes that your Firefox download directory is ~/Downloads/ (Linux)
     * Note! You need to launch your selenium-server to use the firefox profile found in
     * java -jar selenium-server-standalone-2.6.0.jar -firefoxProfileTemplate ~/workspace/ServiceProductionAdmin/Test/Selenium/BrowserProfiles/  firefox.selenium/
     * @see http://kb.mozillazine.org/File%5Ftypes%5Fand%5Fdownload%5Factions
     * @author Antti Hätinen <antti.hatinen@phz.fi>
     */
    public function testCSVDownload()
    {
	//this works only in Firefox, so use browser type *chrome
        self::$browser = "*chrome";
        //clear up
        $filename = getenv("HOME") . '/Downloads/fileToBeDownloaded.csv';
        @unlink($filename);

        //Download
        $this->focusAndClick("button:contains('Download')");
        sleep(3); //wait for download to complete
        $this->assertTrue(file_exists($filename),"CSV was not downloaded to '$filename' . Check that you have configured the Selenium-Server to use the custom firefox profile in myproject/Test/Selenium/BrowserProfiles/firefox.selenium/");

        //delete the file if it exists
        unlink($filename);
    }
}

Then you can run the test

 phpunit --stop-on-failure --filter testCSVDownload DownloadCSVTest.php

And hopefully all tests pass (sooner or later :).