celerity 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. data/History.txt +33 -0
  2. data/README.txt +19 -9
  3. data/Rakefile +9 -3
  4. data/lib/celerity.rb +39 -39
  5. data/lib/celerity/browser.rb +538 -153
  6. data/lib/celerity/clickable_element.rb +48 -7
  7. data/lib/celerity/collections.rb +155 -131
  8. data/lib/celerity/container.rb +766 -385
  9. data/lib/celerity/default_viewer.rb +10 -0
  10. data/lib/celerity/disabled_element.rb +19 -2
  11. data/lib/celerity/element.rb +152 -83
  12. data/lib/celerity/element_collection.rb +106 -0
  13. data/lib/celerity/element_locator.rb +89 -66
  14. data/lib/celerity/elements/button.rb +23 -13
  15. data/lib/celerity/elements/file_field.rb +17 -5
  16. data/lib/celerity/elements/form.rb +21 -16
  17. data/lib/celerity/elements/frame.rb +75 -53
  18. data/lib/celerity/elements/image.rb +76 -63
  19. data/lib/celerity/elements/label.rb +4 -2
  20. data/lib/celerity/elements/link.rb +30 -18
  21. data/lib/celerity/elements/meta.rb +6 -0
  22. data/lib/celerity/{non_control_elements.rb → elements/non_control_elements.rb} +106 -76
  23. data/lib/celerity/elements/option.rb +16 -2
  24. data/lib/celerity/elements/radio_check.rb +55 -20
  25. data/lib/celerity/elements/select_list.rb +65 -29
  26. data/lib/celerity/elements/table.rb +141 -94
  27. data/lib/celerity/elements/table_cell.rb +13 -6
  28. data/lib/celerity/elements/{table_body.rb → table_elements.rb} +20 -8
  29. data/lib/celerity/elements/table_row.rb +23 -7
  30. data/lib/celerity/elements/text_field.rb +89 -33
  31. data/lib/celerity/exception.rb +77 -41
  32. data/lib/celerity/extra/method_generator.rb +42 -24
  33. data/lib/celerity/htmlunit.rb +49 -0
  34. data/lib/celerity/htmlunit/commons-collections-3.2.1.jar +0 -0
  35. data/lib/celerity/htmlunit/htmlunit-2.5-SNAPSHOT.jar +0 -0
  36. data/lib/celerity/htmlunit/htmlunit-core-js-2.5-SNAPSHOT.jar +0 -0
  37. data/lib/celerity/htmlunit/nekohtml-1.9.12-20090308.130127-11.jar +0 -0
  38. data/lib/celerity/htmlunit/serializer-2.7.1.jar +0 -0
  39. data/lib/celerity/htmlunit/xalan-2.7.1.jar +0 -0
  40. data/lib/celerity/htmlunit/xml-apis-1.3.04.jar +0 -0
  41. data/lib/celerity/identifier.rb +3 -2
  42. data/lib/celerity/input_element.rb +5 -5
  43. data/lib/celerity/listener.rb +135 -0
  44. data/lib/celerity/resources/no_viewer.png +0 -0
  45. data/lib/celerity/util.rb +88 -0
  46. data/lib/celerity/version.rb +4 -3
  47. data/lib/celerity/watir_compatibility.rb +35 -25
  48. data/tasks/jar.rake +57 -0
  49. metadata +35 -142
  50. data.tar.gz.sig +0 -0
  51. data/Manifest.txt +0 -150
  52. data/lib/celerity/element_collections.rb +0 -54
  53. data/lib/celerity/element_map.rb +0 -51
  54. data/lib/celerity/elements/table_footer.rb +0 -30
  55. data/lib/celerity/elements/table_header.rb +0 -30
  56. data/lib/celerity/htmlunit/commons-collections-3.2.jar +0 -0
  57. data/lib/celerity/htmlunit/download.sh +0 -23
  58. data/lib/celerity/htmlunit/htmlunit-2.2.jar +0 -0
  59. data/lib/celerity/htmlunit/htmlunit-core-js-2.2.jar +0 -0
  60. data/lib/celerity/htmlunit/nekohtml-1.9.8.jar +0 -0
  61. data/lib/celerity/htmlunit/xalan-2.7.0.jar +0 -0
  62. data/lib/celerity/htmlunit/xml-apis-1.0.b2.jar +0 -0
  63. data/spec/area_spec.rb +0 -97
  64. data/spec/areas_spec.rb +0 -40
  65. data/spec/browser_spec.rb +0 -266
  66. data/spec/button_spec.rb +0 -227
  67. data/spec/buttons_spec.rb +0 -39
  68. data/spec/checkbox_spec.rb +0 -302
  69. data/spec/checkboxes_spec.rb +0 -38
  70. data/spec/div_spec.rb +0 -207
  71. data/spec/divs_spec.rb +0 -39
  72. data/spec/element_spec.rb +0 -79
  73. data/spec/filefield_spec.rb +0 -123
  74. data/spec/filefields_spec.rb +0 -40
  75. data/spec/form_spec.rb +0 -59
  76. data/spec/forms_spec.rb +0 -41
  77. data/spec/frame_spec.rb +0 -121
  78. data/spec/frames_spec.rb +0 -71
  79. data/spec/hidden_spec.rb +0 -127
  80. data/spec/hiddens_spec.rb +0 -39
  81. data/spec/hn_spec.rb +0 -104
  82. data/spec/hns_spec.rb +0 -45
  83. data/spec/html/2000_spans.html +0 -2009
  84. data/spec/html/bug_duplicate_attributes.html +0 -14
  85. data/spec/html/bug_javascript_001.html +0 -11
  86. data/spec/html/form_js_bug.html +0 -11
  87. data/spec/html/forms_with_input_elements.html +0 -114
  88. data/spec/html/frame_1.html +0 -17
  89. data/spec/html/frame_2.html +0 -16
  90. data/spec/html/frames.html +0 -11
  91. data/spec/html/iframes.html +0 -12
  92. data/spec/html/images.html +0 -27
  93. data/spec/html/images/1.gif +0 -0
  94. data/spec/html/images/2.gif +0 -0
  95. data/spec/html/images/3.gif +0 -0
  96. data/spec/html/images/button.jpg +0 -0
  97. data/spec/html/images/circle.jpg +0 -0
  98. data/spec/html/images/map.gif +0 -0
  99. data/spec/html/images/map2.gif +0 -0
  100. data/spec/html/images/minus.gif +0 -0
  101. data/spec/html/images/originaltriangle.jpg +0 -0
  102. data/spec/html/images/plus.gif +0 -0
  103. data/spec/html/images/square.jpg +0 -0
  104. data/spec/html/images/triangle.jpg +0 -0
  105. data/spec/html/invalid_js.html +0 -11
  106. data/spec/html/latin1_text.html +0 -17
  107. data/spec/html/non_control_elements.html +0 -115
  108. data/spec/html/simple_ajax.html +0 -22
  109. data/spec/html/tables.html +0 -119
  110. data/spec/html/utf8_text.html +0 -15
  111. data/spec/htmlunit_spec.rb +0 -26
  112. data/spec/image_spec.rb +0 -220
  113. data/spec/images_spec.rb +0 -39
  114. data/spec/label_spec.rb +0 -79
  115. data/spec/labels_spec.rb +0 -40
  116. data/spec/li_spec.rb +0 -139
  117. data/spec/link_spec.rb +0 -189
  118. data/spec/links_spec.rb +0 -43
  119. data/spec/lis_spec.rb +0 -40
  120. data/spec/map_spec.rb +0 -102
  121. data/spec/maps_spec.rb +0 -40
  122. data/spec/meta_spec.rb +0 -8
  123. data/spec/ol_spec.rb +0 -87
  124. data/spec/ols_spec.rb +0 -40
  125. data/spec/option_spec.rb +0 -154
  126. data/spec/p_spec.rb +0 -171
  127. data/spec/pre_spec.rb +0 -135
  128. data/spec/pres_spec.rb +0 -40
  129. data/spec/ps_spec.rb +0 -40
  130. data/spec/radio_spec.rb +0 -299
  131. data/spec/radios_spec.rb +0 -42
  132. data/spec/select_list_spec.rb +0 -299
  133. data/spec/select_lists_spec.rb +0 -47
  134. data/spec/span_spec.rb +0 -184
  135. data/spec/spans_spec.rb +0 -64
  136. data/spec/spec.opts +0 -1
  137. data/spec/spec_helper.rb +0 -55
  138. data/spec/table_bodies_spec.rb +0 -57
  139. data/spec/table_body_spec.rb +0 -111
  140. data/spec/table_cell_spec.rb +0 -74
  141. data/spec/table_cells_spec.rb +0 -59
  142. data/spec/table_footer_spec.rb +0 -101
  143. data/spec/table_footers_spec.rb +0 -55
  144. data/spec/table_header_spec.rb +0 -101
  145. data/spec/table_headers_spec.rb +0 -55
  146. data/spec/table_row_spec.rb +0 -104
  147. data/spec/table_rows_spec.rb +0 -58
  148. data/spec/table_spec.rb +0 -160
  149. data/spec/tables_spec.rb +0 -42
  150. data/spec/text_field_spec.rb +0 -323
  151. data/spec/text_fields_spec.rb +0 -44
  152. data/spec/ul_spec.rb +0 -88
  153. data/spec/uls_spec.rb +0 -40
  154. data/spec/watir_compatibility_spec.rb +0 -260
  155. data/support/spec_server.rb +0 -73
  156. data/tasks/rspec.rake +0 -30
  157. data/tasks/specserver.rake +0 -21
  158. metadata.gz.sig +0 -0
