Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #16921 -- Added assertHTMLEqual and assertHTMLNotEqual assertio…

…ns, and converted Django tests to use them where appropriate. Thanks Greg Müllegger.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17414 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 844a24bbb97af663ebf8dbeab4499acafe105943 1 parent c82f1dc
Carl Meyer authored January 31, 2012

Showing 61 changed files with 1,345 additions and 639 deletions. Show diff stats Hide diff stats

  1. 221  django/test/html.py
  2. 66  django/test/testcases.py
  3. 94  django/utils/htmlparser.py
  4. 15  docs/releases/1.4.txt
  5. 62  docs/topics/testing.txt
  6. 8  tests/modeltests/generic_relations/tests.py
  7. 48  tests/modeltests/model_forms/tests.py
  8. 84  tests/modeltests/model_formsets/tests.py
  9. 6  tests/regressiontests/admin_inlines/tests.py
  10. 51  tests/regressiontests/admin_views/tests.py
  11. 38  tests/regressiontests/admin_widgets/tests.py
  12. 11  tests/regressiontests/forms/tests/error_messages.py
  13. 35  tests/regressiontests/forms/tests/extra.py
  14. 3  tests/regressiontests/forms/tests/fields.py
  15. 357  tests/regressiontests/forms/tests/forms.py
  16. 38  tests/regressiontests/forms/tests/formsets.py
  17. 4  tests/regressiontests/forms/tests/models.py
  18. 28  tests/regressiontests/forms/tests/regressions.py
  19. 27  tests/regressiontests/forms/tests/util.py
  20. 404  tests/regressiontests/forms/tests/widgets.py
  21. 16  tests/regressiontests/generic_inline_admin/tests.py
  22. 10  tests/regressiontests/i18n/tests.py
  23. 2  tests/regressiontests/localflavor/ar/tests.py
  24. 2  tests/regressiontests/localflavor/at/tests.py
  25. 2  tests/regressiontests/localflavor/au/tests.py
  26. 4  tests/regressiontests/localflavor/be/tests.py
  27. 2  tests/regressiontests/localflavor/br/tests.py
  28. 2  tests/regressiontests/localflavor/ca/tests.py
  29. 2  tests/regressiontests/localflavor/ch/tests.py
  30. 2  tests/regressiontests/localflavor/cl/tests.py
  31. 2  tests/regressiontests/localflavor/cn/tests.py
  32. 2  tests/regressiontests/localflavor/co/tests.py
  33. 2  tests/regressiontests/localflavor/cz/tests.py
  34. 2  tests/regressiontests/localflavor/de/tests.py
  35. 2  tests/regressiontests/localflavor/ec/tests.py
  36. 4  tests/regressiontests/localflavor/es/tests.py
  37. 2  tests/regressiontests/localflavor/fi/tests.py
  38. 2  tests/regressiontests/localflavor/fr/tests.py
  39. 6  tests/regressiontests/localflavor/hr/tests.py
  40. 4  tests/regressiontests/localflavor/id/tests.py
  41. 2  tests/regressiontests/localflavor/ie/tests.py
  42. 2  tests/regressiontests/localflavor/in_/tests.py
  43. 2  tests/regressiontests/localflavor/is_/tests.py
  44. 2  tests/regressiontests/localflavor/it/tests.py
  45. 2  tests/regressiontests/localflavor/jp/tests.py
  46. 4  tests/regressiontests/localflavor/mk/tests.py
  47. 4  tests/regressiontests/localflavor/mx/tests.py
  48. 2  tests/regressiontests/localflavor/nl/tests.py
  49. 4  tests/regressiontests/localflavor/pl/tests.py
  50. 4  tests/regressiontests/localflavor/py/tests.py
  51. 2  tests/regressiontests/localflavor/ro/tests.py
  52. 4  tests/regressiontests/localflavor/ru/tests.py
  53. 2  tests/regressiontests/localflavor/se/tests.py
  54. 2  tests/regressiontests/localflavor/si/tests.py
  55. 4  tests/regressiontests/localflavor/sk/tests.py
  56. 6  tests/regressiontests/localflavor/us/tests.py
  57. 2  tests/regressiontests/localflavor/uy/tests.py
  58. 2  tests/regressiontests/model_forms_regress/tests.py
  59. 4  tests/regressiontests/modeladmin/tests.py
  60. 254  tests/regressiontests/test_utils/tests.py
  61. 2  tests/regressiontests/views/tests/generic/create_update.py
221  django/test/html.py
... ...
@@ -0,0 +1,221 @@
  1
+"""
  2
+Comparing two html documents.
  3
+"""
  4
+import re
  5
+from HTMLParser import HTMLParseError
  6
+from django.utils.encoding import force_unicode
  7
+from django.utils.htmlparser import HTMLParser
  8
+
  9
+
  10
+WHITESPACE = re.compile('\s+')
  11
+
  12
+
  13
+def normalize_whitespace(string):
  14
+    return WHITESPACE.sub(' ', string)
  15
+
  16
+
  17
+class Element(object):
  18
+    def __init__(self, name, attributes):
  19
+        self.name = name
  20
+        self.attributes = sorted(attributes)
  21
+        self.children = []
  22
+
  23
+    def append(self, element):
  24
+        if isinstance(element, basestring):
  25
+            element = force_unicode(element)
  26
+            element = normalize_whitespace(element)
  27
+            if self.children:
  28
+                if isinstance(self.children[-1], basestring):
  29
+                    self.children[-1] += element
  30
+                    self.children[-1] = normalize_whitespace(self.children[-1])
  31
+                    return
  32
+        elif self.children:
  33
+            # removing last children if it is only whitespace
  34
+            # this can result in incorrect dom representations since
  35
+            # whitespace between inline tags like <span> is significant
  36
+            if isinstance(self.children[-1], basestring):
  37
+                if self.children[-1].isspace():
  38
+                    self.children.pop()
  39
+        if element:
  40
+            self.children.append(element)
  41
+
  42
+    def finalize(self):
  43
+        def rstrip_last_element(children):
  44
+            if children:
  45
+                if isinstance(children[-1], basestring):
  46
+                    children[-1] = children[-1].rstrip()
  47
+                    if not children[-1]:
  48
+                        children.pop()
  49
+                        children = rstrip_last_element(children)
  50
+            return children
  51
+
  52
+        rstrip_last_element(self.children)
  53
+        for i, child in enumerate(self.children):
  54
+            if isinstance(child, basestring):
  55
+                self.children[i] = child.strip()
  56
+            elif hasattr(child, 'finalize'):
  57
+                child.finalize()
  58
+
  59
+    def __eq__(self, element):
  60
+        if not hasattr(element, 'name'):
  61
+            return False
  62
+        if hasattr(element, 'name') and self.name != element.name:
  63
+            return False
  64
+        if len(self.attributes) != len(element.attributes):
  65
+            return False
  66
+        if self.attributes != element.attributes:
  67
+            # attributes without a value is same as attribute with value that
  68
+            # equals the attributes name:
  69
+            # <input checked> == <input checked="checked">
  70
+            for i in range(len(self.attributes)):
  71
+                attr, value = self.attributes[i]
  72
+                other_attr, other_value = element.attributes[i]
  73
+                if value is None:
  74
+                    value = attr
  75
+                if other_value is None:
  76
+                    other_value = other_attr
  77
+                if attr != other_attr or value != other_value:
  78
+                    return False
  79
+        if self.children != element.children:
  80
+            return False
  81
+        return True
  82
