Forum: RSpec response.body.should be_xml_with -- how to do nested xpath matches

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Phlip (Guest)
on 2009-03-08 19:56
(Received via mailing list)
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...

   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:

<form action="/users">
   <fieldset>
     <legend>Personal Information</legend>
     <ol>
       <li id="control_user_first_name">
         <label for="user_first_name">First name</label>
         <input type="text" name="user[first_name]" id="user_first_name"
/>
       </li>
     </ol>
   </fieldset>
</form>

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:

<fieldset>
   <legend>
     Personal Information
   </legend>
   <ol>
     <li id='control_user_first_name'>
       <label for='user_first_name'>
         First name
       </label>
       <input name='user[first_name]' type='text' id='user_first_name'/>
     </li>
   </ol>
</fieldset>

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\") ]")
   --> <legend> ... </>

xpath(:"label[ contains(., \"First name\") ]")
   --> <label for='user_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
<form>
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
David C. (Guest)
on 2009-03-08 20:14
(Received via mailing list)
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
Zach D. (Guest)
on 2009-03-08 20:35
(Received via mailing list)
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...
>>        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:
>>
>>    </li>
>>  --> <legend> ... </>
>> 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
Phlip (Guest)
on 2009-03-08 21:24
(Received via mailing list)
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...

  <form blah>
     <input blah>
     <input blah>
     <input blah>
  </form>

...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 <a>
  - the <a> 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":

http://www.oreillynet.com/onlamp/blog/2007/08/xpat...

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 <a> somehow moved away from its
introductory paragraph.

--
   Phlip
   http://www.zeroplayer.com/
Phlip (Guest)
on 2009-03-08 21:25
(Received via mailing list)
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!
David C. (Guest)
on 2009-03-08 21:32
(Received via mailing list)
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
Zach D. (Guest)
on 2009-03-08 22:12
(Received via mailing list)
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 <a>
>  - the <a> 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 <center> 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/xpat...
>
> 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 <a> 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
Phlip (Guest)
on 2009-03-08 22:27
(Received via mailing list)
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.
<a>
cannot be around <div>, 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/
Phlip (Guest)
on 2009-03-09 08:45
(Received via mailing list)
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/
James B. (Guest)
on 2009-03-09 18:08
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.
Phlip (Guest)
on 2009-03-09 22:12
(Received via mailing list)
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
Zach D. (Guest)
on 2009-03-10 00:03
(Received via mailing list)
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. :)

>  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
Andrew P. (Guest)
on 2009-03-11 23:28
(Received via mailing list)
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>
James B. (Guest)
on 2009-03-12 15:27
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:

<h2 id="authentication_request">To Proceed Please Authenticate
Yourself</h2>
<div id="authentication_fields">
  <form action="/user_session"
        class="new_user_session"
        id="new_user_session"
        method="post"
  >
    <label for="user_session_username">User Name</label> <br />
    <input id="user_session_username"
           name="user_session[username]"
           size="30" type="text" />
...
    <input id="user_session_submit"
           name="commit"
           type="submit"
           value="Authenticate" />
  </form>
</div>

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/ticke...
Phlip (Guest)
on 2009-03-12 17:38
(Received via mailing list)
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
This topic is locked and can not be replied to.