@@ -0,0 +1,10 @@
1
+ module Celerity
2
+ class DefaultViewer
3
+ IMAGE = "#{Celerity::DIR}/resources/no_viewer.png"
4
+
5
+ def self.save(path = nil)
6
+ return unless path
7
+ FileUtils.copy(IMAGE, path)
8
+ end
9
+ end
10
+ end
@@ -1,23 +1,40 @@
1
1
  module Celerity
2
- # Module mixed in to all elements that can have the 'disabled' attribute.
2
+
3
+ #
4
+ # Mixed in to all elements that can have the 'disabled' attribute.
5
+ #
6
+
3
7
  module DisabledElement
4
8
  include Celerity::Exception
5
9
 
10
+ #
11
+ # Returns false if the element is disabled.
12
+ #
13
+
6
14
  def enabled?
7
15
  !disabled?
8
16
  end
9
17
 
18
+ #
19
+ # Returns true if the element is disabled.
20
+ #
21
+
10
22
  def disabled?
11
23
  assert_exists unless defined?(@object) && @object
12
24
  @object.isDisabled
13
25
  end
14
26
  alias_method :disabled, :disabled?
15
27
 
28
+ #
29
+ # Used internally.
30
+ # @api private
31
+ #
32
+
16
33
  def assert_enabled