+
  83
+    def __ne__(self, element):
  84
+        return not self.__eq__(element)
  85
+
  86
+    def _count(self, element, count=True):
  87
+        if not isinstance(element, basestring):
  88
+            if self == element:
  89
+                return 1
  90
+        i = 0
  91
+        for child in self.children:
  92
+            # child is text content and element is also text content, then
  93
+            # make a simple "text" in "text"
  94
+            if isinstance(child, basestring):
  95
+                if isinstance(element, basestring):
  96
+                    if count:
  97
+                        i += child.count(element)
  98
+                    elif element in child:
  99
+                        return 1
  100
+            else:
  101
+                i += child._count(element, count=count)
  102
+                if not count and i:
  103
+                    return i
  104
+        return i
  105
+
  106
+    def __contains__(self, element):
  107
+        return self._count(element, count=False) > 0
  108
+
  109
+    def count(self, element):
  110
+        return self._count(element, count=True)
  111
+
  112
+    def __getitem__(self, key):
  113
+        return self.children[key]
  114
+
  115
+    def __unicode__(self):
  116
+        output = u'<%s' % self.name
  117
+        for key, value in self.attributes:
  118
+            if value:
  119
+                output += u' %s="%s"' % (key, value)
  120
+            else:
  121
+                output += u' %s' % key
  122
+        if self.children:
  123
+            output += u'>\n'
  124
+            output += u''.join(unicode(c) for c in self.children)
  125
+            output += u'\n</%s>' % self.name
  126
+        else:
  127
+            output += u' />'
  128
+        return output
  129
+
  130
+    def __repr__(self):
  131
+        return unicode(self)
  132
+
  133
+
  134
+class RootElement(Element):
  135
+    def __init__(self):
  136
+        super(RootElement, self).__init__(None, ())
  137
+
  138
+    def __unicode__(self):
  139
+        return u''.join(unicode(c) for c in self.children)
  140
+
  141
+
  142
+class Parser(HTMLParser):
  143
+    SELF_CLOSING_TAGS = ('br' , 'hr', 'input', 'img', 'meta', 'spacer',
  144
+        'link', 'frame', 'base', 'col')
  145
+
  146
+    def __init__(self):
  147
+        HTMLParser.__init__(self)
  148
+        self.root = RootElement()
  149
+        self.open_tags = []
  150
+        self.element_positions = {}
  151
+
  152
+    def error(self, msg):
  153
+        raise HTMLParseError(msg, self.getpos())
  154
+
  155
+    def format_position(self, position=None, element=None):
  156
+        if not position and element:
  157
+            position = self.element_positions[element]
  158
+        if position is None:
  159
+            position = self.getpos()
  160
+        if hasattr(position, 'lineno'):
  161
+            position = position.lineno, position.offset
  162
+        return 'Line %d, Column %d' % position
  163
+
  164
+    @property
  165
+    def current(self):
  166
+        if self.open_tags:
  167
+            return self.open_tags[-1]
  168
+        else:
  169
+            return self.root
  170
+
  171
+    def handle_startendtag(self, tag, attrs):
  172
+        self.handle_starttag(tag, attrs)
  173
+        if tag not in self.SELF_CLOSING_TAGS:
  174
+            self.handle_endtag(tag)
  175
+
  176
+    def handle_starttag(self, tag, attrs):
  177
+        element = Element(tag, attrs)
  178
+        self.current.append(element)
  179
+        if tag not in self.SELF_CLOSING_TAGS:
  180
+            self.open_tags.append(element)
  181
+        self.element_positions[element] = self.getpos()
  182
+
  183
+    def handle_endtag(self, tag):
  184
+        if not self.open_tags:
  185
+            self.error("Unexpected end tag `%s` (%s)" % (
  186
+                tag, self.format_position()))
  187
+        element = self.open_tags.pop()
  188
+        while element.name != tag:
  189
+            if not self.open_tags:
  190
+                self.error("Unexpected end tag `%s` (%s)" % (
  191
+                    tag, self.format_position()))
  192
+            element = self.open_tags.pop()
  193
+
  194
+    def handle_data(self, data):
  195
+        self.current.append(data)
  196
+
  197
+    def handle_charref(self, name):
  198
+        self.current.append('&%s;' % name)
  199
+
  200
+    def handle_entityref(self, name):
  201
+        self.current.append('&%s;' % name)
  202
+
  203
+
  204
+def parse_html(html):
  205
+    """
  206
+    Takes a string that contains *valid* HTML and turns it into a Python object
  207
+    structure that can be easily compared against other HTML on semantic
  208
+    equivilance. Syntactical differences like which quotation is used on
  209
+    arguments will be ignored.
  210
+
  211
+    """
  212
+    parser = Parser()
  213
+    parser.feed(html)
  214
+    parser.close()
  215
+    document = parser.root
  216
+    document.finalize()
  217
+    # Removing ROOT element if it's not necessary
  218
+    if len(document.children) == 1:
  219
+        if not isinstance(document.children[0], basestring):
  220
+            document = document.children[0]
  221
+    return document
66  django/test/testcases.py
... ...
@@ -1,5 +1,6 @@
1 1
 from __future__ import with_statement
2 2
 
  3
+import difflib
3 4
 import os
4 5
 import re
5 6
 import sys
@@ -29,12 +30,14 @@
29 30
 from django.http import QueryDict
30 31
 from django.test import _doctest as doctest
31 32
 from django.test.client import Client
  33
+from django.test.html import HTMLParseError, parse_html
32 34
 from django.test.signals import template_rendered
33 35
 from django.test.utils import (get_warnings_state, restore_warnings_state,
34 36
     override_settings)
35 37
 from django.test.utils import ContextList
36 38
 from django.utils import simplejson, unittest as ut2
37 39
 from django.utils.encoding import smart_str, force_unicode
  40
+from django.utils.unittest.util import safe_repr
38 41
 from django.views.static import serve
39 42
 
40 43
 __all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase',
@@ -78,6 +81,16 @@ def restore_transaction_methods():
78 81
     transaction.leave_transaction_management = real_leave_transaction_management
79 82
     transaction.managed = real_managed
80 83
 
  84
+
  85
+def assert_and_parse_html(self, html, user_msg, msg):
  86
+    try:
  87
+        dom = parse_html(html)
  88
+    except HTMLParseError, e:
  89
+        standardMsg = u'%s\n%s' % (msg, e.msg)
  90
+        self.fail(self._formatMessage(user_msg, standardMsg))
  91
+    return dom
  92
+
  93
+
81 94
 class OutputChecker(doctest.OutputChecker):
82 95
     def check_output(self, want, got, optionflags):
83 96
         """
@@ -396,6 +409,39 @@ def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
396 409
             self.assertTrue(isinstance(fieldclass(*field_args, **field_kwargs),
397 410
                                        fieldclass))
398 411
 
  412
+    def assertHTMLEqual(self, html1, html2, msg=None):
  413
+        """
  414
+        Asserts that two html snippets are semantically the same,
  415
+        e.g. whitespace in most cases is ignored, attribute ordering is not
  416
+        significant. The passed in arguments must be valid HTML.
  417
+
  418
