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.
- data/History.txt +10 -0
- data/README.txt +8 -11
- data/Rakefile +5 -3
- data/benchmark/bm_2000_spans.rb +48 -0
- data/benchmark/bm_digg.rb +26 -0
- data/benchmark/bm_google_images.rb +36 -0
- data/benchmark/bm_input_locator.rb +69 -0
- data/benchmark/loader.rb +9 -0
- data/lib/celerity.rb +3 -1
- data/lib/celerity/container.rb +23 -171
- data/lib/celerity/disabled_element.rb +1 -1
- data/lib/celerity/element.rb +78 -47
- data/lib/celerity/element_collections.rb +16 -32
- data/lib/celerity/element_locator.rb +135 -0
- data/lib/celerity/elements/button.rb +15 -0
- data/lib/celerity/elements/file_field.rb +1 -1
- data/lib/celerity/elements/form.rb +2 -1
- data/lib/celerity/elements/frame.rb +18 -21
- data/lib/celerity/elements/image.rb +2 -8
- data/lib/celerity/elements/label.rb +1 -3
- data/lib/celerity/elements/link.rb +1 -1
- data/lib/celerity/elements/option.rb +16 -0
- data/lib/celerity/elements/radio_check.rb +18 -7
- data/lib/celerity/elements/select_list.rb +1 -17
- data/lib/celerity/elements/table.rb +4 -4
- data/lib/celerity/elements/table_body.rb +6 -8
- data/lib/celerity/elements/table_cell.rb +3 -14
- data/lib/celerity/elements/table_row.rb +4 -10
- data/lib/celerity/elements/text_field.rb +16 -4
- data/lib/celerity/extra/method_generator.rb +144 -0
- data/lib/celerity/identifier.rb +10 -0
- data/lib/celerity/ie.rb +28 -13
- data/lib/celerity/input_element.rb +0 -4
- data/lib/celerity/non_control_elements.rb +12 -12
- data/lib/celerity/version.rb +1 -1
- data/spec/area_spec.rb +41 -41
- data/spec/areas_spec.rb +11 -11
- data/spec/button_spec.rb +73 -68
- data/spec/buttons_spec.rb +10 -10
- data/spec/checkbox_spec.rb +102 -96
- data/spec/checkboxes_spec.rb +10 -10
- data/spec/div_spec.rb +78 -73
- data/spec/divs_spec.rb +10 -10
- data/spec/element_spec.rb +20 -11
- data/spec/filefield_spec.rb +36 -41
- data/spec/filefields_spec.rb +10 -10
- data/spec/form_spec.rb +29 -29
- data/spec/forms_spec.rb +11 -11
- data/spec/frame_spec.rb +54 -49
- data/spec/hidden_spec.rb +43 -43
- data/spec/hiddens_spec.rb +10 -10
- data/spec/html/2000_spans.html +2009 -0
- data/spec/html/forms_with_input_elements.html +15 -9
- data/spec/html/non_control_elements.html +4 -2
- data/spec/ie_spec.rb +82 -48
- data/spec/image_spec.rb +83 -100
- data/spec/images_spec.rb +10 -10
- data/spec/label_spec.rb +29 -29
- data/spec/labels_spec.rb +10 -10
- data/spec/li_spec.rb +41 -41
- data/spec/link_spec.rb +65 -59
- data/spec/links_spec.rb +11 -11
- data/spec/lis_spec.rb +10 -10
- data/spec/map_spec.rb +30 -30
- data/spec/maps_spec.rb +10 -10
- data/spec/p_spec.rb +49 -49
- data/spec/pre_spec.rb +41 -41
- data/spec/pres_spec.rb +10 -10
- data/spec/ps_spec.rb +10 -10
- data/spec/radio_spec.rb +104 -97
- data/spec/radios_spec.rb +11 -11
- data/spec/select_list_spec.rb +118 -106
- data/spec/select_lists_spec.rb +15 -15
- data/spec/span_spec.rb +54 -54
- data/spec/spans_spec.rb +11 -11
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +23 -3
- data/spec/table_bodies.rb +8 -8
- data/spec/table_bodies_spec.rb +9 -9
- data/spec/table_body_spec.rb +28 -27
- data/spec/table_cell_spec.rb +25 -25
- data/spec/table_cells_spec.rb +16 -16
- data/spec/table_row_spec.rb +16 -16
- data/spec/table_rows_spec.rb +12 -12
- data/spec/table_spec.rb +36 -36
- data/spec/tables_spec.rb +12 -12
- data/spec/text_field_spec.rb +111 -92
- data/spec/text_fields_spec.rb +13 -13
- data/tasks/benchmark.rake +3 -0
- data/tasks/rspec.rake +2 -2
- data/tasks/testserver.rake +15 -0
- metadata +58 -46
- data/tasks/simple_ci.rake +0 -94
data/lib/celerity/element.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
57
|
+
locate
|
68
58
|
unless @object
|
69
|
-
raise UnknownObjectException, "Unable to locate object, using #{
|
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
|
-
|
102
|
-
|
103
|
-
|
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.
|
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
|
-
|
34
|
-
|
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
|
-
|
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
|
-
|
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,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
|