[Zope3-checkins]
SVN: Zope3/branches/testbrowser-integration/src/zope/testbrowser/
- update the interfaces to match the implementation
Benji York
benji at zope.com
Tue Aug 23 15:27:13 EDT 2005
Log message for revision 38050:
- update the interfaces to match the implementation
- add tests that verify interfaces that weren't being verified
- make instances resist having *new* attributes accidentally set on them (with
tests)
- add helpful argument verification for getForm
- remove __getattr__ from Form in preference for properties
Changed:
U Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt
U Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py
U Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py
-=-
Modified: Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt
===================================================================
--- Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt 2005-08-23 17:44:46 UTC (rev 38049)
+++ Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt 2005-08-23 19:27:13 UTC (rev 38050)
@@ -25,6 +25,13 @@
testbrowser doctests suggests using 'click' to navigate further (as discussed
below), except in unusual circumstances.
+The test browser complies with the IBrowser interface.
+
+ >>> from zope.testbrowser import interfaces
+ >>> from zope.interface.verify import verifyObject
+ >>> verifyObject(interfaces.IBrowser, browser)
+ True
+
Page Contents
-------------
@@ -138,6 +145,14 @@
>>> link = browser.getLink('Link Text')
>>> link
<Link text='Link Text' url='http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'>
+
+Link objects comply with the ILink interface.
+
+ >>> verifyObject(interfaces.ILink, link)
+ True
+
+Links expose several attributes for easy access.
+
>>> link.text
'Link Text'
>>> link.tag # links can also be image maps.
@@ -146,6 +161,9 @@
'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
>>> link.attrs
{'href': 'navigate.html?message=By+Link+Text'}
+
+Links can be "clicked" and the brower will navigate to the refrenced URL.
+
>>> link.click()
>>> browser.url
'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
@@ -261,7 +279,8 @@
argument is 'label', and looks up the form on the basis of any associated
label.
- >>> browser.getControl('Text Control')
+ >>> control = browser.getControl('Text Control')
+ >>> control
<Control name='text-value' type='text'>
>>> browser.getControl(label='Text Control') # equivalent
<Control name='text-value' type='text'>
@@ -384,8 +403,6 @@
>>> ctrl = browser.getControl('Text Control')
>>> ctrl
<Control name='text-value' type='text'>
- >>> from zope.interface.verify import verifyObject
- >>> from zope.testbrowser import interfaces
>>> verifyObject(interfaces.IControl, ctrl)
True
@@ -887,6 +904,11 @@
>>> browser.open('http://localhost/@@/testbrowser/forms.html')
>>> form = browser.getForm(name='one')
+Form instances conform to the IForm interface.
+
+ >>> verifyObject(interfaces.IForm, form)
+ True
+
The form exposes several attributes related to forms:
- The name of the form:
@@ -1010,3 +1032,29 @@
...
NotFound: Object: <zope.app.folder.folder.Folder object at ...>,
name: u'invalid'
+
+Hand-Holding
+------------
+
+Instances of the various objects ensure that users don't accidentally set
+instance attributes accidentally.
+
+ >>> browser.nonexistant = None
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'Browser' object has no attribute 'nonexistant'
+
+ >>> form.nonexistant = None
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'Form' object has no attribute 'nonexistant'
+
+ >>> control.nonexistant = None
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'Control' object has no attribute 'nonexistant'
+
+ >>> link.nonexistant = None
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'Link' object has no attribute 'nonexistant'
Modified: Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py
===================================================================
--- Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py 2005-08-23 17:44:46 UTC (rev 38049)
+++ Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py 2005-08-23 19:27:13 UTC (rev 38050)
@@ -59,6 +59,9 @@
else:
return Control(control, form, browser)
+def any(items):
+ return bool(sum([bool(i) for i in items]))
+
def onlyOne(items, description):
total = sum([bool(i) for i in items])
if total == 0 or total > 1:
@@ -71,7 +74,19 @@
"Supply no more than one of %s as arguments" % description)
-class Browser(object):
+class SetattrErrorsMixin(object):
+ _enable_setattr_errors = False
+
+ def __setattr__(self, name, value):
+ if self._enable_setattr_errors:
+ # cause an attribute error if the attribute doesn't already exist
+ getattr(self, name)
+
+ # set the value
+ object.__setattr__(self, name, value)
+
+
+class Browser(SetattrErrorsMixin):
"""A web user agent."""
zope.interface.implements(interfaces.IBrowser)
@@ -82,6 +97,7 @@
self._counter = 0
if url is not None:
self.open(url)
+ self._enable_setattr_errors = True
@property
def url(self):
@@ -227,6 +243,9 @@
def getForm(self, id=None, name=None, action=None, index=None):
zeroOrOne([id, name, action], '"id", "name", and "action"')
+ if index is None and not any([id, name, action]):
+ raise ValueError(
+ 'if no other arguments are given, index is required.')
matching_forms = []
for form in self.mech_browser.forms():
@@ -248,13 +267,14 @@
self._counter += 1
-class Link(object):
+class Link(SetattrErrorsMixin):
zope.interface.implements(interfaces.ILink)
def __init__(self, link, browser):
self.mech_link = link
self.browser = browser
self._browser_counter = self.browser._counter
+ self._enable_setattr_errors = True
def click(self):
if self._browser_counter != self.browser._counter:
@@ -283,15 +303,18 @@
self.__class__.__name__, self.text, self.url)
-class Control(object):
+class Control(SetattrErrorsMixin):
"""A control of a form."""
zope.interface.implements(interfaces.IControl)
+ _enable_setattr_errors = False
+
def __init__(self, control, form, browser):
self.mech_control = control
self.mech_form = form
self.browser = browser
self._browser_counter = self.browser._counter
+ self._enable_setattr_errors = True
# for some reason ClientForm thinks we shouldn't be able to modify
# hidden fields, but while testing it is sometimes very important
@@ -436,7 +459,7 @@
self.browser._changed()
-class ItemControl(object):
+class ItemControl(SetattrErrorsMixin):
zope.interface.implements(interfaces.IItemControl)
def __init__(self, item, form, browser):
@@ -444,6 +467,7 @@
self.mech_form = form
self.browser = browser
self._browser_counter = self.browser._counter
+ self._enable_setattr_errors = True
@property
def control(self):
@@ -487,7 +511,7 @@
self.mech_item.control.type, self.optionValue)
-class Form(object):
+class Form(SetattrErrorsMixin):
"""HTML Form"""
zope.interface.implements(interfaces.IForm)
@@ -500,16 +524,25 @@
self.browser = browser
self.mech_form = form
self._browser_counter = self.browser._counter
+ self._enable_setattr_errors = True
- def __getattr__(self, name):
- # See zope.testbrowser.interfaces.IForm
- names = ['action', 'method', 'enctype', 'name']
- if name in names:
- return getattr(self.mech_form, name, None)
- else:
- raise AttributeError(name)
+ @property
+ def action(self):
+ return self.mech_form.action
@property
+ def method(self):
+ return self.mech_form.method
+
+ @property
+ def enctype(self):
+ return self.mech_form.enctype
+
+ @property
+ def name(self):
+ return self.mech_form.name
+
+ @property
def id(self):
"""See zope.testbrowser.interfaces.IForm"""
return self.mech_form.attrs.get('id')
Modified: Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py
===================================================================
--- Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py 2005-08-23 17:44:46 UTC (rev 38049)
+++ Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py 2005-08-23 19:27:13 UTC (rev 38050)
@@ -155,8 +155,6 @@
description=u'The tag name of the link (a or area, typically)',
required=True)
-class IFormsMapping(zope.interface.common.mapping.IReadMapping):
- """A mapping of all forms in a page."""
class IForm(zope.interface.Interface):
"""An HTML form of the page."""
@@ -254,14 +252,6 @@
description=u"Title of the displayed page",
required=False)
- forms = zope.schema.Object(
- title=u"Forms",
- description=(u"A mapping of form elements on the page. The key is "
- u"actually quiet flexible and searches the id and name "
- u"of the form element."),
- schema=IFormsMapping,
- required=True)
-
handleErrors = zope.schema.Bool(
title=u"Handle Errors",
description=(u"Describes whether server-side errors will be handled "
@@ -319,7 +309,7 @@
"""
def getControl(label=None, name=None, index=None):
- """Get a control in the page.
+ """Get a control from the page.
Only one of ``label`` and ``name`` may be provided. ``label``
searches form labels (including submit button values, per the HTML 4.0
@@ -337,3 +327,18 @@
it is used to choose the index from the ambiguous choices. If the
index does not exist, the code raises a LookupError.
"""
+
+ def getForm(id=None, name=None, action=None, index=None):
+ """Get a form from the page.
+
+ Zero or one of ``id``, ``name``, and ``action`` may be provided. If
+ none are provided the index alone is used to determine the return
+ value.
+
+ If no values are found, the code raises a LookupError.
+
+ If ``index`` is None (the default) and more than one form matches the
+ search, the code raises an AmbiguityError. If an index is provided,
+ it is used to choose the index from the ambiguous choices. If the
+ index does not exist, the code raises a LookupError.
+ """
More information about the Zope3-Checkins
mailing list