Phlip
2009-Mar-08 17:00 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
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 Kotlyarov''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: <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 Chelimsky
2009-Mar-08 18:03 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
On Sun, Mar 8, 2009 at 12:00 PM, Phlip <phlip2005 at gmail.com> wrote:> 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 Kotlyarov''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 > ? ?} > ?endThis 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> > 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 > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Zach Dennis
2009-Mar-08 18:23 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
On Sun, Mar 8, 2009 at 2:03 PM, David Chelimsky <dchelimsky at gmail.com> wrote:> On Sun, Mar 8, 2009 at 12:00 PM, Phlip <phlip2005 at gmail.com> wrote: >> 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 Kotlyarov''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 > > 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 >I like this a lot.> > > >> >> 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 >> >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Phlip
2009-Mar-08 18:41 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
David Chelimsky 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/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 <a> somehow moved away from its introductory paragraph. -- Phlip http://www.zeroplayer.com/
Phlip
2009-Mar-08 18:45 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
Zach Dennis 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 Chelimsky
2009-Mar-08 19:30 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
On Sun, Mar 8, 2009 at 1:41 PM, Phlip <phlip2005 at gmail.com> wrote:> David Chelimsky 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.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> 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/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 <a> somehow moved > away from its introductory paragraph. > > -- > ?Phlip > ?http://www.zeroplayer.com/ > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Zach Dennis
2009-Mar-08 19:55 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
On Sun, Mar 8, 2009 at 2:41 PM, Phlip <phlip2005 at gmail.com> wrote:> David Chelimsky 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.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.> > 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 systemIn 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/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 <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 > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Phlip
2009-Mar-08 20:14 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
Zach Dennis 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
2009-Mar-09 06:43 UTC
[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches
Zach Dennis 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/
Zach Dennis 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. -- Posted via http://www.ruby-forum.com/.
James Byrne wrote:> Zach Dennis 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
On Mon, Mar 9, 2009 at 12:08 PM, James Byrne <lists at ruby-forum.com> wrote:> Zach Dennis 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 Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
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 Byrne <lists at ruby-forum.com>> Zach Dennis 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. > -- > Posted via http://www.ruby-forum.com/. > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20090311/504dc759/attachment-0001.html>
Andrew Premdas 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 shouldThis 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/tickets/2096-added-select-method-tests-to-form_helper_testrb -- Posted via http://www.ruby-forum.com/.
James Byrne 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