celerity 0.0.1 → 0.0.2

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