kraken-mobile 1.0.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/LICENSE +8 -0
- data/README.md +169 -0
- data/bin/kraken-mobile +91 -0
- data/bin/kraken-mobile-calabash-android.rb +85 -0
- data/bin/kraken-mobile-generate.rb +19 -0
- data/bin/kraken-mobile-helpers.rb +48 -0
- data/bin/kraken-mobile-setup.rb +50 -0
- data/calabash-android-features-skeleton/my_first.feature +11 -0
- data/calabash-android-features-skeleton/step_definitions/kraken_steps.rb +1 -0
- data/calabash-android-features-skeleton/support/app_installation_hooks.rb +25 -0
- data/calabash-android-features-skeleton/support/app_life_cycle_hooks.rb +10 -0
- data/calabash-android-features-skeleton/support/env.rb +1 -0
- data/lib/kraken-mobile.rb +29 -0
- data/lib/kraken-mobile/constants.rb +25 -0
- data/lib/kraken-mobile/helpers/command_helper.rb +38 -0
- data/lib/kraken-mobile/helpers/devices_helper/adb_helper.rb +157 -0
- data/lib/kraken-mobile/helpers/devices_helper/manager.rb +43 -0
- data/lib/kraken-mobile/helpers/feature_analyzer.rb +28 -0
- data/lib/kraken-mobile/helpers/feature_grouper.rb +48 -0
- data/lib/kraken-mobile/helpers/reporter.rb +381 -0
- data/lib/kraken-mobile/models/device.rb +32 -0
- data/lib/kraken-mobile/protocols/file_protocol.rb +126 -0
- data/lib/kraken-mobile/runners/calabash/android/android_runner.rb +130 -0
- data/lib/kraken-mobile/runners/calabash/android/apk_signer.rb +14 -0
- data/lib/kraken-mobile/runners/calabash/android/cucumber.rb +27 -0
- data/lib/kraken-mobile/runners/calabash/android/kraken_hooks.rb +15 -0
- data/lib/kraken-mobile/runners/calabash/android/kraken_steps.rb +3 -0
- data/lib/kraken-mobile/runners/calabash/android/monkey_helper.rb +139 -0
- data/lib/kraken-mobile/runners/calabash/android/operations.rb +44 -0
- data/lib/kraken-mobile/runners/calabash/android/steps/communication_steps.rb +57 -0
- data/lib/kraken-mobile/runners/calabash/monkey/monkey_runner.rb +113 -0
- data/lib/kraken-mobile/runners/runner.rb +18 -0
- data/lib/kraken-mobile/version.rb +5 -0
- data/reporter/assets/css/bootstrap.min.css +6 -0
- data/reporter/assets/css/dataTables.bootstrap.min.css +1 -0
- data/reporter/assets/css/feature_index.css +449 -0
- data/reporter/assets/css/font-awesome.min.css +4 -0
- data/reporter/assets/css/responsive.dataTables.min.css +1 -0
- data/reporter/assets/css/scenario_index.css +461 -0
- data/reporter/assets/fonts/FontAwesome.otf +0 -0
- data/reporter/assets/fonts/fontawesome-webfont.eot +0 -0
- data/reporter/assets/fonts/fontawesome-webfont.svg +2671 -0
- data/reporter/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/reporter/assets/fonts/fontawesome-webfont.woff +0 -0
- data/reporter/assets/fonts/fontawesome-webfont.woff2 +0 -0
- data/reporter/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/reporter/assets/fonts/glyphicons-halflings-regular.svg +288 -0
- data/reporter/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/reporter/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/reporter/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/reporter/assets/images/kraken.png +0 -0
- data/reporter/assets/js/Chart.min.js +14 -0
- data/reporter/assets/js/bootstrap.min.js +7 -0
- data/reporter/assets/js/d3.chart.min.js +7 -0
- data/reporter/assets/js/d3.v3.min.js +5 -0
- data/reporter/assets/js/dataTables.bootstrap.min.js +8 -0
- data/reporter/assets/js/dataTables.responsive.min.js +26 -0
- data/reporter/assets/js/first-sankey.js +292 -0
- data/reporter/assets/js/gitgraph.min.js +10 -0
- data/reporter/assets/js/html5shiv.min.js +4 -0
- data/reporter/assets/js/jquery-3.2.1.min.js +4 -0
- data/reporter/assets/js/jquery.dataTables.min.js +167 -0
- data/reporter/assets/js/respond.min.js +5 -0
- data/reporter/assets/js/sankey.js +512 -0
- data/reporter/feature_report.html.erb +274 -0
- data/reporter/index.html.erb +711 -0
- data/reporter/scenario_report.html.erb +174 -0
- metadata +169 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
Feature: Example feature
|
2
|
+
|
3
|
+
@user1
|
4
|
+
Scenario: As a first user I say hi to a second user
|
5
|
+
Given I wait
|
6
|
+
Then I send a signal to user 2 containing "hi"
|
7
|
+
|
8
|
+
@user2
|
9
|
+
Scenario: As a second user I wait for user 1 to say hi
|
10
|
+
Given I wait for a signal containing "hi"
|
11
|
+
Then I wait
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'kraken-mobile/runners/calabash/android/kraken_steps'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'calabash-android/management/app_installation'
|
2
|
+
|
3
|
+
AfterConfiguration do |config|
|
4
|
+
FeatureMemory.feature = nil
|
5
|
+
end
|
6
|
+
|
7
|
+
Before do |scenario|
|
8
|
+
scenario = scenario.scenario_outline if scenario.respond_to?(:scenario_outline)
|
9
|
+
|
10
|
+
feature = scenario.feature
|
11
|
+
if FeatureMemory.feature != feature || ENV['RESET_BETWEEN_SCENARIOS'] == '1'
|
12
|
+
if ENV['RESET_BETWEEN_SCENARIOS'] == '1'
|
13
|
+
log 'New scenario - reinstalling apps'
|
14
|
+
else
|
15
|
+
log 'First scenario in feature - reinstalling apps'
|
16
|
+
end
|
17
|
+
clear_app_data
|
18
|
+
FeatureMemory.feature = feature
|
19
|
+
FeatureMemory.invocation = 1
|
20
|
+
else
|
21
|
+
FeatureMemory.invocation += 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
FeatureMemory = Struct.new(:feature, :invocation).new
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'kraken-mobile/runners/calabash/android/cucumber'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'kraken-mobile/runners/calabash/android/android_runner'
|
2
|
+
require 'kraken-mobile/runners/calabash/monkey/monkey_runner'
|
3
|
+
require 'kraken-mobile/constants'
|
4
|
+
|
5
|
+
module KrakenMobile
|
6
|
+
class App
|
7
|
+
# Constructors
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
@runner = current_runner
|
11
|
+
end
|
12
|
+
|
13
|
+
# Helpers
|
14
|
+
def run_in_parallel
|
15
|
+
@runner.run_in_parallel
|
16
|
+
end
|
17
|
+
|
18
|
+
def current_runner
|
19
|
+
case @options[:runner]
|
20
|
+
when KrakenMobile::Constants::CALABASH_ANDROID
|
21
|
+
Runner::CalabashAndroidRunner.new(@options)
|
22
|
+
when KrakenMobile::Constants::MONKEY
|
23
|
+
Runner::MonkeyRunner.new(@options)
|
24
|
+
else
|
25
|
+
raise "Invalid Kraken runner."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module KrakenMobile
|
2
|
+
module Constants
|
3
|
+
# Runners
|
4
|
+
CALABASH_ANDROID = "calabash-android"
|
5
|
+
MONKEY = "monkey"
|
6
|
+
REPORT_PATH = "./reports"
|
7
|
+
REPORT_DEVICES_FILE_NAME = "devices"
|
8
|
+
REPORT_FILE_NAME = "report"
|
9
|
+
D3_DATA_FILE_NAME = "data"
|
10
|
+
|
11
|
+
# Protocols
|
12
|
+
FILE_PROTOCOL = "file-based"
|
13
|
+
SUPPORTED_PROTOCOLS = [FILE_PROTOCOL]
|
14
|
+
|
15
|
+
# Protocol
|
16
|
+
DEVICE_INBOX_NAME = "inbox"
|
17
|
+
KRAKEN_CONFIGURATION_FILE_NAME = "kraken_settings"
|
18
|
+
DEFAULT_TIMEOUT = 10
|
19
|
+
MONKEY_DEFAULT_TIMEOUT = 5
|
20
|
+
|
21
|
+
# ADB Orientations
|
22
|
+
PORTRAIT = 0
|
23
|
+
LANDSCAPE = 1
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module KrakenMobile
|
2
|
+
class CommandHelper
|
3
|
+
def user_is_using_windows
|
4
|
+
RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/
|
5
|
+
end
|
6
|
+
|
7
|
+
def terminal_command_separator
|
8
|
+
user_is_using_windows ? ' & ' : ';'
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_command commands
|
12
|
+
commands.compact*' '
|
13
|
+
end
|
14
|
+
|
15
|
+
# Exports a list of environment variables to the users computer.
|
16
|
+
def build_export_env_command env_variables
|
17
|
+
commands = env_variables.map { |key, value|
|
18
|
+
user_is_using_windows ? "(SET \"#{key}=#{value}\")" : "#{key}=#{value};export #{key}"
|
19
|
+
}
|
20
|
+
commands.join(terminal_command_separator)
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute_command process_number, command
|
24
|
+
output = open("|#{command}", 'r') { |output| show_output(output, process_number) }
|
25
|
+
exitstatus = $?.exitstatus
|
26
|
+
end
|
27
|
+
|
28
|
+
def show_output(output, process_number)
|
29
|
+
loop do
|
30
|
+
begin
|
31
|
+
line = output.readline()
|
32
|
+
$stdout.print "#{process_number}> #{line}"
|
33
|
+
$stdout.flush
|
34
|
+
end
|
35
|
+
end rescue EOFError
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'kraken-mobile/models/device'
|
2
|
+
require 'kraken-mobile/constants'
|
3
|
+
|
4
|
+
module KrakenMobile
|
5
|
+
module DevicesHelper
|
6
|
+
class AdbHelper
|
7
|
+
# ADB command that returns all phones and emulators connected to the computer.
|
8
|
+
def adb_devices_l
|
9
|
+
`adb devices -l`
|
10
|
+
end
|
11
|
+
|
12
|
+
def file_content file_name, device_id
|
13
|
+
`adb -s #{device_id} shell "cat /sdcard/#{file_name} 2> /dev/null"`
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_content_to_device content, file_name, device_id
|
17
|
+
`adb -s #{device_id} shell "echo "#{content}" > /sdcard/#{file_name}"`
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_file_in_device file_name, device_id
|
21
|
+
`adb -s #{device_id} shell "> /sdcard/#{file_name}"`
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete_file_in_device file_name, device_id
|
25
|
+
`adb -s #{device_id} shell "rm -rf /sdcard/#{file_name}"`
|
26
|
+
end
|
27
|
+
|
28
|
+
def device_screen_size device_id
|
29
|
+
`adb -s #{device_id} shell wm size`
|
30
|
+
end
|
31
|
+
|
32
|
+
def device_sdk_version device_id
|
33
|
+
`adb -s #{device_id} shell getprop ro.build.version.sdk`
|
34
|
+
end
|
35
|
+
|
36
|
+
def device_orientation device_id
|
37
|
+
`adb -s #{device_id} shell dumpsys input | grep 'SurfaceOrientation' | awk '{ print $2 }'`
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_device_connected device_id
|
41
|
+
begin
|
42
|
+
adb_devices_l.include?(device_id)
|
43
|
+
rescue
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns an array with all the devices and emulators connected to the computer.
|
49
|
+
def connected_devices
|
50
|
+
begin
|
51
|
+
devices = []
|
52
|
+
list =
|
53
|
+
adb_devices_l.split("\n").each do |line|
|
54
|
+
line_id = extract_device_id(line)
|
55
|
+
line_model = extract_device_model(line)
|
56
|
+
if line_id && line_model
|
57
|
+
device = Models::Device.new(line_id, line_model, devices.size + 1)
|
58
|
+
devices << device
|
59
|
+
end
|
60
|
+
end
|
61
|
+
devices
|
62
|
+
rescue
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def read_file_content file_name, device_id
|
68
|
+
begin
|
69
|
+
raise "Device #{device_id} not found" unless is_device_connected(device_id)
|
70
|
+
content = file_content("#{file_name}.txt", device_id)
|
71
|
+
content.strip
|
72
|
+
rescue
|
73
|
+
""
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_content_to_file content, file_name, device_id
|
78
|
+
begin
|
79
|
+
raise "Device #{device_id} not found" unless is_device_connected(device_id)
|
80
|
+
write_content_to_device(content, "#{file_name}.txt", device_id)
|
81
|
+
true
|
82
|
+
rescue
|
83
|
+
false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_file file_name, device_id
|
88
|
+
begin
|
89
|
+
raise "Device #{device_id} not found" unless is_device_connected(device_id)
|
90
|
+
create_file_in_device("#{file_name}.txt", device_id)
|
91
|
+
true
|
92
|
+
rescue
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def delete_file file_name, device_id
|
98
|
+
begin
|
99
|
+
raise "Device #{device_id} not found" unless is_device_connected(device_id)
|
100
|
+
delete_file_in_device("#{file_name}.txt", device_id)
|
101
|
+
true
|
102
|
+
rescue
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns height, width
|
108
|
+
def screen_size device_id
|
109
|
+
begin
|
110
|
+
adb_size = device_screen_size device_id
|
111
|
+
parts = adb_size.strip!.split(" ")
|
112
|
+
size = parts[parts.count-1]
|
113
|
+
return 0,0 if !size.include?("x")
|
114
|
+
size_parts = size.split("x")
|
115
|
+
if orientation(device_id) == KrakenMobile::Constants::PORTRAIT
|
116
|
+
return size_parts[1].to_i, size_parts[0].to_i
|
117
|
+
else
|
118
|
+
return size_parts[0].to_i, size_parts[1].to_i
|
119
|
+
end
|
120
|
+
rescue
|
121
|
+
return 0,0
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def sdk_version device_id
|
126
|
+
begin
|
127
|
+
return device_sdk_version device_id
|
128
|
+
rescue
|
129
|
+
return "N/A"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def orientation device_id
|
134
|
+
begin
|
135
|
+
adb_orientation = device_orientation(device_id).strip!
|
136
|
+
return adb_orientation.to_i
|
137
|
+
rescue
|
138
|
+
return KrakenMobile::Constants::PORTRAIT
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Parses the device id from the ADB devices command.
|
143
|
+
def extract_device_id line
|
144
|
+
if line.match(/device(?!s)/)
|
145
|
+
line.split(" ").first
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Parses the device model from the ADB devices command.
|
150
|
+
def extract_device_model line
|
151
|
+
if line.match(/device(?!s)/)
|
152
|
+
line.scan(/model:(.*) device/).flatten.first
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'kraken-mobile/models/device'
|
2
|
+
require 'kraken-mobile/helpers/devices_helper/adb_helper'
|
3
|
+
require 'kraken-mobile/constants'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module KrakenMobile
|
7
|
+
module DevicesHelper
|
8
|
+
class Manager
|
9
|
+
def initialize(options)
|
10
|
+
@runner_name = options[:runner]
|
11
|
+
@config_path = options[:config_path]
|
12
|
+
end
|
13
|
+
|
14
|
+
def connected_devices
|
15
|
+
if @config_path
|
16
|
+
raise "The path of the configuration file is not valid" unless File.exist?(@config_path) && File.file?(@config_path) && @config_path.end_with?(".json")
|
17
|
+
file = open(@config_path)
|
18
|
+
content = file.read
|
19
|
+
configured_devices = JSON.parse(content)
|
20
|
+
devices = []
|
21
|
+
configured_devices.each do |dev_data|
|
22
|
+
device = Models::Device.new(dev_data["id"], dev_data["model"], devices.size + 1, dev_data["config"])
|
23
|
+
devices << device
|
24
|
+
end
|
25
|
+
devices
|
26
|
+
else
|
27
|
+
device_helper.connected_devices
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def device_helper
|
32
|
+
case @runner_name
|
33
|
+
when KrakenMobile::Constants::CALABASH_ANDROID
|
34
|
+
DevicesHelper::AdbHelper.new()
|
35
|
+
when KrakenMobile::Constants::MONKEY
|
36
|
+
DevicesHelper::AdbHelper.new()
|
37
|
+
else
|
38
|
+
raise "Runner is not supported"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'gherkin/parser'
|
2
|
+
require 'gherkin/pickles/compiler'
|
3
|
+
|
4
|
+
def ensure_features_format files
|
5
|
+
files.each do |file_path|
|
6
|
+
ensure_feature_has_unique_tags file_path
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def ensure_feature_has_unique_tags file_path
|
11
|
+
parser = Gherkin::Parser.new
|
12
|
+
file = open(file_path)
|
13
|
+
content = file.read
|
14
|
+
gherkin_document = parser.parse(content)
|
15
|
+
pickles = Gherkin::Pickles::Compiler.new.compile(gherkin_document)
|
16
|
+
tag_hash = {}
|
17
|
+
pickles.each do |scenario|
|
18
|
+
raise "Scenario '#{scenario[:name]}' can't have more than one @user{int} tag." if scenario[:tags].select{ |tag| tag[:name].start_with? "@user" }.count > 1
|
19
|
+
scenario[:tags].each do |tag|
|
20
|
+
tag_name = tag[:name]
|
21
|
+
if tag_hash[tag_name]
|
22
|
+
raise "Tag #{tag_name} is duplicated. Each feature can only have one @user{:int} tag assigned to a scenario."
|
23
|
+
else
|
24
|
+
tag_hash[tag_name] = tag_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'kraken-mobile/helpers/feature_analyzer'
|
3
|
+
|
4
|
+
module KrakenMobile
|
5
|
+
class FeatureGrouper
|
6
|
+
# Returns files in feature_folder distributed equally in group size.
|
7
|
+
def self.distributed_file_groups(feature_folder, group_size)
|
8
|
+
files = feature_files_in_folder feature_folder
|
9
|
+
groups = create_file_groups group_size,files
|
10
|
+
groups
|
11
|
+
end
|
12
|
+
|
13
|
+
# All groups contains all files in feature_folder
|
14
|
+
def self.file_groups(feature_folder, group_size)
|
15
|
+
files = feature_files_in_folder feature_folder
|
16
|
+
ensure_features_format files
|
17
|
+
group_size.times.map { files }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.create_file_groups group_size, files
|
21
|
+
files_per_group = files.size/group_size
|
22
|
+
number_of_remaining_files = files.size % group_size
|
23
|
+
groups = Array.new(group_size) { [] }
|
24
|
+
groups.each do |group|
|
25
|
+
files_per_group.times {
|
26
|
+
group << files.delete_at(0)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
unless number_of_remaining_files == 0
|
30
|
+
groups[0..(number_of_remaining_files-1)].each do |group|
|
31
|
+
group << files.delete_at(0)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
groups.reject(&:empty?)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.feature_files_in_folder(feature_dir_or_file)
|
38
|
+
if File.directory?(feature_dir_or_file) # Is a folder containing feature files.
|
39
|
+
files = Dir[File.join(feature_dir_or_file, "**{,/*/**}/*")].uniq
|
40
|
+
files.grep(/\.feature$/)
|
41
|
+
elsif feature_dir_or_file.include?('.feature') # Is a feature file.
|
42
|
+
[feature_dir_or_file]
|
43
|
+
else
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,381 @@
|
|
1
|
+
require 'kraken-mobile/constants'
|
2
|
+
require 'digest'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module KrakenMobile
|
6
|
+
class Reporter
|
7
|
+
|
8
|
+
PASSED = 'passed'
|
9
|
+
FAILED = 'failed',
|
10
|
+
SKIPPED = 'skipped',
|
11
|
+
PENDING = 'pending',
|
12
|
+
NOT_DEFINED = 'undefined',
|
13
|
+
AMBIGUOUS = 'ambiguous'
|
14
|
+
|
15
|
+
def initialize(execution_id, options)
|
16
|
+
@execution_id = execution_id
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
#-------------------------------
|
21
|
+
# Generator
|
22
|
+
#-------------------------------
|
23
|
+
def generate_general_report
|
24
|
+
erb_file = File.join(File.expand_path("../../../../reporter/", __FILE__), "index.html.erb")
|
25
|
+
html_file = File.join(File.expand_path("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/"), "index.html")
|
26
|
+
# Variables
|
27
|
+
report_file = open("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{KrakenMobile::Constants::REPORT_DEVICES_FILE_NAME}.json")
|
28
|
+
content = report_file.read
|
29
|
+
@devices = JSON.parse(content)
|
30
|
+
devices_report = report_by_devices(@devices)
|
31
|
+
@features_report = fetures_from_report_by_devices devices_report
|
32
|
+
data_hash = feature_by_nodes_and_links @features_report
|
33
|
+
file = open("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/assets/js/#{KrakenMobile::Constants::D3_DATA_FILE_NAME}.json", 'w')
|
34
|
+
file.puts(data_hash.to_json)
|
35
|
+
file.close
|
36
|
+
template = File.read(erb_file)
|
37
|
+
result = ERB.new(template).result(binding)
|
38
|
+
# write result to file
|
39
|
+
File.open(html_file, 'w+') do |f|
|
40
|
+
f.write result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_device_report device
|
45
|
+
@apk_path = device.config["apk_path"] ? device.config["apk_path"] : @options[:apk_path]
|
46
|
+
report_file = open("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{device.id}/#{KrakenMobile::Constants::REPORT_FILE_NAME}.json")
|
47
|
+
content = report_file.read
|
48
|
+
@features = JSON.parse(content)
|
49
|
+
@total_scenarios = total_scenarios @features
|
50
|
+
@device = device
|
51
|
+
@total_failed_scenarios_percentage = total_failed_scenarios_percentage @features
|
52
|
+
@total_passed_scenarios_percentage = total_passed_scenarios_percentage @features
|
53
|
+
@total_passed_features_percentage = total_passed_features_percentage @features
|
54
|
+
@total_failed_features_percentage = total_failed_features_percentage @features
|
55
|
+
erb_file = File.join(File.expand_path("../../../../reporter/", __FILE__), "feature_report.html.erb")
|
56
|
+
html_file = File.join(File.expand_path("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{device.id}/"), File.basename(erb_file, '.erb')) #=>"page.html"
|
57
|
+
# Variables
|
58
|
+
template = File.read(erb_file)
|
59
|
+
result = ERB.new(template).result(binding)
|
60
|
+
# write result to file
|
61
|
+
File.open(html_file, 'w+') do |f|
|
62
|
+
f.write result
|
63
|
+
end
|
64
|
+
generate_features_report @features, device
|
65
|
+
end
|
66
|
+
|
67
|
+
def report_by_devices devices
|
68
|
+
devices_report = {}
|
69
|
+
devices.each do |device|
|
70
|
+
next if !File.exists?("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{device['id']}/#{KrakenMobile::Constants::REPORT_FILE_NAME}.json")
|
71
|
+
report_file = open("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{device['id']}/#{KrakenMobile::Constants::REPORT_FILE_NAME}.json")
|
72
|
+
content = report_file.read
|
73
|
+
devices_report[device['user']] = JSON.parse(content)
|
74
|
+
devices_report[device['user']].each do |d| d["device_model"] = device["model"] if !d["device_model"] end
|
75
|
+
devices_report[device['user']].each do |d| d["device_id"] = device["id"] if !d["device_id"] end
|
76
|
+
end
|
77
|
+
devices_report
|
78
|
+
end
|
79
|
+
|
80
|
+
def fetures_from_report_by_devices report_by_devices
|
81
|
+
features = {}
|
82
|
+
report_by_devices.keys.each do |user_key|
|
83
|
+
report = report_by_devices[user_key]
|
84
|
+
report.each do |feature|
|
85
|
+
features[feature["id"]] = {} if !features[feature["id"]]
|
86
|
+
features[feature["id"]]["name"] = feature["name"] if !features[feature["id"]]["name"] && feature["name"]
|
87
|
+
features[feature["id"]]["devices"] = {} if !features[feature["id"]]["devices"]
|
88
|
+
if feature["elements"] && feature["elements"].count > 0
|
89
|
+
features[feature["id"]]["devices"][user_key] = []
|
90
|
+
if feature["elements"].first["steps"]
|
91
|
+
failed = false
|
92
|
+
feature["elements"].first["steps"].each do |step|
|
93
|
+
next if failed
|
94
|
+
failed = step["result"]["status"] != PASSED
|
95
|
+
image = nil
|
96
|
+
image = step["after"].first["embeddings"].first["data"] if step["after"] && step["after"].count > 0 && step["after"].first["embeddings"] && step["after"].first["embeddings"].count > 0
|
97
|
+
features[feature["id"]]["devices"][user_key] << {
|
98
|
+
name: "#{step['keyword']} #{step['name']}",
|
99
|
+
duration: step["result"]["duration"],
|
100
|
+
image: image,
|
101
|
+
device_model: feature["device_model"],
|
102
|
+
status: failed ? FAILED : PASSED
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
features
|
110
|
+
end
|
111
|
+
|
112
|
+
def feature_by_nodes_and_links features_report
|
113
|
+
features = []
|
114
|
+
features_report.values.each do |feature|
|
115
|
+
features << nodes_and_links(feature["devices"], feature["name"]) if feature["devices"]
|
116
|
+
end
|
117
|
+
features
|
118
|
+
end
|
119
|
+
|
120
|
+
def nodes_and_links feature_report, feature_name
|
121
|
+
last_node_id = 0
|
122
|
+
nodes = [{ name: "", id: "empty", image: nil }]
|
123
|
+
signal_hash = {}
|
124
|
+
links = []
|
125
|
+
feature_report.keys.each do |key|
|
126
|
+
steps = feature_report[key]
|
127
|
+
coming_from_signal = false
|
128
|
+
last_signal = -1
|
129
|
+
steps.each_with_index do |step, index|
|
130
|
+
node_id = last_node_id+1
|
131
|
+
if isReadSignal(step[:name]) && step[:status] == PASSED
|
132
|
+
signal = signalContent(step[:name])
|
133
|
+
already_created_signal = signal_hash[signal] ? true : false
|
134
|
+
signal_hash[signal] = already_created_signal ? signal_hash[signal] : { id: "#{node_id}", receiver: key }
|
135
|
+
node = { name: "Signal: #{signal}, Receiver: #{step[:device_model]}", id: signal_hash[signal][:id], image: nil, status: step[:status] }
|
136
|
+
if already_created_signal
|
137
|
+
entry = nodes.select{ |node| node[:id] == signal_hash[signal][:id] }.first
|
138
|
+
entry[:name] = "Signal: #{signal}, Receiver: #{step[:device_model]}" if entry
|
139
|
+
end
|
140
|
+
source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
|
141
|
+
link = {
|
142
|
+
source: source,
|
143
|
+
target: signal_hash[signal][:id].to_i,
|
144
|
+
value: 1,
|
145
|
+
owner: key,
|
146
|
+
owner_model: step[:device_model]
|
147
|
+
}
|
148
|
+
nodes << node if !already_created_signal
|
149
|
+
links << link
|
150
|
+
last_node_id += 1 if !already_created_signal
|
151
|
+
last_signal = signal_hash[signal][:id].to_i
|
152
|
+
coming_from_signal = true
|
153
|
+
elsif isWriteSignal(step[:name]) && step[:status] == PASSED
|
154
|
+
signal = signalContent(step[:name])
|
155
|
+
receiver = signalReceiver(step[:name])
|
156
|
+
already_created_signal = signal_hash[signal] ? true : false
|
157
|
+
signal_hash[signal] = already_created_signal ? signal_hash[signal] : { id: "#{node_id}", receiver: receiver }
|
158
|
+
node = { name: step[:name], id: signal_hash[signal][:id], image: nil, status: step[:status] }
|
159
|
+
source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
|
160
|
+
link = {
|
161
|
+
source: source,
|
162
|
+
target: signal_hash[signal][:id].to_i,
|
163
|
+
value: 1,
|
164
|
+
owner: key,
|
165
|
+
owner_model: step[:device_model]
|
166
|
+
}
|
167
|
+
nodes << node if !already_created_signal
|
168
|
+
links << link
|
169
|
+
last_node_id += 1 if !already_created_signal
|
170
|
+
last_signal = signal_hash[signal][:id].to_i
|
171
|
+
coming_from_signal = true
|
172
|
+
else
|
173
|
+
node = { name: step[:name], id: "#{node_id}", image: step[:image], status: step[:status] }
|
174
|
+
source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
|
175
|
+
link = {
|
176
|
+
source: source,
|
177
|
+
target: node_id,
|
178
|
+
value: 1,
|
179
|
+
owner: key,
|
180
|
+
owner_model: step[:device_model]
|
181
|
+
}
|
182
|
+
nodes << node
|
183
|
+
links << link
|
184
|
+
last_node_id += 1
|
185
|
+
coming_from_signal = false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
return {
|
190
|
+
name: feature_name,
|
191
|
+
nodes: nodes,
|
192
|
+
links: links
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
def isReadSignal step
|
197
|
+
line = step.split(' ')[1..-1].join(' ')
|
198
|
+
(line =~ /^I wait for a signal containing "([^\"]*)"$/ ? true : false) || (line =~ /^I wait for a signal containing "([^\"]*)" for (\d+) seconds$/ ? true : false)
|
199
|
+
end
|
200
|
+
|
201
|
+
def isWriteSignal step
|
202
|
+
line = step.split(' ')[1..-1].join(' ')
|
203
|
+
line =~ /^I send a signal to user (\d+) containing "([^\"]*)"$/ ? true : false
|
204
|
+
end
|
205
|
+
|
206
|
+
def signalContent step
|
207
|
+
line = step.split(' ')[1..-1].join(' ')
|
208
|
+
line.scan(/"([^\"]*)"/).first.first if line.scan(/"([^\"]*)"/).first
|
209
|
+
end
|
210
|
+
|
211
|
+
def signalReceiver step
|
212
|
+
line = step.split(' ')[1..-1].join(' ')
|
213
|
+
line.scan(/(\d+)/).first.first if line.scan(/(\d+)/).first
|
214
|
+
end
|
215
|
+
|
216
|
+
def generate_features_report features, device
|
217
|
+
features.each do |feature|
|
218
|
+
generate_feature_report feature, device
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def generate_feature_report feature, device
|
223
|
+
Dir.mkdir("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{device.id}/features_report") unless File.exists?("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{device.id}/features_report")
|
224
|
+
file_name = feature_id feature
|
225
|
+
erb_file = File.join(File.expand_path("../../../../reporter/", __FILE__), "scenario_report.html.erb")
|
226
|
+
html_file = File.join(File.expand_path("#{KrakenMobile::Constants::REPORT_PATH}/#{@execution_id}/#{device.id}/features_report"), "#{file_name}.html") #=>"page.html"
|
227
|
+
# Variables
|
228
|
+
@feature = feature
|
229
|
+
template = File.read(erb_file)
|
230
|
+
result = ERB.new(template).result(binding)
|
231
|
+
# write result to file
|
232
|
+
File.open(html_file, 'w+') do |f|
|
233
|
+
f.write result
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# 0: create 1: commit 2: merge
|
238
|
+
def branches features_report
|
239
|
+
branches = {}
|
240
|
+
features_report.keys.each do |key|
|
241
|
+
report = features_report[key]
|
242
|
+
branches[report["hash"]] = {} if !branches[report["hash"]]
|
243
|
+
branches[report["hash"]]["steps"]= [] if !branches[report["hash"]]["steps"]
|
244
|
+
devices = report["devices"]
|
245
|
+
devices.keys.each do |device_key|
|
246
|
+
branches[report["hash"]]["steps"] << { type: 0, name: device_key }
|
247
|
+
feature_steps = devices[device_key][0]["steps"] if devices[device_key].count > 0 && devices[device_key][0]["steps"]
|
248
|
+
feature_steps.each do |step|
|
249
|
+
hash_step = { type: 1, name: device_key}
|
250
|
+
hash_step[:image] = step["after"][0]["embeddings"][0] if step["after"].count > 0 && step["after"][0]["embeddings"] && step["after"][0]["embeddings"].count > 0
|
251
|
+
branches[report["hash"]]["steps"] << hash_step
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
branches
|
256
|
+
end
|
257
|
+
|
258
|
+
#-------------------------------
|
259
|
+
# Helpers
|
260
|
+
#-------------------------------
|
261
|
+
def total_scenarios features
|
262
|
+
how_many = 0
|
263
|
+
features.each do |feature|
|
264
|
+
scenarios = feature["elements"]
|
265
|
+
how_many += scenarios.count if scenarios
|
266
|
+
end
|
267
|
+
how_many
|
268
|
+
end
|
269
|
+
|
270
|
+
def feature_id feature
|
271
|
+
Digest::SHA256.hexdigest("#{feature["id"].strip}#{feature["uri"].strip}")
|
272
|
+
end
|
273
|
+
|
274
|
+
def passed_features features
|
275
|
+
features.select{ |feature| passed_scenarios(feature) == feature["elements"].count }
|
276
|
+
end
|
277
|
+
|
278
|
+
def failed_features features
|
279
|
+
features.select{ |feature| failed_scenarios(feature) == feature["elements"].count }
|
280
|
+
end
|
281
|
+
|
282
|
+
def feature_duration feature
|
283
|
+
scenarios = feature["elements"]
|
284
|
+
how_long = 0
|
285
|
+
scenarios.each do |scenario|
|
286
|
+
how_long += scenario_duration(scenario)
|
287
|
+
end
|
288
|
+
how_long
|
289
|
+
end
|
290
|
+
|
291
|
+
def scenario_duration scenario
|
292
|
+
how_long = 0
|
293
|
+
scenario["steps"].each do |step|
|
294
|
+
how_long += step["result"]["duration"] if step["result"] && step["result"]["duration"]
|
295
|
+
end
|
296
|
+
how_long
|
297
|
+
end
|
298
|
+
|
299
|
+
def passed_scenarios feature
|
300
|
+
scenarios = feature["elements"]
|
301
|
+
scenarios.select{ |scenario|
|
302
|
+
steps = scenario["steps"]
|
303
|
+
steps.all?{ |step| step["result"] && step["result"]["status"] == PASSED }
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
def failed_scenarios feature
|
308
|
+
scenarios = feature["elements"]
|
309
|
+
scenarios.select{ |scenario|
|
310
|
+
steps = scenario["steps"]
|
311
|
+
steps.any?{ |step| step["result"] && step["result"]["status"] != PASSED }
|
312
|
+
}
|
313
|
+
end
|
314
|
+
|
315
|
+
def total_passed_scenarios features
|
316
|
+
how_many = 0
|
317
|
+
features.each do |feature|
|
318
|
+
how_many += passed_scenarios(feature).count
|
319
|
+
end
|
320
|
+
how_many
|
321
|
+
end
|
322
|
+
|
323
|
+
def total_failed_scenarios features
|
324
|
+
how_many = 0
|
325
|
+
features.each do |feature|
|
326
|
+
how_many += failed_scenarios(feature).count
|
327
|
+
end
|
328
|
+
how_many
|
329
|
+
end
|
330
|
+
|
331
|
+
def total_passed_features features
|
332
|
+
how_many = 0
|
333
|
+
features.each do |feature|
|
334
|
+
how_many += 1 if feature_passed?(feature)
|
335
|
+
end
|
336
|
+
how_many
|
337
|
+
end
|
338
|
+
|
339
|
+
def total_failed_features features
|
340
|
+
how_many = 0
|
341
|
+
features.each do |feature|
|
342
|
+
how_many += 1 if !feature_passed?(feature)
|
343
|
+
end
|
344
|
+
how_many
|
345
|
+
end
|
346
|
+
|
347
|
+
def feature_passed_scenarios_percentage feature
|
348
|
+
(passed_scenarios(feature).count.to_f/feature["elements"].count.to_f).round(2) * 100.00
|
349
|
+
end
|
350
|
+
|
351
|
+
def feature_failed_scenarios_percentage feature
|
352
|
+
(failed_scenarios(feature).count.to_f/feature["elements"].count.to_f).round(2) * 100.00
|
353
|
+
end
|
354
|
+
|
355
|
+
def total_passed_scenarios_percentage features
|
356
|
+
(total_passed_scenarios(features).to_f/total_scenarios(features).to_f).round(2) * 100.00
|
357
|
+
end
|
358
|
+
|
359
|
+
def total_passed_features_percentage features
|
360
|
+
(total_passed_features(features).to_f/features.count.to_f).round(2) * 100.00
|
361
|
+
end
|
362
|
+
|
363
|
+
def total_failed_scenarios_percentage features
|
364
|
+
(total_failed_scenarios(features).to_f/total_scenarios(features).to_f).round(2) * 100.00
|
365
|
+
end
|
366
|
+
|
367
|
+
def total_failed_features_percentage features
|
368
|
+
(total_failed_features(features).to_f/features.count.to_f).round(2) * 100.00
|
369
|
+
end
|
370
|
+
|
371
|
+
def feature_passed? feature
|
372
|
+
passed_scenarios(feature).count == feature["elements"].count
|
373
|
+
end
|
374
|
+
|
375
|
+
def format_duration(nanoseconds)
|
376
|
+
duration_in_seconds = nanoseconds.to_f/1000000000.0
|
377
|
+
m, s = duration_in_seconds.divmod(60)
|
378
|
+
"#{m}m #{format('%.3f', s)}s"
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|