celerity 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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