jarib-celerity 0.0.5

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 (69) hide show
  1. data/History.txt +42 -0
  2. data/License.txt +621 -0
  3. data/README.txt +64 -0
  4. data/Rakefile +12 -0
  5. data/lib/celerity.rb +59 -0
  6. data/lib/celerity/browser.rb +410 -0
  7. data/lib/celerity/clickable_element.rb +26 -0
  8. data/lib/celerity/collections.rb +148 -0
  9. data/lib/celerity/container.rb +488 -0
  10. data/lib/celerity/default_viewer.rb +10 -0
  11. data/lib/celerity/disabled_element.rb +27 -0
  12. data/lib/celerity/element.rb +241 -0
  13. data/lib/celerity/element_collections.rb +68 -0
  14. data/lib/celerity/element_locator.rb +167 -0
  15. data/lib/celerity/elements/button.rb +34 -0
  16. data/lib/celerity/elements/file_field.rb +17 -0
  17. data/lib/celerity/elements/form.rb +16 -0
  18. data/lib/celerity/elements/frame.rb +53 -0
  19. data/lib/celerity/elements/image.rb +57 -0
  20. data/lib/celerity/elements/label.rb +9 -0
  21. data/lib/celerity/elements/link.rb +12 -0
  22. data/lib/celerity/elements/meta.rb +6 -0
  23. data/lib/celerity/elements/non_control_elements.rb +93 -0
  24. data/lib/celerity/elements/option.rb +18 -0
  25. data/lib/celerity/elements/radio_check.rb +85 -0
  26. data/lib/celerity/elements/select_list.rb +81 -0
  27. data/lib/celerity/elements/table.rb +117 -0
  28. data/lib/celerity/elements/table_cell.rb +28 -0
  29. data/lib/celerity/elements/table_elements.rb +41 -0
  30. data/lib/celerity/elements/table_row.rb +36 -0
  31. data/lib/celerity/elements/text_field.rb +127 -0
  32. data/lib/celerity/exception.rb +40 -0
  33. data/lib/celerity/extra/method_generator.rb +158 -0
  34. data/lib/celerity/htmlunit.rb +41 -0
  35. data/lib/celerity/htmlunit/commons-codec-1.3.jar +0 -0
  36. data/lib/celerity/htmlunit/commons-collections-3.2.1.jar +0 -0
  37. data/lib/celerity/htmlunit/commons-httpclient-3.1.jar +0 -0
  38. data/lib/celerity/htmlunit/commons-io-1.4.jar +0 -0
  39. data/lib/celerity/htmlunit/commons-lang-2.4.jar +0 -0
  40. data/lib/celerity/htmlunit/commons-logging-1.1.1.jar +0 -0
  41. data/lib/celerity/htmlunit/cssparser-0.9.5.jar +0 -0
  42. data/lib/celerity/htmlunit/htmlunit-2.4-SNAPSHOT.jar +0 -0
  43. data/lib/celerity/htmlunit/htmlunit-core-js-2.4-SNAPSHOT.jar +0 -0
  44. data/lib/celerity/htmlunit/nekohtml-1.9.10-20081209.100757-4.jar +0 -0
  45. data/lib/celerity/htmlunit/sac-1.3.jar +0 -0
  46. data/lib/celerity/htmlunit/serializer-2.7.1.jar +0 -0
  47. data/lib/celerity/htmlunit/xalan-2.7.1.jar +0 -0
  48. data/lib/celerity/htmlunit/xercesImpl-2.8.1.jar +0 -0
  49. data/lib/celerity/htmlunit/xml-apis-1.3.04.jar +0 -0
  50. data/lib/celerity/identifier.rb +11 -0
  51. data/lib/celerity/input_element.rb +25 -0
  52. data/lib/celerity/listener.rb +106 -0
  53. data/lib/celerity/resources/no_viewer.png +0 -0
  54. data/lib/celerity/util.rb +79 -0
  55. data/lib/celerity/version.rb +9 -0
  56. data/lib/celerity/watir_compatibility.rb +85 -0
  57. data/tasks/benchmark.rake +4 -0
  58. data/tasks/deployment.rake +43 -0
  59. data/tasks/environment.rake +7 -0
  60. data/tasks/fix.rake +25 -0
  61. data/tasks/jar.rake +57 -0
  62. data/tasks/rdoc.rake +4 -0
  63. data/tasks/rspec.rake +30 -0
  64. data/tasks/simple_ci.rake +94 -0
  65. data/tasks/snapshot.rake +26 -0
  66. data/tasks/specserver.rake +21 -0
  67. data/tasks/website.rake +17 -0
  68. data/tasks/yard.rake +5 -0
  69. metadata +129 -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
