Skip to content

Commit

Permalink
Make getControl() and friends list all matching items when your query…
Browse files Browse the repository at this point in the history
… fails.

In my experience this ought to make debugging failed testbrowser tests a
lot less painful.
  • Loading branch information
mgedmin committed Feb 7, 2012
1 parent 9acf8bb commit be63fe1
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 25 deletions.
8 changes: 6 additions & 2 deletions CHANGES.txt
Expand Up @@ -12,8 +12,12 @@ CHANGES
environment by setting it to ``None`` when ``Browser.handleErrors`` is
``True``. This makes it easier to test error pages.

- More friendly error message from getControl() et al when you specify
an index that is out of bounds.
- More friendly error messages from getControl() et al:

- when you specify an index that is out of bounds, show the available
choices

- when you fail to find anything, show all the available items


4.0.2 (2011-05-25)
Expand Down
23 changes: 23 additions & 0 deletions src/zope/testbrowser/README.txt
Expand Up @@ -481,6 +481,11 @@ If you request a control that doesn't exist, the code raises a LookupError:
Traceback (most recent call last):
...
LookupError: label 'Does Not Exist'
available items:
<TextControl(text-value=Some Text)>
<PasswordControl(password-value=Password)>
<HiddenControl(hidden-value=Hidden) (readonly)>
...

If you request a control with an ambiguous lookup, the code raises an
AmbiguityError.
Expand Down Expand Up @@ -536,6 +541,7 @@ text in a label. Thus, for instance, a search for 'Add' will match the label
Traceback (most recent call last):
...
LookupError: label 'label needs whitespace normalization'
...
>>> browser.getControl(' Label Needs Whitespace ')
<Control name='label-needs-normalization' type='text'>
>>> browser.getControl('Whitespace')
Expand All @@ -544,6 +550,7 @@ text in a label. Thus, for instance, a search for 'Add' will match the label
Traceback (most recent call last):
...
LookupError: label 'hitespace'
...
>>> browser.getControl('[non word characters should not confuse]')
<Control name='non-word-characters' type='text'>

Expand Down Expand Up @@ -579,6 +586,9 @@ Get also accepts one other search argument, 'name'. Only one of 'label' and
Traceback (most recent call last):
...
LookupError: name 'does-not-exist'
available items:
<TextControl(text-value=Some Text)>
...
>>> browser.getControl(name='ambiguous-control-name', index=1).value
'Second'

Expand Down Expand Up @@ -1162,6 +1172,19 @@ But when sending an image, you can also specify the coordinate you clicked:
</html>


Pages Without Controls
~~~~~~~~~~~~~~~~~~~~~~

What would happen if we tried to look up a control on a page that has none?

>>> browser.open('http://localhost/@@/testbrowser/simple.html')
>>> browser.getControl('anything')
Traceback (most recent call last):
...
LookupError: label 'anything'
(there are no form items in the HTML)


Forms
-----

Expand Down
60 changes: 37 additions & 23 deletions src/zope/testbrowser/browser.py
Expand Up @@ -33,7 +33,7 @@
_compress_re = re.compile(r"\s+")
compressText = lambda text: _compress_re.sub(' ', text.strip())

def disambiguate(intermediate, msg, index, choice_repr=None):
def disambiguate(intermediate, msg, index, choice_repr=None, available=None):
if intermediate:
if index is None:
if len(intermediate) > 1:
Expand All @@ -53,6 +53,13 @@ def disambiguate(intermediate, msg, index, choice_repr=None):
if choice_repr:
msg += ''.join(['\n %d: %s' % (n, choice_repr(choice))
for n, choice in enumerate(intermediate)])
else:
if available:
msg += '\navailable items:' + ''.join([
'\n %s' % choice_repr(choice)
for choice in available])
elif available is not None: # empty list
msg += '\n(there are no form items in the HTML)'
raise LookupError(msg)

def control_form_tuple_repr((ctrl, form)):
Expand Down Expand Up @@ -348,29 +355,27 @@ def follow(self, *args, **kw):
"""Select a link and follow it."""
self.getLink(*args, **kw).click()

def _findByLabel(self, label, forms, include_subcontrols=False):
# forms are iterable of mech_forms
matches = re.compile(r'(^|\b|\W)%s(\b|\W|$)'
% re.escape(compressText(label))).search
found = []
def _findAllControls(self, forms, include_subcontrols=False):
for f in forms:
for control in f.controls:
phantom = control.type in ('radio', 'checkbox')
if not phantom:
for l in control.get_labels():
if matches(l.text):
found.append((control, f))
break
yield (control, f)
if include_subcontrols and (
phantom or control.type=='select'):

for i in control.items:
for l in i.get_labels():
if matches(l.text):
found.append((i, f))
found_one = True
break
yield (i, f)

def _findByLabel(self, label, forms, include_subcontrols=False):
# forms are iterable of mech_forms
matches = re.compile(r'(^|\b|\W)%s(\b|\W|$)'
% re.escape(compressText(label))).search
found = []
for control, form in self._findAllControls(forms, include_subcontrols):
for l in control.get_labels():
if matches(l.text):
found.append((control, form))
break
return found

def _findByName(self, name, forms):
Expand All @@ -383,22 +388,29 @@ def _findByName(self, name, forms):

def getControl(self, label=None, name=None, index=None):
"""See zope.testbrowser.interfaces.IBrowser"""
intermediate, msg = self._get_all_controls(
intermediate, msg, available = self._get_all_controls(
label, name, self.mech_browser.forms(), include_subcontrols=True)
control, form = disambiguate(intermediate, msg, index,
control_form_tuple_repr)
control_form_tuple_repr,
available)
return controlFactory(control, form, self)

def _get_all_controls(self, label, name, forms, include_subcontrols=False):
onlyOne([label, name], '"label" and "name"')

forms = list(forms) # might be an iterator, and we need to iterate twice

available = None
if label is not None:
res = self._findByLabel(label, forms, include_subcontrols)
msg = 'label %r' % label
elif name is not None:
include_subcontrols = False
res = self._findByName(name, forms)
msg = 'name %r' % name
return res, msg
if not res:
available = list(self._findAllControls(forms, include_subcontrols))
return res, msg, available

def getForm(self, id=None, name=None, action=None, index=None):
zeroOrOne([id, name, action], '"id", "name", and "action"')
Expand Down Expand Up @@ -767,13 +779,14 @@ def submit(self, label=None, name=None, index=None, coord=(1,1)):
form = self.mech_form
try:
if label is not None or name is not None:
intermediate, msg = self.browser._get_all_controls(
intermediate, msg, available = self.browser._get_all_controls(
label, name, (form,))
intermediate = [
(control, form) for (control, form) in intermediate if
control.type in ('submit', 'submitbutton', 'image')]
control, form = disambiguate(intermediate, msg, index,
control_form_tuple_repr)
control_form_tuple_repr,
available)
self.browser._clickSubmit(form, control, coord)
else: # JavaScript sort of submit
if index is not None or coord != (1,1):
Expand All @@ -796,8 +809,9 @@ def getControl(self, label=None, name=None, index=None):
"""See zope.testbrowser.interfaces.IBrowser"""
if self._browser_counter != self.browser._counter:
raise zope.testbrowser.interfaces.ExpiredError
intermediate, msg = self.browser._get_all_controls(
intermediate, msg, available = self.browser._get_all_controls(
label, name, (self.mech_form,), include_subcontrols=True)
control, form = disambiguate(intermediate, msg, index,
control_form_tuple_repr)
control_form_tuple_repr,
available)
return controlFactory(control, form, self.browser)

0 comments on commit be63fe1

Please sign in to comment.