kraken-mobile 1.0.4 → 1.0.5

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -11
  3. data/bin/kraken-mobile +55 -67
  4. data/bin/kraken_mobile_calabash_android.rb +39 -0
  5. data/bin/kraken_mobile_helpers.rb +91 -0
  6. data/bin/kraken_mobile_setup.rb +134 -0
  7. data/calabash-android-features-skeleton/step_definitions/mobile_steps.rb +1 -0
  8. data/calabash-android-features-skeleton/support/app_installation_hooks.rb +2 -0
  9. data/calabash-android-features-skeleton/support/app_life_cycle_hooks.rb +3 -4
  10. data/calabash-android-features-skeleton/support/env.rb +1 -1
  11. data/calabash-android-features-skeleton/web/step_definitions/web_steps.rb +3 -0
  12. data/calabash-android-features-skeleton/web/support/app_life_cycle_hooks.rb +15 -0
  13. data/lib/kraken-mobile/device_process.rb +94 -0
  14. data/lib/kraken-mobile/helpers/devices_helper/adb_helper.rb +100 -105
  15. data/lib/kraken-mobile/helpers/kraken_faker.rb +107 -0
  16. data/lib/kraken-mobile/hooks/mobile_kraken_hooks.rb +15 -0
  17. data/lib/kraken-mobile/hooks/mobile_operations.rb +36 -0
  18. data/lib/kraken-mobile/hooks/web_operations.rb +33 -0
  19. data/lib/kraken-mobile/mobile/adb.rb +66 -0
  20. data/lib/kraken-mobile/mobile/android_commands.rb +43 -0
  21. data/lib/kraken-mobile/mobile/mobile_process.rb +82 -0
  22. data/lib/kraken-mobile/models/android_device.rb +121 -0
  23. data/lib/kraken-mobile/models/device.rb +113 -31
  24. data/lib/kraken-mobile/models/feature_file.rb +108 -0
  25. data/lib/kraken-mobile/models/feature_scenario.rb +24 -0
  26. data/lib/kraken-mobile/models/web_device.rb +86 -0
  27. data/lib/kraken-mobile/monkeys/mobile/android_monkey.rb +30 -0
  28. data/lib/kraken-mobile/monkeys/mobile/kraken_android_monkey.rb +54 -0
  29. data/lib/kraken-mobile/runners/calabash/android/steps/communication_steps.rb +15 -0
  30. data/lib/kraken-mobile/steps/general_steps.rb +82 -0
  31. data/lib/kraken-mobile/steps/mobile/kraken_steps.rb +72 -0
  32. data/lib/kraken-mobile/steps/web/kraken_steps.rb +81 -0
  33. data/lib/kraken-mobile/test_scenario.rb +193 -0
  34. data/lib/kraken-mobile/utils/feature_reader.rb +17 -0
  35. data/lib/kraken-mobile/utils/k.rb +67 -0
  36. data/lib/kraken-mobile/utils/mobile_cucumber.rb +2 -0
  37. data/lib/kraken-mobile/utils/reporter.rb +453 -0
  38. data/lib/kraken-mobile/version.rb +2 -2
  39. data/lib/kraken-mobile/web/web_process.rb +39 -0
  40. data/lib/kraken_mobile.rb +77 -0
  41. data/reporter/index.html.erb +6 -6
  42. metadata +89 -9
  43. data/bin/kraken-mobile-calabash-android.rb +0 -85
  44. data/bin/kraken-mobile-generate.rb +0 -19
  45. data/bin/kraken-mobile-helpers.rb +0 -48
  46. data/bin/kraken-mobile-setup.rb +0 -50
  47. data/calabash-android-features-skeleton/step_definitions/kraken_steps.rb +0 -1
  48. data/lib/kraken-mobile.rb +0 -29
