kraken-mobile 1.0.2 → 1.0.9

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