[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