Specifying which button webrat should press

I was writing a feature in which I had webrat push a button. However,
there are many buttons on this page with the same text. I wanted to
specify which one would be push in as close a way I could to the way a
person would describe which one he was pushing. Here is what I ended up
with:

#custom_webrate_steps.rb
require ‘rexml/document’
include REXML

When /^I push “(.)" near "(.)”$/ do |button, text|
#The dom element that contains the text must have an id

rexml_doc = Document.new(response.body)
regexp = /#{Regexp.escape(text)}/
nodes = find_nodes_with_regexp(rexml_doc, regexp)
dom_id = nodes.first.attributes[‘id’]

within ‘#’ + dom_id do |scope|
scope.clicks_button(button)
end
end

def find_nodes_with_regexp(node, regexp)
result = []
result << node if node.methods.include?(‘text’) && node.text =~ regexp
node.each {|n| result += find_nodes_with_regexp(n, regexp)} if
node.methods.include?(‘each’)
result
end

Any ideas for making this nicer?

On Thu, Nov 20, 2008 at 4:32 AM, Pau C. [email protected] wrote:

When /^I push “(.)" near "(.)”$/ do |button, text|
end

Any ideas for making this nicer?

give buttons unique dom ids and pass the id to #clicks_button ?

aslak hellesoy wrote:

give buttons unique dom ids and pass the id to #clicks_button ?

Of course that’s the easy way to do it :slight_smile:

I was hoping to keep my stories a bit closer to English. Plus then I’d
need some extra instance variable so that step can calculate the dom_id.
Here is an example of what I’m going for:

Scenario: Accepting a friend request
Given there is a user named “Fred”
And I am logged in as a normal user named “Joe”
And there is an unaccepted invite from “Fred” to me
And there is a user named “Sally”
And there is an unaccepted invite from “Sally” to me
When I go to “the invites from friends page”
And I push “accept” near “Fred”
# I want to avoid: And I push #accept_button_user_1
Then I should see “Now you are friends with Fred”
When I push “ignore” near “Sally”
Then I should see “Sally won’t ever bother you again”

Or maybe you are suggesting:

Scenario: Accepting a friend request
Given there is a user named “Fred”
And I am logged in as a normal user named “Joe”
And there is an unaccepted invite from “Fred” to me
And there is a user named “Sally”
And there is an unaccepted invite from “Sally” to me
When I go to “the invites from friends page”
And I push “accept” for user “Fred”
Then I should see “Now you are friends with Fred”
When I push “ignore” for user “Sally”
Then I should see “Sally won’t ever bother you again”