+        """
  419
+        dom1 = assert_and_parse_html(self, html1, msg,
  420
+            u'First argument is not valid html:')
  421
+        dom2 = assert_and_parse_html(self, html2, msg,
  422
+            u'Second argument is not valid html:')
  423
+
  424
+        if dom1 != dom2:
  425
+            standardMsg = '%s != %s' % (
  426
+                safe_repr(dom1, True), safe_repr(dom2, True))
  427
+            diff = ('\n' + '\n'.join(difflib.ndiff(
  428
+                           unicode(dom1).splitlines(),
  429
+                           unicode(dom2).splitlines())))
  430
+            standardMsg = self._truncateMessage(standardMsg, diff)
  431
+            self.fail(self._formatMessage(msg, standardMsg))
  432
+
  433
+    def assertHTMLNotEqual(self, html1, html2, msg=None):
  434
+        """Asserts that two HTML snippets are not semantically equivalent."""
  435
+        dom1 = assert_and_parse_html(self, html1, msg,
  436
+            u'First argument is not valid html:')
  437
+        dom2 = assert_and_parse_html(self, html2, msg,
  438
+            u'Second argument is not valid html:')
  439
+
  440
+        if dom1 == dom2:
  441
+            standardMsg = '%s == %s' % (
  442
+                safe_repr(dom1, True), safe_repr(dom2, True))
  443
+            self.fail(self._formatMessage(msg, standardMsg))
  444
+
399 445
 
400 446
 class TransactionTestCase(SimpleTestCase):
401 447
     # The class we'll use for the test client self.client.
@@ -554,7 +600,7 @@ def assertRedirects(self, response, expected_url, status_code=302,
554 600
                 (url, expected_url))
555 601
 
556 602
     def assertContains(self, response, text, count=None, status_code=200,
557  
-                       msg_prefix=''):
  603
+                       msg_prefix='', html=False):
558 604
         """
559 605
         Asserts that a response indicates that some content was retrieved
560 606
         successfully, (i.e., the HTTP status code was as expected), and that
@@ -576,7 +622,13 @@ def assertContains(self, response, text, count=None, status_code=200,
576 622
             msg_prefix + "Couldn't retrieve content: Response code was %d"
577 623
             " (expected %d)" % (response.status_code, status_code))
578 624
         text = smart_str(text, response._charset)
579  
-        real_count = response.content.count(text)
  625
+        content = response.content
  626
+        if html:
  627
+            content = assert_and_parse_html(self, content, None,
  628
+                u"Response's content is not valid html:")
  629
+            text = assert_and_parse_html(self, text, None,
  630
+                u"Second argument is not valid html:")
  631
+        real_count = content.count(text)
580 632
         if count is not None:
581 633
             self.assertEqual(real_count, count,
582 634
                 msg_prefix + "Found %d instances of '%s' in response"
@@ -586,7 +638,7 @@ def assertContains(self, response, text, count=None, status_code=200,
586 638
                 msg_prefix + "Couldn't find '%s' in response" % text)
587 639
 
588 640
     def assertNotContains(self, response, text, status_code=200,
589  
-                          msg_prefix=''):
  641
+                          msg_prefix='', html=False):
590 642
         """
591 643
         Asserts that a response indicates that some content was retrieved
592 644
         successfully, (i.e., the HTTP status code was as expected), and that
@@ -606,7 +658,13 @@ def assertNotContains(self, response, text, status_code=200,
606 658
             msg_prefix + "Couldn't retrieve content: Response code was %d"
607 659
             " (expected %d)" % (response.status_code, status_code))
608 660
         text = smart_str(text, response._charset)
609  
-        self.assertEqual(response.content.count(text), 0,
  661
+        content = response.content
  662
+        if html:
  663
+            content = assert_and_parse_html(self, content, None,
  664
+                u'Response\'s content is no valid html:')
  665
+            text = assert_and_parse_html(self, text, None,
  666
+                u'Second argument is no valid html:')
  667
+        self.assertEqual(content.count(text), 0,
610 668
             msg_prefix + "Response should not contain '%s'" % text)
611 669
 
612 670
     def assertFormError(self, response, form, field, errors, msg_prefix=''):
94  django/utils/htmlparser.py
... ...
@@ -0,0 +1,94 @@
  1
+import HTMLParser as _HTMLParser
  2
+
  3
+
  4
+class HTMLParser(_HTMLParser.HTMLParser):
  5
+    """
  6
+    Patched version of stdlib's HTMLParser with patch from:
  7
+    http://bugs.python.org/issue670664
  8
