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.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +145 -0
- data/LICENSE +201 -0
- data/README.md +177 -0
- data/Rakefile +6 -0
- data/aaet.gemspec +51 -0
- data/bin/aaet +143 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/generate_reports.rb +512 -0
- data/lib/aaet.rb +43 -0
- data/lib/aaet/android/android.rb +190 -0
- data/lib/aaet/android/parser.rb +76 -0
- data/lib/aaet/common/applitools.rb +41 -0
- data/lib/aaet/common/common_methods.rb +646 -0
- data/lib/aaet/common/locators.rb +180 -0
- data/lib/aaet/common/redis.rb +108 -0
- data/lib/aaet/ios/ios.rb +18 -0
- data/lib/aaet/ios/parser.rb +1 -0
- data/lib/version.rb +3 -0
- data/run.rb +294 -0
- data/setup/android_setup_methods.rb +161 -0
- data/setup/common_setup_methods.rb +209 -0
- data/setup/ios_setup_methods.rb +11 -0
- data/template_google_api_format.haml +48 -0
- metadata +354 -0
data/Rakefile
ADDED
data/aaet.gemspec
ADDED
@@ -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
|
data/bin/aaet
ADDED
@@ -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
|
data/bin/console
ADDED
@@ -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
|
data/bin/setup
ADDED
data/generate_reports.rb
ADDED
@@ -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
|