[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
 ---------
 
 [![Build Status](https://travis-ci.org/hickford/MechanicalSoup.svg?branch=master)](https://travis-ci.org/hickford/MechanicalSoup)
+[![Coverage Status](https://codecov.io/gh/hickford/MechanicalSoup/branch/master/graph/badge.svg)](https://codecov.io/gh/hickford/MechanicalSoup)
+[![Requirements Status](https://requires.io/github/hickford/MechanicalSoup/requirements.svg?branch=master)](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