zander 0.1.0
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/.gitignore +38 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +2 -0
- data/bin/zander +62 -0
- data/lib/zander.rb +59 -0
- data/lib/zander/action.rb +225 -0
- data/lib/zander/cmd_mapper.rb +21 -0
- data/lib/zander/ht.rb +20 -0
- data/lib/zander/manual.rb +17 -0
- data/lib/zander/site.rb +90 -0
- data/lib/zander/sites.rb +88 -0
- data/lib/zander/util.rb +8 -0
- data/lib/zander/version.rb +3 -0
- data/share/actions.yaml +56 -0
- data/share/sites.yaml +17 -0
- data/zander.gemspec +19 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3875d0299b3297f68eaa7e38a203019569f21a83
|
4
|
+
data.tar.gz: c0a8da31fe54b84197cc417c763e87fc8a99206c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a532118b690fd416ad00181a5383ad1d2aea20b1cb547daedf52051ed7b7276fd8fda7e63b591c0bf4da3df8b98436a650e83cc78e55caae3ad0718f0ffd7918
|
7
|
+
data.tar.gz: 243f56c183f86eb198a7630964994530b86cb2df4e3eef1a688206630abd971b31ffdfa5e22e182f51fc434128cd4fb48d3f9f4c18f989981037100c21489265
|
data/.gitignore
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
share/html_v2.yaml
|
2
|
+
share/chromedriver
|
3
|
+
|
4
|
+
*.gem
|
5
|
+
*.rbc
|
6
|
+
/.config
|
7
|
+
/coverage/
|
8
|
+
/InstalledFiles
|
9
|
+
/pkg/
|
10
|
+
/spec/reports/
|
11
|
+
/test/tmp/
|
12
|
+
/test/version_tmp/
|
13
|
+
/tmp/
|
14
|
+
|
15
|
+
## Specific to RubyMotion:
|
16
|
+
.dat*
|
17
|
+
.repl_history
|
18
|
+
build/
|
19
|
+
|
20
|
+
## Documentation cache and generated files:
|
21
|
+
/.yardoc/
|
22
|
+
/_yardoc/
|
23
|
+
/doc/
|
24
|
+
/rdoc/
|
25
|
+
|
26
|
+
## Environment normalisation:
|
27
|
+
/.bundle/
|
28
|
+
/lib/bundler/man/
|
29
|
+
|
30
|
+
# for a library or gem, you might want to ignore these files since the code is
|
31
|
+
# intended to run in multiple environments; otherwise, check them in:
|
32
|
+
# Gemfile.lock
|
33
|
+
# .ruby-version
|
34
|
+
# .ruby-gemset
|
35
|
+
|
36
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
37
|
+
.rvmrc
|
38
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Spencer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
data/bin/zander
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#######################################################################################################
|
3
|
+
# This executable file is an example three ways to properly use 'zander'. #
|
4
|
+
# 1.) Demo version #
|
5
|
+
# Simply run this executable file. #
|
6
|
+
# $ ./zander #
|
7
|
+
# #
|
8
|
+
# The demo version will use the fiels provided in the gem's share directory. #
|
9
|
+
# share/sites.yaml #
|
10
|
+
# share/actions.yaml #
|
11
|
+
# #
|
12
|
+
# These files attempt to log on to a google account. #
|
13
|
+
# The user name and password are invalid, and the sign in will fail #
|
14
|
+
# #
|
15
|
+
# 2.) Custom files version #
|
16
|
+
# You must provid both the sites.yaml and actions.yaml #
|
17
|
+
# #
|
18
|
+
# #
|
19
|
+
# 3.) Specific version #
|
20
|
+
# This version allows you to provide an array of the websites you want exectued #
|
21
|
+
# [0] will only execute the actions for the first URL #
|
22
|
+
# [0,1] will execute the actions for the first two URLs, etc #
|
23
|
+
# #
|
24
|
+
# Note: In some cases full control over the driver and loggin is required #
|
25
|
+
# Include the Zander::Manual module which has the @@driver and @@log variables #
|
26
|
+
# overide the self.drive method and make sure to add a 'manual' call in your actions.yaml #
|
27
|
+
# a full example is shown at the bottom #
|
28
|
+
# #
|
29
|
+
#######################################################################################################
|
30
|
+
#require 'zander'
|
31
|
+
require '/Users/sc/projects/ruby/appeals/lib/zander'
|
32
|
+
#######################################################################################################
|
33
|
+
### 1.) Demo - Uses share/sites.yaml and shre/actions.yaml ###
|
34
|
+
Zander.run ###
|
35
|
+
#######################################################################################################
|
36
|
+
|
37
|
+
#######################################################################################################
|
38
|
+
### 2.) Custom files - Example specifying script fiels ###
|
39
|
+
#DIR = File.join((File.expand_path('../', File.expand_path(File.dirname(__FILE__)))), "/share/") ###
|
40
|
+
#Zander.run(sites: "#{DIR}sites.yaml", actions: "#{DIR}actions.yaml") ###
|
41
|
+
#######################################################################################################
|
42
|
+
|
43
|
+
#######################################################################################################
|
44
|
+
### 3.) Specific - Eample specifing which URL entries to process ###
|
45
|
+
#Zander.run(sites: "#{DIR}sites.yaml", actions: "#{DIR}actions.yaml", steps: [0]) ###
|
46
|
+
#######################################################################################################
|
47
|
+
|
48
|
+
#######################################################################################################
|
49
|
+
### Example file that uses the manual driving method ###
|
50
|
+
#######################################################################################################
|
51
|
+
# require 'zander' #
|
52
|
+
# include Zander::Manual #
|
53
|
+
# Zander::Manual.module_eval do #
|
54
|
+
# def self.drive #
|
55
|
+
# driver = @@driver #
|
56
|
+
# log = @@log #
|
57
|
+
# log.debug("I got the logger and the driver") #
|
58
|
+
# log.debug("current url: #{driver.current_url}") #
|
59
|
+
# end #
|
60
|
+
# end #
|
61
|
+
# Zander.run #
|
62
|
+
#######################################################################################################
|
data/lib/zander.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#####################################################################################################################
|
2
|
+
# ZANDER THE GREAT #
|
3
|
+
# ,__ #
|
4
|
+
# | `'. #
|
5
|
+
# __ |`-._/_.:---`-.._ #
|
6
|
+
# \='. _/..--'`__ `'-._ #
|
7
|
+
# \- '-.--"` === / o `', #
|
8
|
+
# )= ( .--_ | _.' #
|
9
|
+
# /_=.'-._ {=_-_ | .--`-. #
|
10
|
+
# /_.' `\`'-._ '-= \ _.' #
|
11
|
+
# ) _.-'`'-.. _..-'` #
|
12
|
+
# /_.' `/";';`| #
|
13
|
+
# \` .'/ #
|
14
|
+
# '--' #
|
15
|
+
# #
|
16
|
+
# Zander is a framerwork for facilitating automated Selenium webdriver testing #
|
17
|
+
# URLs and corrisponding log in credentials are defined in the share/sites.yaml #
|
18
|
+
# The actions to be taken and the element identifiers are defined in share/actions.yaml #
|
19
|
+
#####################################################################################################################
|
20
|
+
lib = File.expand_path('../../lib/', __FILE__)
|
21
|
+
$:.unshift lib unless $:.include?(lib)
|
22
|
+
# p $:.dup
|
23
|
+
|
24
|
+
module Zander
|
25
|
+
def self.run(sites: nil, actions: nil, steps: nil)
|
26
|
+
|
27
|
+
if steps == nil
|
28
|
+
steps = ARGV[0].split(',').map(&:to_i) unless ARGV[0] == nil
|
29
|
+
end
|
30
|
+
|
31
|
+
if (sites != nil && actions != nil)
|
32
|
+
zander = Sites.new(sites,steps)
|
33
|
+
zander.add_actions(actions)
|
34
|
+
else
|
35
|
+
zander = Sites.new(Util.get_path('share/sites.yaml'),steps)
|
36
|
+
zander.add_actions(Util.get_path('share/actions.yaml'))
|
37
|
+
end
|
38
|
+
|
39
|
+
zander.set_log_level Logger::DEBUG
|
40
|
+
zander.sites.each do |site|
|
41
|
+
site.drive
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
require 'zander/cmd_mapper'
|
47
|
+
require 'zander/sites'
|
48
|
+
require 'zander/action'
|
49
|
+
require 'zander/manual'
|
50
|
+
require 'zander/site'
|
51
|
+
require 'zander/util'
|
52
|
+
require 'zander/ht'
|
53
|
+
|
54
|
+
require 'selenium-webdriver'
|
55
|
+
require 'yaml'
|
56
|
+
require 'logger'
|
57
|
+
|
58
|
+
|
59
|
+
|
@@ -0,0 +1,225 @@
|
|
1
|
+
module Zander
|
2
|
+
class Action
|
3
|
+
|
4
|
+
def initialize(site, driver, log, hash)
|
5
|
+
@site = site
|
6
|
+
@log = log
|
7
|
+
@driver = driver
|
8
|
+
@private_variable = false
|
9
|
+
parse_before_action(hash)
|
10
|
+
parse_action_type(hash)
|
11
|
+
parse_after_action(hash)
|
12
|
+
@log.debug("Created #{self.class.name} #{self}")
|
13
|
+
end
|
14
|
+
|
15
|
+
def private_variable?
|
16
|
+
@private_variable
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_before_action(hash)
|
20
|
+
if hash.has_key?(:before_action)
|
21
|
+
@before_action = hash[:before_action]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_after_action(hash)
|
26
|
+
if hash.has_key?(:after_action)
|
27
|
+
@after_action = hash[:after_action]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_action_type(hash)
|
32
|
+
if hash.has_key? :action_type
|
33
|
+
@action_type = hash[:action_type]
|
34
|
+
parse_type(hash)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_type(hash)
|
39
|
+
case @action_type
|
40
|
+
when 'click_element','print_element','switch_to_iframe'
|
41
|
+
set_identifier(hash)
|
42
|
+
when 'input_variable'
|
43
|
+
set_identifier(hash)
|
44
|
+
set_variable(hash)
|
45
|
+
when 'input_data'
|
46
|
+
set_identifier(hash)
|
47
|
+
set_data(hash)
|
48
|
+
when 'done','switch_to_new_window'
|
49
|
+
else
|
50
|
+
@log.error("#{__method__} can't handle action_type #{@action_type} for #{self}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_identifier(hash)
|
55
|
+
if hash.has_key? :identifier
|
56
|
+
identifier = hash[:identifier]
|
57
|
+
@finder_key = identifier.keys[0]
|
58
|
+
@finder_value = identifier.values[0]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_variable(hash)
|
63
|
+
if hash.has_key? :variable
|
64
|
+
@private_variable = true if hash[:variable] == "password"
|
65
|
+
@variable = @site.instance_variable_get("@#{hash[:variable]}".to_sym)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_data(hash)
|
70
|
+
if hash.has_key? :data
|
71
|
+
@data = hash[:data]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def drive
|
76
|
+
element = get_element
|
77
|
+
do_action(element)
|
78
|
+
sleep(1)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_element
|
82
|
+
case @action_type
|
83
|
+
when 'click_element', 'input_variable', 'print_element','switch_to_iframe', 'input_data'
|
84
|
+
get_element_from_finders
|
85
|
+
when 'done','switch_to_new_window'
|
86
|
+
else
|
87
|
+
@log.error("Can't handle action_type #{@action_type} for #{self}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_element_from_finders
|
92
|
+
if @finder_key != nil
|
93
|
+
@log.debug("Find element #{@finder_key}='#{@finder_value}'")
|
94
|
+
case @finder_key
|
95
|
+
when :id,:name,:xpath
|
96
|
+
element = @driver.find_element(@finder_key => @finder_value)
|
97
|
+
when :css
|
98
|
+
when :class
|
99
|
+
# could be multiple // might need to add a n: 0
|
100
|
+
element = @driver.find_element(@finder_key => @finder_value)
|
101
|
+
when :class_name
|
102
|
+
when :link
|
103
|
+
when :link_text
|
104
|
+
when :partial_link_text
|
105
|
+
when :tag_name
|
106
|
+
else
|
107
|
+
@log.error("#{__method__} invalid finder_key, must be a Selenium::WebDriver::SearchContext::FINDERS:")
|
108
|
+
@log.error(Selenium::WebDriver::SearchContext::FINDERS.keys)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
@log.error("#{__method__} tried to locate element, but @finder_key == nil")
|
112
|
+
end
|
113
|
+
element
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_elements_from_finders
|
117
|
+
if @finder_key != nil
|
118
|
+
@log.debug("Find elements #{@finder_key}='#{@finder_value}'")
|
119
|
+
case @finder_key
|
120
|
+
when :id
|
121
|
+
element = @driver.find_elements(@finder_key => @finder_value)
|
122
|
+
when :xpath
|
123
|
+
element = @driver.find_elements(:xpath, identifier[:xpath])
|
124
|
+
when :css
|
125
|
+
when :class
|
126
|
+
when :class_name
|
127
|
+
when :name
|
128
|
+
when :link
|
129
|
+
when :link_text
|
130
|
+
when :partial_link_text
|
131
|
+
when :tag_name
|
132
|
+
else
|
133
|
+
@log.error("#{__method__} invalid finder_key, must be a Selenium::WebDriver::SearchContext::FINDERS:")
|
134
|
+
@log.error(Selenium::WebDriver::SearchContext::FINDERS.keys)
|
135
|
+
end
|
136
|
+
else
|
137
|
+
@log.error("#{__method__} tried to locate element, but @finder_key == nil")
|
138
|
+
end
|
139
|
+
element
|
140
|
+
end
|
141
|
+
|
142
|
+
def do_action(element)
|
143
|
+
@log.debug("try #{@action_type} #{@finder_key}='#{@finder_value}'")
|
144
|
+
case @action_type
|
145
|
+
when 'click_element'
|
146
|
+
element.click
|
147
|
+
when 'input_variable'
|
148
|
+
@log.info("Send '#{masked_variable}' to #{@finder_key}='#{@finder_value}' element")
|
149
|
+
element.clear
|
150
|
+
element.send_keys @variable
|
151
|
+
when 'print_element'
|
152
|
+
@log.info("text: #{element.text}
|
153
|
+
\t\ttag_name: #{element.tag_name}
|
154
|
+
\t\tlocation: (#{element.location.x},#{element.location.y})")
|
155
|
+
when 'input_data'
|
156
|
+
@log.info("Send '#{@data}' to #{@finder_key}='#{@finder_value}' element")
|
157
|
+
element.clear
|
158
|
+
element.send_keys @data
|
159
|
+
when 'switch_to_iframe'
|
160
|
+
@log.error("Can't locate iframe with #{@finder_key}='#{@finder_value}'. iframe can only be located via 'id'") unless @finder_key == :id
|
161
|
+
@driver.switch_to.frame(@finder_value)
|
162
|
+
sleep(3)
|
163
|
+
when 'switch_to_new_window'
|
164
|
+
@log.debug("'main_window' saved")
|
165
|
+
main_window = @driver.window_handle
|
166
|
+
new_window = nil
|
167
|
+
@driver.window_handles.each do |window|
|
168
|
+
if main_window != nil && main_window != window
|
169
|
+
new_window = window
|
170
|
+
end
|
171
|
+
end
|
172
|
+
@log.error("Couldn't find new window") if new_window == nil
|
173
|
+
@driver.switch_to.window(new_window)
|
174
|
+
@log.debug("Switched to new window #{driver.current_url}")
|
175
|
+
close_other_windows
|
176
|
+
when 'done'
|
177
|
+
close_other_windows
|
178
|
+
@driver.quit if @site.last?
|
179
|
+
else
|
180
|
+
@log.error("Could not preform action #{@action_type} on #{@finder_key}='#{@finder_value}' element")
|
181
|
+
end
|
182
|
+
@log.info("#{@action_type} #{@finder_key}='#{@finder_value}'")
|
183
|
+
end
|
184
|
+
|
185
|
+
def close_other_windows
|
186
|
+
main_window = @driver.window_handle
|
187
|
+
@driver.window_handles.each do |other_window|
|
188
|
+
if main_window != nil && main_window != other_window
|
189
|
+
@driver.switch_to.window(other_window)
|
190
|
+
url = @driver.current_url
|
191
|
+
@driver.close
|
192
|
+
@log.debug("Closed window '#{url}'")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
@driver.switch_to.window(main_window)
|
196
|
+
end
|
197
|
+
|
198
|
+
def masked_variable
|
199
|
+
if private_variable?
|
200
|
+
clear = 3 #keep last 3 in clear text
|
201
|
+
masked = @variable[0];
|
202
|
+
(@variable.length-(clear+1)).times do
|
203
|
+
masked << '*'
|
204
|
+
end
|
205
|
+
masked << @variable[(@variable.length-clear)...@variable.length]
|
206
|
+
else
|
207
|
+
@variable
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def inspect
|
212
|
+
to_s
|
213
|
+
end
|
214
|
+
|
215
|
+
def to_s
|
216
|
+
"<#{self.class.name}:0x#{'%x'%(self.object_id<<1)} @site=<Site:0x#{'%x'%(@site.object_id<<1)} ...> @action_type=#{@action_type} >"
|
217
|
+
end
|
218
|
+
|
219
|
+
private :to_s, :masked_variable, :close_other_windows
|
220
|
+
private :do_action, :get_element_from_finders, :get_elements_from_finders
|
221
|
+
private :get_element, :set_data, :set_variable, :set_identifier
|
222
|
+
private :parse_after_action, :parse_action_type, :parse_before_action
|
223
|
+
private :private_variable?
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Zander
|
2
|
+
module CommandMapper
|
3
|
+
def self.map(site, driver, log, cmd)
|
4
|
+
@site = site
|
5
|
+
@driver = driver
|
6
|
+
@log = log
|
7
|
+
@cmd = cmd
|
8
|
+
if cmd.is_a?(Hash)
|
9
|
+
if cmd.has_key? :action
|
10
|
+
Action.new(@site, @driver, @log, @cmd)
|
11
|
+
elsif cmd.has_key? :manual
|
12
|
+
Manual.set(@driver, @log)
|
13
|
+
Manual.drive
|
14
|
+
Manual.destroy
|
15
|
+
else
|
16
|
+
@log.error("Can't mapp comand for #{cmd.inspect}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/zander/ht.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Hash Tools
|
2
|
+
class Hash
|
3
|
+
def change_keys(hash)
|
4
|
+
hash.each do |key,value|
|
5
|
+
if !key.is_a?(Hash) && !value.is_a?(Hash)
|
6
|
+
hash = hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
7
|
+
else
|
8
|
+
hash.map{ |k,v| hash[k] = change_keys(v) if v.is_a?(Hash) }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
hash = hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def keys_to_sym
|
16
|
+
change_keys(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
private :change_keys
|
20
|
+
end
|
data/lib/zander/site.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module Zander
|
2
|
+
class Site
|
3
|
+
|
4
|
+
def initialize(parent:, hash:, driver:, log:)
|
5
|
+
@actions = Array.new
|
6
|
+
@parent = parent unless parent == nil
|
7
|
+
@log = log unless log == nil
|
8
|
+
@driver = driver unless driver == nil
|
9
|
+
create_attr_accessor(hash)
|
10
|
+
@log.debug("Created #{self}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_variables
|
14
|
+
Hash[instance_variables.map { |name| [name, instance_variable_get(name)] } ]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Map each hash in actions array with CommandMapper.
|
18
|
+
# If the result is an Action object, add it to the acctions array
|
19
|
+
def add_actions(actions)
|
20
|
+
actions.each do |action|
|
21
|
+
# convert keys in action hash to symbols
|
22
|
+
puts "Action Hash::: #{action}"
|
23
|
+
action = action.keys_to_sym
|
24
|
+
@log.debug("Create action #{action}")
|
25
|
+
obj = CommandMapper.map(self, @driver, @log, action)
|
26
|
+
@actions.push(obj) if obj.is_a?(Action)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Is this instance the last Site object defined in share/sites.yaml
|
31
|
+
def last?
|
32
|
+
@url != nil && @parent.sites.last.respond_to?(:url) && @parent.sites.last.url == @url
|
33
|
+
end
|
34
|
+
|
35
|
+
def drive()
|
36
|
+
@driver.navigate.to self.url
|
37
|
+
@log.info("Opened #{@driver.current_url}")
|
38
|
+
@actions.each do |action|
|
39
|
+
action.drive
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates instance variables for each key value in a given hash
|
44
|
+
# { url => 'www.google.com', user_name => 'coolUser', password => 'secretPassword' }
|
45
|
+
# would create the following instance variables
|
46
|
+
# @url = 'www.google.com'
|
47
|
+
# @user_name = 'coolUser'
|
48
|
+
# @password = 'secretPassword'
|
49
|
+
def create_attr_accessor(hash)
|
50
|
+
hash.each do |key,value|
|
51
|
+
value = value.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo} if value.is_a?(Hash)
|
52
|
+
instance_variable_set("@#{key}", value)
|
53
|
+
self.class.__send__(:attr_accessor, "#{key}")
|
54
|
+
self.__send__("#{key}=", value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Helper method for to_s
|
59
|
+
# returns a pretty string of all the actions
|
60
|
+
def get_vars
|
61
|
+
vars = ""
|
62
|
+
vh = Hash[self.instance_variables.map {|name|[name, instance_variable_get(name)]}]
|
63
|
+
vh.each_with_index do |(key,value), index|
|
64
|
+
if key.to_s == "@parent"
|
65
|
+
vars << @parent.get_abbr_to_s
|
66
|
+
elsif key.to_s == "@actions"
|
67
|
+
@actions.each do |action|
|
68
|
+
vars << action.to_s
|
69
|
+
end
|
70
|
+
elsif index == vh.size - 1
|
71
|
+
vars << "#{key}=\"#{value}\""
|
72
|
+
else
|
73
|
+
"#{key}=\"#{value}\", "
|
74
|
+
end
|
75
|
+
end
|
76
|
+
vars
|
77
|
+
end
|
78
|
+
|
79
|
+
def inspect
|
80
|
+
to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
"<#{self.class.name}:0x#{'%x'%(self.object_id<<1)} #{get_vars}"
|
85
|
+
end
|
86
|
+
|
87
|
+
private :to_s, :get_vars, :create_attr_accessor, :get_variables
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
data/lib/zander/sites.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module Zander
|
2
|
+
class Sites
|
3
|
+
IMPLICIT_WAIT = 10
|
4
|
+
YAML_URL = 'URL'
|
5
|
+
YAML_ACTIONS = 'ACTIONS'
|
6
|
+
LOG_FILE = STDOUT # 'log.txt'
|
7
|
+
|
8
|
+
attr_reader :sites
|
9
|
+
|
10
|
+
def initialize(yaml_file, steps = nil)
|
11
|
+
@sites_yaml_file = yaml_file
|
12
|
+
@log = Logger.new(LOG_FILE,10,1024000)
|
13
|
+
@log.level = Logger::DEBUG
|
14
|
+
@sites = Array.new
|
15
|
+
### FIREFOX PROFILE
|
16
|
+
profile = Selenium::WebDriver::Firefox::Profile.new
|
17
|
+
profile['browser.download.dir'] = '/Users/sc/Desktop/appeals/HCFA_AND_POTF'
|
18
|
+
profile['browser.download.folderList'] = 2
|
19
|
+
profile['browser.helperApps.neverAsk.saveToDisk'] = "application/pdf"
|
20
|
+
profile['browser.download.alertOnEXEOpen'] = false
|
21
|
+
profile['browser.download.manager.showWhenStarting'] = false
|
22
|
+
profile['browser.download.manager.focusWhenStarting'] = false
|
23
|
+
profile['browser.helperApps.alwaysAsk.force'] = false
|
24
|
+
profile['browser.download.manager.alertOnEXEOpen'] = false
|
25
|
+
profile['browser.download.manager.closeWhenDone'] = false
|
26
|
+
profile['browser.download.manager.showAlertOnComplete'] = false
|
27
|
+
profile['browser.download.manager.useWindow'] = false
|
28
|
+
profile['browser.download.manager.showWhenStarting'] = false
|
29
|
+
profile['services.sync.prefs.sync.browser.download.manager.showWhenStarting'] = false
|
30
|
+
profile['pdfjs.disabled'] = true
|
31
|
+
## END FIREFOX PROFILE
|
32
|
+
@driver = Selenium::WebDriver.for :firefox, :profile => profile
|
33
|
+
@driver.manage.timeouts.implicit_wait = IMPLICIT_WAIT
|
34
|
+
@log.debug("Selenium timeouts set to #{IMPLICIT_WAIT} seconds")
|
35
|
+
yaml = YAML::load(File.read(@sites_yaml_file))
|
36
|
+
if yaml.has_key? 'WEBSITES'
|
37
|
+
yaml['WEBSITES'].each_with_index do |site, index|
|
38
|
+
if steps == nil
|
39
|
+
@log.debug("Create Site #{site}")
|
40
|
+
@sites.push(Site.new(parent: self, hash: site, driver: @driver, log: @log))
|
41
|
+
else
|
42
|
+
if steps.include?(index)
|
43
|
+
@log.debug("Create Site #{site}")
|
44
|
+
@sites.push(Site.new(parent: self, hash: site, driver: @driver, log: @log))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_log_level(level)
|
53
|
+
@log.level = level
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_site(url)
|
57
|
+
@sites.each do |site|
|
58
|
+
return site if site.url == url
|
59
|
+
end
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_abbr_to_s
|
64
|
+
"<#{self.class.name}:0x#{'%x'%(self.object_id<<1)} ...>"
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_actions(yaml_file)
|
68
|
+
yaml = YAML::load(File.read(yaml_file))
|
69
|
+
if yaml.has_key? 'WEBSITES'
|
70
|
+
yaml['WEBSITES'].each do |website|
|
71
|
+
if website.has_key?(YAML_URL) && website.has_key?(YAML_ACTIONS)
|
72
|
+
site = self.get_site(website[YAML_URL])
|
73
|
+
if site != nil
|
74
|
+
@log.debug("Add #{website[YAML_ACTIONS]}")
|
75
|
+
site.add_actions(website[YAML_ACTIONS])
|
76
|
+
else
|
77
|
+
@log.error("Error:: '#{website[YAML_URL]}' in #{yaml_file} was not in #{@sites_yaml_file}")
|
78
|
+
end
|
79
|
+
else
|
80
|
+
@log.error("#{yaml_file} doesn't contain a #{YAML_URL} array")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
@log.error("#{yaml_file} doesn't contain a WEBSITES array")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/zander/util.rb
ADDED
data/share/actions.yaml
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
##
|
2
|
+
# Attribues is a hash of hashes the first hash will always be the way to identify an
|
3
|
+
# html tag ie (id: user_id or class: members) The action hash will consist of a simple action like click
|
4
|
+
# or could contain a complex action like input which will contain a field that mapps to a website object
|
5
|
+
#
|
6
|
+
# Gmail login example:
|
7
|
+
#WEBSTIES:
|
8
|
+
# - URL: https://accounts.google.com
|
9
|
+
# id: Email
|
10
|
+
# action:
|
11
|
+
# - variable: user_name
|
12
|
+
# id: Passwd
|
13
|
+
# action:
|
14
|
+
# - variable: password
|
15
|
+
# id: signIn
|
16
|
+
# action: click
|
17
|
+
# This yaml file would know that for https://accounts.google.com it should find the html attribute
|
18
|
+
# <input id="Email" name="Email" placeholder="Email" value="" spellcheck="false" class="" type="email">
|
19
|
+
# and insert the WebSite#user_name value
|
20
|
+
#
|
21
|
+
##
|
22
|
+
WEBSITES:
|
23
|
+
- URL: https://www.google.com/
|
24
|
+
ACTIONS:
|
25
|
+
- action:
|
26
|
+
action_type: click_element
|
27
|
+
identifier:
|
28
|
+
id: gb_70
|
29
|
+
- action:
|
30
|
+
action_type: print_element
|
31
|
+
identifier:
|
32
|
+
class: tagline
|
33
|
+
- action:
|
34
|
+
action_type: input_variable
|
35
|
+
identifier:
|
36
|
+
id: Email
|
37
|
+
variable: user_name
|
38
|
+
- action:
|
39
|
+
action_type: input_variable
|
40
|
+
identifier:
|
41
|
+
id: Passwd
|
42
|
+
variable: password
|
43
|
+
- action:
|
44
|
+
action_type: click_element
|
45
|
+
identifier:
|
46
|
+
id: signIn
|
47
|
+
- manual:
|
48
|
+
- action:
|
49
|
+
action_type: done
|
50
|
+
# - URL:
|
51
|
+
# ATTRIBUTES:
|
52
|
+
# - identifier:
|
53
|
+
# id:
|
54
|
+
# action:
|
55
|
+
# - identifier:
|
56
|
+
# action: done
|
data/share/sites.yaml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#############################################
|
2
|
+
# Browsie can handle multiple arguements all
|
3
|
+
# that is required is that each email have
|
4
|
+
# corresponding password, and that each
|
5
|
+
# argument have a space followed by a '-'
|
6
|
+
# followed by another space, then the argument
|
7
|
+
#
|
8
|
+
# If you don't have one or both of these accounts
|
9
|
+
# delete out the keywords and arguments alltogether.
|
10
|
+
#############################################
|
11
|
+
WEBSITES:
|
12
|
+
- url: https://www.google.com/
|
13
|
+
user_name: "Replace this with your username"
|
14
|
+
password: "Replace this with your password"
|
15
|
+
# - url:
|
16
|
+
# user_name:
|
17
|
+
# password:
|
data/zander.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require 'zander/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'zander'
|
8
|
+
s.version = Zander::VERSION
|
9
|
+
s.date = '2014-11-13'
|
10
|
+
s.licenses = ['MIT']
|
11
|
+
s.summary = "Simple Web Automation"
|
12
|
+
s.description = "Zabner will help automate web testing using Selenium::WebDriver"
|
13
|
+
s.authors = ["Spencer Carlson"]
|
14
|
+
s.email = 'spencerdcarlson@gmail.com'
|
15
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
s.executables = %w(zander)
|
17
|
+
s.homepage = ''
|
18
|
+
s.require_paths = %w(lib)
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zander
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Spencer Carlson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Zabner will help automate web testing using Selenium::WebDriver
|
14
|
+
email: spencerdcarlson@gmail.com
|
15
|
+
executables:
|
16
|
+
- zander
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".gitignore"
|
21
|
+
- Gemfile
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- bin/zander
|
25
|
+
- lib/zander.rb
|
26
|
+
- lib/zander/action.rb
|
27
|
+
- lib/zander/cmd_mapper.rb
|
28
|
+
- lib/zander/ht.rb
|
29
|
+
- lib/zander/manual.rb
|
30
|
+
- lib/zander/site.rb
|
31
|
+
- lib/zander/sites.rb
|
32
|
+
- lib/zander/util.rb
|
33
|
+
- lib/zander/version.rb
|
34
|
+
- share/actions.yaml
|
35
|
+
- share/sites.yaml
|
36
|
+
- zander.gemspec
|
37
|
+
homepage: ''
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
metadata: {}
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 2.2.2
|
58
|
+
signing_key:
|
59
|
+
specification_version: 4
|
60
|
+
summary: Simple Web Automation
|
61
|
+
test_files: []
|
62
|
+
has_rdoc:
|