testrail_rspec 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +39 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +44 -0
- data/Rakefile +2 -0
- data/lib/testrail_rspec/client.rb +240 -0
- data/lib/testrail_rspec/version.rb +3 -0
- data/lib/testrail_rspec.rb +182 -0
- data/testrail_rspec.gemspec +25 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 892fde2a276a04d5eb848babfe6f5d55f890bc46
|
4
|
+
data.tar.gz: 1367fdb75da3e603bf8ca11ba3bdecf825917d63
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f13bbde2db33c79f2f593f971c81190403e5b5d513a5b4d93fc4c143389dadcdd1b775a60a9b7052a4a34c5cf401a5a258e87dba570a27970d021c762ba3f19a
|
7
|
+
data.tar.gz: 8a780a95af0e588b3ad66ca7e932a384954d298b72c6be55a005e2e8ed6ccea14f831ebd6d24cad33fae59c2f6a504dcbe21787c6c944a932de1ff3bd206d975
|
data/.gitignore
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
Gemfile.lock
|
30
|
+
.ruby-version
|
31
|
+
.ruby-gemset
|
32
|
+
*.bundle
|
33
|
+
*.so
|
34
|
+
*.o
|
35
|
+
*.a
|
36
|
+
mkmf.log
|
37
|
+
|
38
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
39
|
+
.rvmrc
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Michal Kubik
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Testrail::Rspec
|
2
|
+
|
3
|
+
This gem provides custom RSpec formatter allowing to export test results directly to [TestRail][1] instance via their [API][2].
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'testrail_rspec'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install testrail_rspec
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Configuration
|
24
|
+
|
25
|
+
via RSpec.configure in spec_helper.rb - describe it in more details when decided how it is done
|
26
|
+
|
27
|
+
add to .rspec file this way:
|
28
|
+
|
29
|
+
$ --format TestrailRSpec
|
30
|
+
|
31
|
+
or use it from commandline this way:
|
32
|
+
|
33
|
+
$ rspec spec --format TestrailRSpec
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
1. Fork it ( https://github.com/[my-github-username]/testrail-rspec/fork )
|
38
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
39
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
40
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
41
|
+
5. Create a new Pull Request
|
42
|
+
|
43
|
+
[1]: http://www.gurock.com/testrail/ "TestRail"
|
44
|
+
[2]: http://docs.gurock.com/testrail-api2/start "TestRail API"
|
data/Rakefile
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
#
|
2
|
+
# TestRail API binding for Ruby (API v2, available since TestRail 3.0)
|
3
|
+
#
|
4
|
+
# Learn more:
|
5
|
+
#
|
6
|
+
# http://docs.gurock.com/testrail-api2/start
|
7
|
+
# http://docs.gurock.com/testrail-api2/accessing
|
8
|
+
#
|
9
|
+
# Copyright Gurock Software GmbH. See license.md for details.
|
10
|
+
#
|
11
|
+
|
12
|
+
# slightly modified by mkubik
|
13
|
+
|
14
|
+
require 'net/http'
|
15
|
+
require 'net/https'
|
16
|
+
require 'uri'
|
17
|
+
require 'json'
|
18
|
+
|
19
|
+
module TestrailRspec
|
20
|
+
|
21
|
+
AUTOMATED_DESCRIPTION = 'created by automated test suite'
|
22
|
+
|
23
|
+
STATUS = {
|
24
|
+
passed: 1,
|
25
|
+
failed: 5,
|
26
|
+
skipped: 7,
|
27
|
+
pending: 6
|
28
|
+
}
|
29
|
+
|
30
|
+
class Client
|
31
|
+
|
32
|
+
def initialize(args)
|
33
|
+
@projects = nil
|
34
|
+
@sections = Hash.new
|
35
|
+
@suite = Hash.new
|
36
|
+
@suites = Hash.new
|
37
|
+
|
38
|
+
@client = APIClient.new args[:url]
|
39
|
+
%w(user password project).each do |key|
|
40
|
+
raise Exception.new("TestRail configuration key :#{key} not set. Cannot continue without it.") if args[key.intern].nil?
|
41
|
+
@client.send "#{key}=", args[key.intern] if %w(user password).include? key
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# ---------------------------------------------------> projects ----------------------------------------------------
|
46
|
+
def get_projects
|
47
|
+
@projects ||= @client.send_get('get_projects')
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_project(project_name)
|
51
|
+
@projects = nil # invalidate cached stuff
|
52
|
+
@client.send_post('add_project', {name: project_name,
|
53
|
+
announcement: AUTOMATED_DESCRIPTION,
|
54
|
+
show_anouncement: true})
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_project(project_id)
|
58
|
+
self.get_projects.find { |project| project['id'] == project_id }
|
59
|
+
end
|
60
|
+
|
61
|
+
# ----------------------------------------------------> suites <----------------------------------------------------
|
62
|
+
def get_suite(suite_id)
|
63
|
+
@suite[suite_id] ||= @client.send_get("get_suite/#{suite_id}")
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_suites(project_id)
|
67
|
+
@suites[project_id] ||= @client.send_get("get_suites/#{project_id}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def find_suite_by_name(name, project_id)
|
71
|
+
suites = self.get_suites(project_id).select do |suite|
|
72
|
+
suite['name'] == name
|
73
|
+
end
|
74
|
+
puts "TestRail Exporter [WARN] #{suites.size} suites found with name: #{name}. Using first one." if suites.size > 1
|
75
|
+
suites.first
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_suite(name, project_id)
|
79
|
+
@suites.delete(project_id) # invalidate cached stuff
|
80
|
+
puts "TestRail Exporter [INFO] Creating suite: #{name} under project: #{ self.get_project(project_id)['name'] }"
|
81
|
+
@client.send_post("add_suite/#{project_id}", { name: name, description: AUTOMATED_DESCRIPTION })
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_or_create_suite(name, project_id)
|
85
|
+
self.find_suite_by_name(name, project_id) || self.create_suite(name, project_id)
|
86
|
+
end
|
87
|
+
|
88
|
+
# ---------------------------------------------------> sections <---------------------------------------------------
|
89
|
+
def get_sections(suite)
|
90
|
+
@sections[suite['id']] ||= @client.send_get("get_sections/#{suite['project_id']}&suite_id=#{suite['id']}")
|
91
|
+
end
|
92
|
+
|
93
|
+
def section_ids_at_depth(suite, depth)
|
94
|
+
self.get_sections(suite).select{ |s| s['depth'] == depth }.map{ |s| s['id'] }
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_section(name, suite, parent_id)
|
98
|
+
get_sections(suite).select{ |s| s['parent_id'] == parent_id }.find{ |s| s['name'] == name.strip }
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_or_create_section(name, suite, parent, depth)
|
102
|
+
parent_id = (depth == 0) ? nil : parent['id']
|
103
|
+
self.find_section(name, suite, parent_id) || self.create_section(name, suite, parent_id)
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_section(name, suite, parent_id)
|
107
|
+
@sections.delete(suite['id']) # invalidate cached values
|
108
|
+
# TODO: check if JSON created have null for nil parent_id
|
109
|
+
@client.send_post("add_section/#{suite['project_id']}", { name: name,
|
110
|
+
suite_id: suite['id'],
|
111
|
+
description: AUTOMATED_DESCRIPTION,
|
112
|
+
parent_id: parent_id })
|
113
|
+
end
|
114
|
+
|
115
|
+
# ----------------------------------------------------> cases <-----------------------------------------------------
|
116
|
+
def find_or_create_case(title, section, depth)
|
117
|
+
self.find_case(title, section, depth) || self.create_case(title, section['id'])
|
118
|
+
end
|
119
|
+
|
120
|
+
def find_case(title, section, depth)
|
121
|
+
suite = self.get_suite(section['suite_id'])
|
122
|
+
test_cases = @client.send_get("get_cases/#{suite['project_id']}&suite_id=#{suite['id']}")
|
123
|
+
test_cases.find do |test_case|
|
124
|
+
test_case['title'] == title && test_case['section_id'] == section['id']
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def create_case(title, section_id)
|
129
|
+
@client.send_post("add_case/#{section_id}", { title: title })
|
130
|
+
end
|
131
|
+
|
132
|
+
# ----------------------------------------------------> runs <------------------------------------------------------
|
133
|
+
def create_run(suite)
|
134
|
+
@client.send_post("add_run/#{suite['project_id']}", { suite_id: suite['id'], name: "#{nice_time_now} - #{suite['name']}", description: 'describe it somehow'})
|
135
|
+
end
|
136
|
+
|
137
|
+
def add_results_for_cases(run_id, results)
|
138
|
+
@client.send_post("add_results_for_cases/#{run_id}", { results: results })
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def nice_time_now
|
144
|
+
Time.now.strftime('%d %b %Y %R:%S %Z')
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
class APIClient
|
150
|
+
@url = ''
|
151
|
+
@user = ''
|
152
|
+
@password = ''
|
153
|
+
|
154
|
+
attr_accessor :user
|
155
|
+
attr_accessor :password
|
156
|
+
|
157
|
+
def initialize(base_url)
|
158
|
+
if !base_url.match(/\/$/)
|
159
|
+
base_url += '/'
|
160
|
+
end
|
161
|
+
@url = base_url + 'index.php?/api/v2/'
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Send Get
|
166
|
+
#
|
167
|
+
# Issues a GET request (read) against the API and returns the result
|
168
|
+
# (as Ruby hash).
|
169
|
+
#
|
170
|
+
# Arguments:
|
171
|
+
#
|
172
|
+
# uri The API method to call including parameters
|
173
|
+
# (e.g. get_case/1)
|
174
|
+
#
|
175
|
+
def send_get(uri)
|
176
|
+
_send_request('GET', uri, nil)
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Send POST
|
181
|
+
#
|
182
|
+
# Issues a POST request (write) against the API and returns the result
|
183
|
+
# (as Ruby hash).
|
184
|
+
#
|
185
|
+
# Arguments:
|
186
|
+
#
|
187
|
+
# uri The API method to call including parameters
|
188
|
+
# (e.g. add_case/1)
|
189
|
+
# data The data to submit as part of the request (as
|
190
|
+
# Ruby hash, strings must be UTF-8 encoded)
|
191
|
+
#
|
192
|
+
def send_post(uri, data)
|
193
|
+
_send_request('POST', uri, data)
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
def _send_request(method, uri, data)
|
198
|
+
url = URI.parse(@url + uri)
|
199
|
+
if method == 'POST'
|
200
|
+
request = Net::HTTP::Post.new(url.path + '?' + url.query)
|
201
|
+
request.body = JSON.dump(data)
|
202
|
+
else
|
203
|
+
request = Net::HTTP::Get.new(url.path + '?' + url.query)
|
204
|
+
end
|
205
|
+
request.basic_auth(@user, @password)
|
206
|
+
request.add_field('Content-Type', 'application/json')
|
207
|
+
|
208
|
+
conn = Net::HTTP.new(url.host, url.port)
|
209
|
+
if url.scheme == 'https'
|
210
|
+
conn.use_ssl = true
|
211
|
+
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
212
|
+
end
|
213
|
+
response = conn.request(request)
|
214
|
+
|
215
|
+
if response.body && !response.body.empty?
|
216
|
+
begin
|
217
|
+
result = JSON.parse(response.body)
|
218
|
+
rescue JSON::ParserError => e
|
219
|
+
raise APIError.new "TestRail API request (#{request.method} #{url}) failed\n#{e.class}: #{e.message}"
|
220
|
+
end
|
221
|
+
else
|
222
|
+
result = {}
|
223
|
+
end
|
224
|
+
|
225
|
+
if response.code != '200'
|
226
|
+
if result && result.key?('error')
|
227
|
+
error = '"' + result['error'] + '"'
|
228
|
+
else
|
229
|
+
error = 'No additional error message received'
|
230
|
+
end
|
231
|
+
raise APIError.new "TestRail API returned HTTP #{response.code} (#{error})"
|
232
|
+
end
|
233
|
+
|
234
|
+
result
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
class APIError < StandardError
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rspec/core/formatters/base_text_formatter'
|
3
|
+
|
4
|
+
require 'rubytree'
|
5
|
+
|
6
|
+
require "testrail_rspec/version"
|
7
|
+
require "testrail_rspec/client"
|
8
|
+
|
9
|
+
module TestrailRspec
|
10
|
+
|
11
|
+
RSpec.configuration.add_setting :testrail_formatter_options, :default => {}
|
12
|
+
|
13
|
+
class Exporter < RSpec::Core::Formatters::BaseTextFormatter
|
14
|
+
RSpec::Core::Formatters.register self, :start, :close, :dump_summary
|
15
|
+
# # :example_started, :example_passed,
|
16
|
+
# # :example_pending, :example_failed,
|
17
|
+
# :dump_failures, :dump_pending
|
18
|
+
# # :start_dump
|
19
|
+
|
20
|
+
|
21
|
+
# TODO: after exporter is done and working remove unnecessary overriden methods
|
22
|
+
|
23
|
+
def initialize(output)
|
24
|
+
@options = {}
|
25
|
+
@project_id = nil
|
26
|
+
super(output)
|
27
|
+
end
|
28
|
+
|
29
|
+
# To start
|
30
|
+
def start(notification)
|
31
|
+
@options = RSpec.configuration.testrail_formatter_options
|
32
|
+
@client = TestrailRspec::Client.new(@options)
|
33
|
+
@client.get_projects.each { |project| @project_id = project['id'] if project['name'] == @options[:project] }
|
34
|
+
|
35
|
+
puts "TestRail Exporter [INFO] Executing #{notification.count} tests. Loaded in #{notification.load_time}"
|
36
|
+
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
# Once per example group <-----------------------------------------------------------------------------
|
41
|
+
# def example_group_started(notification)
|
42
|
+
# groups = []
|
43
|
+
# current_group = notification.group
|
44
|
+
# until current_group.top_level?
|
45
|
+
# groups << current_group
|
46
|
+
# current_group = current_group.parent if current_group.parent_groups.size > 1
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# groups << current_group
|
50
|
+
#
|
51
|
+
# unless groups[0].examples.empty?
|
52
|
+
# groups.reverse.each_with_index do |group, idx|
|
53
|
+
# puts (idx == 0 ? "Spec: " : "") + (' ' *2 * idx) + "#{group.description}"
|
54
|
+
# end
|
55
|
+
# puts (' ' *2 * groups.size) + groups[0].examples.map(&:description).join("\n" + (' ' *2 * groups.size))
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# super
|
59
|
+
# end
|
60
|
+
|
61
|
+
# Once per example <-----------------------------------------------------------------------------------
|
62
|
+
# def example_started(notification)
|
63
|
+
# puts " - case: #{notification.example.description}"
|
64
|
+
# end
|
65
|
+
|
66
|
+
# One of these per example <---------------------------------------------------------------------------
|
67
|
+
# def example_passed(passed)
|
68
|
+
# puts "\tpass: #{passed.example.description}"
|
69
|
+
# end
|
70
|
+
|
71
|
+
# def example_failed(failure)
|
72
|
+
# puts "\tfail: #{failure.example.description}"
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# def example_pending(pending)
|
76
|
+
# puts "\tpend: #{pending.example.description}"
|
77
|
+
# end
|
78
|
+
|
79
|
+
# Optionally at any time <------------------------------------------------------------------------------
|
80
|
+
# def message(notification)
|
81
|
+
# puts "msg notification: #{notification.inspect}"
|
82
|
+
# super
|
83
|
+
# end
|
84
|
+
|
85
|
+
# At the end of the suite <-----------------------------------------------------------------------------
|
86
|
+
# def stop(notification)
|
87
|
+
# puts "stop notification: #{notification.inspect}"
|
88
|
+
# end
|
89
|
+
|
90
|
+
# def start_dump(null_notification)
|
91
|
+
# puts "start_dump notification: #{null_notification.inspect}"
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
def dump_pending(notification)
|
95
|
+
# puts "dump pend notification: #{notification.inspect}"
|
96
|
+
# super
|
97
|
+
end
|
98
|
+
|
99
|
+
def dump_failures(notification)
|
100
|
+
# puts "dump fail notification: #{notification.inspect}"
|
101
|
+
# super
|
102
|
+
end
|
103
|
+
|
104
|
+
def dump_summary(notification)
|
105
|
+
# Create project if it is not present / could do it setting controlled
|
106
|
+
if @project_id.nil?
|
107
|
+
puts "TestRail Exporter [INFO] Creating project: #{@options[:project]}"
|
108
|
+
@project_id = @client.add_project(@options[:project])['id']
|
109
|
+
end
|
110
|
+
|
111
|
+
suites = Hash.new do |h,k|
|
112
|
+
h[k] = Tree::TreeNode.new(k, @client.find_or_create_suite(k, @project_id) )
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
notification.examples.each do |example|
|
117
|
+
build_hierarchy_tree!(suites, example)
|
118
|
+
end
|
119
|
+
|
120
|
+
suites.each { |_, suite| update_test_run(suite) }
|
121
|
+
|
122
|
+
super
|
123
|
+
end
|
124
|
+
|
125
|
+
def close(null_notification)
|
126
|
+
# TODO: could close any open connection
|
127
|
+
puts "TestRail Exporter [INFO] Closing..."
|
128
|
+
super
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def get_path_for(node)
|
134
|
+
asc_arr = node.is_a?(RSpec::Core::Example) ? [node.description] : []
|
135
|
+
parent = (node.respond_to? :parent) ? node.parent : node.example_group
|
136
|
+
asc_arr << parent.description
|
137
|
+
asc_arr.push(*get_path_for(parent)) unless parent.top_level?
|
138
|
+
node.is_a?(RSpec::Core::Example) ? asc_arr.reverse : asc_arr
|
139
|
+
end
|
140
|
+
|
141
|
+
def build_hierarchy_tree!(suites, example)
|
142
|
+
path = get_path_for(example)
|
143
|
+
|
144
|
+
parent_node = suite_node = suites[path.shift]
|
145
|
+
path.unshift('Direct cases') unless path.size > 1
|
146
|
+
|
147
|
+
path.each_with_index do |item, idx|
|
148
|
+
child_node = (parent_node.children.map(&:name).include? item) ? parent_node[item] : nil
|
149
|
+
if child_node and (idx + 1 == path.size)
|
150
|
+
puts "TestRail Exporter [INFO] Second case with same path and name detected:\n\t#{suite_node.content['name']} -> #{path.join(' -> ')}"
|
151
|
+
end
|
152
|
+
|
153
|
+
unless child_node
|
154
|
+
child_node = if idx + 1 == path.size
|
155
|
+
Tree::TreeNode.new(item, { case: @client.find_or_create_case(item, parent_node.content, idx), result: example })
|
156
|
+
else
|
157
|
+
Tree::TreeNode.new(item, @client.find_or_create_section(item, suite_node.content, parent_node.content, idx))
|
158
|
+
end
|
159
|
+
parent_node << child_node
|
160
|
+
end
|
161
|
+
parent_node = child_node
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
def update_test_run(suite)
|
167
|
+
run_id = @client.create_run(suite.content)['id']
|
168
|
+
results = suite.each_leaf.map do |test|
|
169
|
+
test_result = test.content[:result].execution_result
|
170
|
+
run_time_seconds = test_result.run_time.round(0)
|
171
|
+
{
|
172
|
+
case_id: test.content[:case]['id'],
|
173
|
+
status_id: TestrailRspec::STATUS[test_result.status],
|
174
|
+
elapsed: (run_time_seconds == 0) ? nil : "#{run_time_seconds}s"
|
175
|
+
}
|
176
|
+
end
|
177
|
+
@client.add_results_for_cases(run_id, results)
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'testrail_rspec/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "testrail_rspec"
|
8
|
+
spec.version = TestrailRspec::VERSION
|
9
|
+
spec.authors = ["Michal Kubik"]
|
10
|
+
spec.email = ["michal.kubik@boost.no"]
|
11
|
+
spec.summary = %q{RSpec exporter formatter - pushes test run results to TestRail instance.}
|
12
|
+
spec.description = %q{Allow exporting RSpect test run results into TestRail server.}
|
13
|
+
spec.homepage = "https://github.com/mkubik8080/testrail-rspec"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.add_runtime_dependency "rubytree", "~> 0.9.4"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: testrail_rspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michal Kubik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rubytree
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description: Allow exporting RSpect test run results into TestRail server.
|
56
|
+
email:
|
57
|
+
- michal.kubik@boost.no
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/testrail_rspec.rb
|
68
|
+
- lib/testrail_rspec/client.rb
|
69
|
+
- lib/testrail_rspec/version.rb
|
70
|
+
- testrail_rspec.gemspec
|
71
|
+
homepage: https://github.com/mkubik8080/testrail-rspec
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.4.2
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: RSpec exporter formatter - pushes test run results to TestRail instance.
|
95
|
+
test_files: []
|
96
|
+
has_rdoc:
|