Testing Applications with WebTest¶
author: | Ian Bicking <ianb@colorstudy.com> |
---|---|
maintainer: | Gael Pasgrimaud <gael@gawel.org> |
Status & License¶
WebTest is an extraction of paste.fixture.TestApp
, rewriting
portions to use WebOb. It is under
active development as part of the Pylons cloud of packages.
Feedback and discussion should take place on the Pylons discuss list, and bugs should go into the Github tracker.
This library is licensed under an MIT-style license.
Installation¶
You can use pip or easy_install to get the latest stable release:
$ pip install WebTest
$ easy_install WebTest
Or if you want the development version:
$ pip install https://nodeload.github.com/Pylons/webtest/tar.gz/master
What This Does¶
WebTest helps you test your WSGI-based web applications. This can be any application that has a WSGI interface, including an application written in a framework that supports WSGI (which includes most actively developed Python web frameworks -- almost anything that even nominally supports WSGI should be testable).
With this you can test your web applications without starting an HTTP server, and without poking into the web framework shortcutting pieces of your application that need to be tested. The tests WebTest runs are entirely equivalent to how a WSGI HTTP server would call an application. By testing the full stack of your application, the WebTest testing model is sometimes called a functional test, integration test, or acceptance test (though the latter two are not particularly good descriptions). This is in contrast to a unit test which tests a particular piece of functionality in your application. While complex programming tasks are often suited to unit tests, template logic and simple web programming is often best done with functional tests; and regardless of the presence of unit tests, no testing strategy is complete without high-level tests to ensure the entire programming system works together.
WebTest helps you create tests by providing a convenient interface to run WSGI applications and verify the output.
Quick start¶
The most important object in WebTest is TestApp
, the wrapper
for WSGI applications. It also allows you to perform HTTP requests on it.
To use it, you simply instantiate it with your WSGI application.
Note
If your WSGI application requires any configuration, you must set that up manually in your tests.
Here is a basic application:
>>> def application(environ, start_response):
... headers = [('Content-Type', 'text/html; charset=utf8'),
... ('Content-Length', str(len(body)))]
... start_response('200 OK', headers)
... return [body]
Wrap it into a TestApp
:
>>> from webtest import TestApp
>>> app = TestApp(application)
Then you can get the response of a HTTP GET:
>>> resp = app.get('/')
And check the results, like response's status:
>>> assert resp.status == '200 OK'
>>> assert resp.status_int == 200
Response's headers:
>>> assert resp.content_type == 'text/html'
>>> assert resp.content_length > 0
Or response's body:
>>> resp.mustcontain('<html>')
>>> assert 'form' in resp
WebTest can do much more. In particular, it can handle Form handling.
Contents¶
Functional Testing of Web Applications¶
TestApp¶
Making Requests¶
To make a request, use:
app.get('/path', [params], [headers], [extra_environ], ...)
This call to get()
does a request for
/path
, with any params, extra headers or WSGI
environment keys that you indicate. This returns a
TestResponse
object,
based on webob.response.Response
. It has some
additional methods to make it easier to test.
If you want to do a POST request, use:
app.post('/path', {'vars': 'values'}, [headers], [extra_environ],
[upload_files], ...)
Specifically the second argument of post()
is the body of the request. You
can pass in a dictionary (or dictionary-like object), or a string
body (dictionary objects are turned into HTML form submissions).
You can also pass in the keyword argument upload_files, which is a
list of [(fieldname, filename, field_content)]
. File uploads use a
different form submission data type to pass the structured data.
Making JSON Requests¶
Webtest provide some facilities to test json apis.
The *_json
methods will transform data to json before POST
/PUT
and
add the correct Content-Type
for you.
Also Response have an attribute json
to allow you to retrieve json
contents as a python dict.
Doing POST request with webtest.app.TestApp.post_json()
:
>>> resp = app.post_json('/resource/', dict(id=1, value='value'))
>>> print(resp.request)
POST /resource/ HTTP/1.0
Content-Length: 27
Content-Type: application/json
...
>>> resp.json == {'id': 1, 'value': 'value'}
True
Doing GET request with webtest.app.TestApp.get()
and using json
:
To just parse body of the response, use Response.json:
>>> resp = app.get('/resource/1/')
>>> print(resp.request)
GET /resource/1/ HTTP/1.0
...
>>> resp.json == {'id': 1, 'value': 'value'}
True
Modifying the Environment & Simulating Authentication¶
The best way to simulate authentication is if your application looks
in environ['REMOTE_USER']
to see if someone is authenticated.
Then you can simply set that value, like:
app.get('/secret', extra_environ=dict(REMOTE_USER='bob'))
If you want all your requests to have this key, do:
app = TestApp(my_app, extra_environ=dict(REMOTE_USER='bob'))
If you have to use HTTP authorization you can use the .authorization
property to set the HTTP_AUTHORIZATION
key of the extra_environ
dictionary:
app = TestApp(my_app)
app.authorization = ('Basic', ('user', 'password'))
You can also use bearer token or JWT authorization types:
app = TestApp(my_app)
app.authorization = ('Bearer', 'mytoken')
# or
app.authorization = ('JWT', 'myjwt')
Testing a non wsgi application¶
You can use WebTest to test an application on a real web server. Just pass an url to the TestApp instead of a WSGI application:
app = TestApp('http://my.cool.websi.te')
You can also use the WEBTEST_TARGET_URL
env var to switch from a WSGI
application to a real server without having to modify your code:
os.environ['WEBTEST_TARGET_URL'] = 'http://my.cool.websi.te'
app = TestApp(wsgiapp) # will use the WEBTEST_TARGET_URL instead of the wsgiapp
By default the proxy will use httplib
but you can use other backends by
adding an anchor to your url:
app = TestApp('http://my.cool.websi.te#urllib3')
app = TestApp('http://my.cool.websi.te#requests')
app = TestApp('http://my.cool.websi.te#restkit')
What Is Tested By Default¶
A key concept behind WebTest is that there's lots of things you
shouldn't have to check everytime you do a request. It is assumed
that the response will either be a 2xx or 3xx response; if it isn't an
exception will be raised (you can override this for a request, of
course). The WSGI application is tested for WSGI compliance with
a slightly modified version of wsgiref.validate
(modified to support arguments to InputWrapper.readline
)
automatically. Also it checks that nothing is printed to the
environ['wsgi.errors']
error stream, which typically indicates a
problem (one that would be non-fatal in a production situation, but if
you are testing is something you should avoid).
To indicate another status is expected, use the keyword argument
status=404
to (for example) check that it is a 404 status, or
status="*"
to allow any status, or status="400 Custom Bad Request"
to use custom reason phrase.
If you expect errors to be printed, use expect_errors=True
.
TestResponse¶
The response object is based on webob.response.Response
with some additions
to help with testing.
The inherited attributes that are most interesting:
response.status
:- The text status of the response, e.g.,
"200 OK"
. response.status_int
:- The text status_int of the response, e.g.,
200
. response.headers
:- A dictionary-like object of the headers in the response.
response.body
:- The text body of the response.
response.text
:- The unicode text body of the response.
response.normal_body
:- The whitespace-normalized [1] body of the response.
response.request
:- The
webob.request.BaseRequest
object used to generate this response.
The added methods:
response.follow(**kw)
:- Follows the redirect, returning the new response. It is an error
if this response wasn't a redirect. All keyword arguments are
passed to
webtest.app.TestApp
(e.g.,status
). Returns another response object. response.maybe_follow(**kw)
:- Follows all redirects; does nothing if this response
is not a redirect. All keyword arguments are passed
to
webtest.app.TestApp
(e.g.,status
). Returns another response object. x in response
:- Returns True if the string is found in the response body. Whitespace is normalized for this test.
response.mustcontain(string1, string2, no=string3)
:- Raises an error if any of the strings are not found in the response. If a string of a string list is given as no keyword argument, raise an error if one of those are found in the response. It also prints out the response in that case, so you can see the real response.
response.showbrowser()
:- Opens the HTML response in a browser; useful for debugging.
str(response)
:- Gives a slightly-compacted version of the response. This is
compacted to remove newlines, making it easier to use with
doctest
. response.click(description=None, linkid=None, href=None, anchor=None, index=None, verbose=False)
:- Clicks the described link (see
click()
) response.forms
:- Return a dictionary of forms; you can use both indexes (refer to the forms in order) or the string ids of forms (if you've given them ids) to identify the form. See Form handling for more on the form objects.
response.form
:- If there is just a single form, this returns that. It is an error if you use this and there are multiple forms.
Footnotes
[1] | The whitespace normalization replace sequences of whitespace characters and \n \r \t by a single space. |
Form handling¶
Getting a form¶
If you have a single html form in your page, just use the .form
attribute:
>>> res = app.get('/form.html')
>>> form = res.form
You can use the form index if your html contains more than one form:
>>> form = res.forms[0]
Or the form id:
>>> form = res.forms['myform']
You can check form attributes:
>>> print(form.id)
myform
>>> print(form.action)
/form-submit
>>> print(form.method)
POST
Filling a form¶
You can fill out and submit forms from your tests. Fields are a dict like object:
>>> # dict of fields
>>> form.fields.items()
[(u'text', [<Text name="text">]), ..., (u'submit', [<Submit name="submit">])]
You can check the current value:
>>> print(form['text'].value)
Foo
Then you fill it in fields:
>>> form['text'] = 'Bar'
>>> # When names don't point to a single field:
>>> form.set('text', 'Bar', index=0)
Field types¶
>>> print(form['textarea'].value)
Some text
>>> form['textarea'] = 'Some other text'
You can force the value of an hidden field:
>>> form['hidden'].force_value('2')
Simple select:
>>> print(form['select'].value)
option2
>>> form['select'] = 'option1'
Select multiple:
>>> print(form['multiple'].value)
['option2', 'option3']
>>> form['multiple'] = ['option1']
You can select an option by its text with .select()
:
>>> form['select'].select(text="Option 2")
>>> print(form['select'].value)
option2
For select multiple use .select_multiple()
:
>>> form['multiple'].select_multiple(texts=["Option 1", "Option 2"])
>>> print(form['multiple'].value)
['option1', 'option2']
Select fields can only be set to valid values (i.e., values in an <option>
)
but you can also use .force_value()
to enter values not present in an
option.
>>> form['select'].force_value(['optionX'])
>>> form['multiple'].force_value(['optionX'])
Checkbox
¶You can check if the checkbox is checked and is value:
>>> print(form['checkbox'].checked)
False
>>> print(form['checkbox'].value)
None
You can change the status with the value:
>>> form['checkbox'] = True
Or with the checked attribute:
>>> form['checkbox'].checked =True
If the checkbox is checked then you'll get the value:
>>> print(form['checkbox'].checked)
True
>>> print(form['checkbox'].value)
checkbox 1
If the checkbox has no value then it will be 'on' if you checked it:
>>> print(form['checkbox2'].value)
None
>>> form['checkbox2'].checked = True
>>> print(form['checkbox2'].value)
on
If there are multiple checkboxes of the same name, you can assign a list to that name to check all the checkboxes whose value is present in the list:
>>> form['checkboxes'] = ['a', 'c']
>>> print(form.get('checkboxes', index=0).value)
a
>>> print(form.get('checkboxes', index=1).value)
None
>>> print(form.get('checkboxes', index=2).value)
c
>>> print(form['radio'].value)
Radio 2
>>> form['radio'] = 'Radio 1'
You can deal with file upload by using the Upload class:
>>> from webtest import Upload
>>> form['file'] = Upload('README.rst')
>>> form['file'] = Upload('README.rst', b'data')
>>> form['file'] = Upload('README.rst', b'data', 'text/x-rst')
Submit a form¶
Then you can submit the form:
>>> # Submit with no particular submit button pressed:
>>> res = form.submit()
>>> # Or submit a button:
>>> res = form.submit('submit')
>>> print(res)
Response: 200 OK
Content-Type: text/plain
text=Bar
...
submit=Submit
You can also select a specific submit button by its index:
>>> res = form.submit('submit', index=1)
>>> print(res)
Response: 200 OK
Content-Type: text/plain
...
submit=Submit 2
And you can select it by its value:
>>> res = form.submit('submit', value="Submit 2")
>>> print(res)
Response: 200 OK
Content-Type: text/plain
...
submit=Submit 2
Parsing the Body¶
There are several ways to get parsed versions of the response. These are the attributes:
response.html
:Return a BeautifulSoup version of the response body:
>>> res = app.get('/index.html') >>> res.html <html><body><div id="content">hey!</div></body></html> >>> res.html.__class__ <class '...BeautifulSoup'>
response.xml
:Return an
ElementTree
version of the response body:>>> res = app.get('/document.xml') >>> res.xml <Element 'xml' ...> >>> res.xml[0].tag 'message' >>> res.xml[0].text 'hey!'
response.lxml
:Return an lxml version of the response body:
>>> res = app.get('/index.html') >>> res.lxml <Element html at ...> >>> res.lxml.xpath('//body/div')[0].text 'hey!' >>> res = app.get('/document.xml') >>> res.lxml <Element xml at ...> >>> res.lxml[0].tag 'message' >>> res.lxml[0].text 'hey!'
response.pyquery
:Return a PyQuery version of the response body:
>>> res.pyquery('message') [<message>] >>> res.pyquery('message').text() 'hey!'
response.json
:Return the parsed JSON (parsed with simplejson):
>>> res = app.get('/object.json') >>> sorted(res.json.values()) [1, 2]
In each case the content-type must be correct or an AttributeError is raised. If you do not have the necessary library installed (none of them are required by WebTest), you will get an ImportError.
WSGI Debug application¶
webtest.debugapp.debug_app
is a faker WSGI app to help to test webtest.
Examples of use :
>>> import webtest
>>> from webtest.debugapp import debug_app
>>> app = webtest.TestApp(debug_app)
>>> res = app.post('/', params='foobar')
>>> print(res.body)
CONTENT_LENGTH: 6
CONTENT_TYPE: application/x-www-form-urlencoded
HTTP_HOST: localhost:80
...
wsgi.url_scheme: 'http'
wsgi.version: (1, 0)
-- Body ----------
foobar
Here, you can see, foobar
in body when you pass foobar
in app.post
params
argument.
You can also define the status of response :
>>> res = app.post('/?status=302', params='foobar')
>>> print(res.status)
302 Found
Framework Hooks¶
Frameworks can detect that they are in a testing environment by the
presence (and truth) of the WSGI environmental variable
"paste.testing"
(the key name is inherited from
paste.fixture
).
More generally, frameworks can detect that something (possibly a test
fixture) is ready to catch unexpected errors by the presence and truth
of "paste.throw_errors"
(this is sometimes set outside of testing
fixtures too, when an error-handling middleware is in place).
Frameworks that want to expose the inner structure of the request may
use "paste.testing_variables"
. This will be a dictionary -- any
values put into that dictionary will become attributes of the response
object. So if you do env["paste.testing_variables"]['template'] =
template_name
in your framework, then response.template
will be
template_name
.
webtest
API¶
Routines for testing WSGI applications.
webtest.app.TestApp
¶
-
class
webtest.app.
TestApp
(app, extra_environ=None, relative_to=None, use_unicode=True, cookiejar=None, parser_features=None, json_encoder=None, lint=True)[source]¶ Wraps a WSGI application in a more convenient interface for testing. It uses extended version of
webob.BaseRequest
andwebob.Response
.Parameters: - app (WSGI application) --
May be an WSGI application or Paste Deploy app, like
'config:filename.ini#test'
.New in version 2.0.
It can also be an actual full URL to an http server and webtest will proxy requests with WSGIProxy2.
- extra_environ (dict) -- A dictionary of values that should go into the environment for each request. These can provide a communication channel with the application.
- relative_to (string) -- A directory used for file
uploads are calculated relative to this. Also
config:
URIs that aren't absolute. - cookiejar (CookieJar instance) --
cookielib.CookieJar
alike API that keeps cookies across requests.
A convenient shortcut for a dict of all cookies in
cookiejar
.
Parameters: - parser_features (string or list) -- Passed to BeautifulSoup when parsing responses.
- json_encoder (A subclass of json.JSONEncoder) -- Passed to json.dumps when encoding json
- lint (A boolean) -- If True (default) then check that the application is WSGI compliant
-
RequestClass
¶ alias of
TestRequest
Allow to set the HTTP_AUTHORIZATION environ key. Value should look like one of the following:
('Basic', ('user', 'password'))
('Bearer', 'mytoken')
('JWT', 'myjwt')
If value is None the the HTTP_AUTHORIZATION is removed
-
delete
(url, params='', headers=None, extra_environ=None, status=None, expect_errors=False, content_type=None, xhr=False)[source]¶ Do a DELETE request. Similar to
get()
.Returns: webtest.TestResponse
instance.
-
delete_json
(url, params=<NoDefault>, **kw)¶ Do a DELETE request. Very like the
delete
method.params
are dumped to json and put in the body of the request. Content-Type is set toapplication/json
.Returns a
webtest.TestResponse
object.
-
do_request
(req, status=None, expect_errors=None)[source]¶ Executes the given webob Request (
req
), with the expectedstatus
. Generallyget()
andpost()
are used instead.To use this:
req = webtest.TestRequest.blank('url', ...args...) resp = app.do_request(req)
Note
You can pass any keyword arguments to
TestRequest.blank()
, which will be set on the request. These can be arguments likecontent_type
,accept
, etc.
-
encode_multipart
(params, files)[source]¶ Encodes a set of parameters (typically a name/value list) and a set of files (a list of (name, filename, file_body, mimetype)) into a typical POST body, returning the (content_type, body).
-
get
(url, params=None, headers=None, extra_environ=None, status=None, expect_errors=False, xhr=False)[source]¶ Do a GET request given the url path.
Parameters: - params -- A query string, or a dictionary that will be encoded
into a query string. You may also include a URL query
string on the
url
. - headers (dictionary) -- Extra headers to send.
- extra_environ (dictionary) -- Environmental variables that should be added to the request.
- status (integer or string) -- The HTTP status code you expect in response (if not 200 or 3xx).
You can also use a wildcard, like
'3*'
or'*'
. - expect_errors (boolean) -- If this is False, then if anything is written to
environ
wsgi.errors
it will be an error. If it is True, then non-200/3xx responses are also okay. - xhr (boolean) -- If this is true, then marks response as ajax. The same as headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }
Returns: webtest.TestResponse
instance.- params -- A query string, or a dictionary that will be encoded
into a query string. You may also include a URL query
string on the
Allow to set the HTTP_AUTHORIZATION environ key. Value should look like one of the following:
('Basic', ('user', 'password'))
('Bearer', 'mytoken')
('JWT', 'myjwt')
If value is None the the HTTP_AUTHORIZATION is removed
-
head
(url, params=None, headers=None, extra_environ=None, status=None, expect_errors=False, xhr=False)[source]¶ Do a HEAD request. Similar to
get()
.Returns: webtest.TestResponse
instance.
-
options
(url, headers=None, extra_environ=None, status=None, expect_errors=False, xhr=False)[source]¶ Do a OPTIONS request. Similar to
get()
.Returns: webtest.TestResponse
instance.
-
patch
(url, params='', headers=None, extra_environ=None, status=None, upload_files=None, expect_errors=False, content_type=None, xhr=False)[source]¶ Do a PATCH request. Similar to
post()
.Returns: webtest.TestResponse
instance.
-
patch_json
(url, params=<NoDefault>, **kw)¶ Do a PATCH request. Very like the
patch
method.params
are dumped to json and put in the body of the request. Content-Type is set toapplication/json
.Returns a
webtest.TestResponse
object.
-
post
(url, params='', headers=None, extra_environ=None, status=None, upload_files=None, expect_errors=False, content_type=None, xhr=False)[source]¶ Do a POST request. Similar to
get()
.Parameters: - params --
Are put in the body of the request. If params is an iterator, it will be urlencoded. If it is a string, it will not be encoded, but placed in the body directly.
Can be a
collections.OrderedDict
withwebtest.forms.Upload
fields included:app.post('/myurl', collections.OrderedDict([ ('textfield1', 'value1'), ('uploadfield', webapp.Upload('filename.txt', 'contents'), ('textfield2', 'value2')])))
- upload_files (list) -- It should be a list of
(fieldname, filename, file_content)
. You can also use just(fieldname, filename)
and the file contents will be read from disk. - content_type (string) -- HTTP content type, for example application/json.
- xhr (boolean) -- If this is true, then marks response as ajax. The same as headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }
Returns: webtest.TestResponse
instance.- params --
-
post_json
(url, params=<NoDefault>, **kw)¶ Do a POST request. Very like the
post
method.params
are dumped to json and put in the body of the request. Content-Type is set toapplication/json
.Returns a
webtest.TestResponse
object.
-
put
(url, params='', headers=None, extra_environ=None, status=None, upload_files=None, expect_errors=False, content_type=None, xhr=False)[source]¶ Do a PUT request. Similar to
post()
.Returns: webtest.TestResponse
instance.
-
put_json
(url, params=<NoDefault>, **kw)¶ Do a PUT request. Very like the
put
method.params
are dumped to json and put in the body of the request. Content-Type is set toapplication/json
.Returns a
webtest.TestResponse
object.
-
request
(url_or_req, status=None, expect_errors=False, **req_params)[source]¶ Creates and executes a request. You may either pass in an instantiated
TestRequest
object, or you may pass in a URL and keyword arguments to be passed toTestRequest.blank()
.You can use this to run a request without the intermediary functioning of
TestApp.get()
etc. For instance, to test a WebDAV method:resp = app.request('/new-col', method='MKCOL')
Note that the request won't have a body unless you specify it, like:
resp = app.request('/test.txt', method='PUT', body='test')
You can use
webtest.TestRequest
:req = webtest.TestRequest.blank('/url/', method='GET') resp = app.do_request(req)
Sets a cookie to be passed through with requests.
- app (WSGI application) --
webtest.app.TestRequest
¶
-
class
webtest.app.
TestRequest
(environ, charset=None, unicode_errors=None, decode_param_names=None, **kw)[source]¶ Bases:
webob.request.BaseRequest
A subclass of webob.Request
-
ResponseClass
¶ alias of
webtest.response.TestResponse
-
webtest.response.TestResponse
¶
-
class
webtest.response.
TestResponse
(body=None, status=None, headerlist=None, app_iter=None, content_type=None, conditional_response=None, charset=<object object>, **kw)[source]¶ Bases:
webob.response.Response
Instances of this class are returned by
TestApp
methods.-
click
(description=None, linkid=None, href=None, index=None, verbose=False, extra_environ=None)[source]¶ Click the link as described. Each of
description
,linkid
, andurl
are patterns, meaning that they are either strings (regular expressions), compiled regular expressions (objects with asearch
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<a...>
and</a>
)linkid
is a pattern that matches theid
attribute of the anchor. It will receive the empty string if no id is given.href
is a pattern that matches thehref
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. Ifindex
is not given and more than one link matches, or if no link matches, thenIndexError
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 useapp.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.
Like
click()
, except looks for link-like buttons. This kind of button should look like<button onclick="...location.href='url'...">
.
-
follow
(**kw)[source]¶ 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
webtest.app.TestApp.get
. Returns anotherTestResponse
object.
-
form
¶ If there is only one form on the page, return it as a
Form
object; raise a TypeError is there are no form or multiple forms.
-
forms
¶ Returns a dictionary containing all the forms in the pages as
Form
objects. Indexes are both in order (from zero) and by form id (if the form is given an id).See Form handling for more info on form objects.
-
goto
(href, method='get', **args)[source]¶ Go to the (potentially relative) link
href
, using the given method ('get'
or'post'
) and any extra arguments you want to pass to thewebtest.app.TestApp.get()
orwebtest.app.TestApp.post()
methods.All hostnames and schemes will be ignored.
-
html
¶ Returns the response as a BeautifulSoup object.
Only works with HTML responses; other content-types raise AttributeError.
-
json
¶ Return the response as a JSON response. The content type must be one of json type to use this.
-
lxml
¶ Returns the response as an lxml object. You must have lxml installed to use this.
If this is an HTML response and you have lxml 2.x installed, then an
lxml.html.HTML
object will be returned; if you have an earlier version of lxml then alxml.HTML
object will be returned.
-
maybe_follow
(**kw)[source]¶ Follow all redirects. If this response is not a redirect, do nothing. Any keyword arguments are passed to
webtest.app.TestApp.get
. Returns anotherTestResponse
object.
-
mustcontain
(*strings, no=[])[source]¶ Assert that the response contains all of the strings passed in as arguments.
Equivalent to:
assert string in res
Can take a no keyword argument that can be a string or a list of strings which must not be present in the response.
-
normal_body
¶ Return the whitespace-normalized body
-
pyquery
¶ Returns the response as a PyQuery object.
Only works with HTML and XML responses; other content-types raise AttributeError.
-
showbrowser
()[source]¶ Show this response in a browser window (for debugging purposes, when it's hard to read the HTML).
-
unicode_normal_body
¶ Return the whitespace-normalized body, as unicode
-
xml
¶ Returns the response as an
ElementTree
object.Only works with XML responses; other content-types raise AttributeError
-
webtest.forms
¶
Helpers to fill and submit forms.
-
class
webtest.forms.
Checkbox
(*args, **attrs)[source]¶ Bases:
webtest.forms.Field
Field representing
<input type="checkbox">
-
checked
¶ Returns True if checkbox is checked.
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
Email
(form, tag, name, pos, value=None, id=None, **attrs)[source]¶ Bases:
webtest.forms.Field
Field representing
<input type="email">
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
Field
(form, tag, name, pos, value=None, id=None, **attrs)[source]¶ Bases:
object
Base class for all Field objects.
-
classes
¶ Dictionary of field types (select, radio, etc)
-
value
¶ Set/get value of the field.
-
-
class
webtest.forms.
File
(form, tag, name, pos, value=None, id=None, **attrs)[source]¶ Bases:
webtest.forms.Field
Field representing
<input type="file">
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
Form
(response, text, parser_features='html.parser')[source]¶ Bases:
object
This object represents a form that has been found in a page.
Parameters: - response -- webob.response.TestResponse instance
- text -- Unparsed html of the form
-
text
¶ the full HTML of the form.
-
action
¶ the relative URI of the action.
-
method
¶ the HTTP method (e.g.,
'GET'
).
-
id
¶ the id, or None if not given.
-
enctype
¶ encoding of the form submission
-
fields
¶ a dictionary of fields, each value is a list of fields by that name.
<input type="radio">
and<select>
are both represented as single fields with multiple options.
-
field_order
¶ Ordered list of field names as found in the html.
-
get
(name, index=None, default=<NoDefault>)[source]¶ Get the named/indexed field object, or
default
if no field is found. Throws an AssertionError if no field is found and nodefault
was given.
-
lint
()[source]¶ Check that the html is valid:
- each field must have an id
- each field must have a label
-
select
(name, value=None, text=None, index=None)[source]¶ Like
.set()
, except also confirms the target is a<select>
and allows selecting options by text.
-
select_multiple
(name, value=None, texts=None, index=None)[source]¶ Like
.set()
, except also confirms the target is a<select multiple>
and allows selecting options by text.
-
submit
(name=None, index=None, value=None, **args)[source]¶ Submits the form. If
name
is given, then also select that button (usingindex
orvalue
to disambiguate)``.Any extra keyword arguments are passed to the
webtest.TestResponse.get()
orwebtest.TestResponse.post()
method.Returns a
webtest.TestResponse
object.
-
class
webtest.forms.
Hidden
(form, tag, name, pos, value=None, id=None, **attrs)[source]¶ Bases:
webtest.forms.Text
Field representing
<input type="hidden">
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
MultipleSelect
(*args, **attrs)[source]¶ Bases:
webtest.forms.Field
Field representing
<select multiple="multiple">
-
class
webtest.forms.
Radio
(*args, **attrs)[source]¶ Bases:
webtest.forms.Select
Field representing
<input type="radio">
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
Select
(*args, **attrs)[source]¶ Bases:
webtest.forms.Field
Field representing
<select />
form element.
-
class
webtest.forms.
Submit
(form, tag, name, pos, value=None, id=None, **attrs)[source]¶ Bases:
webtest.forms.Field
Field representing
<input type="submit">
and<button>
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
Text
(form, tag, name, pos, value=None, id=None, **attrs)[source]¶ Bases:
webtest.forms.Field
Field representing
<input type="text">
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
Textarea
(form, tag, name, pos, value=None, id=None, **attrs)[source]¶ Bases:
webtest.forms.Text
Field representing
<textarea>
-
force_value
(value)¶ Like setting a value, except forces it (even for, say, hidden fields).
-
-
class
webtest.forms.
Upload
(filename, content=None, content_type=None)[source]¶ Bases:
object
A file to upload:
>>> Upload('filename.txt', 'data', 'application/octet-stream') <Upload "filename.txt"> >>> Upload('filename.txt', 'data') <Upload "filename.txt"> >>> Upload("README.txt") <Upload "README.txt">
Parameters: - filename -- Name of the file to upload.
- content -- Contents of the file.
- content_type -- MIME type of the file.
webtest.http
¶
This module contains some helpers to deal with the real http world.
-
class
webtest.http.
StopableWSGIServer
(application, *args, **kwargs)[source]¶ Bases:
waitress.server.TcpWSGIServer
StopableWSGIServer is a TcpWSGIServer which run in a separated thread. This allow to use tools like casperjs or selenium.
Server instance have an
application_url
attribute formated with the server host and port.
webtest.lint
¶
Middleware to check for obedience to the WSGI specification.
Some of the things this checks:
- Signature of the application and start_response (including that keyword arguments are not used).
- Environment checks:
- Environment is a dictionary (and not a subclass).
- That all the required keys are in the environment: REQUEST_METHOD, SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors, wsgi.multithread, wsgi.multiprocess, wsgi.run_once
- That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the environment (these headers should appear as CONTENT_LENGTH and CONTENT_TYPE).
- Warns if QUERY_STRING is missing, as the cgi module acts unpredictably in that case.
- That CGI-style variables (that don't contain a .) have (non-unicode) string values
- That wsgi.version is a tuple
- That wsgi.url_scheme is 'http' or 'https' (@@: is this too restrictive?)
- Warns if the REQUEST_METHOD is not known (@@: probably too restrictive).
- That SCRIPT_NAME and PATH_INFO are empty or start with /
- That at least one of SCRIPT_NAME or PATH_INFO are set.
- That CONTENT_LENGTH is a positive integer.
- That SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should be '/').
- That wsgi.input has the methods read, readline, readlines, and __iter__
- That wsgi.errors has the methods flush, write, writelines
- The status is a string, contains a space, starts with an integer, and that integer is in range (> 100).
- That the headers is a list (not a subclass, not another kind of sequence).
- That the items of the headers are tuples of 'native' strings (i.e. bytestrings in Python2, and unicode strings in Python3).
- That there is no 'status' header (that is used in CGI, but not in WSGI).
- That the headers don't contain newlines or colons, end in _ or -, or contain characters codes below 037.
- That Content-Type is given if there is content (CGI often has a default content type, but WSGI does not).
- That no Content-Type is given when there is no content (@@: is this too restrictive?)
- That the exc_info argument to start_response is a tuple or None.
- That all calls to the writer are with strings, and no other methods on the writer are accessed.
- That wsgi.input is used properly:
- .read() is called with zero or one argument
- That it returns a string
- That readline, readlines, and __iter__ return strings
- That .close() is not called
- No other methods are provided
- That wsgi.errors is used properly:
- .write() and .writelines() is called with a string, except with python3
- That .close() is not called, and no other methods are provided.
- The response iterator:
- That it is not a string (it should be a list of a single string; a string will work, but perform horribly).
- That .next() returns a string
- That the iterator is not iterated over until start_response has been called (that can signal either a server or application error).
- That .close() is called (doesn't raise exception, only prints to sys.stderr, because we only know it isn't called when the object is garbage collected).
-
webtest.lint.
middleware
(application, global_conf=None)[source]¶ When applied between a WSGI server and a WSGI application, this middleware will check for WSGI compliancy on a number of levels. This middleware does not modify the request or response in any way, but will throw an AssertionError if anything seems off (except for a failure to close the application iterator, which will be printed to stderr -- there's no way to throw an exception at that point).
webtest.debugapp
¶
Contribute to webtest project¶
Getting started¶
Get your working copy :
$ git clone https://github.com/Pylons/webtest.git
$ cd webtest
$ virtualenv .
$ . bin/activate
$ python setup.py dev
Now, you can hack.
Execute tests¶
$ bin/pytest
Doctest: forms.rst ... ok
Doctest: index.rst ... ok
...
test_url_class (tests.test_testing.TestTesting) ... ok
tests.test_testing.test_print_unicode ... °C
ok
Name Stmts Miss Cover Missing
------------------------------------------------
webtest 18 0 100%
webtest.app 603 92 85% 48, 61-62, 94, 98, 212-221, 264-265, 268-272, 347, 379-386, 422, 426-428, 432-434, 455, 463, 471, 473, 488, 496-497, 515, 520-527, 548, 553-554, 558-559, 577, 592, 597-598, 618, 624, 661-664, 742, 808, 872, 940-941, 945-948, 961-964, 975, 982, 995, 1000, 1006, 1010, 1049, 1051, 1095-1096, 1118-1119, 1122-1127, 1135-1136, 1148, 1155-1160, 1175
webtest.compat 50 11 78% 28-34, 55-56, 61-62
webtest.debugapp 58 0 100%
webtest.ext 80 0 100%
webtest.forms 324 23 93% 23, 49, 58, 61, 92, 116, 177, 205, 411, 478, 482-486, 491-493, 522, 538, 558-561
webtest.http 78 0 100%
webtest.lint 215 45 79% 135, 176, 214-216, 219-224, 227-231, 234, 243-244, 247, 250-251, 254, 263-264, 270, 274, 307, 311, 335, 359, 407, 424-427, 441-444, 476-479, 493, 508
webtest.sel 479 318 34% 38-39, 45-46, 64-78, 88-108, 120, 126, 151-153, 156-158, 164-165, 168-191, 194-201, 219-231, 236, 240, 243-259, 263-297, 301-306, 316-326, 331-336, 340, 344, 347-352, 357-359, 364, 392-394, 397-404, 408, 412-417, 421, 425-426, 430, 434, 438, 442, 445, 448-457, 470-480, 483-485, 488, 492, 495, 503, 506, 515-516, 520, 524, 528, 533, 538, 542-544, 547, 560-565, 576, 579, 582, 593-596, 599-602, 605-606, 617-620, 623-642, 668-677, 680-688, 715, 720, 732, 735, 744-754, 757-762, 770-779, 791, 794, 805-809, 813-826, 838-842
webtest.utils 99 11 89% 19-20, 23, 26, 32, 38, 100, 109, 152-154
------------------------------------------------
TOTAL 2004 500 75%
----------------------------------------------------------------------
Ran 70 tests in 14.940s
Use tox to test many Python versions¶
Tox installation :
$ pip install tox
$ tox
Launch tests with tox :
$ bin/tox
py26: commands succeeded
py27: commands succeeded
py32: commands succeeded
py33: commands succeeded
To execute test on all python versions, you need to have python2.6
, python2.7
, python3.2
and python3.3
in your PATH
.
Generate documentation¶
$ pip install Sphinx
$ cd docs
$ make html
../bin/sphinx-build -b html -d _build/doctrees . _build/html
Running Sphinx v1.1.3
loading pickled environment... done
...
build succeeded, 3 warnings.
Build finished. The HTML pages are in _build/html.
Tips¶
You can use WSGI Debug application object to test webtest.
News¶
3.0.0 (2021-08-19)¶
- Dropped support for Python 2.7 and 3.5.
- Added support for Python 3.9.
- Clean up dependencies and requirements.
- Switch from Travis to GitHub Actions for building and testing.
- Prevent PytestCollectionWarning for TestApp
2.0.35 (2020-04-27)¶
- python3.8 compat
- Remove use of deprecated splittype and splithost
2.0.34 (2020-01-29)¶
- Fix the test
length == 0
incheck_content_type
. - Treat
<input type="search">
like<input type="text">
. - Handle query parameters for the
head
method.
2.0.33 (2019-02-09)¶
- Fixed #210. Allow to reset select multiple with field.value = []
- Support for PYTHONOPTIMIZE=2, fix tests on PYTHONOPTIMIZE=1, 2
- Fixed #196. Fix deprecation warnings for
collections
to usecollections.abc
forIterable
on Python 3.
2.0.32 (2018-10-05)¶
- remove invalid email from setup.py
2.0.31 (2018-10-05)¶
- py33 is no longer supported. It may works but has been removed from tox config
- Fixed #205: Use empty string as default value for submit and button
- tests use pytest
- docs use the standard Pylons template on RTD
2.0.30 (2018-06-23)¶
- Add
Email
class for input fields with type "email". - Documentation bearer token and JWT authorization
2.0.29 (2017-10-21)¶
- Bugfix: Preserve submit order for radio inputs.
- Fixed #186: avoid UnicodeDecodeError in linter with py2 when a header contain non ascii chars
2.0.28 (2017-08-01)¶
- Fixed #185: Fix strict cookie policy
- Fixed #146: Improve fields value checking when enctype is multipart
- Fixed #119: Assertion error should be raised when you have non-string response header
- Bugfix: Allow to set an int value to form fields when enctype is multipart
- Added py36 to tox.ini / .travis.yaml
2.0.27 (2017-03-15)¶
- Bugfix: Allow to use set_cookie when HTTP_HOST is set
- Fix #177: resp.json now always decode body as utf8
2.0.26 (2017-03-05)¶
- Added JWT auth support
- Always show response body when response status is invalid
2.0.25 (2017-02-05)¶
- Fix #173: Do not omit file uploads without a file from post. [Michael Howitz]
2.0.24 (2016-12-16)¶
- Drop python 2.6 support. Newer versions may still work if you use waitress < 1.0
- Remove bs4 warnings
- Docs improvments
- Tets are WebOb 1.7.x compatible
2.0.23 (2016-07-21)¶
- Create universal wheels.
2.0.22 (2016-07-21)¶
- Fix #160: Do not guess encoding if response's charset is set.
2.0.21 (2016-04-12)¶
- PR #154 Allow Bearer auth
- PR #147,#148 Take care of REFERER when using form.submit(), .click() and .clickbutton()
- PR #145 Allow to override content-type when using json methods
2.0.20 (2015-11-03)¶
- nothing new release. just try to make wheel available on pypi
2.0.19 (2015-11-01)¶
- fixed #131 prevent passing HTML parameters that conflict with Field kwargs
- fixed #135 Document that WSGIProxy2 is required for "using webtest with a real url"
- fixed #136 reset values of select multiple
- drop py32 support (still work but test dependencies fail)
2.0.18 (2015-02-05)¶
- Avoid deprecation warning with py3.4
2.0.17 (2014-12-20)¶
- Properly check for default cookiejar arguments [Julian Berman]
- Avoid raising encoding errors from debugapp (needed to use with WSGIProxy2) [Laurence Rowe]
2.0.16 (2014-09-19)¶
- Fixed #110. Forced values for Radio inputs are no longer ignored by value property on get. [bayprogrammer]
- Added method TestApp.set_parser_features to change the parser_features used by BeautifulSoup. [tomasmoreyra]
- Added app.set_cookie [luhn]
2.0.15 (2014-04-17)¶
- Fixed #73. Python < 2.6.5 does not support unicode as keyword arguments names. [Stepan Kolesnik]
- Fixed #84 Application cookies for localhost are no longer ignored [gawel]
- Fixed #89 remove WSGIWarning: You are not supposed to send a body in a DELETE request because we now have a good reason for that. See https://stackoverflow.com/questions/299628/is-an-entity-body-allowed-for-an-http-delete-request/299701#299701 [gawel]
- Fixed #92 You can now override TestApp.JSONEncoder to use a custom encoder [gawel]
- Fixed #93 Support basic authentication [gawel]
- Fixed #103 Broken "Edit me on GitHub" links in documentation [gawel]
- Fixed #106 Make wrapping the app in the lint middleware optional [dmlayton]
- Fixed #107 Explicit error message when WSGIProxy2 is not installer [gawel]
- Fixed #108 cgi.parse_qsl is pending deprecation [gawel]
2.0.14 (2014-01-23)¶
- Allow .select() on <select>s and <select multiple>s. [Markus Bertheau]
2.0.13 (2014-01-23)¶
- Allow selecting <select> options by text [Markus Bertheau]
2.0.12 (2014-01-17)¶
- Ignore the value attribute of file inputs [Markus Bertheau]
- Allow selecting the form submit button by its value [Markus Bertheau]
2.0.11 (2013-12-29)¶
- Depend on unittest2 only for Python versions lower than 2.7 [iElectric]
- Add an optional parameter to TestApp, allowing the user to specify the parser used by BeautifulSoup [lyndsysimon]
2.0.10 (2013-11-14)¶
- Make StopableWSGIServer thread daemonic [lrowe]
2.0.9 (2013-09-18)¶
- Make sure Upload.content_type is not ignored https://github.com/Pylons/webtest/pull/88 [Marius Gedminas]
2.0.8 (2013-09-17)¶
- Allow checking/unchecking a set of same-named checkboxes by assigning a list of values. [carljm]
- fix "AttributeError: 'InputWrapper' object has no attribute 'seek'" [iElectric]
- Added xhr=True parameter to TestApp.get, TestApp.post, TestApp.head, TestApp.delete [kharandziuk]
- Remove old (and broken?) casperjs/selenium backward compat imports. Fix https://github.com/gawel/webtest-selenium/issues/9 [gawel]
- Allow optionally specifying the MIME type of an uploaded form file. Fixes #86 [Marius Gedminas]
2.0.7 (2013-08-07)¶
- Detect JSON if mimetype ends with +json, such as application/vnd.webtest+json [homm]
- Fixed #72. Use WSGIServer new api even if there waitress has backward compat. [gawel]
- Fixed #50. Corrected default value for the delete params argument. [noonat]
- Be sure to decode the content if it is gziped before returning it [Alexis Métaireau]
2.0.6 (2013-05-23)¶
- fixed #64. cookiejar api has changed in python3.3 [gawel]
- allow to use a fixed StopableWSGIServer [gawel]
- Do not alter the BeautifulSoup object when parsing forms. [Georges Dubus]
- Remove first newline while parse textarea block, how modern browsers does. [Victor Safronovich] pull #69
2.0.5 (2013-04-12)¶
- Ignore select.error (not socket.error) following StopableWSGIServer.shutdown. [Laurence Rowe]
- Handle the short form of <select multiple> [Marius Gedminas]
2.0.4 (2013-03-28)¶
- Correctly handle <option> elements with no value attribute [Marius Gedminas]
- Ignore socket.error following StopableWSGIServer.shutdown. [Laurence Rowe]
- <button> without type='submit' attribute is treated as Submit control [Andrey Lebedev].
- Support for redirects having relative "Location" header [Andrey Lebedev]
2.0.3 (2013-03-19)¶
- Treat strings in the WSGI environment as native strings, compliant with PEP-3333. [wosc]
2.0.2 (2013-03-15)¶
- Allow TestResponse.click() to match HTML content again. [ender672]
- Support secure cookies [Andrey Lebedev]
2.0.1 (2013-03-05)¶
- Added Pasword field [diarmuidbourke]
- re-allow to use unknow field type. Like
type="email"
. [gawel] - Don't let BeautifulSoup use lxml. Fix GH-51 [kmike]
- added
webtest.response.TestResponse.maybe_follow()
method [kmike]
2.0 (2013-02-25)¶
- drop zc.buildout usage for development, now using only virtualenv [Domen Kožar]
- Backward incompatibility : Removed the
anchor
argument ofwebtest.response.TestResponse.click()
and thebutton
argument ofwebtest.response.TestResponse.clickbutton()
. It is for the greater good. [madjar] - Rewrote API documentation [Domen Kožar]
- Added wsgiproxy support to do HTTP request to an URL [gawel]
- Use BeautifulSoup4 to parse forms [gawel]
- Added webtest.app.TestApp.patch_json [gawel]
- Implement webtest.app.TestApp.cookiejar support and kindof keep webtest.app.TestApp.cookies functionality. webtest.app.TestApp.cookies should be treated as read-only. [Domen Kožar]
- Split Selenium integration into separate package webtest-selenium [gawel]
- Split casperjs integration into separate package webtest-casperjs [gawel]
- Test coverage improvements [harobed, cdevienne, arthru, Domen Kožar, gawel]
- Fully implement decoding of HTML entities
- Fix tox configuration
1.4.2¶
- fix tests error due to CLRF in a tarball
1.4.1¶
- add travis-ci
- migrate repository to https://github.com/Pylons/webtest
- Fix a typo in apps.py: selectedIndicies
- Preserve field order during parsing (support for deform and such)
- allow equals sign in the cookie by spliting name-value-string pairs on the first '=' sign as per https://tools.ietf.org/html/rfc6265#section-5.2
- fix an error when you use AssertionError(response) with unicode chars in response
1.4.0¶
- added webtest.ext - allow to use casperjs
1.3.6¶
1.3.4¶
1.3.3¶
- added
post_json
,put_json
,delete_json
- fix #25 params dictionary of webtest.AppTest.post() does not support unicode values
1.3¶
- Moved TestApp to app.py
- Added selenium testing framework. See
sel
module.
1.2.4¶
- Accept lists for
app.post(url, params=[...])
- Allow to use url that starts with the SCRIPT_NAME found in extra_environ
- Fix #16 Default content-type is now correctly set to application/octet-stream
- Fix #14 and #18 Allow to use .delete(params={})
- Fix #12
1.2.3¶
- Fix #10, now TestApp.extra_environ doesn't take precedence over a WSGI environment passed in through the request.
- Removed stray print
1.2.2¶
- Revert change to cookies that would add
"
around cookie values. - Added property
webtest.Response.pyquery()
which returns a PyQuery object. - Set base_url on
resp.lxml
- Include tests and docs in tarball.
- Fix sending in webob.Request (or webtest.TestRequest) objects.
- Fix handling forms with file uploads, when no file is selected.
- Added
extra_environ
argument towebtest.TestResponse.click()
. - Fixed/added wildcard statuses, like
status="4*"
- Fix file upload fields in forms: allow upload field to be empty.
- Added support for single-quoted html attributes.
- TestResponse now has unicode support. It is turned on by default for all responses with charset information. This is backward incompatible change if you rely (e.g. in doctests) on parsed form fields or responses returned by json and lxml methods being encoded strings when charset header is in response. In order to switch to old behaviour pass use_unicode=False flag to TestApp constructor.
1.2.1¶
- Added method
TestApp.request()
, which can be used for sending requests with different methods (e.g.,MKCOL
). This method sends all its keyword arguments towebtest.TestRequest.blank()
and then executes the request. The parameters are somewhat different than other methods (likewebtest.TestApp.get()
), as they match WebOb's attribute names exactly (the other methods were written before WebOb existed). - Removed the copying of stdout to stderr during requests.
- Fix file upload fields in forms (#340) -- you could
upload files with
webtest.TestApp.post()
, but if you useresp.form
file upload fields would not work (from rcs-comp.com and Matthew Desmarais).
1.2¶
- Fix form inputs; text inputs always default to the empty string, and unselected radio inputs default to nothing at all. From Daniele Paolella.
- Fix following links with fragments (these fragments should not be sent to the WSGI application). From desmaj.
- Added
force_value
to select fields, likeres.form['select'].force_value("new_value")
. This makes it possible to simulate forms that are dynamically updated. From Matthew Desmarais. - Fixed
webtest.Response.mustcontain()
when you pass in ano=[strings]
argument.
1.1¶
- Changed the
__str__
of responses to make them more doctest friendly:- All headers are displayed capitalized, like Content-Type
- Headers are sorted alphabetically
- Changed
__repr__
to only show the body length if the complete body is not shown (for short bodies the complete body is in the repr) - Note: these are backward incompatible changes if you are using doctest (you'll have to update your doctests with the new format).
- Fixed exception in the
.delete
method. - Added a
content_type
argument toapp.post
andapp.put
, which sets theContent-Type
of the request. This is more convenient when testing REST APIs. - Skip links in
<script>...</script>
tags (since that's not real markup).
1.0.2¶
- Don't submit unnamed form fields.
- Checkboxes with no explicit
value
sendon
(previously they sentchecked
, which isn't what browsers send). - Support for
<select multiple>
fields (from Matthew Desmarais)
1.0.1 ---
- Fix the
TestApp
validator's InputWrapper lacking support for readline with an argument as needed by the cgi module.
1.0¶
- Keep URLs in-tact in cases such as
app.get('http://www.python.org')
(so HTTP_HOST=www.python.org, etc). - Fix
lxml.html
import, so lxml 2.0 users can get HTML lxml objects fromresp.lxml
- Treat
<input type="image">
like a submit button. - Use
BaseCookie
instead ofSimpleCookie
for storing cookies (avoids quoting cookie values). - Accept any
params
argument that has anitems
method (like MultiDict)
0.9¶
Initial release
License¶
Copyright (c) 2010 Ian Bicking and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.