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