kraken-mobile 1.0.2 → 1.0.9

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 (54) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +251 -75
  3. data/bin/kraken-mobile +64 -67
  4. data/bin/kraken_mobile_calabash_android.rb +39 -0
  5. data/bin/kraken_mobile_helpers.rb +107 -0
  6. data/bin/kraken_mobile_setup.rb +138 -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 +130 -0
  14. data/lib/kraken-mobile/helpers/devices_helper/adb_helper.rb +100 -105
  15. data/lib/kraken-mobile/helpers/kraken_faker.rb +108 -0
  16. data/lib/kraken-mobile/helpers/reporter.rb +3 -0
  17. data/lib/kraken-mobile/hooks/mobile_kraken_hooks.rb +15 -0
  18. data/lib/kraken-mobile/hooks/mobile_operations.rb +36 -0
  19. data/lib/kraken-mobile/hooks/web_operations.rb +33 -0
  20. data/lib/kraken-mobile/mobile/adb.rb +66 -0
  21. data/lib/kraken-mobile/mobile/android_commands.rb +43 -0
  22. data/lib/kraken-mobile/mobile/mobile_process.rb +101 -0
  23. data/lib/kraken-mobile/models/android_device.rb +121 -0
  24. data/lib/kraken-mobile/models/device.rb +113 -31
  25. data/lib/kraken-mobile/models/feature_file.rb +135 -0
  26. data/lib/kraken-mobile/models/feature_scenario.rb +24 -0
  27. data/lib/kraken-mobile/models/web_device.rb +89 -0
  28. data/lib/kraken-mobile/monkeys/mobile/android_monkey.rb +30 -0
  29. data/lib/kraken-mobile/monkeys/mobile/kraken_android_monkey.rb +54 -0
  30. data/lib/kraken-mobile/monkeys/web/web_monkey.rb +63 -0
  31. data/lib/kraken-mobile/runners/calabash/android/monkey_helper.rb +2 -2
  32. data/lib/kraken-mobile/runners/calabash/android/steps/communication_steps.rb +18 -2
  33. data/lib/kraken-mobile/runners/calabash/monkey/monkey_runner.rb +2 -2
  34. data/lib/kraken-mobile/steps/general_steps.rb +83 -0
  35. data/lib/kraken-mobile/steps/mobile/kraken_steps.rb +72 -0
  36. data/lib/kraken-mobile/steps/web/kraken_steps.rb +109 -0
  37. data/lib/kraken-mobile/test_scenario.rb +227 -0
  38. data/lib/kraken-mobile/utils/feature_reader.rb +17 -0
  39. data/lib/kraken-mobile/utils/k.rb +68 -0
  40. data/lib/kraken-mobile/utils/mobile_cucumber.rb +2 -0
  41. data/lib/kraken-mobile/utils/reporter.rb +500 -0
  42. data/lib/kraken-mobile/version.rb +2 -2
  43. data/lib/kraken-mobile/web/web_process.rb +41 -0
  44. data/lib/kraken_mobile.rb +81 -0
  45. data/reporter/assets/images/krakenThumbnail.jpg +0 -0
  46. data/reporter/feature_report.html.erb +5 -1
  47. data/reporter/index.html.erb +13 -7
  48. metadata +94 -13
  49. data/bin/kraken-mobile-calabash-android.rb +0 -85
  50. data/bin/kraken-mobile-generate.rb +0 -19
  51. data/bin/kraken-mobile-helpers.rb +0 -48
  52. data/bin/kraken-mobile-setup.rb +0 -50
  53. data/calabash-android-features-skeleton/step_definitions/kraken_steps.rb +0 -1
  54. data/lib/kraken-mobile.rb +0 -29
