Scoping

Most of Rsel's actions and verifications accept an optional hash of scoping keywords, allowing you to be specific about what area of the page to operate on. This page describes how to use them.

within

Say you have a page of contact information with HTML that looks something like this:

<div id="work">
  <label for="work_phone">Phone number</label>
  <input id="work_phone" type="text" />
</div>

<div id="home">
  <label for="home_phone">Phone number</label>
  <input id="home_phone" type="text" />
</div>

Notice that both text inputs are labeled "Phone number", which means that if you try to do this in your Rsel table:

| Type | 123-456-7890 | into | Phone number | field |

Or this with Ruby:

type_into_field "123-456-7890", "Phone number"

Rsel will just fill in the first matching field that it finds (in this case, the "work" phone number). If you needed to fill in the "home" phone number, you'd have to refer to it by something less ambiguous, such as its id:

| Type | 123-456-7890 | into | home_phone | field |

In this case, the id is short and intelligible, so it's not such a bad thing, but it's fairly common for the id to be autogenerated, in which case it might have some ridiculous-looking string like form_customer_contact_field_home_phone_0, and you'd like to avoid that.

This is where scoping comes in. Scoping in Rsel allows you to be more specific about which "Phone number" field you want to type into, by specifying the id of its container:

| Type | 111-222-3333 | into | Phone number | field | !{within:work} |
| Type | 111-222-4444 | into | Phone number | field | !{within:home} |

With plain Ruby:

type_into_field "111-222-3333", "Phone number", :within => "work"
type_into_field "111-222-4444", "Phone number", :within => "home"

Yeah I know, we're still using an id which could be annoyingly long, but if there are a lot of fields in each container, you only need to keep track of one id instead of several.

in_row

Another kind of scoping that can be useful is in_row, which selects only the elements that are in the same table row as a given piece of text. For example, a common approach to editing or deleting items in a list is to place links next to those items in an HTML table:

<table>
  <tr><th>Name</th>   <th>Actions</th></tr>
  <tr><td>Eric</td>   <td><a href="/edit/eric">Edit</a></td></tr>
  <tr><td>Marcus</td> <td><a href="/edit/marcus">Edit</a></td></tr>
  <tr><td>Ken</td>    <td><a href="/edit/ken">Edit</a></td></tr>
</table>

Here, we have three "Edit" links. Clicking "Edit" without any scoping qualifier is ambiguous, and would simply use the first match. If you want to click the "Edit" link in a specific row, include an in_row scope with some text that is unique to that row:

| Click | Edit | link | !{in_row:Marcus} |

In Ruby:

click_link "Edit", :in_row => "Marcus"

This works for any method that accepts scoping qualifiers, so you can use it to operate on checkboxes, dropdowns, or text fields as well.

Caveat

With FitNesse, when using a scoping hash, your tables must not be fully escaped. This just means you should not include a ! at the beginning of your table, otherwise the hash syntax will be interpreted literally, instead of as an embedded hash.

This means you should manually escape any cells in your table that contain URLs, email addresses, or other auto-interpreted text. For example, email addresses normally have HTML added by FitNesse; you'll have to escape those so they'll be treated as literal text:

| Click | [email protected]! | link | !{within:footer} |

The only exception to this is the URL argument provided on the first row of the table; even if FitNesse adds HTML markup to this, the SeleniumTest constructor is smart enough to ignore it, so you can still do this:

| script | selenium test | http://my.site.com | !{port:4445} |

Another important thing to note is that due to the way FitNesse Slim script tables are evaluated, the scoping hash must be added after a cell that contains part of the method name. Cells in these tables must contain alternating (function, argument) chunks. To illustrate, consider the phone number example:

| Type | 111-222-3333 | into | Phone number | field | !{within:work} |

The cells in this row alternate between function and argument, with the first cell being the beginning of the function name, and every alternating cell after that being a continuation of the function name. Picture it like this:

| Type |              | into |              | field |                |
|      | 111-222-3333 |      | Phone number |       | !{within:work} |

That is, this row calls the type_into_field method with three arguments (111-222-3333, Phone number, !{within:work}). If you're using a function call row that does not end with a function-name component, you need to include a semicolon after the last function-name component, so all remaining cells will be treated as arguments. These are valid alternative ways of calling the same function:

| Type | 111-222-3333 | into field; | Phone number | !{within:work} |
| Type into field; | 111-222-3333 | Phone number | !{within:work} |

The SeleniumTest method names were, for the most part, crafted so that the alternating (function, argument) form reads the most naturally.

Additional scoping qualifiers may be added to Rsel if they prove useful. If you have a use case that isn't covered by the existing scopes, please submit an issue, or better yet, implement it yourself and submit a pull request. See Development for more info.

Next: Studying