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,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