@@ -0,0 +1,27 @@
1
+ module Celerity
2
+ # Module mixed in to all elements that can have the 'disabled' attribute.
3
+ module DisabledElement
4
+ include Celerity::Exception
5
+
6
+ # Returns false if the element is disabled.
7
+ def enabled?
8
+ !disabled?
9
+ end
10
+
11
+ # Returns true if the element is disabled.
12
+ def disabled?
13
+ assert_exists unless defined?(@object) && @object
14
+ @object.isDisabled
15
+ end
16
+ alias_method :disabled, :disabled?
17
+
18
+ # Used internally.
19
+ # @api private
20
+ def assert_enabled
21
+ if disabled?
22
+ raise ObjectDisabledException, "Object #{identifier_string} is disabled"
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,241 @@
1
+ module Celerity
2
+
3
+ # Superclass for all HTML elements.
4
+ class Element
5
+ include Exception
6
+ include Container
7
+
8
+ attr_reader :container, :object
9
+
10
+ # number of spaces that separate the property from the value in the create_string method
11
+ TO_S_SIZE = 14
12
+
13
+ # HTML 4.01 Transitional DTD
14
+ HTML_401_TRANSITIONAL = {
15
+ :core => [:class, :id, :style, :title],
16
+ :cell_halign => [:align, :char, :charoff],
17
+ :cell_valign => [:valign],
18
+ :i18n => [:dir, :lang],
19
+ :event => [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover,
20
+ :onmousemove, :onmouseout, :onkeypress, :onkeydown, :onkeyup],
21
+ :sloppy => [:name, :value]
22
+ }
23
+
24
+ CELLHALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_halign]
25
+ CELLVALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_valign]
26
+ BASE_ATTRIBUTES = HTML_401_TRANSITIONAL.values_at(:core, :i18n, :event, :sloppy).flatten
27
+ ATTRIBUTES = BASE_ATTRIBUTES
28
+ TAGS = []
29
+
30
+ DEFAULT_HOW = nil
31
+
32
+ # @api private
33
+ def initialize(container, *args)
34
+ self.container = container
35
+
36
+ case args.size
37
+ when 2
38
+ @conditions = { args[0] => args[1] }
39
+ when 1
40
+ if args.first.is_a? Hash
41
+ @conditions = args.first
42
+ elsif (how = self.class::DEFAULT_HOW)
43
+ @conditions = { how => args.first }
44
+ else
45
+ raise ArgumentError, "wrong number of arguments (1 for 2)"
46
+ end
47
+ else
48
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
49
+ end
50
+
51
+ @conditions.freeze
52
+ end
53
+
54
+ # Get the parent element
55
+ # @return [Celerity::Element, nil] subclass of Celerity::Element, or nil if no parent was found
56
+ def parent
57
+ assert_exists
58
+
59
+ obj = @object.parentNode
60
+ until element_class = Celerity::Util.htmlunit2celerity(obj.class)
61
+ return nil if obj.nil?
62
+ obj = obj.parentNode
63
+ end
64
+
65
+ element_class.new(@container, :object, obj)
66
+ end
67
+
68
+ # Sets the focus to this element.
69
+ def focus
70
+ assert_exists
71
+ @object.focus
72
+ end
73
+
74
+ # Used internally. Find the element on the page.
75
+ # @api private
76
+ def locate
77
+ @object = ElementLocator.new(@container, self.class).find_by_conditions(@conditions)
78
+ end
79
+
80
+ # @return [String] A string representation of the element.
81
+ def to_s
82
+ assert_exists
83
+ create_string(@object)
84
+ end
85
+
86
+ # @param [String, #to_s] The attribute.
87
+ # @return [String] The value of the given attribute.
88
+ def attribute_value(attribute)
89
+ assert_exists
90
+ @object.getAttribute(attribute.to_s)
91
+ end
92
+
93
+ # Check if the element is visible to the user or not.
94
+ # Note that this only takes the _style attribute_ of the element (and
95
+ # its parents) into account - styles from applied CSS is not considered.
96
+ #
97
+ # @return [boolean]
98
+ def visible?
99
+ obj = self
100
+ while obj
101
+ return false if obj.respond_to?(:type) && obj.type == 'hidden'
102
+ return false if obj.style =~ /display\s*:\s*none|visibility\s*:\s*hidden/
103
+ obj = obj.parent
104
+ end
105
+
106
+ return true
107
+ end
108
+
109
+ # Used internally to ensure the element actually exists.
110
+ #
111
+ # @raise [Celerity::Exception::UnknownObjectException] if the element can't be found.
112
+ # @api private
113
+ def assert_exists
114
+ locate
115
+ unless @object
116
+ raise UnknownObjectException, "Unable to locate #{self.class.name[/::(.*)$/, 1]}, using #{identifier_string}"
117
+ end
118
+ end
119
+
120
+ # Checks if the element exists.
121
+ # @return [true, false]
122
+ def exists?
123
+ assert_exists
124
+ true
125
+ rescue UnknownObjectException, UnknownFrameException
126
+ false
127
+ end
128
+ alias_method :exist?, :exists?
129
+
130
+ # Return a text representation of the element as it would appear in a browser.
131
+ #
132
+ # @see inner_text
133
+ # @return [String]
134
+ def text
135
+ assert_exists
136
+ @object.asText.strip # this must behave like ElementLocator
137
+ end
138
+
139
+ # Return the text content of this DOM node, disregarding its visibility.
140
+ #
141
+ # (Celerity-specific?)
142
+ #
143
+ # @see text
144
+ # @return [String]
145
+ def inner_text
146
+ assert_exists
147
+ Celerity::Util.normalize_text @object.getTextContent
148
+ end
149
+
150
+ # @return [String] The normative XML representation of the element (including children).
151
+ def to_xml
152
+ assert_exists
153
+ @object.asXml
154
+ end
155
+ alias_method :asXml, :to_xml
156
+ alias_method :as_xml, :to_xml
157
+ alias_method :html, :to_xml
158
+
159
+ # @return [String] A string representation of the element's attributes.
160
+ def attribute_string
161
+ assert_exists
162
+
163
+ result = ''
164
+ @object.getAttributes.each do |attribute|
165
+ result << %Q{#{attribute.getName}="#{attribute.getHtmlValue.to_s}"}
166
+ end
167
+
168
+ result
169
+ end
170
+
171
+ # return the canonical xpath for this element (Celerity-specific)
172
+ def xpath
173
+ assert_exists
174
+ @object.getCanonicalXPath
175
+ end
176
+
177
+ # Dynamically get element attributes.
178
+ #
179
+ # @see ATTRIBUTES constant for a list of valid methods for a given element.
180
+ #
181
+ # @return [String] The resulting attribute.
182
+ # @raise [NoMethodError] if the element doesn't support this attribute.
183
+ def method_missing(meth, *args, &blk)
184
+ assert_exists
185
+
186
+ meth = selector_to_attribute(meth)
187
+
188
+ if self.class::ATTRIBUTES.include?(meth)
189
+ return @object.getAttributeValue(meth.to_s)
190
+ end
191
+
192
+ Log.warn "Element\#method_missing calling super with #{meth.inspect}"
193
+ super
194
+ end
195
+
196
+ def respond_to?(meth, include_private = false)
197
+ meth = selector_to_attribute(meth)
198
+ return true if self.class::ATTRIBUTES.include?(meth)
199
+ super
200
+ end
201
+
202
+ private
203
+
204
+ def create_string(element)
205
+ ret = []
206
+
207
+ unless (tag = element.getTagName).empty?
208
+ ret << "tag:".ljust(TO_S_SIZE) + tag
209
+ end
210
+
211
+ element.getAttributes.each do |attribute|
212
+ ret << " #{attribute.getName}:".ljust(TO_S_SIZE+2) + attribute.getHtmlValue.to_s
213
+ end
214
+
215
+ unless (text = element.asText).empty?
216
+ ret << " text:".ljust(TO_S_SIZE+2) + element.asText
217
+ end
218
+
219
+ ret.join("\n")
220
+ end
221
+
222
+ def identifier_string
223
+ if @conditions.size == 1
224
+ how, what = @conditions.to_a.first
225
+ "#{how.inspect} and #{what.inspect}"
226
+ else
227
+ @conditions.inspect
228
+ end
229
+ end
230
+
231
+ def selector_to_attribute(meth)
232
+ case meth
233
+ when :class_name then :class
234
+ when :caption then :value
235
+ when :url then :href
236
+ else meth
237
+ end
238
+ end
239
+
240
+ end # Element
241
+ end # Celerity
@@ -0,0 +1,68 @@
1
+ module Celerity
2
+ # This class is the superclass for the iterator classes (Buttons, Links, Spans etc.)
3
+ # It would normally only be accessed by the iterator methods (Browser#spans, Browser#links, ...).
4
+ class ElementCollections
5
+ include Enumerable
6
+
7
+ # @api private
8
+ def initialize(container, how = nil, what = nil)
9
+ @container = container
10
+ @object = (how == :object ? what : nil)
11
+ @length = length
12
+ end
13
+
14
+ # @return [Fixnum] The number of elements in this collection.
15
+ def length
16
+ if @object
17
+ @object.length
18
+ else
19
+ @elements ||= ElementLocator.new(@container, element_class).elements_by_idents
20
+ @elements.size
21
+ end
22
+ end
23
+ alias_method :size, :length
24
+
25
+ # @yieldparam [Celerity::Element] element Iterate through the elements in this collection.
26
+ #
27
+ def each
28
+ if @elements
29
+ @elements.each { |e| yield(element_class.new(@container, :object, e)) }
30
+ else
31
+ 0.upto(@length - 1) { |i| yield iterator_object(i) }
32
+ end
33
+
34
+ @length
35
+ end
36
+
37
+ # Get the element at the given index.
38
+ # This is 1-indexed to keep compatibility with Watir - subject to change.
39
+ # Also note that because of Watir's lazy loading, this will return an Element
40
+ # instance even if the index is out of bounds.
41
+ #
42
+ # @param [Fixnum] n Index of wanted element, 1-indexed.
43
+ # @return [Celerity::Element] Returns a subclass of Celerity::Element
44
+ def [](n)
45
+ if @elements && @elements[n - INDEX_OFFSET]
46
+ element_class.new(@container, :object, @elements[n - INDEX_OFFSET])
47
+ else
48
+ iterator_object(n - INDEX_OFFSET)
49
+ end
50
+ end
51
+
52
+ # Note: This can be quite useful in irb:
53
+ #
54
+ # puts browser.text_fields
55
+ #
56
+ # @return [String] A string representation of all elements in this collection.
57
+ def to_s
58
+ map { |e| e.to_s }.join("\n")
59
+ end
60
+
61
+ private
62
+
63
+ def iterator_object(i)
64
+ element_class.new(@container, :index, i+1)
65
+ end
66
+
67
+ end # ElementCollections
68
+ end # Celerity
@@ -0,0 +1,167 @@
1
+ module Celerity
2
+
3
+ # Used internally to locate elements on the page.
4
+ class ElementLocator
5
+ include Celerity::Exception
6
+ attr_accessor :idents
7
+
8
+
9
+ def initialize(container, element_class)
10
+ container.assert_exists
11
+
12
+ @container = container
13
+ @object = container.object
14
+ @element_class = element_class
15
+ @attributes = @element_class::ATTRIBUTES # could check for 'strict' here?
16
+ @idents = @element_class::TAGS
17
+ @tags = @idents.map { |e| e.tag.downcase }
18
+ end
19
+
20
+ def find_by_conditions(conditions) # TODO: refactor without performance hit
21
+ return nil unless @object # probably means we're on a TextPage (content-type is "text/plain")
22
+
23
+ @condition_idents = []
24
+ attributes = Hash.new { |h, k| h[k] = [] }
25
+ index = 0 # by default, return the first matching element
26
+ text = nil
27
+
28
+ conditions.each do |how, what|
29
+ case how
30
+ when :object
31
+ unless what.is_a?(HtmlUnit::Html::HtmlElement) || what.nil?
32
+ raise ArgumentError, "expected an HtmlUnit::Html::HtmlElement subclass, got #{what.inspect}:#{what.class}"
33
+ end
34
+ return what
35
+ when :id
36
+ return find_by_id(what)
37
+ when :xpath
38
+ return find_by_xpath(what)
39
+ when :label
40
+ return find_by_label(what)
41
+ when :class_name
42
+ how = :class
43
+ when :url
44
+ how = :href
45
+ when :caption
46
+ how = :text
47
+ end
48
+
49
+ if @attributes.include?(how = how.to_sym)
50
+ attributes[how] << what
51
+ elsif how == :index
52
+ index = what.to_i - INDEX_OFFSET
53
+ elsif how == :text
54
+ text = what
55
+ else
56
+ raise MissingWayOfFindingObjectException, "No how #{how.inspect}"
57
+ end
58
+ end
59
+
60
+ @idents.each do |ident|
61
+ merged = attributes.merge(ident.attributes) { |key, v1, v2| v1 | v2 }
62
+ id = Identifier.new(ident.tag, merged)
63
+ id.text = ident.text || text # «original» identifier takes precedence for :text
64
+ @condition_idents << id
65
+ end
66
+
67
+ if index == 0
68
+ element_by_idents(@condition_idents)
69
+ else
70
+ elements_by_idents(@condition_idents)[index]
71
+ end
72
+
73
+ rescue HtmlUnit::ElementNotFoundException
74
+ nil # for rcov
75
+ end
76
+
77
+ def find_by_id(what)
78
+ case what
79
+ when Regexp
80
+ elements_by_tag_names.find { |elem| elem.getIdAttribute =~ what }
81
+ when String
82
+ obj = @object.getHtmlElementById(what)
83
+ return obj if @tags.include?(obj.getTagName)
84
+
85
+ $stderr.puts "warning: multiple elements with identical id? (#{what.inspect})" if $VERBOSE
86
+ elements_by_tag_names.find { |elem| elem.getIdAttribute == what }
87
+ else
88
+ raise TypeError, "expected String or Regexp, got #{what.inspect}:#{what.class}"
89
+ end
90
+ end
91
+
92
+ def find_by_xpath(what)
93
+ what = ".#{what}" if what[0].chr == "/"
94
+ @object.getByXPath(what).to_a.first
95
+ end
96
+
97
+ def find_by_label(what)
98
+ obj = elements_by_tag_names(%w[label]).find { |e| matches?(e.asText, what) }
99
+
100
+ return nil unless obj && (ref = obj.getReferencedElement)
101
+ return ref if @tags.include?(ref.getTagName)
102
+
103
+ find_by_id obj.getForAttribute
104
+ end
105
+
106
+ def elements_by_idents(idents = nil)
107
+ get_by_idents(:select, idents || @idents)
108
+ end
109
+
110
+ def element_by_idents(idents = nil)
111
+ get_by_idents(:find, idents || @idents)
112
+ end
113
+
114
+ private
115
+
116
+ def get_by_idents(meth, idents)
117
+ with_nullpointer_retry do
118
+ @object.getAllHtmlChildElements.send(meth) do |e|
119
+ next unless @tags.include?(e.getTagName)
120
+ idents.any? { |id| element_matches_ident?(e, id) }
121
+ end
122
+ end
123
+ end
124
+
125
+ def element_matches_ident?(element, ident)
126
+ return false unless ident.tag == element.getTagName
127
+
128
+ attr_result = ident.attributes.all? do |key, values|
129
+ values.any? { |val| matches?(element.getAttributeValue(key.to_s), val) }
130
+ end
131
+
132
+ if ident.text
133
+ attr_result && matches?(element.asText.strip, ident.text)
134
+ else
135
+ attr_result
136
+ end
137
+ end
138
+
139
+ def elements_by_tag_names(tags = @tags)
140
+ with_nullpointer_retry do
141
+ # HtmlUnit's getHtmlElementsByTagNames won't get elements in the correct
142
+ # order (making :index fail), so we're using getAllHtmlChildElements instead.
143
+ @object.getAllHtmlChildElements.select do |elem|
144
+ tags.include?(elem.getTagName)
145
+ end
146
+ end
147
+ end
148
+
149
+ # HtmlUnit throws NPEs sometimes when we're locating elements
150
+ # Retry seems to work fine.
151
+ def with_nullpointer_retry(max_retries = 3)
152
+ tries = 0
153
+ yield
154
+ rescue java.lang.NullPointerException => e
155
+ raise e if tries >= max_retries
156
+
157
+ tries += 1
158
+ $stderr.puts "warning: celerity caught #{e} - retry ##{tries}"
159
+ retry
160
+ end
161
+
162
+ def matches?(string, what)
163
+ Regexp === what ? string.strip =~ what : string == what.to_s
164
+ end
165
+
166
+ end # ElementLocator
167
+ end # Celerity