Eric Pierce

I'm a software tester, developer, and graphic designer living in Colorado Springs with my wife and son. I have five years of experience with automated testing, ten years of software engineering and development, and fifteen years in illustration and 3D graphics.

In my spare time, I enjoy programming in Python and learning new languages such as Haskell. I'm a strong proponent of open source and open content. I created Kelp, Cukable, Grinder Webtest, CSVSee, tovid, PySci and other software, and have contributed to several open-source projects. I've also added many illustrations and articles to Wikipedia.

I think any IT professional should have a large repertoire of skills and be open to learning new technologies; nevertheless, I do have some clear favorites:

My web presences:

My Blog

Sun, 10/31/2010 - 15:17

I'm using Cucumber and Capybara for integration testing on a web application that depends heavily on the use of subdomains. Since some features rely on client-side Javascript, some scenarios use the Selenium 2 (WebDriver) driver, while other scenarios use the regular rack-test driver.

The Selenium driver does a great job of handling subdomains, and switching subdomains in the middle of a scenario. Setting Capybara.app_host is all that's needed, with a couple of caveats:

  • You include the port number of your Capybara server
  • Your /etc/hosts has the relevant subdomains defined

Since I'm using Capybara 0.4.0.rc, I've specified which port to use in features/support/custom_env.rb (you could also put this in features/support/env.rb, but since that's overwritten when you upgrade, it's best to keep your customizations separate):

# custom_env.rb
 
Capybara.run_server = true
Capybara.server_port = 9887

Then, when I need to switch subdomains in a step definition:

# features/step_definitions/general_steps.rb
 
DOMAIN = "example.com"
PORT = Capybara.server_port
 
Given /^I am in subdomain "(.+)"$/ do |subdomain|
  Capybara.app_host = "#{subdomain}.#{DOMAIN}:#{PORT}"
end

This works well enough if all your scenarios are tagged @selenium, but what happens if you try to switch subdomains using the default rack-test driver? It doesn't work, because the rack-test driver that comes with Capybara doesn't look at app_host, only default_host. And what's worse, it only uses that when the first rack-test mock session is created:

# lib/capybara/driver/rack_test_driver.rb
 
def build_rack_mock_session
  @mock_session = Rack::MockSession.new(app,
        Capybara.default_host || "www.example.com")
end

Any on-the-fly changes to Capybara.default_host will have no effect, since the session has already been created. The net effect is that your first scenario might use the correct subdomain, then you're stuck in that subdomain--and since rack-test runs rather silently in an invisible mock browser, the only symptom of this may be strange errors about nil objects and missing page content. Some scenarios might run fine in isolation (if they only use one subdomain), but then blow up when you try to run them as part of a larger batch (two scenarios, each using a different subdomain).

