Class: Cukerail::Sender

Inherits:
Object
  • Object
show all
Defined in:
lib/cukerail.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(runtime, io, options) ⇒ Sender

Returns a new instance of Sender.



10
11
12
13
14
15
# File 'lib/cukerail.rb', line 10

def initialize(runtime, io, options)
  if %w(BASE_URL USER PASSWORD).map{|e| ENV["TESTRAIL_#{e}"]}.any?{|e| e=='' || !e}
    raise 'You need to setup Testrail environment parameters see https://bbcworldwide.atlassian.net/wiki/display/BAR/Installing+and+Running+Cukerail' 
  end
  @testrail_api_client = TestRail::APIClient.new(ENV['TESTRAIL_BASE_URL'],ENV['TESTRAIL_USER'],ENV['TESTRAIL_PASSWORD'],ENV['TESTRAIL_PROXY_URL'],ENV['TESTRAIL_PROXY_PORT'])
end

Instance Attribute Details

#failed_stepObject (readonly)

Returns the value of attribute failed_step.



9
10
11
# File 'lib/cukerail.rb', line 9

def failed_step
  @failed_step
end

#testrail_api_clientObject (readonly)

Returns the value of attribute testrail_api_client.



9
10
11
# File 'lib/cukerail.rb', line 9

def testrail_api_client
  @testrail_api_client
end

Instance Method Details

#add_case_to_test_run(testcase_id, run_id) ⇒ Object



244
245
246
247
248
249
250
# File 'lib/cukerail.rb', line 244

def add_case_to_test_run(testcase_id,run_id)
  run = get_run(run_id)
  unless run['include_all']
    case_ids = get_tests_in_a_run(run_id).map{|h| h['case_id']} + [testcase_id]
    update_run(run_id,{'case_ids'=>case_ids})
  end
end

#after_test_case(test_case, result) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/cukerail.rb', line 17

def after_test_case(test_case,result)
  #guard clause
  return false unless test_case.tags.any?{|tag| tag.name =~/project/} && test_case.tags.any?{|tag| tag.name=~/suite/} 
  feature = test_case.feature
  #      ap feature.methods - Object.methods
  #      ap feature.tags
  @id = get_id(test_case)
  raise 'No id found' unless @id
  send_steps(test_case,@id)
  if ENV['UPDATE_SOURCE'] && !test_case.source.any?{|h| h.is_a?(Cucumber::Core::Ast::ScenarioOutline)}
    update_source_file(test_case,@id)
  end
  if ENV['TESTRUN']
    send_result(test_case,result,@id,ENV['TESTRUN'].to_i) 
  end
rescue StandardError => e
  puts "#{e.message} in #{extract_title(test_case)}"
end

#after_test_step(step, result) ⇒ Object



45
46
47
48
49
50
51
# File 'lib/cukerail.rb', line 45

def after_test_step(step,result)
  unless result.passed?
    # only the first non-passed step
    failed_step[:step]   ||= step
    failed_step[:result] ||= result
  end
end

#all_tags(test_case) ⇒ Object



180
181
182
# File 'lib/cukerail.rb', line 180

def all_tags(test_case)
  test_case.tags + test_case.feature.tags
end

#before_test_case(*args) ⇒ Object



53
54
55
56
57
# File 'lib/cukerail.rb', line 53

def before_test_case(*args)
  @test_steps =[]
  @scenario_tags = []
  @failed_step = {}
end

#create_new_case(project_id, suite_id, sub_section_id, test_case) ⇒ Object



108
109
110
# File 'lib/cukerail.rb', line 108

def create_new_case(project_id,suite_id,sub_section_id,test_case)
  testrail_api_client.send_post("add_case/#{sub_section_id || suite_id}",test_case_data(test_case))
end

#defects(test_case) ⇒ Object



184
185
186
# File 'lib/cukerail.rb', line 184

def defects(test_case)
  all_tags(test_case).select{|tag| tag.name =~/(?:jira|defect)_/}.map{|ticket| /(?:jira|defect)_(\w+-\d+)$/.match(ticket.name)[1]}.uniq.join(",")
end

#extract_title(test_case) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/cukerail.rb', line 157

