celerity 0.0.1 → 0.0.2

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 (93) hide show
  1. data/History.txt +10 -0
  2. data/README.txt +8 -11
  3. data/Rakefile +5 -3
  4. data/benchmark/bm_2000_spans.rb +48 -0
  5. data/benchmark/bm_digg.rb +26 -0
  6. data/benchmark/bm_google_images.rb +36 -0
  7. data/benchmark/bm_input_locator.rb +69 -0
  8. data/benchmark/loader.rb +9 -0
  9. data/lib/celerity.rb +3 -1
  10. data/lib/celerity/container.rb +23 -171
  11. data/lib/celerity/disabled_element.rb +1 -1
  12. data/lib/celerity/element.rb +78 -47
  13. data/lib/celerity/element_collections.rb +16 -32
  14. data/lib/celerity/element_locator.rb +135 -0
  15. data/lib/celerity/elements/button.rb +15 -0
  16. data/lib/celerity/elements/file_field.rb +1 -1
  17. data/lib/celerity/elements/form.rb +2 -1
  18. data/lib/celerity/elements/frame.rb +18 -21
  19. data/lib/celerity/elements/image.rb +2 -8
  20. data/lib/celerity/elements/label.rb +1 -3
  21. data/lib/celerity/elements/link.rb +1 -1
  22. data/lib/celerity/elements/option.rb +16 -0
  23. data/lib/celerity/elements/radio_check.rb +18 -7
  24. data/lib/celerity/elements/select_list.rb +1 -17
  25. data/lib/celerity/elements/table.rb +4 -4
  26. data/lib/celerity/elements/table_body.rb +6 -8
  27. data/lib/celerity/elements/table_cell.rb +3 -14
  28. data/lib/celerity/elements/table_row.rb +4 -10
  29. data/lib/celerity/elements/text_field.rb +16 -4
  30. data/lib/celerity/extra/method_generator.rb +144 -0
  31. data/lib/celerity/identifier.rb +10 -0
  32. data/lib/celerity/ie.rb +28 -13
  33. data/lib/celerity/input_element.rb +0 -4
  34. data/lib/celerity/non_control_elements.rb +12 -12
  35. data/lib/celerity/version.rb +1 -1
  36. data/spec/area_spec.rb +41 -41
  37. data/spec/areas_spec.rb +11 -11
  38. data/spec/button_spec.rb +73 -68
  39. data/spec/buttons_spec.rb +10 -10
  40. data/spec/checkbox_spec.rb +102 -96
  41. data/spec/checkboxes_spec.rb +10 -10
  42. data/spec/div_spec.rb +78 -73
  43. data/spec/divs_spec.rb +10 -10
  44. data/spec/element_spec.rb +20 -11
  45. data/spec/filefield_spec.rb +36 -41
  46. data/spec/filefields_spec.rb +10 -10
  47. data/spec/form_spec.rb +29 -29
  48. data/spec/forms_spec.rb +11 -11
  49. data/spec/frame_spec.rb +54 -49
  50. data/spec/hidden_spec.rb +43 -43
  51. data/spec/hiddens_spec.rb +10 -10
  52. data/spec/html/2000_spans.html +2009 -0
  53. data/spec/html/forms_with_input_elements.html +15 -9
  54. data/spec/html/non_control_elements.html +4 -2
  55. data/spec/ie_spec.rb +82 -48
  56. data/spec/image_spec.rb +83 -100
  57. data/spec/images_spec.rb +10 -10
  58. data/spec/label_spec.rb +29 -29
  59. data/spec/labels_spec.rb +10 -10
  60. data/spec/li_spec.rb +41 -41
  61. data/spec/link_spec.rb +65 -59
  62. data/spec/links_spec.rb +11 -11
  63. data/spec/lis_spec.rb +10 -10
  64. data/spec/map_spec.rb +30 -30
  65. data/spec/maps_spec.rb +10 -10
  66. data/spec/p_spec.rb +49 -49
  67. data/spec/pre_spec.rb +41 -41
  68. data/spec/pres_spec.rb +10 -10
  69. data/spec/ps_spec.rb +10 -10
  70. data/spec/radio_spec.rb +104 -97
  71. data/spec/radios_spec.rb +11 -11
  72. data/spec/select_list_spec.rb +118 -106
  73. data/spec/select_lists_spec.rb +15 -15
  74. data/spec/span_spec.rb +54 -54
  75. data/spec/spans_spec.rb +11 -11
  76. data/spec/spec.opts +1 -1
  77. data/spec/spec_helper.rb +23 -3
  78. data/spec/table_bodies.rb +8 -8
  79. data/spec/table_bodies_spec.rb +9 -9
  80. data/spec/table_body_spec.rb +28 -27
  81. data/spec/table_cell_spec.rb +25 -25
  82. data/spec/table_cells_spec.rb +16 -16
  83. data/spec/table_row_spec.rb +16 -16
  84. data/spec/table_rows_spec.rb +12 -12
  85. data/spec/table_spec.rb +36 -36
  86. data/spec/tables_spec.rb +12 -12
  87. data/spec/text_field_spec.rb +111 -92
  88. data/spec/text_fields_spec.rb +13 -13
  89. data/tasks/benchmark.rake +3 -0
  90. data/tasks/rspec.rake +2 -2
  91. data/tasks/testserver.rake +15 -0
  92. metadata +58 -46
  93. data/tasks/simple_ci.rake +0 -94
