[Python-modules-commits] [python-mechanicalsoup] 01/08: New upstream version 0.8.0
Ghislain Vaillant
ghisvail-guest at moszumanska.debian.org
Tue Oct 10 14:52:19 UTC 2017
This is an automated email from the git hooks/post-receive script.
ghisvail-guest pushed a commit to branch master
in repository python-mechanicalsoup.
commit a8b374c3e91936e80ff46fb78accac3419be56d7
Author: Ghislain Antony Vaillant <ghisvail at gmail.com>
Date: Tue Oct 10 11:20:58 2017 +0100
New upstream version 0.8.0
---
MechanicalSoup.egg-info/PKG-INFO | 4 +-
MechanicalSoup.egg-info/SOURCES.txt | 1 +
PKG-INFO | 4 +-
README.md | 32 +++++++--
example.py | 5 +-
mechanicalsoup/__init__.py | 4 +-
mechanicalsoup/__version__.py | 5 ++
mechanicalsoup/browser.py | 47 ++++++++++--
mechanicalsoup/form.py | 20 ++++--
mechanicalsoup/stateful_browser.py | 58 ++++++++-------
setup.cfg | 8 ++-
setup.py | 26 ++++---
tests/test_browser.py | 51 +++++++++++--
tests/test_form.py | 139 ++++++++++++++++++++++++++++++------
tests/test_stateful_browser.py | 121 ++++++++++++++++++++++++++++++-
15 files changed, 445 insertions(+), 80 deletions(-)
diff --git a/MechanicalSoup.egg-info/PKG-INFO b/MechanicalSoup.egg-info/PKG-INFO
index c59b4c3..85c16c7 100644
--- a/MechanicalSoup.egg-info/PKG-INFO
+++ b/MechanicalSoup.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: MechanicalSoup
-Version: 0.7.0
+Version: 0.8.0
Summary: A Python library for automating interaction with websites
Home-page: https://github.com/hickford/MechanicalSoup
Author: UNKNOWN
@@ -10,10 +10,8 @@ Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
diff --git a/MechanicalSoup.egg-info/SOURCES.txt b/MechanicalSoup.egg-info/SOURCES.txt
index 22cdd8b..a5b2274 100644
--- a/MechanicalSoup.egg-info/SOURCES.txt
+++ b/MechanicalSoup.egg-info/SOURCES.txt
@@ -11,6 +11,7 @@ MechanicalSoup.egg-info/dependency_links.txt
MechanicalSoup.egg-info/requires.txt
MechanicalSoup.egg-info/top_level.txt
mechanicalsoup/__init__.py
+mechanicalsoup/__version__.py
mechanicalsoup/browser.py
mechanicalsoup/form.py
mechanicalsoup/stateful_browser.py
diff --git a/PKG-INFO b/PKG-INFO
index c59b4c3..85c16c7 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: MechanicalSoup
-Version: 0.7.0
+Version: 0.8.0
Summary: A Python library for automating interaction with websites
Home-page: https://github.com/hickford/MechanicalSoup
Author: UNKNOWN
@@ -10,10 +10,8 @@ Description: UNKNOWN
Platform: UNKNOWN
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
diff --git a/README.md b/README.md
index d898d66..7555e4f 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ From [PyPI](https://pypi.python.org/pypi/MechanicalSoup/)
pip install MechanicalSoup
-Python versions 2.6-2.7, 3.3-3.6, PyPy and PyPy3 are supported (and tested against).
+Python versions 2.7, 3.4-3.6, PyPy and PyPy3 are supported (and tested against).
Example
------
@@ -35,7 +35,11 @@ args = parser.parse_args()
args.password = getpass("Please enter your GitHub password: ")
-browser = mechanicalsoup.StatefulBrowser()
+browser = mechanicalsoup.StatefulBrowser(
+ soup_config={'features': 'lxml'},
+ raise_on_404=True,
+ user_agent='MyBot/0.1: mysite.example.com/bot_info',
+)
# Uncomment for a more verbose output:
# browser.set_verbose(2)
@@ -87,10 +91,30 @@ Development
---------
[](https://travis-ci.org/hickford/MechanicalSoup)
+[](https://codecov.io/gh/hickford/MechanicalSoup)
+[](https://requires.io/github/hickford/MechanicalSoup/requirements/?branch=master)
-### Tests
+You can develop against multiple versions of Python using [virtualenv](https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments):
+
+ python3 -m venv .virtual-py3 && source .virtual-py3/bin/activate
+ pip install pytest pytest-cov flake8 requests_mock
+and
+
+ virtualenv -p python2 --no-site-packages .virtual-py2 && source .virtual-py2/bin/activate
+ pip install pytest pytest-cov flake8 requests_mock
+
+After making changes, check syntax:
+
+ flake8 $(git ls-files mechanicalsoup/'*.py') example.py
+
+Then run py.test in all virtualenvs:
+
+ source .virtual-py3/bin/activate
+ python setup.py install && pytest
+
+ source .virtual-py2/bin/activate
+ python setup.py install && pytest
- py.test
### Roadmap
diff --git a/example.py b/example.py
index a890d31..76d49ee 100644
--- a/example.py
+++ b/example.py
@@ -11,7 +11,10 @@ args = parser.parse_args()
args.password = getpass("Please enter your GitHub password: ")
-browser = mechanicalsoup.StatefulBrowser(soup_config={'features': 'lxml'})
+browser = mechanicalsoup.StatefulBrowser(
+ soup_config={'features': 'lxml'},
+ raise_on_404=True
+)
# Uncomment for a more verbose output:
# browser.set_verbose(2)
diff --git a/mechanicalsoup/__init__.py b/mechanicalsoup/__init__.py
index 9e36dde..563b8b9 100644
--- a/mechanicalsoup/__init__.py
+++ b/mechanicalsoup/__init__.py
@@ -2,5 +2,7 @@ from .utils import LinkNotFoundError
from .browser import Browser
from .form import Form
from .stateful_browser import StatefulBrowser
+from .__version__ import __version__
-__all__ = ['LinkNotFoundError', 'Browser', 'StatefulBrowser', 'Form']
+__all__ = ['LinkNotFoundError', 'Browser', 'StatefulBrowser', 'Form',
+ '__version__']
diff --git a/mechanicalsoup/__version__.py b/mechanicalsoup/__version__.py
new file mode 100644
index 0000000..d68ea5b
--- /dev/null
+++ b/mechanicalsoup/__version__.py
@@ -0,0 +1,5 @@
+__title__ = 'MechanicalSoup'
+__description__ = 'A Python library for automating interaction with websites'
+__url__ = 'https://github.com/hickford/MechanicalSoup'
+__version__ = '0.8.0'
+__license__ = 'MIT'
diff --git a/mechanicalsoup/browser.py b/mechanicalsoup/browser.py
index 99301c3..afc6b86 100644
--- a/mechanicalsoup/browser.py
+++ b/mechanicalsoup/browser.py
@@ -6,6 +6,8 @@ from six import string_types
from .form import Form
import webbrowser
import tempfile
+from .utils import LinkNotFoundError
+from .__version__ import __version__, __title__
# see
# https://www.crummy.com/software/BeautifulSoup/bs4/doc/#specifying-the-parser-to-use
@@ -15,9 +17,13 @@ warnings.filterwarnings(
class Browser(object):
- def __init__(self, session=None, soup_config=None, requests_adapters=None):
+ def __init__(self, session=None, soup_config=None, requests_adapters=None,
+ raise_on_404=False, user_agent=None):
+ self.__raise_on_404 = raise_on_404
self.session = session or requests.Session()
+ self.set_user_agent(user_agent)
+
if requests_adapters is not None:
for adaptee, adapter in requests_adapters.items():
self.session.mount(adaptee, adapter)
@@ -30,6 +36,30 @@ class Browser(object):
response.soup = bs4.BeautifulSoup(
response.content, **soup_config)
+ def set_cookiejar(self, cookiejar):
+ """Replaces the current cookiejar in the requests session. Since the
+ session handles cookies automatically without calling this function,
+ only use this when default cookie handling is insufficient."""
+ self.session.cookies = cookiejar
+
+ def get_cookiejar(self):
+ """Gets the cookiejar from the requests session."""
+ return self.session.cookies
+
+ def set_user_agent(self, user_agent):
+ # set a default user_agent if not specified
+ if user_agent is None:
+ try:
+ requests_ua = requests.utils.default_user_agent()
+ except AttributeError:
+ user_agent = '%s/%s' % (__title__, __version__)
+ else:
+ user_agent = '%s (%s/%s)' % (
+ requests_ua, __title__, __version__)
+
+ # the requests module uses a case-insensitive dict for session headers
+ self.session.headers['User-agent'] = user_agent
+
def request(self, *args, **kwargs):
response = self.session.request(*args, **kwargs)
Browser.add_soup(response, self.soup_config)
@@ -37,6 +67,8 @@ class Browser(object):
def get(self, *args, **kwargs):
response = self.session.get(*args, **kwargs)
+ if self.__raise_on_404 and response.status_code == 404:
+ raise LinkNotFoundError()
Browser.add_soup(response, self.soup_config)
return response
@@ -56,10 +88,8 @@ class Browser(object):
data = kwargs.pop("data", dict())
files = kwargs.pop("files", dict())
- for input in form.select("input"):
+ for input in form.select("input[name], button[name]"):
name = input.get("name")
- if not name:
- continue
if input.get("type") in ("radio", "checkbox"):
if "checked" not in input.attrs:
@@ -110,6 +140,7 @@ class Browser(object):
kwargs["params"] = data
else:
kwargs["data"] = data
+
return requests.Request(method, url, files=files, **kwargs)
def _prepare_request(self, form, url=None, **kwargs):
@@ -129,3 +160,11 @@ class Browser(object):
with tempfile.NamedTemporaryFile(delete=False) as file:
file.write(soup.encode())
webbrowser.open('file://' + file.name)
+
+ def close(self):
+ """Close the current session"""
+ self.session.cookies.clear()
+ self.session.close()
+
+ def __del__(self):
+ self.close()
diff --git a/mechanicalsoup/form.py b/mechanicalsoup/form.py
index e9f0293..57c9576 100644
--- a/mechanicalsoup/form.py
+++ b/mechanicalsoup/form.py
@@ -109,6 +109,10 @@ class Form(object):
return control
def choose_submit(self, el):
+ '''Selects the submit input (or button) element specified by 'el',
+ where 'el' can be either a bs4.element.Tag or just its name attribute.
+ If the element is not found or if multiple elements match, raise a
+ LinkNotFoundError exception.'''
# In a normal web browser, when a input[type=submit] is clicked,
# all other submits aren't sent. You can use simulate this as
# following:
@@ -122,13 +126,19 @@ class Form(object):
# return browser.submit(form, url)
found = False
- for inp in self.form.select("input"):
- if inp.get('type') != 'submit':
- continue
+ inps = self.form.select('input[type="submit"], button[type="submit"]')
+ for inp in inps:
if inp == el or inp['name'] == el:
+ if found:
+ raise LinkNotFoundError(
+ "Multiple submit elements match: {0}".format(el)
+ )
+ found = True
continue
del inp['name']
- found = True
- return found
+ if not found:
+ raise LinkNotFoundError(
+ "Specified submit element not found: {0}".format(el)
+ )
diff --git a/mechanicalsoup/stateful_browser.py b/mechanicalsoup/stateful_browser.py
index 5d05c67..9f44795 100644
--- a/mechanicalsoup/stateful_browser.py
+++ b/mechanicalsoup/stateful_browser.py
@@ -6,12 +6,14 @@ from .utils import LinkNotFoundError
from .form import Form
import sys
import re
+import bs4
class StatefulBrowser(Browser):
- def __init__(self, session=None, soup_config=None, requests_adapters=None):
+ def __init__(self, session=None, soup_config=None, requests_adapters=None,
+ *args, **kwargs):
super(StatefulBrowser, self).__init__(
- session, soup_config, requests_adapters)
+ session, soup_config, requests_adapters, *args, **kwargs)
self.__debug = False
self.__verbose = 0
self.__current_page = None
@@ -80,9 +82,19 @@ class StatefulBrowser(Browser):
self.__current_form = None
return resp
+ def open_fake_page(self, page_text, url=None, soup_config=None):
+ """Behave as if opening a page whose text is page_text, but do not
+ perform any network access. If url is set, pretend the page's URL
+ is url. Useful mainly for testing."""
+ soup_config = soup_config or dict()
+ self.__current_page = bs4.BeautifulSoup(
+ page_text, **soup_config)
+ self.__current_url = url
+ self.__current_form = None
+
def open_relative(self, url, *args, **kwargs):
"""Like open, but URL can be relative to the currently visited page."""
- return self.open(self.absolute_url(url))
+ return self.open(self.absolute_url(url), *args, **kwargs)
def select_form(self, *args, **kwargs):
"""Select a form in the current page. Arguments are the same
@@ -98,19 +110,13 @@ class StatefulBrowser(Browser):
return self.__current_form
def submit_selected(self, btnName=None, *args, **kwargs):
- """Submit the form selected with select_form()."""
+ """Submit the form selected with select_form(). If there are multiple
+ submit input/button elements, use 'btnName' to choose between them."""
if btnName is not None:
- if 'data' not in kwargs:
- kwargs['data'] = dict()
- kwargs['data'][btnName] = ''
+ self.get_current_form().choose_submit(btnName)
- form = self.get_current_form()
- if "action" in form.form:
- url = self.__current_url
- else:
- url = self.absolute_url(form.form["action"])
resp = self.submit(self.__current_form,
- url=url,
+ url=self.__current_url,
*args, **kwargs)
self.__current_url = resp.url
if hasattr(resp, "soup"):
@@ -148,21 +154,25 @@ class StatefulBrowser(Browser):
else:
return links[0]
- def follow_link(self, url_regex=None, *args, **kwargs):
- """Find a link whose href property matches url_regex, and follow it.
+ def follow_link(self, link=None, *args, **kwargs):
+ """Follow a previously found link
+
+ if the `link` argument doesn't have a 'href' attribute, treat
+ it as a url_regex and look it up with `find_link`
If the link is not found, Raise LinkNotFoundError.
Before raising LinkNotFoundError, if debug is activated, list
available links in the page and launch a browser."""
- try:
- link = self.find_link(url_regex, *args, **kwargs)
- return self.open(self.absolute_url(link['href']))
- except LinkNotFoundError:
- if self.get_debug():
- print('follow_link failed for', url_regex)
- self.list_links()
- self.launch_browser()
- raise
+ if not hasattr(link, 'attrs') or 'href' not in link.attrs:
+ try:
+ link = self.find_link(link, *args, **kwargs)
+ except LinkNotFoundError:
+ if self.get_debug():
+ print('follow_link failed for', link)
+ self.list_links()
+ self.launch_browser()
+ raise
+ return self.open(self.absolute_url(link['href']))
def launch_browser(self):
"""Launch a browser on the page, for debugging purpose."""
diff --git a/setup.cfg b/setup.cfg
index 6f08d0e..beda8f7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,8 +1,14 @@
+[aliases]
+test = pytest
+
[bdist_wheel]
universal = 1
+[tool:pytest]
+addopts = --cov --cov-config .coveragerc
+
[egg_info]
tag_build =
-tag_date = 0
tag_svn_revision = 0
+tag_date = 0
diff --git a/setup.py b/setup.py
index 7cf1e5c..288d4b3 100644
--- a/setup.py
+++ b/setup.py
@@ -4,17 +4,21 @@ from os import path
here = path.abspath(path.dirname(__file__))
+about = {}
+with open(path.join(here, 'mechanicalsoup', '__version__.py'), 'r', 'utf-8') as f:
+ exec(f.read(), about)
+
setup(
- name='MechanicalSoup',
+ name=about['__title__'],
# useful: python setup.py sdist bdist_wheel upload
- version='0.7.0',
+ version=about['__version__'],
- description='A Python library for automating interaction with websites',
+ description=about['__description__'],
- url='https://github.com/hickford/MechanicalSoup',
+ url=about['__url__'],
- license='MIT',
+ license=about['__license__'],
classifiers=[
'License :: OSI Approved :: MIT License',
@@ -22,10 +26,8 @@ setup(
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
@@ -41,5 +43,13 @@ setup(
'requests >= 2.0',
'beautifulsoup4',
'six >= 1.4'
- ],
+ ],
+ setup_requires=[
+ 'pytest-runner',
+ ],
+ tests_require=[
+ 'pytest',
+ 'pytest-cov',
+ 'requests_mock'
+ ]
)
diff --git a/tests/test_browser.py b/tests/test_browser.py
index 67a6d75..282ae2e 100644
--- a/tests/test_browser.py
+++ b/tests/test_browser.py
@@ -1,7 +1,9 @@
import mechanicalsoup
+import sys
from bs4 import BeautifulSoup
import tempfile
-
+from requests.cookies import RequestsCookieJar
+import pytest
def test_submit_online():
"""Complete and submit the pizza form at http://httpbin.org/forms/post """
@@ -29,6 +31,9 @@ def test_submit_online():
assert data["topping"] == ["cheese", "onion"]
assert data["comments"] == "freezer"
+ assert json["headers"]["User-Agent"].startswith('python-requests/')
+ assert 'MechanicalSoup' in json["headers"]["User-Agent"]
+
form_html = """
<form method="post" action="http://httpbin.org/post">
<input name="customer" value="Philip J. Fry"/>
@@ -94,7 +99,45 @@ def test_prepare_request_file():
request = browser._prepare_request(form)
assert "multipart/form-data" in request.headers["Content-Type"]
+def test_no_404():
+ browser = mechanicalsoup.Browser()
+ resp = browser.get("http://httpbin.org/nosuchpage")
+ assert resp.status_code == 404
+
+def test_404():
+ browser = mechanicalsoup.Browser(raise_on_404=True)
+ with pytest.raises(mechanicalsoup.LinkNotFoundError) as context:
+ resp = browser.get("http://httpbin.org/nosuchpage")
+ resp = browser.get("http://httpbin.org/")
+ assert resp.status_code == 200
+
+def test_set_cookiejar():
+ """Set cookies locally and test that they are received remotely."""
+ # construct a phony cookiejar and attach it to the session
+ jar = RequestsCookieJar()
+ jar.set('field', 'value')
+ assert jar.get('field') == 'value'
+
+ browser = mechanicalsoup.Browser()
+ browser.set_cookiejar(jar)
+ resp = browser.get("http://httpbin.org/cookies")
+ assert resp.json() == {'cookies': {'field': 'value'}}
+
+def test_get_cookiejar():
+ """Test that cookies set by the remote host update our session."""
+ browser = mechanicalsoup.Browser()
+ resp = browser.get("http://httpbin.org/cookies/set?k1=v1&k2=v2")
+ assert resp.json() == {'cookies': {'k1': 'v1', 'k2': 'v2'}}
+
+ jar = browser.get_cookiejar()
+ assert jar.get('k1') == 'v1'
+ assert jar.get('k2') == 'v2'
+
+def test_post():
+ browser = mechanicalsoup.Browser()
+ data = {'color': 'blue', 'colorblind': 'True'}
+ resp = browser.post("http://httpbin.org/post", data)
+ assert(resp.status_code == 200 and resp.json()['form'] == data)
+
if __name__ == '__main__':
- test_submit_online()
- test_build_request()
- test_prepare_request_file()
+ pytest.main(sys.argv)
diff --git a/tests/test_form.py b/tests/test_form.py
index a6a5757..8ddfe99 100644
--- a/tests/test_form.py
+++ b/tests/test_form.py
@@ -1,5 +1,7 @@
import mechanicalsoup
+import sys
import requests_mock
+import pytest
try:
from urllib.parse import parse_qsl
except ImportError:
@@ -78,39 +80,136 @@ choose_submit_form = '''
<div class="buttons">
<input type="submit" name="preview" value="Preview Page" />
<input type="submit" name="diff" value="Review Changes" />
- <input type="submit" id="save" name="save" value="Submit changes" />
- <input type="submit" name="cancel" value="Cancel" />
+ <input type="submit" id="save" name="save" value="Submit changes" />
+ <button type="submit" name="cancel" value="Cancel" />
</div>
</form>
</body>
</html>
'''
-def test_choose_submit():
+def setup_mock_browser(expected_post=None):
url = 'mock://multi-button-form.com'
mock = requests_mock.Adapter()
mock.register_uri('GET', url, headers={'Content-Type': 'text/html'}, text=choose_submit_form)
- def text_callback(request, context):
- expect = [('comment', 'Created new page'),
- ('save', 'Submit changes'),
- ('text', '= Heading =\n\nNew page here!\n')]
- query = parse_qsl(request.text)
- assert(set(query) == set(expect))
- return 'Success!'
- mock.register_uri('POST', url + '/post', text=text_callback)
-
- browser = mechanicalsoup.StatefulBrowser(requests_adapters={'mock': mock})
+ if expected_post:
+ def text_callback(request, context):
+ query = parse_qsl(request.text)
+ assert(set(query) == set(expected_post))
+ return 'Success!'
+ mock.register_uri('POST', url + '/post', text=text_callback)
+ return mechanicalsoup.StatefulBrowser(requests_adapters={'mock': mock}), url
+
+ at pytest.mark.parametrize("expected_post", [
+ pytest.param(
+ [
+ ('comment', 'Testing preview page'),
+ ('preview', 'Preview Page'),
+ ('text', 'Setting some text!')
+ ], id='preview'),
+ pytest.param(
+ [
+ ('comment', 'Created new page'),
+ ('save', 'Submit changes'),
+ ('text', '= Heading =\n\nNew page here!\n')
+ ], id='save'),
+ pytest.param(
+ [
+ ('comment', 'Testing choosing cancel button'),
+ ('cancel', 'Cancel'),
+ ('text', '= Heading =\n\nNew page here!\n')
+ ], id='cancel'),
+])
+def test_choose_submit(expected_post):
+ browser, url = setup_mock_browser(expected_post=expected_post)
browser.open(url)
form = browser.select_form('#choose-submit-form')
- browser['text'] = '= Heading =\n\nNew page here!\n'
- browser['comment'] = 'Created new page'
- found = form.choose_submit('save')
- assert(found)
+ browser['text'] = expected_post[2][1]
+ browser['comment'] = expected_post[0][1]
+ form.choose_submit(expected_post[1][0])
res = browser.submit_selected()
assert(res.status_code == 200 and res.text == 'Success!')
+choose_submit_fail_form = '''
+<html>
+ <form id="choose-submit-form">
+ <input type="submit" name="test_submit" value="Test Submit" />
+ </form>
+</html>
+'''
+
+ at pytest.mark.parametrize("select_name", [
+ pytest.param({'name': 'does_not_exist', 'fails': True}, id='not found'),
+ pytest.param({'name': 'test_submit', 'fails': False}, id='found'),
+])
+def test_choose_submit_fail(select_name):
+ browser = mechanicalsoup.StatefulBrowser()
+ browser.open_fake_page(choose_submit_fail_form)
+ form = browser.select_form('#choose-submit-form')
+ if select_name['fails']:
+ with pytest.raises(mechanicalsoup.utils.LinkNotFoundError):
+ form.choose_submit(select_name['name'])
+ else:
+ form.choose_submit(select_name['name'])
+
+
+choose_submit_multiple_match_form = '''
+<html>
+ <form id="choose-submit-form">
+ <input type="submit" name="test_submit" value="First Submit" />
+ <input type="submit" name="test_submit" value="Second Submit" />
+ </form>
+</html>
+'''
+
+def test_choose_submit_multiple_match():
+ browser = mechanicalsoup.StatefulBrowser()
+ browser.open_fake_page(choose_submit_multiple_match_form)
+ form = browser.select_form('#choose-submit-form')
+ with pytest.raises(mechanicalsoup.utils.LinkNotFoundError):
+ form.choose_submit('test_submit')
+
+
+submit_form_noaction = '''
+<html>
+ <body>
+ <form id="choose-submit-form">
+ <input type="text" name="text1" value="someValue1" />
+ <input type="text" name="text2" value="someValue2" />
+ <input type="submit" name="save" />
+ </form>
+ </body>
+</html>
+'''
+
+def test_form_noaction():
+ browser, url = setup_mock_browser()
+ browser.open_fake_page(submit_form_noaction, url=url)
+ form = browser.select_form('#choose-submit-form')
+ browser['text1'] = 'newText1'
+ res = browser.submit_selected()
+ assert(res.status_code == 200 and browser.get_url() == url)
+
+submit_form_action = '''
+<html>
+ <body>
+ <form id="choose-submit-form" action="mock://multi-button-form.com">
+ <input type="text" name="text1" value="someValue1" />
+ <input type="text" name="text2" value="someValue2" />
+ <input type="submit" name="save" />
+ </form>
+ </body>
+</html>
+'''
+
+def test_form_action():
+ browser, url = setup_mock_browser()
+ browser.open_fake_page(submit_form_action, url="http://example.com/invalid/")
+ form = browser.select_form('#choose-submit-form')
+ browser['text1'] = 'newText1'
+ res = browser.submit_selected()
+ assert(res.status_code == 200 and browser.get_url() == url)
+
if __name__ == '__main__':
- test_submit_online()
- test_submit_set()
- test_choose_submit()
+ pytest.main(sys.argv)
diff --git a/tests/test_stateful_browser.py b/tests/test_stateful_browser.py
index 5bd828c..0ad53c3 100644
--- a/tests/test_stateful_browser.py
+++ b/tests/test_stateful_browser.py
@@ -1,10 +1,19 @@
import mechanicalsoup
-
+import sys
+import re
+from bs4 import BeautifulSoup
+from test_form import setup_mock_browser
+import pytest
def test_submit_online():
"""Complete and submit the pizza form at http://httpbin.org/forms/post """
browser = mechanicalsoup.StatefulBrowser()
+ browser.set_user_agent('testing https://github.com/hickford/MechanicalSoup')
browser.open("http://httpbin.org/")
+ for link in browser.links():
+ if link["href"] == "/":
+ browser.follow_link(link)
+ break
browser.follow_link("forms/post")
assert browser.get_url() == "http://httpbin.org/forms/post"
browser.select_form("form")
@@ -23,6 +32,114 @@ def test_submit_online():
assert data["comments"] == "Some comment here"
assert data["nosuchfield"] == "new value"
+ assert (json["headers"]["User-Agent"] ==
+ 'testing https://github.com/hickford/MechanicalSoup')
+ # Ensure we haven't blown away any regular headers
+ assert set(('Content-Length', 'Host', 'Content-Type', 'Connection', 'Accept',
+ 'User-Agent', 'Accept-Encoding')).issubset(json["headers"].keys())
+
+
+def test_no_404():
+ browser = mechanicalsoup.StatefulBrowser()
+ resp = browser.open("http://httpbin.org/nosuchpage")
+ assert resp.status_code == 404
+
+def test_404():
+ browser = mechanicalsoup.StatefulBrowser(raise_on_404=True)
+ with pytest.raises(mechanicalsoup.LinkNotFoundError) as context:
+ resp = browser.open("http://httpbin.org/nosuchpage")
+ resp = browser.open("http://httpbin.org/")
+ assert resp.status_code == 200
+
+def test_user_agent():
+ browser = mechanicalsoup.StatefulBrowser(user_agent='007')
+ resp = browser.open("http://httpbin.org/user-agent")
+ assert resp.json() == {'user-agent': '007'}
+
+def test_open_relative():
+ # Open an arbitrary httpbin page to set the current URL
+ browser = mechanicalsoup.StatefulBrowser()
+ browser.open("http://httpbin.org/html")
+
+ # Open a relative page and make sure remote host and browser agree on URL
+ resp = browser.open_relative("/get")
+ assert resp.json()['url'] == "http://httpbin.org/get"
+ assert browser.get_url() == "http://httpbin.org/get"
+
+ # Test passing additional kwargs to the session
+ resp = browser.open_relative("/basic-auth/me/123", auth=('me', '123'))
+ assert browser.get_url() == "http://httpbin.org/basic-auth/me/123"
+ assert resp.json() == {"authenticated": True, "user": "me"}
+
+def test_links():
+ browser = mechanicalsoup.StatefulBrowser()
+ html = '''<a class="bluelink" href="/blue" id="blue_link">A Blue Link</a>
+ <a class="redlink" href="/red" id="red_link">A Red Link</a>'''
+ expected = [BeautifulSoup(html).a]
+ browser.open_fake_page(html)
+
+ # Test StatefulBrowser.links url_regex argument
+ assert browser.links(url_regex="bl") == expected
+ assert browser.links(url_regex="bluish") == []
+
+ # Test StatefulBrowser.links link_text argument
+ assert browser.links(link_text="A Blue Link") == expected
+ assert browser.links(link_text="Blue") == []
+
+ # Test StatefulBrowser.links kwargs passed to BeautifulSoup.find_all
+ assert browser.links(string=re.compile('Blue')) == expected
+ assert browser.links(class_="bluelink") == expected
+ assert browser.links(id="blue_link") == expected
+ assert browser.links(id="blue") == []
+
+ # Test returning a non-singleton
+ two_links = browser.links(id=re.compile('_link'))
+ assert len(two_links) == 2
+ assert two_links == BeautifulSoup(html).find_all('a')
+
+ at pytest.mark.parametrize("expected_post", [
+ pytest.param(
+ [
+ ('comment', 'Selecting an input submit'),
+ ('diff', 'Review Changes'),
+ ('text', 'Setting some text!')
+ ], id='input'),
+ pytest.param(
+ [
+ ('comment', 'Selecting a button submit'),
+ ('cancel', 'Cancel'),
+ ('text', '= Heading =\n\nNew page here!\n')
+ ], id='button'),
+])
+def test_submit_btnName(expected_post):
+ '''Tests that the btnName argument chooses the submit button.'''
+ browser, url = setup_mock_browser(expected_post=expected_post)
+ browser.open(url)
+ form = browser.select_form('#choose-submit-form')
+ browser['text'] = expected_post[2][1]
+ browser['comment'] = expected_post[0][1]
+ res = browser.submit_selected(btnName = expected_post[1][0])
+ assert(res.status_code == 200 and res.text == 'Success!')
+
+def test_get_set_debug():
+ browser = mechanicalsoup.StatefulBrowser()
+ # Debug mode is off by default
+ assert(not browser.get_debug())
+ browser.set_debug(True)
+ assert(browser.get_debug())
+
+def test_list_links(capsys):
+ # capsys is a pytest fixture that allows us to inspect the std{err,out}
+ browser = mechanicalsoup.StatefulBrowser()
+ links = '''
+ <a href="/link1">Link #1</a>
+ <a href="/link2" id="link2"> Link #2</a>
+'''
+ browser.open_fake_page('<html>{0}</html>'.format(links))
+ browser.list_links()
+ out, err = capsys.readouterr()
+ expected = 'Links in the current page:{0}'.format(links)
+ assert out == expected
if __name__ == '__main__':
- test_submit_online()
+ pytest.main(sys.argv)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-mechanicalsoup.git
More information about the Python-modules-commits
mailing list