17
34
  if disabled?
18
35
  raise ObjectDisabledException, "Object #{identifier_string} is disabled"
19
36
  end
20
37
  end
21
-
38
+
22
39
  end
23
40
  end
@@ -1,105 +1,151 @@
1
1
  module Celerity
2
2
 
3
+ #
3
4
  # Superclass for all HTML elements.
5
+ #
6
+
4
7
  class Element
5
8
  include Exception
6
9
  include Container
7
-
10
+
8
11
  attr_reader :container, :object
9
-
12
+
10
13
  # number of spaces that separate the property from the value in the create_string method
11
14
  TO_S_SIZE = 14
12
-
15
+
13
16
  # HTML 4.01 Transitional DTD
14
- HTML_401_TRANSITIONAL = {
17
+ HTML_401_TRANSITIONAL = {
15
18
  :core => [:class, :id, :style, :title],
16
19
  :cell_halign => [:align, :char, :charoff],
17
20
  :cell_valign => [:valign],
18
21
  :i18n => [:dir, :lang],
19
- :event => [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover,
22
+ :event => [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover,
20
23
  :onmousemove, :onmouseout, :onkeypress, :onkeydown, :onkeyup],
21
24
  :sloppy => [:name, :value]
22
25
  }
23
-
26
+
24
27
  CELLHALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_halign]
25
28
  CELLVALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_valign]
26
29
  BASE_ATTRIBUTES = HTML_401_TRANSITIONAL.values_at(:core, :i18n, :event, :sloppy).flatten
27
30
  ATTRIBUTES = BASE_ATTRIBUTES
31
+ TAGS = []
28
32
 
29
33
  DEFAULT_HOW = nil
30
-
31
- # @api internal
34
+
35
+ # @api private
32
36
  def initialize(container, *args)
33
37
  self.container = container
34
-
38
+
35
39
  case args.size
36
40
  when 2
37
41
  @conditions = { args[0] => args[1] }
38
42
  when 1
39
43
  if args.first.is_a? Hash
40
44
  @conditions = args.first
