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:
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.
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.
No Pingbacks for this post yet...
A private blog for life, cycling, and my computing fiddlings.
| 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 | ||