Using semantic meaning in features and user interfaces
WORK IN PROGRESS
This explains why I need to fork webrat, and why I believe features should rely upon css classes and id's.
I believe that the CSS class is generally the best mechanism for expressing the semantic meaning of a particular piece of html (combined with DOM location and html tags). For example if somehow in viewing an order we want to reorder the order then I would first produce
.order
.reorder
# re-order goes here
This clearly establishes the semantic meaning - that I can reorder an order. Now the actual UI could be a number of things
- Instructions:
Reorder by phoning 0800 555 5555 and quoting order# - a button
[reorder] - a link
click <here> to reorder
Now as far as my business requirement and feature is concerned I don't care about these implementation details, and these details are certainly volatile. However the semantic layout is fixed. Here I see a representation of an order and can reorder the order.
I can now refine this feature to state that I should be able to tell the order to reorder. When I do this I should see some response in the UI which indicates whether my command has been successful.
When I reorder
Then I should see ...
In web applications you can do this in two ways
- follow a link
- submit a form (by pressing a button)
Again my feature does not care how I reorder unfortunately with webrat I now have to choose - I can either 'click' or 'press'.
.order
.reorder
%a{:href => order_path(:id => order.id)}= h(order.name)
above I am 'clicking' a link, whilst below I am pressing a button
.order
.reorder
-form_for @order do |f|
...
f.submit "reorder"
Now as far as my feature is concerned I don't give a damn how the re-ordering is done I just want to reorder. However I have to change my scenario (yes I know I can hide this change to a degree by re-using steps and having fancy step definition).
Scenario: Reorder
Given there is an order
When I view orders
And I click reorder
Then ...
OR
Scenario: Reorder
Given there is an order
When I view orders
And I press reorder
Then ...
Webrat compounds this frustration by making it really hard to press or click. Currently it will click only using text, id or an href, and its even more restrictive when it comes to pressing. So my first improvement was to extend click link to also be able to use classes. Now at least I can do
click link reorder
Although I have to change my implementation code
.order
%a{:clases => reorder, :href => order_path(:id => order.id)}= h(order.name)
My Fork of Webrat
Webrat has become a very important part of my coding toolkit, to the point where its omissions and flaws cause great annoyance. However with the rise of Github we can just fork webrat, fix things and then issue pull requests and hope to get things back into the original.
This is what I have done with Webrat.
However my pull request has been ignored and now has been rejected ticket ? Here again Git provides a solution, I just keep maintaining my current version of the plugin pulling changes from the original. To do this I have to overcome a couple of problems
- gem generation
- differentiating my version of the plugin from the original
Gem Generation
Webrat uses a rake task to generate its gem, and does not want github to generate gems. I do want github to generate my gem so I've added 'jeweler' tasks to the rakefile so that it is easy for me to pull changes and regenerate a .gemspec for github to use. As a gemspec contains a list of every file in the gem, it is very important to update the gempspec every time I pull otherwise my version of the gem will end up missing files.
So I installed jeweler
sudo gem install jeweler
Then added following to rake file
require 'jeweler'
...
Jeweler::Tasks.new(spec)
This adds loads of rake tasks rake -T, and I can use rake gemspec to build my gem.
Differentiating my Version
With a correct gemspec in my repository. Github should do the business and build diabolo-webrat. Now peeps can use this with the same convenience as the original.
I discuss in another article my reasons for forking webrat.
Webrat Visits and Redirects
Depressing how long it took me to find out that the visits method in webrat follows all redirects. This means that having a story like
When I visit /products/1
Then I should be redirected to the home page
is nonsense as you cannot detect the redirect. In fact all webrats methods follow redirects
Of course you can detect that you have gone to the home page so
When I visit /products/1
Then I should go to the home page
Another alternative is to change the implementation of the redirect step. Currently restful authentication has
Then "$actor should be redirected to $path" do |_, path|
response.should redirect_to(grok_path(path))
end
We could change our story implementation to something like
Then "$actor should be redirected to $path" do |_, path|
response.url should_be (grok_path(path))
end
However this isn't a great solution either.
Webrat isn't to blame here. It has the strong opinion that how you get to a particular location is an implementation detail, and that such detail has no place in features stories or integration tests which is what webrat targets. Restful_Authentication is making the mistake of introducing this detail into its story testing and compounding this by using webrat to implement many of its tests. This has the potential to cause all sorts of problems
I agree with webrat that the mechanism of redirecting is the sort of detail should be tested at a lower level in functional/controller tests, and because the word redirect is so closely associated with the mechanism of redirection - it has no place in our features.
So our original story should become
When I visit /products/1
Then I should go to the home page
An additional benefit with this clearer language is that it makes it significantly clearer how wrong our example story is
Cucumber Webrat and Debugging Features
Debugging with Cucumber
Simple as putting a debugger statement in any steps file.
Note also that you can run just one feature at a time with cucumber using -l. See cucumber -h
Needed this when dealing with a problem with Restful Authentication forms. What I wanted to do was run the following story
Scenario: Anonymous user can create an account
Given I am on the signup page
When I fill in "Login" with "My product name"
However this kept on failing with
When I fill in "Login" with "My product name" # features/steps//common_webrat.rb:17
Could not find input "Login". (Test::Unit::AssertionFailedError)
Very puzzling as this sort of test works fine with Products. The matcher for this task is
When /^I fill in "(.*)" with "(.*)"$/ do |field, value|
fills_in(field, :with => value)
So I added a debugger and stepped into the code
When /^I fill in "(.*)" with "(.*)"$/ do |field, value|
debugger
fills_in(field, :with => value)
After a bit of traipsing around I found out that webrat uses a library called hpricot to implement most of its functionality, and that it was working fine. However the restful_authentication forms were not so good. Turns out that the labels were not matching the fields in the forms.
Learnt quite alot from this
- More about how webrat works
- How to debug a failing step
Anyhow learnt how to debug a feature