+    """
  9
+    def __init__(self):
  10
+        _HTMLParser.HTMLParser.__init__(self)
  11
+        self.cdata_tag = None
  12
+
  13
+    def set_cdata_mode(self, tag):
  14
+        self.interesting = _HTMLParser.interesting_cdata
  15
+        self.cdata_tag = tag.lower()
  16
+
  17
+    def clear_cdata_mode(self):
  18
+        self.interesting = _HTMLParser.interesting_normal
  19
+        self.cdata_tag = None
  20
+
  21
+    # Internal -- handle starttag, return end or -1 if not terminated
  22
+    def parse_starttag(self, i):
  23
+        self.__starttag_text = None
  24
+        endpos = self.check_for_whole_start_tag(i)
  25
+        if endpos < 0:
  26
+            return endpos
  27
+        rawdata = self.rawdata
  28
+        self.__starttag_text = rawdata[i:endpos]
  29
+
  30
+        # Now parse the data between i+1 and j into a tag and attrs
  31
+        attrs = []
  32
+        match = _HTMLParser.tagfind.match(rawdata, i + 1)
  33
+        assert match, 'unexpected call to parse_starttag()'
  34
+        k = match.end()
  35
+        self.lasttag = tag = rawdata[i + 1:k].lower()
  36
+
  37
+        while k < endpos:
  38
+            m = _HTMLParser.attrfind.match(rawdata, k)
  39
+            if not m:
  40
+                break
  41
+            attrname, rest, attrvalue = m.group(1, 2, 3)
  42
+            if not rest:
  43
+                attrvalue = None
  44
+            elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
  45
+                 attrvalue[:1] == '"' == attrvalue[-1:]:
  46
+                attrvalue = attrvalue[1:-1]
  47
+                attrvalue = self.unescape(attrvalue)
  48
+            attrs.append((attrname.lower(), attrvalue))
  49
+            k = m.end()
  50
+
  51
+        end = rawdata[k:endpos].strip()
  52
+        if end not in (">", "/>"):
  53
+            lineno, offset = self.getpos()
  54
+            if "\n" in self.__starttag_text:
  55
+                lineno = lineno + self.__starttag_text.count("\n")
  56
+                offset = len(self.__starttag_text) \
  57
+                         - self.__starttag_text.rfind("\n")
  58
+            else:
  59
+                offset = offset + len(self.__starttag_text)
  60
+            self.error("junk characters in start tag: %r"
  61
+                       % (rawdata[k:endpos][:20],))
  62
+        if end.endswith('/>'):
  63
+            # XHTML-style empty tag: <span attr="value" />
  64
+            self.handle_startendtag(tag, attrs)
  65
+        else:
  66
+            self.handle_starttag(tag, attrs)
  67
+            if tag in self.CDATA_CONTENT_ELEMENTS:
  68
+                self.set_cdata_mode(tag) # <--------------------------- Changed
  69
+        return endpos
  70
+
  71
+    # Internal -- parse endtag, return end or -1 if incomplete
  72
+    def parse_endtag(self, i):
  73
+        rawdata = self.rawdata
  74
+        assert rawdata[i:i + 2] == "</", "unexpected call to parse_endtag"
  75
+        match = _HTMLParser.endendtag.search(rawdata, i + 1) # >
  76
+        if not match:
  77
+            return -1
  78
+        j = match.end()
  79
+        match = _HTMLParser.endtagfind.match(rawdata, i) # </ + tag + >
  80
+        if not match:
  81
+            if self.cdata_tag is not None: # *** add ***
  82
+                self.handle_data(rawdata[i:j]) # *** add ***
  83
+                return j # *** add ***
  84
+            self.error("bad end tag: %r" % (rawdata[i:j],))
  85
+        # --- changed start ---------------------------------------------------
  86
+        tag = match.group(1).strip()
  87
+        if self.cdata_tag is not None:
  88
+            if tag.lower() != self.cdata_tag:
  89
+                self.handle_data(rawdata[i:j])
  90
+                return j
  91
+        # --- changed end -----------------------------------------------------
  92
+        self.handle_endtag(tag.lower())
  93
+        self.clear_cdata_mode()
  94
+        return j
15  docs/releases/1.4.txt
@@ -475,6 +475,21 @@ Time zone support is enabled by default in new projects created with
475 475
 :djadmin:`startproject`. If you want to use this feature in an existing
476 476
 project, read the :ref:`migration guide <time-zones-migration-guide>`.
477 477
 
  478
+HTML comparisons in tests
  479
+~~~~~~~~~~~~~~~~~~~~~~~~~
  480
+
  481
+The :class:`~django.test.testcase.TestCase` base class now has some helpers to
  482
+compare HTML without tripping over irrelevant differences in whitespace,
  483
+argument quoting and ordering, and closing of self-closing tags. HTML can
  484
+either be compared directly with the new
  485
+:meth:`~django.test.testcase.TestCase.assertHTMLEqual` and
  486
+:meth:`~django.test.testcase.TestCase.assertHTMLNotEqual` assertions, or use
  487
+the ``html=True`` flag with
  488
+:meth:`~django.test.testcase.TestCase.assertContains` and
  489
+:meth:`~django.test.testcase.TestCase.assertNotContains` to test if the test
  490
+client's response contains a given HTML fragment. See the :ref:`assertion
  491
+documentation<assertions>` for more information.
  492
+
478 493
 Minor features
479 494
 ~~~~~~~~~~~~~~
480 495
 
62  docs/topics/testing.txt
@@ -1542,17 +1542,33 @@ your test suite.
1542 1542
         self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': [u'Enter a valid e-mail address.']})
1543 1543
 
1544 1544
 
1545  
-.. method:: TestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='')
  1545
+.. method:: TestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
1546 1546
 
1547 1547
     Asserts that a ``Response`` instance produced the given ``status_code`` and
1548 1548
     that ``text`` appears in the content of the response. If ``count`` is
1549 1549
     provided, ``text`` must occur exactly ``count`` times in the response.
1550 1550
 
1551  
-.. method:: TestCase.assertNotContains(response, text, status_code=200, msg_prefix='')
  1551
+    .. versionadded:: 1.4
  1552
+
  1553
+    Set ``html`` to ``True`` to handle ``text`` as HTML. The comparison with
  1554
+    the response content will be based on HTML semantics instead of
  1555
+    character-by-character equality. Whitespace is ignored in most cases,
  1556
+    attribute ordering is not significant. See
  1557
+    :func:`~TestCase.assertHTMLEqual` for more details.
  1558
+
  1559
+.. method:: TestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
1552 1560
 
1553 1561
     Asserts that a ``Response`` instance produced the given ``status_code`` and
1554 1562
     that ``text`` does not appears in the content of the response.
1555 1563
 
  1564
+    .. versionadded:: 1.4
  1565
+
  1566
+    Set ``html`` to ``True`` to handle ``text`` as HTML. The comparison with
  1567
+    the response content will be based on HTML semantics instead of
  1568
+    character-by-character equality. Whitespace is ignored in most cases,
  1569
+    attribute ordering is not significant. See
  1570
+    :func:`~TestCase.assertHTMLEqual` for more details.
  1571
+
1556 1572
 .. method:: TestCase.assertFormError(response, form, field, errors, msg_prefix='')
1557 1573
 
1558 1574
     Asserts that a field on a form raises the provided list of errors when
@@ -1656,6 +1672,48 @@ your test suite.
1656 1672
             Person.objects.create(name="Aaron")
1657 1673
             Person.objects.create(name="Daniel")
1658 1674
 
  1675
+.. method:: TestCase.assertHTMLEqual(html1, html2, msg=None)
  1676
+
  1677
+    .. versionadded:: 1.4
  1678
+
  1679
+    Asserts that the strings ``html1`` and ``html2`` are equal. The comparison
  1680
+    is based on HTML semantics. The comparison takes following things into
  1681
+    account:
  1682
+
  1683
+    * Whitespace before and after HTML tags is ignored
  1684
+    * All types of whitespace are considered equivalent
  1685
+    * All open tags are closed implicitly, i.e. when a surrounding tag is
  1686
+      closed or the HTML document ends
  1687
+    * Empty tags are equivalent to their self-closing version
  1688
+    * The ordering of attributes of an HTML element is not significant
  1689
+    * Attributes without an argument are equal to attributes that equal in
  1690
+      name and value (see the examples)
  1691
+
  1692
+    The following examples are valid tests and don't raise any
  1693
+    ``AssertionError``::
  1694
+
  1695
+        self.assertHTMLEqual('<p>Hello <b>world!</p>',
  1696
+            '''<p>
  1697
+                Hello   <b>world! <b/>
  1698
+            </p>''')
  1699
+        self.assertHTMLEqual(
  1700
+            '<input type="checkbox" checked="checked" id="id_accept_terms" />',
  1701
+            '<input id="id_accept_terms" type='checkbox' checked>')
  1702
+
  1703
+    ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
  1704
+    raised if one of them cannot be parsed.
  1705
+
  1706
+.. method:: TestCase.assertHTMLNotEqual(html1, html2, msg=None)
  1707
+
  1708
+    .. versionadded:: 1.4
  1709
+
  1710
+    Asserts that the strings ``html1`` and ``html2`` are *not* equal. The
  1711
+    comparison is based on HTML semantics. See
  1712
+    :func:`~TestCase.assertHTMLEqual` for details.
  1713
+
  1714
+    ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
  1715
+    raised if one of them cannot be parsed.
  1716
+
1659 1717
 
1660 1718
 .. _topics-testing-email:
1661 1719
 
8  tests/modeltests/generic_relations/tests.py
@@ -200,11 +200,11 @@ def test_gfk_subclasses(self):
200 200
     def test_generic_inline_formsets(self):
201 201
         GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
202 202
         formset = GenericFormSet()
203  
-        self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
  203
+        self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
204 204
 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>""")
205 205
 
206 206
         formset = GenericFormSet(instance=Animal())
207  
-        self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
  207
+        self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
208 208
 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>""")
209 209
 
210 210
         platypus = Animal.objects.create(
@@ -216,13 +216,13 @@ def test_generic_inline_formsets(self):
216 216
         tagged_item_id = TaggedItem.objects.get(
217 217
             tag='shiny', object_id=platypus.id
218 218
         ).id
219  
-        self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50" /></p>
  219
+        self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50" /></p>
220 220
 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="%s" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p><p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50" /></p>
221 221
 <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>""" % tagged_item_id)
222 222
 
223 223
         lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo")
224 224
         formset = GenericFormSet(instance=lion, prefix='x')
225  
-        self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_x-0-tag">Tag:</label> <input id="id_x-0-tag" type="text" name="x-0-tag" maxlength="50" /></p>
  225
