Response.body.should be_xml_with -- how to do nested xpath matches


#1

RSpecsters:

I like nested XPath expressions, because the outer XPath assertion can
clip the
diagnostic of the inner XPath assertion. You don’t get the whole page
spewed
into your face when you fault!

This specification, for example, tests Yury K.'s user login page,
from:

http://github.com/yura/howto-rspec-custom-matchers/tree/master

it ‘should have xpathic tags’ do
render ‘/users/new’

 response.body.should be_xml_with{
   xpath :form, :action => '/users' do
     xpath :fieldset do
       xpath :'legend[ contains(., "Personal Information") ]' and
       xpath :'label[ contains(., "First name") ]' and
       xpath :input, :type => 'text', :name => 'user[first_name]'
     end
   end
 }

end

That tests this (otherwise innocuous) new.html.erb:

Personal Information
  1. First name

If that code had any major complex <%= erb %> activity, the tests^W
specifications would keep it honest.

Let’s change the specification, to simulate a bug, and try it:

 xpath :input, :type => 'text', :name => 'user[first_nome]' # was 

_name

That provides this mind-blast of errata:

‘/users/new should have xpathic tags’ FAILED
xpath: “descendant-or-self::input[@type = $type and @name = $name]”
arguments: {“name”=>“user[first_nome]”, “type”=>“text”}

xml context:

Personal Information
  1. First name

assert{ ( ( xpath(:“legend[ contains(., “Personal Information”) ]”) )
and
( ( ( xpath(:“label[ contains(., “First name”) ]”) ) and
( xpath(:input, { :type => “text”, :name => “user[first_nome]” }) )
) ) ) }
–> nil - should pass

xpath(:“legend[ contains(., “Personal Information”) ]”)
–> … </>

xpath(:“label[ contains(., “First name”) ]”)
–> … </>

xpath(:input, { :type => “text”, :name => “user[first_nome]” })
–> nil

./spec/views/users/new.html.erb_spec.rb:63:
script/spec:5:

Finished in 0.116823 seconds

2 examples, 1 failure

Note that the error message restricted itself to the XHTML inside the

tag. This is a major benefit when diagnosing a huge page that failed. (But also note that your HTML, like your code, should come in small reusable snippets, such as partials, and that these should get tested directly!)

Soon I will upgrade this system to use Nokogiri instead of (>cough<)
REXML.

Now, while I go and put the “simple” matcher that does this onto
Twitter,
YouTube, Mingle, Facebook, Mindfuck, Reddit, Tumblog, LinkedIn, and
Gist, you
all can just read it below my sig.


Phlip
http://www.zeroplayer.com/

require File.dirname(FILE) + “/…/…/spec_helper”
require ‘assert2/xpath’
require ‘spec/matchers/wrap_expectation’

Spec::Runner.configure do |c|
c.include Test::Unit::Assertions
end # TODO blog this

describe “/users/new” do

it “should have user form” do
render ‘/users/new’
response.should have_form(’/users’) do
with_field_set ‘Personal Information’ do
with_text_field ‘First name’, ‘user’, ‘first_name’
end
end
end

class BeXmlWith

 def initialize(scope, &block)
   @scope, @block = scope, block
 end

 def matches?(stwing, &block)
   waz_xdoc = @xdoc

   @scope.wrap_expectation self do
     @scope.assert_xhtml stwing
     return (block || @block || proc{}).call
   end
 ensure
   @xdoc = waz_xdoc
 end

 attr_accessor :failure_message

 def negative_failure_message
   "yack yack yack"
 end

end

def be_xml_with(&block)
BeXmlWith.new(self, &block)
end

def be_xml_with_(&block)
waz_xdoc = @xdoc
simple_matcher ‘yo’ do |given, matcher|
wrap_expectation matcher do
assert_xhtml given # this works
block.call # crashes with a nil.first error!
end
end
ensure
@xdoc = waz_xdoc
end

it ‘should have xpathic tags’ do
render ‘/users/new’

 response.body.should be_xml_with{
   xpath :form, :action => '/users' do
     xpath :fieldset do
       xpath :'legend[ contains(., "Personal Information") ]' and
       xpath :'label[ contains(., "First name") ]' and
       xpath :input, :type => 'text', :name => 'user[first_name]'
     end
   end
 }

end

end


#2

On Sun, Mar 8, 2009 at 12:00 PM, Phlip removed_email_address@domain.invalid wrote:

 end

}
end