41
- elsif self.class::DEFAULT_HOW
42
- @conditions = { self.class::DEFAULT_HOW => args.first }
45
+ elsif (how = self.class::DEFAULT_HOW)
46
+ @conditions = { how => args.first }
43
47
  else
44
48
  raise ArgumentError, "wrong number of arguments (1 for 2)"
45
49
  end
46
50
  else
47
51
  raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
48
52
  end
53
+
54
+ @conditions.freeze
49
55
  end
50
-
51
- # Get the parent element (experimental, avoid if possible)
52
- # @return [Celerity::Element] subclass
56
+
57
+ #
58
+ # Get the parent element
59
+ # @return [Celerity::Element, nil] subclass of Celerity::Element, or nil if no parent was found
60
+ #
61
+
53
62
  def parent
54
63
  assert_exists
55
-
56
- obj = @object.getParentNode
57
- until element_class = HtmlUnit2CelerityElement[obj.class]
64
+
65
+ obj = @object.parentNode
66
+ until element_class = Celerity::Util.htmlunit2celerity(obj.class)
58
67
  return nil if obj.nil?
59
68
  obj = obj.parentNode
60
69
  end
61
-
70
+
62
71
  element_class.new(@container, :object, obj)
63
72
  end
64
-
73
+
74
+ #
65
75
  # Sets the focus to this element.
76
+ #
77
+
66
78
  def focus
67
79
  assert_exists
68
80
  @object.focus
69
81
  end
70
-
71
- # Locates the element
72
- # @api internal
82
+
83
+ #
84
+ # Used internally. Find the element on the page.
85
+ # @api private
86
+ #
87
+
73
88
  def locate
74
- @object = ElementLocator.new(@container.object, self.class).find_by_conditions(@conditions)
89
+ @object = ElementLocator.new(@container, self.class).find_by_conditions(@conditions)
75
90
  end
76
-
91
+
92
+ #
77
93
  # @return [String] A string representation of the element.
94
+ #
95
+
78
96
  def to_s
79
97
  assert_exists
80
98
  create_string(@object)
81
99
  end
82
-
100
+
101
+ #
83
102
  # @param [String, #to_s] The attribute.
84
103
  # @return [String] The value of the given attribute.
104
+ #
105
+
85
106
  def attribute_value(attribute)
86
107
  assert_exists
87
108
  @object.getAttribute(attribute.to_s)
88
109
  end
89
110
 
90
- # Locates the element.
111
+ #
112
+ # Check if the element is visible to the user or not.
113
+ # Note that this only takes the _style attribute_ of the element (and
114
+ # its parents) into account - styles from applied CSS is not considered.
115
+ #
116
+ # @return [boolean]
117
+ #
118
+
119
+ def visible?
120
+ obj = self
121
+ while obj
122
+ return false if obj.respond_to?(:type) && obj.type == 'hidden'
123
+ return false if obj.style =~ /display\s*:\s*none|visibility\s*:\s*hidden/
124
+ obj = obj.parent
125
+ end
126
+
127
+ return true
128
+ end
129
+
130
+ #
131
+ # Used internally to ensure the element actually exists.
132
+ #
133
+ # @raise [Celerity::Exception::UnknownObjectException] if the element can't be found.
134
+ # @api private
91
135
  #
92
- # @raise UnknownObjectException
93
- # @api internal
136
+
94
137
  def assert_exists
95
138
  locate
96
139
  unless @object
97
- raise UnknownObjectException, "Unable to locate object, using #{identifier_string}"
140
+ raise UnknownObjectException, "Unable to locate #{self.class.name[/::(.*)$/, 1]}, using #{identifier_string}"
98
141
  end
99
142
  end
100
-
143
+
144
+ #
101
145
  # Checks if the element exists.
102
146
  # @return [true, false]
147
+ #
148
+
103
149
  def exists?
104
150
  assert_exists
105
151
  true
@@ -107,39 +153,37 @@ module Celerity
107
153
  false
108
154
  end
109
155
  alias_method :exist?, :exists?
110
- alias_method :exists, :exists?
111
-
112
- # Return a text representation of the element.
156
+
157
+ #
158
+ # Return a text representation of the element as it would appear in a browser.
159
+ #
160
+ # @see inner_text
113
161
  # @return [String]
162
+ #
163
+
114
164
  def text
115
165
  assert_exists
