Skip to content

Commit

Permalink
added an ExtendedTestBrowser that speaks HTTP
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Lotze committed Jul 8, 2010
1 parent 92a528a commit a2a6763
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 85 deletions.
5 changes: 4 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
CHANGES
=======

1.3.2 (unreleased)
1.4.0 (unreleased)
==================

- Took zope.securitypolicy refactoring and zope.testing.doctest deprecation
into account.

- Added a variant of the ExtendedTestBrowser that speaks HTTP and has much
fewer dependencies than the existing one, which talks to the publisher.


1.3.1 (2010-01-18)
==================
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()

setup(name='z3c.etestbrowser',
version = '1.3.2dev',
version = '1.4.0dev',
author='Christian Theune',
author_email='ct@gocept.com',
description='Extensions for zope.testbrowser',
Expand Down
111 changes: 111 additions & 0 deletions src/z3c/etestbrowser/browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
##############################################################################
#
# Copyright (c) 2006 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Extensions for z3c.etestbrowser
$Id$
"""

import re
import htmllib
import formatter

import lxml.etree
import lxml.html

import zope.testbrowser.browser


RE_CHARSET = re.compile('.*;charset=(.*)')


def indent(elem, level=0):
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
for e in elem:
indent(e, level+1)
if not e.tail or not e.tail.strip():
e.tail = i + " "
if not e.tail or not e.tail.strip():
e.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i


class ExtendedTestBrowser(zope.testbrowser.browser.Browser):
"""An extended testbrowser implementation.
Features:
- offers the content also as parsed etree
"""

xml_strict = False

_etree = None
_normalized_contents = None

@property
def etree(self):
if self._etree is not None:
return self._etree
# I'm not using any internal knowledge about testbrowser
# here, to avoid breakage. Memory usage won't be a problem.
if self.xml_strict:
self._etree = lxml.etree.fromstring(
self.contents,
parser=lxml.etree.XMLParser(resolve_entities=False))
else:
# This is a workaround against the broken fallback for
# encoding detection of libxml2.
# We have a chance of knowing the encoding as Zope states this in
# the content-type response header.
content = self.contents
content_type = self.headers['content-type']
match = RE_CHARSET.match(content_type)
if match is not None:
charset = match.groups()[0]
content = content.decode(charset)
self._etree = lxml.etree.HTML(content)

return self._etree

@property
def normalized_contents(self):
if self._normalized_contents is None:
indent(self.etree)
self._normalized_contents = lxml.etree.tostring(
self.etree, pretty_print=True)
return self._normalized_contents

def _changed(self):
super(ExtendedTestBrowser, self)._changed()
self._etree = None
self._normalized_contents = None

def pretty_print(self):
"""Print a pretty (formatted) version of the HTML content.
If the content is not text/html then it is just printed.
"""
if not self.headers['content-type'].lower().startswith('text/html'):
print self.contents
else:
parser = htmllib.HTMLParser(
formatter.AbstractFormatter(formatter.DumbWriter()))
parser.feed(self.contents)
parser.close()
43 changes: 43 additions & 0 deletions src/z3c/etestbrowser/over_the_wire.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
=================================
Using testbrowser On the Internet
=================================

The ``z3c.etestbrowser`` module exposes an ``ExtendedTestBrowser`` class that
simulates a web browser similar to Mozilla Firefox or IE.

>>> from z3c.etestbrowser.browser import ExtendedTestBrowser
>>> browser = ExtendedTestBrowser()

It can send arbitrary headers; this is helpful for setting the language value,
so that your tests format values the way you expect in your tests, if you rely
on zope.i18n locale-based formatting or a similar approach.

>>> browser.addHeader('Accept-Language', 'en-US')

The browser can `open` web pages:

>>> # This is tricky, since in Germany I am forwarded to google.de usually;
>>> # The `ncr` forces to really go to google.com.
>>> browser.open('http://google.com/ncr')
>>> browser.url
'http://www.google.com/'
>>> 'html' in browser.contents.lower()
True

We can access the etree of the page .. contents:

>>> browser.etree.xpath('//body')
[<Element body at ...>]

We'll put some text in the query box...

>>> browser.getControl(name='q').value = 'z3c.etestbrowser'

...and then click the search button.

>>> browser.getControl('Google Search').click()
Traceback (most recent call last):
...
RobotExclusionError: HTTP Error 403: request disallowed by robots.txt

Oops! Google doesn't let robots use their search engine. Oh well.
86 changes: 3 additions & 83 deletions src/z3c/etestbrowser/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,96 +16,16 @@
$Id$
"""

import re
import StringIO
import htmllib
import formatter

import lxml.etree, lxml.html

import z3c.etestbrowser.browser
import zope.testbrowser.testing


RE_CHARSET = re.compile('.*;charset=(.*)')


def indent(elem, level=0):
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
for e in elem:
indent(e, level+1)
if not e.tail or not e.tail.strip():
e.tail = i + " "
if not e.tail or not e.tail.strip():
e.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i


class ExtendedTestBrowser(zope.testbrowser.testing.Browser):
class ExtendedTestBrowser(zope.testbrowser.testing.Browser,
z3c.etestbrowser.browser.ExtendedTestBrowser):
"""An extended testbrowser implementation.
Features:
- offers the content also as parsed etree
"""

xml_strict = False

_etree = None
_normalized_contents = None

@property
def etree(self):
if self._etree is not None:
return self._etree
# I'm not using any internal knowledge about testbrowser
# here, to avoid breakage. Memory usage won't be a problem.
if self.xml_strict:
self._etree = lxml.etree.fromstring(
self.contents,
parser=lxml.etree.XMLParser(resolve_entities=False))
else:
# This is a workaround against the broken fallback for
# encoding detection of libxml2.
# We have a chance of knowing the encoding as Zope states this in
# the content-type response header.
content = self.contents
content_type = self.headers['content-type']
match = RE_CHARSET.match(content_type)
if match is not None:
charset = match.groups()[0]
content = content.decode(charset)
self._etree = lxml.etree.HTML(content)

return self._etree

@property
def normalized_contents(self):
if self._normalized_contents is None:
indent(self.etree)
self._normalized_contents = lxml.etree.tostring(
self.etree, pretty_print=True)
return self._normalized_contents

def _changed(self):
super(ExtendedTestBrowser, self)._changed()
self._etree = None
self._normalized_contents = None

def pretty_print(self):
"""Print a pretty (formatted) version of the HTML content.
If the content is not text/html then it is just printed.
"""
if not self.headers['content-type'].lower().startswith('text/html'):
print self.contents
else:
parser = htmllib.HTMLParser(
formatter.AbstractFormatter(formatter.DumbWriter()))
parser.feed(self.contents)
parser.close()
1 change: 1 addition & 0 deletions src/z3c/etestbrowser/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def test_suite():
suite = unittest.TestSuite()
test = functional.FunctionalDocFileSuite(
"README.txt",
"over_the_wire.txt",
optionflags=doctest.REPORT_NDIFF|doctest.NORMALIZE_WHITESPACE|
doctest.ELLIPSIS)
test.layer = layer
Expand Down

0 comments on commit a2a6763

Please sign in to comment.