cheezy/page-object — pull request #103 by robkid (2012-06-19 21:45:08+00:00)
Title: Implementation of indexed_property for accessing sets of related fields

In our application, we have a number of forms that have a table-like construction where each row represents a logical object, but may have a significant number of different form elements to interact with (mainly text inputs and buttons). Since there an arbitrary number of rows of elements, the original design of the page used an array index style approach to developing ids (e.g. `table[].fieldName`). I've developed an extension for page_object that mimics this logical structure, allowing the user to define a property on the page object that can be indexed in the same way. While in our main use cases, the pages organize the elements in a table, this is not required by the indexed_property code; the only requirement is that there is a single substitution that can be performed given an index to generate the ids of the elements.

An example is given in the test feature on the branch, but in general, you define a property on your page object that looks like:

```ruby
class MyPage
include PageObject

indexed_property(:my_table, [
[:text_field, :field_1, :id => 'my_table[%s].field_1'],
[:button, :button_1, :id => 'my_table[%s].button_1'],
[:text_field, :field_2, :name => 'my_table[%s].field_2']
])
end
```

This will result in a method named my_table defined on the class MyPage which will return an object that only responds to the [] method. When the [] method is invoked, the parameter is used to perform string substitution on the "what" values in the definition of the property, and another object is returned that behaves like a page object with those identifiers defined. To continue the example, `my_page.my_table['foo']` would return an object that would behave similarly to a page object defined like:

```ruby
class MyPageRow
include PageObject

text_field(:field_1, :id => 'my_table[foo].field_1')
button(:button_1, :id => 'my_table[foo].button_1')
text_field(:field_2, :name => 'my_table[foo].field_2')
end
```

This has made it significantly easier for us to define a page object for one of these dynamic pages that does not care what objects will actually be displayed, but allow the tests to specify the indices dynamically, and thus the what/how pairs dynamically.

The bulk of the logic exists in the two classes that are returned by the main property and the [] method. I put them in a new submodule, PageObject::IndexedProperties. I will confess that I'm not a Ruby guru, and I had to fight a bit to create something that behaves like a normal page object but is part of the page object module itself (and thus can't include PageObject with the exact same behavior an external class would, since PageObject is not fully initialized yet, or at least that's my understanding of the situation). This implementation works, but may not be the cleanest way to define the classes. I also extracted part of the normal PageObject initializer so I could reuse the browser initialization parts in my new classes.

Please let me know if you have any questions or suggestions. Thanks for your consideration!