Wrapping APIs in your framework
Same client as in a refactoring writeup, but this time I was refactoring out some un-necessary calls through the browser and replacing them with hooks into an API that is available.
Whenever it makes sense to do so, you should try to not do things in the browser. Yes, it is one of those great ironies of writing Selenium scripts. Here was my report on it.
<email>
I have removed the ClientPage.get_validated_random_search_term() method and replaced it with a call to the REST API. This call was pulling a term from a csv and trying it as a search term. If a video was returned with that term, then that term was returned. Now, if there wasn’t an API available is a clever solution. But we have one. :)
So lets have a look at this. Here is the hook to actually get the information from the server. Notice how it doesn’t do any transformation. It is the raw response.
modules/clientrest/videohub.py
<pre lang="python">import json
import requests
from ClientException import APIException
import clientrest
from saunter.ConfigWrapper import ConfigWrapper as config
class VideoHub(clientrest.ClientRest):
@classmethod
def popular_videos(cls):
url = "/rest/videohub/popularVideos?output=json"
r = requests.get(config().config.get("Selenium", "base_url") + url)
if r.status_code != 200:
raise APIException("%s response from %s" % (r.status_code, url))
return json.loads(r.content)
Any transformation/filtering is done a layer higher. In this case we only care about the tags to be used as search terms.
modules/providers/searchterms.py
<pre lang="python">import random
import os.path
import clientrest.videohub
class SearchTerms(object):
@classmethod
def random_video(cls):
search_terms = []
popular_videos = clientrest.videohub.VideoHub.popular_videos()
for video in popular_videos["videoList"]:
search_terms.extend(video["tags"])
return random.choice(search_terms)
Which means we can just do
<pre lang="python">search_results_page = self.video_dasboard_page.perform_search(SearchTerms.random_video())
or if we need to recycle that same term
<pre lang="python">search_term = SearchTerms.random_video()
search_results_page = self.video_dasboard_page.perform_search(search_term)
</email>
The key thing here is the separation of concerns. One class deals only with the interaction with the API and does nothing specific around how the information will ultimately be used. That is the responsibility of a different class which will likely grow random_photo, random_music and random_radio when we need them. Other uses of the returned API information would be in their own, separate, class. And by structuring things in this manner, if (when) the API changes or how we determine the ‘tags’ changes, the script is isolated from this and should not have to modify it.