GithubHelp home page GithubHelp logo

pytest-base-url's Introduction

pytest-base-url

pytest-base-url is a simple plugin for pytest that provides an optional base URL via the command line or configuration file.

License PyPI Travis Issues Requirements

Requirements

You will need the following prerequisites in order to use pytest-base-url:

  • Python 3.8+ or PyPy3

Installation

To install pytest-base-url:

$ pip install pytest-base-url

Contributing

We welcome contributions.

To learn more, see Development

Specifying a Base URL

Rather than repeating or abstracting a base URL in your tests, pytest-base-url provides a base_url fixture that returns the specified base URL.

import urllib2

def test_example(base_url):
    assert 200 == urllib2.urlopen(base_url).getcode()

Using the Command Line

You can specify the base URL on the command line:

$ pytest --base-url http://www.example.com

Using a Configuration File

You can specify the base URL using a configuration file:

[pytest]
base_url = http://www.example.com

Using an Environment Variable

You can specify the base URL by setting the PYTEST_BASE_URL environment variable.

Using a Fixture

If your test harness takes care of launching an instance of your application under test, you may not have a predictable base URL to provide on the command line. Fortunately, it's easy to override the base_url fixture and return the correct URL to your test.

In the following example a live_server fixture is used to start the application and live_server.url returns the base URL of the site.

import urllib2
import pytest

@pytest.fixture
def base_url(live_server):
    return live_server.url

def test_search(base_url):
    assert 200 == urllib2.urlopen('{0}/search'.format(base_url)).getcode()

Available Live Servers

It's relatively simple to create your own live_server fixture, however you may be able to take advantage of one of the following:

  • Django applications can use pytest-django's live_server fixture.
  • Flask applications can use pytest-flask's live_server fixture.

Verifying the Base URL

If you specify a base URL for a site that's unavailable then all tests using that base URL will likely fail. To avoid running every test in this instance, you can enable base URL verification. This will check the base URL is responding before proceeding with the test suite. To enable this, specify the --verify-base-url command line option or set the VERIFY_BASE_URL environment variable to TRUE.

Skipping Base URLs

You can skip tests based on the value of the base URL so long as it is provided either by the command line or in a configuration file:

import urllib2
import pytest

@pytest.mark.skipif(
    "'dev' in config.getoption('base_url')",
    reason='Search not available on dev')
def test_search(base_url):
    assert 200 == urllib2.urlopen('{0}/search'.format(base_url)).getcode()

Unfortunately if the URL is provided by a fixture, there is no way to know this value at test collection.

Resources

pytest-base-url's People

Contributors

b4handjr avatar beyondevil avatar davehunt avatar nicoddemus avatar pre-commit-ci[bot] avatar zac-hd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pytest-base-url's Issues

Question: Using a Fixture does not work for me - What did I missunderstood?

Hi,
It's very likely I did it wrong.

I'm using playwright in combination with pytest and pytest-base-url and running for example
pytest --base-url "https://10.10.201.131" tests/test_01.py and it works as expected.

For testing/learning purpose, I did the following code in the test_01.py file:

