qat-reporter-xray 6.0.0

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.
@@ -0,0 +1,16 @@
1
+ require 'cucumber/core/test/result'
2
+
3
+ #Patch for Cucumber::Core::Test::Result::Unknown to implement methods used by the formatters
4
+ #@since 1.1.0
5
+ class Cucumber::Core::Test::Result::Unknown
6
+
7
+ #Dummy function
8
+ def with_appended_backtrace(_)
9
+ ''
10
+ end
11
+
12
+ #Dummy function
13
+ def with_filtered_backtrace(_)
14
+ ''
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'core_ext/result'
2
+ require_relative 'core_ext/formatter/html'
3
+ require_relative 'core_ext/formatter/junit'
@@ -0,0 +1,96 @@
1
+ require 'cucumber/formatter/io'
2
+ require 'json'
3
+
4
+ module QAT
5
+ module Formatter
6
+ class Xray
7
+ class TestIds
8
+ include Cucumber::Formatter::Io
9
+
10
+ def initialize(runtime, path_or_io, options)
11
+ @io = ensure_io(path_or_io)
12
+ @tags = []
13
+ @scenario_tags = []
14
+ @no_test_id = {}
15
+ @max_test_id = 0
16
+ @duplicate_test_ids = {}
17
+ @test_id_mapping = {}
18
+ @options = options
19
+ end
20
+
21
+ def before_feature(feature)
22
+ @in_scenarios = false
23
+ end
24
+
25
+ def tag_name(tag_name)
26
+ @scenario_tags << tag_name if @in_scenarios
27
+ end
28
+
29
+ def after_tags(tags)
30
+ @in_scenarios = true unless @in_scenarios
31
+ end
32
+
33
+ def scenario_name(keyword, name, file_colon_line, source_indent)
34
+ if @scenario_tags.any? { |tag| tag.match(/@id:(\d+)/) }
35
+ id = @scenario_tags.map { |tag| tag.match(/@id:(\d+)/) }.compact.first.captures.first.to_i
36
+ @max_test_id = id if id > @max_test_id
37
+
38
+ test_id_info = { name: name,
39
+ path: file_colon_line }
40
+
41
+ if @test_id_mapping[id]
42
+ if @duplicate_test_ids[id]
43
+ @duplicate_test_ids[id] << test_id_info
44
+ else
45
+ @duplicate_test_ids[id] = [@test_id_mapping[id], test_id_info]
46
+ end
47
+ else
48
+ @test_id_mapping[id] = test_id_info
49
+ end
50
+
51
+ else
52
+ @no_test_id[name] = file_colon_line unless @scenario_tags.include?('@dummy_test')
53
+ end
54
+ @scenario_tags = []
55
+ end
56
+
57
+ def after_features(features)
58
+ publish_result
59
+ @io.flush
60
+ end
61
+
62
+ private
63
+
64
+ def publish_result
65
+ content = {
66
+ max: @max_test_id,
67
+ untagged: @no_test_id,
68
+ mapping: Hash[@test_id_mapping.sort],
69
+ duplicate: Hash[@duplicate_test_ids.sort]
70
+ }
71
+
72
+ if @duplicate_test_ids.any?
73
+ dups_info = @duplicate_test_ids.map do |id, dups|
74
+ text = dups.map { |dup| "Scenario: #{dup[:name]} - #{dup[:path]}" }.join("\n")
75
+ "TEST ID #{id}:\n#{text}\n"
76
+ end
77
+
78
+ duplicates_info = <<-TXT.gsub(/^\s*/, '')
79
+ ------------------------------------
80
+ Duplicate test ids found!
81
+ ------------------------------------
82
+ #{dups_info.join("\n")}
83
+ TXT
84
+ puts duplicates_info
85
+ end
86
+
87
+ @io.puts(content.to_json({
88
+ indent: ' ',
89
+ space: ' ',
90
+ object_nl: "\n"
91
+ }))
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,186 @@
1
+ require 'cucumber/formatter/io'
2
+ require 'json'
3
+ require 'fileutils'
4
+ require 'qat/logger'
5
+ require 'time'
6
+ require 'base64'
7
+ require_relative '../reporter/xray/config'
8
+ require_relative '../reporter/xray/test_execution'
9
+
10
+ module QAT
11
+ # Namespace for custom Cucumber formatters and helpers.
12
+ #@since 0.1.0
13
+ module Formatter
14
+ # Namespace for Xray formatter
15
+ #@since 1.0.0
16
+ class Xray
17
+ include ::Cucumber::Formatter::Io
18
+ include QAT::Logger
19
+
20
+ #@api private
21
+ def initialize(runtime, path_or_io, options)
22
+ @io = ensure_io(path_or_io)
23
+ @options = options
24
+ @tests = []
25
+ end
26
+
27
+ #@api private
28
+ def tag_name(tag_name)
29
+ @test_jira_id = tag_name.to_s.split('_')[1] if tag_name.match(test_tag_regex)
30
+ end
31
+
32
+ #@api private
33
+ def before_test_case(test_case)
34
+ @current_scenario = test_case.source[1]
35
+
36
+ @exception = nil
37
+
38
+ @start_time = Time.now
39
+ @evidences = []
40
+ @file_counter = 0
41
+ end
42
+
43
+ #@api private
44
+ def after_test_case(_, status)
45
+ # When jira type is cloud the test result string must be different (accordingly with xray api)
46
+ test_status = if status.is_a? ::Cucumber::Core::Test::Result::Passed
47
+ jira_type == 'cloud' ? 'PASSED' : 'PASS'
48
+ elsif status.is_a? ::Cucumber::Core::Test::Result::Failed
49
+ jira_type == 'cloud' ? 'FAILED' : 'FAIL'
50
+ else
51
+ 'NO RUN'
52
+ end
53
+
54
+ @end_time = Time.now
55
+
56
+ comment = status.respond_to?(:exception) ? build_exception(status.exception) : ''
57
+
58
+ log.warn 'Jira ID is not defined!' unless @test_jira_id
59
+ if @current_scenario.is_a? ::Cucumber::Core::Ast::ScenarioOutline
60
+ save_current_scenario_outline(test_status, comment)
61
+ else
62
+ save_current_scenario(test_status, comment)
63
+ end
64
+
65
+ end
66
+
67
+ #@api private
68
+ def after_features(*_)
69
+ publish_result
70
+ end
71
+
72
+ def embed(src, mime_type, label)
73
+
74
+
75
+ data = if File.file?(src)
76
+ File.open(src) do |file|
77
+ Base64.strict_encode64(file.read)
78
+ end
79
+ elsif src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
80
+ src
81
+ else
82
+ Base64.strict_encode64(src)
83
+ end
84
+
85
+ ext = mime_type.split('/').last
86
+
87
+ file_name = if File.file?(src)
88
+ File.basename(src)
89
+ elsif label.to_s.empty?
90
+ "file_#{@file_counter += 1}.#{ext}"
91
+ else
92
+ "#{label}.#{ext}"
93
+ end
94
+
95
+ @evidences << { data: data, filename: file_name, contentType: mime_type }
96
+ end
97
+
98
+ private
99
+
100
+ def jira_type
101
+ QAT::Reporter::Xray::Config.jira_type
102
+ end
103
+
104
+ def test_prefix
105
+ QAT::Reporter::Xray::Config.test_prefix
106
+ end
107
+
108
+ def project_key
109
+ QAT::Reporter::Xray::Config.project_key
110
+ end
111
+
112
+ def test_tag_regex
113
+ /@#{test_prefix}(#{project_key}-\d+)/
114
+ end
115
+
116
+ def build_exception(exception)
117
+ "#{exception.message} (#{exception.class})\n#{exception.backtrace.join("\n")}"
118
+ end
119
+
120
+ def save_current_scenario(status, comment = '')
121
+ test_info = {
122
+ testKey: @test_jira_id,
123
+ start: @start_time.iso8601,
124
+ finish: @end_time.iso8601,
125
+ comment: comment,
126
+ status: status
127
+ }
128
+ test_info[:evidences] = @evidences unless @evidences.empty?
129
+ @tests << test_info
130
+ end
131
+
132
+ def save_current_scenario_outline(status, comment = '')
133
+ if @tests.any? { |test| test[:testKey] == @test_jira_id }
134
+ outline = @tests.select { |test| test[:testKey] == @test_jira_id }.first
135
+
136
+ outline[:finish] = @end_time.iso8601
137
+ unless outline[:status] == 'FAIL' || outline[:status] == 'FAILED'
138
+ outline[:status] = status
139
+ outline[:comment] = comment
140
+ end
141
+ outline[:examples] << status
142
+
143
+ @tests.delete_if { |test| test[:testKey] == @test_jira_id }
144
+ @tests << outline
145
+ else
146
+ @tests << {
147
+ testKey: @test_jira_id,
148
+ start: @start_time.iso8601,
149
+ finish: @end_time.iso8601,
150
+ comment: comment,
151
+ status: status,
152
+ examples: [
153
+ status
154
+ ]
155
+ }
156
+ if @tests.is_a? Array
157
+ @tests.first[:evidences] = @evidences unless @evidences.empty?
158
+ else
159
+ @tests[:evidences] = @evidences unless @evidences.empty?
160
+ end
161
+
162
+ end
163
+ end
164
+
165
+ # Writes results to a JSON file
166
+ def publish_result
167
+ content = {
168
+ tests: @tests
169
+ }
170
+ @io.puts(JSON.pretty_generate(content))
171
+
172
+ jira_id = ENV['XRAY_TEST_EXECUTION'].to_s
173
+
174
+ if jira_id.empty?
175
+ log.warn 'Importing Test results to a new Test Execution.'
176
+ jira_id = nil
177
+ else
178
+ log.info "Importing Test results to Test Execution '#{jira_id}'."
179
+ end
180
+
181
+ QAT::Reporter::Xray::TestExecution.new(jira_id).import_execution_results(content)
182
+
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,153 @@
1
+ require_relative 'publisher'
2
+
3
+ module QAT
4
+ module Reporter
5
+ class Xray
6
+ # QAT::Reporter::Xray configuration module
7
+ module Config
8
+ class << self
9
+
10
+ attr_accessor :project_key, :test_prefix, :story_prefix, :jira_url, :xray_default_api_url, :login_credentials, :publisher, :jira_type,
11
+ :cloud_xray_api_credentials, :xray_test_environment, :xray_test_version, :xray_test_revision, :xray_export_test_keys, :xray_export_test_filter
12
+
13
+ # Default xray API url (Jira Cloud)
14
+ DEFAULT_XRAY_URL = 'https://xray.cloud.xpand-it.com'
15
+
16
+ # Default test tag prefix
17
+ DEFAULT_TEST_PREFIX = 'TEST_'
18
+ # Default story tag prefix
19
+ DEFAULT_STORY_PREFIX = 'STORY_'
20
+
21
+ # Returns the xray instanced type (hosted or cloud)
22
+ def jira_type
23
+ @jira_type
24
+ end
25
+
26
+ # Returns the jira url
27
+ def jira_url
28
+ @jira_url
29
+ end
30
+
31
+ # Returns the default xray jira url for cloud api
32
+ def xray_default_api_url
33
+ DEFAULT_XRAY_URL
34
+ end
35
+
36
+ # Returns the login credentials array could -> [username, password, apiToken]
37
+ def login_credentials
38
+ @login_credentials
39
+ end
40
+
41
+ # Returns the login credentials array for cloud api [client_id, client_secret]
42
+ def cloud_xray_api_credentials
43
+ @cloud_xray_api_credentials || nil
44
+ end
45
+
46
+ # Returns the test keys to export
47
+ def xray_export_test_keys
48
+ @keys || nil
49
+ end
50
+
51
+ # Returns the test filter to export
52
+ def xray_export_test_filter
53
+ @filter || nil
54
+ end
55
+
56
+ # Returns the project key value
57
+ def project_key
58
+ @project_key
59
+ end
60
+
61
+ # Returns the test tag prefix value
62
+ def test_prefix
63
+ @test_prefix || DEFAULT_TEST_PREFIX
64
+ end
65
+
66
+ # Returns the story tag prefix value
67
+ def story_prefix
68
+ @story_prefix || DEFAULT_STORY_PREFIX
69
+ end
70
+
71
+ # Returns the xray test environment value
72
+ def xray_test_environment
73
+ @xray_test_environment || get_env_from_qat_config
74
+ end
75
+
76
+ # Returns the xray test version value
77
+ def xray_test_version
78
+ @xray_test_version || get_version_from_qat_config
79
+ end
80
+
81
+ # Returns the xray test revision value
82
+ def xray_test_revision
83
+ @xray_test_revision || get_revision_from_qat_config
84
+ end
85
+
86
+ def publisher=(publisher)
87
+ @publisher = publisher
88
+ end
89
+
90
+ def publisher
91
+ @publisher
92
+ end
93
+
94
+
95
+ private
96
+
97
+ def get_env_from_qat_config
98
+ begin
99
+ QAT.configuration.dig(:xray, :environment_name)
100
+ rescue ArgumentError
101
+ raise(NoEnvironmentDefined, 'JIRA\'s environment must be defined!')
102
+ end
103
+ end
104
+
105
+ def get_version_from_qat_config
106
+ begin
107
+ QAT.configuration.dig(:xray, :version)
108
+ rescue ArgumentError
109
+ raise(NoVersionDefined, 'JIRA\'s version must be defined!')
110
+ end
111
+ end
112
+
113
+ def get_revision_from_qat_config
114
+ begin
115
+ QAT.configuration.dig(:xray, :revision)
116
+ rescue ArgumentError
117
+ raise(NoRevisionDefined, 'JIRA\'s revision must be defined!')
118
+ end
119
+ end
120
+
121
+ # Error returned when the QAT project has not defined the Jira Environment
122
+ class NoEnvironmentDefined < StandardError
123
+ end
124
+ # Error returned when the QAT project has not defined the Jira Version
125
+ class NoVersionDefined < StandardError
126
+ end
127
+ # Error returned when the QAT project has not defined the Jira Revision
128
+ class NoRevisionDefined < StandardError
129
+ end
130
+ end
131
+ end
132
+
133
+ class << self
134
+ # Configures the QAT::Formatter::Xray
135
+ def configure(&block)
136
+ yield Config
137
+
138
+ QAT::Reporter::Xray::Config.publisher = QAT::Reporter::Xray::Publisher.const_get(QAT::Reporter::Xray::Config.jira_type.capitalize).new
139
+
140
+ raise(ProjectKeyUndefinedError, 'JIRA\'s project key must be defined!') unless QAT::Reporter::Xray::Config.project_key
141
+ raise(LoginCredentialsUndefinedError, 'JIRA\'s login credentials must be defined!') unless QAT::Reporter::Xray::Config.login_credentials
142
+ end
143
+
144
+ # Error returned when the the JIRA project key is not defined
145
+ class ProjectKeyUndefinedError < StandardError
146
+ end
147
+ # Error returned when the the JIRA login credentials is not defined
148
+ class LoginCredentialsUndefinedError < StandardError
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,46 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+
4
+ module QAT
5
+ module Reporter
6
+ class Xray
7
+ # QAT::Reporter::Xray::Issue represents an abstract Xray issue
8
+ class Issue
9
+
10
+ attr_reader :jira_id
11
+
12
+ # Initializes Xray Publisher url and login information
13
+ def initialize(jira_id = nil)
14
+ @jira_id = jira_id
15
+ if jira_id
16
+ raise(InvalidIssueType, "The given issue '#{jira_id}' type does not correspond!") unless valid_test_execution?
17
+ end
18
+ end
19
+
20
+ # Creates a issue
21
+ def create(data)
22
+ QAT::Reporter::Xray::Config.publisher.create_issue(data)
23
+ end
24
+
25
+ # Error returned when the the JIRA Issue does not correspond
26
+ class InvalidIssueType < StandardError
27
+ end
28
+ # Error returned when publisher string is not known
29
+ class PublisherNotKnownError < StandardError
30
+ end
31
+
32
+ private
33
+
34
+ def valid_test_execution?
35
+ base = Publisher::Base.new
36
+ response = JSON.parse(Publisher::Base::Client.new(base.base_url).get("/rest/api/2/issue/#{jira_id}", base.default_headers))
37
+ if response.dig('fields', 'issuetype', 'name').eql? 'Test Execution'
38
+ true
39
+ else
40
+ false
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end