capybarista 0.1.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.
- checksums.yaml +7 -0
- data/lib/capybarista.rb +111 -0
- data/lib/capybarista/extensions.rb +197 -0
- data/lib/capybarista/javascript.rb +19 -0
- data/lib/capybarista/queries.rb +35 -0
- data/lib/capybarista/unique_xpath.rb +46 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5d190cddc7761afa7b8b64d03980227c46051ddc
|
4
|
+
data.tar.gz: 58e660f9a37c9254f24dce17eb91aab10f0cd79e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21e7f7a7399b56dbef611917f9e7d0b16fc3d0df53cbf357ac166353c4cd157282bf8c5ee839efcfd7bdb4283b83be6c12fc637b8b90db1c6a82d120a57c3ae4
|
7
|
+
data.tar.gz: a50372ff46a47aeb66f3e73ddfe84e6c79b2e350429eec58baa0e94ace4320c2042a7af9ded6ac3d2960544b5d57cd13f9fe6d8b849fc4a02381308976cc9ee1
|
data/lib/capybarista.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
require 'capybarista/extensions'
|
3
|
+
|
4
|
+
module Capybarista
|
5
|
+
|
6
|
+
module Finders
|
7
|
+
|
8
|
+
def all(*args)
|
9
|
+
basis.all(*args).map{|f| Capybarista::Element.for(f) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def find(*args)
|
13
|
+
Capybarista::Element.for basis.find(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_button(*args)
|
17
|
+
Capybarista::Element.for basis.find_button(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by_id(*args)
|
21
|
+
Capybarista::Element.for basis.find_by_id(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_field(*args)
|
25
|
+
Capybarista::Element.for basis.find_field(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_link(*args)
|
29
|
+
Capybarista::Element.for basis.find_link(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def first(*args)
|
33
|
+
Capybarista::Element.for basis.first(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
class Session
|
41
|
+
include Capybarista::Finders
|
42
|
+
include Capybarista::Extensions::Session
|
43
|
+
|
44
|
+
attr_reader :basis
|
45
|
+
|
46
|
+
def initialize(capybara_session)
|
47
|
+
@basis = capybara_session
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.for(input)
|
51
|
+
if input.is_a? Capybarista::Session
|
52
|
+
return input
|
53
|
+
else
|
54
|
+
return Capybarista::Session.new input
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def within(input, *args, &block)
|
60
|
+
input = Capybarista::Element.unwrap(input)
|
61
|
+
@basis.within(input, *args, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
def method_missing(name, *args, &block)
|
67
|
+
@basis.public_send(name, *args, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
class Element
|
75
|
+
include Capybarista::Finders
|
76
|
+
include Capybarista::Extensions::Element
|
77
|
+
|
78
|
+
attr_reader :basis, :session
|
79
|
+
|
80
|
+
def initialize(element, session)
|
81
|
+
@basis = element
|
82
|
+
@session = session
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.for(input, session = nil)
|
86
|
+
if input.is_a? Capybarista::Element
|
87
|
+
return input
|
88
|
+
else
|
89
|
+
session ||= Capybarista::Session.new(input.session)
|
90
|
+
return Capybarista::Element.new input
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def self.unwrap(input)
|
96
|
+
while input.is_a? Capybarista::Element
|
97
|
+
input = input.basis
|
98
|
+
end
|
99
|
+
input
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
def method_missing(name, *args, &block)
|
105
|
+
@basis.public_send(name, *args, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
|
2
|
+
require 'capybara'
|
3
|
+
|
4
|
+
require 'capybarista/queries'
|
5
|
+
require 'capybarista/unique_xpath'
|
6
|
+
require 'capybarista/javascript'
|
7
|
+
|
8
|
+
require 'logbert'
|
9
|
+
|
10
|
+
module Capybarista
|
11
|
+
|
12
|
+
module Extensions
|
13
|
+
LOG = Logbert[self]
|
14
|
+
|
15
|
+
J = Capybarista::Javascript
|
16
|
+
|
17
|
+
def self.applied?
|
18
|
+
@applied
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.apply!
|
22
|
+
|
23
|
+
if applied?
|
24
|
+
LOG.debug "Capybarista extensions have already been applied"
|
25
|
+
else
|
26
|
+
Capybara::Session.class_eval do
|
27
|
+
include Capybarista::Extensions::Session
|
28
|
+
end
|
29
|
+
|
30
|
+
Capybara::Node::Element.class_eval do
|
31
|
+
include Capybarista::Extensions::Element
|
32
|
+
end
|
33
|
+
|
34
|
+
@applied = true
|
35
|
+
LOG.warning "Capybara::Session and Capybara::Node::Element have been monkey-patched w/ the Capybarista extensions"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
module Base
|
42
|
+
|
43
|
+
# Returns the list of fields that require user input.
|
44
|
+
def all_fields(options = {})
|
45
|
+
all(:xpath, Capybarista::Queries::XPath.all_fields, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
module Session
|
52
|
+
include Base
|
53
|
+
|
54
|
+
#this method returns a list of labels that are sorted from bottom up, right to left
|
55
|
+
def new_label_list
|
56
|
+
fields = all_fields(visible:true).map{|f| {f: f, pos: f.top_left} }
|
57
|
+
list_of_lists = fields.map{ |i| i[:pos] }
|
58
|
+
sorted_list = list_of_lists.sort {|item1, item2| item2[1]<=>item1[1]}
|
59
|
+
labels_list = Array.new
|
60
|
+
sorted_list.each do |item|
|
61
|
+
label = fields.find { |h| h[:pos] == item }[:f]
|
62
|
+
labels_list.push(label)
|
63
|
+
end
|
64
|
+
labels_list
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
module Element
|
70
|
+
include Base
|
71
|
+
|
72
|
+
# Syntactic sugar. Yum!
|
73
|
+
def scoped
|
74
|
+
s = session
|
75
|
+
s.within(self){ yield s }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a map containing only the specified attributes.
|
79
|
+
# If the element does not contain an attribute, then its
|
80
|
+
# key/value pairing will be omitted.
|
81
|
+
#
|
82
|
+
# FIXME: This methods filters attributes that are equal
|
83
|
+
# to the empty string :-(
|
84
|
+
def filtered_attributes(*keys)
|
85
|
+
retval = {}
|
86
|
+
|
87
|
+
keys.each do |k|
|
88
|
+
value = self[k]
|
89
|
+
if value and not value.empty?
|
90
|
+
retval[k] = value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
return retval
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns 0 or more labels for the current element
|
98
|
+
def labels
|
99
|
+
ids = filtered_attributes(:id, :name).values
|
100
|
+
|
101
|
+
if ids.any?
|
102
|
+
query = Capybarista::Queries::XPath.labels_for(*ids)
|
103
|
+
return session.all(:xpath, query)
|
104
|
+
else
|
105
|
+
return []
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Attempts to find the label for the current element.
|
111
|
+
# If no label exists, then raise Capybara::ElementNotFound
|
112
|
+
def label!
|
113
|
+
ids = filtered_attributes(:id, :name).values
|
114
|
+
|
115
|
+
if ids.any?
|
116
|
+
query = Capybarista::Queries::XPath.labels_for(*ids)
|
117
|
+
return session.find(:xpath, query)
|
118
|
+
else
|
119
|
+
raise Capybara::ElementNotFound, "The element has no labels"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Attempts to find the label for the current element.
|
125
|
+
# If no label exists, then return nil.
|
126
|
+
def label
|
127
|
+
ids = filtered_attributes(:id, :name).values
|
128
|
+
if ids.any?
|
129
|
+
query = Capybarista::Queries::XPath.labels_for(*ids)
|
130
|
+
return session.first(:xpath, query)
|
131
|
+
else
|
132
|
+
return nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def top_left
|
137
|
+
long_function = %Q{
|
138
|
+
(
|
139
|
+
function(){
|
140
|
+
var obj = document.evaluate("#{unique_xpath}", document, null, XPathResult.ANY_TYPE, null ).iterateNext();
|
141
|
+
if(obj) {
|
142
|
+
|
143
|
+
var curleft = 0; var curtop = 0;
|
144
|
+
if (obj && obj.offsetParent) {
|
145
|
+
do {
|
146
|
+
curleft += obj.offsetLeft;
|
147
|
+
curtop += obj.offsetTop;
|
148
|
+
} while (obj = obj.offsetParent);
|
149
|
+
}
|
150
|
+
return [curleft,curtop];
|
151
|
+
};
|
152
|
+
}()
|
153
|
+
);
|
154
|
+
}
|
155
|
+
long_string = long_function.delete("\n")
|
156
|
+
session.evaluate_script(long_string)
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
def unique_xpath
|
162
|
+
Capybarista::UniqueXPath.for(self)
|
163
|
+
end
|
164
|
+
|
165
|
+
def inner_html
|
166
|
+
session.evaluate_script %Q{(function(){ var result = #{ J.find_xpath(unique_xpath) }; if(result) { return result.innerHTML; } }()); }
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def outer_html
|
171
|
+
session.evaluate_script %Q{(function(){ var result = #{ J.find_xpath(unique_xpath) }; if(result) { return result.outerHTML; } }()); }
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
def highlight
|
176
|
+
session.execute_script %Q{(function(){ var result = #{ J.find_xpath(unique_xpath) }; if(result) { var old_color = result.style.backgroundColor; result.style.backgroundColor = "yellow"; setTimeout(function(){ result.style.backgroundColor = old_color; }, 1000); } }()); }
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
def blur
|
181
|
+
script = %Q{ (function() { var target = #{ J.find_xpath(unique_xpath) }; if(target) { var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("blur", true, true,window, 1, 1, 1, 1, 1, false, false, false, false, 0, target); target.dispatchEvent(evt); } })(); }
|
182
|
+
session.execute_script(script)
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def focus
|
187
|
+
script = %Q{ (function() { var target = #{ J.find_xpath(unique_xpath) }; if(target) { var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("focus", true, true,window, 1, 1, 1, 1, 1, false, false, false, false, 0, target); target.dispatchEvent(evt); } })(); }
|
188
|
+
session.execute_script(script)
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Capybarista
|
4
|
+
module Javascript
|
5
|
+
|
6
|
+
# Generates a fragment of Javascript that will evaluate the
|
7
|
+
# specified XPath query.
|
8
|
+
def find_xpath(query)
|
9
|
+
%Q{document.evaluate("#{query}", document, null, XPathResult.ANY_TYPE, null ).iterateNext()}
|
10
|
+
end
|
11
|
+
|
12
|
+
module_function :find_xpath
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
require 'xpath'
|
3
|
+
|
4
|
+
module Capybarista
|
5
|
+
|
6
|
+
# This module contains a number of functions
|
7
|
+
# for generating XPath and CSS queries
|
8
|
+
module Queries
|
9
|
+
|
10
|
+
module XPath
|
11
|
+
|
12
|
+
|
13
|
+
def self.string(value)
|
14
|
+
# The underlying API changes betw/ versions
|
15
|
+
# 0.1.4 and 2.0.0 . So, let's wrap the method.
|
16
|
+
::XPath::Expression::StringLiteral.new(value.to_s).to_xpath
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Queries returns all fields that accept user input
|
21
|
+
def self.all_fields
|
22
|
+
".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden' or ./@type='button')]"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.labels_for(*ids)
|
26
|
+
condition = ids.map{|id| "@for=#{string(id)}" }.join(" or ")
|
27
|
+
"//label[#{condition}]"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module Capybarista
|
3
|
+
|
4
|
+
module UniqueXPath
|
5
|
+
|
6
|
+
class UniquenessError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.for(element)
|
10
|
+
session = element.session
|
11
|
+
|
12
|
+
fragments = []
|
13
|
+
loop do
|
14
|
+
fragments.unshift fragment(element)
|
15
|
+
|
16
|
+
query = "//" + fragments.join("/")
|
17
|
+
|
18
|
+
occurrences = session.all(:xpath, query).count
|
19
|
+
if occurrences == 1
|
20
|
+
return query
|
21
|
+
elsif occurrences == 0
|
22
|
+
raise UniquenessError, "Failed to produce a unique xpath for the element"
|
23
|
+
end
|
24
|
+
|
25
|
+
element = element.find(:xpath, "..")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def self.fragment(element)
|
31
|
+
tag = element.tag_name
|
32
|
+
id = element[:id]
|
33
|
+
|
34
|
+
if id.nil? or id.empty?
|
35
|
+
index = element.all(:xpath, "preceding-sibling::#{ tag }").count + 1
|
36
|
+
"#{tag}[#{index}]"
|
37
|
+
else
|
38
|
+
"#{tag}[@id='#{id}']"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capybarista
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Lauber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: capybara
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: xpath
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.4
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.4
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: logbert
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.4
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.4
|
55
|
+
description: Useful extensions for Capybara
|
56
|
+
email: blauber@jibe.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- lib/capybarista/extensions.rb
|
62
|
+
- lib/capybarista/javascript.rb
|
63
|
+
- lib/capybarista/queries.rb
|
64
|
+
- lib/capybarista/unique_xpath.rb
|
65
|
+
- lib/capybarista.rb
|
66
|
+
homepage:
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.0.3
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Useful extensions for Capybara
|
90
|
+
test_files: []
|