This is nice, but the abstractions are operating at different levels.
The keyword is “xpath” but the args passed to that look nothing like
xpath. How about something like:

response.body.should be_xml_with do
form :action => ‘/users’ do
fieldset do
legend “Personal Information”
label “First name”
input :type => ‘text’, :name => ‘user[first_name]’
end
end
end


#3

On Sun, Mar 8, 2009 at 2:03 PM, David C. removed_email_address@domain.invalid
wrote:

http://github.com/yura/howto-rspec-custom-matchers/tree/master
end
fieldset do
legend “Personal Information”
label “First name”
input :type => ‘text’, :name => ‘user[first_name]’
end
end
end

I like this a lot.

 <li id="control_user_first_name">

Let’s change the specification, to simulate a bug, and try it:

--> ... Finished in 0.116823 seconds Now, while I go and put the "simple" matcher that does this onto Twitter,
   with_text_field 'First name', 'user', 'first_name'

def matches?(stwing, &block)
attr_accessor :failure_message
def be_xml_with_(&block)

 end

rspec-users mailing list
removed_email_address@domain.invalid
http://rubyforge.org/mailman/listinfo/rspec-users


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


#4

Zach D. wrote:

response.body.should be_xml_with do
form :action => ‘/users’ do
fieldset do
legend “Personal Information”
label “First name”
input :type => ‘text’, :name => ‘user[first_name]’
end
end
end

I like this a lot.

Then it should be “.should be_html_with”…

…and the diagnostics should be as comprehensive & detailed as mine!


#5

David C. wrote:

     xpath :'legend[ contains(., "Personal Information") ]' and
     xpath :'label[ contains(., "First name") ]' and
     xpath :input, :type => 'text', :name => 'user[first_name]'

This is nice, but the abstractions are operating at different levels.
The keyword is “xpath” but the args passed to that look nothing like
xpath.

That’s because it’s a DSL. You can use raw XPath, XPath with one
convenience
(:‘div[ @class = “content” ]’ as a symbol implies “any descendant”), or
you can
use an option hash that generates XPath.

The DSL’s benefit is tags like :attribute => foo will automatically
escape their
strings correctly. (That’s why the top line of the diagnostic was a
little
cluttered!) foo could contain mixed ’ and " payload, and we don’t care.

How about something like:

response.body.should be_xml_with do
form :action => ‘/users’ do
fieldset do
legend “Personal Information”
label “First name”

Because I’m not webrat?

Yes it would be kewt if label(‘yo’) went into method_missing and came
out as
//label[ contains(., “yo”) ]. But I have found that spot checks of
algorithmic
details, such as eRB tags, are more valuable to development than
slavishly
matching your tests to your HTML contents.

If you write this HTML…

…and if you test it with a sequence of assertions that exactly match
it…

page.should contain
form blah
input blah
input blah
input blah
end
end

…then you are not really doing TDD. You are merely replicating your
code as
your test, and that will only cause irritation at upgrade time or bug
time.

