kraken-mobile 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
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