Monday, May 28, 2012

System Testing Bookmarklets: Selenium

My switch search engine bookmarklet had two bugs. First when I deployed the page, the URL was screwed up (thanks for telling me Chris) and second my bookmarklet was incompatible with FireFox[1].  



Conveniently the software engineering community has a solution to this class of problems and it's called  system testing. In this context, System testing means interact with the web pages via a browser just like users do.  I did some digging and I couldn't find a perfect (or awesome) tool for system testing web apps.



I was able to get a tool that could drive FireFox and Chrome (IE isn't working).  This tool had some warts, and took some experimentation to get working, but it mostly meets the needs. Please comment if you find a better tool!



The tool I used is called Selenium, and it's scriptable via python (also Java and C#). Selenium is able to impersonate a user performing actions via the common web browsers. The code below should make reasonable sense to someone familiar with the DOM. As usual, the code is also available on bitbucket:



from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
import unittest, time, re
import urllib

class RunTheClick(unittest.TestCase):
def setUp(self):
self.base_url = "http://ig2600.blogspot.com"
self.drivers=[]

def test_chrome(self):
d = webdriver.Chrome()
self.drivers.append(d)
self.run_suite(d)

def test_firefox(self):
d = webdriver.Firefox()
self.drivers.append(d)
self.run_suite(d)

def run_suite(self,driverToUse):
self.driver = driverToUse
driver = self.driver

# Get Blog Page
driver.get(self.base_url + "/2012/05/assembly-to-javascript-tsrs-to.html")


bookmarklet = driver.find_element_by_partial_link_text("SwapSearchEngine")

#bookmarkletURL starts with uri scheme(javascript:), remove it to get code
bookmarkletURL = bookmarklet.get_attribute("href")
bookmarkletCode = bookmarkletURL.split("javascript:")[1]

# BUGBUG: bookmarkletCode is URLEncoded in FF but not in Chrome.
# In future, unquoting might not be safe in chrome, find correct way to handle this.
bookmarkletCode = urllib.unquote(bookmarkletCode)

# click on bookmarklet in page and verify it throws a failure alert.
bookmarklet.click()
self.verify_and_dismess_not_on_google_or_bing_alert()

# Selenium can't execute bookmarklet if there is an alert in play - that's too bad.
# otherwise we'd do the below
# driver.execute_async_script(bookmarkletCode)
# self.verify_and_dismess_not_on_google_or_bing_alert()

#go to google and search
driver.get("http://google.com")

# Very brittle (found it using web inspect in firefox developer tools)
textBox = self.driver.find_element_by_id("gbqfq")
textBox.send_keys ("a b c")
textBox.send_keys (Keys.RETURN)
time.sleep(2) # sleep to let autocomplete finish.
self.assertEqual(driver.title,u"a b c - Google Search")

# execute bookmarklet
print bookmarkletCode
driver.execute_script(bookmarkletCode);

# verify we switched to bing
self.assertEqual(driver.title,u"a b c - Bing")

# execute bookmarklet
driver.execute_script(bookmarkletCode);

# verify back on google.
self.assertEqual(driver.title,u"a b c - Google Search")

def verify_and_dismess_not_on_google_or_bing_alert(self):
# Click on the alert
alert = self.driver.switch_to_alert()
self.assertEqual(alert.text,u"Only works on a Google or Bing search")
alert.accept()
time.sleep(2) # sleep to let dialog go away.


def tearDown(self):
for d in self.drivers:
d.quit()

if __name__ == "__main__":
unittest.main()

Notes:

[1] InnerText isn't support in FF, need to use InnerHtml instead.




Sunday, May 27, 2012

Assembly to JavaScript;TSRs to Bookmarklets



Back when I started computing (the nineties), the coolest thing to to know was assembly. If you knew assembly you could make the computer do anything because the computer was essentially executing assembly[1].



Back in those days the way to inject code into the system was something called a Terminate And Stay Resident (TSR). This mini application could be invoked via a hotkey and run arbitrary code in your memory space - this was a great way to hack applications, or provide additional capabilities to programs.



Luckily history repeats itself.  Today, assembly has been replace by JavaScript, and  the mechanism for  TSRs is called bookmarklets. Bookmarklets are book marks which execute arbitrary javascript code.



To show you the power of bookmarklets I wrote a JavaScript bookmarklet that will take a search from  bing and run it on google or vice versa. You can install it by dragging SwapSearchEngine to your bookmark bar and then clicking on it.  I've embedded a video showing the bookmarklet in use.



If you'd like to play with bookmarklets the code is below and also available @ bitbucket.

javascript:function swapSearchEngine() {
/* Test cases:
1) do a google search - run bookmarklet
2) do a bing search - run bookmarklet
3) Go to a random page - run bookmarklet - make sure error occurs.
*/

/* Find page title. */
var title = document.getElementsByTagName('title')[0].innerText;
var errorMessage = "Only works on a Google or Bing search";

/* search engine format is title="search term - Search Engine" */
var searchEngineIndex = title.lastIndexOf(" - ");

if (searchEngineIndex == -1) {
alert(errorMessage);
return;
}

var currentSearchEngine = title.substring(searchEngineIndex + 3);
var newSearchLocation = "";
if (currentSearchEngine == "Google Search") {
newSearchLocation = "http://www.bing.com";
} else if (currentSearchEngine == "Bing") {
newSearchLocation = "http://www.google.com";
} else {
alert(errorMessage);
return;
}

var searchTerms = title.substring(0, searchEngineIndex);
var newURL = newSearchLocation + "/search?q=" + escape(searchTerms);
window.location = newURL;
};
swapSearchEngine();
void(0);



Notes:

 [1] Technically the computer didn't execute assembly, but there was a 1:1 mapping between assembly and what the computer executes.

Sunday, May 20, 2012

Soft Skills: Listen to people, and understand what they are telling you

(This post is not designed to be belittling, instead it's designed to be root cause analysis.)



When Zach, my two year old, figures something out he screams out barely intelligible words and repeats them until I can figure out what he is saying and repeat it back to him.  The more non-obvious Zach's discovery the more insistent Zach will be that I understand his unintelligible statement.



For example, the other day Zach and I were outside and Zach started screaming buddaf -  I was baffled! Zach was very insistent so I started looking around and sure enough I found a picture of a butterfly painted on a bus stop. I screamed butterfly, and Zach became ecstatic and we were able to move on to another topic.



By contrast if I can't figure out what Zach is saying he gets sad, and acts rejected. This morning at breakfast Zach wanted something and I couldn't figure out what it was.  For 3 minutes Zach kept repeating masfd (which I still can't figure out) with vigour and passion. Then Zach started saying masfd quieter and quieter, looking sadder and sadder. Finally, rejected, Zach got up and left the table.



When people grow up, they're the same. They still have novel thoughts that they want heard and understood. If you can't understand and hear those thoughts the speaker will feel sad and rejected, and likely feel rejected for significantly longer than a two year old.



So, when someone, lets call her Alice, tells you something - hear Alice, and work really hard to understand what Alice says. Otherwise, Alice will feel sad and rejected, and will probably continue repeating her thoughts for a lot longer.  Notice that just because you hear and understand Alice, doesn't mean you have to agree with her, just hearing Alice is often sufficient.