{"id":2436,"date":"2015-02-18T02:02:12","date_gmt":"2015-02-18T07:02:12","guid":{"rendered":"http:\/\/qxf2.com\/blog\/?p=2436"},"modified":"2018-06-21T00:48:21","modified_gmt":"2018-06-21T04:48:21","slug":"api-testing-python-mechanize","status":"publish","type":"post","link":"https:\/\/qxf2.com\/blog\/api-testing-python-mechanize\/","title":{"rendered":"API Testing with Python Mechanize"},"content":{"rendered":"<p>This is the third part in our series on API testing. Rather than focus on traditional approaches to API testing, we have decided to arm you with tools that let you interact with the API at different levels of abstractions. In <a href=\"https:\/\/qxf2.com\/blog\/api-testing-developer-tools\/\">part I<\/a> we showed you how to inspect the API calls of any web application. In <a href=\"https:\/\/qxf2.com\/blog\/api-testing-ui-tools-postman\/\">part II<\/a>, we gave examples of UI tools that could help you interact and mimic the API calls. In this third part, we will show you how you can use a higher level scripting language to interact with the API and write scripts that are repeatable and robust. <\/p>\n<p>For this tutorial, we have chosen to use the Python module Mechanize. We use these API test scripts in a couple different ways. One, to test the API themselves. Two, to quickly setup complex test scenarios. Using scripts that work off the API to setup test scenarios is more robust and much faster than using GUI automation. <\/p>\n<hr>\n<h3>Why Mechanize?<\/h3>\n<p><a href=\"http:\/\/www.qxf2.com\/?utm_source=api_testing_3&#038;utm_medium=click&#038;utm_campaign=From%20blog\">We<\/a> love Python. So naturally this tutorial uses Python. There are plenty of good Python modules to use for API tests. For simple tasks, we love the <a href=\"http:\/\/docs.python-requests.org\/en\/latest\/\">Requests module<\/a> which has a very clean and intuitive interface. For this tutorial, we have chosen to use Mechanize. Mechanize emulates the browser and makes it easy to handle authentication, sessions and cookies. It also helps us get past some sticky areas where the application API is not very clean and you need the browser to make requests. Of course, Mechanize comes with some drawbacks &#8211; like a lack of support for PUT, DELETE, PATCH HTTP methods. Qxf2 went  through the source code and wrote our own &#8216;<a href=\"https:\/\/qxf2.com\/blog\/python-mechanize-the-missing-manual\/\">Missing Manual<\/a>&#8216; that now makes Python Mechanize extremely powerful. We have used Python Mechanize for the past two years now and have found it suitable for our API testing needs.<\/p>\n<hr>\n<h3>Overview<\/h3>\n<p>In <a href=\"https:\/\/qxf2.com\/blog\/api-testing-developer-tools\/\">part 1<\/a>, we used the developer tools to inspect the http requests\/responses. We used <a href=\"http:\/\/droptask.com\/\">DropTask<\/a> as the application under test. DropTask is a free web based task and project management application that offers a visual approach towards managing daily workloads. For this tutorial, we will:<br \/>\n1. Login to DropTask<br \/>\n2. Get the account details<br \/>\n3. Delete an existing task<\/p>\n<p>While this workflow may seem odd\/not-realistic to professional testers, we chose these specific steps to keep this blog post at a reasonable length while still illustrating some common ways in which we expect you to use Mechanize when testing your web application. We highly recommend you scan <a href=\"https:\/\/qxf2.com\/blog\/api-testing-developer-tools\/\">Part I<\/a> of our tutorial if you have not yet already read it.<\/p>\n<hr>\n<h3>Implementation details using Python Mechanize<\/h3>\n<p><strong>1. Login to DropTask<\/strong><br \/>\nHere the browser url is set to DropTask login page. We make a <strong>POST request<\/strong> with the encoded parameters (email and password).<br \/>\n<a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Login_ContentType.png\" data-rel=\"lightbox-image-0\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Login_ContentType-1024x269.png\" alt=\"content-type login request\" width=\"1024\" height=\"269\" class=\"aligncenter size-large wp-image-2516\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Login_ContentType-1024x269.png 1024w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Login_ContentType-300x79.png 300w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Login_ContentType.png 1148w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<pre lang='python'>\r\ndef login(self,username,password):\r\n        \" Login to Droptask \"\r\n        #Setup the login POST request\r\n        my_params={'email':username,'password':password}                \r\n        params_encoded = urllib.urlencode(my_params) \/\/Content-Type = 'application\/x-www-form-urlencoded'\r\n        self.browser.method='POST'\r\n        login_response= self.browser.open(self.url,data=params_encoded)\r\n        \r\n        #Verifying that the login was successful.\r\n        #We have chosen to check if we were taken back to the login page or not\r\n        forms = mechanize.ParseResponse(login_response, backwards_compat=False)        \r\n        if (len(forms)!=0) and (forms[0].find_control(\"password\") is not None): #If we were redirected to the login page forms[0].find_control(\"password\") would not be None indicating that Login failed\r\n            self.write(\"Login failed\")\r\n            return False\r\n        else:\r\n            self.write(\"Login success\")\r\n            return True\r\n<\/pre>\n<p><strong>2. Get the account details<\/strong><br \/>\nOn successful login, we make a <strong>GET request<\/strong> with Unix timestamp in the url parameter to get the account details about templates, tasks, users, groups, workspaces etc. This call is an example where imagination and deduction shine through. We looked at the GET call in developer tools and were wondering what the cryptic number was. After trying a few calls in rapid succession and noticing that the number only slightly changed, we were able to guess that it had something to do with a timestamp. The number beginning with &#8217;14&#8217; made us guess its a Unix timestamp. And the guess turned out to be right! Given that DropTask seems to support real-time collaboration (see the calls to <a href=\"http:\/\/www.pubnub.com\/\">pubnub<\/a> in the screenshots?), it makes sense for the GET request to indicate that it wants the latest account details.<\/p>\n<pre lang='python'>\r\ndef get_account_details(self):\r\n        \" Get account details.\"\r\n        #Current unix timestamp in milliseconds is needed!\r\n        curr_time = int(time.time())*1000\r\n        url = \"https:\/\/www.droptask.com\/v1\/account?%s\"%curr_time\r\n        response = self.browser.open(mechanize.Request(url=url))\r\n        self.account_details = json.loads(response.read())\r\n        return json.dumps(self.account_details)\r\n<\/pre>\n<p><strong>3. Delete a given task<\/strong><br \/>\nMechanize only supports GET and POST methods out of the box. To extend Mechanize to support the other HTTP methods, you can refer to our <a href=\"https:\/\/qxf2.com\/blog\/python-mechanize-the-missing-manual\/\">Mechanize tutorial<\/a>. To keep this post complete, you need to add this class to your test file:<\/p>\n<pre lang='python'>\r\nclass Mechanize_Delete_Request_class(mechanize.Request):\r\n    \"Extend the mechanize Request class to allow a HTTP DELETE\"\r\n    def get_method(self):\r\n        return \"DELETE\"\r\n<\/pre>\n<p>We are assuming you already have a task in a particular project. If not create one in your workspace\/project. To delete the task we need to make a <strong>DELETE<\/strong> request. Since different projects could have tasks of the same name, our method takes 2 parameters -project name and task name. Inspecting the delete API calls, we notice that DropTask&#8217;s API expects a workspace id and the task id to perform a delete. Using the project name we get the workspace id from the account details.<\/p>\n<p><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Workspace.png\" data-rel=\"lightbox-image-1\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Workspace.png\" alt=\"json response workspace\" width=\"928\" height=\"243\" class=\"aligncenter size-full wp-image-2511\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Workspace.png 928w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Workspace-300x79.png 300w\" sizes=\"auto, (max-width: 928px) 100vw, 928px\" \/><\/a><\/p>\n<p>This workspace id and the task name(title) help us to get the task id. Using the task id we make a DELETE request.<\/p>\n<p><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Tasks.png\" data-rel=\"lightbox-image-2\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Tasks-1024x461.png\" alt=\"json response tasks\" width=\"1024\" height=\"461\" class=\"aligncenter size-large wp-image-2510\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Tasks-1024x461.png 1024w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Tasks-300x135.png 300w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/Json_Tasks.png 1223w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<p>Sample DELETE request when a task is deleted using the browser.<br \/>\n<a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/delete_task.gif\" data-rel=\"lightbox-image-3\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/delete_task-1024x181.gif\" alt=\"delete task\" width=\"1024\" height=\"181\" class=\"aligncenter size-large wp-image-2545\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/delete_task-1024x181.gif 1024w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2015\/02\/delete_task-300x53.gif 300w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<pre lang='python'>\r\ndef get_task_id(self,task_name,workspace_name):\r\n        \" Returns the task id for a given task in a project\"\r\n        self.task_id = None\r\n        workspace_id = self.get_workspace_id(workspace_name)\r\n        \r\n        if workspace_id is not None:            \r\n            for tasks in self.account_details['tasks']:\r\n                if tasks['workspace']== self.workspace_id and tasks['title']== task_name:\r\n                    self.task_id= tasks['_id']\r\n                    break\r\n\r\n        return self.task_id\r\n\r\ndef delete_task(self,task_name,workspace_name):\r\n        \" Deletes the given task by finding the task id\"\r\n        self.task_id = self.get_task_id(task_name,workspace_name)        \r\n        result = False\r\n        \r\n        if self.task_id is not None:\r\n            self.browser.method='DELETE'\r\n            del_task_res= self.browser.open(Mechanize_Delete_Request_class(\"https:\/\/www.droptask.com\/v1\/tasks\/%s\" % self.task_id))\r\n            self.write(\"Response Code for delete task %s is: %s\"%(task_name,del_task_res.code))\r\n            return del_task_res.code\r\n        else:\r\n            self.write(\"Could not find the given task :%s in workspace :%s\"%(task_name,workspace_name))\r\n            return None \r\n<\/pre>\n<hr>\n<h3>Notes<\/h3>\n<p>1. We did not have an opportunity to show you how to check response codes. With Mechanize, the response code for given response object is simply response.code<br \/>\n&nbsp;<br \/>\n2. <strong>To the testing team at DropTask<\/strong>, as part of preparing for this post, we ended up writing a lot more code. We created a small library using the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Facade_pattern\">facade pattern<\/a> to quickly automate many of your API calls. We believe it will help you setup complex test scenarios quickly and robustly. Hit us up at <strong>&#109;&#097;&#107;&#064;&#113;&#120;&#102;&#050;.com<\/strong> and we can share it with you.<br \/>\n&nbsp;<br \/>\nThis ends our three part series on arming you with a range of tools for performing API testing. We would love to hear your feedback and questions in the comments section.<\/p>\n<hr>\n<script>(function() {\n\twindow.mc4wp = window.mc4wp || {\n\t\tlisteners: [],\n\t\tforms: {\n\t\t\ton: function(evt, cb) {\n\t\t\t\twindow.mc4wp.listeners.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tevent   : evt,\n\t\t\t\t\t\tcallback: cb\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n})();\n<\/script><!-- Mailchimp for WordPress v4.10.1 - https:\/\/wordpress.org\/plugins\/mailchimp-for-wp\/ --><form id=\"mc4wp-form-1\" class=\"mc4wp-form mc4wp-form-6165 mc4wp-form-theme mc4wp-form-theme-blue\" method=\"post\" data-id=\"6165\" data-name=\"Newsletter\" ><div class=\"mc4wp-form-fields\"><div style=\"border:3px; border-style:dashed;border-color:#56d1e1;padding:1.2em;\">\r\n  <h1 style=\"text-align: center; padding-top: 20px; padding-bottom: 20px; color: #592b1b;\">Subscribe to our weekly Newsletter<\/h1>\r\n  <input style=\"margin: auto;\" type=\"email\" name=\"EMAIL\" placeholder=\"Your email address\" required \/>\r\n  <br>\r\n  <p style=\"text-align: center;\">\r\n    <input style=\"background-color: #890c06 !important; border-color: #890c06;\" type=\"submit\" value=\"Sign up\" \/>\r\n    \r\n  <\/p>\r\n  <p style=\"text-align: center;\">\r\n    <a href=\"http:\/\/mailchi.mp\/c9c7b81ddf13\/the-informed-testers-newsletter-20-oct-2017\"><small>View a sample<\/small><\/a>\r\n  <\/p>\r\n  <br>\r\n<\/div><\/div><label style=\"display: none !important;\">Leave this field empty if you're human: <input type=\"text\" name=\"_mc4wp_honeypot\" value=\"\" tabindex=\"-1\" autocomplete=\"off\" \/><\/label><input type=\"hidden\" name=\"_mc4wp_timestamp\" value=\"1777315524\" \/><input type=\"hidden\" name=\"_mc4wp_form_id\" value=\"6165\" \/><input type=\"hidden\" name=\"_mc4wp_form_element_id\" value=\"mc4wp-form-1\" \/><div class=\"mc4wp-response\"><\/div><\/form><!-- \/ Mailchimp for WordPress Plugin -->\n<hr>\n","protected":false},"excerpt":{"rendered":"<p>This is the third part in our series on API testing. Rather than focus on traditional approaches to API testing, we have decided to arm you with tools that let you interact with the API at different levels of abstractions. In part I we showed you how to inspect the API calls of any web application. In part II, we [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[43,26,18],"tags":[],"class_list":["post-2436","post","type-post","status-publish","format-standard","hentry","category-api-testing","category-mechanize-2","category-python"],"_links":{"self":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/2436","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/comments?post=2436"}],"version-history":[{"count":36,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/2436\/revisions"}],"predecessor-version":[{"id":6233,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/2436\/revisions\/6233"}],"wp:attachment":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/media?parent=2436"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/categories?post=2436"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/tags?post=2436"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}