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