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
I have started my career with Networking Domain.Have 8 years of relevant IT experience in network protocol testing.Experienced in functional testing of various networking protocols and Device-specific features.I was involved mainly in Manual Testing, Writing Test Plans, Updating Release Notes, Documentation for the same.Mainly worked on L4-L7 Load Balancers, Application Accelerators, Application Delivery Controllers.After that, I was looking for Automation Testing and Remote Work Opportunities.I found Qxf2 is the right place.Learning Python,Automation Framework, Exploring New Tools for Automation.My hobbies are listening classical,non-classical music .
Thanks for the post.