@@ -5,7 +5,7 @@ module Celerity
5
5
  end
6
6
 
7
7
  def disabled?
8
- assert_exists
8
+ assert_exists unless @object
9
9
  @object.isDisabled
10
10
  end
11
11
  alias_method :disabled, :disabled?
@@ -4,38 +4,43 @@ module Celerity
4
4
  include Container
5
5
  attr_accessor :container, :object
6
6
 
7
+ # number of spaces that separate the property from the value in the create_string method
8
+ TO_S_SIZE = 14
9
+
7
10
  # HTML 4.01 Transitional DTD
8
- CELLHALIGN_ATTRIBUTES = [:align, :char, :charoff]
9
- CELLVALIGN_ATTRIBUTES = [:valign]
10
- CORE_ATTRIBUTES = [:class, :id, :style, :title] # Not valid in base, head, html, meta, param, script, style, and title elements.
11
- I18N_ATTRIBUTES = [:dir, :lang] # Not valid in base, br, frame, frameset, hr, iframe, param, and script elements.
12
- EVENT_ATTRIBUTES = [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover, :onmousemove, :onmouseout, :onkeypress, :onkeydown, :onkeyup]
13
- SLOPPY_ATTRIBUTES = [:name, :value]
14
- BASE_ATTRIBUTES = CORE_ATTRIBUTES | I18N_ATTRIBUTES | EVENT_ATTRIBUTES | SLOPPY_ATTRIBUTES
15
- ATTRIBUTES = BASE_ATTRIBUTES
11
+ HTML_401_TRANSITIONAL = {
12
+ :core => [:class, :id, :style, :title], # Not valid in base, head, html, meta, param, script, style, and title elements.
13
+ :cell_halign => [:align, :char, :charoff],
14
+ :cell_valign => [:valign],
15
+ :i18n => [:dir, :lang], # Not valid in base, br, frame, frameset, hr, iframe, param, and script elements.
16
+ :event => [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover, :onmousemove, :onmouseout, :onkeypress, :onkeydown, :onkeyup],
17
+ :sloppy => [:name, :value]
18
+ }
19
+
20
+ CELLHALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_halign]
21
+ CELLVALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_valign]
22
+ BASE_ATTRIBUTES = HTML_401_TRANSITIONAL.values_at(:core, :i18n, :event, :sloppy).flatten
23
+ ATTRIBUTES = BASE_ATTRIBUTES
16
24
 
17
- class Identifier < Struct.new(:tag, :attributes)
18
- def initialize(t, a={}); super(t, a); end
19
- end
20
-
21
25
  def initialize(container, *args)
22
26
  set_container container
23
- process_arguments(*args)
24
- end
25
-
26
- def process_arguments(*args)
27
- if args.size == 1 #&& !args.first.kind_of?(Hash) #(support multiple attributes)
28
- raise ArgumentError, "wrong number of arguments (1 for 2), DEFAULT_HOW not defined" unless defined? self.class::DEFAULT_HOW
29
- raise NotImplementedError if args[0].class == Hash
30
- @how = self.class::DEFAULT_HOW
31
- @what = args.shift
32
- else
33
- @how, @what = *args
27
+
28
+ case args.size
29
+ when 2
30
+ @conditions = {args[0] => args[1]}
31
+ when 1
32
+ if Hash === args.first
33
+ @conditions = args.first
34
+ elsif defined?(self.class::DEFAULT_HOW)
35
+ @conditions = {self.class::DEFAULT_HOW => args.first}
36
+ else
37
+ raise ArgumentError, "wrong number of arguments (1 for 2)"
38
+ end
34
39
  end