@@ -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,68 @@
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
+ SCREENSHOT_PATH = 'SCREENSHOT_PATH' unless defined? SCREENSHOT_PATH
22
+
23
+ unless defined? DEVICES_REPORT_FILE_NAME
24
+ DEVICES_REPORT_FILE_NAME = 'devices.json'
25
+ end
26
+
27
+ unless defined? REPORT_ASSETS_PATH
28
+ REPORT_ASSETS_PATH = '../../../../reporter/assets/'
29
+ end
30
+
31
+ unless defined? DEFAULT_START_TIMEOUT_SECONDS
32
+ DEFAULT_START_TIMEOUT_SECONDS = 600 # 10.minutes
33
+ end
34
+
35
+ unless defined? DEFAULT_FINISH_TIMEOUT_SECONDS
36
+ DEFAULT_FINISH_TIMEOUT_SECONDS = 600 # 10.minutes
37
+ end
38
+
39
+ unless defined? DEVICES_READY_PATH
40
+ DEVICES_READY_PATH = '.devices_ready_to_start'
41
+ end
42
+
43
+ unless defined? DEVICES_FINISHED_PATH
44
+ DEVICES_FINISHED_PATH = '.devices_ready_to_finish'
45
+ end
46
+
47
+ unless defined? PROCESS_STATES
48
+ PROCESS_STATES = {
49
+ ready_to_start: 0,
50
+ ready_to_finish: 1
51
+ }.freeze
52
+ end
53
+
54
+ unless defined? PROCESS_STATE_FILE_PATH
55
+ PROCESS_STATE_FILE_PATH = {
56
+ K::PROCESS_STATES[:ready_to_start] => DEVICES_READY_PATH,
57
+ K::PROCESS_STATES[:ready_to_finish] => DEVICES_FINISHED_PATH
58
+ }.freeze
59
+ end
60
+
61
+ unless defined? CALABASH_MONKEY_ACTIONS
62
+ CALABASH_MONKEY_ACTIONS = [
63
+ :move,
64
+ :down,
65
+ :up
66
+ ].freeze
67
+ end
68
+ end
@@ -0,0 +1,2 @@
1
+ require 'calabash-android/cucumber'
2
+ require 'kraken-mobile/hooks/mobile_kraken_hooks'
@@ -0,0 +1,500 @@
1
+ require 'kraken-mobile/utils/k.rb'
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
+ report_file.close
52
+ devices_report = report_by_devices
53
+ @features_report = fetures_from_report_by_devices(devices_report)
54
+ data_hash = feature_by_nodes_and_links @features_report
55
+ open(
56
+ "#{K::REPORT_PATH}/#{test_execution_id}/assets/js/#{K::D3_DATA_FILE_NAME}",
57
+ 'w'
58
+ ) do |file|
59
+ file.puts(data_hash.to_json)
60
+ end
61
+ template = File.read(erb_file)
62
+ result = ERB.new(template).result(binding)
63
+ File.open(html_file, 'w+') do |f|
64
+ f.write result
65
+ end
66
+ end
67
+
68
+ def generate_each_device_report
69
+ devices.each_with_index do |device, index|
70
+ generate_device_report(device, index + 1)
71
+ end
72
+ end
73
+
74
+ def generate_device_report(device, id)
75
+ if device.is_a? AndroidDevice
76
+ generate_mobile_report(device, id)
77
+ elsif device.is_a? WebDevice
78
+ generate_web_report(device, id)
79
+ else
80
+ raise 'ERROR: Platform not supported'
81
+ end
82
+ end
83
+
84
+ def generate_mobile_report(device, id)
85
+ process = MobileProcess.new(
86
+ id: id,
87
+ device: device,
88
+ test_scenario: @test_scenario
89
+ )
90
+ @apk_path = process.apk_path
91
+ report_file = open("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/#{K::FILE_REPORT_NAME}")
92
+ content = report_file.read
93
+ report_file.close
94
+ @features = JSON.parse(content)
95
+ @total_scenarios = total_scenarios @features
96
+ @device = device
97
+ @total_failed_scenarios_percentage = total_failed_scenarios_percentage @features
98
+ @total_passed_scenarios_percentage = total_passed_scenarios_percentage @features
99
+ @total_passed_features_percentage = total_passed_features_percentage @features
100
+ @total_failed_features_percentage = total_failed_features_percentage @features
101
+ erb_file = File.join(File.expand_path('../../../../reporter', __FILE__), "feature_report.html.erb")
102
+ html_file = File.join(File.expand_path("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/"), File.basename(erb_file, '.erb')) #=>"page.html"
103
+ # Variables
104
+ template = File.read(erb_file)
105
+ result = ERB.new(template).result(binding)
106
+ # write result to file
107
+ File.open(html_file, 'w+') do |f|
108
+ f.write result
109
+ end
110
+ generate_features_report @features, device
111
+ end
112
+
113
+ def generate_web_report(device, id)
114
+ process = WebProcess.new(
115
+ id: id,
116
+ device: device,
117
+ test_scenario: @test_scenario
118
+ )
119
+ @apk_path = nil
120
+ report_file = open("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/#{K::FILE_REPORT_NAME}")
121
+ content = report_file.read
122
+ report_file.close
123
+ @features = JSON.parse(content)
124
+ @total_scenarios = total_scenarios @features
125
+ @device = device
126
+ @total_failed_scenarios_percentage = total_failed_scenarios_percentage @features
127
+ @total_passed_scenarios_percentage = total_passed_scenarios_percentage @features
128
+ @total_passed_features_percentage = total_passed_features_percentage @features
129
+ @total_failed_features_percentage = total_failed_features_percentage @features
130
+ erb_file = File.join(File.expand_path('../../../../reporter', __FILE__), "feature_report.html.erb")
131
+ html_file = File.join(File.expand_path("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/"), File.basename(erb_file, '.erb')) #=>"page.html"
132
+ # Variables
133
+ template = File.read(erb_file)
134
+ result = ERB.new(template).result(binding)
135
+ # write result to file
136
+ File.open(html_file, 'w+') do |f|
137
+ f.write result
138
+ end
139
+ generate_features_report @features, device
140
+ end
141
+
142
+ #-------------------------------
143
+ # Methods
144
+ #-------------------------------
145
+ def test_execution_id
146
+ raise 'ERROR: Invalid test scenario' if @test_scenario.nil?
147
+
148
+ @test_scenario.execution_id
149
+ end
150
+
151
+ def create_report_execution_report_folder
152
+ Dir.mkdir(K::REPORT_PATH) unless File.exist?(K::REPORT_PATH)
153
+ Dir.mkdir("#{K::REPORT_PATH}/#{test_execution_id}")
154
+ Dir.mkdir(screenshot_path)
155
+ FileUtils.cp_r(
156
+ File.expand_path(K::REPORT_ASSETS_PATH, __FILE__),
157
+ "#{K::REPORT_PATH}/#{test_execution_id}/"
158
+ )
159
+ end
160
+
161
+ def save_execution_devices_list
162
+ open(
163
+ "#{K::REPORT_PATH}/#{test_execution_id}/#{K::DEVICES_REPORT_FILE_NAME}",
164
+ 'w'
165
+ ) do |file|
166
+ file.puts(devices_json.to_json)
167
+ end
168
+ end
169
+
170
+ def create_devices_execution_report_folder
171
+ devices.each do |device|
172
+ Dir.mkdir(
173
+ "#{KrakenMobile::Constants::REPORT_PATH}/#{test_execution_id}/"\
174
+ "#{device.id}"
175
+ )
176
+ end
177
+ end
178
+
179
+ def screenshot_path
180
+ "#{K::REPORT_PATH}/#{test_execution_id}/screenshots/"
181
+ end
182
+
183
+ private
184
+
185
+ def generate_features_report features, device
186
+ features.each do |feature|
187
+ generate_feature_report feature, device
188
+ end
189
+ end
190
+
191
+ def generate_feature_report feature, device
192
+ 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")
193
+ file_name = feature_id feature
194
+ erb_file = File.join(File.expand_path('../../../../reporter', __FILE__), "scenario_report.html.erb")
195
+ html_file = File.join(File.expand_path("#{K::REPORT_PATH}/#{test_execution_id}/#{device.id}/features_report"), "#{file_name}.html") #=>"page.html"
196
+ # Variables
197
+ @feature = feature
198
+ template = File.read(erb_file)
199
+ result = ERB.new(template).result(binding)
200
+ # write result to file
201
+ File.open(html_file, 'w+') do |f|
202
+ f.write result
203
+ end
204
+ end
205
+
206
+ def report_by_devices
207
+ devices_report = {}
208
+ devices_json.each do |device|
209
+ unless File.exist?(
210
+ "#{K::REPORT_PATH}/#{test_execution_id}/#{device[:id]}/#{K::FILE_REPORT_NAME}"
211
+ )
212
+ next
213
+ end
214
+
215
+ report_file = open(
216
+ "#{K::REPORT_PATH}/#{test_execution_id}/#{device[:id]}/#{K::FILE_REPORT_NAME}"
217
+ )
218
+ content = report_file.read
219
+ report_file.close
220
+ devices_report[device[:user]] = JSON.parse(content)
221
+ devices_report[device[:user]].each do |d| d['device_model'] = device[:model] if !d['device_model'] end
222
+ devices_report[device[:user]].each do |d| d['device_id'] = device[:id] if !d['device_id'] end
223
+ end
224
+ devices_report
225
+ end
226
+
227
+ def fetures_from_report_by_devices report_by_devices
228
+ features = {}
229
+ report_by_devices.keys.each do |user_key|
230
+ report = report_by_devices[user_key]
231
+ report.each do |feature|
232
+ features[feature["id"]] = {} if !features[feature["id"]]
233
+ features[feature["id"]]["name"] = feature["name"] if !features[feature["id"]]["name"] && feature["name"]
234
+ features[feature["id"]]["devices"] = {} if !features[feature["id"]]["devices"]
235
+ if feature["elements"] && feature["elements"].count > 0
236
+ features[feature["id"]]["devices"][user_key] = []
237
+ if feature["elements"].first["steps"]
238
+ failed = false
239
+ feature["elements"].first["steps"].each do |step|
240
+ next if failed
241
+ failed = step["result"]["status"] != PASSED
242
+ image = nil
243
+ image = step["after"].first["embeddings"].first["data"] if step["after"] && step["after"].count > 0 && step["after"].first["embeddings"] && step["after"].first["embeddings"].count > 0
244
+ features[feature["id"]]["devices"][user_key] << {
245
+ name: "#{step['keyword']} #{step['name']}",
246
+ duration: step["result"]["duration"],
247
+ image: image,
248
+ device_model: feature["device_model"],
249
+ status: failed ? FAILED : PASSED
250
+ }
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+ features
257
+ end
258
+
259
+ def feature_by_nodes_and_links features_report
260
+ features = []
261
+ features_report.values.each do |feature|
262
+ features << nodes_and_links(feature["devices"], feature["name"]) if feature["devices"]
263
+ end
264
+ features
265
+ end
266
+
267
+ def nodes_and_links feature_report, feature_name
268
+ last_node_id = 0
269
+ nodes = [{ name: "", id: "empty", image: nil }]
270
+ signal_hash = {}
271
+ links = []
272
+ feature_report.keys.each do |key|
273
+ steps = feature_report[key]
274
+ coming_from_signal = false
275
+ last_signal = -1
276
+ steps.each_with_index do |step, index|
277
+ node_id = last_node_id+1
278
+ if isReadSignal(step[:name]) && step[:status] == PASSED
279
+ signal = signalContent(step[:name])
280
+ already_created_signal = signal_hash[signal] ? true : false
281
+ signal_hash[signal] = already_created_signal ? signal_hash[signal] : { id: "#{node_id}", receiver: key }
282
+ node = { name: "Signal: #{signal}, Receiver: #{step[:device_model]}", id: signal_hash[signal][:id], image: nil, status: step[:status] }
283
+ if already_created_signal
284
+ entry = nodes.select{ |node| node[:id] == signal_hash[signal][:id] }.first
285
+ entry[:name] = "Signal: #{signal}, Receiver: #{step[:device_model]}" if entry
286
+ end
287
+ source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
288
+ link = {
289
+ source: source,
290
+ target: signal_hash[signal][:id].to_i,
291
+ value: 1,
292
+ owner: key,
293
+ owner_model: step[:device_model]
294
+ }
295
+ nodes << node if !already_created_signal
296
+ links << link
297
+ last_node_id += 1 if !already_created_signal
298
+ last_signal = signal_hash[signal][:id].to_i
299
+ coming_from_signal = true
300
+ elsif isWriteSignal(step[:name]) && step[:status] == PASSED
301
+ signal = signalContent(step[:name])
302
+ receiver = signalReceiver(step[:name])
303
+ already_created_signal = signal_hash[signal] ? true : false
304
+ signal_hash[signal] = already_created_signal ? signal_hash[signal] : { id: "#{node_id}", receiver: receiver }
305
+ node = { name: step[:name], id: signal_hash[signal][:id], image: nil, status: step[:status] }
306
+ source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
307
+ link = {
308
+ source: source,
309
+ target: signal_hash[signal][:id].to_i,
310
+ value: 1,
311
+ owner: key,
312
+ owner_model: step[:device_model]
313
+ }
314
+ nodes << node if !already_created_signal
315
+ links << link
316
+ last_node_id += 1 if !already_created_signal
317
+ last_signal = signal_hash[signal][:id].to_i
318
+ coming_from_signal = true
319
+ else
320
+ node = { name: step[:name], id: "#{node_id}", image: step[:image], status: step[:status] }
321
+ source = (coming_from_signal ? last_signal : (index == 0 ? 0 : last_node_id))
322
+ link = {
323
+ source: source,
324
+ target: node_id,
325
+ value: 1,
326
+ owner: key,
327
+ owner_model: step[:device_model]
328
+ }
329
+ nodes << node
330
+ links << link
331
+ last_node_id += 1
332
+ coming_from_signal = false
333
+ end
334
+ end
335
+ end
336
+ return {
337
+ name: feature_name,
338
+ nodes: nodes,
339
+ links: links
340
+ }
341
+ end
342
+
343
+ def isReadSignal step
344
+ line = step.split(' ')[1..-1].join(' ')
345
+ (line =~ /^I wait for a signal containing "([^\"]*)"$/ ? true : false) || (line =~ /^I wait for a signal containing "([^\"]*)" for (\d+) seconds$/ ? true : false)
346
+ end
347
+
348
+ def isWriteSignal step
349
+ line = step.split(' ')[1..-1].join(' ')
350
+ line =~ /^I send a signal to user (\d+) containing "([^\"]*)"$/ ? true : false
351
+ end
352
+
353
+ def signalContent step
354
+ line = step.split(' ')[1..-1].join(' ')
355
+ line.scan(/"([^\"]*)"/).first.first if line.scan(/"([^\"]*)"/).first
356
+ end
357
+
358
+ def signalReceiver step
359
+ line = step.split(' ')[1..-1].join(' ')
360
+ line.scan(/(\d+)/).first.first if line.scan(/(\d+)/).first
361
+ end
362
+
363
+ def devices
364
+ raise 'ERROR: Invalid test scenario' if @test_scenario.nil?
365
+
366
+ @test_scenario.devices.compact
367
+ end
368
+
369
+ def devices_json
370
+ devices.map.with_index do |device, index|
371
+ height, width = device.screen_size
372
+ {
373
+ user: (index + 1), id: device.id,
374
+ model: device.model, screen_height: height,
375
+ screen_width: width, sdk: device.sdk_version,
376
+ type: device.type
377
+ }
378
+ end
379
+ end
380
+
381
+ def total_scenarios features
382
+ how_many = 0
383
+ features.each do |feature|
384
+ scenarios = feature["elements"]
385
+ how_many += scenarios.count if scenarios
386
+ end
387
+ how_many
388
+ end
389
+
390
+ def failed_scenarios feature
391
+ scenarios = feature["elements"]
392
+ scenarios.select{ |scenario|
393
+ steps = scenario["steps"]
394
+ steps.any?{ |step| step["result"] && step["result"]["status"] != PASSED }
395
+ }
396
+ end
397
+
398
+ def total_passed_scenarios features
399
+ how_many = 0
400
+ features.each do |feature|
401
+ how_many += passed_scenarios(feature).count
402
+ end
403
+ how_many
404
+ end
405
+
406
+ def total_failed_scenarios features
407
+ how_many = 0
408
+ features.each do |feature|
409
+ how_many += failed_scenarios(feature).count
410
+ end
411
+ how_many
412
+ end
413
+
414
+ def total_passed_features features
415
+ how_many = 0
416
+ features.each do |feature|
417
+ how_many += 1 if feature_passed?(feature)
418
+ end
419
+ how_many
420
+ end
421
+
422
+ def total_failed_features features
423
+ how_many = 0
424
+ features.each do |feature|
425
+ how_many += 1 if !feature_passed?(feature)
426
+ end
427
+ how_many
428
+ end
429
+
430
+ def feature_passed_scenarios_percentage feature
431
+ (passed_scenarios(feature).count.to_f/feature["elements"].count.to_f).round(2) * 100.00
432
+ end
433
+
434
+ def feature_failed_scenarios_percentage feature
435
+ (failed_scenarios(feature).count.to_f/feature["elements"].count.to_f).round(2) * 100.00
436
+ end
437
+
438
+ def total_passed_scenarios_percentage features
439
+ (total_passed_scenarios(features).to_f/total_scenarios(features).to_f).round(2) * 100.00
440
+ end
441
+
442
+ def total_passed_features_percentage features
443
+ (total_passed_features(features).to_f/features.count.to_f).round(2) * 100.00
444
+ end
445
+
446
+ def total_failed_scenarios_percentage features
447
+ (total_failed_scenarios(features).to_f/total_scenarios(features).to_f).round(2) * 100.00
448
+ end
449
+
450
+ def total_failed_features_percentage features
451
+ (total_failed_features(features).to_f/features.count.to_f).round(2) * 100.00
452
+ end
453
+
454
+ def feature_passed? feature
455
+ passed_scenarios(feature).count == feature["elements"].count
456
+ end
457
+
458
+ def format_duration(nanoseconds)
459
+ duration_in_seconds = nanoseconds.to_f/1000000000.0
460
+ m, s = duration_in_seconds.divmod(60)
461
+ "#{m}m #{format('%.3f', s)}s"
462
+ end
463
+
464
+ def passed_features features
465
+ features.select{ |feature| passed_scenarios(feature) == feature["elements"].count }
466
+ end
467
+
468
+ def failed_features features
469
+ features.select{ |feature| failed_scenarios(feature) == feature["elements"].count }
470
+ end
471
+
472
+ def feature_duration feature
473
+ scenarios = feature["elements"]
474
+ how_long = 0
475
+ scenarios.each do |scenario|
476
+ how_long += scenario_duration(scenario)
477
+ end
478
+ how_long
479
+ end
480
+
481
+ def scenario_duration scenario
482
+ how_long = 0
483
+ scenario["steps"].each do |step|
484
+ how_long += step["result"]["duration"] if step["result"] && step["result"]["duration"]
485
+ end
486
+ how_long
487
+ end
488
+
489
+ def passed_scenarios feature
490
+ scenarios = feature["elements"]
491
+ scenarios.select{ |scenario|
492
+ steps = scenario["steps"]
493
+ steps.all?{ |step| step["result"] && step["result"]["status"] == PASSED }
494
+ }
495
+ end
496
+
497
+ def feature_id feature
498
+ Digest::SHA256.hexdigest("#{feature["id"].strip}#{feature["uri"].strip}")
499
+ end
500
+ end