aaet 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.
@@ -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