# -*- coding: utf-8 -*- import re from json import loads from webtest import forms from webtest import utils from webtest.compat import print_stderr from webtest.compat import splittype from webtest.compat import splithost from webtest.compat import PY3 from webtest.compat import urlparse from webtest.compat import to_bytes from six import string_types from six import binary_type from six import text_type from bs4 import BeautifulSoup import webob class TestResponse(webob.Response): """ Instances of this class are returned by :class:`~webtest.app.TestApp` methods. """ request = None _forms_indexed = None parser_features = 'html.parser' @property def forms(self): """ Returns a dictionary containing all the forms in the pages as :class:`~webtest.forms.Form` objects. Indexes are both in order (from zero) and by form id (if the form is given an id). See :doc:`forms` for more info on form objects. """ if self._forms_indexed is None: self._parse_forms() return self._forms_indexed @property def form(self): """ If there is only one form on the page, return it as a :class:`~webtest.forms.Form` object; raise a TypeError is there are no form or multiple forms. """ forms_ = self.forms if not forms_: raise TypeError( "You used response.form, but no forms exist") if 1 in forms_: # There is more than one form raise TypeError( "You used response.form, but more than one form exists") return forms_[0] @property def testbody(self): self.decode_content() if self.charset: try: return self.text except UnicodeDecodeError: return self.body.decode(self.charset, 'replace') return self.body.decode('ascii', 'replace') _tag_re = re.compile(r'<(/?)([:a-z0-9_\-]*)(.*?)>', re.S | re.I) def _parse_forms(self): forms_ = self._forms_indexed = {} form_texts = [text_type(f) for f in self.html('form')] for i, text in enumerate(form_texts): form = forms.Form(self, text, self.parser_features) forms_[i] = form if form.id: forms_[form.id] = form def _follow(self, **kw): location = self.headers['location'] abslocation = urlparse.urljoin(self.request.url, location) type_, rest = splittype(abslocation) host, path = splithost(rest) # @@: We should test that it's not a remote redirect return self.test_app.get(abslocation, **kw) def follow(self, **kw): """ If this response is a redirect, follow that redirect. It is an error if it is not a redirect response. Any keyword arguments are passed to :class:`webtest.app.TestApp.get`. Returns another :class:`TestResponse` object. """ assert 300 <= self.status_int < 400, ( "You can only follow redirect responses (not %s)" % self.status) return self._follow(**kw) def maybe_follow(self, **kw): """ Follow all redirects. If this response is not a redirect, do nothing. Any keyword arguments are passed to :class:`webtest.app.TestApp.get`. Returns another :class:`TestResponse` object. """ remaining_redirects = 100 # infinite loops protection response = self while 300 <= response.status_int < 400 and remaining_redirects: response = response._follow(**kw) remaining_redirects -= 1 assert remaining_redirects > 0, "redirects chain looks infinite" return response def click(self, description=None, linkid=None, href=None, index=None, verbose=False, extra_environ=None): """ Click the link as described. Each of ``description``, ``linkid``, and ``url`` are *patterns*, meaning that they are either strings (regular expressions), compiled regular expressions (objects with a ``search`` method), or callables returning true or false. All the given patterns are ANDed together: * ``description`` is a pattern that matches the contents of the anchor (HTML and all -- everything between ```` and ````) * ``linkid`` is a pattern that matches the ``id`` attribute of the anchor. It will receive the empty string if no id is given. * ``href`` is a pattern that matches the ``href`` of the anchor; the literal content of that attribute, not the fully qualified attribute. If more than one link matches, then the ``index`` link is followed. If ``index`` is not given and more than one link matches, or if no link matches, then ``IndexError`` will be raised. If you give ``verbose`` then messages will be printed about each link, and why it does or doesn't match. If you use ``app.click(verbose=True)`` you'll see a list of all the links. You can use multiple criteria to essentially assert multiple aspects about the link, e.g., where the link's destination is. """ found_html, found_desc, found_attrs = self._find_element( tag='a', href_attr='href', href_extract=None, content=description, id=linkid, href_pattern=href, index=index, verbose=verbose) extra_environ = extra_environ or {} extra_environ.setdefault('HTTP_REFERER', str(self.request.url)) return self.goto(str(found_attrs['uri']), extra_environ=extra_environ) def clickbutton(self, description=None, buttonid=None, href=None, index=None, verbose=False): """ Like :meth:`~webtest.response.TestResponse.click`, except looks for link-like buttons. This kind of button should look like ``