[Checkins] SVN: zope.testbrowser/trunk/ Make getControl() and friends list all matching items when your query fails.

Marius Gedminas marius at pov.lt
Tue Feb 7 19:41:01 UTC 2012


Log message for revision 124329:
  Make getControl() and friends list all matching items when your query fails.
  
  In my experience this ought to make debugging failed testbrowser tests a
  lot less painful.

Changed:
  U   zope.testbrowser/trunk/CHANGES.txt
  U   zope.testbrowser/trunk/src/zope/testbrowser/README.txt
  U   zope.testbrowser/trunk/src/zope/testbrowser/browser.py

-=-
Modified: zope.testbrowser/trunk/CHANGES.txt
===================================================================
--- zope.testbrowser/trunk/CHANGES.txt	2012-02-07 19:19:10 UTC (rev 124328)
+++ zope.testbrowser/trunk/CHANGES.txt	2012-02-07 19:41:01 UTC (rev 124329)
@@ -12,10 +12,14 @@
   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)
 ------------------
 

Modified: zope.testbrowser/trunk/src/zope/testbrowser/README.txt
===================================================================
--- zope.testbrowser/trunk/src/zope/testbrowser/README.txt	2012-02-07 19:19:10 UTC (rev 124328)
+++ zope.testbrowser/trunk/src/zope/testbrowser/README.txt	2012-02-07 19:41:01 UTC (rev 124329)
@@ -481,6 +481,11 @@
     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.
@@ -536,6 +541,7 @@
     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')
@@ -544,6 +550,7 @@
     Traceback (most recent call last):
     ...
     LookupError: label 'hitespace'
+    ...
     >>> browser.getControl('[non word characters should not confuse]')
     <Control name='non-word-characters' type='text'>
 
@@ -579,6 +586,9 @@
     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'
 
@@ -1162,6 +1172,19 @@
     </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
 -----
 

Modified: zope.testbrowser/trunk/src/zope/testbrowser/browser.py
===================================================================
--- zope.testbrowser/trunk/src/zope/testbrowser/browser.py	2012-02-07 19:19:10 UTC (rev 124328)
+++ zope.testbrowser/trunk/src/zope/testbrowser/browser.py	2012-02-07 19:41:01 UTC (rev 124329)
@@ -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:
@@ -53,6 +53,13 @@
                 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)):
@@ -348,29 +355,27 @@
         """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):
@@ -383,22 +388,29 @@
 
     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"')
@@ -767,13 +779,14 @@
         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):
@@ -796,8 +809,9 @@
         """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)



More information about the checkins mailing list