Here’s an example from our projects at work:

 get :thanks_for_purchasing
 assert_xpath :'center[ . = "Your order has been accepted!" ]'
 assert_xpath :div, :align => :center do
   a = assert_xpath(:'p[ contains(., "questions about your 

membership") ]/a’)
assert{ a.text == a[:href] }
assert{ a[:href] == SubscriptionController::CUSTOMER_SERVICE_LINK
}
end

Note how much that specifies in the customer acknowledgment page.

  • the confirmation is centered
  • the explanation div is centered (yes, we could identify it better!)
  • part of the explanation div’s contents contains “questions…”
  • next to the “questions…” is an
  • the contains an href matching its own text contents
  • the href links to our customer service system

I call this topic “XPath can see around corners”:

We are using XPath to pin down several elements in a page, allowing the
other
elements to change freely, but forcing them to maintain their
correlations. That
test would fail, for example, if the somehow moved away from its
introductory paragraph.


Phlip
http://www.zeroplayer.com/


#6

On Sun, Mar 8, 2009 at 2:41 PM, Phlip removed_email_address@domain.invalid wrote:

That’s because it’s a DSL. You can use raw XPath, XPath with one convenience

response.body.should be_xml_with do
slavishly matching your tests to your HTML contents.

time.
This doesn’t make a whole lot sense. How does the following move you
from TDD to non-TDD?

xpath :input, :name => “foo”
to
input :name => “foo”

XPath is merely the mechanism in which you’re allowed to find
particular elements on a page. After typing:

xpath :input, :name => “foo”
xpath :input, :name => “bar”
xpath :input, :name => “baz”

I would probably write my own wrapper so I could omit the redundant
“xpath” call all over the place. After all, I only care that the input
is found, if it uses XPath to do it–awesome.

end

Note how much that specifies in the customer acknowledgment page.

  • the confirmation is centered
  • the explanation div is centered (yes, we could identify it better!)
  • part of the explanation div’s contents contains “questions…”
  • next to the “questions…” is an
  • the contains an href matching its own text contents
  • the href links to our customer service system

In my experience relying on the syntactic details of the page is
extremely brittle and cumbersome. Writing semantic HTML and semantic
view specs (whether you use rspec or assert2.0 or whatever) seems to
remove unnecessary overhead, such as caring about a “div” or a “p” tag
on the page. Some tags have both syntactic and semantic meaning, such
as forms, labels, fieldsets, and anchor tags.

Given your above assertions, if you replaced your tag (which
is deprecated in HTML 4.01) with a div, and made it centered via CSS,
your assertion would fail. But should it? The presentation is changing
without affecting the behaviour of the view–it’s still displaying the
“Your order has been accepted” confirmation. Why not utilize semantic
HTML to not be affected by a syntactic details that doesn’t affect
whether or not your view is doing the right thing.

I’d apply the same considerations to the div and the p tags you’re
making assertions against.

I call this topic “XPath can see around corners”:

http://www.oreillynet.com/onlamp/blog/2007/08/xpath_checker_and_assert_xpath.html

We are using XPath to pin down several elements in a page, allowing the
other elements to change freely, but forcing them to maintain their
correlations. That test would fail, for example, if the somehow moved
away from its introductory paragraph.

Are these correlations best managed by specs? Our customers and
designers move things around on the page all of the time without
affecting behaviour, merely presentation, and having those changes
break all of the view specs is painful. It also seems unnecessary if
they move from a series of “div” tags to a “ul” with “li” elements.


Phlip
http://www.zeroplayer.com/


rspec-users mailing list
removed_email_address@domain.invalid
http://rubyforge.org/mailman/listinfo/rspec-users


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


#7

On Sun, Mar 8, 2009 at 1:41 PM, Phlip removed_email_address@domain.invalid wrote:

That’s because it’s a DSL. You can use raw XPath, XPath with one convenience

response.body.should be_xml_with do
slavishly matching your tests to your HTML contents.

time.
I’m only talking about syntax of the DSL - I don’t see how changing
the syntax would make you suddenly start writing crappy examples or
not doing TDD.

  • David

#8

Zach D. wrote:

XPath is merely the mechanism in which you’re allowed to find
particular elements on a page. After typing:

xpath :input, :name => “foo”
xpath :input, :name => “bar”
xpath :input, :name => “baz”

I would probably write my own wrapper so I could omit the redundant
“xpath” call all over the place. After all, I only care that the input
is found, if it uses XPath to do it–awesome.

Yet such a wrapper can fit quite comfortably on the application side -
not
necessarily on the vendor side. For example, if I care that a given
sequence of
inputs have a class keyed to the current user’s permissions, then that
(and the
naughty xpath) go inside the new custom assertion:
assert_input_for(@user). Such
wrappers obviously should not fall on the vendor side.

I like leaving the XPath hanging out because its operators and axes are
always
available without more DSLization.

Your remaining points are noted - the answers are either “violent
agreement” or
“then don’t do it like that”. Augmented by my next post!


Phlip
http://www.zeroplayer.com/


#9

Zach D. wrote:

In my experience relying on the syntactic details of the page is
extremely brittle and cumbersome. … Some tags have both syntactic
and semantic meaning, such as forms, labels, fieldsets, and anchor tags.

Is it “brittle” to test for specific css selectors that are tied to page
details? A typical case is where one has a button to drive in webrat
and instead of using the button text we use the associated id value
instead.


#10

Zach D. wrote:

response.body.should be_xml_with do
form :action => ‘/users’ do
fieldset do
legend “Personal Information”
label “First name”
input :type => ‘text’, :name => ‘user[first_name]’
end
end
end

I like this a lot.

I just thought of about 3 truly sick ways to do that.

Let me write them up, and then we will resume this thread.

I expect to also throw in Strict XHTML compliance checking as an option.

cannot be around

, etc; stuff like that. (Or whatever those rules
are!)

If I were evil, I would also permit :type => /e/, for those who want any
type
with an /e/ in it!


Phlip
http://www.zeroplayer.com/


#11

On Mon, Mar 9, 2009 at 12:08 PM, James B. removed_email_address@domain.invalid
wrote:

Zach D. wrote:

In my experience relying on the syntactic details of the page is
extremely brittle and cumbersome. … Some tags have both syntactic
and semantic meaning, such as forms, labels, fieldsets, and anchor tags.

Is it “brittle” to test for specific css selectors that are tied to page
details?

I think it depends on what details you’re tying them to. :slight_smile:

A typical case is where one has a button to drive in webrat
and instead of using the button text we use the associated id value
instead.

Does the id have meaning? Or is it something that communicates nothing
about what it is? ie:

click_button “add_new_item”

vs.

click_button "foobar


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


#12

James B. wrote:

Zach D. wrote:

In my experience relying on the syntactic details of the page is
extremely brittle and cumbersome. … Some tags have both syntactic
and semantic meaning, such as forms, labels, fieldsets, and anchor tags.

Is it “brittle” to test for specific css selectors that are tied to page
details? A typical case is where one has a button to drive in webrat
and instead of using the button text we use the associated id value
instead.

It is if your art department refuses to stop putting styles on ID tags!

Other than that, if you need more than one TR, for example, and your
test should
scrape them up and look at them, searching for their class is nice.

Does anyone here allow their art departments to directly edit and check
in
html.erb files? I suppose that would change things!


Phlip


#13

Andrew P. wrote:

The key phrase is semantic meaning. Its good to use CSS to give semantic
meaning to things that appear on web pages. Not only can these id’s or
classes be useful for using with screen readers, they should be stable
things that don’t change even if the design of the page does. It should

This is an example of my current practice, consequential on the
discussions I have had on this list:

To Proceed Please Authenticate Yourself

User Name
...

My intent is that id values are tied to the presentation elements they
deal with, not to the layout they find themselves in. So I test the UI
by calling the ids in webrat, rather than the labels. This will ease
any future transition to I18n.t calls in the templates.

As a side issue, in getting the input box ids to work with label ids as
I specified in the templates, I discovered that I had misread the
FormHelper api. I uncovered my mistake by writing a few tests to
exercise this feature as Rails own test/form_helper_test.rb did not.

The point is that I have submitted these tests as a patch to Rails and
they are awaiting verification for inclusion. Patches require three
reviewers for consideration by the core team. My submission has had one
positive review already. I would appreciate a couple of more. The
lighthouse ticket is 2096.

http://rails.lighthouseapp.com/projects/8994/tickets/2096-added-select-method-tests-to-form_helper_testrb


#14

James B. wrote:

As a side issue, in getting the input box ids to work with label ids as
I specified in the templates, I discovered that I had misread the
FormHelper api. I uncovered my mistake by writing a few tests to
exercise this feature as Rails own test/form_helper_test.rb did not.

The point is that I have submitted these tests as a patch to Rails and
they are awaiting verification for inclusion. Patches require three
reviewers for consideration by the core team. My submission has had one
positive review already. I would appreciate a couple of more. The
lighthouse ticket is 2096.

That reminds me of form_test_helper…

The deal is, with Rails out-of-the-box, you can test that a page wrote a
form
correctly, and the next text can show the post processes the arguments
correctly…

…but the test cannot show the form contained the correct fields. You
could
mis-name a field, mis-name it in the first test, name it correctly in
the next
test, and all tests would pass.

form_test_helper scrapes your @response.body, finds a target form, and
then
posts it. It would catch the mis-named field situation.

Can webrat close that gap? Can Rails integration tests close that gap?


Phlip


#15

The key phrase is semantic meaning. Its good to use CSS to give semantic
meaning to things that appear on web pages. Not only can these id’s or
classes be useful for using with screen readers, they should be stable
things that don’t change even if the design of the page does. It should
be
easy to say to designers don’t change these tags - they can either add
extra
classes or change the style definition.

However if you label things with poor semantic meaning then don’t be
suprised when they change. Testing for a mediumBlueBox is asking for a
kicking!

Finally finding good semantic descriptions is not easy. I think
“submitCustomerDetails” is better than “submitForm”, “submitButton” or
even
“submitCustomerDetailsButton”. But I have no idea whether anyone else
would
agree with me :).

2009/3/9 James B. removed_email_address@domain.invalid