cucumber_testrail 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ecd80cf8e65a0501a6682cbf5dc0d3673711927
4
+ data.tar.gz: 2ff6834a864a02c9e85ea4c0a27be6af917dcb18
5
+ SHA512:
6
+ metadata.gz: 96c2ea59b4b6b57a8d94b73a928e06660cdd23f67ceb53b5f37d0c251eb9679a5b551ae3dde621fc9da4c04aef3b0dbfe79a6e51cd704b3bb247bbc2c55db4d4
7
+ data.tar.gz: e369fa2f42664c73d8de0f94dc5e85fa020ad0979799c99dc3abcb698da5eef6d994a998285ea524130a474be3a0cf3d9d613c6a6264090b480ed62f27439d37
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ *.swp
24
+
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ cuke_rail
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.1.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cucumber_testrail.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 John Small
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,85 @@
1
+ # CucumberTestrail
2
+
3
+ ## Overview
4
+
5
+ This gem helps you synchronise your Cucumber test suites into Testrail. It uses tags to link Testrail projects, test suites, testcases
6
+ and write reports for testruns into Testrail. It will also create new testcases if it can't find them in Testrail and update your feature files
7
+ to add the tag to the scenario. It writes the step definition into the testcase and keeps it updated.
8
+
9
+ The central idea is that tags in the features files are used to link your scenarios to Testrail. The identifiers used are 'project','suite','testcase','testrun' they are all looked up
10
+ from tags on the scenario, tags on the feature or environment variables. E.g. on the scenario you might have a tag **@testcase_99**, this will link to the Testrail tescase
11
+ with id=99.
12
+
13
+ In Testrail, tests are instances of testcases within the context of a testrun. Therefore to report on a test instance you need a combination of testcase
14
+ and testrun. The testrun can be one of a tag at scenario level, a tag at feature level or and environment level, e.g. a tag **@testrun_5** or
15
+ environment variable **TESTRUN=5**. The combination of testcase and testrun form a Testrail test and the result of the senario is add to a list of results for the Testrail test. How you allocate test suites, testcases and testruns is entirely up to you.
16
+
17
+ ## Installation
18
+ The gem is hosted our own gemserver at http://ec2-54-77-102-63.eu-west-1.compute.amazonaws.com:9292 so you need to add these lines to your Gemfile
19
+
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ source "http://ec2-54-77-102-63.eu-west-1.compute.amazonaws.com:9292"
24
+ gem 'cucumber_testrail',"~>1.1.0" #whatever is the latest version
25
+
26
+ $ bundle
27
+
28
+ ### Basic usage
29
+
30
+ Once you've installed the gem and bundled it, you need to add the methods in the gem to the World object. In **env.rb** add
31
+
32
+ World CucumberTestrail
33
+
34
+ Then you need to add the code to After scenario hooks
35
+
36
+ send_result(scenario)
37
+
38
+ Then state which testrun you're operating in. This can be either a tag e.g. **@testrun_5** at the scenario or feature level, or in the environment **TESTRUN=5**
39
+
40
+ Set up environment variables for the Testrail URL, username and password
41
+
42
+ export TESTRAIL__USER="your-testrail-user"
43
+ export TESTRAIL_PASSWORD="your-testrail-password"
44
+ export TESTRAIL_BASE_URL="your-testrail-server"
45
+
46
+
47
+ #### What if I don't have a testcase id?
48
+
49
+ The gem will try to find one that matches the title and if it can't it'll create a new testcase. It then takes the testcase id and adds a tag to your
50
+ feature file. That requires at least one line in between each scenario, but you do that anyway.
51
+
52
+ To create the testcase it needs to know the project id suite id and the sub section id.
53
+ Those should be added as a tags at feature level e.g. **@project_2 @suite_10 @sub_section_14**
54
+
55
+ #### Outlines!!
56
+
57
+ Each line in the Examples section of a Scenario Outline will be mapped to a unique testcase using the scenario title, and the named variables in the
58
+ examples. e.g.
59
+
60
+ Scenario Outline: A title
61
+ Given some steps
62
+ Examples: some example lines
63
+ |heading 1 | heading 2 |
64
+ | ex1 | ex2 |
65
+
66
+ Will be mapped to a testcase with title "A title : heading 1='ex1', heading 2='ex2'". Because we will have many testcases for a single scenario outline
67
+ then the gem doesn't add testcase tags
68
+
69
+ #### What if I don't want to send a report to Testrail
70
+
71
+ There's a special tag **@ignore_testrail** and environment variable **IGNORE_TESTRAIL=true**. So you can specify one scenario should not be linked to Testrail,
72
+ one feature file, or the whole run.
73
+
74
+ #### What if I want to tag it with a jira ticket number?
75
+
76
+ Add the tag **@jira_STORE-123** for example. This will then appear in the defect section of the test report as STORE-123. You can add multiple jira tags to a scenario
77
+
78
+
79
+ ### Synchronising Testrail testcases to Cucumber scenarios?
80
+
81
+ The gem relies first and foremost on the tag **@testcase_id**. If that tag is present it will use that id without further checks. When it writes the result of the
82
+ test it also updates the title of the testcase to match the title of the scenario and the contents of the testcase steps to match the scenario steps. It overwrites
83
+ these without any checks. That means if you have a Testrail testcase 5, with title 'Check that the sun comes up in the morning' and you give a tag **@testcase_5**
84
+ to a scenario titled 'The user should be logged in', then at the end of the run that Testrail testcase will now have the title taken from the scenario.
85
+
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ Dir.glob('tasks/**/*.rake').each(&method(:import))
3
+ task :default => :spec
4
+
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cucumber_testrail/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cucumber_testrail"
8
+ spec.version = CucumberTestrail::VERSION
9
+ spec.authors = ["John Small"]
10
+ spec.email = ["john.small@bbc.com"]
11
+ spec.summary = %q{Sync Cucumber specs and results to TestRail}
12
+ spec.description = %q{Call cucumber_testrail.send_result(scenario) in an After hook. It will match existing testcases by scenario title or add new testcases
13
+ and then add the result of the run to TestRail. See the README.md}
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "cucumber","~>1.3"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake","~>10.3"
25
+ spec.add_development_dependency "rspec","~>3.0"
26
+ spec.add_development_dependency "vcr","~>2.9"
27
+ spec.add_development_dependency "webmock","~>1.18"
28
+ spec.add_development_dependency "rdoc","~>4.1"
29
+ spec.add_development_dependency "geminabox","~>0.12"
30
+ end
@@ -0,0 +1,7 @@
1
+ require "cucumber_testrail/version"
2
+ require "cucumber_testrail/reopen_scenario"
3
+ require "cucumber_testrail/testrail"
4
+ require "cucumber_testrail/external_reporting"
5
+ module CucumberTestrail
6
+ include Report
7
+ end
@@ -0,0 +1,106 @@
1
+ require 'tempfile'
2
+ require 'fileutils'
3
+ module Report
4
+
5
+ ##
6
+ # allows access to an instance of the testrail object. If it's not set then it will be created on the first read
7
+ # most useful when testing because you can put a spy in place of the testrail api to check the expected calls are being made
8
+ attr_accessor :testrail
9
+
10
+ ## The only entry point from your Cucumber After scenario hooks.
11
+ # You need to have this code ;-
12
+ #
13
+ # After do |scenario|
14
+ # send_result(scenario)
15
+ # end
16
+ #
17
+ # It returns true if it wrote to Testrail and false if it didn't. I.e. if you set *ignore_testrail* then it won't send results to Testrail and it will return false
18
+ #
19
+ def send_result(scenario)
20
+ if scenario.ignore_testrail?
21
+ result = false
22
+ else
23
+ external_reference = get_an_external_reference(scenario)
24
+ raise 'duff external reference' unless external_reference.to_i > 0
25
+ #p "external reference #{external_reference}"
26
+ send_test_result_to_testrail(scenario,external_reference) unless scenario.skip_result?
27
+ update_test_case(scenario,external_reference)
28
+ # only write the testcase tag if it's a scenario, not a scenario outline
29
+ update_source_file(scenario,"testcase_#{external_reference}") if scenario.is_a?(Cucumber::Ast::Scenario)
30
+ result = true
31
+ end
32
+ result
33
+ end
34
+
35
+
36
+ ##
37
+ # This method takes an external reference, e.g. testcase_119 and adds it as a tag before the scenario
38
+ #
39
+ def update_source_file(scenario, external_reference)
40
+ #this could be done on one line with sed. But the format is different on Mac OS and GNU Linux version and it definitely won't work on a Windows machine
41
+ path = scenario.file
42
+ lines = IO.readlines(scenario.file)
43
+ lines[scenario.tag_line].gsub!(/@testcase_(\d*)|\n/," @#{external_reference}") unless lines[scenario.tag_line] =~/#{external_reference}/
44
+ temp_file = Tempfile.new('foo')
45
+ begin
46
+ File.open(path, 'r') do |file|
47
+ lines.each do |line|
48
+ temp_file.puts line
49
+ end
50
+ end
51
+ temp_file.close
52
+ FileUtils.mv(temp_file.path, path)
53
+ ensure
54
+ temp_file.close
55
+ temp_file.unlink
56
+ end
57
+ end
58
+
59
+ ##
60
+ # If we don't have a tag testcase id then we need to find one. Either by matching a testcase by title, or by creating a new testcase
61
+ # The input is a scenario and the result is a testcase id
62
+ def get_an_external_reference(scenario)
63
+ (check_if_a_test_case_id_exists(scenario) || locate_a_test_case_by_name(scenario) || create_a_test_case_from_scenario(scenario)['id']).to_i
64
+ end
65
+
66
+ def create_a_test_case_from_scenario(scenario)
67
+ testrail.send_post("add_case/#{scenario.sub_section || scenario.suite}",scenario.testrail_testcase)
68
+ end
69
+
70
+ def locate_a_test_case_by_name(scenario)
71
+ if scenario.sub_section
72
+ restrict_by ="#{scenario.project}&suite_id=#{scenario.suite}&section_id=#{scenario.sub_section}"
73
+ else
74
+ restrict_by ="#{scenario.project}&suite_id=#{scenario.suite}"
75
+ end
76
+ matches = testrail.send_get("get_cases/#{restrict_by}").select{|m| m['title'] == scenario.title}
77
+ unless matches.empty?
78
+ matches.first['id']
79
+ end
80
+ end
81
+
82
+ def check_if_a_test_case_id_exists(scenario)
83
+ if scenario.testcase
84
+ matches = testrail.send_get("get_cases/#{scenario.project}&suite_id=#{scenario.suite}").select{|m| m['id'].to_i == scenario.testcase.to_i}
85
+ ref = matches.first['id']
86
+ end
87
+ ref
88
+ end
89
+
90
+ def send_test_result_to_testrail(scenario,external_reference=nil)
91
+ testcase = external_reference || scenario.testcase
92
+ testrail.send_post("add_result_for_case/#{scenario.testrun}/#{testcase}",scenario.testrail_test_report)
93
+ end
94
+
95
+ def update_test_case(scenario,external_reference)
96
+ testcase = external_reference || scenario.testcase
97
+ testrail.send_post("update_case/#{testcase}",scenario.testrail_testcase)
98
+ end
99
+
100
+ def testrail
101
+ raise 'TESTRAIL_BASE_URL must be set up in your environment' unless ENV['TESTRAIL_BASE_URL']
102
+ raise 'TESTRAIL_USER must be set up in your environment' unless ENV['TESTRAIL_USER']
103
+ raise 'TESTRAIL_PASSWORD must be set up in your environment' unless ENV['TESTRAIL_PASSWORD']
104
+ @testrail ||= TestRail::APIClient.new(ENV['TESTRAIL_BASE_URL'],ENV['TESTRAIL_USER'],ENV['TESTRAIL_PASSWORD'])
105
+ end
106
+ end
@@ -0,0 +1,58 @@
1
+ require_relative 'testrail_extensions'
2
+ module Cucumber #:nodoc:
3
+ module Ast #:nodoc:
4
+ class Scenario
5
+ include Testrail::Scenario
6
+ ##
7
+ # return the steps and as strings ready to write to the testcase
8
+ def steps_as_string
9
+ to_sexp.select{|a| a[0]==:step_invocation}.map do |s|
10
+ if s[4]
11
+ ["#{s[2]}#{s[3]}"]+s[4].select{|e| e.is_a?(Array) && e[0]==:row}.map do |l|
12
+ "| #{l.select{|e| e.is_a?(Array)}.map{|e| e[1]}.join(' | ')} |"
13
+ end
14
+ else
15
+ "#{s[2]}#{s[3]}"
16
+ end
17
+ end.flatten.join("\n")
18
+ end
19
+
20
+ def tag_line
21
+ line - 2
22
+ end
23
+ end
24
+
25
+ class ScenarioOutline
26
+ def my_steps
27
+ @steps ||= @background.step_collection(step_invocations)
28
+ end
29
+ end
30
+
31
+ class OutlineTable
32
+ class ExampleRow
33
+ include Testrail::Scenario
34
+
35
+ def feature_tags
36
+ scenario_outline.feature_tags
37
+ end
38
+
39
+ def title
40
+ line = self.to_hash.to_a.map{|a| "#{a[0]}='#{a[1]}'"}.join(', ')
41
+ "#{scenario_outline.title} :: #{line}"
42
+ end
43
+
44
+ def steps_as_string
45
+ #example rows in an outline table don't respond to steps so we have to check the scenario_outline
46
+ scenario_outline.raw_steps.map{|s| "#{s.keyword} #{s.name}"}.join("\n")
47
+ end
48
+
49
+ def tag_line
50
+ scenario_outline.file_colon_line.split(':')[1].to_i - 2
51
+ end
52
+ def file
53
+ scenario_outline.file
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,108 @@
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
10
+ #
11
+
12
+ require 'net/http'
13
+ require 'net/https'
14
+ require 'uri'
15
+ require 'json'
16
+
17
+ module TestRail
18
+ class APIClient
19
+ @url = ''
20
+ @user = ''
21
+ @password = ''
22
+
23
+ attr_accessor :user
24
+ attr_accessor :password
25
+
26
+ def initialize(base_url,user,password)
27
+ if !base_url.match(/\/$/)
28
+ base_url += '/'
29
+ end
30
+ @url = base_url + 'index.php?/api/v2/'
31
+ @user ||=user
32
+ @password ||=password
33
+ end
34
+
35
+ #
36
+ # Send Get
37
+ #
38
+ # Issues a GET request (read) against the API and returns the result
39
+ # (as Ruby hash).
40
+ #
41
+ # Arguments:
42
+ #
43
+ # uri The API method to call including parameters
44
+ # (e.g. get_case/1)
45
+ #
46
+ def send_get(uri)
47
+ _send_request('GET', uri, nil)
48
+ end
49
+
50
+ #
51
+ # Send POST
52
+ #
53
+ # Issues a POST request (write) against the API and returns the result
54
+ # (as Ruby hash).
55
+ #
56
+ # Arguments:
57
+ #
58
+ # uri The API method to call including parameters
59
+ # (e.g. add_case/1)
60
+ # data The data to submit as part of the request (as
61
+ # Ruby hash, strings must be UTF-8 encoded)
62
+ #
63
+ def send_post(uri, data)
64
+ _send_request('POST', uri, data)
65
+ end
66
+
67
+ private
68
+ def _send_request(method, uri, data)
69
+ url = URI.parse(@url + uri)
70
+ if method == 'POST'
71
+ request = Net::HTTP::Post.new(url.path + '?' + url.query)
72
+ request.body = JSON.dump(data)
73
+ else
74
+ request = Net::HTTP::Get.new(url.path + '?' + url.query)
75
+ end
76
+ request.basic_auth(@user, @password)
77
+ request.add_field('Content-Type', 'application/json')
78
+
79
+ conn = Net::HTTP.new(url.host, url.port)
80
+ if url.scheme == 'https'
81
+ conn.use_ssl = true
82
+ conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
83
+ end
84
+ response = conn.request(request)
85
+
86
+ if response.body && !response.body.empty?
87
+ result = JSON.parse(response.body)
88
+ else
89
+ result = {}
90
+ end
91
+
92
+ if response.code != '200'
93
+ if result && result.key?('error')
94
+ error = '"' + result['error'] + '"'
95
+ else
96
+ error = 'No additional error message received'
97
+ end
98
+ raise APIError.new('TestRail API returned HTTP %s (%s)' %
99
+ [response.code, error])
100
+ end
101
+
102
+ result
103
+ end
104
+ end
105
+
106
+ class APIError < StandardError
107
+ end
108
+ end
@@ -0,0 +1,139 @@
1
+ module Testrail
2
+ module Scenario
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ base.set_testrail_configuration(:status_map,{'passed'=>1,'failed'=>5,'undefined'=>7,'pending'=>6})
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ @@testrail_configuration={}
11
+
12
+ ##
13
+ # set a configuration property at the class level. Initially used to set the mapping between Cucumber test results and Testrail result ids
14
+ def set_testrail_configuration(property_name,property_value)
15
+ @@testrail_configuration[property_name] = property_value
16
+ end
17
+ ##
18
+ # get a named Testrail configuration property. Currenly only used for the results status map
19
+ def get_testrail_configuration(property_name)
20
+ @@testrail_configuration[property_name]
21
+ end
22
+
23
+ end
24
+
25
+ ##
26
+ # * Looks for a tagname at scenario, feature file and environment levels in that order
27
+ # * Unless the tagname == 'ignore_testrail' it will return the id of the tag. e.g. input tagname 'testcase' output '5'
28
+ # * If the tagname == 'ignore_testrail' then it will return nil or true depeneding if it can find the tag or the environment variable
29
+ def testrail_tag(tagname)
30
+ #check scenario, feature, environment in that order
31
+ result = nil
32
+ unless tagname =~ /ignore_testrail|manual|skip_result/
33
+ [source_tag_names,feature_tags.tags.map{|t| t.name}].each do | tags |
34
+ extracted_tag = tags.map{|tag| /^@#{tagname}_(\d+)$|^@#{tagname}_(.*)$/.match(tag)}.compact
35
+ if extracted_tag
36
+ if tagname=='jira'
37
+ result = extracted_tag.map{|e| e.to_a.compact[1]}.join(', ')
38
+ else
39
+ result = extracted_tag.first.to_a.compact[1] # we compact it because there are two possible matches, if it hits the second then the first will be nil so after compacting
40
+ # the result is always the first second element. Try it in irb to convince yourself
41
+ end
42
+ break
43
+ end
44
+ end
45
+ else
46
+ [source_tag_names,feature_tags.tags.map{|t| t.name}].each do | tags |
47
+ extracted_tag = tags.map{|tag| /#{tagname}/.match(tag)}.compact.first
48
+ if extracted_tag
49
+ result = true
50
+ break
51
+ end
52
+ end
53
+ end
54
+ result ||=ENV[tagname.upcase]
55
+ end
56
+
57
+ ##
58
+ # returns true if the tag @ignore_testrail or environment variable IGNORE_TESTRAIL=true are found
59
+ # OR the suite and project id cannot be found
60
+ def ignore_testrail?
61
+ testrail_tag('ignore_testrail') || !suite || !project
62
+ end
63
+ ##
64
+ # is this a manual scenario?
65
+ def is_manual?
66
+ testrail_tag('manual')
67
+ end
68
+ ##
69
+ # skip sending the result - useful for simply loading the testcases into Testrail
70
+ def skip_result?
71
+ testrail_tag('skip_result')
72
+ end
73
+ ##
74
+ # return the project id
75
+ def project
76
+ testrail_tag('project')
77
+ end
78
+
79
+ ##
80
+ # return the suite id
81
+ def suite
82
+ testrail_tag('suite')
83
+ end
84
+
85
+ ##
86
+ # return the sub_section id
87
+ def sub_section
88
+ testrail_tag('sub_section')
89
+ end
90
+
91
+ ##
92
+ # return the testcase id
93
+ def testcase
94
+ testrail_tag('testcase')
95
+ end
96
+
97
+ ##
98
+ # return the tesrun id
99
+ def testrun
100
+ testrail_tag('testrun')
101
+ end
102
+
103
+ ##
104
+ # return test results if it failed, ready to write into the testcase test result
105
+ def test_result
106
+ if status =~ /failed/
107
+ "#{status} #{exception} line #{backtrace_line}"[0..249]
108
+ end
109
+ end
110
+ ##
111
+ #return the jira ticket number if there is one
112
+ def jira
113
+ testrail_tag('jira')
114
+ end
115
+ ##
116
+ # translate the scenario status into a testrail id using the configured status map. This can be changed in your set up env.rb
117
+ # but by default it maps
118
+ # * passed to 1
119
+ # * failed to 5
120
+ # * undefined to 2
121
+ # * pending to 2
122
+ def testrail_status
123
+ self.class.get_testrail_configuration(:status_map)[status.to_s]
124
+ end
125
+
126
+ ##
127
+ # returns a hash of status_id and comment to write into Testrail test report
128
+ def testrail_test_report
129
+ {status_id:testrail_status,comment:test_result,defects:jira}
130
+ end
131
+
132
+ ##
133
+ # returns a hash of :title, :type_id and :custom_steps to create or update a Testrail testcase
134
+ # [TODO] parameterize the mapping of type_id to manual, in other installations it might be different
135
+ def testrail_testcase
136
+ {'title'=>title,'type_id'=>(is_manual? ? 7 : 1 ),'custom_steps'=>steps_as_string}
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,3 @@
1
+ module CucumberTestrail
2
+ VERSION = "1.3.0"
3
+ end
data/report.log ADDED
@@ -0,0 +1,2 @@
1
+ [webmock] Handling request: [get https://john.small%40bbc.com:314Sukra%21@paulokikiade.testrail.com/index.php?/api/v2/get_cases/1&suite_id=1] (disabled: false)
2
+ [webmock] Identified request type (unhandled) for [get https://john.small%40bbc.com:314Sukra%21@paulokikiade.testrail.com/index.php?/api/v2/get_cases/1&suite_id=1]
@@ -0,0 +1,15 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require "spec_helper"
4
+ require 'rspec'
5
+ require 'rspec/mocks'
6
+
7
+ describe Cucumber::Ast::Scenario do
8
+ # let(:world) do
9
+ # world = class_double('Funky')
10
+ # world.extend subject
11
+ # world
12
+ # end
13
+ #
14
+ end
15
+
@@ -0,0 +1,103 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require "spec_helper"
4
+ require 'rspec'
5
+ require 'rspec/mocks'
6
+ describe CucumberTestrail do
7
+ before(:each) do
8
+ ENV['TESTRAIL_BASE_URL']='https://paulokikiade.testrail.com'
9
+ ENV['TESTRAIL_USER'] ='test.rail@bbc.com'
10
+ ENV['TESTRAIL_PASSWORD']='a_password'
11
+ end
12
+
13
+ let(:world) do
14
+ world = class_double('Funky')
15
+ world.extend subject
16
+ world
17
+ end
18
+
19
+ describe "#testrail" do
20
+ it "should raise an exception if any of the environment variables are not set up" do
21
+ ['TESTRAIL_BASE_URL','TESTRAIL_USER','TESTRAIL_PASSWORD'].each do | var |
22
+ save_env = ENV[var]
23
+ begin
24
+ ENV[var] = nil
25
+ expect{world.testrail}.to raise_exception(RuntimeError,/#{var}/)
26
+ ensure
27
+ ENV[var] = save_env
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#send result" do
34
+ it 'should not send the result if scenario.ignore_testrail is true' do
35
+ scenario = instance_double('Scenario',ignore_testrail?: true)
36
+ expect(world.send_result(scenario)).to be_falsey
37
+ end
38
+ end
39
+
40
+ describe "#locate_a_test_case_by_name" do
41
+ it "doesn't find a phoney test" do
42
+ VCR.use_cassette('title_not_found') do
43
+ scenario = instance_double("Scenario",title:'A silly title',project:1,suite:1,sub_section:2)
44
+ expect(world.locate_a_test_case_by_name(scenario)).to be_nil
45
+ end
46
+ end
47
+
48
+ it "does find a real test" do
49
+ VCR.use_cassette('title_is_found') do
50
+ scenario = instance_double("Scenario",title:'Carousel - Test Case 1',project:1,suite:1,sub_section:2)
51
+ expect(world.locate_a_test_case_by_name(scenario)).not_to be_nil
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#create_a_test_case_from_scenario' do
57
+ let(:testrail_testcase) {{'title'=>'A test scenario','type_id'=>1,'custom_steps'=>'Given some steps'}}
58
+ let(:scenario) {instance_double("Scenario",suite:2,sub_section:2,testrail_testcase:testrail_testcase)}
59
+ it "should get a testcase id" do
60
+ VCR.use_cassette('create_a_test_case') do
61
+ expect(world.create_a_test_case_from_scenario(scenario)['id']).not_to be_nil
62
+ end
63
+ end
64
+
65
+ it "should put the steps into custom steps" do
66
+ VCR.use_cassette('create_a_test_case') do
67
+ expect(world.create_a_test_case_from_scenario(scenario)['custom_steps']).to eq('Given some steps')
68
+ end
69
+ end
70
+ it "should add the test case to the test suite" do
71
+ VCR.use_cassette('create_a_test_case') do
72
+ expect(world.create_a_test_case_from_scenario(scenario)['section_id']).to eq(2)
73
+ end
74
+ end
75
+ it "should set the testcase title" do
76
+ VCR.use_cassette('create_a_test_case') do
77
+ expect(world.create_a_test_case_from_scenario(scenario)['title']).to eq('A test scenario')
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#send_test_result_to_testrail' do
83
+ let(:testrail_test_report) { {status_id:1,defects:'none',comment:'comment'}}
84
+ let(:scenario) {instance_double("Scenario",testcase:10,testrun:5,testrail_test_report:testrail_test_report)}
85
+ it 'should return a valid test result' do
86
+ VCR.use_cassette('create_a_test_result') do
87
+ expect(world.send_test_result_to_testrail(scenario)['id']).not_to be_nil
88
+ end
89
+ end
90
+
91
+ it 'should set the defects' do
92
+ VCR.use_cassette('create_a_test_result') do
93
+ expect(world.send_test_result_to_testrail(scenario)['defects']).to eq('none')
94
+ end
95
+ end
96
+
97
+ it 'should set the comment' do
98
+ VCR.use_cassette('create_a_test_result') do
99
+ expect(world.send_test_result_to_testrail(scenario)['comment']).to eq('comment')
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,39 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://test.rail%40bbc.com:a_password@paulokikiade.testrail.com/index.php?/api/v2/add_case/2
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"title":"A test scenario","type_id":1,"custom_steps":"Given some steps"}'
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Ruby
16
+ Content-Type:
17
+ - application/json
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Date:
24
+ - Mon, 07 Jul 2014 09:51:06 GMT
25
+ Server:
26
+ - Apache
27
+ Content-Length:
28
+ - '322'
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Set-Cookie:
32
+ - gr_sid=2f527; path=/
33
+ body:
34
+ encoding: UTF-8
35
+ string: '{"id":13,"title":"A test scenario","section_id":2,"type_id":1,"priority_id":4,"milestone_id":null,"refs":null,"created_by":2,"created_on":1404726666,"updated_by":2,"updated_on":1404726666,"estimate":null,"estimate_forecast":null,"suite_id":2,"custom_preconds":null,"custom_steps":"Given
36
+ some steps","custom_expected":null}'
37
+ http_version:
38
+ recorded_at: Mon, 07 Jul 2014 09:51:11 GMT
39
+ recorded_with: VCR 2.9.2
@@ -0,0 +1,38 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://test.rail%40bbc.com:a_password@paulokikiade.testrail.com/index.php?/api/v2/add_result_for_case/5/10
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"status_id":1,"defects":"none","comment":"comment"}'
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Ruby
16
+ Content-Type:
17
+ - application/json
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Date:
24
+ - Mon, 07 Jul 2014 12:35:24 GMT
25
+ Server:
26
+ - Apache
27
+ Content-Length:
28
+ - '163'
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Set-Cookie:
32
+ - gr_sid=2f527; path=/
33
+ body:
34
+ encoding: UTF-8
35
+ string: '{"id":51,"test_id":25,"status_id":1,"created_by":2,"created_on":1404736525,"assignedto_id":null,"comment":"comment","version":null,"elapsed":null,"defects":"none"}'
36
+ http_version:
37
+ recorded_at: Mon, 07 Jul 2014 12:35:29 GMT
38
+ recorded_with: VCR 2.9.2
@@ -0,0 +1,45 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://test.rail%40bbc.com:a_password@paulokikiade.testrail.com/index.php?/api/v2/get_cases/1&section_id=2&suite_id=1
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Ruby
16
+ Content-Type:
17
+ - application/json
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Date:
24
+ - Mon, 07 Jul 2014 09:26:34 GMT
25
+ Server:
26
+ - Apache
27
+ Content-Length:
28
+ - '1057'
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Cache-Control:
32
+ - private
33
+ Set-Cookie:
34
+ - gr_sid=b7690; path=/
35
+ body:
36
+ encoding: UTF-8
37
+ string: '[{"id":1,"title":"Carousel - Test Case 1","section_id":1,"type_id":2,"priority_id":5,"milestone_id":null,"refs":null,"created_by":1,"created_on":1403122853,"updated_by":1,"updated_on":1403122853,"estimate":"2m","estimate_forecast":"2m","suite_id":1,"custom_preconds":"Ensure
38
+ hope page is present","custom_steps":"test homepage renders in 1 sec","custom_expected":"Homepage
39
+ renders in 1 sec"},{"id":2,"title":"Ensure test is tested","section_id":1,"type_id":6,"priority_id":4,"milestone_id":null,"refs":null,"created_by":1,"created_on":1403122907,"updated_by":1,"updated_on":1403122907,"estimate":null,"estimate_forecast":null,"suite_id":1,"custom_preconds":"test
40
+ test test","custom_steps":"test test test test","custom_expected":"test test"},{"id":3,"title":"3rd
41
+ test","section_id":1,"type_id":6,"priority_id":4,"milestone_id":null,"refs":null,"created_by":1,"created_on":1403122925,"updated_by":1,"updated_on":1403122925,"estimate":null,"estimate_forecast":null,"suite_id":1,"custom_preconds":"test
42
+ ","custom_steps":"test ","custom_expected":"test again"}]'
43
+ http_version:
44
+ recorded_at: Mon, 07 Jul 2014 09:26:39 GMT
45
+ recorded_with: VCR 2.9.2
@@ -0,0 +1,45 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://test.rail%40bbc.com:a_password@paulokikiade.testrail.com/index.php?/api/v2/get_cases/1&section_id=2&suite_id=1
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Ruby
16
+ Content-Type:
17
+ - application/json
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Date:
24
+ - Mon, 07 Jul 2014 09:21:56 GMT
25
+ Server:
26
+ - Apache/2.2.14 (Ubuntu)
27
+ Content-Length:
28
+ - '1057'
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Cache-Control:
32
+ - private
33
+ Set-Cookie:
34
+ - gr_sid=dba88; path=/
35
+ body:
36
+ encoding: UTF-8
37
+ string: '[{"id":1,"title":"Carousel - Test Case 1","section_id":1,"type_id":2,"priority_id":5,"milestone_id":null,"refs":null,"created_by":1,"created_on":1403122853,"updated_by":1,"updated_on":1403122853,"estimate":"2m","estimate_forecast":"2m","suite_id":1,"custom_preconds":"Ensure
38
+ hope page is present","custom_steps":"test homepage renders in 1 sec","custom_expected":"Homepage
39
+ renders in 1 sec"},{"id":2,"title":"Ensure test is tested","section_id":1,"type_id":6,"priority_id":4,"milestone_id":null,"refs":null,"created_by":1,"created_on":1403122907,"updated_by":1,"updated_on":1403122907,"estimate":null,"estimate_forecast":null,"suite_id":1,"custom_preconds":"test
40
+ test test","custom_steps":"test test test test","custom_expected":"test test"},{"id":3,"title":"3rd
41
+ test","section_id":1,"type_id":6,"priority_id":4,"milestone_id":null,"refs":null,"created_by":1,"created_on":1403122925,"updated_by":1,"updated_on":1403122925,"estimate":null,"estimate_forecast":null,"suite_id":1,"custom_preconds":"test
42
+ ","custom_steps":"test ","custom_expected":"test again"}]'
43
+ http_version:
44
+ recorded_at: Mon, 07 Jul 2014 09:22:01 GMT
45
+ recorded_with: VCR 2.9.2
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require "cucumber_testrail"
4
+ require 'vcr'
5
+ require 'rspec/mocks'
6
+
7
+ VCR.configure do |c|
8
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
9
+ c.hook_into :webmock # or :fakeweb
10
+ #c.debug_logger = File.open('report.log', 'w')
11
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,4 @@
1
+
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cucumber_testrail
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.0
5
+ platform: ruby
6
+ authors:
7
+ - John Small
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cucumber
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
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.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
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.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.18'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.18'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rdoc
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: geminabox
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.12'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.12'
125
+ description: |-
126
+ Call cucumber_testrail.send_result(scenario) in an After hook. It will match existing testcases by scenario title or add new testcases
127
+ and then add the result of the run to TestRail. See the README.md
128
+ email:
129
+ - john.small@bbc.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - ".gitignore"
135
+ - ".ruby-gemset"
136
+ - ".ruby-version"
137
+ - Gemfile
138
+ - LICENSE.txt
139
+ - README.md
140
+ - Rakefile
141
+ - cucumber_testrail.gemspec
142
+ - lib/cucumber_testrail.rb
143
+ - lib/cucumber_testrail/external_reporting.rb
144
+ - lib/cucumber_testrail/reopen_scenario.rb
145
+ - lib/cucumber_testrail/testrail.rb
146
+ - lib/cucumber_testrail/testrail_extensions.rb
147
+ - lib/cucumber_testrail/version.rb
148
+ - report.log
149
+ - spec/cucumber_ast_scenario_spec.rb
150
+ - spec/cucumber_testrail_spec.rb
151
+ - spec/fixtures/vcr_cassettes/create_a_test_case.yml
152
+ - spec/fixtures/vcr_cassettes/create_a_test_result.yml
153
+ - spec/fixtures/vcr_cassettes/title_is_found.yml
154
+ - spec/fixtures/vcr_cassettes/title_not_found.yml
155
+ - spec/spec_helper.rb
156
+ - tasks/rspec.rake
157
+ homepage:
158
+ licenses:
159
+ - MIT
160
+ metadata: {}
161
+ post_install_message:
162
+ rdoc_options: []
163
+ require_paths:
164
+ - lib
165
+ required_ruby_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ required_rubygems_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ requirements: []
176
+ rubyforge_project:
177
+ rubygems_version: 2.2.2
178
+ signing_key:
179
+ specification_version: 4
180
+ summary: Sync Cucumber specs and results to TestRail
181
+ test_files:
182
+ - spec/cucumber_ast_scenario_spec.rb
183
+ - spec/cucumber_testrail_spec.rb
184
+ - spec/fixtures/vcr_cassettes/create_a_test_case.yml
185
+ - spec/fixtures/vcr_cassettes/create_a_test_result.yml
186
+ - spec/fixtures/vcr_cassettes/title_is_found.yml
187
+ - spec/fixtures/vcr_cassettes/title_not_found.yml
188
+ - spec/spec_helper.rb