From 1297e36734da2d61c4076d6d74f9ce3922a91335 Mon Sep 17 00:00:00 2001 From: Paul Thexton Date: Thu, 17 Sep 2020 15:06:00 +0100 Subject: [PATCH 1/7] Bugfix: disable_ssl_verification breaking authentication Setting the ssl context was clearing out the authorization bearer http header. Now fixed. --- lib/report_portal/http_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_portal/http_client.rb b/lib/report_portal/http_client.rb index 55cd64c..d3f84a1 100644 --- a/lib/report_portal/http_client.rb +++ b/lib/report_portal/http_client.rb @@ -38,7 +38,7 @@ def create_client def add_insecure_ssl_options ssl_context = OpenSSL::SSL::SSLContext.new ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE - @http.default_options = { ssl_context: ssl_context } + @http.default_options = @http.default_options.with_ssl_context(ssl_context) end # Response should be consumed before sending next request via the same persistent connection. From 196770d7f7de34d161504e0c1dc90783ee2dc9df Mon Sep 17 00:00:00 2001 From: Paul Thexton Date: Thu, 17 Sep 2020 15:14:13 +0100 Subject: [PATCH 2/7] Bugfix: attach_to_launch not working Making use of attach_to_launch to allow individual executions of cucumber for each feature file wasn't working. Looks like perhaps this plugin hasn't been updated in line with the API? --- lib/report_portal/cucumber/report.rb | 4 +-- lib/reportportal.rb | 38 +++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index f331c2e..c2f4a70 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -173,9 +173,9 @@ def start_feature_with_parentage(feature, desired_time) type = :TEST end # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process - if parallel? && + if (parallel? || attach_to_launch?) && index < path_components.size - 1 && # is folder? - (id_of_created_item = ReportPortal.item_id_of(name, parent_node)) # get id for folder from report portal + (id_of_created_item = ReportPortal.uuid_of(name, parent_node)) # get id for folder from report portal # get child id from other process item = ReportPortal::TestItem.new(name: name, type: type, id: id_of_created_item, start_time: time_to_send(desired_time), description: description, closed: false, tags: tags) child_node = Tree::TreeNode.new(path_component, item) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 4315e95..bb7a3c3 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -126,7 +126,7 @@ def delete_items(item_ids) # needed for parallel formatter def item_id_of(name, parent_node) path = if parent_node.is_root? # folder without parent folder - "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{CGI.escape(name)}&filter.size.path=0" + "item?filter.eq.launchId=#{launch_id_to_number}&filter.eq.name=#{CGI.escape(name)}" else "item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{CGI.escape(name)}" end @@ -136,12 +136,30 @@ def item_id_of(name, parent_node) end end + # needed for parallel formatter + def uuid_of(name, parent_node) + itemid = item_id_of(name,parent_node) + return nil if itemid.nil? + + path = "item/#{itemid}" + data = send_request(:get, path) + + (data.key? 'uuid') ? data['uuid'] : nil + end + + # needed for parallel formatter + def launch_id_to_number() + path = "launch/#{@launch_id}" + data = send_request(:get, path) + data['id'] + end + # needed for parallel formatter def close_child_items(parent_id) path = if parent_id.nil? - "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" + "item?filter.eq.launchId=#{launch_id_to_number}" else - "item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" + "item?filter.eq.launchId=#{launch_id_to_number}&filter.eq.parentId=#{parent_id}&page.page=1&page.size=100" end ids = [] loop do @@ -153,14 +171,22 @@ def close_child_items(parent_id) url = nil end data['content'].each do |i| - ids << i['id'] if i['has_childs'] && i['status'] == 'IN_PROGRESS' + ids << i['id'] if i['hasChildren'] && i['status'] == 'IN_PROGRESS' end break if url.nil? end + # There's a mix of numerical ID and string UUID going on here in the API + # When querying child items here, we need to use the numerical id, but when + # we call finish_item, the API it calls wants the UUID. Simplify by creating + # a map. + ids = ids.map { |id| + {id: id, uuid: send_request(:get, "item/#{id}")['uuid']} + } + ids.each do |id| - close_child_items(id) - finish_item(TestItem.new(id: id)) + close_child_items(id[:id]) + finish_item(TestItem.new(id: id[:uuid])) end end From 740a96d13ff1bf2775c9a2298cf32ae089467d0d Mon Sep 17 00:00:00 2001 From: Paul Thexton Date: Thu, 17 Sep 2020 15:15:55 +0100 Subject: [PATCH 3/7] Bugfix: Use default issue_type strings It looks like this has possibly changed in the API over time? the existing values are rejected by the server as being invalid. I've simply changed them to "valid" locator values from an almost vanilla docker-compose instance. But I'm not sure if this should really be using statically included strings at all, but rather using ones specified in the report_portal.yml or scenario tags instead? I'll leave this one for the project maintainers to ponder --- lib/reportportal.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reportportal.rb b/lib/reportportal.rb index bb7a3c3..627850d 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -63,9 +63,9 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? if force_issue && status != :passed # TODO: check for :passed status is probably not needed - data[:issue] = { issue_type: 'AUTOMATION_BUG', comment: force_issue.to_s } + data[:issue] = { issue_type: 'ab001', comment: force_issue.to_s } elsif status == :skipped - data[:issue] = { issue_type: 'NOT_ISSUE' } + data[:issue] = { issue_type: 'nb001' } end send_request(:put, "item/#{item.id}", json: data) item.closed = true From 81e5cd505f9a3701f279a31addd0e8d8108df4b8 Mon Sep 17 00:00:00 2001 From: Paul Thexton Date: Thu, 17 Sep 2020 15:16:48 +0100 Subject: [PATCH 4/7] Bugfix: Don't assume config.out_stream is of type IO If cucumber is invoked with multiple formatters, our formatter may be given the path to a file for output as opposed to being given STDOUT --- lib/report_portal/cucumber/formatter.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 8c4d13b..c50c78c 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -9,7 +9,12 @@ def initialize(config) setup_message_processing - @io = config.out_stream + case config.out_stream + when IO + @io = config.out_stream + when String + @io = File.open(config.out_stream, "w") + end %i[test_case_started test_case_finished test_step_started test_step_finished test_run_finished].each do |event_name| config.on_event event_name do |event| @@ -21,8 +26,8 @@ def initialize(config) def puts(message) process_message(:puts, message) - @io.puts(message) - @io.flush + @io.puts(message) unless @io.nil? + @io.flush unless @io.nil? end def embed(*args) From 6d49ce6ddf5643e1ad823b86d1b1fc578fe9d75c Mon Sep 17 00:00:00 2001 From: Paul Thexton Date: Thu, 17 Sep 2020 16:32:04 +0100 Subject: [PATCH 5/7] Add support for Cucumber > 4.0.0 Refactored report.rb to extract retrieval of information about features/scenarios from a dependency injected class extractor Extractor is created by a factory method which will return a class appropriate for the current version of cucumber --- lib/report_portal/cucumber/extractor.rb | 138 ++++++++++++++++++++++++ lib/report_portal/cucumber/formatter.rb | 7 +- lib/report_portal/cucumber/report.rb | 49 ++++----- 3 files changed, 162 insertions(+), 32 deletions(-) create mode 100644 lib/report_portal/cucumber/extractor.rb diff --git a/lib/report_portal/cucumber/extractor.rb b/lib/report_portal/cucumber/extractor.rb new file mode 100644 index 0000000..996da26 --- /dev/null +++ b/lib/report_portal/cucumber/extractor.rb @@ -0,0 +1,138 @@ +module ReportPortal +module Cucumber + + class ExtractorCucumber4 + def initialize(config) + require 'cucumber/formatter/ast_lookup' + @ast_lookup=::Cucumber::Formatter::AstLookup.new(config) + end + + # For Cucumber4 we actually return a representation of the gherkin + # document. So when querying tags, keywords etc, these need to come + # from the 'feature' attribute of the gherkin document + def feature(test_case) + @ast_lookup.gherkin_document(test_case.location.file) + end + + def feature_location(gherkin) + gherkin.uri + end + + def feature_tags(gherkin) + gherkin.feature.tags + end + + def feature_name(gherkin) + "#{gherkin.feature.keyword}: #{gherkin.feature.name}" + end + + def same_feature_as_previous_test_case?(previous_name,gherkin) + previous_name == gherkin.uri.split(File::SEPARATOR).last + end + + def scenario_keyword(test_case) + @ast_lookup.scenario_source(test_case).scenario.keyword + end + + def scenario_name(test_case) + @ast_lookup.scenario_source(test_case).scenario.name + end + + def step_source(test_step) + @ast_lookup.step_source(test_step).step + end + + def step_multiline_arg(test_step) + test_step.multiline_arg + end + + def step_backtrace_line(test_step) + test_step.backtrace_line + end + + def step_type(test_step) + case step?(test_step) + when true + 'Step' + when false + "#{test_step.text} at #{test_step.location.to_s}" + end + end + + def step?(test_step) + !test_step.hook? + end + end + + class ExtractorCucumber3 + def initialize(config) + require 'cucumber/formatter/hook_query_visitor' + end + + def feature(test_case) + test_case.feature + end + + def feature_location(feature) + feature.location.file + end + + def feature_tags(feature) + feature.tags + end + + def feature_name(feature) + "#{feature.keyword}: #{feature.name}" + end + + def same_feature_as_previous_test_case?(previous_name,feature) + previous_name == feature.location.file.split(File::SEPARATOR).last + end + + def scenario_keyword(test_case) + test_case.keyword + end + + def scenario_name(test_case) + test_case.name + end + + def step_source(test_step) + test_step.source.last + end + + def step_multiline_arg(test_step) + step_source(test_step).multiline_arg + end + + def step_backtrace_line(test_step) + test_step.source.last.backtrace_line + end + + def step_type(test_step) + case step?(test_step) + when true + 'Step' + when false + hook_class_name = test_step.source.last.class.name.split('::').last + "#{hook_class_name} at #{test_step.location}" + end + end + + def step?(test_step) + !::Cucumber::Formatter::HookQueryVisitor.new(test_step).hook? + end + end + + class Extractor + def self.create(config) + if(::Cucumber::VERSION.split('.').map(&:to_i) <=> [4,0,0]) > 0 + ExtractorCucumber4.new(config) + else + ExtractorCucumber3.new(config) + end + end + end + +end +end diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index c50c78c..34f6db3 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -1,4 +1,5 @@ require_relative 'report' +require_relative 'extractor' module ReportPortal module Cucumber @@ -6,6 +7,10 @@ class Formatter # @api private def initialize(config) ENV['REPORT_PORTAL_USED'] = 'true' + @config = config + # Helper class used to abstract away the internal + # differences of cucumber 3 / 4+ + @extractor = Extractor.create(config) setup_message_processing @@ -37,7 +42,7 @@ def embed(*args) private def report - @report ||= ReportPortal::Cucumber::Report.new + @report ||= ReportPortal::Cucumber::Report.new(@extractor) end def setup_message_processing diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index c2f4a70..1afcaaf 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -1,5 +1,4 @@ require 'cucumber/formatter/io' -require 'cucumber/formatter/hook_query_visitor' require 'tree' require 'securerandom' @@ -18,7 +17,8 @@ def attach_to_launch? ReportPortal::Settings.instance.formatter_modes.include?('attach_to_launch') end - def initialize + def initialize(extractor) + @extractor = extractor @last_used_time = 0 @root_node = Tree::TreeNode.new('') @parent_item_node = @root_node @@ -45,13 +45,13 @@ def start_launch(desired_time = ReportPortal.now) # TODO: time should be a required argument def test_case_started(event, desired_time = ReportPortal.now) test_case = event.test_case - feature = test_case.feature - if report_hierarchy? && !same_feature_as_previous_test_case?(feature) + feature = @extractor.feature(test_case) + if report_hierarchy? && !@extractor.same_feature_as_previous_test_case?(@parent_item_node.name, feature) end_feature(desired_time) unless @parent_item_node.is_root? start_feature_with_parentage(feature, desired_time) end - name = "#{test_case.keyword}: #{test_case.name}" + name = "#{@extractor.scenario_keyword(test_case)}: #{@extractor.scenario_name(test_case)}" description = test_case.location.to_s tags = test_case.tags.map(&:name) type = :STEP @@ -76,13 +76,14 @@ def test_case_finished(event, desired_time = ReportPortal.now) def test_step_started(event, desired_time = ReportPortal.now) test_step = event.test_step - if step?(test_step) # `after_test_step` is also invoked for hooks - step_source = test_step.source.last + if @extractor.step?(test_step) # `after_test_step` is also invoked for hooks + step_source = @extractor.step_source(test_step) message = "-- #{step_source.keyword}#{step_source.text} --" - if step_source.multiline_arg.doc_string? - message << %(\n"""\n#{step_source.multiline_arg.content}\n""") - elsif step_source.multiline_arg.data_table? - message << step_source.multiline_arg.raw.reduce("\n") { |acc, row| acc << "| #{row.join(' | ')} |\n" } + multiline_arg=@extractor.step_multiline_arg(test_step) + if multiline_arg.doc_string? + message << %(\n"""\n#{multiline_arg.content}\n""") + elsif multiline_arg.data_table? + message << multiline_arg.raw.reduce("\n") { |acc, row| acc << "| #{row.join(' | ')} |\n" } end ReportPortal.send_log(:trace, message, time_to_send(desired_time)) end @@ -98,20 +99,14 @@ def test_step_finished(event, desired_time = ReportPortal.now) ex = result.exception format("%s: %s\n %s", ex.class.name, ex.message, ex.backtrace.join("\n ")) else - format("Undefined step: %s:\n%s", test_step.text, test_step.source.last.backtrace_line) + format("Undefined step: %s:\n%s", test_step.text, @extractor.step_backtrace_line(test_step)) end ReportPortal.send_log(:error, exception_info, time_to_send(desired_time)) end if status != :passed log_level = status == :skipped ? :warn : :error - step_type = if step?(test_step) - 'Step' - else - hook_class_name = test_step.source.last.class.name.split('::').last - location = test_step.location - "#{hook_class_name} at `#{location}`" - end + step_type = @extractor.step_type(test_step) ReportPortal.send_log(log_level, "#{step_type} #{status}", time_to_send(desired_time)) end end @@ -150,14 +145,10 @@ def time_to_send(desired_time) @last_used_time = time_to_send end - def same_feature_as_previous_test_case?(feature) - @parent_item_node.name == feature.location.file.split(File::SEPARATOR).last - end - def start_feature_with_parentage(feature, desired_time) parent_node = @root_node child_node = nil - path_components = feature.location.file.split(File::SEPARATOR) + path_components = @extractor.feature_location(feature).split(File::SEPARATOR) path_components.each_with_index do |path_component, index| child_node = parent_node[path_component] unless child_node # if child node was not created yet @@ -167,9 +158,9 @@ def start_feature_with_parentage(feature, desired_time) tags = [] type = :SUITE else - name = "#{feature.keyword}: #{feature.name}" - description = feature.file # TODO: consider adding feature description and comments - tags = feature.tags.map(&:name) + name = @extractor.feature_name(feature) + description = @extractor.feature_location(feature) # TODO: consider adding feature description and comments + tags = @extractor.feature_tags(feature).map(&:name) type = :TEST end # TODO: multithreading # Parallel formatter always executes scenarios inside the same feature in the same process @@ -208,10 +199,6 @@ def close_all_children_of(root_node) end end - def step?(test_step) - !::Cucumber::Formatter::HookQueryVisitor.new(test_step).hook? - end - def report_hierarchy? !ReportPortal::Settings.instance.formatter_modes.include?('skip_reporting_hierarchy') end From 73ff7ac8664fd01a0eb117e3c9af564d12e2b9dc Mon Sep 17 00:00:00 2001 From: Paul Thexton Date: Thu, 17 Sep 2020 19:49:21 +0100 Subject: [PATCH 6/7] Implement attach method for cucumber4 formatting The use of embed method is deprecated, as is the use of puts Log messages supplied by use of the log method come through to the attach method with an appropriate mime-type --- lib/report_portal/cucumber/formatter.rb | 6 ++++++ lib/report_portal/cucumber/report.rb | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index 34f6db3..dde3610 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -39,6 +39,12 @@ def embed(*args) process_message(:embed, *args) end + # embed is deprecated from cucumber4, should use attach + # instead + def attach(*args) + process_message(:attach, *args) + end + private def report diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 1afcaaf..5e548e8 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -129,6 +129,17 @@ def embed(path_or_src, mime_type, label, desired_time = ReportPortal.now) ReportPortal.send_file(:info, path_or_src, label, time_to_send(desired_time), mime_type) end + def attach(path_or_src, mime_type, desired_time = ReportPortal.now) + # Cucumber > 4 has deprecated the use of puts, and instead wants + # the use of "log". This in turn calls attach on all formatters + # with mime-type 'text/x.cucumber.log+plain' + if mime_type == 'text/x.cucumber.log+plain' + ReportPortal.send_log(:info, path_or_src, time_to_send(desired_time)) + else + ReportPortal.send_file(:info, path_or_src, nil, time_to_send(desired_time), mime_type) + end + end + private # Report Portal sorts logs by time. However, several logs might have the same time. From 0c306e2ea3835c428e7c829f4cdd3efcc37522a7 Mon Sep 17 00:00:00 2001 From: Paul Thexton Date: Thu, 17 Sep 2020 20:30:59 +0100 Subject: [PATCH 7/7] Fix rubocop warnings for pull request --- lib/report_portal/cucumber/extractor.rb | 256 ++++++++++++------------ lib/report_portal/cucumber/formatter.rb | 12 +- lib/report_portal/cucumber/report.rb | 6 +- lib/reportportal.rb | 18 +- 4 files changed, 147 insertions(+), 145 deletions(-) diff --git a/lib/report_portal/cucumber/extractor.rb b/lib/report_portal/cucumber/extractor.rb index 996da26..9f4e89d 100644 --- a/lib/report_portal/cucumber/extractor.rb +++ b/lib/report_portal/cucumber/extractor.rb @@ -1,138 +1,140 @@ module ReportPortal -module Cucumber - + module Cucumber + # extract interesting data from a cucumber v4+ event class ExtractorCucumber4 - def initialize(config) - require 'cucumber/formatter/ast_lookup' - @ast_lookup=::Cucumber::Formatter::AstLookup.new(config) - end - - # For Cucumber4 we actually return a representation of the gherkin - # document. So when querying tags, keywords etc, these need to come - # from the 'feature' attribute of the gherkin document - def feature(test_case) - @ast_lookup.gherkin_document(test_case.location.file) - end - - def feature_location(gherkin) - gherkin.uri - end - - def feature_tags(gherkin) - gherkin.feature.tags - end - - def feature_name(gherkin) - "#{gherkin.feature.keyword}: #{gherkin.feature.name}" - end - - def same_feature_as_previous_test_case?(previous_name,gherkin) - previous_name == gherkin.uri.split(File::SEPARATOR).last - end - - def scenario_keyword(test_case) - @ast_lookup.scenario_source(test_case).scenario.keyword - end - - def scenario_name(test_case) - @ast_lookup.scenario_source(test_case).scenario.name - end - - def step_source(test_step) - @ast_lookup.step_source(test_step).step - end - - def step_multiline_arg(test_step) - test_step.multiline_arg - end - - def step_backtrace_line(test_step) - test_step.backtrace_line - end - - def step_type(test_step) - case step?(test_step) - when true - 'Step' - when false - "#{test_step.text} at #{test_step.location.to_s}" - end - end - - def step?(test_step) - !test_step.hook? - end + def initialize(config) + require 'cucumber/formatter/ast_lookup' + @ast_lookup = ::Cucumber::Formatter::AstLookup.new(config) + end + + # For Cucumber4 we actually return a representation of the gherkin + # document. So when querying tags, keywords etc, these need to come + # from the 'feature' attribute of the gherkin document + def feature(test_case) + @ast_lookup.gherkin_document(test_case.location.file) + end + + def feature_location(gherkin) + gherkin.uri + end + + def feature_tags(gherkin) + gherkin.feature.tags + end + + def feature_name(gherkin) + "#{gherkin.feature.keyword}: #{gherkin.feature.name}" + end + + def same_feature_as_previous_test_case?(previous_name, gherkin) + previous_name == gherkin.uri.split(File::SEPARATOR).last + end + + def scenario_keyword(test_case) + @ast_lookup.scenario_source(test_case).scenario.keyword + end + + def scenario_name(test_case) + @ast_lookup.scenario_source(test_case).scenario.name + end + + def step_source(test_step) + @ast_lookup.step_source(test_step).step + end + + def step_multiline_arg(test_step) + test_step.multiline_arg + end + + def step_backtrace_line(test_step) + test_step.backtrace_line + end + + def step_type(test_step) + case step?(test_step) + when true + 'Step' + when false + "#{test_step.text} at #{test_step.location}" + end + end + + def step?(test_step) + !test_step.hook? + end end + # extract interesting data from a cucumber v3 event class ExtractorCucumber3 - def initialize(config) - require 'cucumber/formatter/hook_query_visitor' - end - - def feature(test_case) - test_case.feature - end - - def feature_location(feature) - feature.location.file - end - - def feature_tags(feature) - feature.tags - end - - def feature_name(feature) - "#{feature.keyword}: #{feature.name}" - end - - def same_feature_as_previous_test_case?(previous_name,feature) - previous_name == feature.location.file.split(File::SEPARATOR).last - end - - def scenario_keyword(test_case) - test_case.keyword - end - - def scenario_name(test_case) - test_case.name - end - - def step_source(test_step) - test_step.source.last - end - - def step_multiline_arg(test_step) - step_source(test_step).multiline_arg - end - - def step_backtrace_line(test_step) - test_step.source.last.backtrace_line - end - - def step_type(test_step) - case step?(test_step) - when true - 'Step' - when false - hook_class_name = test_step.source.last.class.name.split('::').last - "#{hook_class_name} at #{test_step.location}" - end - end - - def step?(test_step) - !::Cucumber::Formatter::HookQueryVisitor.new(test_step).hook? - end + def initialize(*) + require 'cucumber/formatter/hook_query_visitor' + end + + def feature(test_case) + test_case.feature + end + + def feature_location(feature) + feature.location.file + end + + def feature_tags(feature) + feature.tags + end + + def feature_name(feature) + "#{feature.keyword}: #{feature.name}" + end + + def same_feature_as_previous_test_case?(previous_name, feature) + previous_name == feature.location.file.split(File::SEPARATOR).last + end + + def scenario_keyword(test_case) + test_case.keyword + end + + def scenario_name(test_case) + test_case.name + end + + def step_source(test_step) + test_step.source.last + end + + def step_multiline_arg(test_step) + step_source(test_step).multiline_arg + end + + def step_backtrace_line(test_step) + test_step.source.last.backtrace_line + end + + def step_type(test_step) + case step?(test_step) + when true + 'Step' + when false + hook_class_name = test_step.source.last.class.name.split('::').last + "#{hook_class_name} at #{test_step.location}" + end + end + + def step?(test_step) + !::Cucumber::Formatter::HookQueryVisitor.new(test_step).hook? + end end + # Simple factory to provide an implementation that can extract interesting + # data from a cucumber event class Extractor - def self.create(config) - if(::Cucumber::VERSION.split('.').map(&:to_i) <=> [4,0,0]) > 0 - ExtractorCucumber4.new(config) - else - ExtractorCucumber3.new(config) - end + def self.create(config) + if (::Cucumber::VERSION.split('.').map(&:to_i) <=> [4, 0, 0]).positive? + ExtractorCucumber4.new(config) + else + ExtractorCucumber3.new(config) end + end end - -end + end end diff --git a/lib/report_portal/cucumber/formatter.rb b/lib/report_portal/cucumber/formatter.rb index dde3610..c15acf5 100644 --- a/lib/report_portal/cucumber/formatter.rb +++ b/lib/report_portal/cucumber/formatter.rb @@ -15,10 +15,10 @@ def initialize(config) setup_message_processing case config.out_stream - when IO - @io = config.out_stream - when String - @io = File.open(config.out_stream, "w") + when IO + @io = config.out_stream + when String + @io = File.open(config.out_stream, 'w') end %i[test_case_started test_case_finished test_step_started test_step_finished test_run_finished].each do |event_name| @@ -31,8 +31,8 @@ def initialize(config) def puts(message) process_message(:puts, message) - @io.puts(message) unless @io.nil? - @io.flush unless @io.nil? + @io&.puts(message) + @io&.flush end def embed(*args) diff --git a/lib/report_portal/cucumber/report.rb b/lib/report_portal/cucumber/report.rb index 5e548e8..723d865 100644 --- a/lib/report_portal/cucumber/report.rb +++ b/lib/report_portal/cucumber/report.rb @@ -79,7 +79,7 @@ def test_step_started(event, desired_time = ReportPortal.now) if @extractor.step?(test_step) # `after_test_step` is also invoked for hooks step_source = @extractor.step_source(test_step) message = "-- #{step_source.keyword}#{step_source.text} --" - multiline_arg=@extractor.step_multiline_arg(test_step) + multiline_arg = @extractor.step_multiline_arg(test_step) if multiline_arg.doc_string? message << %(\n"""\n#{multiline_arg.content}\n""") elsif multiline_arg.data_table? @@ -134,9 +134,9 @@ def attach(path_or_src, mime_type, desired_time = ReportPortal.now) # the use of "log". This in turn calls attach on all formatters # with mime-type 'text/x.cucumber.log+plain' if mime_type == 'text/x.cucumber.log+plain' - ReportPortal.send_log(:info, path_or_src, time_to_send(desired_time)) + ReportPortal.send_log(:info, path_or_src, time_to_send(desired_time)) else - ReportPortal.send_file(:info, path_or_src, nil, time_to_send(desired_time), mime_type) + ReportPortal.send_file(:info, path_or_src, nil, time_to_send(desired_time), mime_type) end end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index 627850d..84ea2f8 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -138,20 +138,20 @@ def item_id_of(name, parent_node) # needed for parallel formatter def uuid_of(name, parent_node) - itemid = item_id_of(name,parent_node) + itemid = item_id_of(name, parent_node) return nil if itemid.nil? path = "item/#{itemid}" data = send_request(:get, path) - (data.key? 'uuid') ? data['uuid'] : nil + data.key?('uuid') ? data['uuid'] : nil end # needed for parallel formatter - def launch_id_to_number() - path = "launch/#{@launch_id}" - data = send_request(:get, path) - data['id'] + def launch_id_to_number + path = "launch/#{@launch_id}" + data = send_request(:get, path) + data['id'] end # needed for parallel formatter @@ -180,9 +180,9 @@ def close_child_items(parent_id) # When querying child items here, we need to use the numerical id, but when # we call finish_item, the API it calls wants the UUID. Simplify by creating # a map. - ids = ids.map { |id| - {id: id, uuid: send_request(:get, "item/#{id}")['uuid']} - } + ids = ids.map do |id| + { id: id, uuid: send_request(:get, "item/#{id}")['uuid'] } + end ids.each do |id| close_child_items(id[:id])