imedo Development Blog

there is no charge for awesomeness

Archive for the ‘have_tag’ tag

Matching elements on complex web pages with Webrat

without comments

Writing feature, integration, or acceptance tests with Webrat is a lot easier with simple web pages than it is with huge pages which provide much content. It can get really annoying to find the correct links or buttons to click. The same applies for matching content elements on complex pages in order to evaluate whether a test case was successful or not, since the same content can appear on different areas of one web page. Both issues can escalate if the position of layout elements is variable like in A/B usability tests or if a front-end layout manager is used. CSS selectors can help to find a layout element on a web page independent of its position. In this article is demonstrated how CSS selectors can be used with Webrat.

Given

In order to present the usage of CSS selectors with Webrat the following example is used. It shows a layout element called “content” which contains a table with two rows and each row provides the same options for its entry: Edit and delete.

<div id="content">
  <table>
    <thead>
      <tr><th>Name</th><th>Options</th></tr>
    </thead>
    <tbody>
      <tr id="entry_1" class="odd">
        <td>Entry 1</td>
        <td><a href="">Edit</a> <a href="">Delete</a></td>
      </tr>
      <tr id="entry_2" class="even">
        <td>Entry 2</td>
        <td><a href="">Edit</a> <a href="">Delete</a></td>
      </tr>
    </tbody>
  </table>
</div>

When

In order to find the corresponding delete link for one particular entry in the example, it is necessary to define a scope in which the link can be found. With Webrat this can be achieved with the within() method.

within "#entry_2" do |scope|
  scope.click_link "Delete"
end

Within() does only support the following selectors which have been defined in W3C CSS level 1.

  • Type selectors, e.g. “table”
  • Class selectors, e.g. “tr.odd”
  • ID selectors, e.g. “#content”

The nesting of within() calls is currently not supported which means that well chosen IDs in the HTML code are necessary for good tests.

Then

In order to evaluate the response of the example after clicking the delete link of the second entry, the following statement would be sufficient, but in case the test did not work as expected it would return an error message which includes the content of the whole web page.

response.body.should_not contain("Entry 2")

Narrowing the part of the HTML code to the CSS selector which should be evaluated helps to get a more readable error message, since it only includes the selected part. In opposite to the the within() method have_selector() and have_tag() can be used nested, too.  If a selector, e.g. in case of an error, is not available on a web page, an error message for the current scope is generated. This could be the whole page if the selector was the first one. Therefore it is recommended to start with a selector which is available even if an error occurs.

response.body.should have_selector("#content") do |content|
  content.should have_tag("tbody") do |table|
    table.should_not contain("Entry 2")
  end
end

Using selectors in order to match elements on a web page has the disadvantage of a bigger dependency to the HTML code. Changing the name of a CSS selector can break test case. But this disadvantage should be insignificant compared to the achievement of error messages in which the actual problem is easily identifiable.

UPDATE: Due to a bug in Webrat should_not() does currently not work in a have_selector() block. The bug ticket contains a workaround for this issue.

Popularity: 8% [?]

Written by tkadauke

November 12th, 2009 at 3:01 pm