After failing at a bunch of different workarounds, I was ready to give up and just stick @selenium tags on everything (effectively tripling Cucumber's execution time). Just as I was preparing to leap from the precipice, a co-worker pointed me towards Tristan Dunn's article Multiple Sessions in Cucumber & Selenium. This technique was designed to allow multiple Selenium browser instances to be in different subdomains, by creating separate sessions for each one. Since my problem with rack-test was directly related to being unable to create a new session for each subdomain, this proved to be an excellent starting point.

I mainly wanted the ability to switch sessions whenever the subdomain changed, so I factored out a new method switch_session:

# features/support/session.rb
 
module Capybara
  module Driver
    module Sessions
      def set_session(id)
        Capybara.instance_variable_set('@session_pool', {
         "#{Capybara.current_driver}#{Capybara.app.object_id}" => $sessions[id]
        })
      end
 
      def switch_session(id)
        $sessions ||= {}
        $sessions[:default] ||= Capybara.current_session
        $sessions[id]       ||= Capybara::Session.new(Capybara.current_driver, Capybara.app)
        set_session(id)
      end
 
      def in_session(id, &block)
        switch_session(id)
        yield
        set_session(:default)
      end
    end
  end
end
 
World(Capybara::Driver::Sessions)

Now, changes to Capybara.default_host will be picked up whenever a new subdomain is used for the first time. When I switch to subdomain foo, then $sessions['foo'] stores a new Capybara::Session. If I switch to a different subdomain, then back to foo later on, it'll just use the same foo session created earlier.

And now my subdomain-switching step looks like this:

# features/step_definitions/general_steps.rb
 
Given /^I am in subdomain "(.+)"$/ do |subdomain|
  if Capybara.current_driver == :selenium
    Capybara.app_host = "#{subdomain}.#{DOMAIN}:#{PORT}"
  else
    Capybara.default_host = "#{subdomain}.#{DOMAIN}"
    switch_session(subdomain)
    visit("http://#{subdomain}.#{DOMAIN}")
  end
end

At first, this worked well, except my Selenium scenarios kept opening new browser windows. Since I don't really need multiple-session support in my Selenium tests at this point, I solved this by using a single session for all Selenium-driven tests:

# features/support/custom_env.rb
 
Before('@selenium') do
  switch_session('selenium')
end

Now, my Selenium and rack-test scenarios can coexist, with reliable subdomain behavior. Success!

Sun, 12/26/2010 - 23:33

I've had this idea kicking around in my head for a while that someone ought to rewrite The Zen of Python from a Ruby perspective. Despite their many similarities (very high-level, multi-paradigm, interpreted and dynamic), Python and Ruby have nearly opposite design principles in many areas. Similarly, programmers of the two languages seem (to me anyway) to have different attitudes about readability and documentation, and what constitutes "cleverness" in programming (and whether cleverness is good or bad).

Having used Ruby regularly for a few years, and Python for much longer, and having formed some negative impressions of Ruby, I thought I would attempt this tongue-in-cheek comparison of the two languages. Don't get me wrong--I like Ruby quite a lot, even more than Python in some respects (especially blocks); the Zen of Ruby may come across as a little insulting, and more than a little inaccurate, but it was fun to write, and I think a productive way to vent a few of my frustrations with learning the language.

Without further ado:

The Zen of Python
by Tim Peters
The Zen of Ruby
by Eric Pierce
Beautiful is better than ugly. Beauty is in the eye of the beholder.
Explicit is better than implicit. Implicit is preferable to explicit.
Simple is better than complex. Simple is boring.
Complex is better than complicated. Complex is interesting.
Flat is better than nested. Delegate the details to someone else.
Sparse is better than dense. If possible, make it a one-liner.
Readability counts. Readability is sometimes nice.
Special cases aren't special enough to break the rules. Special cases are everywhere; the rules can't cover them all.
Although practicality beats purity. When in doubt, monkeypatch.
Errors should never pass silently. Errors should be suppressed.
Unless explicitly silenced. Unless whiny nils is turned on.
In the face of ambiguity, refuse the temptation to guess. When in doubt, make assumptions about what the user wanted.
There should be one-- and preferably only one --obvious way to do it. There should be many-- preferably dozens --of non-obvious ways to do it.
Although that way may not be obvious at first unless you're Dutch. What's obvious to you may be completely unintuitive to someone else.
Now is better than never. Now is better than later.
Although never is often better than *right* now. And later is better than never.
If the implementation is hard to explain, it's a bad idea. If the design is flawed, explain why in the implementation docs.
If the implementation is easy to explain, it may be a good idea. If the design is good, don't bother with implementation docs.
Namespaces are one honking great idea -- let's do more of those! Namespaces are completely unnecessary -- let's make everything global!
Mon, 11/15/2010 - 14:51

I've been using Capybara with the Selenium driver for automated testing of a Rails project via Cucumber. Certain scenarios involve dealing with client-side Javascript (such as confirming a popup message before deleting a record), which is the reason I started using the Selenium driver. But I soon discovered that Selenium is slow, taking three or four times as long as the standard Rack-test driver. This pushed me to find non-Selenium ways of testing those features.

Web applications often involve working with a list of records that can be created, edited, and deleted. A common way of presenting these records is in an HTML table, with one row for each record, something like this:

<table>
  <tr>
    <td>Foo</td>
    <td><a href="...">Edit</a></td>
    <td><a href="...">Delete</a></td>
  </tr>
  <tr>
    <td>Bar</td>
    <td><a href="...">Edit</a></td>
    <td><a href="...">Delete</a></td>
  </tr>
</table>

If I want Capybara to interact with these, I need a way to click on the "Edit" or "Delete" link for a specific row. This can be accomplished with a bit of XPath:

def xpath_row_containing(a, b)
  "//table/*/tr[contains(., '#{a}') and contains(., '#{b}')]"
end

This expression looks for a table row that contains two specific bits of text, and is pretty generic. I want to use it for clicking the "Delete" link next to "Bar", so I'll write a step definition like this:

When /^I follow "(.+)" next to "(.+)"$/ do |link, identifier|
  row = find(:xpath, xpath_row_containing(link, identifier))
  row.click_link(link)
end

Then I can write a step like this:

  When I follow "Delete" next to "Bar"

Now, let's say my application displays a popup message using Javascript, confirming that I want to delete the record. How do I deal with that? Well, it's things like this that prompted me to start using Capybara and Selenium in the first place. Unfortunately, as of this writing, Selenium WebDriver has no support for Javascript popups. The issue has been open for over 3 years with no sign of resolution, so it's down to finding an ugly workaround, and this particular workaround is the least ugly one I've seen. Basically, instead of dealing with the popup, I override the builtin confirm function to just return true:

When /^I confirm the next popup$/ do
  page.evaluate_script("window.confirm = function(msg) { return true; }")
end

Now my deletion-steps can be something like this:

  When I confirm the next popup
    And I follow "Delete" next to "Bar"
  Then I should see "Bar has been deleted"

Yeah, so the sequence is screwy, but that's OK because I'm gonna hide this in a step definition pretty soon, so my scenario steps don't have to be so ugly.

This takes care of the Selenium-oriented steps. But I have a lot of scenarios dealing with deleting records, and I don't want to use Selenium for all of them. Again, the Selenium driver is slow; 10 minutes of rack-test scenarios becomes 30 or 40 minutes of Selenium scenarios, and nobody wants to wait 40 minutes for integration tests to run before every commit and merge. Since I'm just avoiding the popup anyway, I might as well invoke the deletion directly.

My application uses REST (as every good web application should), so I have routes something like this:

blah GET      /blah/:id  {:controller=>"blah", :action=>"show"}
     PUT      /blah/:id  {:controller=>"blah", :action=>"update"}
     DELETE   /blah/:id  {:controller=>"blah", :action=>"destroy"}

In other words, that "Delete" link just sends a DELETE request to /blah/:id, where :id is the primary key of whatever record is being deleted. All I gotta do is find the relevant delete link, then send the DELETE request directly. I'll use XPath again here:

def delete_link(record_name)
  "//table/*/tr[contains(., '#{record_name}')]/*/a[contains(., 'Delete')]"
end

I know, XPath looks a little ugly, but it is awesome. This XPath expression finds a table row that contains the record name, and looks within it for a link that says "Delete". Now I can do this:

  link = find(:xpath, delete_link(record_name))
  page.driver.delete(link[:href])

Note that only the Rack-test driver includes the delete method; Selenium doesn't have it, because Selenium is supposed to mimic a user's actions within the browser, and users can't send DELETE requests explicitly.

So now I have two ways of doing the same thing--one with Selenium, another without. What if I want my scenario to work with both drivers? Wrap the whole thing in a step definition--I can make a generic step for deleting any record via its "Delete" link:

When /^I delete record "(.+)"$/ do |record_name|
  if Capybara.current_driver == :selenium
    When %{I confirm the next popup}
    And %{I follow "Delete" next to "#{record_name}"}
  else
    link = find(:xpath, delete_link(record_name))
    page.driver.delete(link[:href])
    # I need to follow a redirection at this point -- your app may not require this
    When %{I follow "redirected"}
  end
  Then %{I should see "#{record_name} has been deleted"}
end

Now, regardless of whether my scenario is using Selenium or not, I can delete a record with a simple step like this:

  When I delete record "Bar"

This gives me the ability to switch to Selenium when I want to debug my steps, then switch back to Rack-test later when I have a need for speed.