+        self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""<p><label for="id_x-0-tag">Tag:</label> <input id="id_x-0-tag" type="text" name="x-0-tag" maxlength="50" /></p>
226 226
 <p><label for="id_x-0-DELETE">Delete:</label> <input type="checkbox" name="x-0-DELETE" id="id_x-0-DELETE" /><input type="hidden" name="x-0-id" id="id_x-0-id" /></p>""")
227 227
 
228 228
     def test_gfk_manager(self):
48  tests/modeltests/model_forms/tests.py
@@ -298,7 +298,7 @@ class SubclassMeta(SomeCategoryForm):
298 298
             class Meta(SomeCategoryForm.Meta):
299 299
                 exclude = ['url']
300 300
 
301  
-        self.assertEqual(
  301
+        self.assertHTMLEqual(
302 302
             str(SubclassMeta()),
303 303
             """<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
304 304
 <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
@@ -313,7 +313,7 @@ class Meta:
313 313
 
314 314
         self.assertEqual(OrderFields.base_fields.keys(),
315 315
                          ['url', 'name'])
316  
-        self.assertEqual(
  316
+        self.assertHTMLEqual(
317 317
             str(OrderFields()),
318 318
             """<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>
319 319
 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>"""
@@ -344,15 +344,15 @@ class Meta:
344 344
 class TestWidgets(TestCase):
345 345
     def test_base_widgets(self):
346 346
         frm = TestWidgetForm()
347  
-        self.assertEqual(
  347
+        self.assertHTMLEqual(
348 348
             str(frm['name']),
349 349
             '<textarea id="id_name" rows="10" cols="40" name="name"></textarea>'
350 350
         )
351  
-        self.assertEqual(
  351
+        self.assertHTMLEqual(
352 352
             str(frm['url']),
353 353
             '<input id="id_url" type="text" class="url" name="url" maxlength="40" />'
354 354
         )
355  
-        self.assertEqual(
  355
+        self.assertHTMLEqual(
356 356
             str(frm['slug']),
357 357
             '<input id="id_slug" type="text" name="slug" maxlength="20" />'
358 358
         )
@@ -563,25 +563,25 @@ class OldFormForXTests(TestCase):
563 563
     def test_base_form(self):
564 564
         self.assertEqual(Category.objects.count(), 0)
565 565
         f = BaseCategoryForm()
566  
-        self.assertEqual(
  566
+        self.assertHTMLEqual(
567 567
             str(f),
568 568
             """<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
569 569
 <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
570 570
 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>"""
571 571
             )
572  
-        self.assertEqual(
  572
+        self.assertHTMLEqual(
573 573
             str(f.as_ul()),
574 574
             """<li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li>
575 575
 <li><label for="id_slug">Slug:</label> <input id="id_slug" type="text" name="slug" maxlength="20" /></li>
576 576
 <li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li>"""
577 577
             )
578  
-        self.assertEqual(
  578
+        self.assertHTMLEqual(
579 579
             str(f["name"]),
580 580
             """<input id="id_name" type="text" name="name" maxlength="20" />""")
581 581
 
582 582
     def test_auto_id(self):
583 583
         f = BaseCategoryForm(auto_id=False)
584  
-        self.assertEqual(
  584
+        self.assertHTMLEqual(
585 585
             str(f.as_ul()),
586 586
             """<li>Name: <input type="text" name="name" maxlength="20" /></li>
587 587
 <li>Slug: <input type="text" name="slug" maxlength="20" /></li>
@@ -653,7 +653,7 @@ def test_with_data(self):
653 653
         # ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any
654 654
         # fields with the 'choices' attribute are represented by a ChoiceField.
655 655
         f = ArticleForm(auto_id=False)
656  
-        self.assertEqual(unicode(f), '''<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
  656
+        self.assertHTMLEqual(unicode(f), '''<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
657 657
 <tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr>
658 658
 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
659 659
 <tr><th>Writer:</th><td><select name="writer">
@@ -681,14 +681,14 @@ def test_with_data(self):
681 681
         # a value of None. If a field isn't specified on a form, the object created
682 682
         # from the form can't provide a value for that field!
683 683
         f = PartialArticleForm(auto_id=False)
684  
-        self.assertEqual(unicode(f), '''<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
  684
+        self.assertHTMLEqual(unicode(f), '''<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
685 685
 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>''')
686 686
 
687 687
         # When the ModelForm is passed an instance, that instance's current values are
688 688
         # inserted as 'initial' data in each Field.
689 689
         w = Writer.objects.get(name='Mike Royko')
690 690
         f = RoykoForm(auto_id=False, instance=w)
691  
-        self.assertEqual(unicode(f), '''<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br /><span class="helptext">Use both first and last names.</span></td></tr>''')
  691
+        self.assertHTMLEqual(unicode(f), '''<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br /><span class="helptext">Use both first and last names.</span></td></tr>''')
692 692
 
693 693
         art = Article(
694 694
                     headline='Test article',
@@ -701,7 +701,7 @@ def test_with_data(self):
701 701
         art_id_1 = art.id
702 702
         self.assertEqual(art_id_1 is not None, True)
703 703
         f = TestArticleForm(auto_id=False, instance=art)
704  
-        self.assertEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
  704
+        self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
705 705
 <li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li>
706 706
 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
707 707
 <li>Writer: <select name="writer">
@@ -741,7 +741,7 @@ def test_with_data(self):
741 741
                 'slug': 'new-headline',
742 742
                 'pub_date': u'1988-01-04'
743 743
             }, auto_id=False, instance=art)
744  
-        self.assertEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
  744
+        self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
745 745
 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
746 746
 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>''')
747 747
         self.assertEqual(f.is_valid(), True)
@@ -755,7 +755,7 @@ def test_with_data(self):
755 755
         new_art.categories.add(Category.objects.get(name='Entertainment'))
756 756
         self.assertEqual(map(lambda o: o.name, new_art.categories.all()), ["Entertainment"])
757 757
         f = TestArticleForm(auto_id=False, instance=new_art)
758  
-        self.assertEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
  758
+        self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
759 759
 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
760 760
 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
761 761
 <li>Writer: <select name="writer">
@@ -783,7 +783,7 @@ def test_with_data(self):
783 783
                     'headline': 'Your headline here',
784 784
                     'categories': [str(c1.id), str(c2.id)]
785 785
                 })
786  
-        self.assertEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="Your headline here" maxlength="50" /></li>
  786
+        self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="Your headline here" maxlength="50" /></li>
787 787
 <li>Slug: <input type="text" name="slug" maxlength="50" /></li>
788 788
 <li>Pub date: <input type="text" name="pub_date" /></li>
789 789
 <li>Writer: <select name="writer">
@@ -877,7 +877,7 @@ def test_with_data(self):
877 877
         # at runtime, based on the data in the database when the form is displayed, not
878 878
         # the data in the database when the form is instantiated.
879 879
         f = ArticleForm(auto_id=False)
880  
-        self.assertEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
  880
+        self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
881 881
 <li>Slug: <input type="text" name="slug" maxlength="50" /></li>
882 882
 <li>Pub date: <input type="text" name="pub_date" /></li>
883 883
 <li>Writer: <select name="writer">
@@ -902,7 +902,7 @@ def test_with_data(self):
902 902
         self.assertEqual(c4.name, 'Fourth')
903 903
         w_bernstein = Writer.objects.create(name='Carl Bernstein')
904 904
         self.assertEqual(w_bernstein.name, 'Carl Bernstein')
905  
-        self.assertEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
  905
+        self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
906 906
 <li>Slug: <input type="text" name="slug" maxlength="50" /></li>
907 907
 <li>Pub date: <input type="text" name="pub_date" /></li>
908 908
 <li>Writer: <select name="writer">
@@ -1081,7 +1081,7 @@ def test_with_data(self):
1081 1081
         bw2.delete()
1082 1082
 
1083 1083
         form = WriterProfileForm()
1084  
-        self.assertEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer">
  1084
+        self.assertHTMLEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer">
1085 1085
 <option value="" selected="selected">---------</option>
1086 1086
 <option value="%s">Bob Woodward</option>
1087 1087
 <option value="%s">Carl Bernstein</option>
@@ -1099,7 +1099,7 @@ def test_with_data(self):
1099 1099
         self.assertEqual(unicode(instance), 'Bob Woodward is 65')
1100 1100
 
1101 1101
         form = WriterProfileForm(instance=instance)
1102  
-        self.assertEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer">
  1102
+        self.assertHTMLEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer">
1103 1103
 <option value="">---------</option>
1104 1104
 <option value="%s" selected="selected">Bob Woodward</option>
1105 1105
 <option value="%s">Carl Bernstein</option>
@@ -1374,7 +1374,7 @@ def test_media_on_modelform(self):
1374 1374
         # Similar to a regular Form class you can define custom media to be used on
1375 1375
         # the ModelForm.
1376 1376
         f = ModelFormWithMedia()
1377  
-        self.assertEqual(unicode(f.media), '''<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />
  1377
+        self.assertHTMLEqual(unicode(f.media), '''<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />
1378 1378
 <script type="text/javascript" src="/some/form/javascript"></script>''')
1379 1379
 
1380 1380
         f = CommaSeparatedIntegerForm({'field': '1,2,3'})
@@ -1443,7 +1443,7 @@ def test_foreignkeys_which_use_to_field(self):
1443 1443
             (22, u'Pear')))
1444 1444
 
1445 1445
         form = InventoryForm(instance=core)
1446  
-        self.assertEqual(unicode(form['parent']), '''<select name="parent" id="id_parent">
  1446
+        self.assertHTMLEqual(unicode(form['parent']), '''<select name="parent" id="id_parent">
1447 1447
 <option value="">---------</option>
1448 1448
 <option value="86" selected="selected">Apple</option>
1449 1449
 <option value="87">Core</option>
@@ -1464,7 +1464,7 @@ class Meta:
1464 1464
         self.assertEqual(CategoryForm.base_fields.keys(),
1465 1465
                          ['description', 'url'])
1466 1466
 
1467  
-        self.assertEqual(unicode(CategoryForm()), '''<tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr>
  1467
+        self.assertHTMLEqual(unicode(CategoryForm()), '''<tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr>
1468 1468
 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>''')
1469 1469
         # to_field_name should also work on ModelMultipleChoiceField ##################
1470 1470
 
@@ -1479,5 +1479,5 @@ class Meta:
1479 1479
 
1480 1480
     def test_model_field_that_returns_none_to_exclude_itself_with_explicit_fields(self):
1481 1481
         self.assertEqual(CustomFieldForExclusionForm.base_fields.keys(), ['name'])
1482  
-        self.assertEqual(unicode(CustomFieldForExclusionForm()),
  1482
+        self.assertHTMLEqual(unicode(CustomFieldForExclusionForm()),
1483 1483
                          '''<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="10" /></td></tr>''')
84  tests/modeltests/model_formsets/tests.py
@@ -95,11 +95,11 @@ def test_simple_save(self):
95 95
 
96 96
         formset = AuthorFormSet(queryset=qs)
97 97
         self.assertEqual(len(formset.forms), 3)
98  
-        self.assertEqual(formset.forms[0].as_p(),
  98
+        self.assertHTMLEqual(formset.forms[0].as_p(),
99 99
             '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>')
100  
-        self.assertEqual(formset.forms[1].as_p(),
  100
+        self.assertHTMLEqual(formset.forms[1].as_p(),
101 101
             '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>')
102  
-        self.assertEqual(formset.forms[2].as_p(),
  102
+        self.assertHTMLEqual(formset.forms[2].as_p(),
103 103
             '<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>')
104 104
 
105 105
         data = {
@@ -133,11 +133,11 @@ def test_simple_save(self):
133 133
 
134 134
         formset = AuthorFormSet(queryset=qs)
135 135
         self.assertEqual(len(formset.forms), 3)
136  
-        self.assertEqual(formset.forms[0].as_p(),
  136
+        self.assertHTMLEqual(formset.forms[0].as_p(),
137 137
             '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /></p>' % author2.id)
138  
-        self.assertEqual(formset.forms[1].as_p(),
  138
+        self.assertHTMLEqual(formset.forms[1].as_p(),
139 139
             '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" /></p>' % author1.id)
140  
-        self.assertEqual(formset.forms[2].as_p(),
  140
+        self.assertHTMLEqual(formset.forms[2].as_p(),
141 141
             '<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>')
142 142
 
143 143
         data = {
@@ -171,16 +171,16 @@ def test_simple_save(self):
171 171
 
172 172
         formset = AuthorFormSet(queryset=qs)
173 173
         self.assertEqual(len(formset.forms), 4)
174  
-        self.assertEqual(formset.forms[0].as_p(),
  174
+        self.assertHTMLEqual(formset.forms[0].as_p(),
175 175
             '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>\n'
176 176
             '<p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /><input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /></p>' % author2.id)
177  
-        self.assertEqual(formset.forms[1].as_p(),
  177
+        self.assertHTMLEqual(formset.forms[1].as_p(),
178 178
             '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>\n'
179 179
             '<p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" /></p>' % author1.id)
180  
-        self.assertEqual(formset.forms[2].as_p(),
  180
+        self.assertHTMLEqual(formset.forms[2].as_p(),
181 181
             '<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" value="Paul Verlaine" maxlength="100" /></p>\n'
182 182
             '<p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /><input type="hidden" name="form-2-id" value="%d" id="id_form-2-id" /></p>' % author3.id)
183  
-        self.assertEqual(formset.forms[3].as_p(),
  183
+        self.assertHTMLEqual(formset.forms[3].as_p(),
184 184
             '<p><label for="id_form-3-name">Name:</label> <input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /></p>\n'
185 185
             '<p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>')
186 186
 
@@ -381,7 +381,7 @@ def test_model_inheritance(self):
381 381
         BetterAuthorFormSet = modelformset_factory(BetterAuthor)
382 382
         formset = BetterAuthorFormSet()
383 383
         self.assertEqual(len(formset.forms), 1)
384  
-        self.assertEqual(formset.forms[0].as_p(),
  384
+        self.assertHTMLEqual(formset.forms[0].as_p(),
385 385
             '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>\n'
386 386
             '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr" /></p>')
387 387
 
@@ -404,10 +404,10 @@ def test_model_inheritance(self):
404 404
 
405 405
         formset = BetterAuthorFormSet()
406 406
         self.assertEqual(len(formset.forms), 2)
407  
-        self.assertEqual(formset.forms[0].as_p(),
  407
+        self.assertHTMLEqual(formset.forms[0].as_p(),
408 408
             '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Ernest Hemingway" maxlength="100" /></p>\n'
409 409
             '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" value="%d" id="id_form-0-author_ptr" /></p>' % hemingway_id)
410  
-        self.assertEqual(formset.forms[1].as_p(),
  410
+        self.assertHTMLEqual(formset.forms[1].as_p(),
411 411
             '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>\n'
412 412
             '<p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr" /></p>')
413 413
 
@@ -436,11 +436,11 @@ def test_inline_formsets(self):
436 436
 
437 437
         formset = AuthorBooksFormSet(instance=author)
438 438
         self.assertEqual(len(formset.forms), 3)
439  
-        self.assertEqual(formset.forms[0].as_p(),
  439
+        self.assertHTMLEqual(formset.forms[0].as_p(),
440 440
             '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-author" value="%d" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>'  % author.id)
441  
-        self.assertEqual(formset.forms[1].as_p(),
  441
+        self.assertHTMLEqual(formset.forms[1].as_p(),
442 442
             '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="%d" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>' % author.id)
443  
-        self.assertEqual(formset.forms[2].as_p(),
  443
+        self.assertHTMLEqual(formset.forms[2].as_p(),
444 444
             '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="%d" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>' % author.id)
445 445
 
446 446
         data = {
@@ -470,11 +470,11 @@ def test_inline_formsets(self):
470 470
 
471 471
         formset = AuthorBooksFormSet(instance=author)
472 472
         self.assertEqual(len(formset.forms), 3)
473  
-        self.assertEqual(formset.forms[0].as_p(),
  473
+        self.assertHTMLEqual(formset.forms[0].as_p(),
474 474
             '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-author" value="%d" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" value="%d" id="id_book_set-0-id" /></p>' % (author.id, book1.id))
475  
-        self.assertEqual(formset.forms[1].as_p(),
  475
+        self.assertHTMLEqual(formset.forms[1].as_p(),
476 476
             '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="%d" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>' % author.id)
477  
-        self.assertEqual(formset.forms[2].as_p(),
  477
+        self.assertHTMLEqual(formset.forms[2].as_p(),
478 478
             '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="%d" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>' % author.id)
479 479
 
480 480
         data = {
@@ -534,9 +534,9 @@ def test_inline_formsets_save_as_new(self):
534 534
 
535 535
         formset = AuthorBooksFormSet(prefix="test")
536 536
         self.assertEqual(len(formset.forms), 2)
537  
-        self.assertEqual(formset.forms[0].as_p(),
  537
+        self.assertHTMLEqual(formset.forms[0].as_p(),
538 538
             '<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-author" id="id_test-0-author" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>')
539  
-        self.assertEqual(formset.forms[1].as_p(),
  539
+        self.assertHTMLEqual(formset.forms[1].as_p(),
540 540
             '<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-author" id="id_test-1-author" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>')
541 541
 
542 542
     def test_inline_formsets_with_custom_pk(self):
@@ -548,7 +548,7 @@ def test_inline_formsets_with_custom_pk(self):
548 548
 
549 549
         formset = AuthorBooksFormSet2(instance=author)
550 550
         self.assertEqual(len(formset.forms), 1)
551  
-        self.assertEqual(formset.forms[0].as_p(),
  551
+        self.assertHTMLEqual(formset.forms[0].as_p(),
552 552
             '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input type="text" name="bookwithcustompk_set-0-my_pk" id="id_bookwithcustompk_set-0-my_pk" /></p>\n'
553 553
             '<p><label for="id_bookwithcustompk_set-0-title">Title:</label> <input id="id_bookwithcustompk_set-0-title" type="text" name="bookwithcustompk_set-0-title" maxlength="100" /><input type="hidden" name="bookwithcustompk_set-0-author" value="1" id="id_bookwithcustompk_set-0-author" /></p>')
554 554
 
@@ -580,7 +580,7 @@ def test_inline_formsets_with_multi_table_inheritance(self):
580 580
 
581 581
         formset = AuthorBooksFormSet3(instance=author)
582 582
         self.assertEqual(len(formset.forms), 1)
583  
-        self.assertEqual(formset.forms[0].as_p(),
  583
+        self.assertHTMLEqual(formset.forms[0].as_p(),
584 584
             '<p><label for="id_alternatebook_set-0-title">Title:</label> <input id="id_alternatebook_set-0-title" type="text" name="alternatebook_set-0-title" maxlength="100" /></p>\n'
585 585
             '<p><label for="id_alternatebook_set-0-notes">Notes:</label> <input id="id_alternatebook_set-0-notes" type="text" name="alternatebook_set-0-notes" maxlength="100" /><input type="hidden" name="alternatebook_set-0-author" value="1" id="id_alternatebook_set-0-author" /><input type="hidden" name="alternatebook_set-0-book_ptr" id="id_alternatebook_set-0-book_ptr" /></p>')
586 586
 
@@ -671,15 +671,15 @@ def save(self, commit=True):
671 671
         custom_qs = Book.objects.order_by('-title')
672 672
         formset = AuthorBooksFormSet(instance=author, queryset=custom_qs)
673 673
         self.assertEqual(len(formset.forms), 5)
674  
-        self.assertEqual(formset.forms[0].as_p(),
  674
+        self.assertHTMLEqual(formset.forms[0].as_p(),
675 675
             '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Paradis Artificiels" maxlength="100" /><input type="hidden" name="book_set-0-author" value="1" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>')
676  
-        self.assertEqual(formset.forms[1].as_p(),
  676
+        self.assertHTMLEqual(formset.forms[1].as_p(),
677 677
             '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-1-author" value="1" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" value="2" id="id_book_set-1-id" /></p>')
678  
-        self.assertEqual(formset.forms[2].as_p(),
  678
+        self.assertHTMLEqual(formset.forms[2].as_p(),
679 679
             '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" value="Flowers of Evil" maxlength="100" /><input type="hidden" name="book_set-2-author" value="1" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" value="3" id="id_book_set-2-id" /></p>')
680  
-        self.assertEqual(formset.forms[3].as_p(),
  680
+        self.assertHTMLEqual(formset.forms[3].as_p(),
681 681
             '<p><label for="id_book_set-3-title">Title:</label> <input id="id_book_set-3-title" type="text" name="book_set-3-title" maxlength="100" /><input type="hidden" name="book_set-3-author" value="1" id="id_book_set-3-author" /><input type="hidden" name="book_set-3-id" id="id_book_set-3-id" /></p>')
682  
-        self.assertEqual(formset.forms[4].as_p(),
  682
+        self.assertHTMLEqual(formset.forms[4].as_p(),
683 683
             '<p><label for="id_book_set-4-title">Title:</label> <input id="id_book_set-4-title" type="text" name="book_set-4-title" maxlength="100" /><input type="hidden" name="book_set-4-author" value="1" id="id_book_set-4-author" /><input type="hidden" name="book_set-4-id" id="id_book_set-4-id" /></p>')
684 684
 
685 685
         data = {
@@ -700,11 +700,11 @@ def save(self, commit=True):
700 700
 
701 701
         custom_qs = Book.objects.filter(title__startswith='F')
702 702
         formset = AuthorBooksFormSet(instance=author, queryset=custom_qs)
703  
-        self.assertEqual(formset.forms[0].as_p(),
  703
+        self.assertHTMLEqual(formset.forms[0].as_p(),
704 704
             '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Flowers of Evil" maxlength="100" /><input type="hidden" name="book_set-0-author" value="1" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" value="3" id="id_book_set-0-id" /></p>')
705  
-        self.assertEqual(formset.forms[1].as_p(),
  705
+        self.assertHTMLEqual(formset.forms[1].as_p(),
706 706
             '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="1" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>')
707  
-        self.assertEqual(formset.forms[2].as_p(),
  707
+        self.assertHTMLEqual(formset.forms[2].as_p(),
708 708
             '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="1" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>')
709 709
 
710 710
         data = {
@@ -725,7 +725,7 @@ def test_custom_pk(self):
725 725
         CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey)
726 726
         formset = CustomPrimaryKeyFormSet()
727 727
         self.assertEqual(len(formset.forms), 1)
728  
-        self.assertEqual(formset.forms[0].as_p(),
  728
+        self.assertHTMLEqual(formset.forms[0].as_p(),
729 729
             '<p><label for="id_form-0-my_pk">My pk:</label> <input id="id_form-0-my_pk" type="text" name="form-0-my_pk" maxlength="10" /></p>\n'
730 730
             '<p><label for="id_form-0-some_field">Some field:</label> <input id="id_form-0-some_field" type="text" name="form-0-some_field" maxlength="100" /></p>')
731 731
 
@@ -736,9 +736,9 @@ def test_custom_pk(self):
736 736
         FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False)
737 737
         formset = FormSet(instance=place)
738 738
         self.assertEqual(len(formset.forms), 2)
739  
-        self.assertEqual(formset.forms[0].as_p(),
  739
+        self.assertHTMLEqual(formset.forms[0].as_p(),
740 740
             '<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" maxlength="100" /><input type="hidden" name="owner_set-0-place" value="1" id="id_owner_set-0-place" /><input type="hidden" name="owner_set-0-auto_id" id="id_owner_set-0-auto_id" /></p>')
741  
-        self.assertEqual(formset.forms[1].as_p(),
  741
+        self.assertHTMLEqual(formset.forms[1].as_p(),
742 742
             '<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-place" value="1" id="id_owner_set-1-place" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p>')
743 743
 
744 744
         data = {
@@ -760,12 +760,12 @@ def test_custom_pk(self):
760 760
 
761 761
         formset = FormSet(instance=place)
762 762
         self.assertEqual(len(formset.forms), 3)
763  
-        self.assertEqual(formset.forms[0].as_p(),
  763
+        self.assertHTMLEqual(formset.forms[0].as_p(),
764 764
             '<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" value="Joe Perry" maxlength="100" /><input type="hidden" name="owner_set-0-place" value="1" id="id_owner_set-0-place" /><input type="hidden" name="owner_set-0-auto_id" value="%d" id="id_owner_set-0-auto_id" /></p>'
765 765
             % owner1.auto_id)
766  
-        self.assertEqual(formset.forms[1].as_p(),
  766
+        self.assertHTMLEqual(formset.forms[1].as_p(),
767 767
             '<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-place" value="1" id="id_owner_set-1-place" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p>')
768  
-        self.assertEqual(formset.forms[2].as_p(),
  768
+        self.assertHTMLEqual(formset.forms[2].as_p(),
769 769
             '<p><label for="id_owner_set-2-name">Name:</label> <input id="id_owner_set-2-name" type="text" name="owner_set-2-name" maxlength="100" /><input type="hidden" name="owner_set-2-place" value="1" id="id_owner_set-2-place" /><input type="hidden" name="owner_set-2-auto_id" id="id_owner_set-2-auto_id" /></p>')
770 770
 
771 771
         data = {
@@ -791,7 +791,7 @@ def test_custom_pk(self):
791 791
 
792 792
         FormSet = modelformset_factory(OwnerProfile)
793 793
         formset = FormSet()
794  
-        self.assertEqual(formset.forms[0].as_p(),
  794
+        self.assertHTMLEqual(formset.forms[0].as_p(),
795 795
             '<p><label for="id_form-0-owner">Owner:</label> <select name="form-0-owner" id="id_form-0-owner">\n'
796 796
             '<option value="" selected="selected">---------</option>\n'
797 797
             '<option value="%d">Joe Perry at Giordanos</option>\n'
@@ -806,7 +806,7 @@ def test_custom_pk(self):
806 806
 
807 807
         formset = FormSet(instance=owner1)
808 808
         self.assertEqual(len(formset.forms), 1)
809  
-        self.assertEqual(formset.forms[0].as_p(),
  809
+        self.assertHTMLEqual(formset.forms[0].as_p(),
810 810
             '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
811 811
             % owner1.auto_id)
812 812
 
@@ -827,7 +827,7 @@ def test_custom_pk(self):
827 827
 
828 828
         formset = FormSet(instance=owner1)
829 829
         self.assertEqual(len(formset.forms), 1)
830  
-        self.assertEqual(formset.forms[0].as_p(),
  830
+        self.assertHTMLEqual(formset.forms[0].as_p(),
831 831
             '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" value="54" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
832 832
             % owner1.auto_id)
833 833
 
@@ -856,7 +856,7 @@ def test_unique_true_enforces_max_num_one(self):
856 856
 
857 857
         formset = FormSet(instance=place)
858 858
         self.assertEqual(len(formset.forms), 1)
859  
-        self.assertEqual(formset.forms[0].as_p(),
  859
+        self.assertHTMLEqual(formset.forms[0].as_p(),
860 860
             '<p><label for="id_location_set-0-lat">Lat:</label> <input id="id_location_set-0-lat" type="text" name="location_set-0-lat" maxlength="100" /></p>\n'
861 861
             '<p><label for="id_location_set-0-lon">Lon:</label> <input id="id_location_set-0-lon" type="text" name="location_set-0-lon" maxlength="100" /><input type="hidden" name="location_set-0-place" value="1" id="id_location_set-0-place" /><input type="hidden" name="location_set-0-id" id="id_location_set-0-id" /></p>')
862 862
 
@@ -982,7 +982,7 @@ def test_callable_defaults(self):
982 982
         now = form.fields['date_joined'].initial()
983 983
         result = form.as_p()
984 984
         result = re.sub(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?', '__DATETIME__', result)
985  
-        self.assertEqual(result,
  985
+        self.assertHTMLEqual(result,
986 986
             '<p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="__DATETIME__" id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="__DATETIME__" id="initial-membership_set-0-id_membership_set-0-date_joined" /></p>\n'
987 987
             '<p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="%d" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p>'
988 988
             % person.id)
6  tests/regressiontests/admin_inlines/tests.py
@@ -34,7 +34,7 @@ def test_can_delete(self):
34 34
         can_delete should be passed to inlineformset factory.
35 35
         """
36 36
         response = self.client.get(self.change_url)
37  
-        inner_formset = response.context[-1]['inline_admin_formsets'][0].formset
  37
+        inner_formset = response.context['inline_admin_formsets'][0].formset
38 38
         expected = InnerInline.can_delete
39 39
         actual = inner_formset.can_delete
40 40
         self.assertEqual(expected, actual, 'can_delete must be equal')
@@ -134,7 +134,7 @@ def test_non_related_name_inline(self):
134 134
                 'id="id_-1-0-capo_famiglia" />')
135 135
         self.assertContains(response,
136 136
                 '<input id="id_-1-0-name" type="text" class="vTextField" '
137  
-                'name="-1-0-name" maxlength="100" />')
  137
+                'name="-1-0-name" maxlength="100" />', html=True)
138 138
 
139 139
         self.assertContains(response,
140 140
                 '<input type="hidden" name="-2-0-id" id="id_-2-0-id" />')
@@ -143,7 +143,7 @@ def test_non_related_name_inline(self):
143 143
                 'id="id_-2-0-capo_famiglia" />')
144 144
         self.assertContains(response,