qat-reporter-xray 6.0.0

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