aaet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ # require "rspec/core/rake_task"
3
+ #
4
+ # RSpec::Core::RakeTask.new(:spec)
5
+ #
6
+ # task :default => :spec
@@ -0,0 +1,51 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "#{Dir.pwd}/lib/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "aaet"
8
+ spec.version = Aaet::VERSION
9
+ spec.authors = ["isonic1"]
10
+ spec.email = ["justin.ison@gmail.com"]
11
+
12
+ spec.summary = %q{A CLI to crawl native mobile Android applications with Appium on Devices, Emulators & Cloud Serivces}
13
+ spec.description = %q{A CLI to collect metadata about an applicaton on every run from the command line}
14
+ spec.homepage = "https://github.com/isonic1/Appium-Native-Crawler"
15
+ spec.license = "Apache-2.0"
16
+
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
21
+ end
22
+
23
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
24
+ f.match(%r{^(test|spec|features|output|pkg|example|output|runs|reports)/})
25
+ end
26
+ #spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ spec.bindir = "bin"
28
+ spec.executables = ["aaet"]
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_development_dependency "bundler", "~> 1.13"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "rspec", "~> 3.0"
34
+ spec.add_dependency "commander"
35
+ spec.add_dependency "awesome_print"
36
+ spec.add_dependency "colorize"
37
+ spec.add_dependency "faker"
38
+ spec.add_dependency "os"
39
+ spec.add_dependency "pry"
40
+ spec.add_dependency "redic", '~> 1.5.0'
41
+ spec.add_dependency "apktools", '~> 0.7.1'
42
+ spec.add_dependency "parallel", '~> 1.9.0'
43
+ spec.add_dependency "eyes_selenium", '= 3.10.1'
44
+ spec.add_dependency "tilt"
45
+ spec.add_dependency "haml"
46
+ spec.add_dependency "curb"
47
+ spec.add_dependency "httparty"
48
+ spec.add_dependency "toml-rb"
49
+ spec.add_dependency "appium_lib"
50
+ spec.add_dependency "jsonpath"
51
+ end
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../run.rb'
3
+ require 'commander/import'
4
+ require 'colorize'
5
+
6
+ program :version, '0.1.0'
7
+ program :description, 'Appium Automated Exploratory Tester'
8
+
9
+ command :crawler do |c|
10
+ c.syntax = 'aaet [options]'
11
+ c.summary = 'Run Aaet Crawler'
12
+ c.description = c.summary
13
+ c.example 'description', "aaet crawler --seconds 300 --emulator avdName(s) --config path/to/configFile.txt"
14
+ c.option '-s', '--seconds SECONDS', Integer, 'Set Runtime in Seconds: e.g. -s 300 (for 5 minutes)'
15
+ c.option '-c', '--config CONFIG', String, 'Set config file location: e.g. -c myAppConfig.txt'
16
+ c.option '-e', '--emulator EMULATOR', Array, 'Set emulator(s) AVD Name(s) to start: e.g. -e Nexus or Array as -e "NexusOne emulator1 emulator2"'
17
+ c.option '-o', '--orientation ORIENTATION', String, 'Set devices orientation: e.g. -o landscape'
18
+ c.option '-l', '--language LANGUAGE', Array, 'Set languages(s): e.g. -l "en" or -l "en de ar" Default: en'
19
+ c.option '-u', '--uuid UUID', Array, 'Set Real Device UUID. e.g. --uuid IBZ5AQMBCY7DHASK or Array as -u "UUID1 UUID2"'
20
+ #Boolean options. Only pass the argument (e.g. --debug) to set to true. Otherwise they are all false by default.
21
+ c.option '--keepEmulatorAlive', "Keep emulator running after crawl. e.g. --keepEmulatorAlive"
22
+ c.option '--bothOrientations', "If running in parallel with multiple devices. Tell Crawler to run both Orientations, if available. e.g --bothOrientations"
23
+ c.option '--translate', 'Translate strings after job finish: e.g. --translate'
24
+ c.option '--applitools', 'Run Applitools Tests. e.g --applitools'
25
+ c.option '--updateBaseline', 'Update Applitools baseline images. e.g. --updateBaseline'
26
+ c.option '--debug', 'Print debug output: e.g. --debug'
27
+ c.option '--resetAppium', 'Reset Appium Session - Reinstall app on startup or restart. e.g. --resetAppium'
28
+ c.option '--cloud', 'Crawl in cloud. e.g. --cloud. Default: false. Make sure your cloud settings are set in [cloud] and caps is pointed to a config/toml file'
29
+ c.action do |args, options|
30
+ options.default \
31
+ mode: "crawler",
32
+ emulator: nil,
33
+ orientation: "PORTRAIT",
34
+ language: ["en"],
35
+ uuid: nil,
36
+ keepEmulatorAlive: false,
37
+ bothOrientations: false,
38
+ translate: false,
39
+ applitools: false,
40
+ updateBaseline: false,
41
+ debug: false,
42
+ resetAppium: false,
43
+ cloud: false
44
+
45
+ if options.config.nil?
46
+ puts "\nYou must supply a config TOML file... e.g. -c configs/app-debug.txt\n".red
47
+ abort
48
+ end
49
+
50
+ if options.emulator.nil? and options.uuid.nil?
51
+ puts "\nYou must supply a avdName/simulator or UUID... e.g. -e Nexus10 or -u UUID\n".red
52
+ abort
53
+ end
54
+
55
+ if options.cloud
56
+ options.uuid = nil
57
+ options.emulator = nil
58
+ end
59
+
60
+ options.orientation = options.orientation.upcase
61
+
62
+ crawler(options.default)
63
+ end
64
+ end
65
+
66
+ command :monkey do |c|
67
+ c.syntax = 'aaet [options]'
68
+ c.summary = 'Run Aaet Monkey'
69
+ c.description = c.summary
70
+ c.example 'description', "aaet monkey -c app-debug.txt -s 300 -e EM1 --trace"
71
+ c.option '-s', '--seconds SECONDS', Integer, 'Set Runtime in Seconds: e.g. -s 300 (for 5 minutes)'
72
+ c.option '-c', '--config CONFIG', String, 'Set config file location: e.g. -c myAppConfig.txt'
73
+ c.option '-e', '--emulator EMULATOR', Array, 'Set emulator(s) AVD Name(s) to start: e.g. -e Nexus or Array as -e "NexusOne emulator1 emulator2"'
74
+ c.option '-u', '--uuid UUID', Array, 'Set Device UUID. e.g. --uuid IBZ5AQMBCY7DHASK or Array as -u "IBZ5AQMBCY7DHASK skso9eosdlldfjs'
75
+ c.option '-o', '--orientation ORIENTATION', String, 'Set devices orientation: e.g. -o landscape'
76
+ #Boolean options. Only pass the argument (e.g. --debug) to set to true. Otherwise they are all false by default.
77
+ c.option '--keepEmulatorAlive', "Keep emulator running after crawl. e.g. --keepEmulatorAlive"
78
+ c.option '--bothOrientations', "If running in parallel with multiple devices. Tell Crawler to run both Orientations, if available. e.g --bothOrientations"
79
+ c.option '--debug', 'Print debug output: e.g. --debug'
80
+ c.option '--resetAppium', 'Reset Appium Session - Reinstall app on startup. e.g. --resetAppium'
81
+ c.option '--cloud', 'Crawl in cloud. e.g. --cloud. Default: false. Make sure your cloud settings are set in [cloud] and caps is pointed to a config/toml file'
82
+ c.action do |args, options|
83
+ options.default \
84
+ mode: "monkey",
85
+ applitools: false,
86
+ seconds: 300,
87
+ debug: false,
88
+ language: "en",
89
+ orientation: "PORTRAIT",
90
+ bothOrientations: false,
91
+ resetAppium: false,
92
+ cloud: false,
93
+ emulator: nil,
94
+ uuid: nil,
95
+ keepEmulatorAlive: false
96
+
97
+ if options.config.nil?
98
+ puts "\nYou must supply a config TOML file... e.g. -c configs/app-debug.txt\n".red
99
+ abort
100
+ end
101
+
102
+ if options.emulator.nil? and options.uuid.nil?
103
+ puts "\nYou must supply a avdName/simulator or UUID... e.g. -e Nexus10 or -u UUID\n".red
104
+ abort
105
+ end
106
+
107
+ if options.cloud
108
+ options.uuid = nil
109
+ options.emulator = nil
110
+ end
111
+
112
+ options.orientation = options.orientation.upcase
113
+
114
+ monkey(options.default)
115
+ end
116
+ end
117
+
118
+ command :replay do |c|
119
+ c.syntax = 'aaet [options]'
120
+ c.summary = 'Replay Last Aaet Run'
121
+ c.description = c.summary
122
+ c.example 'description', "aaet replay -c app-debug.txt -s 300 --trace"
123
+ c.option '-s', '--seconds SECONDS', Integer, 'Set Max Runtime in Seconds: e.g. -s 300 (for 5 minutes). Crawler will run until the replay steps are finished or this value'
124
+ c.option '-c', '--config CONFIG', String, 'Set config file location: e.g. -c myAppConfig.txt'
125
+ #Boolean options. Only pass the argument (e.g. --debug) to set to true. Otherwise they are all false by default.
126
+ c.option '--keepEmulatorAlive', "Keep emulator running after crawl. e.g. --keepEmulatorAlive"
127
+ c.option '--debug', 'Print debug output: e.g. --debug'
128
+
129
+ c.action do |args, options|
130
+ options.default \
131
+ mode: "replay",
132
+ applitools: false,
133
+ debug: false,
134
+ keepEmulatorAlive: false
135
+
136
+ if options.config.nil?
137
+ puts "\nYou must supply a config TOML file... e.g. -c configs/app-debug.txt\n".red
138
+ abort
139
+ end
140
+
141
+ replay(options.default)
142
+ end
143
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "aaet"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,512 @@
1
+ require 'redic'
2
+ require 'json'
3
+ require 'awesome_print'
4
+ require 'tilt/haml'
5
+ require 'httparty'
6
+ require 'jsonpath'
7
+ require 'fileutils'
8
+ require 'pry'
9
+ require 'colorize'
10
+
11
+ class ReportBuilder
12
+
13
+ attr_accessor :translate, :process, :options
14
+
15
+ def initialize translate, process, options
16
+ self.translate = translate
17
+ self.options = options
18
+ self.process = process
19
+ @redis = Redic.new
20
+ end
21
+
22
+ def get_list list
23
+ @redis.call("LRANGE", "#{list}-#{process}", 0, -1).map { |x| JSON.parse(x) }.uniq.flatten rescue []
24
+ end
25
+
26
+ def get_list_without_process list
27
+ @redis.call("LRANGE", list, 0, -1).map { |x| JSON.parse(x) }.uniq.flatten rescue []
28
+ end
29
+
30
+ def found_activities
31
+ @redis.call("LRANGE", "found_activities-#{process}", 0, -1)
32
+ end
33
+
34
+ def clicked_elements
35
+ get_list("clicked").reverse
36
+ end
37
+
38
+ def app_crashed?
39
+ eval(@redis.call("LRANGE", "crashed-#{process}", 0, -1)[0]) rescue false
40
+ end
41
+
42
+ def clicked_page_changed
43
+ clicked = clicked_elements.find_all { |x| x["page_changed"] == true }
44
+ if app_crashed?
45
+ clicked << clicked_elements.last
46
+ clicked.uniq
47
+ else
48
+ clicked.uniq
49
+ end
50
+ end
51
+
52
+ def find_max_of_my_array(arr,type)
53
+ arr.select{|x| x[:type] == type}.max_by{|x| x[:value_length]}
54
+ end
55
+
56
+ def max_cpu
57
+ list = clicked_page_changed
58
+ list.max_by { |x| x["performance"]["app_cpu"] || 0 }
59
+ end
60
+
61
+ def max_memory
62
+ list = clicked_page_changed
63
+ list.max_by { |x| x["performance"]["app_mem"] || 0 }
64
+ end
65
+
66
+ def page_texts
67
+ get_list "page_text"
68
+ end
69
+
70
+ def screenshots dir
71
+ Dir["#{dir}/*.png"]
72
+ end
73
+
74
+ def find_screenshot_path screenshot_array, md5
75
+ screenshot_array.find { |x| x.include? md5 }
76
+ end
77
+
78
+ def parse_data dir
79
+ ss = screenshots(dir)
80
+ array = []
81
+ e = clicked_page_changed
82
+ e.each do |h|
83
+ ss_path = find_screenshot_path(ss, h["page"])
84
+ array << { screenshot: ss_path }.merge!(h)
85
+ end
86
+ array
87
+ end
88
+
89
+ def create_dir dir
90
+ Dir.mkdir dir unless File.exists? dir
91
+ end
92
+
93
+ def create_graph(data, graph_opts = {})
94
+ template_path = "#{File.dirname(__FILE__)}/template_google_api_format.haml"
95
+ default_graph_settings = { miniValue: 0, maxValue: 2000, width: 1500, height: 900 }
96
+
97
+ template = Tilt::HamlTemplate.new(template_path)
98
+ template.render(Object.new,
99
+ title: graph_opts[:title],
100
+ header1: graph_opts[:header1],
101
+ data_file_path: data,
102
+ graph_settings: graph_opts[:graph_settings] || default_graph_settings)
103
+ end
104
+
105
+ def export_as_google_api_format(data)
106
+ google_api_data_format = google_api_format
107
+ data.each do |hash|
108
+ puts hash
109
+ a_google_api_data_format = {
110
+ c: [
111
+ { v: "Date(#{hash[:time]})" },
112
+ { v: hash["app_mem"] },
113
+ { v: "<img src='#{hash[:screenshot]}' alt='' width='240' height='400'>" },
114
+ { v: hash["app_cpu"] },
115
+ { v: "<img src='#{hash[:screenshot]}' alt='' width='240' height='400'>" },
116
+ { v: hash["user"] },
117
+ { v: "<img src='#{hash[:screenshot]}' alt='' width='240' height='400'>" },
118
+ { v: hash["sys"] },
119
+ { v: "<img src='#{hash[:screenshot]}' alt='' width='240' height='400'>" },
120
+ ],
121
+ }
122
+ google_api_data_format[:rows].push(a_google_api_data_format)
123
+ end
124
+ JSON.generate google_api_data_format
125
+ end
126
+
127
+ def google_api_format
128
+ {
129
+ cols: [
130
+ { label: 'time', type: 'datetime' },
131
+ { label: 'app_mem', type: 'number' },
132
+ { role: 'tooltip', type: 'string', p: { html: true } },
133
+ { label: 'app_cpu', type: 'number' },
134
+ { role: 'tooltip', type: 'string', p: { html: true } },
135
+ { label: 'user', type: 'number' },
136
+ { role: 'tooltip', type: 'string', p: { html: true } },
137
+ { label: 'sys', type: 'number' },
138
+ { role: 'tooltip', type: 'string', p: { html: true } },
139
+ ],
140
+ rows: [
141
+ ],
142
+ }
143
+ end
144
+
145
+ def convert_time string
146
+ require 'date'
147
+ DateTime.parse(string).strftime('%H:%M:%S')
148
+ end
149
+
150
+ def convert_time_to_unix timestamp
151
+ (Time.parse(timestamp).to_f * 1000).ceil
152
+ end
153
+
154
+ def generate_performance_html_report fileaname, report_data
155
+ create_dir "#{Dir.pwd}/reports"
156
+ report_dir = "#{Dir.pwd}/reports/#{fileaname}"
157
+ create_dir report_dir
158
+
159
+ maxcpu = report_data[:max_values][:max_cpu]
160
+ maxmem = report_data[:max_values][:max_mem]
161
+
162
+ app_data = report_data[:app_info]
163
+ device_data = report_data[:device_info]
164
+
165
+ header = app_data[:package_name]
166
+ title = "| v: #{app_data[:version]} | maxCpu: #{maxcpu} | maxMem: #{maxmem} | appSize: #{app_data[:app_size]}mb | os: #{device_data[:os]} | sdk:#{device_data[:sdk]} |"
167
+ data = report_data[:data].map { |x| { time: convert_time_to_unix(x["time"]), screenshot: x[:screenshot]}.merge(x["performance"]) unless x["performance"].nil? }.compact
168
+ output = export_as_google_api_format data
169
+
170
+ open("#{report_dir}/perf-#{fileaname}.txt", 'w') { |f| f << output }
171
+ graph_opts = { title: title, header1: header}
172
+ report = create_graph("#{report_dir}/perf-#{fileaname}.txt", graph_opts)
173
+ open("#{report_dir}/perf-#{fileaname}.html", 'w') { |f| f << report }
174
+ end
175
+
176
+ def detect array
177
+ begin
178
+ array.map { |string| { string: string, detected: get_locale(string) } }
179
+ rescue
180
+ nil
181
+ end
182
+ end
183
+
184
+ def get_locale string
185
+ res = google_api string
186
+ if res.code == 200
187
+ lang = jpath(res["data"], "language").join
188
+ conf = jpath(res["data"], "confidence").join
189
+ { locale: lang, confidence: conf }
190
+ else
191
+ puts "\nGetting Invalid Status Code '#{res.code}' from Google API".red
192
+ puts "Response Message: #{res.message}\n".red
193
+ { locale: 'api-error', confidence: 'api-error' }
194
+ end
195
+ end
196
+
197
+ def google_api(string, key = ENV["GOOGLE_API_KEY"] || options[:config][:settings][:google_translate_key])
198
+ HTTParty.get("https://www.googleapis.com/language/translate/v2/detect", query: { q: string, key: key })
199
+ end
200
+
201
+ def jpath(hash, key)
202
+ JsonPath.on(hash, "$..#{key}")
203
+ end
204
+
205
+ def collect_bad_strings strings = page_texts, correct_locale
206
+ text_array = strings.map { |x| x["text"] }.flatten.uniq
207
+ text_array.reject! { |e| e.to_s.empty? }
208
+ detected_locales = detect(text_array)
209
+ detected_locales.collect do |t|
210
+ strings.find_all { |x| x["text"].include? t[:string] }.map { |x| { activity: x["activity"], page: x["page"], translated: t } if t[:detected][:locale] != correct_locale }.compact
211
+ end.flatten.group_by { |h| h[:page] }.each { |_,v| v.map! { |h| h[:translated][:string] } }
212
+ end
213
+
214
+ def generate_translations_report filename, title, array
215
+ create_dir "#{Dir.pwd}/reports"
216
+ report_dir = "#{Dir.pwd}/reports/#{filename}"
217
+ create_dir report_dir
218
+
219
+ gallery_css = <<-CSS
220
+ img {
221
+ padding: 0px;
222
+ margin: 0px 24px 24px 0px;
223
+ border: 3px solid #ccc;
224
+ border-radius: 2px;
225
+ box-shadow: 3px 3px 5px #ccc;
226
+ }
227
+ CSS
228
+
229
+ File.open("#{report_dir}/strings-#{filename}.html", 'w') do |f|
230
+
231
+ layout = <<-HTML
232
+ <!DOCTYPE html>
233
+ <html>
234
+ <head>
235
+ <title>#{title}</title>
236
+ <style type="text/css" media="screen">
237
+ #{ gallery_css }
238
+ </style>
239
+ </head>
240
+ <body>
241
+ <div class="container">
242
+ <h1>#{title}</h1>
243
+ </div>
244
+ </body>
245
+ </html>
246
+ HTML
247
+
248
+ f << layout
249
+
250
+ array.each do |data|
251
+ f << "<p>Detected Strings: #{data[:strings].join(" , ")}</p>"
252
+ f << "<img src=\"#{data[:screenshot]}\">"
253
+ f << "<P class=\"breakhere\">"
254
+ end
255
+ end
256
+ end
257
+
258
+ def generate_screenshots_report filename, title
259
+ create_dir "#{Dir.pwd}/reports"
260
+ report_dir = "#{Dir.pwd}/reports/#{filename}"
261
+ create_dir report_dir
262
+
263
+ gallery_css = <<-CSS
264
+ div.gallery {
265
+ margin: 5px;
266
+ border: 1px solid #ccc;
267
+ float: left;
268
+ width: 180px;
269
+ }
270
+
271
+ div.gallery:hover {
272
+ border: 1px solid #777;
273
+ }
274
+
275
+ div.gallery img {
276
+ width: 100%;
277
+ height: auto;
278
+ }
279
+
280
+ div.desc {
281
+ padding: 15px;
282
+ text-align: center;
283
+ }
284
+ CSS
285
+
286
+ screenshots = Dir["#{options[:output_dir]}/*.png"].sort
287
+
288
+ File.open("#{report_dir}/screenshots-#{filename}.html", 'w') do |f|
289
+
290
+ layout = <<-HTML
291
+ <!DOCTYPE html>
292
+ <html>
293
+ <head>
294
+ <title>#{title}</title>
295
+ <style>
296
+ #{ gallery_css }
297
+ </style>
298
+ </head>
299
+ <body>
300
+ <div class="container">
301
+ <h1>#{title}</h1>
302
+ </div>
303
+ </body>
304
+ </html>
305
+ HTML
306
+
307
+ f << layout
308
+
309
+ screenshots.each do |image|
310
+ f << "<div class='gallery'>"
311
+ f << "<a target='_blank' href='#{image}'>"
312
+ f << "<img src='#{image}' width='300' height='200'>"
313
+ f << "</a>"
314
+ #"<div class='desc'>Add a description of the image here</div>"
315
+ f << "</div>"
316
+ end
317
+ end
318
+ end
319
+
320
+ def create_base_report filename
321
+ create_dir "#{Dir.pwd}/reports"
322
+ report_dir = "#{Dir.pwd}/reports/#{filename}"
323
+ create_dir report_dir
324
+ report_links = Dir["#{report_dir}/*"].map { |r| { name: File.basename(r).split("-")[0].upcase, report: r } }
325
+
326
+ File.open("#{report_dir}/#{filename}.html", 'w') do |f|
327
+
328
+ layout = <<-HTML
329
+ <!DOCTYPE html>
330
+ <html>
331
+ <head>
332
+ <title>#{options[:config][:caps][:caps][:appPackage]}</title>
333
+ <style type="text/css" media="screen">
334
+ table, th, td {
335
+ border: 1px solid black;
336
+ border-collapse: collapse;
337
+ }
338
+ th, td {
339
+ padding: 5px;
340
+ }
341
+ th {
342
+ text-align: left;
343
+ }
344
+ </style>
345
+ </head>
346
+ <body>
347
+ <div class="container">
348
+ <h1>#{options[:config][:caps][:caps][:appPackage]}</h1>
349
+ </div>
350
+ <p>App Version: #{options[:app_and_device_data][:app_version]}</p>
351
+ <p>App Size: #{options[:app_and_device_data][:app_size]} MB</p>
352
+
353
+ </body>
354
+ </html>
355
+ HTML
356
+
357
+ f << layout
358
+
359
+ f << generate_table(f, "Report Links", "Report", "Link", report_links)
360
+ f << "<p></p>"
361
+ f << "<p></p>"
362
+ f << generate_table(f, "AAET Arguments", "Option", "Value", options[:options])
363
+ f << generate_table(f, "Capabilities Used", "Capability", "Value", options[:config][:caps][:caps])
364
+ f << generate_table(f, "Device Attributes", "Attribute", "Value", options[:app_and_device_data][:device])
365
+ f << "<p></p>"
366
+ f << "<p></p>"
367
+
368
+ activities = options[:app_and_device_data][:activities].map { |a| { :"#{a}"=> found_activities.include?(a).to_s } }.reduce({}, :merge)
369
+ f << generate_table(f, "App Activities", "Activity", "Found", activities)
370
+
371
+ if options[:options][:applitools]
372
+ all_tests = options[:config][:applitools][0].keys
373
+ all_tests.delete(:do_not_upload)
374
+
375
+ #results_array = get_list("applitools_results")
376
+ results_array = get_list_without_process("applitools_results")
377
+
378
+ url = results_array[0]["url"]
379
+ results_hash = results_array.map { |t| { :"#{t["test"]}"=> t["passed"] } }.reduce({}, :merge)
380
+ missing_tests = all_tests - results_hash.keys
381
+ missing_tests.each { |t| results_hash.merge!({:"#{t}" => "N/A"}) }
382
+ f << generate_table(f, "Applitools Tests", "Test", "Passed", results_hash, url)
383
+ end
384
+ end
385
+ end
386
+
387
+ def generate_table report_block, table_name, column1, column2, data_array, *url
388
+ report_block << "<table style='display: inline-block;'>"
389
+ if url[0]
390
+ report_block << "<caption><a href='#{url[0]}'>#{table_name}</a></caption>"
391
+ else
392
+ report_block << "<caption>#{table_name}</caption>"
393
+ end
394
+ report_block << "<tr>"
395
+ report_block << "<th>#{column1}</th>"
396
+ report_block << "<th>#{column2}</th>"
397
+ report_block << "</tr>"
398
+ data_array.each do |key, value|
399
+ if key.is_a? Hash
400
+ report_block << "<tr>"
401
+ report_block << "<td>#{key.values[0]}</td>"
402
+ report_block << "<td><a href='#{key.values[-1]}'>#{File.basename(key.values[-1])}</a></td>"
403
+ report_block << "</tr>"
404
+ else
405
+ report_block << "<tr>"
406
+ report_block << "<td>#{key}</td>"
407
+ report_block << "<td>#{value}</td>"
408
+ report_block << "</tr>"
409
+ end
410
+ end
411
+ report_block << "</table>"
412
+ end
413
+
414
+ def generate_reports
415
+ #output dir
416
+ dir = options[:output_dir]
417
+
418
+ #app info
419
+ app_data = options[:app_and_device_data]
420
+ package_name = app_data[:app_package]
421
+ app_version = app_data[:app_version]
422
+ app_size = app_data[:app_size]
423
+ language = options[:config][:caps][:caps][:language]
424
+ orientation = options[:config][:caps][:caps][:orientation]
425
+ activities = options[:app_and_device_data][:activities]
426
+
427
+ #device info
428
+ device_date = options[:app_and_device_data][:device]
429
+ platform = device_date[:platform]
430
+ uuid = device_date[:uuid]
431
+ manufacturer = device_date[:manufacturer]
432
+ model = device_date[:model]
433
+ os = device_date[:os]
434
+ sdk = device_date[:sdk]
435
+
436
+ #Max performance values
437
+ #unless options[:options][:cloud]
438
+ maxmem = max_memory["performance"]["app_mem"] rescue 0
439
+ maxcpu = max_cpu["performance"]["app_cpu"] rescue 0
440
+ #end
441
+
442
+ #did the app crash?
443
+ crashed = app_crashed?
444
+
445
+ app_info = { package_name: package_name, version: app_version, language: language, orientation: orientation, app_size: app_size }
446
+ device_info = { platform: platform, manufacturer: manufacturer, model: model, uuid: uuid, os: os, sdk: sdk }
447
+ max_values = { max_mem: maxmem, max_cpu: maxcpu }
448
+ data = parse_data(dir)
449
+ report = { crashed: crashed, app_info: app_info, device_info: device_info, max_values: max_values, data: data }
450
+
451
+ if crashed
452
+ filename = "#{options[:run_time]}-#{uuid}-#{process}-#{language}-#{package_name}.crashed"
453
+ else
454
+ filename = "#{options[:run_time]}-#{uuid}-#{process}-#{language}-#{package_name}"
455
+ end
456
+
457
+ create_dir "reports"
458
+ report_dir = "#{Dir.pwd}/reports/#{filename}"
459
+ create_dir report_dir
460
+
461
+ if options[:options][:translate]
462
+ if ENV["GOOGLE_API_KEY"].nil? and (options[:config][:settings][:google_translate_key].nil? or options[:config][:settings][:google_translate_key].empty?)
463
+ puts "\nSkipping Google Translate Report!!!".red
464
+ puts "Need a GOOGLE_API_KEY Environment Variable API Key set OR place key in config file under settings...\n".red
465
+ puts "Get a key at: https://cloud.google.com/translate/docs/quickstart\n".yellow
466
+ else
467
+ bad = collect_bad_strings(language)
468
+ screenshot_array = screenshots(dir)
469
+ grouped = bad.map { |x| { screenshot: find_screenshot_path(screenshot_array, x[0]), strings: x[1] } }
470
+
471
+ if grouped.empty?
472
+ puts "\nNo Translation Descrepancies Found! Skipping Translation Report Generation...\n".green
473
+ else
474
+ generate_translations_report(filename, "Translations", grouped)
475
+ end
476
+ end
477
+ end
478
+
479
+ #generate perforance report.
480
+ unless options[:options][:cloud]
481
+ generate_performance_html_report filename, report
482
+
483
+ #copy logcat to report dir
484
+ logcat = dir + "/exception-#{options[:process]}.log"
485
+ FileUtils.cp logcat, "#{Dir.pwd}/reports/#{filename}"
486
+ FileUtils.rm_f logcat if File.exists? logcat
487
+
488
+ #copy appium log to report dir
489
+ appium_log = options[:output_dir_base] + "/appium-#{options[:process]}.log"
490
+ FileUtils.cp appium_log, "#{Dir.pwd}/reports/#{filename}"
491
+ FileUtils.rm_f appium_log if File.exists? appium_log
492
+ end
493
+
494
+ #create yml file from AAET options for reporting
495
+ File.write("#{Dir.pwd}/reports/#{filename}/aaet_options-#{options[:process]}.yml", options.to_yaml)
496
+
497
+ #create screenshots report
498
+ device = options[:config][:caps][:caps][:udid]
499
+ generate_screenshots_report filename, "#{device} Screenshots"
500
+
501
+ create_base_report filename
502
+
503
+ #save run data to runs dir.
504
+ report_dir = "#{Dir.pwd}/runs"
505
+ create_dir report_dir
506
+ open("#{report_dir}/#{filename}.json", 'w') { |f| f << report.merge!({options: options, activities: activities}) }
507
+
508
+ puts "\nVIEW REPORTS: file:///#{Dir.pwd}/reports/#{filename}/#{filename}.html\n Open in FIREFOX!... Chrome, Safari wont display the performance report...\n".yellow
509
+ end
510
+ end
511
+
512
+ #ReportBuilder.new(false).generate_reports