@@ -0,0 +1,81 @@
1
+ require 'selenium-webdriver'
2
+ require 'uri'
3
+
4
+ driver = Selenium::WebDriver.for :chrome
5
+
6
+ Given(/^I navigate to page "([^\"]*)"$/) do |web_url|
7
+ raise 'ERROR: Invalid URL' if web_url.nil?
8
+ raise 'ERROR: Invalid URL' unless web_url =~ URI::DEFAULT_PARSER.make_regexp
9
+
10
+ driver.navigate.to web_url
11
+ sleep 2
12
+ end
13
+
14
+ Then(/^I enter "([^\"]*)" into input field having id "([^\"]*)"$/) do |text, id|
15
+ driver.find_element(:id, id).send_keys(text)
16
+ sleep 2
17
+ end
18
+
19
+ Then(
20
+ /^I enter "([^\"]*)" into input field having css selector "([^\"]*)"$/
21
+ ) do |text, selector|
22
+ driver.find_element(:css, selector).send_keys(text)
23
+ sleep 2
24
+ end
25
+
26
+ Then(/^I click on element having id "(.*?)"$/) do |id|
27
+ driver.find_element(:id, id).click
28
+ sleep 2
29
+ end
30
+
31
+ Then(/^I wait for (\d+) seconds$/) do |seconds|
32
+ return if seconds.nil?
33
+
34
+ sleep seconds.to_i
35
+ end
36
+
37
+ Then(/^I should see text "(.*?)"$/) do |text|
38
+ driver.page_source.include?(text)
39
+ end
40
+
41
+ # Kraken Steps
42
+ Then(
43
+ /^I send a signal to user (\d+) containing "([^\"]*)"$/
44
+ ) do |process_id, signal|
45
+ device = Device.find_by_process_id(process_id)
46
+ raise 'ERROR: Device not found' if device.nil?
47
+ if process_id.to_s == current_process_id.to_s
48
+ raise 'ERROR: Can\'t send signal to same device'
49
+ end
50
+
51
+ device.write_signal(signal)
52
+ end
53
+
54
+ Then(/^I wait for a signal containing "([^\"]*)"$/) do |signal|
55
+ raise 'ERROR: Invalid scenario tag' if @scenario_tags.nil?
56
+ raise 'ERROR: Invalid scenario tag' if @scenario_tags.grep(/@user/).none?
57
+
58
+ device = Device.find_by_process_id(current_process_id)
59
+ raise 'ERROR: Device not found' if device.nil?
60
+
61
+ device.read_signal(signal)
62
+ end
63
+
64
+ Then(
65
+ /^I wait for a signal containing "([^\"]*)" for (\d+) seconds$/
66
+ ) do |signal, seconds|
67
+ raise 'ERROR: Invalid scenario tag' if @scenario_tags.nil?
68
+ raise 'ERROR: Invalid scenario tag' if @scenario_tags.grep(/@user/).none?
69
+
70
+ device = Device.find_by_process_id(current_process_id)
71
+ raise 'ERROR: Device not found' if device.nil?
72
+
73
+ device.read_signal(signal, seconds)
74
+ end
75
+
76
+ private
77
+
78
+ def current_process_id
79
+ tag_process_id = @scenario_tags.grep(/@user/).first
80
+ tag_process_id.delete_prefix('@user')
81
+ end
@@ -0,0 +1,193 @@
1
+ require 'kraken-mobile/helpers/feature_analyzer'
2
+ require 'kraken-mobile/mobile/mobile_process'
3
+ require 'kraken-mobile/models/feature_file'
4
+ require 'kraken-mobile/models/web_device'
5
+ require 'kraken-mobile/web/web_process'
6
+ require 'kraken-mobile/utils/reporter'
7
+ require 'kraken-mobile/mobile/adb'
8
+ require 'kraken-mobile/utils/k'
9
+ require 'parallel'
10
+
11
+ class TestScenario
12
+ #-------------------------------
13
+ # Fields
14
+ #-------------------------------
15
+ attr_accessor :devices
16
+ attr_accessor :kraken_app
17
+ attr_accessor :feature_file
18
+ attr_accessor :execution_id
19
+ attr_accessor :reporter
20
+
21
+ #-------------------------------
22
+ # Constructors
23
+ #-------------------------------
24
+ def initialize(kraken_app:, feature_file_path:)
25
+ @feature_file = FeatureFile.new(file_path: feature_file_path)
26
+ @devices = sample_devices
27
+ @kraken_app = kraken_app
28
+ @execution_id = Digest::SHA256.hexdigest(Time.now.to_f.to_s)
29
+ @reporter = Reporter.new(test_scenario: self)
30
+ end
31
+
32
+ #-------------------------------
33
+ # Lifecycle
34
+ #-------------------------------
35
+ def before_execution
36
+ delete_all_web_inboxes
37
+ File.delete(K::DIRECTORY_PATH) if File.exist?(K::DIRECTORY_PATH)
38
+ File.delete(K::DICTIONARY_PATH) if File.exist?(K::DICTIONARY_PATH)
39
+ K::PROCESS_STATE_FILE_PATH.each do |_state, file_path|
40
+ File.delete(file_path) if File.exist?(file_path)
41
+ end
42
+ @reporter.create_report_folder_requirements
43
+ end
44
+
45
+ def run
46
+ unless @feature_file.right_syntax?
47
+ raise "ERROR: Verify feature file #{@file_path} has one unique @user tag"\
48
+ ' for each scenario'
49
+ end
50
+
51
+ before_execution
52
+ execute
53
+ after_execution
54
+ end
55
+
56
+ def after_execution
57
+ File.delete(K::DIRECTORY_PATH) if File.exist?(K::DIRECTORY_PATH)
58
+ File.delete(K::DICTIONARY_PATH) if File.exist?(K::DICTIONARY_PATH)
59
+ K::PROCESS_STATE_FILE_PATH.each do |_state, file_path|
60
+ File.delete(file_path) if File.exist?(file_path)
61
+ end
62
+ @reporter.save_report
63
+ notify_scenario_finished
64
+ end
65
+
66
+ #-------------------------------
67
+ # Methods
68
+ #-------------------------------
69
+ def execute
70
+ Parallel.map_with_index(
71
+ @devices, in_threads: @devices.count
72
+ ) do |device, index|
73
+ user_id = index + 1
74
+ start_process_for_user_id_in_device(
75
+ user_id,
76
+ device
77
+ )
78
+ end
79
+ end
80
+
81
+ def self.ready_to_start?
82
+ process_ids = DeviceProcess.registered_process_ids
83
+ processes_ready = DeviceProcess.processes_in_state(
84
+ K::PROCESS_STATES[:ready_to_start]
85
+ )
86
+ process_ids.all? do |process_id|
87
+ processes_ready.include?(process_id) ||
88
+ Device.find_by_process_id(process_id)&.connected? == false
89
+ end
90
+ end
91
+
92
+ def self.ready_to_finish?
93
+ process_ids = DeviceProcess.registered_process_ids
94
+ processes_finished = DeviceProcess.processes_in_state(
95
+ K::PROCESS_STATES[:ready_to_finish]
96
+ )
97
+ process_ids.all? do |process_id|
98
+ processes_finished.include?(process_id) ||
99
+ Device.find_by_process_id(process_id)&.connected? == false
100
+ end
101
+ end
102
+
103
+ def requires_predefined_devices?
104
+ !ENV[K::CONFIG_PATH].nil?
105
+ end
106
+
107
+ private
108
+
109
+ def start_process_for_user_id_in_device(user_id, device)
110
+ if device.is_a? AndroidDevice
111
+ start_mobile_process_for_user_id_in_device(user_id, device)
112
+ elsif device.is_a? WebDevice
113
+ start_web_process_for_user_id_in_device(user_id, device)
114
+ else
115
+ raise 'ERROR: Platform not supported'
116
+ end
117
+ end
118
+
119
+ def start_mobile_process_for_user_id_in_device(user_id, device)
120
+ MobileProcess.new(
121
+ id: user_id,
122
+ device: device,
123
+ test_scenario: self
124
+ ).run
125
+ end
126
+
127
+ def start_web_process_for_user_id_in_device(user_id, device)
128
+ WebProcess.new(
129
+ id: user_id,
130
+ device: device,
131
+ test_scenario: self
132
+ ).run
133
+ end
134
+
135
+ def sample_devices
136
+ return predefined_devices if requires_predefined_devices?
137
+
138
+ (sample_mobile_devices + sample_web_devices).flatten
139
+ end
140
+
141
+ def sample_mobile_devices
142
+ android_devices = ADB.connected_devices
143
+ android_devices.sample(
144
+ @feature_file.number_of_required_mobile_devices
145
+ )
146
+ end
147
+
148
+ def sample_web_devices
149
+ web_devices = []
150
+ @feature_file.number_of_required_web_devices.times do
151
+ web_devices << WebDevice.factory_create
152
+ end
153
+ web_devices
154
+ end
155
+
156
+ def notify_scenario_finished
157
+ @kraken_app.on_test_scenario_finished
158
+ end
159
+
160
+ def user_id_is_mobile?(user_id)
161
+ complement_tags = @feature_file.tags_for_user_id(user_id).map(
162
+ &:downcase
163
+ )
164
+ complement_tags.include?('@mobile')
165
+ end
166
+
167
+ def user_id_is_web?(user_id)
168
+ complement_tags = @feature_file.tags_for_user_id(user_id).map(
169
+ &:downcase
170
+ )
171
+ complement_tags.include?('@web')
172
+ end
173
+
174
+ def delete_all_web_inboxes
175
+ Dir.glob(".*_#{K::INBOX_FILE_NAME}").each do |file|
176
+ File.delete(file)
177
+ end
178
+ end
179
+
180
+ def predefined_devices
181
+ config_absolute_path = File.expand_path(ENV[K::CONFIG_PATH])
182
+ file = open(config_absolute_path)
183
+ content = file.read
184
+ devices_json = JSON.parse(content).values
185
+
186
+ devices_json.map do |device_json|
187
+ AndroidDevice.new(
188
+ id: device_json['id'],
189
+ model: device_json['model']
190
+ )
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,17 @@
1
+ require 'kraken-mobile/models/feature_file'
2
+
3
+ module Utils
4
+ module FeatureReader
5
+ def feature_files
6
+ features_dir = File.join(FileUtils.pwd, K::FEATURES_PATH)
7
+ unless File.exist?(features_dir)
8
+ raise "ERROR: File or directory '#{features_dir}' does not exists"
9
+ end
10
+ # Is a file not directory
11
+ return [features_dir] if features_dir.include?('.feature')
12
+
13
+ files = Dir[File.join(features_dir, '**{,/*/**}/*')].uniq
14
+ files.grep(/\.feature$/)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module K
4
+ SEPARATOR = ';' unless defined? SEPARATOR
5
+ DIRECTORY_PATH = '.device_directory' unless defined? DIRECTORY_PATH
6
+ DICTIONARY_PATH = 'dictionary.json' unless defined? DICTIONARY_PATH
7
+ INBOX_FILE_NAME = 'inbox.txt' unless defined? INBOX_FILE_NAME
8
+ FEATURES_PATH = './features' unless defined? FEATURES_PATH
9
+ DEFAULT_TIMEOUT_SECONDS = 30 unless defined? DEFAULT_TIMEOUT_SECONDS
10
+ ANDROID_PORTRAIT = 0 unless defined? ANDROID_PORTRAIT
11
+ ANDROID_LANDSCAPE = 1 unless defined? ANDROID_LANDSCAPE
12
+ WEB_PORTRAIT = 1 unless defined? WEB_PORTRAIT
13
+ MONKEY_DEFAULT_TIMEOUT = 5 unless defined? MONKEY_DEFAULT_TIMEOUT
14
+ WEB_DEVICE = 'WEB_DEVICE' unless defined? WEB_DEVICE
15
+ ANDROID_DEVICE = 'ANDROID_DEVICE' unless defined? ANDROID_DEVICE
16
+ PROPERTIES_PATH = 'PROPERTIES_PATH' unless defined? PROPERTIES_PATH
17
+ CONFIG_PATH = 'CONFIG_PATH' unless defined? CONFIG_PATH
18
+ REPORT_PATH = './reports' unless defined? REPORT_PATH
19
+ FILE_REPORT_NAME = 'report.json' unless defined? FILE_REPORT_NAME
20
+ D3_DATA_FILE_NAME = 'data.json' unless defined? D3_DATA_FILE_NAME
21
+
22
+ unless defined? DEVICES_REPORT_FILE_NAME
23
+ DEVICES_REPORT_FILE_NAME = 'devices.json'
24
+ end
25
+
26
+ unless defined? REPORT_ASSETS_PATH
27
+ REPORT_ASSETS_PATH = '../../../../reporter/assets/'
28
+ end
29
+
30
+ unless defined? DEFAULT_START_TIMEOUT_SECONDS
31
+ DEFAULT_START_TIMEOUT_SECONDS = 600 # 10.minutes
32
+ end
33
+
34
+ unless defined? DEFAULT_FINISH_TIMEOUT_SECONDS
35
+ DEFAULT_FINISH_TIMEOUT_SECONDS = 600 # 10.minutes
36
+ end
37
+
38
+ unless defined? DEVICES_READY_PATH
39
+ DEVICES_READY_PATH = '.devices_ready_to_start'
40
+ end
41
+
42
+ unless defined? DEVICES_FINISHED_PATH
43
+ DEVICES_FINISHED_PATH = '.devices_ready_to_finish'
44
+ end
45
+
46
+ unless defined? PROCESS_STATES
47
+ PROCESS_STATES = {
48
+ ready_to_start: 0,
49
+ ready_to_finish: 1
50
+ }.freeze
51
+ end
52
+
53
+ unless defined? PROCESS_STATE_FILE_PATH
54
+ PROCESS_STATE_FILE_PATH = {
55
+ K::PROCESS_STATES[:ready_to_start] => DEVICES_READY_PATH,
56
+ K::PROCESS_STATES[:ready_to_finish] => DEVICES_FINISHED_PATH
57
+ }.freeze
58
+ end
59
+
60
+ unless defined? CALABASH_MONKEY_ACTIONS
61
+ CALABASH_MONKEY_ACTIONS = [
62
+ :move,
63
+ :down,
64
+ :up
65
+ ].freeze
66
+ end
67
+ end
@@ -0,0 +1,2 @@
1
+ require 'calabash-android/cucumber'
2
+ require 'kraken-mobile/hooks/mobile_kraken_hooks'
@@ -0,0 +1,453 @@
1
+ require 'kraken-mobile/utils/k'
2
+ require 'json'
3
+
4
+ class Reporter
5
+ #-------------------------------
6
+ # Fields
7
+ #-------------------------------
8
+ PASSED = 'passed'.freeze
9
+ FAILED = 'failed'.freeze
10
+ SKIPPED = 'skipped'.freeze
11
+ PENDING = 'pending'.freeze
12
+ NOT_DEFINED = 'undefined'.freeze
13
+ AMBIGUOUS = 'ambiguous'.freeze
14
+
15
+ attr_accessor :test_scenario
16
+
17
+ #-------------------------------
18
+ # Constructors
19
+ #-------------------------------
20
+ def initialize(test_scenario:)
21
+ @test_scenario = test_scenario
22
+ end
23
+
24
+ #-------------------------------
25
+ # Lifecycle
26
+ #-------------------------------
27
+ def create_report_folder_requirements
28
+ create_report_execution_report_folder
29
+ create_devices_execution_report_folder
30
+ save_execution_devices_list
31
+ end
32
+
33
+ def save_report
34
+ generate_each_device_report
35
+ generate_general_report
36
+ end
37
+
38
+ def generate_general_report
39
+ erb_file = File.join(
40
+ File.expand_path('../../../../reporter', __FILE__),
41
+ 'index.html.erb'
42
+ )
43
+ html_file = File.join(
44
+ File.expand_path("#{K::REPORT_PATH}/#{test_execution_id}/"),
45
+ 'index.html'
46
+ )
47
+ report_file = open(
48
+ "#{K::REPORT_PATH}/#{test_execution_id}/#{K::DEVICES_REPORT_FILE_NAME}"
49
+ )
50
+ content = report_file.read
51
+ devices_report = report_by_devices
52
+ @features_report = fetures_from_report_by_devices(devices_report)
53
+ data_hash = feature_by_nodes_and_links @features_report
54
+ open(
55
+ "#{K::REPORT_PATH}/#{test_execution_id}/assets/js/#{K::D3_DATA_FILE_NAME}",
56
+ 'w'
57
+ ) do |file|
58
+ file.puts(data_hash.to_json)
59
+ end
60
+ template = File.read(erb_file)
61
+ result = ERB.new(template).result(binding)
62
+ File.open(html_file, 'w+') do |f|
63
+ f.write result
64
+ end
65
+ end
66
+
67
+ def generate_each_device_report
68
+ devices.each_with_index do |device, index|
69
+ generate_device_report(device, index + 1)
70
+ end
71
+ end
72
+
73
+ def generate_device_report(device, id)
74
+ process = MobileProcess.new(
75
+ id: id,
76
+ device: device,
77
+ test_scenario: @test_scenario
78
+ )
79
+ @apk_path = process.apk_path
80
+ report_file = open("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/#{K::FILE_REPORT_NAME}")
81
+ content = report_file.read
82
+ @features = JSON.parse(content)
83
+ @total_scenarios = total_scenarios @features
84
+ @device = device
85
+ @total_failed_scenarios_percentage = total_failed_scenarios_percentage @features
86
+ @total_passed_scenarios_percentage = total_passed_scenarios_percentage @features
87
+ @total_passed_features_percentage = total_passed_features_percentage @features
88
+ @total_failed_features_percentage = total_failed_features_percentage @features
89
+ erb_file = File.join(File.expand_path('../../../../reporter', __FILE__), "feature_report.html.erb")
90
+ html_file = File.join(File.expand_path("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/"), File.basename(erb_file, '.erb')) #=>"page.html"
91
+ # Variables
92
+ template = File.read(erb_file)
93
+ result = ERB.new(template).result(binding)
94
+ # write result to file
95
+ File.open(html_file, 'w+') do |f|
96
+ f.write result
97
+ end
98
+ generate_features_report @features, device
99
+ end
100
+
101
+ #-------------------------------
102
+ # Methods
103
+ #-------------------------------
104
+ def test_execution_id
105
+ raise 'ERROR: Invalid test scenario' if @test_scenario.nil?
106
+
107
+ @test_scenario.execution_id
108
+ end
109
+
110
+ def create_report_execution_report_folder
111
+ Dir.mkdir(K::REPORT_PATH) unless File.exist?(K::REPORT_PATH)
112
+ Dir.mkdir("#{K::REPORT_PATH}/#{test_execution_id}")
113
+ FileUtils.cp_r(
114
+ File.expand_path(K::REPORT_ASSETS_PATH, __FILE__),
115
+ "#{K::REPORT_PATH}/#{test_execution_id}/"
116
+ )
117
+ end
118
+
119
+ def save_execution_devices_list
120
+ open(
121
+ "#{K::REPORT_PATH}/#{test_execution_id}/#{K::DEVICES_REPORT_FILE_NAME}",
122
+ 'w'
123
+ ) do |file|
124
+ file.puts(devices_json.to_json)
125
+ end
126
+ end
127
+
128
+ def create_devices_execution_report_folder
129
+ devices.each do |device|
130
+ Dir.mkdir(
131
+ "#{KrakenMobile::Constants::REPORT_PATH}/#{test_execution_id}/"\
132
+ "#{device.id}"
133
+ )
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ def generate_features_report features, device
140
+ features.each do |feature|
141
+ generate_feature_report feature, device
142
+ end
143
+ end
144
+
145
+ def generate_feature_report feature, device
146
+ Dir.mkdir("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/features_report") unless File.exists?("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/features_report")
147
+ file_name = feature_id feature
148
+ erb_file = File.join(File.expand_path('../../../../reporter', __FILE__), "scenario_report.html.erb")
149
+ html_file = File.join(File.expand_path("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/features_report"), "#{file_name}.html") #=>"page.html"
150
+ # Variables
151
+ @feature = feature
152
+ template = File.read(erb_file)
153
+ result = ERB.new(template).result(binding)
154
+ # write result to file
155
+ File.open(html_file, 'w+') do |f|
156
+ f.write result
157
+ end
158
+ end
159
+
160
+ def report_by_devices
161
+ devices_report = {}
162
+ devices_json.each do |device|
163
+ unless File.exist?(
164
+ "#{K::REPORT_PATH}/#{test_execution_id}/#{device[:id]}/#{K::FILE_REPORT_NAME}"
165
+ )
166
+ next
167
+ end
168
+
169
+ report_file = open(
170
+ "#{K::REPORT_PATH}/#{test_execution_id}/#{device[:id]}/#{K::FILE_REPORT_NAME}"
171
+ )
172
+ content = report_file.read
173
+ devices_report[device[:user]] = JSON.parse(content)
174
+ devices_report[device[:user]].each do |d| d['device_model'] = device[:model] if !d['device_model'] end
175
+ devices_report[device[:user]].each do |d| d['device_id'] = device[:id] if !d['device_id'] end
176
+ end
177
+ devices_report
178
+ end
179
+
180
+ def fetures_from_report_by_devices report_by_devices
181
+ features = {}
182
+ report_by_devices.keys.each do |user_key|
183
+ report = report_by_devices[user_key]
184
+ report.each do |feature|
185
+ features[feature["id"]] = {} if !features[feature["id"]]
186
+ features[feature["id"]]["name"] = feature["name"] if !features[feature["id"]]["name"] && feature["name"]
187
+ features[feature["id"]]["devices"] = {} if !features[feature["id"]]["devices"]
188
+ if feature["elements"] && feature["elements"].count > 0
189
+ features[feature["id"]]["devices"][user_key] = []
190
+ if feature["elements"].first["steps"]
191
+ failed = false
192
+ feature["elements"].first["steps"].each do |step|
193
+ next if failed
194
+ failed = step["result"]["status"] != PASSED
195
+ image = nil
196
+ image = step["after"].first["embeddings"].first["data"] if step["after"] && step["after"].count > 0 && step["after"].first["embeddings"] && step["after"].first["embeddings"].count > 0
197
+ features[feature["id"]]["devices"][user_key] << {
198
+ name: "#{step['keyword']} #{step['name']}",
199
+ duration: step["result"]["duration"],
200
+ image: image,
201
+ device_model: feature["device_model"],
202
+ status: failed ? FAILED : PASSED
203
+ }
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ features
210
+ end
211
+
212
+ def feature_by_nodes_and_links features_report
213
+ features = []
214
+ features_report.values.each do |feature|
215
+ features << nodes_and_links(feature["devices"], feature["name"]) if feature["devices"]
216
+ end
217
+ features
218
+ end
219
+
220
+ def nodes_and_links feature_report, feature_name
221
+ last_node_id = 0
222
+ nodes = [{ name: "", id: "empty", image: nil }]
223
+ signal_hash = {}
224
+ links = []
225
+ feature_report.keys.each do |key|
226
+ steps = feature_report[key]
227
+ coming_from_signal = false
228
+ last_signal = -1
229
+ steps.each_with_index do |step, index|
230
+ node_id = last_node_id+1
231
+ if isReadSignal(step[:name]) && step[:status] == PASSED
232
+ signal = signalContent(step[:name])
233
+ already_created_signal = signal_hash[signal] ? true : false
234
+ signal_hash[signal] = already_created_signal ? signal_hash[signal] : { id: "#{node_id}", receiver: key }
235
+ node = { name: "Signal: #{signal}, Receiver: #{step[:device_model]}", id: signal_hash[signal][:id], image: nil, status: step[:status] }
236
+ if already_created_signal
237
+ entry = nodes.select{ |node| node[:id] == signal_hash[signal][:id] }.first
238
+ entry[:name] = "Signal: #{signal}, Receiver: #{step[:device_model]}" if entry
239
+ end
240
+ source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
241
+ link = {
242
+ source: source,
243
+ target: signal_hash[signal][:id].to_i,
244
+ value: 1,
245
+ owner: key,
246
+ owner_model: step[:device_model]
247
+ }
248
+ nodes << node if !already_created_signal
249
+ links << link
250
+ last_node_id += 1 if !already_created_signal
251
+ last_signal = signal_hash[signal][:id].to_i
252
+ coming_from_signal = true
253
+ elsif isWriteSignal(step[:name]) && step[:status] == PASSED
254
+ signal = signalContent(step[:name])
255
+ receiver = signalReceiver(step[:name])
256
+ already_created_signal = signal_hash[signal] ? true : false
257
+ signal_hash[signal] = already_created_signal ? signal_hash[signal] : { id: "#{node_id}", receiver: receiver }
258
+ node = { name: step[:name], id: signal_hash[signal][:id], image: nil, status: step[:status] }
259
+ source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
260
+ link = {
261
+ source: source,
262
+ target: signal_hash[signal][:id].to_i,
263
+ value: 1,
264
+ owner: key,
265
+ owner_model: step[:device_model]
266
+ }
267
+ nodes << node if !already_created_signal
268
+ links << link
269
+ last_node_id += 1 if !already_created_signal
270
+ last_signal = signal_hash[signal][:id].to_i
271
+ coming_from_signal = true
272
+ else
273
+ node = { name: step[:name], id: "#{node_id}", image: step[:image], status: step[:status] }
274
+ source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
275
+ link = {
276
+ source: source,
277
+ target: node_id,
278
+ value: 1,
279
+ owner: key,
280
+ owner_model: step[:device_model]
281
+ }
282
+ nodes << node
283
+ links << link
284
+ last_node_id += 1
285
+ coming_from_signal = false
286
+ end
287
+ end
288
+ end
289
+ return {
290
+ name: feature_name,
291
+ nodes: nodes,
292
+ links: links
293
+ }
294
+ end
295
+
296
+ def isReadSignal step
297
+ line = step.split(' ')[1..-1].join(' ')
298
+ (line =~ /^I wait for a signal containing "([^\"]*)"$/ ? true : false) || (line =~ /^I wait for a signal containing "([^\"]*)" for (\d+) seconds$/ ? true : false)
299
+ end
300
+
301
+ def isWriteSignal step
302
+ line = step.split(' ')[1..-1].join(' ')
303
+ line =~ /^I send a signal to user (\d+) containing "([^\"]*)"$/ ? true : false
304
+ end
305
+
306
+ def signalContent step
307
+ line = step.split(' ')[1..-1].join(' ')
308
+ line.scan(/"([^\"]*)"/).first.first if line.scan(/"([^\"]*)"/).first
309
+ end
310
+
311
+ def signalReceiver step
312
+ line = step.split(' ')[1..-1].join(' ')
313
+ line.scan(/(\d+)/).first.first if line.scan(/(\d+)/).first
314
+ end
315
+
316
+ def devices
317
+ raise 'ERROR: Invalid test scenario' if @test_scenario.nil?
318
+
319
+ @test_scenario.devices
320
+ end
321
+
322
+ def devices_json
323
+ devices.map.with_index do |device, index|
324
+ height, width = device.screen_size
325
+ {
326
+ user: (index + 1), id: device.id,
327
+ model: device.model, screen_height: height,
328
+ screen_width: width, sdk: device.sdk_version,
329
+ type: device.type
330
+ }
331
+ end
332
+ end
333
+
334
+ def total_scenarios features
335
+ how_many = 0
336
+ features.each do |feature|
337
+ scenarios = feature["elements"]
338
+ how_many += scenarios.count if scenarios
339
+ end
340
+ how_many
341
+ end
342
+
343
+ def failed_scenarios feature
344
+ scenarios = feature["elements"]
345
+ scenarios.select{ |scenario|
346
+ steps = scenario["steps"]
347
+ steps.any?{ |step| step["result"] && step["result"]["status"] != PASSED }
348
+ }
349
+ end
350
+
351
+ def total_passed_scenarios features
352
+ how_many = 0
353
+ features.each do |feature|
354
+ how_many += passed_scenarios(feature).count
355
+ end
356
+ how_many
357
+ end
358
+
359
+ def total_failed_scenarios features
360
+ how_many = 0
361
+ features.each do |feature|
362
+ how_many += failed_scenarios(feature).count
363
+ end
364
+ how_many
365
+ end
366
+
367
+ def total_passed_features features
368
+ how_many = 0
369
+ features.each do |feature|
370
+ how_many += 1 if feature_passed?(feature)
371
+ end
372
+ how_many
373
+ end
374
+
375
+ def total_failed_features features
376
+ how_many = 0
377
+ features.each do |feature|
378
+ how_many += 1 if !feature_passed?(feature)
379
+ end
380
+ how_many
381
+ end
382
+
383
+ def feature_passed_scenarios_percentage feature
384
+ (passed_scenarios(feature).count.to_f/feature["elements"].count.to_f).round(2) * 100.00
385
+ end
386
+
387
+ def feature_failed_scenarios_percentage feature
388
+ (failed_scenarios(feature).count.to_f/feature["elements"].count.to_f).round(2) * 100.00
389
+ end
390
+
391
+ def total_passed_scenarios_percentage features
392
+ (total_passed_scenarios(features).to_f/total_scenarios(features).to_f).round(2) * 100.00
393
+ end
394
+
395
+ def total_passed_features_percentage features
396
+ (total_passed_features(features).to_f/features.count.to_f).round(2) * 100.00
397
+ end
398
+
399
+ def total_failed_scenarios_percentage features
400
+ (total_failed_scenarios(features).to_f/total_scenarios(features).to_f).round(2) * 100.00
401
+ end
402
+
403
+ def total_failed_features_percentage features
404
+ (total_failed_features(features).to_f/features.count.to_f).round(2) * 100.00
405
+ end
406
+
407
+ def feature_passed? feature
408
+ passed_scenarios(feature).count == feature["elements"].count
409
+ end
410
+
411
+ def format_duration(nanoseconds)
412
+ duration_in_seconds = nanoseconds.to_f/1000000000.0
413
+ m, s = duration_in_seconds.divmod(60)
414
+ "#{m}m #{format('%.3f', s)}s"
415
+ end
416
+
417
+ def passed_features features
418
+ features.select{ |feature| passed_scenarios(feature) == feature["elements"].count }
419
+ end
420
+
421
+ def failed_features features
422
+ features.select{ |feature| failed_scenarios(feature) == feature["elements"].count }
423
+ end
424
+
425
+ def feature_duration feature
426
+ scenarios = feature["elements"]
427
+ how_long = 0
428
+ scenarios.each do |scenario|
429
+ how_long += scenario_duration(scenario)
430
+ end
431
+ how_long
432
+ end
433
+
434
+ def scenario_duration scenario
435
+ how_long = 0
436
+ scenario["steps"].each do |step|
437
+ how_long += step["result"]["duration"] if step["result"] && step["result"]["duration"]
438
+ end
439
+ how_long
440
+ end
441
+
442
+ def passed_scenarios feature
443
+ scenarios = feature["elements"]
444
+ scenarios.select{ |scenario|
445
+ steps = scenario["steps"]
446
+ steps.all?{ |step| step["result"] && step["result"]["status"] == PASSED }
447
+ }
448
+ end
449
+
450
+ def feature_id feature
451
+ Digest::SHA256.hexdigest("#{feature["id"].strip}#{feature["uri"].strip}")
452
+ end
453
+ end