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