File Uploads in Selenium 1 and 2

File Uploads in Selenium 1 and 2

2011/03/27 | by Doug [mail] | Categories: Computing, Python

Over on the SauceLabs blog Santiago Suarez Ordoñez has a nice description of how to use the attach_file method in Selenium 1, and some of the restrictions that pertain to it. One of those restrictions is that the file that attach_file operates on has to be available from the root level of an HTTP server. To quote Santi:

using the following URL will not work:

http://saucelabs.com/subdirectory/subdirectory2/file.txt
while the following will:

http://saucelabs.com/file.txt

So, you could drop your test files into the root directory of some Apache server you have control over, but Python offers a more flexible solution: run an adhoc server to serve files from your test files directory:

python -m SimpleHTTPServer 8000

Nice, but you still have to make sure that adhoc server is running or you tests will fail. With a little work I was able to come up with a way to make my tests self-contained by starting up the server to handle requests from attach_file in a text class setup method. The key is that the server has to run in its own thread, otherwise it blocks the tests from executing:

Code:

import os
import posixpath
from selenium import selenium
from SimpleHTTPServer import SimpleHTTPRequestHandler
import SocketServer
import threading
import urllib
import unittest2 as unittest
 
 
class AttachmentFileRequestHandler(SimpleHTTPRequestHandler):
    """Serve files from the :file:`test_data` sub-directory of the
    current directory and any of its sub-directories.
    """
    def translate_path(self, path):
        """Translate a /-separated PATH to the local filename syntax.
 
        Components that mean special things to the local file system
        (e.g. drive or directory names) are ignored.
        """
        # abandon query parameters
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = [word for word in words if word]
        path = os.getcwd()
        path = os.path.join(path, 'test_data')
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path
 
 
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass
 
 
class TestSeleniumFileUpload(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        httpd = ThreadedTCPServer(('', 8001), AttachmentFileReqestHandler)
        httpd_thread = threading.Thread(target=httpd.serve_forever)
        httpd_thread.daemon = True
        httpd_thread.start()
        cls.attachment_server = httpd
        cls.attachment_server_thread = httpd_thread
 
 
    @classmethod
    def tearDownClass(cls):
        cls.attachment_server.shutdown()
        cls.attachment_server_thread.join()
 
        
    def test_upload_file(self):
        browser = selenium(
            host='localhost', port=4444,
            browserStartCommand='firefox',
            browserURL='http://localhost:8000')
        browser.start()
        browser.open('/')
        browser.attach_file('id=attachment', 'http://localhost:8001/foo.txt')
        self.assertTrue(browser.get_value('id=attachment').endswith('foo.txt'))
        browser.stop()
 
 
if __name__ == '__main__':
    unittest.main()

The AttachmentFileRequestHandler is there to override translate_path from SimpleHTTPRequestHandler to that the upload test files are served from a sub-directory of the test suite.

ThreadedTCPServer just uses SocketServer.ThreadingMixIn to enable the server to run in its own thread.

The server is started before the tests run by the setUpClass class method and shutdown when they finish by tearDownClass, another class method. Those methods are courtesy of Michael Foord’s excellent improvements to the unittest module; available from PyPI as unittest2 for Python 2.4, 2.5, and 2.6, and from the standard library as unittest for Python 2.7, and 3.2 and up.

The test I’ve shown here is trivial - just confirming that the value of the input tag is set in the HTML fragment:

Code:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>selenium upload example</title>
</head>
 
<body>
<input id="attachment" type="file">
</body>
</html>

In my real tests I confirm that the uploaded file has been stored as an attachment to a CouchDB object, and then use httplib to do a download request for the attachment and confirm that the response headers are correct.

As also Santi points out in his post, attach_file only works if you run your tests against Firefox.

Selenium 2

The introduction of webdriver in Selenium 2 make things easier. No more need for attach_file and a server to handle its requests - just use the send_keys method to put the absolute path of the test upload file into the input element.

Code:

import os
from selenium import webdriver
import unittest2 as unittest
 
 
class TestSeleniumFileUpload(unittest.TestCase):
    def upload_file(self, driver):
        driver.get('http://localhost:8000/')
        element = driver.find_element_by_id('attachment')
        path = os.path.join(os.getcwd(), 'test_data/foo.txt')
        element.send_keys(path)
        self.assertTrue(element.value.endswith('foo.txt'))
 
 
    def test_firefox_upload_file(self):
        driver = webdriver.Firefox()
        self.upload_file(driver)
        driver.close()
 
 
    def test_remote_htmlunit_upload_file(self):
        driver = webdriver.Remote(
            desired_capabilities=webdriver.DesiredCapabilities.HTMLUNIT)
        self.upload_file(driver)
        driver.close()
 
 
if __name__ == '__main__':
    unittest.main()

You can test against a local instance of Firefox as shown in test_firefox_upload_file or use the standalone Selenium server to test against a remote browser. In test_remote_htmlunit_upload_file I’ve used that approach to run the test against the HtmlUnit driver which is blazingly fast because it doesn’t render content to a screen. It’s not available directly in Python, hence the use of Remote.

One caveat: Selenium 2 doesn’t support the file selector input element yet for WebKit (Chrome, Safari, …) browsers.

The code above is based on the selenium2.0b3 Python bindings available from PyPI. The Selenium 1 example and the Remote Selenium 2 test were run against selenium-server-standalone-2.0b3.jar available from the Selenium project repository on code.google.com.

Permalink

Pingbacks:

No Pingbacks for this post yet...

Doug's Home Blog

A private blog for life, cycling, and my computing fiddlings.

October 2014
Mon Tue Wed Thu Fri Sat Sun
 << <   > >>
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

Misc

XML Feeds

What is RSS?

powered by b2evolution free blog software