aaet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,180 @@
1
+ module Aaet
2
+ class Locators
3
+
4
+ def activity
5
+ count = 0
6
+ begin
7
+ JSON.parse(`curl -s #{caps_url}/session/#{driver.session_id}/appium/device/current_activity`)['value']
8
+ rescue
9
+ count+=1
10
+ return if count > 3
11
+ retry
12
+ end
13
+ end
14
+
15
+ def get_center(element)
16
+ location = element.location
17
+ location_x = location.x.to_f
18
+ location_y = location.y.to_f
19
+ size = element.size
20
+ size_width = size.width.to_f
21
+ size_height = size.height.to_f
22
+ x = location_x + (size_width / 2.0)
23
+ y = location_y + (size_height / 2.0)
24
+ {x: x, y: y}
25
+ end
26
+
27
+ def element_center(location, size)
28
+ location_x = location.x.to_f
29
+ location_y = location.y.to_f
30
+ size_width = size.width.to_f
31
+ size_height = size.height.to_f
32
+ x = location_x + (size_width / 2.0)
33
+ y = location_y + (size_height / 2.0)
34
+ {x: x, y: y}
35
+ end
36
+
37
+ def long_press element
38
+ center = get_center(element)
39
+ center.merge!(fingers: 1, duration: 550)
40
+ action = Appium::TouchAction.new.long_press center
41
+ action.release.perform
42
+ end
43
+
44
+ def press coords
45
+ coords.merge!(fingers: 1, duration: 550)
46
+ action = Appium::TouchAction.new.long_press coords
47
+ begin action.release.perform rescue nil end
48
+ end
49
+
50
+ def tap args = {}
51
+ begin
52
+ Appium::TouchAction.new.tap(args).release.perform
53
+ rescue
54
+ nil
55
+ end
56
+ end
57
+
58
+ def tap2 coords
59
+ begin
60
+ Appium::TouchAction.new.tap(coords).release.perform
61
+ rescue
62
+ nil
63
+ end
64
+ end
65
+
66
+ def random_tap
67
+ x = rand(0..@window_size[0])
68
+ y = rand(0..@window_size[1])
69
+ puts "\nRandom Tap Location: { x: #{x}, y: #{y} }\n".yellow
70
+ tap({:x=>x, :y=>y})
71
+ end
72
+
73
+ def pull_to_refresh
74
+ size = get_window_size
75
+ start_x = size[:width] / 2
76
+ start_y = size[:height] / 2
77
+ end_x = size[:width] / 2
78
+ end_y = size[:height] - 100
79
+ Appium::TouchAction.new.press({x:start_x,y:start_y}).wait(200).move_to({x:end_x,y:end_y}).release.perform
80
+ end
81
+
82
+ def get_window_size
83
+ window_size.to_h
84
+ end
85
+
86
+ def swipe_down
87
+ size = @window_size
88
+ start_x = size[0] / 2
89
+ start_y = size[1] / 2
90
+ end_x = size[0] / 2
91
+ end_y = size[1] - 200
92
+ Appium::TouchAction.new.press({x:start_x,y:start_y}).wait(200).move_to({x:end_x,y:end_y}).release.perform
93
+ end
94
+
95
+ def swipe_up
96
+ size = @window_size
97
+ start_x = size[0] / 2
98
+ start_y = size[1] / 2
99
+ end_x = size[0] / 2
100
+ end_y = 100
101
+ Appium::TouchAction.new.press({x:start_x,y:start_y}).wait(200).move_to({x:end_x,y:end_y}).release.perform
102
+ end
103
+
104
+ def swipe_left
105
+ size = @window_size
106
+ start_x = (size[0] - 60).to_f
107
+ start_y = (size[1] / 2).to_f
108
+ end_x = 60.to_f
109
+ end_y = (size[1] / 2).to_f
110
+ Appium::TouchAction.new.press({x:start_x,y:start_y}).wait(200).move_to({x:end_x,y:end_y}).release.perform
111
+ #swipe({start_x:63,end_y:326})
112
+ end
113
+
114
+ def swipe_right
115
+ size = @window_size
116
+ start_x = 60.to_f
117
+ start_y = (size[1] / 2).to_f
118
+ end_x = (size[0] - 60).to_f
119
+ end_y = (size[1] / 2).to_f
120
+ Appium::TouchAction.new.press({x:start_x,y:start_y}).wait(200).move_to({x:end_x,y:end_y}).release.perform
121
+ end
122
+
123
+ def fe locator
124
+ begin
125
+ find_element(locator)
126
+ rescue
127
+ nil
128
+ end
129
+ end
130
+
131
+ def fa locator
132
+ begin
133
+ find_elements(locator)
134
+ rescue
135
+ []
136
+ end
137
+ end
138
+
139
+ def get_text string
140
+ begin
141
+ text string
142
+ rescue
143
+ nil
144
+ end
145
+ end
146
+
147
+ def displayed? locator
148
+ begin
149
+ fe(locator).displayed?
150
+ rescue
151
+ false
152
+ end
153
+ end
154
+
155
+ def click locator, *optional
156
+ begin
157
+ locator.click
158
+ rescue
159
+ nil
160
+ end
161
+ end
162
+
163
+ def type string
164
+ begin
165
+ driver.keyboard.send_keys string
166
+ rescue
167
+ nil
168
+ end
169
+ end
170
+
171
+ ###For Login Method
172
+ def enter locator, string
173
+ begin
174
+ locator.send_keys string
175
+ rescue
176
+ nil
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,108 @@
1
+ module Aaet
2
+ class Redis
3
+
4
+ attr_accessor :redis, :process
5
+
6
+ def initialize process
7
+ self.redis = Redic.new
8
+ self.process = process
9
+ update_list("crashed", false)
10
+ end
11
+
12
+ def del_list list
13
+ redis.call "DEL", "#{list}-#{process}" rescue nil
14
+ end
15
+
16
+ def update_applitools list, body
17
+ redis.call "LPUSH", list, JSON.generate(body)
18
+ end
19
+
20
+ def update_list list, body
21
+ redis.call "LPUSH", "#{list}-#{process}", JSON.generate(body)
22
+ end
23
+
24
+ def activity_exists? act
25
+ activities.any? { |a| a.include? act }
26
+ end
27
+
28
+ def remove_activity activity
29
+ redis.call "LREM", "activities", -1, activity
30
+ end
31
+
32
+ def activities
33
+ redis.call("LRANGE", "activities", 0, -1)
34
+ end
35
+
36
+ def found_activities
37
+ redis.call("LRANGE", "found_activities-#{process}", 0, -1)
38
+ end
39
+
40
+ def found_activity_exists? act
41
+ found_activities.any? { |a| a.include? act }
42
+ end
43
+
44
+ def add_found_activity act
45
+ redis.call "LPUSH", "found_activities-#{process}", act
46
+ end
47
+
48
+ def update_activity_count act
49
+ unless found_activity_exists? act
50
+ checkmark = "\u2713"
51
+ puts ""
52
+ puts "\nNew Activity Found! #{checkmark}".green
53
+ add_found_activity act
54
+ remaining = (activities.count - found_activities.count)
55
+ puts "Remaining Activities: #{remaining}\n"
56
+ puts ""
57
+ end
58
+ end
59
+
60
+ def get_list list
61
+ redis_list = redis.call("LRANGE", "#{list}-#{process}", 0, -1).map { |x| JSON.parse(x) }.uniq.flatten rescue []
62
+ unless redis_list.empty?
63
+ redis_list = symbolize(redis_list)
64
+ end
65
+ redis_list
66
+ end
67
+
68
+ def symbolize(obj)
69
+ return obj.reduce({}) do |memo, (k, v)|
70
+ memo.tap { |m| m[k.to_sym] = symbolize(v) }
71
+ end if obj.is_a? Hash
72
+
73
+ return obj.reduce([]) do |memo, v|
74
+ memo << symbolize(v); memo
75
+ end if obj.is_a? Array
76
+
77
+ obj
78
+ end
79
+
80
+ def list_includes_value list, value
81
+ redis.call("LRANGE", "#{list}-#{process}", 0, -1).any? { |a| a.include? value }
82
+ end
83
+
84
+ def list_includes_applitools_value list, value
85
+ redis.call("LRANGE", list, 0, -1).any? { |a| a.include? value }
86
+ end
87
+
88
+ def app_crashed boolean
89
+ redis.call "LPUSH", "crashed-#{process}", boolean
90
+ end
91
+
92
+ def lpop list
93
+ redis.call "LPOP", "#{list}-#{process}"
94
+ end
95
+
96
+ def hincr(key, field)
97
+ redis.call("HINCRBY", "#{key}-#{process}", field, 1)
98
+ end
99
+
100
+ def hset(key, field, value)
101
+ redis.call("HSET", "#{key}-#{process}", field, value)
102
+ end
103
+
104
+ def hget(key, field)
105
+ redis.call("HGET", "#{key}-#{process}", field)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,18 @@
1
+ #iOS Placeholder
2
+ module Aaet
3
+ class Ios
4
+
5
+ # include Appium::Ios
6
+ # include Appium::Common
7
+ # include Appium::Device
8
+
9
+ def keyboard_open?
10
+ begin
11
+ #driver.driver.manage.timeouts.implicit_wait = 0.1
12
+ fe({xpath: '//UIAKeyboard'}).displayed?
13
+ rescue Selenium::WebDriver::Error::NoSuchElementError
14
+ false
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ #iOS Placeholder
@@ -0,0 +1,3 @@
1
+ module Aaet
2
+ VERSION = "0.1.0"
3
+ end
data/run.rb ADDED
@@ -0,0 +1,294 @@
1
+ require_relative 'lib/aaet'
2
+ require 'parallel'
3
+ require_relative 'generate_reports'
4
+ require_relative 'setup/common_setup_methods'
5
+ require_relative "setup/android_setup_methods"
6
+ require_relative "setup/ios_setup_methods"
7
+ require 'pry'
8
+ require 'uri'
9
+
10
+ class Executer < ReportBuilder
11
+
12
+ attr_accessor :program_opts, :appium_reset, :platform_setup, :appium_arg, :uuid_arg, :setup
13
+ attr_writer :run
14
+
15
+ def initialize settings
16
+ self.program_opts = settings
17
+ generate_instance_variables nil, settings
18
+ if caps_platform == 'android'
19
+ self.platform_setup = AndroidSetup.new settings
20
+ avd = caps_avd rescue nil
21
+ uuid = caps_udid rescue nil
22
+ if avd
23
+ platform_setup.check_emulator caps_avd
24
+ self.appium_arg = "--avd #{caps_avd}"
25
+ elsif uuid
26
+ platform_setup.check_uuid caps_udid
27
+ self.appium_arg = "--udid #{caps_udid}"
28
+ end
29
+ elsif caps_platform == 'ios'
30
+ #placeholder for iOS
31
+ self.platform_setup = IosSetup.new settings
32
+ end
33
+
34
+ if options[:resetAppium]
35
+ self.appium_reset = nil
36
+ else
37
+ self.appium_reset = '--no-reset'
38
+ end
39
+
40
+ if options_debug
41
+ self.instance_variables.each { |var| puts "#{var}: #{self.instance_variable_get(var)}" }
42
+ end
43
+ self.setup = CommonSetup.new
44
+ end
45
+
46
+ def generate_instance_variables(parent, hash)
47
+ #turn options/settings nested hash into instance variables
48
+ hash.each do |key, value|
49
+ if value.is_a?(Hash)
50
+ generate_instance_variables(key, value)
51
+ self.class.send(:attr_accessor, key.to_sym)
52
+ self.instance_variable_set("@#{key}", value)
53
+ else
54
+ if parent.nil?
55
+ self.class.send(:attr_accessor, "#{key}".to_sym)
56
+ self.instance_variable_set("@#{key}", value)
57
+ else
58
+ self.class.send(:attr_accessor, "#{parent}_#{key}".to_sym)
59
+ self.instance_variable_set("@#{parent}_#{key}", value)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def driver_alive?
66
+ @run.current_activity["message"] != "A session is either terminated or not started"
67
+ end
68
+
69
+ def kill_everything
70
+ if caps_platform == "android" and !options_cloud
71
+ Process.kill("HUP", program_opts[:logcat_pid])
72
+ #binding.pry
73
+ setup.kill_processes "'#{@uuid} logcat'"
74
+ %x(adb -s #{@uuid} shell settings put global policy_control null*) #put status bar back...
75
+ system("adb -s #{@uuid} emu kill") if options_emulator and !options_keepEmulatorAlive
76
+ sleep 1
77
+ end
78
+ driver.quit rescue nil
79
+ unless options_cloud
80
+ pid = Process.getpgid(Process.pid)
81
+ Signal.trap('TERM') { Process.kill('TERM', -pid); exit }
82
+ Signal.trap('INT' ) { Process.kill('INT', -pid); exit }
83
+ Process.kill("HUP", program_opts[:appium_pid])
84
+ end
85
+ end
86
+
87
+ def runner time
88
+ require 'timeout'
89
+ begin
90
+ Timeout.timeout(time) { go }
91
+ rescue Timeout::Error
92
+ puts "\n#{time} Seconds Exceeded!!!\nKilling Driver for #{@uuid}...".red
93
+ end
94
+ end
95
+
96
+ def force_device_orientation(orientation)
97
+ desired_orientation = orientation.downcase.to_sym
98
+ begin
99
+ if desired_orientation != driver.orientation.to_sym
100
+ puts "Current orientation: #{driver.orientation.to_sym}"
101
+ puts "Setting device to orientation: #{desired_orientation}"
102
+ driver.rotate desired_orientation
103
+ end
104
+ rescue
105
+ puts "\n#{@uuid}: Cannot rotate device to desired orientation: #{desired_orientation}".red
106
+ puts "Keeping Current orientation: #{driver.orientation}\n".yellow
107
+ end
108
+ driver.orientation.to_s.upcase
109
+ end
110
+
111
+ def initialize_driver
112
+ if options_cloud #only do once for crawler, reply will have these settings already.
113
+ local_app_path = caps[:caps][:app]
114
+ caps[:caps][:app] = caps_cloud_app
115
+ if ["crawler", "monkey"].include? options[:mode]
116
+ cloud_url = caps[:appium_lib][:server_url].insert(7, "#{cloud_user}:#{cloud_key}")
117
+ caps[:caps][:url] = cloud_url
118
+ caps[:appium_lib][:server_url] = cloud_url
119
+ end
120
+ else
121
+ old_url = URI(caps[:caps][:url])
122
+ old_port = old_url.port.to_s
123
+ new_url = old_url.to_s.gsub(old_port, program_opts[:appium_port].to_s) #replace port with parallel process port
124
+ caps[:caps][:url] = new_url
125
+ caps[:appium_lib][:server_url] = new_url
126
+ end
127
+ Appium::Driver.new(caps, true).start_driver
128
+ Appium.promote_appium_methods Object
129
+ caps[:caps][:app] = local_app_path if options_cloud #set app back to local app file for activity parsing later...
130
+ sleep 5
131
+ #update program data...
132
+ @uuid = driver.capabilities["deviceUDID"]
133
+ caps[:caps][:udid] = @uuid
134
+ program_opts[:app_and_device_data] = platform_setup.app_and_device_data @uuid
135
+ @resolution = driver.capabilities["deviceScreenSize"]
136
+ program_opts[:app_and_device_data][:device][:resolution] = @resolution
137
+ program_opts[:app_and_device_data][:activities] = program_opts[:activities] #will need to figure out how to parse iOS activities
138
+ end
139
+
140
+ def generate_output_dir
141
+ dir_array = [
142
+ run_time,
143
+ "#{program_opts[:app_and_device_data][:app_package]}-v#{program_opts[:app_and_device_data][:app_version]}",
144
+ @resolution,
145
+ "#{@uuid}-#{process}",
146
+ program_opts[:app_and_device_data][:device][:os],
147
+ caps_language,
148
+ @orientation
149
+ ]
150
+ dir = setup.create_directories dir_array
151
+ program_opts[:output_dir] = dir
152
+ end
153
+
154
+ def go
155
+ unless options_cloud
156
+ appium_info = setup.start_appium_server(program_opts[:process], appium_reset, appium_arg, run_time)
157
+ program_opts[:appium_pid] = appium_info[:appium_pid]
158
+ program_opts[:appium_port] = appium_info[:appium_port]
159
+ program_opts[:appium_log] = appium_info[:appium_log]
160
+ end
161
+ initialize_driver
162
+ caps[:caps][:desired_orientation] = caps[:caps][:orientation] #set this ignored capabilitiy for reporting later...
163
+ @orientation = force_device_orientation(caps[:caps][:orientation]) #setting in caps does not always change orientation so I'm forcing it...
164
+ options[:orientation] = @orientation
165
+ caps[:caps][:orientation] = @orientation
166
+ generate_output_dir
167
+ @run = Aaet::Runner.new program_opts
168
+ unless options_cloud
169
+ if caps_platform == 'android'
170
+ program_opts[:logcat_pid] = @run.monitor_log_start
171
+ elsif caps_platform == 'ios'
172
+ #placeholder for iOS
173
+ #TODO: do something with iOS...
174
+ end
175
+ end
176
+ begin
177
+ if ["crawler", "monkey"].include? options_mode
178
+ loop { @run.instance_eval(options_mode) }
179
+ else
180
+ @run.instance_eval(options_mode)
181
+ end
182
+ rescue Interrupt
183
+ puts "\nGot Interrupted. Stopping...".red
184
+ ensure
185
+ kill_everything
186
+ if options_mode == "crawler"
187
+ ReportBuilder.new(options_translate, process, program_opts).generate_reports
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ def crawler options
194
+ setup = CommonSetup.new
195
+ setup.prep_environment
196
+ settings = setup.format_options options
197
+ settings[:run_time] = Time.now.strftime("%Y.%m.%d.%H.%M")
198
+ output_dir_base = setup.create_output_directory "output", settings[:run_time]
199
+ settings[:output_dir_base] = output_dir_base
200
+
201
+ if options[:applitools] and settings[:config][:applitools][0][:key].empty?
202
+ if ENV['APPLITOOLS_API_KEY'].nil?
203
+ puts "\nYou must provide and Applitools Key in the config file or add APPLITOOLS_API_KEY environment variable\n".red
204
+ abort
205
+ else
206
+ settings[:config][:applitools][0][:key] = ENV['APPLITOOLS_API_KEY']
207
+ end
208
+ end
209
+
210
+ #TODO: https://github.com/celluloid/celluloid #maybe look into replacing parallel gem with this...
211
+ Parallel.each(settings[:caps], in_processes: settings[:caps].count, interrupt_signal: 'TERM') do |caps|
212
+ settings[:process] = Parallel.worker_number #set the current process running...
213
+ settings[:config][:caps] = caps
214
+ if settings[:options][:bothOrientations] and settings[:caps].count > 1
215
+ if settings[:process].even?
216
+ settings[:config][:caps][:caps][:orientation] = "PORTRAIT"
217
+ else
218
+ settings[:config][:caps][:caps][:orientation] = "LANDSCAPE"
219
+ end
220
+ end
221
+ settings.delete :caps
222
+ puts "Testing Languages: #{settings[:options][:language]}"
223
+ settings[:options][:language].shuffle.each do |language|
224
+ puts "Running Language: #{language}"
225
+ settings[:config][:caps][:caps][:language] = language
226
+ Executer.new(settings).runner(settings[:options][:seconds])
227
+ puts "Finished language: #{language}\n"
228
+ end
229
+ end
230
+ end
231
+
232
+ def monkey options
233
+ setup = CommonSetup.new
234
+ setup.prep_environment
235
+ settings = setup.format_options options
236
+ settings[:run_time] = Time.now.strftime("%Y.%m.%d.%H.%M")
237
+ output_dir_base = setup.create_output_directory "output", settings[:run_time]
238
+ settings[:output_dir_base] = output_dir_base
239
+
240
+ Parallel.each(settings[:caps], in_processes: settings[:caps].count, interrupt_signal: 'TERM') do |caps|
241
+ settings[:process] = Parallel.worker_number #set the current process running...
242
+ settings[:config][:caps] = caps
243
+ if settings[:options][:bothOrientations] and settings[:caps].count > 1
244
+ if settings[:process].even?
245
+ settings[:config][:caps][:caps][:orientation] = "PORTRAIT"
246
+ else
247
+ settings[:config][:caps][:caps][:orientation] = "LANDSCAPE"
248
+ end
249
+ end
250
+ settings.delete :caps
251
+ settings[:config][:caps][:caps][:language] = settings[:options][:language]
252
+ Executer.new(settings).runner(settings[:options][:seconds])
253
+ end
254
+ end
255
+
256
+ def replay options
257
+ puts "\nREPLAY Mode is Experimental. Not 100% to work every time...\n".yellow
258
+ #TODO: create a report with images highlighting each step clicked with ImageMagik in case replay does not work.
259
+
260
+ setup = CommonSetup.new
261
+ setup.prep_environment
262
+
263
+ config = TomlRB.load_file(options[:config], symbolize_keys: true)
264
+ package_name = config[:caps][:appPackage]
265
+ file = setup.select_run(package_name)
266
+ last_run = setup.symbolize(eval(open(file).read))
267
+
268
+ if options[:applitools] and last_run[:options][:config][:applitools][0][:key].empty?
269
+ if ENV['APPLITOOLS_API_KEY'].nil?
270
+ puts "\nYou must provide and Applitools Key in the config file or add APPLITOOLS_API_KEY environment variable\n".red
271
+ abort
272
+ else
273
+ settings[:config][:applitools][0][:key] = ENV['APPLITOOLS_API_KEY']
274
+ end
275
+ end
276
+
277
+ #override last crawl run options with current replay options.
278
+ options.each { |k,v| last_run[:options][:options][k] = v }
279
+
280
+ setup.load_activities last_run[:activities]
281
+
282
+ settings = {
283
+ activities: last_run[:activities],
284
+ options: last_run[:options][:options],
285
+ config: last_run[:options][:config]
286
+ }
287
+ settings[:run_time] = Time.now.strftime("%Y.%m.%d.%H.%M")
288
+ output_dir_base = setup.create_output_directory "output", settings[:run_time]
289
+ settings[:output_dir_base] = output_dir_base
290
+ settings[:last_run_steps] = last_run[:data]
291
+ settings[:process] = 0
292
+
293
+ Executer.new(settings).runner(settings[:options][:seconds])
294
+ end