Ugly hack of the week: matching text in a div
Thursday, March 19th, 2009Edit 10/27/2010: As some folks pointed out in the comments, there are MUCH better ways to do this. Like using webrat’s have_selector matcher. This was a nice exercise in writing a simple parser for me though!
Edit 3/20/2009: Updated code o work for other tags besides div!
Often in my cucumber scenarios I check for some text on a page:
When I visit / Then I should see "booga booga"
The trouble is, sometimes I care not just whether the text is there, but where it is. If I’m looking at a report about our chickens, I’d like to make sure that some text is showing up with a specific one:
When I visit /chickens Then in "oscar" I should see "color: yellow"
There was an interesting discussion (here and here) on the RSpec list this month about how to do something like that. Apparently you can do with with the XPath matchers. And Phlip write a very nice RSpec matcher for the purpose.
But I’m using Sinatra with Cucumber and I haven’t really around to setting up RSpec with it, let alone figuring out how to configure a custom matcher. I started to set that stuff up, but it started to feel like yak shaving, so I just rolled my own, including a very rudimentary parser:
# features/step_definitions/my_webrat_steps.rb Then /^in "(.*)" I should see "(.*)"$/ do |id, text| body = get_inner_html(id, response.body) body.should =~ /#{text}/i end Then /^in "(.*)" I should not see "(.*)"$/ do |id, text| body = get_inner_html(id, response.body) body.should_not =~ /#{text}/i end def get_inner_html(id, body) start = false nested = 0 body = body.gsub(/\n/, "") buffer = Buffer.new(body) while buffer.next_one do if start == false tag = "<[a-z]* id=\"#{id}\"[^>]*>" if buffer.ends_with?(tag) match = buffer.first_half.match(/<([a-z]*) id="#{id}"[^>]*>$/) full_tag = match[0] entity = match[1] start = buffer.position+1 end else if buffer.ends_with?("<" << entity) nested += 1 end if buffer.ends_with?("<!--" << entity << "-->") if nested == 0 return body[start, buffer.position-start-entity.length-2] else nested -= 1 end end end end end class Buffer def initialize(str) @str = str @pointer = -1 end def next_one @pointer += 1 end def first_half @str[0,@pointer+1] end def ends_with?(end_str) end_str <<= "$" first_half.match(end_str) end def position @pointer end end
It seems to work ok!