Skip to content

Commit

Permalink
mechanize related functions separated.
Browse files Browse the repository at this point in the history
  • Loading branch information
Taito Horiuchi committed Sep 2, 2011
1 parent 35ae4a8 commit a6daf21
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 255 deletions.
136 changes: 40 additions & 96 deletions leo/testing/browser.py
@@ -1,15 +1,10 @@
from leo.testing.mime import BOUNDARY
from leo.testing.mime import MULTIPART_ONE_FILE_TMPL
from leo.testing.mime import MULTIPART_TEXT_TMPL
from leo.testing.mime import multifile
from leo.testing.mech import LeoMechanizeBrowser
from leo.testing.mime import multipart_formdata
from plone.portlets.interfaces import IPortletAssignmentMapping
from plone.portlets.interfaces import IPortletManager
from plone.testing import z2
from plone.testing._z2_testbrowser import Zope2HTTPHandler
from plone.testing._z2_testbrowser import Zope2MechanizeBrowser
from tempfile import gettempdir
from Testing.ZopeTestCase.utils import startZServer
from urllib2 import URLError
# from Testing.ZopeTestCase.utils import startZServer
from zope.component import getMultiAdapter
from zope.component import getUtility

Expand All @@ -23,6 +18,9 @@ class Browser(z2.Browser):
_base_url = None

def __init__(self, app, url=None):
"""Use __init__ method from the super class,
but pass LeoMechanizeBrowser instance to mech_browser to use
POST method for <form />s instead of GET method."""
super(z2.Browser, self).__init__(url=url, mech_browser=LeoMechanizeBrowser(app))

def setBaseUrl(self, base_url):
Expand Down Expand Up @@ -56,111 +54,57 @@ def login(self, username, password, login_url='/login_form'):
self.getControl(name='__ac_name').value = username
self.getControl(name='__ac_password').value = password
self.getControl('Log in').click()
# assert 'You are now logged in' in self.contents

def deletePortletManager(self, portal, name):
"""Delete portlet manager of the name."""
column = getUtility(IPortletManager, name=name)
assignable = getMultiAdapter((portal, column), IPortletAssignmentMapping)
del assignable[name]

def startZserver(self, web_browser_name='firefox'):
"""Start ZServer so we can inspect site state with a normal browser
like FireFox."""
echo = startZServer()
webbrowser.open_new_tab('http://{0}:{1}/plone'.format(echo[0], echo[1]))

def openBrowser(self, web_browser_name='firefox'):
## This method is not working properly for now.
# def startZserver(self, browser=None):
# """Start ZServer so we can inspect site state with a normal browser
# like FireFox.
# If browser is not specified, system default browser will be used.

# :param browser: Excutable browser name such as 'firefox'.
# :type browser: str
# """
# host, port = startZServer()
# url = 'http://{0}:{1}/plone'.format(host, port)
# wbrowser = webbrowser if browser is None else webbrowser.get(browser)
# wbrowser.open_new_tab(url)