35
40
  end
36
41
 
37
42
  def locate
38
- @object = @container.locate_tagged_element(self, @how, @what)
43
+ @object ||= ElementLocator.new(@container.object, self.class).find_by_conditions(@conditions)
39
44
  end
40
45
 
41
46
  def to_s
@@ -43,30 +48,15 @@ module Celerity
43
48
  create_string(@object)
44
49
  end
45
50
 
46
- # number of spaces that separate the property from the value in the create_string method
47
- TO_S_SIZE = 14
48
-
49
- def create_string(element)
50
- n = []
51
- n << "tag:".ljust(TO_S_SIZE) + element.getTagName if element.getTagName.length > 0
52
- iterator = element.getAttributeEntriesIterator
53
- while (iterator.hasNext)
54
- attribute = iterator.next
55
- n << " #{attribute.getName}:".ljust(TO_S_SIZE+2) + attribute.getHtmlValue.to_s
56
- end
57
- n << " text:".ljust(TO_S_SIZE+2) + element.asText if element.asText.length > 0
58
- return n.join("\n")
59
- end
60
-
61
51
  def attribute_value(attribute)
62
52
  assert_exists
63
53
  @object.getAttribute(attribute)
64
54
  end
65
55
 
66
56
  def assert_exists
67
- locate if defined?(locate)
57
+ locate
68
58
  unless @object
69
- raise UnknownObjectException, "Unable to locate object, using #{@how.inspect} and #{@what.inspect}"
59
+ raise UnknownObjectException, "Unable to locate object, using #{identifier_string}"
70
60
  end
71
61
  end
72
62
 
@@ -74,7 +64,7 @@ module Celerity
74
64
  begin
75
65
  assert_exists
76
66
  true
77
- rescue UnknownObjectException
67
+ rescue UnknownObjectException, UnknownFrameException
78
68
  false
79
69
  end
80
70
  end
@@ -88,6 +78,18 @@ module Celerity
88
78
  alias_method :innerText, :text
89
79
  alias_method :inner_text, :text
90
80
 
81
+ def contains_text(expected_text)
82
+ assert_exists
83
+ case expected_text
84
+ when Regexp
85
+ text().match(expected_text)
86
+ when String
87
+ text().index(expected_text)
88
+ else
89
+ raise ArgumentError, "Argument #{expected_text.inspect} should be a String or Regexp."
90
+ end
91
+ end
92
+
91
93
  def to_xml
92
94
  assert_exists
93
95
  @object.asXml
@@ -98,17 +100,46 @@ module Celerity
98
100
 
99
101
  # used to get attributes
100
102
  def method_missing(meth, *args, &blk)
101
- assert_exists
102
- meth = :class if meth == :class_name
103
- meth = :value if meth == :caption
104
-
103
+ case meth
104
+ when :class_name
105
+ meth = :class
106
+ when :caption
107
+ meth = :value
108
+ when :url
109
+ meth = :href
110
+ end
111
+
105
112
  if self.class::ATTRIBUTES.include?(meth)
113
+ assert_exists
106
114
  @object.getAttributeValue(meth.to_s)
107
115
  else
108
- Log.info "Element\#method_missing calling super with #{meth.inspect}"
116
+ Log.warn "Element\#method_missing calling super with #{meth.inspect}"
109
117
  super
110
118
  end
111
119
  end
120
+
121
+ private
122
+
123
+ def create_string(element)
124
+ n = []
125
+ n << "tag:".ljust(TO_S_SIZE) + element.getTagName unless element.getTagName.empty?
126
+ iterator = element.getAttributeEntriesIterator
127
+ while (iterator.hasNext)
128
+ attribute = iterator.next
129
+ n << " #{attribute.getName}:".ljust(TO_S_SIZE+2) + attribute.getHtmlValue.to_s
130
+ end
131
+ n << " text:".ljust(TO_S_SIZE+2) + element.asText unless element.asText.empty?
132
+ return n.join("\n")
133
+ end
134
+
135
+ def identifier_string
136
+ if @conditions.size == 1
137
+ how, what = @conditions.shift
138
+ "#{how.inspect} and #{what.inspect}"
139
+ else
140
+ @conditions.inspect
141
+ end
142
+ end
112
143
 
