Author Archive for 54bottles

03
Jun
09

Javascript testing in Ruby with Selenium

InvisibleHand, a Firefox plugin that checks alternative prices as you are shopping online and alerts you if a bargain is found, consists of client part (the extension itself, in Javascript) and server part (the database of products, in Ruby). In order for the extension to always stay up-to-date it downloads pieces of JS code with price scraping logic from the server when it starts. I look after the JS code updating it whenever the layout of pages on retailers’ websites changes.

The challenge was to test this stuff automatically because the JS code makes part of a Ruby project and I want to ‘rake test’ it all. Getting a JS engine in my project would be an overkill, so I resorted to Selenium.

So, how to test a piece of Javascript code stored in your Ruby project?

First, download Selenium :)

Then, launch the server
java -jar selenium-server.jar

Then, install selenium client
sudo gem install selenium-client

In your unit test, launch Selenium:

attr_reader :browser

def setup
  @browser = Selenium::Client::Driver.new "localhost", 4444, "*firefox", "http://www.google.com/", 10000
  browser.start_new_browser_session
end

def teardown
  browser.close_current_browser_session
end

 

The base address that you use to launch Selenium (google in this example) doesn't matter as long as your code isn't doing cross-domain requests.

Then, in your test:

def test_js
  str = "Ah, Satan sees Natasha".to_json
  js = %Q[
    function reverseString(str) {
      var rstr = '';
      for (var i=str.length-1; i>=0; i--)
        rstr += str.charAt(i);
      return rstr;
    } 
    reverseString(#{str});
  ]
  result = browser.js_eval(js)
  assert_equal 'ahsataN sees nataS ,hA', result
end

A few comments:

  1. The conversion to json is not necessary in this example but if you are sending an array of data or strings with characters which should be escaped, use json.
  2. js_eval() function returns the result of the last line in the script.
  3. If you need to return anything more complex than a string or a number, consider converting your data to json before returning and then parsing it in ruby (that's what I'm doing in my tests but I omitted it here for clarity)

The problem with this code is that is damn slow because a new instance of Firefox is started for this test and it's not fast. However, it's better than no tests at all and if you don't need to start a new instance of firefox for every test, it's actually tolerable.
Loaded suite /Users/evgeny/invisiblehand/test/selenium/js_test
Started
.
Finished in 7.403807 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Good luck!

13
May
09

libxml Extra content at the end of the document

Ruby libxml parser that I use to process large xml files in SAX mode refused to process a file that looked perfectly valid, throwing ‘Extra content at the end of the document’ error somewhere in the middle of the file. It turned out that it disliked control character \x0B (vertical tab), which is not allowed in XML according to the spec.

To simply remove the vertical tabs from the file (or, rather, replace them with spaces), I tried using sed like this

sed s/\x0B/\ /g file.xml

but I found out that \xXX syntax is not supported by OSX sed version, which is a shame, so I used a ruby script, which, to my surprise, was quick enough to process a 800 MB file.

output = File.open("out.xml", 'w+')
File.open('file.xml').each{|p| output.puts p.gsub(/\x0B/, ' ')}

10
May
09

TextMate: lstat – No such file or directory

Sometimes in TextMate I get exceptions like this when trying to run unit tests:

/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/pathname.rb:420:in `lstat': No such file or directory - /Users/evgeny/Projects/project/test/unit/test (Errno::ENOENT) from
<... stack trace skipped ...> 

The exception above is caused by the call to realpath() in path_to_url_chunk() in Bundles/Ruby.tmbundle/Support/RubyMate/run_script.rb.

def path_to_url_chunk(path)
  unless path == "untitled"
    file = Pathname.new(path).realpath.to_s
    "url=file://#{e_url(path)}&"
  else
    ''
  end
end

There are two problems here. First, the file variable is not used, so the hyperlink to the failed method in textmate output would be broken as a result. Second, realpath() raises an exception because for some reason (I didn’t dig deeper) the current directory is ‘/path/to/test/unit’ and path is ‘test/unit/my_test.rb’, so realpath() can’t find the test.

The modified version of the function works better:

def path_to_url_chunk(path)
unless path == "untitled"
Dir.chdir "../.."
file = Pathname.new(path).realpath.to_s
"url=file://#{e_url(file)}&"
else
''
end
end

It’s not a proper solution because the either the file path or the working directory should be corrected before this function is called. If you know a better solution to this problem, please leave a comment.




March 2010
M T W T F S S
« Jun    
1234567
891011121314
15161718192021
22232425262728
293031