116
-
117
- # this could work, but breaks some tests atm
118
- # @object.getTextContent.strip
119
-
120
- @object.asText.strip
166
+ @object.asText.strip # this must behave like ElementLocator
121
167
  end
122
- alias_method :innerText, :text
123
- alias_method :inner_text, :text
124
168
 
125
- # Check if the element contains the given text.
126
169
  #
127
- # @param [String, Regexp] expected_text The text to look for.
128
- # @return [Fixnum, nil] The index of the matched text, or nil if it doesn't match.
129
- def contains_text(expected_text)
170
+ # Return the text content of this DOM node, disregarding its visibility.
171
+ #
172
+ # (Celerity-specific?)
173
+ #
174
+ # @see text
175
+ # @return [String]
176
+ #
177
+
178
+ def inner_text
130
179
  assert_exists
131
-
132
- case expected_text
133
- when Regexp
134
- text() =~ expected_text
135
- when String
136
- text().index(expected_text)
137
- else
138
- raise ArgumentError, "Argument #{expected_text.inspect} should be a String or Regexp."
139
- end
180
+ Celerity::Util.normalize_text @object.getTextContent
140
181
  end
141
182
 
183
+ #
142
184
  # @return [String] The normative XML representation of the element (including children).
185
+ #
186
+
143
187
  def to_xml
144
188
  assert_exists
145
189
  @object.asXml
@@ -147,35 +191,57 @@ module Celerity
147
191
  alias_method :asXml, :to_xml
148
192
  alias_method :as_xml, :to_xml
149
193
  alias_method :html, :to_xml
150
-
194
+
195
+ #
151
196
  # @return [String] A string representation of the element's attributes.
197
+ #
198
+
152
199
  def attribute_string
153
200
  assert_exists
154
-
201
+
155
202
  result = ''
