walidhalabi-celerity 0.0.6.11
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +75 -0
- data/License.txt +621 -0
- data/README.txt +73 -0
- data/Rakefile +12 -0
- data/lib/celerity.rb +74 -0
- data/lib/celerity/browser.rb +811 -0
- data/lib/celerity/clickable_element.rb +69 -0
- data/lib/celerity/collections.rb +156 -0
- data/lib/celerity/container.rb +788 -0
- data/lib/celerity/default_viewer.rb +10 -0
- data/lib/celerity/disabled_element.rb +40 -0
- data/lib/celerity/element.rb +313 -0
- data/lib/celerity/element_collection.rb +107 -0
- data/lib/celerity/element_locator.rb +170 -0
- data/lib/celerity/elements/button.rb +43 -0
- data/lib/celerity/elements/file_field.rb +25 -0
- data/lib/celerity/elements/form.rb +23 -0
- data/lib/celerity/elements/frame.rb +75 -0
- data/lib/celerity/elements/image.rb +76 -0
- data/lib/celerity/elements/label.rb +11 -0
- data/lib/celerity/elements/link.rb +30 -0
- data/lib/celerity/elements/meta.rb +6 -0
- data/lib/celerity/elements/non_control_elements.rb +106 -0
- data/lib/celerity/elements/option.rb +32 -0
- data/lib/celerity/elements/radio_check.rb +115 -0
- data/lib/celerity/elements/select_list.rb +121 -0
- data/lib/celerity/elements/table.rb +144 -0
- data/lib/celerity/elements/table_cell.rb +29 -0
- data/lib/celerity/elements/table_elements.rb +42 -0
- data/lib/celerity/elements/table_row.rb +48 -0
- data/lib/celerity/elements/text_field.rb +169 -0
- data/lib/celerity/exception.rb +77 -0
- data/lib/celerity/htmlunit.rb +61 -0
- data/lib/celerity/htmlunit/commons-codec-1.3.jar +0 -0
- data/lib/celerity/htmlunit/commons-collections-3.2.1.jar +0 -0
- data/lib/celerity/htmlunit/commons-httpclient-3.1.jar +0 -0
- data/lib/celerity/htmlunit/commons-io-1.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-lang-2.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-logging-1.1.1.jar +0 -0
- data/lib/celerity/htmlunit/cssparser-0.9.5.jar +0 -0
- data/lib/celerity/htmlunit/htmlunit-2.6-SNAPSHOT.jar +0 -0
- data/lib/celerity/htmlunit/htmlunit-core-js-2.5.jar +0 -0
- data/lib/celerity/htmlunit/nekohtml-1.9.13-20090507.082850-2.jar +0 -0
- data/lib/celerity/htmlunit/sac-1.3.jar +0 -0
- data/lib/celerity/htmlunit/serializer-2.7.1.jar +0 -0
- data/lib/celerity/htmlunit/xalan-2.7.1.jar +0 -0
- data/lib/celerity/htmlunit/xercesImpl-2.8.1.jar +0 -0
- data/lib/celerity/htmlunit/xml-apis-1.3.04.jar +0 -0
- data/lib/celerity/identifier.rb +11 -0
- data/lib/celerity/input_element.rb +25 -0
- data/lib/celerity/listener.rb +141 -0
- data/lib/celerity/resources/no_viewer.png +0 -0
- data/lib/celerity/short_inspect.rb +20 -0
- data/lib/celerity/util.rb +91 -0
- data/lib/celerity/version.rb +10 -0
- data/lib/celerity/watir_compatibility.rb +84 -0
- data/tasks/jar.rake +57 -0
- data/tasks/rdoc.rake +4 -0
- metadata +130 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# Used internally to locate elements on the page.
|
5
|
+
#
|
6
|
+
|
7
|
+
class ElementLocator
|
8
|
+
include Celerity::Exception
|
9
|
+
attr_accessor :idents
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(container, element_class)
|
13
|
+
container.assert_exists
|
14
|
+
|
15
|
+
@container = container
|
16
|
+
@object = container.object
|
17
|
+
@element_class = element_class
|
18
|
+
@attributes = @element_class::ATTRIBUTES # could check for 'strict' here?
|
19
|
+
@idents = @element_class::TAGS
|
20
|
+
@tags = @idents.map { |e| e.tag.downcase }
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_by_conditions(conditions) # TODO: refactor without performance hit
|
24
|
+
return nil unless @object # probably means we're on a TextPage (content-type is "text/plain")
|
25
|
+
|
26
|
+
@condition_idents = []
|
27
|
+
attributes = Hash.new { |h, k| h[k] = [] }
|
28
|
+
index = 0 # by default, return the first matching element
|
29
|
+
text = nil
|
30
|
+
|
31
|
+
conditions.each do |how, what|
|
32
|
+
case how
|
33
|
+
when :object
|
34
|
+
unless what.is_a?(HtmlUnit::Html::HtmlElement) || what.nil?
|
35
|
+
raise ArgumentError, "expected an HtmlUnit::Html::HtmlElement subclass, got #{what.inspect}:#{what.class}"
|
36
|
+
end
|
37
|
+
return what
|
38
|
+
when :xpath
|
39
|
+
return find_by_xpath(what)
|
40
|
+
when :label
|
41
|
+
return find_by_label(what) unless @attributes.include?(:label)
|
42
|
+
when :class_name
|
43
|
+
how = :class
|
44
|
+
when :url
|
45
|
+
how = :href
|
46
|
+
when :caption
|
47
|
+
how = :text
|
48
|
+
end
|
49
|
+
|
50
|
+
if how == :id && conditions.size == 1
|
51
|
+
return find_by_id(what)
|
52
|
+
elsif @attributes.include?(how = how.to_sym)
|
53
|
+
attributes[how] << what
|
54
|
+
elsif how == :index
|
55
|
+
index = what.to_i - Celerity.index_offset
|
56
|
+
elsif how == :text
|
57
|
+
text = what
|
58
|
+
else
|
59
|
+
raise MissingWayOfFindingObjectException, "No how #{how.inspect}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@idents.each do |ident|
|
64
|
+
merged = attributes.merge(ident.attributes) { |key, v1, v2| v1 | v2 }
|
65
|
+
id = Identifier.new(ident.tag, merged)
|
66
|
+
id.text = ident.text || text # «original» identifier takes precedence for :text
|
67
|
+
@condition_idents << id
|
68
|
+
end
|
69
|
+
|
70
|
+
if index == 0
|
71
|
+
element_by_idents(@condition_idents)
|
72
|
+
else
|
73
|
+
elements_by_idents(@condition_idents)[index]
|
74
|
+
end
|
75
|
+
|
76
|
+
rescue HtmlUnit::ElementNotFoundException
|
77
|
+
nil # for rcov
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_by_id(what)
|
81
|
+
case what
|
82
|
+
when Regexp
|
83
|
+
elements_by_tag_names.find { |elem| elem.getId =~ what }
|
84
|
+
when String
|
85
|
+
obj = @object.getElementById(what)
|
86
|
+
return obj if @tags.include?(obj.getTagName)
|
87
|
+
|
88
|
+
$stderr.puts "warning: multiple elements with identical id? (#{what.inspect})" if $VERBOSE
|
89
|
+
elements_by_tag_names.find { |elem| elem.getId == what }
|
90
|
+
else
|
91
|
+
raise TypeError, "expected String or Regexp, got #{what.inspect}:#{what.class}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def find_by_xpath(what)
|
96
|
+
what = ".#{what}" if what[0].chr == "/"
|
97
|
+
@object.getByXPath(what).to_a.first
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_by_label(what)
|
101
|
+
obj = elements_by_tag_names(%w[label]).find { |e| matches?(e.asText, what) }
|
102
|
+
|
103
|
+
return nil unless obj && (ref = obj.getReferencedElement)
|
104
|
+
return ref if @tags.include?(ref.getTagName)
|
105
|
+
|
106
|
+
find_by_id obj.getForAttribute
|
107
|
+
end
|
108
|
+
|
109
|
+
def elements_by_idents(idents = @idents)
|
110
|
+
get_by_idents(:select, idents)
|
111
|
+
end
|
112
|
+
|
113
|
+
def element_by_idents(idents = @idents)
|
114
|
+
get_by_idents(:find, idents)
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def get_by_idents(meth, idents)
|
120
|
+
with_nullpointer_retry do
|
121
|
+
@object.getAllHtmlChildElements.send(meth) do |e|
|
122
|
+
next unless @tags.include?(e.getTagName)
|
123
|
+
idents.any? { |id| element_matches_ident?(e, id) }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def element_matches_ident?(element, ident)
|
129
|
+
return false unless ident.tag == element.getTagName
|
130
|
+
|
131
|
+
attr_result = ident.attributes.all? do |key, values|
|
132
|
+
values.any? { |val| matches?(element.getAttribute(key.to_s), val) }
|
133
|
+
end
|
134
|
+
|
135
|
+
if ident.text
|
136
|
+
attr_result && matches?(element.asText.strip, ident.text)
|
137
|
+
else
|
138
|
+
attr_result
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def elements_by_tag_names(tags = @tags)
|
143
|
+
with_nullpointer_retry do
|
144
|
+
# HtmlUnit's getHtmlElementsByTagNames won't get elements in the correct
|
145
|
+
# order (making :index fail), so we're using getAllHtmlChildElements instead.
|
146
|
+
@object.getAllHtmlChildElements.select do |elem|
|
147
|
+
tags.include?(elem.getTagName)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# HtmlUnit throws NPEs sometimes when we're locating elements
|
153
|
+
# Retry seems to work fine.
|
154
|
+
def with_nullpointer_retry(max_retries = 3)
|
155
|
+
tries = 0
|
156
|
+
yield
|
157
|
+
rescue java.lang.NullPointerException => e
|
158
|
+
raise e if tries >= max_retries
|
159
|
+
|
160
|
+
tries += 1
|
161
|
+
$stderr.puts "warning: celerity caught #{e} - retry ##{tries}"
|
162
|
+
retry
|
163
|
+
end
|
164
|
+
|
165
|
+
def matches?(string, what)
|
166
|
+
Regexp === what ? string.strip =~ what : string == what.to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
end # ElementLocator
|
170
|
+
end # Celerity
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# Input: Button
|
5
|
+
#
|
6
|
+
# Class representing button elements
|
7
|
+
#
|
8
|
+
|
9
|
+
class Button < InputElement
|
10
|
+
TAGS = [ Identifier.new('button'),
|
11
|
+
Identifier.new('input', :type => %w[submit reset image button]) ]
|
12
|
+
|
13
|
+
# Attribute list is a little weird due to this class covering both <button>
|
14
|
+
# and <input type="submit|reset|image|button" />
|
15
|
+
ATTRIBUTES = BASE_ATTRIBUTES | [:type, :disabled, :tabindex, :accesskey, :onfocus, :onblur] | [:src, :usemap, :ismap]
|
16
|
+
DEFAULT_HOW = :value
|
17
|
+
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
|
22
|
+
def locate
|
23
|
+
# We want the :value attribute to point to the inner HTML for <button> elements,
|
24
|
+
# and to the value attribute for <input type="button"> elements.
|
25
|
+
|
26
|
+
if (val = @conditions[:value])
|
27
|
+
button_ident = Identifier.new('button')
|
28
|
+
button_ident.text = val
|
29
|
+
input_ident = Identifier.new('input', :type => %w[submit reset image button], :value => [val])
|
30
|
+
|
31
|
+
locator = ElementLocator.new(@container, self.class)
|
32
|
+
locator.idents = [button_ident, input_ident]
|
33
|
+
|
34
|
+
conditions = @conditions.dup
|
35
|
+
conditions.delete(:value)
|
36
|
+
@object = locator.find_by_conditions(conditions)
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end # Button
|
43
|
+
end # Celerity
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# For fields that accept file uploads
|
5
|
+
#
|
6
|
+
|
7
|
+
class FileField < InputElement
|
8
|
+
TAGS = [ Identifier.new('input', :type => %w[file]) ]
|
9
|
+
DEFAULT_HOW = :name
|
10
|
+
|
11
|
+
#
|
12
|
+
# Set the file field to the given path
|
13
|
+
#
|
14
|
+
|
15
|
+
def set(path)
|
16
|
+
assert_exists
|
17
|
+
path = path.to_s
|
18
|
+
@container.update_page @object.setValueAttribute(path)
|
19
|
+
unless @object.getContentType
|
20
|
+
@object.setContentType(Celerity::Util.content_type_for(path))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end # FileField
|
25
|
+
end # Celerity
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Celerity
|
2
|
+
class Form < Element
|
3
|
+
include Container
|
4
|
+
|
5
|
+
TAGS = [Identifier.new('form')]
|
6
|
+
|
7
|
+
# HTML 4.01 Transitional DTD
|
8
|
+
ATTRIBUTES = BASE_ATTRIBUTES | [:action, :method, :enctype, :accept, :name, :onsubmit, :onreset, :target, :'accept-charset']
|
9
|
+
DEFAULT_HOW = :name
|
10
|
+
|
11
|
+
#
|
12
|
+
# Submits the form.
|
13
|
+
#
|
14
|
+
# This method should be avoided - invoke the user interface element that triggers the submit instead.
|
15
|
+
#
|
16
|
+
|
17
|
+
def submit
|
18
|
+
assert_exists
|
19
|
+
@container.update_page @object.submit(nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
end # Form
|
23
|
+
end # Celerity
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Celerity
|
2
|
+
class Frame < Element
|
3
|
+
include Container
|
4
|
+
attr_accessor :page
|
5
|
+
|
6
|
+
TAGS = [Identifier.new('frame'), Identifier.new('iframe')]
|
7
|
+
ATTRIBUTES = BASE_ATTRIBUTES | [:longdesc, :name, :src, :frameborder, :marginwidth, :marginheight, :noresize, :scrolling]
|
8
|
+
DEFAULT_HOW = :name
|
9
|
+
|
10
|
+
#
|
11
|
+
# Override the default locate to handle frame and inline frames.
|
12
|
+
# @api private
|
13
|
+
#
|
14
|
+
|
15
|
+
def locate
|
16
|
+
super
|
17
|
+
if @object
|
18
|
+
@inline_frame_object = @object.getEnclosedWindow.getFrameElement
|
19
|
+
self.page = @object.getEnclosedPage
|
20
|
+
if (frame = self.page.getDocumentElement)
|
21
|
+
@object = frame
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Override assert_exists to raise UnknownFrameException (for Watir compatibility)
|
28
|
+
# @api private
|
29
|
+
#
|
30
|
+
|
31
|
+
def assert_exists
|
32
|
+
locate
|
33
|
+
unless @object
|
34
|
+
raise UnknownFrameException, "unable to locate frame, using #{identifier_string}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Executes the given JavaScript string in this frame. (Celerity only)
|
40
|
+
#
|
41
|
+
|
42
|
+
def execute_script(source)
|
43
|
+
assert_exists
|
44
|
+
@page.executeJavaScript(source.to_s).getJavaScriptResult
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Updates the brwoser page with the page from this frame's top window.
|
49
|
+
# Used internally.
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
#
|
53
|
+
|
54
|
+
def update_page(value)
|
55
|
+
@browser.page = value.getEnclosingWindow.getTopWindow.getEnclosedPage
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
assert_exists
|
60
|
+
create_string(@inline_frame_object)
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_missing(meth, *args, &blk)
|
64
|
+
meth = selector_to_attribute(meth)
|
65
|
+
if self.class::ATTRIBUTES.include?(meth)
|
66
|
+
assert_exists
|
67
|
+
@inline_frame_object.getAttribute(meth.to_s)
|
68
|
+
else
|
69
|
+
Log.warn "Element\#method_missing calling super with #{meth.inspect}"
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end # Frame
|
75
|
+
end # Celerity
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
class Image < Element
|
4
|
+
include ClickableElement
|
5
|
+
|
6
|
+
TAGS = [ Identifier.new('img') ]
|
7
|
+
|
8
|
+
ATTRIBUTES = BASE_ATTRIBUTES | [:src, :alt, :longdesc, :name, :height, :width, :usemap, :ismap, :align, :border, :hspace, :vspace]
|
9
|
+
DEFAULT_HOW = :src
|
10
|
+
|
11
|
+
#
|
12
|
+
# returns the file created date of the image
|
13
|
+
#
|
14
|
+
|
15
|
+
def file_created_date
|
16
|
+
assert_exists
|
17
|
+
web_response = @object.getWebResponse(true)
|
18
|
+
Time.parse(web_response.getResponseHeaderValue("Last-Modified").to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# returns the file size of the image in bytes
|
23
|
+
#
|
24
|
+
|
25
|
+
def file_size
|
26
|
+
assert_exists
|
27
|
+
web_response = @object.getWebResponse(true)
|
28
|
+
web_response.getContentAsBytes.length
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# returns the width in pixels of the image, as a string
|
33
|
+
#
|
34
|
+
|
35
|
+
def width
|
36
|
+
assert_exists
|
37
|
+
@object.getWidth
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# returns the height in pixels of the image, as a string
|
42
|
+
#
|
43
|
+
|
44
|
+
def height
|
45
|
+
assert_exists
|
46
|
+
@object.getHeight
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# returns true if the image is loaded
|
51
|
+
#
|
52
|
+
|
53
|
+
def loaded?
|
54
|
+
assert_exists
|
55
|
+
begin
|
56
|
+
@object.getImageReader
|
57
|
+
true
|
58
|
+
rescue
|
59
|
+
false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Saves the image to the given file
|
65
|
+
#
|
66
|
+
|
67
|
+
def save(filename)
|
68
|
+
assert_exists
|
69
|
+
image_reader = @object.getImageReader
|
70
|
+
file = java.io.File.new(filename)
|
71
|
+
buffered_image = image_reader.read(0);
|
72
|
+
javax.imageio.ImageIO.write(buffered_image, image_reader.getFormatName(), file);
|
73
|
+
end
|
74
|
+
|
75
|
+
end # Image
|
76
|
+
end # Celerity
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Celerity
|
2
|
+
class Link < Element
|
3
|
+
include ClickableElement
|
4
|
+
|
5
|
+
TAGS = [ Identifier.new('a') ]
|
6
|
+
ATTRIBUTES = BASE_ATTRIBUTES | [:charset, :type, :name, :href, :hreflang,
|
7
|
+
:target, :rel, :rev, :accesskey, :shape,
|
8
|
+
:coords, :tabindex, :onfocus, :onblur]
|
9
|
+
DEFAULT_HOW = :href
|
10
|
+
|
11
|
+
#
|
12
|
+
# Returns the absolute URL for this link (Celerity-specific)
|
13
|
+
#
|
14
|
+
# (Watir/IE does this for href(), but we don't want that.)
|
15
|
+
#
|
16
|
+
|
17
|
+
def absolute_url
|
18
|
+
assert_exists
|
19
|
+
href = @object.getAttribute('href')
|
20
|
+
|
21
|
+
unless href.empty? || URI.parse(href).absolute?
|
22
|
+
href = URI.join(browser.url, href).to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
href
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
end # Link
|
30
|
+
end # Celerity
|