shiken 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/browser.rb +266 -0
- data/lib/button.rb +7 -0
- data/lib/clickable.rb +22 -0
- data/lib/downloads.rb +80 -0
- data/lib/dropdown.rb +30 -0
- data/lib/element.rb +104 -0
- data/lib/field.rb +16 -0
- data/lib/kendo.rb +138 -0
- data/lib/link.rb +7 -0
- data/lib/page.rb +62 -0
- data/lib/radio_set.rb +20 -0
- data/lib/shiken.rb +83 -0
- data/lib/table.rb +56 -0
- data/lib/trace.rb +59 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5edfc3ceb874cd976dd729485e38e8752cdc693a
|
4
|
+
data.tar.gz: ec9dd9bfc1143a913e956c3d96b50ad659db0468
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21d62b4814a02148e10f94a1a64be9cc993af62a04c4402d4202a4fb51dad74ef8d55c9271e6a03ae75e865682c96bcfd7d005f42a84218f5d1bf5e622d3b9cb
|
7
|
+
data.tar.gz: 90b5dc74cff9bead2d8461e0e48a0005da7e43981b2ae426b7a28bc95a73326ac7b58ec7b91174f95c71cc5cc3d47a8790edbc0799b7567bad98c6699f94f74c
|
data/lib/browser.rb
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'shiken/trace'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module SK::Browser
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
### local utilities
|
10
|
+
|
11
|
+
def pause(secs,text="")
|
12
|
+
trace("pause #{secs} seconds #{text}")
|
13
|
+
sleep secs
|
14
|
+
end
|
15
|
+
def trace(s)
|
16
|
+
SK::Trace.trace(s)
|
17
|
+
end
|
18
|
+
def error(s)
|
19
|
+
SK::Trace.error(s)
|
20
|
+
end
|
21
|
+
def warn(s)
|
22
|
+
SK::Trace.notice(s)
|
23
|
+
end
|
24
|
+
|
25
|
+
###### main stuff #########
|
26
|
+
|
27
|
+
def goto(site,page='')
|
28
|
+
gotourl("https://#{site}/#{page}")
|
29
|
+
end
|
30
|
+
def gotox(site,page='')
|
31
|
+
gotourl("http://#{site}/#{page}")
|
32
|
+
end
|
33
|
+
def gotourl(url,seconds=1)
|
34
|
+
uri = URI.escape(url)
|
35
|
+
puts("go to #{uri}")
|
36
|
+
rescue_exceptions { SK::driver.get uri }
|
37
|
+
sleep seconds
|
38
|
+
end
|
39
|
+
|
40
|
+
def option(dd)
|
41
|
+
warn "browser.option is depricated. use SK::Dropdown instead"
|
42
|
+
Selenium::WebDriver::Support::Select.new(dd)
|
43
|
+
end
|
44
|
+
|
45
|
+
def select(locator,value)
|
46
|
+
dropdown(locator,"option",value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dropdown(locator,tagname,list_option)
|
50
|
+
warn "browser.dropdown is depricated. use SK::Dropdown instead. locator=#{locator}"
|
51
|
+
# actually returns the option object for the dropdown
|
52
|
+
# so the calling code can chain a select by method
|
53
|
+
# option(find({name: name}))
|
54
|
+
select_box = find(locator)
|
55
|
+
# select_box = $driver.find_element(locator)
|
56
|
+
_dropdown_el(select_box,tagname,list_option)
|
57
|
+
end
|
58
|
+
def _dropdown_el(select_box,tagname,list_option)
|
59
|
+
# trace "dropdown el... select_box=#{select_box}"
|
60
|
+
# trace "dropdown el... tagname=#{tagname} option=#{list_option}"
|
61
|
+
|
62
|
+
# actually returns the option object for the dropdown
|
63
|
+
# so the calling code can chain a select by method
|
64
|
+
# option(find({name: name}))
|
65
|
+
options = select_box.find_elements(:tag_name => "#{tagname}")
|
66
|
+
|
67
|
+
options.each do |option_field|
|
68
|
+
# trace "looking at #{option_field.text}"
|
69
|
+
if option_field.text == "#{list_option}"
|
70
|
+
option_field.click
|
71
|
+
break
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def search(base_locator,target_locator,value,attr='text')
|
77
|
+
# search within the locator element for all matching tags (by name)
|
78
|
+
# return that element that has the attribute of text that matches
|
79
|
+
# the text_value passed
|
80
|
+
element = find(base_locator)
|
81
|
+
unless element # was found
|
82
|
+
error "wt search: element not found. locator = #{base_locator}"
|
83
|
+
return false
|
84
|
+
end
|
85
|
+
elements = element.find_elements(target_locator)
|
86
|
+
elements.each do |el|
|
87
|
+
el_attr = _get_attribute(el,attr)
|
88
|
+
# trace "wt search at #{el_attr} for #{value}"
|
89
|
+
return el if el_attr == "#{value}"
|
90
|
+
end
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
|
94
|
+
def _get_attribute(el,attr)
|
95
|
+
attr == 'text' ? el.text : el.attribute(attr)
|
96
|
+
end
|
97
|
+
|
98
|
+
def radio(locator)
|
99
|
+
warn "browser.radio is depricated. use SK::RadioSet instead. locator=#{locator}"
|
100
|
+
self.click(locator,0)
|
101
|
+
end
|
102
|
+
|
103
|
+
def set(locator,value)
|
104
|
+
# set now accepts a single or an
|
105
|
+
# array of locators, by using newfind
|
106
|
+
el = find(locator)
|
107
|
+
error "cannot set #{locator}" unless el
|
108
|
+
_set_el(el,value) if el
|
109
|
+
end
|
110
|
+
def _set_el(el,value)
|
111
|
+
el.clear
|
112
|
+
el.send_keys(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
def submit(name,secs=1)
|
116
|
+
click(name: name)
|
117
|
+
sleep secs
|
118
|
+
end
|
119
|
+
|
120
|
+
def find(param)
|
121
|
+
# find accepts a single value or array
|
122
|
+
# of locators to find an element. the array
|
123
|
+
# is meant to be a this under this structure
|
124
|
+
if param.kind_of?(Array)
|
125
|
+
base = driver
|
126
|
+
param.reverse.each do |loc|
|
127
|
+
base = _find_inside(loc,base)
|
128
|
+
end
|
129
|
+
return base
|
130
|
+
else
|
131
|
+
# not an array, a single
|
132
|
+
_find_inside(param,driver)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def _find_inside(locator, base)
|
137
|
+
# trace "find inside. locator=#{locator} base=#{base}"
|
138
|
+
# find an element by locator inside another element (base)
|
139
|
+
case
|
140
|
+
when locator.has_key?(:value)
|
141
|
+
# trace "... locate by value: #{locator[:value]}}"
|
142
|
+
find_input(:value,locator[:value])
|
143
|
+
when locator.has_key?(:type)
|
144
|
+
# trace "... locate by type: #{locator[:type]}"
|
145
|
+
find_input(:type,locator[:type])
|
146
|
+
else
|
147
|
+
# a standard selenium locator so just use it
|
148
|
+
rescue_exceptions { base.find_element(locator) }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def find_input(key,type)
|
153
|
+
# selenium does not support a locator search by type
|
154
|
+
# so we need a specialized function to find the element
|
155
|
+
elements = all({tag_name: 'input'})
|
156
|
+
elements.find do |e|
|
157
|
+
# trace "> #{e.attribute(key)} :: #{type}"
|
158
|
+
e.attribute(key) == type
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def all(locator)
|
163
|
+
rescue_exceptions { driver.find_elements(locator) }
|
164
|
+
end
|
165
|
+
|
166
|
+
def match(text,locator)
|
167
|
+
elements = all(locator)
|
168
|
+
# trace("searching #{elements.length} links...")
|
169
|
+
elements.find{ |e| e.text.include? text }
|
170
|
+
end
|
171
|
+
|
172
|
+
def count_refs(ref, locator)
|
173
|
+
elements = all(locator)
|
174
|
+
# trace("count from #{elements.length} links...")
|
175
|
+
elements.count{ |e| e.attribute('href').include? ref }
|
176
|
+
end
|
177
|
+
|
178
|
+
def has_content?(content)
|
179
|
+
# puts trace("has content? #{content}")
|
180
|
+
return self.source.include? content
|
181
|
+
end
|
182
|
+
|
183
|
+
def has_el?(locator)
|
184
|
+
find(locator)
|
185
|
+
# rescue_exceptions { driver.find_element(locator) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def has_text?(locator,text)
|
189
|
+
find(locator).text == text
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_rows(locator,less_row)
|
193
|
+
rest = less_row.to_i
|
194
|
+
#table = rescue_exceptions { driver.find_elements(table_count) }
|
195
|
+
table = all(locator)
|
196
|
+
return table.count-rest
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_text(locator)
|
200
|
+
find(locator).text
|
201
|
+
# return rescue_exceptions { driver.find_element(locator).text }
|
202
|
+
end
|
203
|
+
|
204
|
+
def get_value(locator)
|
205
|
+
find(locator).attribute('value')
|
206
|
+
end
|
207
|
+
|
208
|
+
### Actions ####
|
209
|
+
|
210
|
+
def click(locator,pause=0)
|
211
|
+
SK::Trace.trace("*** click is depricated")
|
212
|
+
el = find(locator)
|
213
|
+
_click_el(el,pause) if el
|
214
|
+
end
|
215
|
+
|
216
|
+
def _click_el(el,extra)
|
217
|
+
SK::Trace.trace("*** click_el")
|
218
|
+
rescue_exceptions { el.click }
|
219
|
+
sleep 1+extra
|
220
|
+
self
|
221
|
+
end
|
222
|
+
|
223
|
+
### Key Core Operations ###
|
224
|
+
|
225
|
+
def rescue_exceptions
|
226
|
+
# we do not want our tests to fail because selenium throws an error
|
227
|
+
# instead we want to explicitly test using "expects" in the cases
|
228
|
+
begin
|
229
|
+
yield
|
230
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError
|
231
|
+
warn "Element does not exist."
|
232
|
+
false
|
233
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
234
|
+
warn "Element is stale."
|
235
|
+
false
|
236
|
+
rescue Selenium::WebDriver::Error::ElementNotVisibleError
|
237
|
+
warn "Element is not visible."
|
238
|
+
false
|
239
|
+
rescue Selenium::WebDriver::Error::WebDriverError => e
|
240
|
+
error "Webdriver Error. #{e}"
|
241
|
+
false
|
242
|
+
rescue Net::ReadTimeout
|
243
|
+
error "Network Timeout - no response."
|
244
|
+
false
|
245
|
+
rescue IOError
|
246
|
+
error "IOError"
|
247
|
+
false
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
### Simple Accessors ####
|
252
|
+
|
253
|
+
def driver
|
254
|
+
SK::driver
|
255
|
+
end
|
256
|
+
def source
|
257
|
+
driver.page_source
|
258
|
+
end
|
259
|
+
def url
|
260
|
+
driver.current_url
|
261
|
+
end
|
262
|
+
def title
|
263
|
+
driver.title
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
data/lib/button.rb
ADDED
data/lib/clickable.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class SK::Clickable < SK::Element
|
2
|
+
|
3
|
+
# a clickable element can be created with a custom delay
|
4
|
+
# so that the tester does not have to set the delay at
|
5
|
+
# every calling point... just once at the creation oint
|
6
|
+
|
7
|
+
def initialize(locator,delay: 0)
|
8
|
+
super(locator) # creates the el and locator
|
9
|
+
@delay = delay
|
10
|
+
end
|
11
|
+
|
12
|
+
def click(delay = @delay)
|
13
|
+
if self.el
|
14
|
+
# SK::Trace.trace "SK::Clickable.click #{self.locator} #{self.el}"
|
15
|
+
self.el.click
|
16
|
+
sleep delay
|
17
|
+
else
|
18
|
+
SK::Trace.error "SK::Clickable.click: element not initialized for #{locator}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/lib/downloads.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module SK::Downloads
|
2
|
+
|
3
|
+
@path = nil
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def path=(p)
|
8
|
+
@path = p
|
9
|
+
end
|
10
|
+
|
11
|
+
def path
|
12
|
+
@path = @path || determine_mac_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def determine_mac_path
|
16
|
+
parts = File.dirname(__FILE__).split("/")
|
17
|
+
# will work for AR and PC on a Mac (for now)
|
18
|
+
"/#{parts[1]}/#{parts[2]}/downloads"
|
19
|
+
end
|
20
|
+
|
21
|
+
def files
|
22
|
+
# NOTE: every time this is called it goes out to the OS for
|
23
|
+
# a fresh list of the files... we may want to change to a
|
24
|
+
# "refresh" or "readfiles" model for performance
|
25
|
+
Dir["#{self.path}/*"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def names
|
29
|
+
files.collect {|f| File.basename(f) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def include?(filename)
|
33
|
+
filepath = "#{path}/#{filename}"
|
34
|
+
files.include? filepath
|
35
|
+
end
|
36
|
+
|
37
|
+
def last
|
38
|
+
files.max_by {|f| File.mtime(f)}
|
39
|
+
end
|
40
|
+
|
41
|
+
def last_name
|
42
|
+
File.basename last # just the file name
|
43
|
+
end
|
44
|
+
|
45
|
+
def count
|
46
|
+
files.length
|
47
|
+
end
|
48
|
+
|
49
|
+
# def first
|
50
|
+
# files.first
|
51
|
+
# end
|
52
|
+
|
53
|
+
# def first_content
|
54
|
+
# wait_for_download
|
55
|
+
# File.read(first)
|
56
|
+
# end
|
57
|
+
|
58
|
+
def wait_for_download
|
59
|
+
Timeout.timeout(1) do
|
60
|
+
sleep 0.1 until downloaded?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def downloaded?
|
65
|
+
!downloading? && files.any?
|
66
|
+
end
|
67
|
+
|
68
|
+
def downloading?
|
69
|
+
files.grep(/\.part$/).any?
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete_any(name)
|
73
|
+
list = files.select { |f| File.basename(f).include?(name) }
|
74
|
+
FileUtils.rm_f(list)
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete_all
|
78
|
+
FileUtils.rm_f(files)
|
79
|
+
end
|
80
|
+
end
|
data/lib/dropdown.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class SK::Dropdown < SK::Element
|
2
|
+
|
3
|
+
def initialize(locator)
|
4
|
+
super(locator) # creates the el and locator via
|
5
|
+
SK::Trace.warn "Dropdown failed to initialize. locator = #{locator}" unless el
|
6
|
+
@options = el.find_elements(tag_name: "option")
|
7
|
+
end
|
8
|
+
|
9
|
+
def select(value)
|
10
|
+
select_by('text',value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def select_by(attr,value)
|
14
|
+
@options.each do |option|
|
15
|
+
optval = option.attribute(attr)
|
16
|
+
# trace "looking at #{optval}"
|
17
|
+
if optval == value
|
18
|
+
option.click
|
19
|
+
break
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_index(index)
|
25
|
+
n = [@options.length-1,index].min
|
26
|
+
@options[n].click
|
27
|
+
return @options[n].attribute('text')
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/lib/element.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
class SK::Element
|
2
|
+
|
3
|
+
def initialize(arg,src="xxx")
|
4
|
+
# puts "element init arg=#{arg} src=#{src}"
|
5
|
+
if !arg
|
6
|
+
# sometimes we want to create an element from
|
7
|
+
# a find that fails... so create a dummy element
|
8
|
+
@locator = { src: src }
|
9
|
+
@el = false
|
10
|
+
elsif arg.instance_of?(Selenium::WebDriver::Element)
|
11
|
+
@locator = { src: src }
|
12
|
+
@el = arg
|
13
|
+
else # it is a locator
|
14
|
+
@locator = arg # remember
|
15
|
+
@el = SK::Browser.find(arg)
|
16
|
+
SK::Trace.debug "element locator=#{arg} el=#{@el}"
|
17
|
+
SK::Trace.warn "element failed to initialize with locator = #{arg}" unless @el
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :el
|
22
|
+
attr_reader :locator
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"<SK::Element #{locator}>"
|
26
|
+
end
|
27
|
+
|
28
|
+
def exists?
|
29
|
+
!!el
|
30
|
+
end
|
31
|
+
alias :exist? :exists?
|
32
|
+
|
33
|
+
def displayed?
|
34
|
+
puts "+++ element displayed?"
|
35
|
+
SK::Browser.rescue_exceptions { self.el.displayed? }
|
36
|
+
end
|
37
|
+
|
38
|
+
def html
|
39
|
+
# useful to see for debugging
|
40
|
+
self.el.attribute('innerHTML')
|
41
|
+
end
|
42
|
+
|
43
|
+
def enabled?
|
44
|
+
puts "enabled? 1:#{exists?}"
|
45
|
+
false unless exists?
|
46
|
+
puts "enabled? 2:#{el.enabled?}"
|
47
|
+
el.enabled?
|
48
|
+
end
|
49
|
+
|
50
|
+
def text
|
51
|
+
# useful to see contents for debugging
|
52
|
+
str1 = self.el ? self.el.text : ''
|
53
|
+
# str2 = str1.gsub(/\n\s+/, " ") # shrink white space
|
54
|
+
str1.gsub(/\n+/, " ").strip # remove new lines
|
55
|
+
end
|
56
|
+
|
57
|
+
def find(locator,klass=SK::Element)
|
58
|
+
# trace "find #{locator} in #{self.locator}"
|
59
|
+
child_el = find_child_el(locator)
|
60
|
+
# trace "child el = #{child_el}"
|
61
|
+
klass.new(child_el,"#{locator}")
|
62
|
+
end
|
63
|
+
alias :child :find
|
64
|
+
|
65
|
+
def find_child_el(locator)
|
66
|
+
# trace "find child of #{self.locator}. locator=#{locator}"
|
67
|
+
# find an element by locator inside another element (base)
|
68
|
+
case
|
69
|
+
when locator.has_key?(:value)
|
70
|
+
# trace "... locate by value: #{locator[:value]}}"
|
71
|
+
search(:value,locator[:value])
|
72
|
+
when locator.has_key?(:type)
|
73
|
+
# trace "... locate by type: #{locator[:type]}"
|
74
|
+
search(:type,locator[:type])
|
75
|
+
else
|
76
|
+
# a standard selenium locator so just use selenium
|
77
|
+
SK::Browser.rescue_exceptions { self.el.find_element(locator) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def children(locator,klass=SK::Element)
|
82
|
+
# get the matching children elements
|
83
|
+
els = SK::Browser.rescue_exceptions { self.el.find_elements(locator) }
|
84
|
+
# but return as one of our class types
|
85
|
+
els.map { |el| klass.new(el) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def search(key,val)
|
89
|
+
trace "element#search key=#{key} val=#{val}"
|
90
|
+
# selenium does not support a locator search by type
|
91
|
+
# so we need a specialized function to find the element
|
92
|
+
elements = self.el.find_elements({})
|
93
|
+
#elements = all({tag_name: 'input'})
|
94
|
+
elements.find do |e|
|
95
|
+
# trace "> #{e.attribute(key)} :: #{val}"
|
96
|
+
e.attribute(key) == val
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def parent_el
|
101
|
+
self.el.find_element({xpath: ".."})
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/lib/field.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class SK::Field < SK::Element
|
2
|
+
|
3
|
+
def initialize(locator)
|
4
|
+
super(locator) # creates the el and locator
|
5
|
+
end
|
6
|
+
|
7
|
+
def set(value)
|
8
|
+
if self.el
|
9
|
+
self.el.clear
|
10
|
+
self.el.send_keys(value)
|
11
|
+
else
|
12
|
+
SK::Trace.error "SK::Field.set: element not initialized for #{locator}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/kendo.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
class SK::KElement
|
2
|
+
|
3
|
+
def initialize(el)
|
4
|
+
@element = el
|
5
|
+
end
|
6
|
+
|
7
|
+
def el
|
8
|
+
@element
|
9
|
+
end
|
10
|
+
|
11
|
+
def exists?
|
12
|
+
!!el
|
13
|
+
end
|
14
|
+
|
15
|
+
def text
|
16
|
+
# useful to see for debugging
|
17
|
+
self.el.text
|
18
|
+
end
|
19
|
+
|
20
|
+
def html
|
21
|
+
# useful to see for debugging
|
22
|
+
self.el.attribute('innerHTML')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class SK::KCell < SK::KElement
|
28
|
+
def as_num
|
29
|
+
# trace "cell = #{cell.text}"
|
30
|
+
sign = self.text.include?('(') ? -1 : 1
|
31
|
+
sign * self.text.gsub(/[$,()]/, '').to_f
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class SK::KRow < SK::KElement
|
36
|
+
def cells()
|
37
|
+
self.el.find_elements({tag_name: 'td'}).collect{ |e| SK::KCell.new(e) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class SK::KGrid < SK::KElement
|
42
|
+
def initialize(locator={id: 'grid'})
|
43
|
+
super(SK::Browser.find(locator))
|
44
|
+
end
|
45
|
+
def grid
|
46
|
+
el
|
47
|
+
end
|
48
|
+
def items_label()
|
49
|
+
return nil unless grid
|
50
|
+
self.grid.find_element({class: 'k-pager-info'})
|
51
|
+
end
|
52
|
+
def items_count()
|
53
|
+
return 0 unless self.grid
|
54
|
+
return 0 unless self.items_label
|
55
|
+
# trace "*** items label = #{self.items_label.text}"
|
56
|
+
count = /(\d*) items/.match(self.items_label.text)[1].to_i
|
57
|
+
return count
|
58
|
+
end
|
59
|
+
def pages_count
|
60
|
+
last_page_button.attribute('data-page').to_i
|
61
|
+
end
|
62
|
+
def last_page_button
|
63
|
+
self.grid.find_element({class: 'k-pager-last'})
|
64
|
+
end
|
65
|
+
def first_page_button
|
66
|
+
self.grid.find_element({class: 'k-pager-first'})
|
67
|
+
end
|
68
|
+
def prev_page_button
|
69
|
+
# there is an easily identifiable span inside the button
|
70
|
+
span = self.grid.find_element({class: 'k-i-arrow-w'})
|
71
|
+
span.find_element({xpath: '..'})
|
72
|
+
end
|
73
|
+
def next_page_button
|
74
|
+
# there is an easily identifiable span inside the button
|
75
|
+
span = self.grid.find_element({class: 'k-i-arrow-e'})
|
76
|
+
span.find_element({xpath: '..'})
|
77
|
+
end
|
78
|
+
def rows
|
79
|
+
set = self.grid.find_elements({tag_name: 'tr'})
|
80
|
+
# trace "wt grid rows length = #{set.length}"
|
81
|
+
set
|
82
|
+
end
|
83
|
+
def row(n)
|
84
|
+
SK::KRow.new(self.rows[n])
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class SK::KFilter
|
90
|
+
def initialize(n)
|
91
|
+
@filter = nil # in case of error
|
92
|
+
# first check that the current page has a kendo grid
|
93
|
+
grid = SK::KGrid.new()
|
94
|
+
return if ! grid
|
95
|
+
# then work our way throught the page/grid...
|
96
|
+
# list of all the column headers; this is a little
|
97
|
+
# fragile if the page has other tables before the grid
|
98
|
+
th_list = SK::Browser.all({tag_name: 'th'})
|
99
|
+
return if !th_list
|
100
|
+
return if th_list.length < n
|
101
|
+
header = th_list[n] # the nth header for the column
|
102
|
+
# puts "attribute = #{header.attribute('data-field')}"
|
103
|
+
@filter = header.find_element({class: 'k-grid-filter'})
|
104
|
+
# puts "find filter = #{@filter}"
|
105
|
+
end
|
106
|
+
def click
|
107
|
+
# puts "click filter = #{@filter}"
|
108
|
+
@filter.click() # makes AC it visible
|
109
|
+
# pause 1
|
110
|
+
end
|
111
|
+
def clear
|
112
|
+
buttons = SK::Browser.all({tag_name: 'button'})
|
113
|
+
button = buttons.find { |el| el.text == 'Clear' }
|
114
|
+
button.click if button
|
115
|
+
sleep 1
|
116
|
+
end
|
117
|
+
def select(value)
|
118
|
+
containers = SK::Browser.all({class: 'k-animation-container'})
|
119
|
+
container = containers.find { |c| c.displayed? }
|
120
|
+
sleep 1 # to find the textbox???
|
121
|
+
textbox = container.find_element({class: 'k-textbox'})
|
122
|
+
trace "filter's textbox is not visisble" unless textbox.displayed?
|
123
|
+
textbox.send_keys(value)
|
124
|
+
button = container.find_element({class: 'k-primary'})
|
125
|
+
button.click()
|
126
|
+
sleep 1
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
module SK::Kendo
|
131
|
+
class << self
|
132
|
+
|
133
|
+
def grid()
|
134
|
+
return SK::KGrid.new()
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
data/lib/link.rb
ADDED
data/lib/page.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# TODO: move some of the browser methods to page
|
2
|
+
|
3
|
+
class SK::Page
|
4
|
+
|
5
|
+
def initialize(base,page)
|
6
|
+
@url = "#{base}/#{page}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def url
|
10
|
+
@url
|
11
|
+
end
|
12
|
+
|
13
|
+
# def goto
|
14
|
+
# SK::Browser.gotox(url)
|
15
|
+
# end
|
16
|
+
|
17
|
+
def goto(opts={})
|
18
|
+
query = opts.map { |k,v| k.to_s+"="+v.to_s }.join("&")
|
19
|
+
url = @url
|
20
|
+
url += "?" + query unless query == ""
|
21
|
+
trace("goto: url=#{url}")
|
22
|
+
SK::Browser.gotox(url)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def present?
|
27
|
+
urls_match?
|
28
|
+
end
|
29
|
+
|
30
|
+
def urls_match?
|
31
|
+
# change to the looser include instead of equal
|
32
|
+
return true if SK::Browser.url.include? self.url
|
33
|
+
|
34
|
+
# return true if SK::Browser.url == self.url # old code
|
35
|
+
|
36
|
+
# because we almost always need to see some clues when the expected page is
|
37
|
+
# not present we trace some outpout the the tester
|
38
|
+
trace "SK::Page url_match? expected: #{url} did not match current: #{SK::Browser.url}"
|
39
|
+
false # returned
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_content?(content)
|
43
|
+
result = SK::Browser.source.include? content
|
44
|
+
# trace "missing content = #{content}" unless result
|
45
|
+
return result
|
46
|
+
end
|
47
|
+
|
48
|
+
def here?(text)
|
49
|
+
return false unless urls_match?
|
50
|
+
# urls do match, but also require that
|
51
|
+
has_content? text
|
52
|
+
end
|
53
|
+
|
54
|
+
def source
|
55
|
+
SK::Browser.source
|
56
|
+
end
|
57
|
+
|
58
|
+
def title
|
59
|
+
SK::Browser.title
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/radio_set.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class SK::RadioSet
|
2
|
+
|
3
|
+
def initialize(locator)
|
4
|
+
# get all the elements for the radio
|
5
|
+
@els = SK::Browser.all(locator)
|
6
|
+
# trace("radio els = #{@els}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def select(value)
|
10
|
+
# trace("radio els = #{@els}")
|
11
|
+
@els.each do |el|
|
12
|
+
#trace "radio looking at #{el.text}: #{value}"
|
13
|
+
if el.attribute('value') == value
|
14
|
+
el.click
|
15
|
+
break
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/shiken.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
|
3
|
+
# NOTE: the requires are at bottom
|
4
|
+
|
5
|
+
module SK
|
6
|
+
|
7
|
+
@driver = nil
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def version
|
12
|
+
v = "0.0.1"
|
13
|
+
# Trace.trace "version: #{v}"
|
14
|
+
return v
|
15
|
+
end
|
16
|
+
|
17
|
+
def init(seconds=2, browser=:safari)
|
18
|
+
|
19
|
+
# browser_stack_url = "http://patconley2:LBqJyYGj5TsyNBWe2Bjy@hub.browserstack.com/wd/hub"
|
20
|
+
# $driver = Selenium::WebDriver.for(:remote, :url => browser_stack_url)
|
21
|
+
|
22
|
+
# Trace.trace "SK::init"
|
23
|
+
# seconds = max time to wait for the element to appear
|
24
|
+
wait = Selenium::WebDriver::Wait.new(:timeout => seconds)
|
25
|
+
|
26
|
+
profile = Selenium::WebDriver::Firefox::Profile.new
|
27
|
+
profile["javascript.enabled"] = false # NOT SURE WHY????
|
28
|
+
# trace "wt init download path = #{SK::Downloads.path}"
|
29
|
+
profile['browser.download.dir'] = SK::Downloads.path
|
30
|
+
#
|
31
|
+
# # The value of browser.download.folderList can be set to either 0, 1, or 2.
|
32
|
+
# # When set to 0, Firefox will save all files downloaded via the browser on the
|
33
|
+
# # user's desktop. When set to 1, these downloads are stored in the Downloads folder.
|
34
|
+
# # When set to 2, the location specified for the most recent download is utilized again
|
35
|
+
profile['browser.download.folderList'] = 2 # use the specified directory
|
36
|
+
|
37
|
+
# Suppress "open with" dialog for a file types
|
38
|
+
profile['browser.helperApps.neverAsk.saveToDisk'] = "text/csv,application/pdf,application/xls,image/tiff"
|
39
|
+
profile['browser.helperApps.alwaysAsk.force'] = false
|
40
|
+
# disable Firefox's built-in PDF viewer
|
41
|
+
profile["pdfjs.disabled"] = true
|
42
|
+
# disable Adobe Acrobat PDF preview plugin
|
43
|
+
profile["plugin.scan.plid.all"] = false
|
44
|
+
profile["plugin.scan.Acrobat"] = "99.0"
|
45
|
+
|
46
|
+
@driver = Selenium::WebDriver.for(browser)
|
47
|
+
|
48
|
+
# NOTE 3/4/17 the profile code failed for safari... see use of FIREFOX in creating profile!!
|
49
|
+
|
50
|
+
# @driver = Selenium::WebDriver.for(browser, :profile => profile)
|
51
|
+
# $driver = Selenium::WebDriver.for :firefox # :safari :chrome
|
52
|
+
|
53
|
+
# TODO: what does this do... overried the above???
|
54
|
+
# @driver.manage.timeouts.implicit_wait = 5
|
55
|
+
|
56
|
+
@driver # returned ????
|
57
|
+
end
|
58
|
+
|
59
|
+
def quit
|
60
|
+
# Trace.trace "SK::quit"
|
61
|
+
driver.quit if driver
|
62
|
+
@driver = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def driver
|
66
|
+
@driver
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'shiken/trace'
|
72
|
+
require 'shiken/browser'
|
73
|
+
require 'shiken/page'
|
74
|
+
require 'shiken/element'
|
75
|
+
require 'shiken/field'
|
76
|
+
require 'shiken/clickable'
|
77
|
+
require 'shiken/button'
|
78
|
+
require 'shiken/link'
|
79
|
+
require 'shiken/dropdown'
|
80
|
+
require 'shiken/radio_set'
|
81
|
+
require 'shiken/kendo'
|
82
|
+
require 'shiken/table'
|
83
|
+
require 'shiken/downloads'
|
data/lib/table.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
class SK::Cell < SK::Element
|
2
|
+
|
3
|
+
# row is an element that "remembers" it's index in the row
|
4
|
+
|
5
|
+
attr_reader :index
|
6
|
+
|
7
|
+
def initialize(locator,index)
|
8
|
+
super(locator) # creates the el and locator
|
9
|
+
@index = index # remember your position
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_num
|
13
|
+
# trace "cell = #{cell.text}"
|
14
|
+
sign = self.text.include?('(') ? -1 : 1
|
15
|
+
sign * self.text.gsub(/[$,()]/, '').to_f
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"<SK::Cell #{@index} [#{self.text}]>"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class SK::Row < SK::Element
|
25
|
+
|
26
|
+
# row is an element that "remembers" it's index in the table
|
27
|
+
|
28
|
+
attr_reader :index
|
29
|
+
|
30
|
+
def initialize(locator,index)
|
31
|
+
super(locator) # creates the el and locator
|
32
|
+
@index = index # remember your position
|
33
|
+
end
|
34
|
+
|
35
|
+
def cells()
|
36
|
+
ds = self.el.find_elements({tag_name: 'td'})
|
37
|
+
ds.map.with_index { |e,i| SK::Cell.new(e,i) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
text = cells.map { |cell| cell.text }.join("][")
|
42
|
+
"<SK::Row #{@index} [#{text}] >"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class SK::Table < SK::Element
|
47
|
+
|
48
|
+
def rows
|
49
|
+
rs = self.el.find_elements({tag_name: 'tr'})
|
50
|
+
SK::Trace.debug "SK::Table el rows length = #{rs.length}"
|
51
|
+
ws = rs.map.with_index { |r,i| SK::Row.new(r,i) }
|
52
|
+
SK::Trace.debug "SK::Table wt rows length = #{ws.length}"
|
53
|
+
ws # returned
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/trace.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module SK::Trace
|
4
|
+
|
5
|
+
DEBUG = 3
|
6
|
+
WARN = 2
|
7
|
+
ERROR = 1
|
8
|
+
QUIET = 0
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
@level = DEBUG
|
13
|
+
|
14
|
+
def level=(value)
|
15
|
+
case value
|
16
|
+
when :debug
|
17
|
+
n = DEBUG
|
18
|
+
when :warn
|
19
|
+
n = WARN
|
20
|
+
when :error
|
21
|
+
n = ERROR
|
22
|
+
when :quiet
|
23
|
+
n = QUIET
|
24
|
+
else
|
25
|
+
n = value.to_i
|
26
|
+
end
|
27
|
+
@level = n
|
28
|
+
end
|
29
|
+
|
30
|
+
def level
|
31
|
+
@level
|
32
|
+
end
|
33
|
+
|
34
|
+
def trace(s)
|
35
|
+
write "SK::Trace : #{s}".cyan, DEBUG
|
36
|
+
end
|
37
|
+
def debug(s)
|
38
|
+
write "SK::Debug : #{s}".cyan, DEBUG
|
39
|
+
end
|
40
|
+
def error(s)
|
41
|
+
write "SK::Error: #{s}".red, ERROR
|
42
|
+
end
|
43
|
+
def notice(s)
|
44
|
+
write "SK::Notice: #{s}".yellow, WARN
|
45
|
+
end
|
46
|
+
def warn(s)
|
47
|
+
write "SK::Warn : #{s}".yellow, WARN
|
48
|
+
end
|
49
|
+
|
50
|
+
def write(text,lev)
|
51
|
+
puts text if level >= lev
|
52
|
+
end
|
53
|
+
|
54
|
+
# def xfail(num,s)
|
55
|
+
# error num, s
|
56
|
+
# fail
|
57
|
+
# end
|
58
|
+
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shiken
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pat Conley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A layer to simplify ruby use of selenium
|
14
|
+
email: pconley312@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/browser.rb
|
20
|
+
- lib/button.rb
|
21
|
+
- lib/clickable.rb
|
22
|
+
- lib/downloads.rb
|
23
|
+
- lib/dropdown.rb
|
24
|
+
- lib/element.rb
|
25
|
+
- lib/field.rb
|
26
|
+
- lib/kendo.rb
|
27
|
+
- lib/link.rb
|
28
|
+
- lib/page.rb
|
29
|
+
- lib/radio_set.rb
|
30
|
+
- lib/shiken.rb
|
31
|
+
- lib/table.rb
|
32
|
+
- lib/trace.rb
|
33
|
+
homepage: https://github.com/pconley/Shiken.git
|
34
|
+
licenses:
|
35
|
+
- MIT
|
36
|
+
metadata: {}
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 2.5.1
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: A Selenium Facade in Ruby
|
57
|
+
test_files: []
|