def openBrowser(self, browser=None, filename='testbrowser.html'):
"""Dumps self.browser.contents (HTML) to a file and opens it with
a normal browser."""
tmp_filename = 'testbrowser.html'
filepath = os.path.join(gettempdir(), tmp_filename)
a normal browser.
If browser is not specified, system default browser will be used.
:param browser: Excutable browser name such as 'firefox'.
:type browser: str
:param filename: HTML file name where the results of html contents will be writen.
:type filename: str
"""
filepath = os.path.join(gettempdir(), filename)
with open(filepath, 'w') as file:
file.write(self.contents)
webbrowser.open_new_tab('file://' + filepath)
if browser == None:
wbrowser = webbrowser
else:
wbrowser = webbrowser.get(browser)
wbrowser.open_new_tab('file://' + filepath)

def post(self, url, data):
"""Posting to url with multipart/form-data instead of application/x-www-form-urlencoded
:param url: where data will be posted.
:type url: str
:param data: data to be posted.
:type data: str or dict
:param data: Data which is list of tuples for data posting.
:type data: list
"""
if isinstance(data, dict):
body, content_type = self.multipart_formdata(data)
data = "Content-Type: {0}\r\n\r\n{1}".format(content_type, body)
body, content_type = multipart_formdata(data)
data = "Content-Type: {0}\r\n\r\n{1}".format(content_type, body)

return super(Browser, self).post(url, data)

def multipart_formdata(self, fields):
"""
Given a dictionary field parameters, returns the HTTP request body and the
content_type (which includes the boundary string), to be used with an
httplib-like call.
This function is adapted from
http://urllib3.googlecode.com/svn/trunk/urllib3/filepost.py
"""

body = []
for key, value in sorted(fields.items()):
if isinstance(value, dict):
body.append(MULTIPART_ONE_FILE_TMPL.format({
'boundary': BOUNDARY,
'name': key,
'filename': value['filename'],
'content-type': value['content-type'],
'value': value['data'].read(),
}))
elif isinstance(value, list):
body.append('\r\n'.join(multifile(key, value, BOUNDARY)))
else:
body.append(MULTIPART_TEXT_TMPL.format({
'boundary': BOUNDARY,
'name': key,
'value': value,
'length': len(value),
}))
body.append('--{0}--\r\n'.format(BOUNDARY))
content_type = 'multipart/form-data; boundary={0}'.format(BOUNDARY)

return ''.join(body), content_type


class LeoMechanizeBrowser(Zope2MechanizeBrowser):

def __init__(self, app, *args, **kws):
import mechanize

def httpHandlerFactory():
return LeoHTTPHandler(app)

self.handler_classes = mechanize.Browser.handler_classes.copy()
self.handler_classes["http"] = httpHandlerFactory
self.default_others = [cls for cls in self.default_others
if cls in mechanize.Browser.handler_classes]
mechanize.Browser.__init__(self, *args, **kws)


class LeoHTTPHandler(Zope2HTTPHandler):

def do_request_(self, request):
host = request.get_host()
if not host:
raise URLError('no host given')

if request.has_data(): # POST
data = request.get_data()
if not request.has_header('Content-type'):
request.add_unredirected_header(
'Content-type',
'multipart/form-data; boundary={0}'.format(BOUNDARY))
if not request.has_header('Content-length'):
request.add_unredirected_header(
'Content-length', '{0}'.format(len(data)))
return Zope2HTTPHandler.do_request_(self, request)

http_request = do_request_
10 changes: 10 additions & 0 deletions leo/testing/docs/index.rst
@@ -1,6 +1,16 @@
leo.testing
===========

This package provides useful helper methods for browser testing in Plone.



.. automodule:: leo.testing.browser

.. autoclass:: Browser
:members:
:member-order: bysource


Contents:

Expand Down
40 changes: 40 additions & 0 deletions leo/testing/mime.py
Expand Up @@ -113,3 +113,43 @@ def multifile(key, value, boundary):
values,
'']
return parts


def multipart_formdata(data):
"""
Given a dictionary field parameters, returns the HTTP request body and the
content_type (which includes the boundary string), to be used with an
httplib-like call.
This function is adapted from
http://urllib3.googlecode.com/svn/trunk/urllib3/filepost.py
:param data: Data which is list of tuples.
:type data: list
"""

body = []
# for key, value in sorted(fields.items()):
for key, value in data:
if isinstance(value, dict):
body.append(MULTIPART_ONE_FILE_TMPL.format({
'boundary': BOUNDARY,
'name': key,
'filename': value['filename'],
'content-type': value['content-type'],
'value': value['data'].read(),
}))
elif isinstance(value, list):
body.append('\r\n'.join(multifile(key, value, BOUNDARY)))
else:
body.append(MULTIPART_TEXT_TMPL.format({
'boundary': BOUNDARY,
'name': key,
'value': value,
'length': len(value),
}))
body.append('--{0}--\r\n'.format(BOUNDARY))
content_type = 'multipart/form-data; boundary={0}'.format(BOUNDARY)

return ''.join(body), content_type
72 changes: 63 additions & 9 deletions leo/testing/tests/functional/browser.txt
Expand Up @@ -5,7 +5,7 @@

Post username::

>>> browser.post('/@@echo', {'username': 'Some Name'})
>>> browser.post('/@@echo', [('username', 'Some Name')])

>>> print browser.headers['status']
200 Ok
Expand All @@ -18,16 +18,70 @@ Post username::
Post username and one file::

>>> from StringIO import StringIO
>>> data = {
... 'username': 'Some Name',
... 'my_text': {
... 'filename': 'file1.txt',
... 'content-type': 'text/plain',
... 'data': StringIO('Some Data')
... }
... }
>>> data = [
... (
... 'username',
... 'Some Name'
... ),
... (
... 'my_text',
... {
... 'filename': 'file1.txt',
... 'content-type': 'text/plain',
... 'data': StringIO('Some Data')
... }
... )
... ]

>>> browser.post('/@@echo', data)
>>> print browser.contents
REQUEST_METHOD: POST
["my_text", "username"]


Post multiple files, one file and username::

>>> data = [
... (
... 'multiple_files',
... [
... {
... 'data': StringIO('text_file'),
... 'content-type': 'text/plain',
... 'filename': 'filename.txt'
... },
... {
... 'data': StringIO('image_file'),
... 'content-type': 'image/gif',
... 'filename': 'filename.png'
... }
... ]
... ),
... (
... 'single_file',
... {
... 'data': StringIO('single_text_file'),
... 'content-type': 'text/plain',
... 'filename': 'single_filename.txt'
... }
... ),
... (
... 'username',
... 'Some Name'
... ),
... ]
>>> browser.post('/@@echo', data)
>>> print browser.contents
REQUEST_METHOD: POST
["multiple_files", "single_file", "username"]

#Open browser::
#
# >>> browser.openBrowser()
# >>> browser.openBrowser('epiphany')

#Start zServer::
#
# >>> browser.startZserver()
#
# >>> browser.startZserver('firefox')

0 comments on commit a6daf21

Please sign in to comment.