156
- iterator = @object.getAttributeEntriesIterator
157
-
158
- while iterator.hasNext
159
- attribute = iterator.next
160
- result << "#{attribute.getName}=\"#{attribute.getHtmlValue.to_s}\" "
203
+ @object.getAttributes.each do |attribute|
204
+ result << %Q{#{attribute.getName}="#{attribute.getHtmlValue}"}
161
205
  end
162
-
206
+
163
207
  result
164
208
  end
165
209
 
210
+ #
211
+ # return the canonical xpath for this element (Celerity-specific)
212
+ #
213
+
214
+ def xpath
215
+ assert_exists
216
+ @object.getCanonicalXPath
217
+ end
218
+
219
+ #
166
220
  # Dynamically get element attributes.
221
+ #
222
+ # @see ATTRIBUTES constant for a list of valid methods for a given element.
223
+ #
167
224
  # @return [String] The resulting attribute.
168
- # @raise NoMethodError If the element doesn't have this attribute.
225
+ # @raise [NoMethodError] if the element doesn't support this attribute.
226
+ #
227
+
169
228
  def method_missing(meth, *args, &blk)
229
+ assert_exists
230
+
170
231
  meth = selector_to_attribute(meth)
171
-
232
+
172
233
  if self.class::ATTRIBUTES.include?(meth)
173
- assert_exists
174
- @object.getAttributeValue(meth.to_s)
175
- else
176
- Log.warn "Element\#method_missing calling super with #{meth.inspect}"
177
- super
234
+ return @object.getAttributeValue(meth.to_s)
178
235
  end
236
+
237
+ Log.warn "Element\#method_missing calling super with #{meth.inspect}"
238
+ super
239
+ end
240
+
241
+ def methods(*args)
242
+ ms = super
243
+ ms += self.class::ATTRIBUTES.map { |e| e.to_s }
244
+ ms.sort
179
245
  end
180
246
 
181
247
  def respond_to?(meth, include_private = false)
@@ -185,39 +251,42 @@ module Celerity
185
251
  end
186
252
 
187
253
  private
188
-
254
+
189
255
  def create_string(element)
190
256
  ret = []
191
- ret << "tag:".ljust(TO_S_SIZE) + element.getTagName unless element.getTagName.empty?
192
-
193
- iterator = element.getAttributeEntriesIterator
194
- while iterator.hasNext
195
- attribute = iterator.next
257
+
258
+ unless (tag = element.getTagName).empty?
259
+ ret << "tag:".ljust(TO_S_SIZE) + tag
260
+ end
261
+
262
+ element.getAttributes.each do |attribute|
196
263
  ret << " #{attribute.getName}:".ljust(TO_S_SIZE+2) + attribute.getHtmlValue.to_s
197
264
  end
198
-
199
- ret << " text:".ljust(TO_S_SIZE+2) + element.asText unless element.asText.empty?
200
-
265
+
266
+ unless (text = element.asText).empty?
267
+ ret << " text:".ljust(TO_S_SIZE+2) + element.asText
268
+ end
269
+
201
270
  ret.join("\n")
202
271
  end
203
-
272
+
204
273
  def identifier_string
205
274
  if @conditions.size == 1
206
- how, what = @conditions.shift
275
+ how, what = @conditions.to_a.first
207
276
  "#{how.inspect} and #{what.inspect}"
208
277
  else
209
278
  @conditions.inspect
210
279
  end
211
280
  end
212
-
281
+
213
282
  def selector_to_attribute(meth)
214
283
  case meth
215
284
  when :class_name then :class
216
- when :caption then :value
217
- when :url then :href
285
+ when :caption then :value
286
+ when :url then :href
218
287
  else meth
219
288
  end
220
289
  end
221
-
290
+
222
291
  end # Element
223
292
  end # Celerity
@@ -0,0 +1,106 @@
1
+ module Celerity
2
+
3
+ #
4
+ # This class is the superclass for the iterator classes (Buttons, Links, Spans etc.)
5
+ # It would normally only be accessed by the iterator methods (Browser#spans, Browser#links, ...).
6
+ #
7
+
8
+ class ElementCollection
9
+ include Enumerable
10
+
11
+ #
12
+ # @api private
13
+ #
14
+
15
+ def initialize(container, how = nil, what = nil)
16
+ @container = container
17
+ @object = (how == :object ? what : nil)
18
+ @length = length
19
+ end
20
+
21
+ #
22
+ # @return [Fixnum] The number of elements in this collection.
23
+ #
24
+
25
+ def length
26
+ if @object
27
+ @object.length
28
+ else
29
+ @elements ||= ElementLocator.new(@container, element_class).elements_by_idents
30
+ @elements.size
31
+ end
32
+ end
33
+ alias_method :size, :length
34
+
35
+ #
36
+ # @yieldparam [Celerity::Element] element Iterate through the elements in this collection.
37
+ #
38
+
39
+ def each
40
+ if @elements
41
+ @elements.each { |e| yield(element_class.new(@container, :object, e)) }
42
+ else
43
+ 0.upto(@length - 1) { |i| yield iterator_object(i) }
44
+ end
45
+
46
+ @length
47
+ end
48
+
49
+ #
50
+ # Get the element at the given index.
51
+ # This is 1-indexed to keep compatibility with Watir - subject to change.
52
+ # Also note that because of Watir's lazy loading, this will return an Element
53
+ # instance even if the index is out of bounds.
54
+ #
55
+ # @param [Fixnum] n Index of wanted element, 1-indexed.
56
+ # @return [Celerity::Element] Returns a subclass of Celerity::Element
57
+ #
58
+
59
+ def [](n)
60
+ if @elements && @elements[n - INDEX_OFFSET]
61
+ element_class.new(@container, :object, @elements[n - INDEX_OFFSET])
62
+ else
63
+ iterator_object(n - INDEX_OFFSET)
64
+ end
65
+ end
66
+
67
+ #
68
+ # Get the first element in this collection. (Celerity-specific)
69
+ #
70
+ # @return [Celerity::Element] Returns a subclass of Celerity::Element
71
+ #
72
+
73
+ def first
74
+ self[INDEX_OFFSET]
75
+ end
76
+
77
+ #
78
+ # Get the last element in this collection. (Celerity-specific)
79
+ #
80
+ # @return [Celerity::Element] Returns a subclass of Celerity::Element
81
+ #
82
+
83
+ def last
84
+ self[INDEX_OFFSET - 1]
85
+ end
86
+
87
+ #
88
+ # Note: This can be quite useful in irb:
89
+ #
90
+ # puts browser.text_fields
91
+ #
92
+ # @return [String] A string representation of all elements in this collection.
93
+ #
94
+
95
+ def to_s
96
+ map { |e| e.to_s }.join("\n")
97
+ end
98
+
99
+ private
100
+
101
+ def iterator_object(i)
102
+ element_class.new(@container, :index, i+1)
103
+ end
104
+
105
+ end # ElementCollection
106
+ end # Celerity