Even framework opinions should be weakly held
I bill Saunter as an opinionated framework. That is, there is really only one right place for things and it behaves in certain ways. One of these opinions is that the element being interacted with should have focus, which in Se-RC will move it into the visible part of the browser. This is extremely useful when babysitting a run or looking at screen captures after the fact.
It also caused interaction with a particular bit of UI controls for a client to need a massive hack put in place. The bit of UI in this case is a search box which has a bit of default wording in the box when the mouse is not over it or when it is not the active element in the DOM.
Here is the original code
<pre lang="python">def delete_search_word_and_check_default_text(self):
self.se.window_focus()
self.search_header_field = ""
self.search_footer_field = ""
header_default_text_visibility = self.se.get_attribute("css=div#searchHeader div.searchInput > div@style")
# We simulate a keystroke event on anything apart from the footer search field
# in order to get focus away from the footer search field and hence see the default text
self.se.type_keys(locators["search_field_header"], "")
footer_default_text_visibility = self.se.get_attribute("css=div#searchResultsFooter div.searchInput > div@style")
return header_default_text_visibility, footer_default_text_visibility
which aside from the hack around focus has a couple smells
- locators right in the Action rather than up at the top of the module
- It is checking styles rather than content. Styles are calculated at runtime and really, look-and-feel is something better left to humans I think.
- The method name claims to be doing a check, but it is not. And if it was then it shouldn’t be as that doesn’t belong in a Page Object but in the script itself
What I replaced it with is
<pre lang="python">def clear_search(self, which_search):
if which_search == "header":
self.search_header_field = ""
self.se.fire_event(locators["search_field_header"], "blur")
elif which_search == "footer":
self.search_footer_field = ""
self.se.fire_event(locators["search_field_footer"], "blur")
Coming back to the idea of weakly held opinions, the clear_search method maybe took 2 minutes to write and 4 hours to debug. I knew that the way to tell the browser to loose focus on an element is to send the blur event to it. But it wasn’t working. Sure, the text was cleared, but the default value wasn’t being inserted.
Next stop, the source.
<pre lang="javascript">function searchParamOnBlur(a,b) {
if (a.value == "" && a != document.activeElement) {
b.style.visibility="visible"
}
}
After staring at this for longer than I care to admit I realized what the problem was; the a != document.activeElement part. Recall that one opinion of Saunter is to set focus on things before interacting with them so what was happening was essentially ‘set focus on the element, and then fire the blur event at the element’ which would fail the condition in the JS and not put the default text back in.
So Saunter needed to grow the ability to turn off the auto-focusing. Not globally of course, but per command. With that in place and a specific focus on a different element on the page, the default text started to appear.
<pre lang="python">def clear_search(self, which_search):
if which_search == "header":
self.search_header_field = ""
self.se.focus(locators["top_listed_search_results_link"])
self.se.fire_event(locators["search_field_header"], "blur", False)
elif which_search == "footer":
self.search_footer_field = ""
self.se.focus(locators["top_listed_search_results_link"])
self.se.fire_event(locators["search_field_footer"], "blur", False)
One other thing to note about this is when to acknowledge that an opinion, even one embedded into a framework is really a heuristic. That is, it can be wrong for some situations. And when it is, provide the ability to take the opposite (or at least an alternative) approach.
Oh. And of course that understand the code of the thing thats causing your to consider hacky solutions will often lead you to the correct (or a correct) one.