113
144
  end # Element
114
145
  end # Celerity
@@ -4,6 +4,12 @@ module Celerity
4
4
  class ElementCollections
5
5
  include Enumerable
6
6
 
7
+ def initialize(container, how = nil, what = nil)
8
+ @container = container
9
+ @object = (how == :object ? what : nil)
10
+ @length = length # defined by subclasses
11
+ end
12
+
7
13
  def element_tags
8
14
  element_class::TAGS
9
15
  end
@@ -11,54 +17,32 @@ module Celerity
11
17
  def length
12
18
  if @object
13
19
  @object.length
14
- elsif Element::Identifier === element_tags.first
15
- idents = element_tags
16
- tags = idents.map { |e| e.tag }
17
- tags.map! { |t| t.downcase }
18
- elements = @container.object.getAllHtmlChildElements.iterator.to_a.select do |elem|
19
- tags.include?(elem.getTagName)
20
- end
21
- elements = elements.select do |e|
22
- idents.any? do |ident|
23
- next unless ident.tag == e.getTagName
24
- if ident.attributes.empty?
25
- true
26
- else
27
- ident.attributes.any? { |key, value| value.include?(e.getAttributeValue(key.to_s)) }
28
- end
29
- end
30
- end
31
- return elements.length
32
20
  else
33
- length = 0
34
- element_tags.each { |element_tag| length += @container.object.getHtmlElementsByTagName(element_tag).toArray.size }
35
- return length
21
+ @elements = ElementLocator.new(@container.object, element_class).elements_by_idents
22
+ @elements.size
36
23
  end
37
24
  end
38
25
  alias_method :size, :length
39
26
 
40
- def initialize(container, how = nil, what = nil)
41
- @container = container
42
- if how == :object
43
- @object = what
44
- end
45
- @length = length # defined by subclasses
46
- end
47
-
48
27
  def each
49
- 0.upto(@length-1) { |i| yield iterator_object(i) }
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
50
33
  end
51
34
 
52
35
  def [](n)
53
- return iterator_object(n-1)
36
+ @elements ? element_class.new(@container, :object, @elements[n - 1]) : iterator_object(n-1)
54
37
  end
55
38
 
56
39
  def to_s
57
40
  return self.collect { |i| i.to_s }.join("\n")
58
41
  end
59
42
 
60
- # this method creates an object of the correct type that the iterators use
61
43
  private
44
+
45
+ # this method creates an object of the correct type that the iterators use
62
46
  def iterator_object(i)
63
47
  element_class.new(@container, :index, i+1)
64
48
  end
