Ruby-based testing will only get you so far in the web development world - sooner or later you need to make sure your code works in a real browser. So for the last few years I’ve been occasionally trying to get a browser-based test automation tool running - often something in the Selenium family. It usually takes a full day for me to give up, usually because I just can’t get the tool to actually drive the browser.
Today I got FireWatir working! It wasn’t that hard, but I had to consult a few different web pages, so here it is all in one place.
- Via FireWatir Installation, install the gem:
sudo gem install firewatir
and the JSSh Firefox extension (Mac FF 3.5 version) to make Firefox steerable.
- Set up a Firefox profile, so you can run tests without all your weird extensions in play, and without messing with your personal browser session and history. Mine is called “tester”.
- Start up Firefox with JSSh and your new profile (you can do this while Firefox is running):
/Applications/Firefox.app/Contents/MacOS/firefox -jssh -P "tester"
- In an
irb session, try some stuff - for example, on a new Spree instance:
ff = FireWatir::Firefox.new
ff.goto 'http://localhost:3000/'
ff.link(:text, 'Clothing').click
Voila! Now I want to try Cucumber and Watir.
posted by erik at 12:14 pm
John Resig, of jQuery and other fame, spoke tonight at the BayJax meetup in the Yahoo! cafeteria. His slides are probably online somewhere, but they are lucid, informative, illustrated with pictures and code samples, and written by someone who knows what he’s talking about, not to mention who was paying attention while writing them. (Update: Video on iTunes and not on iTunes.) If that’s not your bag, you’ll want to read my notes instead:
(more…)
posted by erik at 11:39 pm
You know those times when you’re looking all over the house for something and then somebody says, “Is that it in your other hand?”
Last night I spent at least an hour trying to figure out why I was getting a nil.rewrite error from Rails’s url_for method. I’ve seen hard-to-debug errors from there before, so I dug right down into it, but I couldn’t figure out why the @url variable was set in some cases and not in others… and then I determined that it actually was being set in all cases, but then it was mysteriously blank by the time url_for needed it….
And then I realized it was because I had a @url variable in my own controller action. And then I went to bed.
Most of the time I really love how dynamic and open Ruby is as a language, but sometimes private instance variables are just what you’d like to have had.
posted by erik at 11:39 am
A useful testing method in Rails is assert_select. In a functional test, you can use it to make sure the controller’s response has the HTML you expect:
should "display the suggestion as a link" do
assert_select 'a[href=?]', @click_url, @display_url
end
… says there should be an <a> tag with the given href attribute and content. Clearer and less brittle than computing a string and searching for it.
But what if you want to assert something about the HTML structure of something that isn’t the controller response? Like, if you want to look for a link in the flash hash?
I just added this to my test_helper.rb:
class ActiveSupport::TestCase
def assert_select_from(text, *args)
@selected = HTML::Document.new(text).root.children
assert_select(*args)
end
end
Now in my tests I can use the full power of assert_select on an arbitrary string:
should "provide an undo/delete link" do
assert_select_from(flash[:notice], 'a[href=?]',
venue_path(assigns(:venue)), 'undo')
end
posted by erik at 8:57 pm
This weekend RailsBridge ran its first Rails BugMash:
The idea is simple: RailsBridge has a lot of energy. The Rails Lighthouse has a lot of open tickets. With the help of some Rails Core team members, we’re going to see what we can do to cut down the number of open tickets, encourage more people to get involved with the Rails source, and have some fun.
Definitely a success - reportedly at least 120 tickets were closed, I’m sure a lot of new people got involved, and the people on #railsbridge seemed to be having fun.
(more…)
posted by erik at 10:34 am
The time had come to add a search engine to Touring Machine. I went pretty far down the road with Xapian/Xapit, but:
You may want to trigger [reindexing] via a cron job on a recurring schedule (i.e. every day) to update the Xapian database. However it will only take effect after the Rails application is restarted because the Xapian database is stored in memory.
I see the Xapit Sync project to fix this has since ceased to be vaporware. Well, maybe next time.
Anyway, so RubyTrends tells me the cool kids use Thinking Sphinx, and I want to be cool, but I’m running Touring Machine on the cheap - on a shared DreamHost server - and they don’t want me to run server processes, and although some guy on the Internet says it’s probably fine, I’m leery of defying them. But last week they were running a discount offer on DreamHost PS, their quasi-VPS service - no root, but you can run whatever you want, within the resources (memory, CPU) you pay for.
Sounds like a fine place to run the search engine, but I didn’t want to run the whole Rails app there - since I’ve already got a place to run it that is effectively free. (DreamHost is very cheap, and I run a bunch of sites on it.) So I set out to run a distributed site, with the web app running on DreamHost shared hosting, and the search engine running on DreamHost PS.
It took some setup, but I think I’ve got it working. Here are some notes. As usual, this isn’t a tutorial. You should read everyone else’s instructions - especially the official Thinking Sphinx documentation, and J. Wade Winningham’s post about Capistrano tasks. (I didn’t use his deploy.rb, though.)
(more…)
posted by erik at 5:47 pm
With the infrastructure in place for the suggestions feature, it’s easy to add new kinds of suggestions - especially if someone else has already done most of the work. For example, given an address, we should be able to look up the names of businesses at that address, and suggest them as venue names.
Yahoo! and Google both do this automatically if you search for something they recognize as an address. But Google doesn’t seem to provide an API. Yahoo! Local does, and the YM4R gem already provides a nice Ruby wrapper for it. So:
def make_suggestions_based_on_address
businesses = Ym4r::YahooMaps::BuildingBlock::LocalSearch::get(
:location => address.block,
:radius => 0.001,
:results => 20,
:query => '*'
)
businesses.each do |business|
add_suggestion(:name, business.title)
add_suggestion(:url, business.business_url) unless business.business_url.blank?
end
end
You might wonder about the radius parameter. Technically, Yahoo! doesn’t provide the service I want; I want “tell me what’s at this address”, it has “tell me what’s near this address”. With a radius of 0, it returns up to 10 results. After a little experimentation, it looks to me like 0.01 is a tight enough radius to get me all the businesses at an address - or at least the top 10 - and none of the ones next door.
This is actually one of two ways I’m subverting the API here. It’s intended for search, not simple lookup, which is why it’s a little awkward to get a simple address. It’s also why Yahoo! provides multiple URLs for each business. The BusinessUrl is the actual URL for the business’s web site. There’s also a BusinessClickUrl, which “contains extra information that helps [them] to optimize [their] search services. Yahoo! requests that you display the BusinessUrl, but link to the BusinessClickUrl, so they can track usage.
The latest code actually uses both URLs, but I’m going to stop here for now.
posted by erik at 12:22 am
Here’s how the suggestions feature for Touring Machine works so far.
UI
- As the user enters data in the new venue form, if we’re able to make any guesses, we pop them up in a panel to the right of the form.
- Each suggestion is a link. If a user clicks a suggestion link, the suggestion is copied into the corresponding form field, and the field is given focus, so the user can edit it. (For example, if the “page title” suggestion has additional text around the venue name.)
There’s also an “accessible” JavaScript-free UI, but it’s bad, so I’m going to pretend it’s not there.
Client and Server
I started drawing an interaction diagram, but it’s really standard Rails stuff:
- Model, controller, and view produce the new venue form.
- When any form input’s value changes, we submit the whole form (as an AJAX GET) to the controller’s
suggest action.
- The server responds with an RJS view that populates and shows the suggestions panel (if there are any suggestions) or hides it (if not).
- The suggestions are links that call a JavaScript function that copies, highlights, and focuses.
- Once the user is done editing, the
create action just works as normal.
Model
In keeping with the Skinny Controller, Fat Model approach, the only interesting code is in the model. It took me a couple of tries to get a design I liked, but eventually I went with Rails-style magic:
def make_suggestions
@suggestions = {}
methods.grep(/^make_suggestions_based_on_/).each do |method|
attrs = method.sub('make_suggestions_based_on_', '').split('_and_')
values = attrs.map {|attr| send(attr)}
send(method) unless values.any?(&:blank?)
end
suggestions
end
When asked for suggestions:
- We look through the Venue object for methods of the form
make_suggestions_from_attr_and_other_attr. (We support multiple attributes so that, for example, given a venue name and vague address - city and state - we can look up the exact address.) Call these “suggesters”.
- We call each suggester only if we have non-blank values for all the named attributes (because how are you going to base a suggestion on the venue’s address if you don’t know its address?). Note that although I’m saying “attributes”, this code works for attributes, associatons, and in fact any method. This is important because a venue’s address is actually a
has_one association.
- The suggesters populate
@suggestions, and then we return it.
A small thing I’m pleased by: The make_suggestions method works both as “create suggestion objects” and in the idiomatic sense of “make a suggestion” as “suggest something”.
posted by erik at 1:55 pm
What happened, see, was that I finally started using my aforementioned old Rails app in earnest, and when I did, I discovered that… I couldn’t log out. I didn’t actually need to log out, but it bugged me. I dug around a little. I could log out in development mode, but not production mode. The logout method thought it was working, but when the next page loaded I was still logged in. Something to do with sessions and cookies.
Something down in the depths of the authentication system, and I thought, well, I could try to debug this ancient plugin that I didn’t write, and that has probably never been updated to newer versions of Rails… or I could try out one of those new systems I’ve been reading about. That have documentation, and support, and Rails 2.3 compatibility. How hard could it be?
(more…)
posted by erik at 4:02 pm
This week I updated the authentication system in an old Rails app from the pre-REST acts_as_authenticated plugin to thoughtbot’s shiny new Clearance gem. What follows isn’t a how-to (because really, who else is going to be making this switch?) but it might be useful for someone. May the Great Gazoogle guide them to these shores.
(more…)
posted by erik at 3:39 pm