def extract_title(test_case)
  requirements_tags = all_tags(test_case).select{|tag| tag.name =~ /req_\w+/}.map{|tag| /req_(\w+)/.match(tag.name)[1]}.join(', ')
  if test_case.source.last.is_a?(Cucumber::Core::Ast::ExamplesTable::Row)
    title  = test_case.source.select{|s| s.is_a?(Cucumber::Core::Ast::ScenarioOutline)}.first.name
    if ENV['OLD_STYLE_OUTLINE_NAMES'] 
      title += ' :: ' + test_case.source.last.send(:data).map{|key,value| "#{key}='#{value}'"}.join(', ')
    else
      title += " " + test_case.source.last.send(:data).map{|key,value| "#{key}=#{value}"}.join(', ')
    end
  else
    title = test_case.source.last.name
  end
  [requirements_tags,title].compact.join(' ').strip
end

#get_id(test_case) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/cukerail.rb', line 59

def get_id(test_case)
  #      ap test_case.methods - Object.methods
  tagged_id = test_case.tags.detect{|tag| tag.name =~/testcase/}
  if tagged_id
    result = /testcase_(\d+)/.match(tagged_id.name)[1]
  else
    tags = test_case.tags
    project_id = /\d+/.match(tags.select{|tag| tag.name =~/project/}.last.name)[0] 
    suite_id = /\d+/.match(tags.select{|tag| tag.name =~/suite/}.last.name)[0] 
    section_id = /\d+/.match(tags.select{|tag| tag.name =~/sub_section/}.last.name)[0] 
    title = extract_title(test_case)
    found_case = testrail_api_client.send_get("get_cases/#{project_id}&suite_id=#{suite_id}&section_id=#{section_id}").select{|c| c['title'] == title}.first
    if found_case
      result= found_case['id']
    else
      result = create_new_case(project_id,suite_id,section_id,test_case)['id']
    end
  end
  return result
end

#get_run(run_id) ⇒ Object



172
173
174
# File 'lib/cukerail.rb', line 172

def get_run(run_id)
  testrail_api_client.send_get("get_run/#{run_id}")
end

#get_tests_in_a_run(run_id) ⇒ Object



176
177
178
# File 'lib/cukerail.rb', line 176

def get_tests_in_a_run(run_id)
  testrail_api_client.send_get("get_tests/#{run_id}")
end

#refs(test_case) ⇒ Object



188
189
190
# File 'lib/cukerail.rb', line 188

def refs(test_case)
  all_tags(test_case).select{|tag| tag.name =~/(?:jira|ref)_/}.map{|ticket| /(?:jira|ref)_(\w+-\d+)$/.match(ticket.name)[1]}.uniq.join(",")
end

#remove_case_from_test_run(testcase, run_id) ⇒ Object



235
236
237
238
239
240
241
242
# File 'lib/cukerail.rb', line 235

def remove_case_from_test_run(testcase,run_id)
  testcase_id = get_id(testcase)
  run = get_run(run_id)
  unless run['include_all']
    case_ids = get_tests_in_a_run(run_id).map{|h| h['case_id']} - [testcase_id]
    update_run(run_id,{'case_ids'=>case_ids})
  end
end

#send_result(test_case, result, id, testrun) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/cukerail.rb', line 112

def send_result(test_case,result,id,testrun)
  testrail_status = case 
                    when result.passed?
                      {id:1,comment: 'passed'}
                    when result.failed?
                      {id:5,comment: 'failed'}
                    when result.undefined?
                      {id:7,comment: 'undefined step'}
                    when result.pending?
                      {id:6,comment: 'pending step'}
                    end
  unless result.passed?
    # the before step can fail. So we have to check
    if @failed_step[:step].source.last.is_a?(Cucumber::Hooks::BeforeHook)
      failed_step = 'failed in the before hook'
      location = 'before hook'
    elsif @failed_step[:step].source.last.is_a?(Cucumber::Hooks::AfterHook)
      failed_step = 'failed in the after hook'
      location = 'after hook'
    else
      failed_step = "#{@failed_step[:step].source.last.keyword}#{@failed_step[:step].source.last.name}"
      location=@failed_step[:step].source.last.file_colon_line
    end
    failure_message = "    Error message: \#{testrail_status[:comment]} \#{result.exception.message}\n    Step: \#{failed_step}\n    Location: \#{location}\n    FAILURE\n  else\n    failure_message = nil\n  end\n  #only send defects if the test is not passed\n  report_on_result =  {status_id:testrail_status[:id],comment:failure_message,defects:testrail_status[:id]==1 ? '' : defects(test_case)}\n  begin\n    testrail_api_client.send_post(\"add_result_for_case/\#{testrun}/\#{id}\",report_on_result)\n  rescue => e\n    if e.message =~ /No \\(active\\) test found for the run\\/case combination/\n      add_case_to_test_run(id,testrun)\n      retry\n    else\n      puts \"\#{e.message} testrun=\#{testrun} test case id=\#{id}\"\n    end\n  end\nend\n"