with this matcher:
When /^I push “(.)" for user "(.)”$/ do |button, user_name|
user = User.find_by_name(user_name)
scope.clicks_button(“accept_#{dom_id(user)}”)
end

Am I putting too much thought into this?

Paul

P.S. I am sorry if this has been a dumb question. I’m still just trying
to learn the best way to approach this stuff.

On Thu, Nov 20, 2008 at 12:07 AM, Pau C. [email protected] wrote:

Given there is a user named “Fred”
And I am logged in as a normal user named “Joe”
And there is an unaccepted invite from “Fred” to me
And there is a user named “Sally”
And there is an unaccepted invite from “Sally” to me
When I go to “the invites from friends page”
And I push “accept” near “Fred”

I want to avoid: And I push #accept_button_user_1

You’re on the right track, and I don’t think Aslak was recommending
putting accept_button_user_1 in the step.

What you can do is put it in the step definition, and use some sort of
map.

For example, I usually have a map of page names to paths so I can say:

When I visit the new pet form

which is implemented like this:

When /I visit the (.*)/ do |page|
visit case page
when “new pet form”
new_pet_path
end
end

So you can do a similar mapping with Fred. Maybe it works like this
(just guessing - don’t know what your app looks like)

When /I push “(.)” near “(.*)” do |what, who|
user = User.find_by_first_name(who)
clicks_button “#{what}button_user#{user.id}”
end

As long as you keep this sort of thing simple, I find it very
manageable and it actually begins to encourage good APIs and
conventions in the app.

And there is an unaccepted invite from “Fred” to me
And there is a user named “Sally”
And there is an unaccepted invite from “Sally” to me

I might do something to eliminate the need for the previous statement:

Given /there is an unaccepted invite from “(.*)” to me/ do |username|
user = User.find_or_create_by_first_name(username)
# whatever you need to create the invite
end

Now these first lines can be reduced to:

Given I am logged in as a normal user named “Joe”
And there is an unaccepted invite from “Fred” to me
And there is an unaccepted invite from “Sally” to me

That’s a lot less noise, and there is a clear implication that Fred
and Sally exist.

WDYT?

David

I did something similar with

Scenario: Cart shows item price and total price
Given there is a product with name foo and price £24.50
And I am on the products page
When I add product with name foo to cart

And a step matcher

When /^I add product with name (.*) to cart$/ do |name|
p = Product.find_by_name(name)
p.should_not be_nil
clicks_link_within(“#product_#{product.id}”, “Add to Basket”)
end

A couple of things that influenced this where

  1. I didn’t think it was appropriate to have the technical mechanism
    for adding the product (push button, click add link etc.) in the
    feature

  2. Any page with multiple button|links with the same text must have
    some surrounding element that allows you to differentiate. This
    technical detail belongs in the steps

As far as your features go I think that

And I accept Fred’s invite

scans better than

And I push “accept” near “Fred”

HTH

2008/11/20 Pau C. [email protected]:

Given this:

If I want webrat to select on the ccs id (#submit_commit_client) rather
than the value (Create) how do I pass this to click_button?

I have tried this:

click_button("#submit_commit_client")

Which gives this error:

Could not find button “#submit_commit_client” (Webrat::NotFoundError)

/usr/lib64/ruby/gems/1.8/gems/webrat-0.4.1/lib/webrat/core/locators/locator.rb:14:in
`locate!’

On Fri, Feb 20, 2009 at 3:13 PM, James B. [email protected]
wrote:

If I want webrat to select on the ccs id (#submit_commit_client) rather
than the value (Create) how do I pass this to click_button?

I have tried this:

click_button(“#submit_commit_client”)

Can you use the following or do you need to use an id for other reason?

click_button “Create”

[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users


Zach D.
http://www.continuousthinking.com

Andrew P. wrote:

And I accept Fred’s invite
scans better than
And I push “accept” near “Fred”

That definitely is better. I guess I was trying to be too broad. My
original goal was to write a match that would work on any page with
multiple links/buttons (of the same name).

It seems like everyone is suggesting that I write a step that is more
specific to this particular page. That will make my step matcher MUCH
simpler, and it will make my story more readable.

Thanks for all the advice!

Paul

On Fri, Feb 20, 2009 at 3:50 PM, James B. [email protected]
wrote:

Zach D. wrote:

Can you use the following or do you need to use an id for other reason?

click_button “Create”

That is how I am doing the check now. I am simply investigating whether
another means is available to me.

You should be able to click the button by id, by not using a CSS
selector. ie:

click_button “submit_commit_client”

At the back of my mind is the idea
that it is the presence of the function and not the form of the label
that is important. By adding an id to the input tag and selecting on
that then whatever the button itself is labeled as becomes unimportant.


Zach D.
http://www.continuousthinking.com

Zach D. wrote:

Can you use the following or do you need to use an id for other reason?

click_button “Create”

That is how I am doing the check now. I am simply investigating whether
another means is available to me. At the back of my mind is the idea
that it is the presence of the function and not the form of the label
that is important. By adding an id to the input tag and selecting on
that then whatever the button itself is labeled as becomes unimportant.

Zach D. wrote:

You should be able to click the button by id, by not using a CSS
selector. ie:

click_button “submit_commit_client”

You are right. That works. Thank you.

Regards,