Skip to content

How do I assert that element doesn't exist or has no children? #13

@x-yuri

Description

@x-yuri

It doesn't seem to be possible right now.

Possible workaround:

    def assertXpathExists(self, node, xpath):
        if self.eval_xpath(node, 'count(' + xpath + ')') == 0:
            self.fail('''xpath "%s" doesn't exist''' % xpath)

    def assertXpathNotExists(self, node, xpath):
        if self.eval_xpath(node, 'count(' + xpath + ')') > 0:
            self.fail('''xpath "%s" exists''' % xpath)

    def eval_xpath(self, node, xpath):
        expression = self.build_xpath_expression(node, xpath)
        try:
            return expression.evaluate(node)
        except etree.XPathEvalError as error:
            self.fail_xpath_error(node, expression.path, error)

UPD I don't mean to offend you, but I ended up writing a bunch of my own helpers. The only thing I'm still using so far is assertXmlValidDTD(). They may be far from being perfect, but with them, my tests are much more readable. And they don't suffer from the flaw (the way I see it) that e.g. assertXpathValues() passes when the target element doesn't exist.

Leaving them here, for what it's worth:

from django.template import Template, RequestContext
from lxml import etree

import xml.dom.minidom as mdom

class XmlTestMixin:
    # assertXML + can compare not the whole document but a subtree
    # + can ignore subtrees
    # _remove_doctype is a temporary fix
    # assertXMLEqual bails out for documents containing doctype
    # they have fixed it in the master
    # https://code.djangoproject.com/ticket/30497
    def assertXML(self, expected, actual, **kwargs):
        if 'subtree' in kwargs:
            actual = self._extract_subtree(actual, kwargs['subtree'])
        if 'ignore' in kwargs:
            actual = self._ignore_xpaths(actual, kwargs['ignore'])
        self.assertXMLEqual(
            self._remove_doctype(expected),
            self._remove_doctype(actual))

    def _extract_subtree(self, xml, xpath):
        t = etree.fromstring(xml.encode()).getroottree()
        return etree.tostring(
            self.xpath(t, xpath)[0],
            encoding=t.docinfo.encoding
        ).decode()

    def _ignore_xpaths(self, xml, xpaths):
        t = etree.fromstring(xml.encode()).getroottree()
        for xp in xpaths:
            for el in t.xpath(xp):
                el.getparent().remove(el)
        kwargs = {'encoding': t.docinfo.encoding, **(
            {'xml_declaration': True}
            if self._has_xml_declaration(xml)
            else {}
        )}
        return etree.tostring(t, **kwargs).decode()

    def _remove_doctype(self, xml):
        dom = mdom.parseString(xml)
        if not dom.version:  # no xml declaration
            return xml
        for n in dom.childNodes:
            if n.nodeType == mdom.Node.DOCUMENT_TYPE_NODE:
                dom.removeChild(n)
        return dom.toxml()

    def _has_xml_declaration(self, xml):
        return mdom.parseString(xml).version

    # I usually start with a test that handles the general case
    # (compare the whole document + ignore subtrees or compare a subtree)
    # that's where Django templates come in handy
    # succeeding tests deal with the details
    def render_template(self, template, context, response):
        return Template(template) \
            .render(RequestContext(response.wsgi_request, context))


    def assertElementExists(self, node, xpath):
        if self.xpath(node, 'count(' + xpath + ')') == 0:
            self.fail('''Element doesn't exist (%s)''' % xpath)

    def assertElementNotExists(self, node, xpath):
        if self.xpath(node, 'count(' + xpath + ')') > 0:
            self.fail('Element exists (%s)' % xpath)

    def assertElementText(self, root, xpath, text):
        r = self.xpath(root, xpath)
        self._assertOneElement(xpath, r)
        if self._normalize_space(r[0].text) != text:
            self.fail('''Element's text differs (%s)\n'''
                      'Expected: %s\n'
                      'Actual: %s\n'
                      % (xpath, text, r[0].text))

    def assertElementsText(self, root, xpath, texts):
        r = self.xpath(root, xpath)
        if len(r) != len(texts):
            self.fail('''Number of elements doesn't match'''
                      'that of the expected values (%s)'
                      % xpath)
        actual_texts = set(self._normalize_space(e.text) for e in r)
        if actual_texts != texts:
            self.fail('''Elements' texts differ (%s)\n'''
                      'Expected: %s\n'
                      'Actual: %s\n'
                      % (xpath, texts, actual_texts))

    def assertAttributeValue(self, root, xpath, attr, value):
        r = self.xpath(root, xpath)
        self._assertOneElement(xpath, r)
        if attr not in r[0].attrib:
            self.fail('''Attribute doesn't exist (%s)''' % xpath)
        if r[0].attrib[attr] != value:
            self.fail('Attribute value differs (%s)\n'
                      'Expected: %s\n'
                      'Actual: %s\n'
                      % (xpath, value, r[0].attrib[attr]))
        
    def _assertOneElement(self, xpath, r):
        if len(r) == 0:
            self.fail('xpath "%s" not found' % xpath)
        elif len(r) > 1:
            self.fail('xpath "%s" resolves to multiple elements' % xpath)

    def xpath(self, xml, xpath):
        tree = etree.fromstring(xml.encode()).getroottree() \
            if isinstance(xml, str) \
            else xml
        return tree.xpath(xpath)

    def _normalize_space(self, s):
        return " ".join(s.split())

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions