Implementing Python Requests library in our automation testing framework

Qxf2 automation framework supports API testing. We used Mechanize library to make REST calls for our testing framework. As we migrated from Python 2 to Python 3 and since Mechanize is not supported on Python 3 we had to come up with an alternative module compatible with Python 2 & Python 3. We decided to use Requests library. In this post, we will have a look at changes we made and how we implemented Requests library to our testing framework


Why this post?

Requests is the most popular Python library for making HTTP requests. Its the cleanest way we have seen to make API calls programmatically in Python. When creating the first version of Qxf2’s automation framework (way back in 2013!), we chose to use Mechanize to make API calls because all our clients were using CAS as the authentication server and we needed something that could mimic the browser to get past it. But now, most of our clients use different authentication mechanisms and it made sense to start using Requests. We have used Requests library with Python 2 in the past but when implementing Requests in Qxf2’s testing framework we learned more about it and wanted to share the details on changes we built our framework around it.


Brief on our API testing framework

API automation is fast, robust and much easier to maintain than GUI automation. The Qxf2’s API automation framework is based on the object-oriented approach. We built wrappers around Mechanize Python library to make API calls. With Mechanize not being supported in Python 3 we are now building our framework around the Requests library. For more details, on the architecture of our API framework, you can refer to our earlier blog here.


Changes we made to implement Requests library

We renamed our Base method from Base_Mechanize to Base_API. Then we substituted the GET, POST, DELETE, PUT wrappers to use Requests wrappers. These methods return JSON response with status code or error details with status code for further debugging.

Changes to Get request: For Get Request with Mechanize module, we had to create browser instance. But Requests handles sessions without creating browser instance.

Get request implementation using Mechanize

def get_browser(self):
        "Create and return a browser object"
        browser = mechanize.Browser()
        browser.set_handle_robots(False) 
 
def get(self, url, headers={}):
        "Mechanize Get request"
        browser = self.get_browser()
        response = browser.open(mechanize.Request(url))

Whereas making get request with Requests is pretty simple and straightforward.

def get(self, url, headers={}):
        "Get request"
        try:
            response = requests.get(url=url,headers=headers)

Get method using Requests.

Get method in requests is handled as below, which is very straightforward.

def get(self, url, headers={}):
        "Get request"
        json_response = None 
        error = {}
        try:
            response = requests.get(url=url,headers=headers)
            try:
                json_response = response.json()
            except:
                json_response = None
        except (HTTPError,URLError) as e:
            error = e
            if isinstance(e,HTTPError):
                error_message = e.read()
                print("\n******\nGET Error: %s %s" %
                    (url, error_message))
            elif (e.reason.args[0] == 10061):
                print("\033[1;31m\nURL open error: Please check if the API server is up or there is any other issue accessing the URL\033[1;m")
                raise e
            else:
                print(e.reason.args)
                # bubble error back up after printing relevant details
                raise e # We raise error only when unknown errors occurs (other than HTTP error and url open error 10061) 
 
        return {'response': response.status_code,'text':response.text,'json_response':json_response, 'error': error}

Post method using requests.

Post method in requests is handled as below:

def post(self, url,params=None, data=None,json=None,headers={}):
        "Post request"
        error = {}
        json_response = None
        try:
            response = requests.post(url,params=params,json=json,headers=headers)
            try:
                json_response = response.json()
            except:
                json_response = None
        except (HTTPError,URLError) as e:
            error = e
            if isinstance(e,HTTPError,URLError):
                error_message = e.read()
                print("\n******\nPOST Error: %s %s %s" %
                    (url, error_message, str(json)))
            elif (e.reason.args[0] == 10061):
                print("\033[1;31m\nURL open error: Please check if the API server is up or there is any other issue accessing the URL\033[1;m")
            else:
                print(e.reason.args)
                # bubble error back up after printing relevant details
            raise e
 
        return {'response': response.status_code,'text':response.text,'json_response':json_response, 'error': error}

Put method using requests.

Put method in requests is handled as below:

def put(self,url,json=None, headers={}):
        "Put request"
        error = {}
        response = False
        try:
            response = requests.put(url,json=json,headers=headers)
            try:
                json_response = response.json()
            except:
                json_response = None
 
 
        except (HTTPError,URLError) as e:
            error = e
            if isinstance(e,HTTPError):
                error_message = e.read()
                print("\n******\nPUT Error: %s %s %s" %
                      (url, error_message, str(data)))
            elif (e.reason.args[0] == 10061):
                print("\033[1;31m\nURL open error: Please check if the API server is up or there is any other issue accessing the URL\033[1;m")
            else:
                print(str(e.reason.args))
            # bubble error back up after printing relevant details
            raise e
 
        return {'response': response.status_code,'text':response.text,'json_response':json_response, 'error': error}

Delete method using requests.

Delete method in requests is handled as below:

def delete(self, url,headers={}):
        "Delete request"
        response = False
        error = {}
        try:
            response = requests.delete(url,headers = headers)
            try:
                json_response = response.json()
            except:
                json_response = None
 
        except (HTTPError,URLError) as e:
            error = e
            if isinstance(e,HTTPError):
                error_message = e.read()
                print("\n******\nPUT Error: %s %s %s" %
                      (url, error_message, str(data)))
            elif (e.reason.args[0] == 10061):
                print("\033[1;31m\nURL open error: Please check if the API server is up or there is any other issue accessing the URL\033[1;m")
            else:
                print(str(e.reason.args))
            # bubble error back up after printing relevant details
            raise e
 
        return {'response': response.status_code,'text':response.text,'json_response':json_response, 'error': error}

Running some example test

We are not going to cover this section in detail as we have already covered it in our previous post here.
We have created a sample API automation test to verify & validate the changes we keep making to the API framework. You can download the test(with the framework) from Qxf2 automation framework.

The sample API test runs against this application cars app. We have created this application using Flask, it is a collection of cars & their attributes. It uses REST API to add or delete cars from the list.

You can refer the README.md file in the cars-api GitHub page to run the flask application locally and try adding/deleting cars using the Python request module using your Python interpreter
or
To run the sample API test from the Qxf2 automation framework against the hosted app, use the following command from the framework’s root dir

python -m pytest tests/test_api_example.py

References

1. Requests Document to have more information

2. Youtube video to know more about requests


One thought on “%1$s”

Leave a Reply

Your email address will not be published. Required fields are marked *