@pytest.fixture(scope="session")
def base_url():
    """Return a base URL"""
    return "base_url"
    ```
I expected a non working test (as returning just a faulty sring), but it still use the base-url from cmdline. 

Trying to verify a hostname with a self signed cert fails

When I try to use --verify-base-url against a host that has a self signed certificate, verify times out with this error:

request = <SubRequest '_verify_url' for <Function test_owned_experiments_page_loads>>, base_url = 'https://localhost'

    @pytest.fixture(scope='session', autouse=True)
    def _verify_url(request, base_url):
        """Verifies the base URL"""
        verify = request.config.option.verify_base_url
        if base_url and verify:
            session = requests.Session()
            retries = Retry(backoff_factor=0.1,
                            status_forcelist=[500, 502, 503, 504])
            session.mount(base_url, HTTPAdapter(max_retries=retries))
>           session.get(base_url)

.tox/integration-test/lib/python3.6/site-packages/pytest_base_url/plugin.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.tox/integration-test/lib/python3.6/site-packages/requests/sessions.py:546: in get
    return self.request('GET', url, **kwargs)
.tox/integration-test/lib/python3.6/site-packages/requests/sessions.py:533: in request
    resp = self.send(prep, **send_kwargs)
.tox/integration-test/lib/python3.6/site-packages/requests/sessions.py:646: in send
    r = adapter.send(request, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <requests.adapters.HTTPAdapter object at 0x7f576912eac8>, request = <PreparedRequest [GET]>, stream = False, timeout = <urllib3.util.timeout.Timeout object at 0x7f5769bc3c50>, verify = True, cert = None, proxies = OrderedDict()

    def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
        """Sends PreparedRequest object. Returns Response object.
    
        :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
        :param stream: (optional) Whether to stream the request content.
        :param timeout: (optional) How long to wait for the server to send
            data before giving up, as a float, or a :ref:`(connect timeout,
            read timeout) <timeouts>` tuple.
        :type timeout: float or tuple or urllib3 Timeout object
        :param verify: (optional) Either a boolean, in which case it controls whether
            we verify the server's TLS certificate, or a string, in which case it
            must be a path to a CA bundle to use
        :param cert: (optional) Any user-provided SSL certificate to be trusted.
        :param proxies: (optional) The proxies dictionary to apply to the request.
        :rtype: requests.Response
        """
    
        try:
            conn = self.get_connection(request.url, proxies)
        except LocationValueError as e:
            raise InvalidURL(e, request=request)
    
        self.cert_verify(conn, request.url, verify, cert)
        url = self.request_url(request, proxies)
        self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
    
        chunked = not (request.body is None or 'Content-Length' in request.headers)
    
        if isinstance(timeout, tuple):
            try:
                connect, read = timeout
                timeout = TimeoutSauce(connect=connect, read=read)
            except ValueError as e:
                # this may raise a string formatting error.
                err = ("Invalid timeout {}. Pass a (connect, read) "
                       "timeout tuple, or a single float to set "
                       "both timeouts to the same value".format(timeout))
                raise ValueError(err)
        elif isinstance(timeout, TimeoutSauce):
            pass
        else:
            timeout = TimeoutSauce(connect=timeout, read=timeout)
    
        try:
            if not chunked:
                resp = conn.urlopen(
                    method=request.method,
                    url=url,
                    body=request.body,
                    headers=request.headers,
                    redirect=False,
                    assert_same_host=False,
                    preload_content=False,
                    decode_content=False,
                    retries=self.max_retries,
                    timeout=timeout
                )
    
            # Send the request.
            else:
                if hasattr(conn, 'proxy_pool'):
                    conn = conn.proxy_pool
    
                low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
    
                try:
                    low_conn.putrequest(request.method,
                                        url,
                                        skip_accept_encoding=True)
    
                    for header, value in request.headers.items():
                        low_conn.putheader(header, value)
    
                    low_conn.endheaders()
    
                    for i in request.body:
                        low_conn.send(hex(len(i))[2:].encode('utf-8'))
                        low_conn.send(b'\r\n')
                        low_conn.send(i)
                        low_conn.send(b'\r\n')
                    low_conn.send(b'0\r\n\r\n')
    
                    # Receive the response from the server
                    try:
                        # For Python 2.7, use buffering of HTTP responses
                        r = low_conn.getresponse(buffering=True)
                    except TypeError:
                        # For compatibility with Python 3.3+
                        r = low_conn.getresponse()
    
                    resp = HTTPResponse.from_httplib(
                        r,
                        pool=conn,
                        connection=low_conn,
                        preload_content=False,
                        decode_content=False
                    )
                except:
                    # If we hit any problems here, clean up the connection.
                    # Then, reraise so that we can handle the actual exception.
                    low_conn.close()
                    raise
    
        except (ProtocolError, socket.error) as err:
            raise ConnectionError(err, request=request)
    
        except MaxRetryError as e:
            if isinstance(e.reason, ConnectTimeoutError):
                # TODO: Remove this in 3.0.0: see #2811
                if not isinstance(e.reason, NewConnectionError):
                    raise ConnectTimeout(e, request=request)
    
            if isinstance(e.reason, ResponseError):
                raise RetryError(e, request=request)
    
            if isinstance(e.reason, _ProxyError):
                raise ProxyError(e, request=request)
    
            if isinstance(e.reason, _SSLError):
                # This branch is for urllib3 v1.22 and later.
>               raise SSLError(e, request=request)
E               requests.exceptions.SSLError: HTTPSConnectionPool(host='localhost', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:852)'),))

.tox/integration-test/lib/python3.6/site-packages/requests/adapters.py:514: SSLError

According to this there is a way for requests to skip this verification. Of course that can lead to other problems.

Lazy load requests dependency

Closed PR (and start of discussion): #9

Before I consider implementing the lazy load, I need to understand the time/speed issue 100% and have exhausted all other (resonable) approaches.

@boxed

Allow fixture scope to be customised

Some live servers support fixtures with a scope other than session, for example pytest-flask. When using a narrower scope with a custom base_url fixture, it is unable to be injected into the wider scoped _verify_url fixture:

ScopeMismatch: You tried to access the package scoped fixture base_url with a session scoped request object, involved factories:
.venv/lib/python3.12/site-packages/pytest_base_url/plugin.py:19:  def _verify_url(request, base_url)

Could the fixtures support dynamic scopes to resolve this?

ERROR tests/test_verify_base_url.py

Hi, over Arch Linux we have the following error with recent python 3.10.2-1. Full log is here.

============================= test session starts ==============================
platform linux -- Python 3.10.2, pytest-6.2.5, py-1.11.0, pluggy-0.13.1
rootdir: /tmp/makepkg/python-pytest-base-url/src/pytest-base-url, configfile: tox.ini, testpaths: tests
plugins: base-url-1.4.2, localserver-0.5.1
collected 10 items

tests/test_base_url.py .....                                             [ 50%]
tests/test_verify_base_url.py ..E.E..E                                   [100%]

==================================== ERRORS ====================================
_______________ ERROR at teardown of test_enable_verify_via_cli ________________

cls = <class '_pytest.runner.CallInfo'>
func = <function call_runtest_hook.<locals>.<lambda> at 0x7f083c56b880>
when = 'teardown'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: "Callable[[], TResult]",
        when: "Literal['collect', 'setup', 'call', 'teardown']",
        reraise: Optional[
            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
        ] = None,
    ) -> "CallInfo[TResult]":
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: Optional[TResult] = func()

/usr/lib/python3.10/site-packages/_pytest/runner.py:311: 

Support specifying a base URL via environment variable

It's already possible to do this using PYTEST_ADDOPTS but I think it would be a nice idea to add support for a PYTEST_BASE_URL environment variable. If present this would be used, but would be overridden by any other method for specifying the URL.

`pytest.config` removed

The readme suggests:

@pytest.mark.skipif(
    'dev' in pytest.config.getoption('base_url'),
    reason='Search not available on dev')

but use of pytest.config is deprecated in v4 and removed in v5. (Latest stable release as of writing is 5.1.2.)

Not clear to me what the best recommendation is now, there's a pytestconfig fixture, but since it's a fixture it can't be used in a mark.

Skipping in the body of the test is inelegant for a common case, but perhaps it's the only solution.

Malformed URL: / is not a valid URL

Hello. I got an error, when use base-url for open main page with xdist plugin. My test starts with:
main_page.open()

I run it:
pytest -n 2

And gets an Error:
selenium.common.exceptions.InvalidArgumentException: Message: Malformed URL: / is not a valid URL.

If I not use base_url and go to page this way:
main_page.open_url('www.google.com')
and comment it at pytest.ini - everything is good.

My conftest.py have:
@pytest.fixture()
def page_main(selenium):
return pages.MainPage(selenium)

My Pipfile:

pytest==5.1.2
pytest-bdd==3.2.1
pytest-xdist==1.31.0
pytest-base-url==1.4.1
pytest-selenium==1.17.0

gcloud==0.18.3
firebase==3.0.1
python_jwt==3.2.4
sseclient-py==1.7
py-postgresql==1.2.1
requests-toolbelt==0.9.1
arrow==0.15.4
kombu==4.6.7

My pytest.ini

[pytest]
bdd_features_base_dir = tests
base_url = https://projectname.com
sensitive_url = https://projectname.com
addopts = --driver Remote
          --port 4444
          --capability browserName firefox
          --html tests/logs/report.html
          --cucumberjson=tests/uploads/cucumber.json
          --cucumberjson-expanded
          -v
filterwarnings =
    ignore::DeprecationWarning
markers =
    smoke: fast result
    crypto: add calculations

Here is track trace:

result = call_fixture_func(fixturefunc, request, kwargs)
../../.pyenv/versions/3.6.8/envs/projectname/lib/python3.6/site-packages/_pytest/fixtures.py:778: in call_fixture_func
res = fixturefunc(**kwargs)
tests/functional/test_02_deposit_by_dash.py:20: in user_signed_in
page_main.open()
../../.pyenv/versions/3.6.8/envs/projectname/lib/python3.6/site-packages/pypom/page.py:130: in open
self.driver_adapter.open(self.seed_url)
../../.pyenv/versions/3.6.8/envs/projectname/lib/python3.6/site-packages/pypom/selenium_driver.py:48: in open
self.driver.get(url)
../../.pyenv/versions/3.6.8/envs/projectname/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py:333: in get
self.execute(Command.GET, {'url': url})
../../.pyenv/versions/3.6.8/envs/projectname/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py:321: in execute
self.error_handler.check_response(response)


self = <selenium.webdriver.remote.errorhandler.ErrorHandler object at 0x10faa6f98>
response = {'status': 400, 'value': '{"value":{"error":"invalid argument","message":"Malformed URL: / is not a valid URL.","stack...dArgumentError@chrome://marionette/content/error.js:304:5\nget@chrome://marionette/content/listener.js:1132:19\n"}}'}
E selenium.common.exceptions.InvalidArgumentException: Message: Malformed URL: / is not a valid URL.

Please help me to figure out usage of base-url plugin or fix It if you can.

Multiple attempts to verify the base URL.

I think it would be nice to have multiple attempts at trying to verify the base URL, especially if a server is starting up. Maybe just a number of attempts, or a max time allowed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.