@@ -0,0 +1,135 @@
1
+ module Celerity
2
+
3
+ # What we essentially want is the ability to perform a fast SQLish 'select' against the DOM.
4
+ # Instead of iterating twice through a lot of elements, we could modify the Identifier objects to contain
5
+ # everything in our 'query'.
6
+ # Jari - 2008-05-11
7
+ class ElementLocator
8
+ attr_accessor :idents
9
+
10
+
11
+ def initialize(object, element_class)
12
+ @object = object
13
+ @element_class = element_class
14
+ @attributes = @element_class::ATTRIBUTES # could check for 'strict' here?
15
+ @idents = @element_class::TAGS
16
+ @tags = @idents.map { |e| e.tag.downcase }
17
+ end
18
+
19
+ def find_by_conditions(conditions)
20
+ @condition_idents = []
21
+ attributes = Hash.new { |h, k| h[k] = [] }
22
+ index = 0 # by default, return the first matching element
23
+ text = nil
24
+
25
+ begin
26
+ conditions.each do |how, what|
27
+ case how
28
+ when :object
29
+ return what
30
+ when :id
31
+ return find_by_id(what)
32
+ when :xpath
33
+ return find_by_xpath(what)
34
+ when :class_name
35
+ how = :class
36
+ when :url
37
+ how = :href
38
+ when :caption
39
+ how = :text
40
+ end
41
+
42
+ if @attributes.include?(how)
43
+ attributes[how] << what
44
+ elsif how == :index
45
+ index = what.to_i - 1
46
+ elsif how == :text
47
+ text = what
48
+ else
49
+ raise MissingWayOfFindingObjectException, "No how #{how.inspect}"
50
+ end
51
+
52
+ end
53
+
54
+ @idents.each do |ident|
55
+ merged = attributes.merge(ident.attributes) do |key, v1, v2|
56
+ attributes[key] = v1 | v2
57
+ end
58
+
59
+ id = Identifier.new(ident.tag, merged)
60
+ # «original» identifier takes precedence for :text
61
+ id.text = ident.text || text
62
+ @condition_idents << id
63
+ end
64
+
65
+ if index == 0
66
+ element_by_idents(@condition_idents)
67
+ else
68
+ elements_by_idents(@condition_idents)[index]
69
+ end
70
+
71
+ rescue HtmlUnit::ElementNotFoundException
72
+ nil # for rcov
73
+ end
74
+ end
75
+
76
+ def find_by_id(what)
77
+ case what
78
+ when Regexp
79
+ elements_by_tag_names.find { |elem| elem.getIdAttribute =~ what }
80
+ when String
81
+ @object.getHtmlElementById(what)
82
+ else
83
+ raise ArgumentError, "Argument #{what.inspect} should be a String or Regexp"
84
+ end
85
+ end
86
+
87
+ def find_by_xpath(what)
88
+ what = ".#{what}" if what[0] == ?/
89
+ @object.getByXPath(what).to_a.first
90
+ end
91
+
92
+ def elements_by_idents(idents = nil)
93
+ get_by_idents(:select, idents || @idents)
94
+ end
95
+
96
+ def element_by_idents(idents = nil)
97
+ get_by_idents(:find, idents || @idents)
98
+ end
99
+
100
+ private
101
+
102
+ def get_by_idents(meth, idents)
103
+ @object.getAllHtmlChildElements.iterator.to_a.send(meth) do |e|
104
+ if @tags.include?(e.getTagName)
105
+ idents.any? do |ident|
106
+ next unless ident.tag == e.getTagName
107
+ attr_result = ident.attributes.all? do |key, value|
108
+ value.any? { |val| matches?(e.getAttributeValue(key.to_s), val) }
109
+ end
110
+
111
+ if ident.text
112
+ attr_result && matches?(e.asText, ident.text)
113
+ else
114
+ attr_result
115
+ end
116
+
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def matches?(string, what)
123
+ Regexp === what ? string.match(what) : string == what.to_s
124
+ end
125
+
126
+ def elements_by_tag_names
127
+ # HtmlUnit's getHtmlElementsByTagNames won't get elements in the correct order, making :index fail
128
+ @object.getAllHtmlChildElements.iterator.to_a.select do |elem|
129
+ @tags.include?(elem.getTagName)
130
+ end
131
+ end
132
+
133
+
134
+ end # ElementLocator
135
+ end # Celerity
@@ -8,6 +8,21 @@ module Celerity
8
8
  TAGS = [ Identifier.new('button'),
9
9
  Identifier.new('input', :type => %w(submit reset image button)) ]
10
10
  DEFAULT_HOW = :value
11
+
12
+ def locate
13
+ # ugly..
14
+ if (val = @conditions.delete(:value))
15
+ locator = ElementLocator.new(@container.object, self.class)
16
+ button_ident = Identifier.new('button')
17
+ button_ident.text = val
18
+ input_ident = Identifier.new('input', :type => %w(submit reset image button), :value => [val])
19
+ locator.idents = [button_ident, input_ident]
20
+ @object = locator.find_by_conditions(@conditions)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
11
26
  end
12
27
 
13
28
  end
@@ -1,4 +1,3 @@
1
-
2
1
  module Celerity
3
2
  # For fields that accept file uploads
4
3
  class FileField < InputElement
@@ -6,6 +5,7 @@ module Celerity
6
5
 
7
6
  def set(value)
8
7
  assert_exists
8
+ # TODO: trigger events?
9
9
  @object.setValueAttribute(value.to_s)
10
10
  end
11
11
  end
@@ -1,7 +1,7 @@
1
1
  module Celerity
2
2
  class Form < Element
3
3
  include Container
4
- TAGS = ['form']
4
+ TAGS = [Identifier.new('form')]
5
5
  # HTML 4.01 Transitional DTD
6
6
  ATTRIBUTES = BASE_ATTRIBUTES | [:action, :method, :enctype, :accept, :name, :onsubmit, :onreset, :target, :'accept-charset']
7
7
  DEFAULT_HOW = :name
@@ -9,6 +9,7 @@ module Celerity
9
9
  def submit
10
10
  assert_exists
11
11
  raise NotImplementedError
12
+ # waiting for HtmlUnit fix?
12
13
  end
13
14
 
14
15
  end