Remote file uploads with Selenium & PHP

When testing file uploading functionality we have to consider two scenarios... running browsers locally and running them remotely. Locally there is no problem: test browsers have access to the files the tests specify. When running the same tests remotely, with the browser running on a different machine than the test programs, we run into a significant problem: the files we specify to upload are not available to the browser to upload! So, simply sending the name of the file to upload will not work:

    public function testFileUpload() {
        $filebox = $this->byName('myfile');
        $this->sendKeys($filebox, "./kirk.jpg");
        $this->byId('submit')->submit();

        $this->assertTextPresent("kirk.jpg (image/jpeg)");
    }

The remote browser will not be able to do whatever it does with the file, in this case printing out the file name and content type. Selenium 2 solves this problem by providing a method to upload the file to the server, and then giving the page the remote path the the file, and it does this transparently. For local tests you just use the sendKeys method to enter text into the file upload form element. For remote tests you do the same, but while setting up the tests you call the fileDetector method in order to let the WebDriver system know that a local file is being added rather than just the name of the file. While all the Selenium WebDriver language bindings developed by the Selenium project have this functionality exposed (on Java and Ruby, see this tutorial, and for more information on Ruby, see this discussion), the PHP bindings do not. In order to facilitate remote testing in the Sauce Labs Selenium 2 Cloud, this functionality has been added to Sausage, allowing you to use PHPUnit to run your tests both locally and in the Sauce Cloud. The crux of the solution is to give the system a way to know that a file might be uploaded, and how to discern this, by passing it a function that will be called when trying to send keys to form elements. This function takes a string and returns something truthy if it should be interpreted as the name of a file, however that is to be determined on your system. The most basic form, obviously, simply tests for the existence of the string as a file on the local file system. This function is then sent to the test library through a call to fileDetector:

    $this->fileDetector(function($filename) {
        if(file_exists($filename)) {
            return $filename;
        } else {
            return NULL;
        }
    });

If, when sending a string to a form element, the supplied function returns true, the system will, before changing the value of the function, transparently read the file, encode it in Base64, and send it to the Selenium server. It then puts the remote path, rather than the local one, into the file upload form element. This ensures the file contents to be available to the remote browser just as if it were running locally, allowing tests to proceed without trouble! A full example (also available on GitHub):

  <?php
  require_once 'vendor/autoload.php';

  class WebDriverDemo extends Sauce\Sausage\WebDriverTestCase {
      public static $browsers = array(
          // run FF15 on Windows 8 on Sauce
          array(
              'browserName' => 'firefox',
              'desiredCapabilities' => array(
                  'version' => '15',
                  'platform' => 'Windows 2012',
              )
          )
      );

      public function setUpPage() {
          $this->url("http://sl-test.herokuapp.com/guinea_pig/file_upload");

          // set the method which knows if this is a file we're trying to upload
          $this->fileDetector(function($filename) {
              if(file_exists($filename)) {
                  return $filename;
              } else {
                  return NULL;
              }
          });
      }

      public function testFileUpload() {
          $filebox = $this->byName('myfile');
          $this->sendKeys($filebox, "./kirk.jpg");
          $this->byId('submit')->submit();

          $this->assertTextPresent("kirk.jpg (image/jpeg)");
      }
  }

Written by

Isaac Murchie

Topics

SeleniumCross browser