[Zope3-checkins] SVN:
Zope3/trunk/src/zope/app/testing/testbrowser/
add the package formerly known as zc.mechtest
Julien Anguenot
ja at nuxeo.com
Wed Jul 27 06:24:18 EDT 2005
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Hi Benji,
You checked in code containing decorators which is not working with
Python-2.3.5 and this version is still the current, offically, supported
version for Zope3...
Thus tests are failing.
J.
Benji York wrote:
> Log message for revision 37446:
> add the package formerly known as zc.mechtest
>
>
> Changed:
> A Zope3/trunk/src/zope/app/testing/testbrowser/
> A Zope3/trunk/src/zope/app/testing/testbrowser/README.txt
> A Zope3/trunk/src/zope/app/testing/testbrowser/__init__.py
> A Zope3/trunk/src/zope/app/testing/testbrowser/browser.py
> A Zope3/trunk/src/zope/app/testing/testbrowser/ftests.py
> A Zope3/trunk/src/zope/app/testing/testbrowser/testing.py
>
> -=-
> Added: Zope3/trunk/src/zope/app/testing/testbrowser/README.txt
> ===================================================================
> --- Zope3/trunk/src/zope/app/testing/testbrowser/README.txt 2005-07-26 22:39:45 UTC (rev 37445)
> +++ Zope3/trunk/src/zope/app/testing/testbrowser/README.txt 2005-07-27 02:58:18 UTC (rev 37446)
> @@ -0,0 +1,193 @@
> +zope.app.testing.testbrowser
> +============================
> +
> +The zope.app.testing.testbrowser module exposes a `Browser` class that
> +simulates a web browser similar to Mozilla Firefox or IE.
> +
> + >>> from zope.app.testing.testbrowser import Browser
> + >>> browser = Browser()
> + >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
> +
> +The browser can `open` web pages:
> +
> + >>> browser.open('http://localhost/++etc++site/default')
> + >>> browser.url
> + 'http://localhost/++etc++site/default'
> +
> +
> +Page Contents
> +=============
> +
> +The contents of the current page are available:
> +
> + >>> print browser.contents
> + <...
> + <html...>
> + <body...>
> + ...
> +
> +Making assertions about page contents are easy.
> +
> + >>> '<a href="RootErrorReportingUtility">' in browser.contents
> + True
> +
> +
> +Headers
> +=======
> +
> +The page's headers are also available as an httplib.HTTPMessage instance:
> +
> + >>> browser.headers
> + <httplib.HTTPMessage instance...>
> +
> +The headers can be accesed as a string:
> +
> + >>> print browser.headers
> + Status: 200 Ok
> + Content-Length: ...
> + Content-Type: text/html;charset=utf-8
> + X-Powered-By: Zope (www.zope.org), Python (www.python.org)
> +
> +Or as a mapping:
> +
> + >>> browser.headers['content-type']
> + 'text/html;charset=utf-8'
> +
> +
> +Navigation
> +==========
> +
> +If you want to simulate clicking on a link, there is a `click` method.
> +
> + >>> browser.click('RootErrorReportingUtility')
> + >>> browser.url
> + 'http://localhost/++etc++site/default/RootErrorReportingUtility'
> +
> +We'll navigate to a form and fill in some values and the submit the form.
> +
> + >>> browser.click('Configure')
> + >>> browser.url
> + 'http://localhost/++etc++site/default/RootErrorReportingUtility/@@configure.html'
> +
> +
> +Forms
> +=====
> +
> +The current page has a form on it, let's look at some of the controls:
> +
> + >>> browser.controls['keep_entries']
> + '20'
> + >>> browser.controls['copy_to_zlog']
> + False
> +
> +If we request a control that doesn't exist, an exception is raised.
> +
> + >>> browser.controls['does_not_exist']
> + Traceback (most recent call last):
> + ...
> + KeyError: 'does_not_exist'
> +
> +We want to change some of the form values and submit.
> +
> + >>> browser.controls['keep_entries'] = '40'
> + >>> browser.controls['copy_to_zlog'] = True
> + >>> browser.click('Save Changes')
> +
> +Are our changes reflected on the resulting page?
> +
> + >>> browser.controls['keep_entries']
> + '40'
> + >>> browser.controls['copy_to_zlog']
> + True
> +
> +The `controls` object also has an `update()` method similar to that of
> +a dictionary:
> +
> + >>> browser.controls.update(dict(keep_entries='30', copy_to_zlog=False))
> + >>> browser.click('Save Changes')
> + >>> browser.controls['keep_entries']
> + '30'
> + >>> browser.controls['copy_to_zlog']
> + False
> +
> +Finding Specific Forms
> +======================
> +
> +Because pages can have multiple forms with like-named controls, it is sometimes
> +neccesary to access forms by name or id. The browser's `forms` attribute can
> +be used to do so. The key value is the form's name or id. If more than one
> +form has the same name or id, the first one will be returned.
> +
> +XXX these need to be re-targeted to pages registered just for this test
> +## >>> # zope form and use that instead
> +## >>> form = browser.forms['portlet_form']
> +
> +The form exposes several attributes:
> +
> +## >>> form.name
> +## 'portlet_form'
> +## >>> form.action
> +## 'http://localhost/++etc++site/default/...'
> +## >>> form.method
> +## 'POST'
> +## >>> form.id is None
> +## True
> +
> +The form's controls can also be accessed with the `controls` mapping.
> +
> +## >>> form.controls['portlet_action']
> +## '...'
> +
> +More Forms
> +==========
> +
> +Now, let's navegate to a page with a slightly more complex form.
> +
> + >>> browser.click('Registration')
> + >>> browser.click('Advanced Options')
> + >>> browser.click('UtilityRegistration')
> +
> +Is the expected control on the page?
> +
> + >>> 'field.permission' in browser.controls
> + True
> +
> +Good, let's retrieve it then:
> +
> + >>> permission = browser.getControl('field.permission')
> +
> +What kind of control is it?
> +
> + >>> permission.type
> + 'select'
> +
> +Is it a single- or multi-select?
> +
> + >>> permission.multiple
> + False
> +
> +What options are available for the "field.permission" control?
> +
> + >>> permission.options
> + ['', 'zope.Public', ... 'zope.ManageContent', ... 'zope.View', ...]
> +
> +
> +We'll store the current setting so we can set it back later.
> +
> + >>> original_permission = permission.value
> +
> +Let's set one of the options and submit the form.
> +
> + >>> permission.value = ['zope.Public']
> + >>> browser.click('Change')
> +
> +Ok, did our change take effect? (Note that the order may not be preserved for
> +multi-selects.)
> +
> + >>> browser.controls['field.permission'] == ['zope.Public']
> + True
> +
> +Let's set it back, so we don't mess anything up.
> +
> + >>> permission.value = original_permission
> + >>> browser.click('Change')
>
>
> Property changes on: Zope3/trunk/src/zope/app/testing/testbrowser/README.txt
> ___________________________________________________________________
> Name: svn:eol-style
> + native
>
> Added: Zope3/trunk/src/zope/app/testing/testbrowser/__init__.py
> ===================================================================
> --- Zope3/trunk/src/zope/app/testing/testbrowser/__init__.py 2005-07-26 22:39:45 UTC (rev 37445)
> +++ Zope3/trunk/src/zope/app/testing/testbrowser/__init__.py 2005-07-27 02:58:18 UTC (rev 37446)
> @@ -0,0 +1,4 @@
> +try:
> + from testing import Browser
> +except ImportError:
> + pass
>
>
> Property changes on: Zope3/trunk/src/zope/app/testing/testbrowser/__init__.py
> ___________________________________________________________________
> Name: svn:keywords
> + Id
> Name: svn:eol-style
> + native
>
> Added: Zope3/trunk/src/zope/app/testing/testbrowser/browser.py
> ===================================================================
> --- Zope3/trunk/src/zope/app/testing/testbrowser/browser.py 2005-07-26 22:39:45 UTC (rev 37445)
> +++ Zope3/trunk/src/zope/app/testing/testbrowser/browser.py 2005-07-27 02:58:18 UTC (rev 37446)
> @@ -0,0 +1,248 @@
> +import re
> +
> +class Browser(object):
> + def __init__(self, url=None, mech_browser=None):
> + if mech_browser is None:
> + import mechanize
> + mech_browser = mechanize.Browser()
> +
> + self.mech_browser = mech_browser
> + if url is not None:
> + self.open(url)
> +
> + def open(self, url, data=None):
> + self.mech_browser.open(url, data)
> +
> + def addHeader(self, key, value):
> + self.mech_browser.addheaders.append( (key, value) )
> +
> + @property
> + def url(self):
> + return self.mech_browser.geturl()
> +
> + def reload(self):
> + self.mech_browser.reload()
> + self._changed()
> +
> + def goBack(self, count=1):
> + self.mech_browser.back(self, count)
> + self._changed()
> +
> + @property
> + def links(self, *args, **kws):
> + return self.mech_browser.links(*args, **kws)
> +
> + @property
> + def isHtml(self):
> + return self.mech_browser.viewing_html()
> +
> + @property
> + def title(self):
> + return self.mech_browser.title()
> +
> + def click(self, text=None, url=None, id=None, name=None, coord=(1,1)):
> + form, control = self._findControl(text, id, name, type='submit')
> + if control is not None:
> + self._clickSubmit(form, control, coord)
> + self._changed()
> + return
> +
> + # if we get here, we didn't find a control to click, so we'll look for
> + # a regular link
> +
> + if id is not None:
> + predicate = lambda link: link.attrs.get('id') == id
> + self.mech_browser.follow_link(predicate=predicate)
> + else:
> + if text is not None:
> + text_regex = re.compile(text)
> + else:
> + text_regex = None
> + if url is not None:
> + url_regex = re.compile(url)
> + else:
> + url_regex = None
> +
> + self.mech_browser.follow_link(text_regex=text_regex,
> + url_regex=url_regex)
> + self._changed()
> +
> + @property
> + def _findControl(self):
> + def _findControl(text, id, name, type=None, form=None):
> + for control_form, control in self._controls:
> + if form is None or control_form == form:
> + if (((id is not None and control.id == id)
> + or (name is not None and control.name == name)
> + or (text is not None and re.search(text, str(control.value)))
> + ) and (type is None or control.type == type)):
> + self.mech_browser.form = control_form
> + return control_form, control
> +
> + return None, None
> + return _findControl
> +
> + def _findForm(self, id, name, action):
> + for form in self.mech_browser.forms():
> + if ((id is not None and form.attrs.get('id') == id)
> + or (name is not None and form.name == name)
> + or (action is not None and re.search(action, str(form.action)))):
> + self.mech_browser.form = form
> + return form
> +
> + return None
> +
> + def _clickSubmit(self, form, control, coord):
> + self.mech_browser.open(form.click(
> + id=control.id, name=control.name, coord=coord))
> +
> + __controls = None
> + @property
> + def _controls(self):
> + if self.__controls is None:
> + self.__controls = []
> + for form in self.mech_browser.forms():
> + for control in form.controls:
> + self.__controls.append( (form, control) )
> + return self.__controls
> +
> + @property
> + def controls(self):
> + return ControlsMapping(self)
> +
> + @property
> + def forms(self):
> + return FormsMapping(self)
> +
> + def getControl(self, text):
> + form, control = self._findControl(text, text, text)
> + if control is None:
> + raise ValueError('could not locate control: ' + text)
> + return Control(control)
> +
> + @property
> + def contents(self):
> + response = self.mech_browser.response()
> + old_location = response.tell()
> + response.seek(0)
> + for line in iter(lambda: response.readline().strip(), ''):
> + pass
> + contents = response.read()
> + response.seek(old_location)
> + return contents
> +
> + @property
> + def headers(self):
> + return self.mech_browser.response().info()
> +
> + def _changed(self):
> + self.__controls = None
> +
> +
> +class Control(object):
> + def __init__(self, control):
> + self.mech_control = control
> +
> + def __getattr__(self, name):
> + names = ['options', 'disabled', 'type', 'name', 'readonly', 'multiple']
> + if name in names:
> + return getattr(self.mech_control, name, None)
> + else:
> + raise AttributeError(name)
> +
> + @apply
> + def value():
> + def fget(self):
> + value = self.mech_control.value
> + if self.mech_control.type == 'checkbox':
> + value = bool(value)
> + return value
> + def fset(self, value):
> + if self.mech_control.type == 'checkbox':
> + if value:
> + value = ['on']
> + else:
> + value = []
> + self.mech_control.value = value
> + return property(fget, fset)
> +
> + def clear(self):
> + self.mech_control.clear()
> +
> + @property
> + def options(self):
> + try:
> + return self.mech_control.possible_items()
> + except:
> + raise AttributeError('options')
> +
> +
> +class FormsMapping(object):
> + def __init__(self, browser):
> + self.browser = browser
> +
> + def __getitem__(self, key):
> + try:
> + form = self.browser._findForm(key, key, None)
> + except ValueError:
> + raise KeyError(key)
> + return Form(self.browser, form)
> +
> + def __contains__(self, item):
> + return self.browser._findForm(key, key, None) is not None
> +
> +
> +class ControlsMapping(object):
> + def __init__(self, browser, form=None):
> + """Initialize the ControlsMapping
> +
> + browser - a Browser instance
> + form - a ClientForm instance
> + """
> + self.browser = browser
> + self.mech_form = form
> +
> + def __getitem__(self, key):
> + form, control = self.browser._findControl(key, key, key)
> + if control is None:
> + raise KeyError(key)
> + if self.mech_form is not None and self.mech_form != form:
> + raise KeyError(key)
> + return Control(control).value
> +
> + def __setitem__(self, key, value):
> + form, control = self.browser._findControl(key, key, key)
> + if control is None:
> + raise KeyError(key)
> + Control(control).value = value
> +
> + def __contains__(self, item):
> + try:
> + self[item]
> + except KeyError:
> + return False
> + else:
> + return True
> +
> + def update(self, mapping):
> + for k, v in mapping.items():
> + self[k] = v
> +
> +
> +class Form(ControlsMapping):
> +
> + def __getattr__(self, name):
> + names = ['action', 'method', 'enctype', 'name']
> + if name in names:
> + return getattr(self.mech_form, name, None)
> + else:
> + raise AttributeError(name)
> +
> + @property
> + def id(self):
> + return self.mech_form.attrs.get(id)
> +
> + @property
> + def controls(self):
> + return ControlsMapping(browser=self.browser, form=self.mech_form)
> +
>
>
> Property changes on: Zope3/trunk/src/zope/app/testing/testbrowser/browser.py
> ___________________________________________________________________
> Name: svn:keywords
> + Id
> Name: svn:eol-style
> + native
>
> Added: Zope3/trunk/src/zope/app/testing/testbrowser/ftests.py
> ===================================================================
> --- Zope3/trunk/src/zope/app/testing/testbrowser/ftests.py 2005-07-26 22:39:45 UTC (rev 37445)
> +++ Zope3/trunk/src/zope/app/testing/testbrowser/ftests.py 2005-07-27 02:58:18 UTC (rev 37446)
> @@ -0,0 +1,27 @@
> +##############################################################################
> +#
> +# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
> +#
> +# This software is subject to the provisions of the Zope Visible Source
> +# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
> +# distribution.
> +#
> +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
> +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
> +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
> +# FOR A PARTICULAR PURPOSE
> +#
> +##############################################################################
> +import unittest
> +from zope.app.testing.functional import FunctionalDocFileSuite
> +
> +def test_suite():
> + try:
> + import mechanize
> + except ImportError:
> + return
> + else:
> + return FunctionalDocFileSuite('demo.txt')
> +
> +if __name__ == '__main__':
> + unittest.main(defaultTest='test_suite')
>
>
> Property changes on: Zope3/trunk/src/zope/app/testing/testbrowser/ftests.py
> ___________________________________________________________________
> Name: svn:keywords
> + Id
> Name: svn:eol-style
> + native
>
> Added: Zope3/trunk/src/zope/app/testing/testbrowser/testing.py
> ===================================================================
> --- Zope3/trunk/src/zope/app/testing/testbrowser/testing.py 2005-07-26 22:39:45 UTC (rev 37445)
> +++ Zope3/trunk/src/zope/app/testing/testbrowser/testing.py 2005-07-27 02:58:18 UTC (rev 37446)
> @@ -0,0 +1,113 @@
> +##############################################################################
> +#
> +# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
> +#
> +# This software is subject to the provisions of the Zope Visible Source
> +# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
> +# distribution.
> +#
> +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
> +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
> +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
> +# FOR A PARTICULAR PURPOSE
> +#
> +##############################################################################
> +import httplib
> +import urllib2
> +from cStringIO import StringIO
> +
> +import mechanize
> +import ClientCookie
> +
> +from zope.app.testing.functional import HTTPCaller
> +
> +
> +class PublisherConnection:
> +
> + def __init__(self, host):
> + self.host = host
> + self.caller = HTTPCaller()
> +
> + def set_debuglevel(self, level):
> + pass
> +
> + def request(self, method, url, body=None, headers=None):
> + header_chunks = []
> + if body is None:
> + body = ''
> +
> + if headers is not None:
> + for header in headers.items():
> + header_chunks.append('%s: %s' % header)
> + headers = '\n'.join(header_chunks) + '\n'
> + else:
> + headers = ''
> +
> + request_string = (method + ' ' + url + ' HTTP/1.1\n'
> + + headers + '\n' + body)
> +
> + self.response = self.caller(request_string)
> +
> + def getresponse(self):
> + headers = self.response.header_output.headersl
> + real_response = self.response._response
> + status = real_response.getStatus()
> + reason = real_response._reason # XXX should add a getReason method
> + output = (real_response.getHeaderText(real_response.getHeaders()) +
> + self.response.getBody())
> + return PublisherResponse(output, status, reason)
> +
> +
> +class PublisherResponse:
> +
> + def __init__(self, content, status, reason):
> + self.content = content
> + self.status = status
> + self.reason = reason
> + self.msg = httplib.HTTPMessage(StringIO(content), 0)
> + self.content_as_file = StringIO(content)
> +
> + def read(self, amt=None):
> + return self.content_as_file.read(amt)
> +
> +
> +class PublisherHandler(urllib2.HTTPHandler):
> +
> + http_request = urllib2.AbstractHTTPHandler.do_request_
> +
> + def http_open(self, req):
> + return self.do_open(PublisherConnection, req)
> +
> +
> +import browser
> +
> +class MyMechBrowser(mechanize.Browser):
> + handler_classes = {
> + # scheme handlers
> + "http": PublisherHandler,
> +
> + "_http_error": ClientCookie.HTTPErrorProcessor,
> + "_http_request_upgrade": ClientCookie.HTTPRequestUpgradeProcessor,
> + "_http_default_error": urllib2.HTTPDefaultErrorHandler,
> +
> + # feature handlers
> + "_authen": urllib2.HTTPBasicAuthHandler,
> + "_redirect": ClientCookie.HTTPRedirectHandler,
> + "_cookies": ClientCookie.HTTPCookieProcessor,
> + "_refresh": ClientCookie.HTTPRefreshProcessor,
> + "_referer": mechanize.Browser.handler_classes['_referer'],
> + "_equiv": ClientCookie.HTTPEquivProcessor,
> + "_seek": ClientCookie.SeekableProcessor,
> + }
> +
> + default_schemes = ["http"]
> + default_others = ["_http_error", "_http_request_upgrade",
> + "_http_default_error"]
> + default_features = ["_authen", "_redirect", "_cookies", "_seek"]
> +
> +
> +class Browser(browser.Browser):
> + def __init__(self, url=None):
> + mech_browser = MyMechBrowser()
> + mech_browser.add_handler(PublisherHandler())
> + super(Browser, self).__init__(url=url, mech_browser=mech_browser)
>
>
> Property changes on: Zope3/trunk/src/zope/app/testing/testbrowser/testing.py
> ___________________________________________________________________
> Name: svn:keywords
> + Id
> Name: svn:eol-style
> + native
>
> _______________________________________________
> Zope3-Checkins mailing list
> Zope3-Checkins at zope.org
> http://mail.zope.org/mailman/listinfo/zope3-checkins
- --
Julien Anguenot | Nuxeo R&D (Paris, France)
CPS Platform : http://www.cps-project.org
Zope3 / ECM : http://www.z3lab.org
mail: anguenot at nuxeo.com; tel: +33 (0) 6 72 57 57 66
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org
iD8DBQFC52DQGhoG8MxZ/pIRAu36AJ4/NM3e8SDJ33298VY/dW42ZxP1FACfQ9TX
AQIrY5Y8jrZqNe7dq7mpOno=
=3aSl
-----END PGP SIGNATURE-----
More information about the Zope3-Checkins
mailing list