[Checkins] SVN: zc.testbrowser/trunk/src/zc/testbrowser/ - renamed
waitForPageLoad() to wait()
Benji York
benji at zope.com
Tue Feb 26 23:11:57 EST 2008
Log message for revision 84315:
- renamed waitForPageLoad() to wait()
- added wait() to IBrowser and implementations; having an explicit "wait for
the page to load" call seems to work much better (for example, some
link.click()s trigger a page load and some don't, now we don't have to guess
if we need to wait or not)
- separated out some interfaces out of IBrowser
- made zc.testbrowser.real not pretend to implement anything but, the now
smaller, IBrowser
- use modern import style
Changed:
U zc.testbrowser/trunk/src/zc/testbrowser/README.txt
U zc.testbrowser/trunk/src/zc/testbrowser/browser.py
U zc.testbrowser/trunk/src/zc/testbrowser/interfaces.py
A zc.testbrowser/trunk/src/zc/testbrowser/performance.txt
U zc.testbrowser/trunk/src/zc/testbrowser/real.py
U zc.testbrowser/trunk/src/zc/testbrowser/screen-shots.txt
U zc.testbrowser/trunk/src/zc/testbrowser/tests.py
-=-
Modified: zc.testbrowser/trunk/src/zc/testbrowser/README.txt
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/README.txt 2008-02-27 03:27:20 UTC (rev 84314)
+++ zc.testbrowser/trunk/src/zc/testbrowser/README.txt 2008-02-27 04:11:56 UTC (rev 84315)
@@ -23,6 +23,8 @@
>>> from zope.interface.verify import verifyObject
>>> zc.testbrowser.interfaces.IBrowser.providedBy(browser)
True
+ >>> verifyObject(zc.testbrowser.interfaces.IBrowser, browser)
+ True
Page Contents
@@ -128,6 +130,7 @@
Links can be "clicked" and the browser will navigate to the referenced URL.
>>> link.click()
+ >>> browser.wait()
>>> browser.url
'http://localhost:.../target.html'
>>> browser.contents
@@ -145,6 +148,7 @@
>>> link.text
'Link Text with Whitespace Normalization (and parens)'
>>> link.click()
+ >>> browser.wait()
>>> browser.url
'http://localhost:.../target.html'
@@ -171,6 +175,7 @@
>>> browser.open('navigate.html')
>>> browser.getLink(url='target.html').click()
+ >>> browser.wait()
>>> browser.url
'http://localhost:.../target.html'
@@ -181,6 +186,7 @@
'...<a href="target.html" id="anchorid">By Anchor Id</a>...'
>>> browser.getLink(id='anchorid').click()
+ >>> browser.wait()
>>> browser.url
'http://localhost:.../target.html'
@@ -191,6 +197,7 @@
>>> browser.open('navigate.html')
>>> link = browser.getLink(id='zope3')
>>> link.click()
+ >>> browser.wait()
>>> browser.url
'http://localhost:.../target.html'
@@ -892,6 +899,7 @@
>>> browser.getControl('Text Control').value = 'Other Text'
>>> browser.getControl('Submit').click()
+ >>> browser.wait()
>>> browser.contents
"...'text-value': ['Other Text']..."
@@ -911,6 +919,7 @@
>>> browser.open('controls.html')
>>> browser.getControl('Text Control').value = 'Other Text'
>>> browser.getControl(name='image-value').click()
+ >>> browser.wait()
>>> browser.contents
"...'text-value': ['Other Text']..."
@@ -1015,6 +1024,7 @@
>>> browser.open('forms.html')
>>> form = browser.getForm('2')
>>> form.getControl('Submit').click()
+ >>> browser.wait()
>>> browser.contents
"...'text-value': ['Second Text']..."
>>> browser.open('forms.html')
@@ -1053,21 +1063,6 @@
ValueError: if no other arguments are given, index is required.
-Performance Testing
--------------------
-
-Browser objects keep up with how much time each request takes. This can be
-used to ensure a particular request's performance is within a tolerable range.
-Be very careful using raw seconds, cross-machine differences can be huge,
-pystones is usually a better choice.
-
- >>> browser.open('index.html')
- >>> browser.lastRequestSeconds < 10 # really big number for safety
- True
- >>> browser.lastRequestPystones < 100000 # really big number for safety
- True
-
-
Hand-Holding
------------
@@ -1109,6 +1104,7 @@
>>> browser.open('navigate.html')
>>> browser.getLink('Spaces in the URL').click()
+ >>> browser.wait()
.goBack() Truncation
~~~~~~~~~~~~~~~~~~~~
Modified: zc.testbrowser/trunk/src/zc/testbrowser/browser.py
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/browser.py 2008-02-27 03:27:20 UTC (rev 84314)
+++ zc.testbrowser/trunk/src/zc/testbrowser/browser.py 2008-02-27 04:11:56 UTC (rev 84315)
@@ -142,8 +142,11 @@
class Browser(SetattrErrorsMixin):
"""A web user agent."""
- zope.interface.implements(zc.testbrowser.interfaces.IBrowser)
+ zope.interface.implements(zc.testbrowser.interfaces.IBrowser,
+ zc.testbrowser.interfaces.IHeaders,
+ zc.testbrowser.interfaces.ITiming)
+
base = None
_contents = None
_counter = 0
@@ -368,7 +371,10 @@
self._counter += 1
self._contents = None
+ def wait(self):
+ pass # no need to wait for this browser type
+
class Link(SetattrErrorsMixin):
zope.interface.implements(zc.testbrowser.interfaces.ILink)
Modified: zc.testbrowser/trunk/src/zc/testbrowser/interfaces.py
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/interfaces.py 2008-02-27 03:27:20 UTC (rev 84314)
+++ zc.testbrowser/trunk/src/zc/testbrowser/interfaces.py 2008-02-27 04:11:56 UTC (rev 84315)
@@ -13,63 +13,42 @@
##############################################################################
__docformat__ = "reStructuredText"
-from zope import interface, schema
+import zope.interface
+import zope.schema
class LinkNotFoundError(ValueError):
- """Exception raised if a link could not be found for the given parameter."""
+ """Exception raised if a link could not be found for the given parameter"""
-class IBrowser(interface.Interface):
- """A Programmatic Web Browser."""
+class IBrowser(zope.interface.Interface):
+ """A Programmatic Web Browser"""
- url = schema.URI(
+ url = zope.schema.URI(
title=u"URL",
description=u"The URL the browser is currently showing.",
required=True)
- headers = schema.Field(
- title=u"Headers",
- description=(u"Headers of the HTTP response; a "
- "``httplib.HTTPMessage``."),
- required=True)
-
- contents = schema.Text(
+ contents = zope.schema.Text(
title=u"Contents",
description=u"The complete response body of the HTTP request.",
required=True)
- isHtml = schema.Bool(
+ isHtml = zope.schema.Bool(
title=u"Is HTML",
description=u"Tells whether the output is HTML or not.",
required=True)
- title = schema.TextLine(
+ title = zope.schema.TextLine(
title=u"Base",
description=u"Base URL for opening relative paths",
required=False)
- title = schema.TextLine(
+ title = zope.schema.TextLine(
title=u"Title",
description=u"Title of the displayed page",
required=False)
- handleErrors = schema.Bool(
- title=u"Handle Errors",
- description=(u"Describes whether server-side errors will be handled "
- u"by the publisher. If set to ``False``, the error will "
- u"progress all the way to the test, which is good for "
- u"debugging."),
- default=True,
- required=True)
-
- def addHeader(key, value):
- """Adds a header to each HTTP request.
-
- Adding additional headers can be useful in many ways, from setting the
- credentials token to specifying the browser identification string.
- """
-
def open(url, data=None):
"""Open a URL in the browser.
@@ -108,29 +87,6 @@
o ``id`` -- The id attribute of the anchor tag submit button.
"""
- lastRequestSeconds = schema.Field(
- title=u"Seconds to Process Last Request",
- description=(
- u"""Return how many seconds (or fractions) the last request took.
-
- The values returned have the same resolution as the results from
- ``time.clock``.
- """),
- required=True,
- readonly=True)
-
- lastRequestPystones = schema.Field(
- title=
- u"Approximate System-Independent Effort of Last Request (Pystones)",
- description=(
- u"""Return how many pystones the last request took.
-
- This number is found by multiplying the number of pystones/second at
- which this system benchmarks and the result of ``lastRequestSeconds``.
- """),
- required=True,
- readonly=True)
-
def getControl(label=None, name=None, index=None):
"""Get a control from the page.
@@ -167,38 +123,106 @@
"""
+class IWait(zope.interface.Interface):
+ """Wait for asynchronous events"""
+
+ def wait():
+ """Wait for a page load to complete.
+
+ Any action that causes a page load must be complete before more browser
+ actions are executed.
+ """
+
+
+class IHeaders(zope.interface.Interface):
+ """Expose HTTP headers and the manipulation thereof"""
+
+ headers = zope.schema.Field(
+ title=u"Headers",
+ description=(u"Headers of the HTTP response; a "
+ "``httplib.HTTPMessage``."),
+ required=True)
+
+ def addHeader(key, value):
+ """Adds a header to each HTTP request.
+
+ Adding additional headers can be useful in many ways, from setting the
+ credentials token to specifying the browser identification string.
+ """
+
+
+class ITiming(zope.interface.Interface):
+ """Keep up with how long requests take in seconds and pystones"""
+
+ lastRequestSeconds = zope.schema.Field(
+ title=u"Seconds to Process Last Request",
+ description=(
+ u"""Return how many seconds (or fractions) the last request took.
+
+ The values returned have the same resolution as the results from
+ ``time.clock``.
+ """),
+ required=True,
+ readonly=True)
+
+ lastRequestPystones = zope.schema.Field(
+ title=
+ u"Approximate System-Independent Effort of Last Request (Pystones)",
+ description=(
+ u"""Return how many pystones the last request took.
+
+ This number is found by multiplying the number of pystones/second at
+ which this system benchmarks and the result of ``lastRequestSeconds``.
+ """),
+ required=True,
+ readonly=True)
+
+
+class IZopeBrowser(IBrowser, IHeaders, ITiming):
+ """A browser for use with the Zope functional testing framework"""
+
+ handleErrors = zope.schema.Bool(
+ title=u"Handle Errors",
+ description=(u"Describes whether server-side errors will be handled "
+ u"by the publisher. If set to ``False``, the error will "
+ u"progress all the way to the test, which is good for "
+ u"debugging."),
+ default=True,
+ required=True)
+
+
class ExpiredError(Exception):
"""The browser page to which this was attached is no longer active"""
-class IControl(interface.Interface):
+class IControl(zope.interface.Interface):
"""A control (input field) of a page."""
- name = schema.TextLine(
+ name = zope.schema.TextLine(
title=u"Name",
description=u"The name of the control.",
required=True)
- value = schema.Field(
+ value = zope.schema.Field(
title=u"Value",
description=u"The value of the control",
default=None,
required=True)
- type = schema.Choice(
+ type = zope.schema.Choice(
title=u"Type",
description=u"The type of the control",
values=['text', 'password', 'hidden', 'submit', 'checkbox', 'select',
'radio', 'image', 'file'],
required=True)
- disabled = schema.Bool(
+ disabled = zope.schema.Bool(
title=u"Disabled",
description=u"Describes whether a control is disabled.",
default=False,
required=False)
- multiple = schema.Bool(
+ multiple = zope.schema.Bool(
title=u"Multiple",
description=u"Describes whether this control can hold multiple values.",
default=False,
@@ -211,20 +235,20 @@
class IListControl(IControl):
"""A radio button, checkbox, or select control"""
- options = schema.List(
+ options = zope.schema.List(
title=u"Options",
description=u"""\
A list of possible values for the control.""",
required=True)
- displayOptions = schema.List(
+ displayOptions = zope.schema.List(
# TODO: currently only implemented for select by ClientForm
title=u"Options",
description=u"""\
A list of possible display values for the control.""",
required=True)
- displayValue = schema.Field(
+ displayValue = zope.schema.Field(
# TODO: currently only implemented for select by ClientForm
title=u"Value",
description=u"The value of the control, as rendered by the display",
@@ -238,7 +262,7 @@
'Add a contact' but not 'Address'. A word is defined as one or more
alphanumeric characters or the underline."""
- controls = interface.Attribute(
+ controls = zope.interface.Attribute(
"""a list of subcontrols for the control. mutating list has no effect
on control (although subcontrols may be changed as usual).""")
@@ -255,65 +279,65 @@
"click the submit button with optional coordinates"
-class IItemControl(interface.Interface):
+class IItemControl(zope.interface.Interface):
"""a radio button or checkbox within a larger multiple-choice control"""
- control = schema.Object(
+ control = zope.schema.Object(
title=u"Control",
description=(u"The parent control element."),
schema=IControl,
required=True)
- disabled = schema.Bool(
+ disabled = zope.schema.Bool(
title=u"Disabled",
description=u"Describes whether a subcontrol is disabled.",
default=False,
required=False)
- selected = schema.Bool(
+ selected = zope.schema.Bool(
title=u"Selected",
description=u"Whether the subcontrol is selected",
default=None,
required=True)
- optionValue = schema.TextLine(
+ optionValue = zope.schema.TextLine(
title=u"Value",
description=u"The value of the subcontrol",
default=None,
required=False)
-class ILink(interface.Interface):
+class ILink(zope.interface.Interface):
def click():
"""click the link, going to the URL referenced"""
- url = schema.TextLine(
+ url = zope.schema.TextLine(
title=u"URL",
description=u"The normalized URL of the link",
required=False)
- text = schema.TextLine(
+ text = zope.schema.TextLine(
title=u'Text',
description=u'The contained text of the link',
required=False)
-class IForm(interface.Interface):
- """An HTML form of the page."""
+class IForm(zope.interface.Interface):
+ """An HTML form of the page"""
- action = schema.TextLine(
+ action = zope.schema.TextLine(
title=u"Action",
description=u"The action (or URI) that is opened upon submittance.",
required=True)
- name = schema.TextLine(
+ name = zope.schema.TextLine(
title=u"Name",
description=u"The value of the `name` attribute in the form tag, "
u"if specified.",
required=True)
- id = schema.TextLine(
+ id = zope.schema.TextLine(
title=u"Id",
description=u"The value of the `id` attribute in the form tag, "
u"if specified.",
@@ -355,5 +379,6 @@
filtered to find only submit and image controls.
"""
+
class ITextAreaControl(IControl):
- """An HTML text area. Mostly just a marker."""
+ """An HTML text area. Mostly just a marker"""
Added: zc.testbrowser/trunk/src/zc/testbrowser/performance.txt
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/performance.txt (rev 0)
+++ zc.testbrowser/trunk/src/zc/testbrowser/performance.txt 2008-02-27 04:11:56 UTC (rev 84315)
@@ -0,0 +1,14 @@
+Performance Testing
+-------------------
+
+Browser objects keep up with how much time each request takes. This can be
+used to ensure a particular request's performance is within a tolerable range.
+Be very careful using raw seconds, cross-machine differences can be huge,
+pystones is usually a better choice.
+
+ >>> browser.base = 'http://localhost:%s/' % TEST_PORT
+ >>> browser.open('index.html')
+ >>> browser.lastRequestSeconds < 10 # really big number for safety
+ True
+ >>> browser.lastRequestPystones < 100000 # really big number for safety
+ True
Property changes on: zc.testbrowser/trunk/src/zc/testbrowser/performance.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: zc.testbrowser/trunk/src/zc/testbrowser/real.py
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/real.py 2008-02-27 03:27:20 UTC (rev 84314)
+++ zc.testbrowser/trunk/src/zc/testbrowser/real.py 2008-02-27 04:11:56 UTC (rev 84315)
@@ -56,9 +56,11 @@
return Control(token, browser)
+
def any(items):
return bool(sum([bool(i) for i in items]))
+
class JSFunctionProxy(object):
def __init__(self, executor, name):
self.executor = executor
@@ -90,8 +92,10 @@
class Browser(zc.testbrowser.browser.SetattrErrorsMixin):
- zope.interface.implements(zc.testbrowser.interfaces.IBrowser)
+ zope.interface.implements(zc.testbrowser.interfaces.IBrowser,
+ zc.testbrowser.interfaces.IWait)
+
base = None
raiseHttpErrors = True
_counter = 0
@@ -99,7 +103,6 @@
def __init__(self, url=None, host='localhost', port=4242):
self.js = JSProxy(self.execute)
- self.timer = zc.testbrowser.browser.PystoneTimer()
self.init_repl(host, port)
self._enable_setattr_errors = True
@@ -158,25 +161,25 @@
def url(self):
return self.execute('content.location')
- def waitForPageLoad(self):
+ def wait(self):
start = time.time()
while self.execute('tb_page_loaded') == 'false':
time.sleep(0.001)
if time.time() - start > self.timeout:
raise RuntimeError('timed out waiting for page load')
+ assert self.execute('tb_page_loaded;') == 'true'
self.execute('tb_page_loaded = false;')
+ assert self.execute('tb_page_loaded;') == 'false'
def open(self, url, data=None):
if self.base is not None:
url = urlparse.urljoin(self.base, url)
assert data is None
- self.start_timer()
try:
self.execute('content.location = ' + simplejson.dumps(url))
- self.waitForPageLoad()
+ self.wait()
finally:
- self.stop_timer()
self._changed()
# TODO raise non-200 errors
@@ -203,52 +206,17 @@
"content.document.getElementsByTagName('pre')[0].innerHTML")
return self.execute('content.document.documentElement.innerHTML')
- @property
- def headers(self):
- raise NotImplementedError
-
- @apply
- def handleErrors():
- def get(self):
- raise NotImplementedError
-
- def set(self, value):
- raise NotImplementedError
-
- return property(get, set)
-
- def start_timer(self):
- self.timer.start()
-
- def stop_timer(self):
- self.timer.stop()
-
- @property
- def lastRequestPystones(self):
- return self.timer.elapsedPystones
-
- @property
- def lastRequestSeconds(self):
- return self.timer.elapsedSeconds
-
def reload(self):
- self.start_timer()
self.execute('content.document.location = content.document.location')
- self.waitForPageLoad()
- self.stop_timer()
+ self.wait()
def goBack(self, count=1):
- self.start_timer()
self.execute('content.back()')
# Our method of knowing when the page finishes loading doesn't work
# for "back", so for now just sleep a little, and hope it is enough.
time.sleep(1)
- self.stop_timer()
self._changed()
- def addHeader(self, key, value):
- raise NotImplementedError
-
def getLink(self, text=None, url=None, id=None, index=0):
zc.testbrowser.browser.onlyOne((text, url, id), 'text, url, or id')
js_index = simplejson.dumps(index)
@@ -269,10 +237,6 @@
return Link(token, self)
- def _follow_link(self, token):
- self.js.tb_follow_link(token)
- self.waitForPageLoad()
-
def getControlToken(self, label=None, name=None, index=None,
context_token=None, xpath=None):
js_index = simplejson.dumps(index)
@@ -348,9 +312,7 @@
def click(self):
if self._browser_counter != self.browser._counter:
raise zc.testbrowser.interfaces.ExpiredError
- self.browser.start_timer()
- self.browser._follow_link(self.token)
- self.browser.stop_timer()
+ self.browser.js.tb_click_token(self.token)
self.browser._changed()
@property
@@ -687,8 +649,6 @@
if self._browser_counter != self.browser._counter:
raise zc.testbrowser.interfaces.ExpiredError
- self.browser.start_timer()
-
if (label is None and
name is None):
self.browser.execute('tb_tokens[%s].submit()' % self.token)
@@ -696,7 +656,6 @@
button = self.browser.getControlToken(
label, name, index, self.token)
self.browser.js.tb_click_token(button, *coord)
- self.browser.stop_timer()
self.browser._changed()
def getControl(self, label=None, name=None, index=None):
Modified: zc.testbrowser/trunk/src/zc/testbrowser/screen-shots.txt
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/screen-shots.txt 2008-02-27 03:27:20 UTC (rev 84314)
+++ zc.testbrowser/trunk/src/zc/testbrowser/screen-shots.txt 2008-02-27 04:11:56 UTC (rev 84315)
@@ -1,4 +1,4 @@
>>> from zc.testbrowser.real import Browser
>>> browser = Browser()
- >>> browser.open('http://slashdot.org')
+ >>> browser.open('http://localhost:%s/index.html' % TEST_PORT)
>>> browser.execute('tb_take_screen_shot("/tmp/1.png")')
Modified: zc.testbrowser/trunk/src/zc/testbrowser/tests.py
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/tests.py 2008-02-27 03:27:20 UTC (rev 84314)
+++ zc.testbrowser/trunk/src/zc/testbrowser/tests.py 2008-02-27 04:11:56 UTC (rev 84315)
@@ -437,7 +437,7 @@
def setUpServer(test):
global server_stop
server_stop = False
- port = random.randint(20000,30000)
+ port = random.randint(20000, 30000)
test.globs['TEST_PORT'] = port
server = BaseHTTPServer.HTTPServer(('localhost', port), TestHandler)
thread = threading.Thread(target=serve_requests, args=[server])
@@ -459,42 +459,38 @@
test.globs['Browser'] = zc.testbrowser.real.Browser
setUpServer(test)
-def tearDownReal(test):
- tearDownServer(test)
-
-def setUpReadme(test):
+def setUpBrowserClass(test):
test.globs['Browser'] = zc.testbrowser.browser.Browser
setUpServer(test)
-def tearDownReadme(test):
- tearDownServer(test)
-
-def setUpHeaders(test):
- setUpServer(test)
+def setUpBrowserInstance(test):
test.globs['browser'] = zc.testbrowser.browser.Browser()
+ setUpServer(test)
-def tearDownHeaders(test):
- tearDownServer(test)
-
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
readme = doctest.DocFileSuite('README.txt', optionflags=flags,
- checker=checker, setUp=setUpReadme, tearDown=tearDownReadme)
+ checker=checker, setUp=setUpBrowserClass, tearDown=tearDownServer)
headers = doctest.DocFileSuite('headers.txt', optionflags=flags,
- setUp=setUpHeaders, tearDown=tearDownHeaders)
+ setUp=setUpBrowserInstance, tearDown=tearDownServer)
+ performance = doctest.DocFileSuite('performance.txt', optionflags=flags,
+ setUp=setUpBrowserInstance, tearDown=tearDownServer)
+
real_readme = doctest.DocFileSuite('README.txt', optionflags=flags,
- checker=checker, setUp=setUpReal, tearDown=tearDownReal)
+ checker=checker, setUp=setUpReal, tearDown=tearDownServer)
real_readme.level = 3
- screen_shots = doctest.DocFileSuite('screen-shots.txt', optionflags=flags)
+ screen_shots = doctest.DocFileSuite('screen-shots.txt', optionflags=flags,
+ setUp=setUpServer, tearDown=tearDownServer)
screen_shots.level = 3
this_file = doctest.DocTestSuite(checker=checker)
- return unittest.TestSuite((this_file, readme, real_readme, screen_shots))
+ return unittest.TestSuite((this_file, readme, real_readme, headers,
+ performance, screen_shots))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
More information about the Checkins
mailing list