#send_steps(test_case, id) ⇒ Object



104
105
106
# File 'lib/cukerail.rb', line 104

def send_steps(test_case,id)
  testrail_api_client.send_post("update_case/#{id}",test_case_data(test_case))
end

#step_name(keyword, step_match, status, source_indent, background, file_colon_line) ⇒ Object



40
41
42
43
# File 'lib/cukerail.rb', line 40

def step_name(keyword, step_match, status, source_indent, background, file_colon_line)
  step_name = step_match.format_args(lambda{|param| "*#{param}*"})
  @test_steps << "#{keyword}#{step_name}"
end

#tag_name(tag_name) ⇒ Object



36
37
38
# File 'lib/cukerail.rb', line 36

def tag_name(tag_name)
  @scenario_tags << tag_name
end

#test_case_data(test_case) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/cukerail.rb', line 200

def test_case_data(test_case)
  steps_as_string = test_case.test_steps.map{|step| step.source.last}
  .select{|step| step.is_a?(Cucumber::Core::Ast::Step)}
  .reject{|step| step.is_a?(Cucumber::Hooks::BeforeHook)}.map do | g_step |
    str = g_step.send(:keyword)+g_step.send(:name)
    str += g_step.multiline_arg.raw.map{|l|"\n| #{l.join(' | ')} |"}.join if g_step.multiline_arg.data_table?
    str
  end.join("\n")

  {'title'=>extract_title(test_case),
          'type_id'=>type_id(test_case),
          'custom_steps'=>steps_as_string,
          'refs'=>refs(test_case)
  }
end

#type_id(test_case) ⇒ Object



192
193
194
195
196
197
198
# File 'lib/cukerail.rb', line 192

def type_id(test_case)
  type_ids = [1]
  type_ids << 7  if test_case.tags.any?{|tag| tag.name =~/manual/}
  type_ids << 13 if test_case.tags.any?{|tag| tag.name =~/on_hold/}
  #get the highest precedence type found in the tags. E.g. if it's @on_hold and @manual it selects 13 for on hold
  ([13,7,1] & type_ids).first
end

#update_plan(plan_id, run_id, case_ids) ⇒ Object



229
230
231
232
233
# File 'lib/cukerail.rb', line 229

def update_plan(plan_id,run_id,case_ids)
  test_plan = testrail_api_client.send_get("get_plan/#{plan_id}")
  entry_id = test_plan['entries'].detect{|e| e['runs'].any?{|r| r['id']==run_id}}['id']
  testrail_api_client.send_post("update_plan_entry/#{plan_id}/#{entry_id}",case_ids)
end

#update_run(run_id, case_ids) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/cukerail.rb', line 216

def update_run(run_id,case_ids)
  run = get_run(run_id)
  begin
    if run['plan_id']
      update_plan(run['plan_id'],run_id,case_ids)
    else
      testrail_api_client.send_post("update_run/#{run_id}",case_ids)
    end
  rescue => e
    puts "#{e.message} testrun=#{run_id} test case ids=#{case_ids}"
  end
end

#update_source_file(scenario, external_reference) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/cukerail.rb', line 80

def update_source_file(scenario, external_reference)
  #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
  # byebug
  path = scenario.location.file
  # ap path
  lines = IO.readlines(path)
  lines[scenario.location.line-2].gsub!(/ @testcase_\d*/," @testcase_#{external_reference}") 
  lines[scenario.location.line-2].gsub!(/\n/," @testcase_#{external_reference}") unless lines[scenario.location.line-2] =~ /testcase/ 
  temp_file = Tempfile.new('foo')
  begin
    File.open(path, 'r') do |file|
      lines.each do |line|
        # puts line
        temp_file.puts line
      end
    end
    temp_file.close
    FileUtils.mv(temp_file.path, path)
  ensure
    temp_file.close
    temp_file.unlink
  end
end