closer 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b625623a1f35935806a9bb599580458493ae5e4548bc2bcd58b4a3c47df3a87f
4
- data.tar.gz: '09d8fcb96c6119cdb1f3f4e68c373de923ec61d30325b2dab3fad93f002c5b66'
3
+ metadata.gz: c5c8127b7871c95d6b79f5be5a9034bdc0275f759b7e6a8111797cc836ac0d1a
4
+ data.tar.gz: af47e23f5c6c6e328c61b9fc914692d1e0d7837ac01ed425e5e22944090cc94d
5
5
  SHA512:
6
- metadata.gz: d5088995d29993749ad54ff89cb70ff6ec292a13b8699d225dae6f2474cbac794856892db4a65e5b085dd0e450dc49e68d48ac2508ead691d0dc56220e8d2b6e
7
- data.tar.gz: 8be384eae2da7cad8ebc3f6edcccf27d0b1f43781e2ffde36729265a6d56cc02ecc2e990c53d03ac8003d42abf099bc790fa29bdeea4d8f6dd9d95dbdf9119d0
6
+ metadata.gz: 1b0f79b3cb25a32c990d3c62d56cd058323f7f59071c013ef817ac0f4798392738ced226987916fcb7f9c2dae62229ef14a9bca2dd84923e7167422ab7450752
7
+ data.tar.gz: 83530d41db510502c5201018ebd9c369759d4c5410dda5e2c552e924df6a2820ba3f2a0fce77b803e4016d6bd13d60c29614f9e59bbcf9ff79e03c99eabe585e
data/HISTORY.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.13.0
2
+ Release Date: 2024-11-10
3
+ * add support for cucumber-4
4
+ * drop support for cucumber-3
5
+
1
6
  # 0.11.0
2
7
  Release Date: 2023-09-01
3
8
 
data/Rakefile CHANGED
@@ -1,2 +1 @@
1
1
  require "bundler/gem_tasks"
2
-
data/closer.gemspec CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.required_ruby_version = '~> 2.7'
21
21
 
22
- spec.add_runtime_dependency 'cucumber', '>= 3.0', '< 9.0'
23
- spec.add_runtime_dependency 'capybara', '>= 3.0'
24
- spec.add_runtime_dependency 'selenium-webdriver', '>= 4.8', '<= 5.0'
22
+ spec.add_dependency 'cucumber', '>= 4.0', '< 9.0'
23
+ spec.add_dependency 'capybara', '>= 3.0'
24
+ spec.add_dependency 'selenium-webdriver', '>= 4.8', '<= 5.0'
25
25
 
26
26
  spec.add_development_dependency 'minitest', '~> 5.10'
27
27
  spec.add_development_dependency 'rake', '~> 13.0'
@@ -3,11 +3,11 @@ require 'simplecov-rcov'
3
3
  module Closer
4
4
  module Coverage
5
5
  class RcovFormatter < SimpleCov::Formatter::RcovFormatter
6
-
6
+
7
7
  private
8
-
8
+
9
9
  def write_file(template, output_filename, binding)
10
- rcov_result = template.result( binding ).force_encoding('UTF-8')
10
+ rcov_result = template.result(binding).force_encoding('UTF-8')
11
11
 
12
12
  File.open( output_filename, "w" ) do |file_result|
13
13
  file_result.write rcov_result
@@ -1,620 +1,14 @@
1
- require 'erb'
2
- require 'cucumber/formatter/duration'
3
- require 'cucumber/formatter/io'
4
- require 'cucumber/core/report/summary'
5
- require 'cucumber/core/test/result'
6
- require 'pathname'
7
- require_relative 'html_builder'
8
- require_relative 'closer_html'
1
+ require 'cucumber/formatter/html'
9
2
 
10
3
  module Closer
11
4
  module Formatter
12
- class Html
13
- include CloserHtml
5
+ class HTML < ::Cucumber::Formatter::HTML
14
6
 
15
- # TODO: remove coupling to types
16
- AST_CLASSES = {
17
- Cucumber::Core::Ast::Scenario => 'scenario',
18
- Cucumber::Core::Ast::ScenarioOutline => 'scenario outline'
19
- }
20
-
21
- AST_DATA_TABLE = ::Cucumber::Formatter::LegacyApi::Ast::MultilineArg::DataTable
22
-
23
- include ERB::Util # for the #h method
24
- include ::Cucumber::Formatter::Duration
25
- include ::Cucumber::Formatter::Io
26
-
27
- attr_reader :builder
28
- private :builder
29
-
30
- def initialize(runtime, path_or_io, options)
31
- @io = ensure_io(path_or_io)
32
- @runtime = runtime
33
- @options = options
34
- @buffer = {}
35
- @builder = HtmlBuilder.new(target: @io, indent: 0)
36
- @feature_number = 0
37
- @scenario_number = 0
38
- @step_number = 0
39
- @header_red = nil
40
- @delayed_messages = []
41
- @inside_outline = false
42
- @previous_step_keyword = nil
43
- @summary = ::Cucumber::Core::Report::Summary.new(runtime.configuration.event_bus)
44
- end
45
-
46
- def before_features(features)
47
- @step_count = features && features.step_count || 0 #TODO: Make this work with core!
48
-
49
- builder.build_document!
50
- builder.format_features! features
51
- end
52
-
53
- def after_features(features)
54
- print_stats(features)
55
- builder << '</div>'
56
- builder << '</body>'
57
- builder << '</html>'
58
- end
59
-
60
- def before_feature(_feature)
61
- dir = feature_dir(_feature)
62
- unless dir.empty?
63
- if @feature_dir != dir
64
- builder << '<div class="feature_dir"><span class="val" onclick="toggle_feature_dir(this);">'
65
- builder << dir
66
- builder << '</span></div>'
67
- end
68
-
69
- @feature_dir = dir
70
- end
71
-
72
- @feature = _feature
73
- @exceptions = []
74
- builder << "<div id=\"#{feature_id}\" class=\"feature\">"
75
- end
76
-
77
- def after_feature(_feature)
78
- builder << '</div>'
79
- end
80
-
81
- def before_comment(_comment)
82
- return if magic_comment?(_comment)
83
-
84
- builder << '<pre class="comment">'
85
- end
86
-
87
- def after_comment(_comment)
88
- return if magic_comment?(_comment)
89
-
90
- builder << '</pre>'
91
- end
92
-
93
- def comment_line(comment_line)
94
- return if magic_comment?(comment_line)
95
-
96
- builder.text!(comment_line)
97
- builder.br
98
- end
99
-
100
- def after_tags(_tags)
101
- @tag_spacer = nil
102
- end
103
-
104
- def tag_name(tag_name)
105
- builder.text!(@tag_spacer) if @tag_spacer
106
- @tag_spacer = ' '
107
- builder.span(tag_name, :class => 'tag')
108
- end
109
-
110
- def feature_name(keyword, name)
111
- title = feature_dir(@feature, true) + @feature.file.split('/').last.gsub(/\.feature/, '')
112
- lines = name.split(/\r?\n/)
113
- return if lines.empty?
114
- builder.h2 do |h2|
115
- builder.span(:class => 'val') do
116
- builder << title
117
- end
118
- end
119
-
120
- if lines.size > 1
121
- builder.div(:class => 'narrative') do
122
- builder << lines[1..-1].join("\n")
123
- end
124
- end
125
- end
126
-
127
- def before_test_case(_test_case)
128
- @previous_step_keyword = nil
129
- end
130
-
131
- def before_background(_background)
132
- @in_background = true
133
- builder << '<div class="background">'
134
- end
135
-
136
- def after_background(_background)
137
- @in_background = nil
138
- builder << '</div>'
7
+ def output_envelope(envelope)
8
+ @html_formatter.write_message(envelope)
9
+ @html_formatter.write_post_message if envelope.test_run_finished
139
10
  end
140
11
 
141
- def background_name(keyword, name, _file_colon_line, _source_indent)
142
- @listing_background = true
143
- builder.h3(:id => "background_#{@scenario_number}") do |h3|
144
- builder.span(keyword, :class => 'keyword')
145
- builder.text!(' ')
146
- builder.span(name, :class => 'val')
147
- end
148
- end
149
-
150
- def before_feature_element(feature_element)
151
- @scenario_number+=1
152
- @scenario_red = false
153
- css_class = AST_CLASSES[feature_element.class]
154
- builder << "<div class='#{css_class}'>"
155
- @in_scenario_outline = feature_element.class == Cucumber::Core::Ast::ScenarioOutline
156
- end
157
-
158
- def after_feature_element(_feature_element)
159
- unless @in_scenario_outline
160
- print_messages
161
- builder << '</ol>'
162
- end
163
- builder << '</div>'
164
- @in_scenario_outline = nil
165
- end
166
-
167
- def scenario_name(keyword, name, file_colon_line, _source_indent)
168
- builder.span(:class => 'scenario_file hidden') do
169
- builder << file_colon_line
170
- end
171
- @listing_background = false
172
- scenario_id = "scenario_#{@scenario_number}"
173
- if @inside_outline
174
- @outline_row += 1
175
- scenario_id += "_#{@outline_row}"
176
- @scenario_red = false
177
- end
178
-
179
- lines = name.split("\n")
180
- title = lines.shift
181
- builder.h3(:id => scenario_id) do
182
- builder.span(title, :class => 'val pointer')
183
- end
184
-
185
- if lines.size > 0
186
- builder.pre(:class => 'narrative hidden') do
187
- trim_size = indent_size(lines.first)
188
- builder << lines.map{|line| line[trim_size..-1] }.join("\n")
189
- end
190
- end
191
- end
192
-
193
- def before_outline_table(_outline_table)
194
- @inside_outline = true
195
- @outline_row = 0
196
- builder << '<table>'
197
- end
198
-
199
- def after_outline_table(_outline_table)
200
- builder << '</table>'
201
- @outline_row = nil
202
- @inside_outline = false
203
- end
204
-
205
- def before_examples(_examples)
206
- builder << '<div class="examples">'
207
- end
208
-
209
- def after_examples(_examples)
210
- builder << '</div>'
211
- end
212
-
213
- def examples_name(keyword, name)
214
- builder.h4 do
215
- builder.span(keyword, :class => 'keyword')
216
- builder.text!(' ')
217
- builder.span(name, :class => 'val')
218
- end
219
- end
220
-
221
- def before_steps(_steps)
222
- builder << '<ol class="hidden">'
223
- end
224
-
225
- def after_steps(_steps)
226
- print_messages
227
- builder << '</ol>' if @in_background || @in_scenario_outline
228
- end
229
-
230
- def before_step(step)
231
- print_messages
232
- @step_id = step.dom_id
233
- @step_number += 1
234
- @step = step
235
- end
236
-
237
- def after_step(_step)
238
- end
239
-
240
- def before_step_result(_keyword, step_match, _multiline_arg, status, exception, _source_indent, background, _file_colon_line)
241
- @step_match = step_match
242
- @hide_this_step = false
243
- if exception
244
- if @exceptions.include?(exception)
245
- @hide_this_step = true
246
- return
247
- end
248
- @exceptions << exception
249
- end
250
- if status != :failed && @in_background ^ background
251
- @hide_this_step = true
252
- return
253
- end
254
- @status = status
255
- return if @hide_this_step
256
- set_scenario_color(status)
257
-
258
- if ! @delayed_messages.empty? and status == :passed
259
- builder << "<li id='#{@step_id}' class='step #{status} expand'>"
260
- else
261
- builder << "<li id='#{@step_id}' class='step #{status}'>"
262
- end
263
- end
264
-
265
- def after_step_result(keyword, step_match, _multiline_arg, status, _exception, _source_indent, _background, _file_colon_line)
266
- return if @hide_this_step
267
- # print snippet for undefined steps
268
- unless outline_step?(@step)
269
- keyword = @step.actual_keyword(@previous_step_keyword)
270
- @previous_step_keyword = keyword
271
- end
272
- if status == :undefined
273
- builder.pre do |pre|
274
- # TODO: snippet text should be an event sent to the formatter so we don't
275
- # have this couping to the runtime.
276
- pre << @runtime.snippet_text(keyword,step_match.instance_variable_get('@name') || '', @step.multiline_arg)
277
- end
278
- end
279
- builder << '</li>'
280
-
281
- unless status == :undefined
282
- step_file = step_match.file_colon_line
283
- step_contents = "<div class=\"step_contents hidden\"><pre>"
284
- step_file.gsub(/^([^:]*\.rb):(\d*)/) do
285
- line_index = $2.to_i - 1
286
-
287
- file = $1.force_encoding('UTF-8')
288
- file = File.join(gem_dir, file) unless File.exist?(file)
289
- File.readlines(file)[line_index..-1].each do |line|
290
- step_contents << line
291
- break if line.chop == 'end' or line.chop.start_with?('end ')
292
- end
293
- end
294
- step_contents << "</pre></div>"
295
- builder << step_contents
296
- end
297
-
298
- print_messages
299
- end
300
-
301
- def step_name(keyword, step_match, status, _source_indent, background, _file_colon_line)
302
- background_in_scenario = background && !@listing_background
303
- @skip_step = background_in_scenario
304
-
305
- unless @skip_step
306
- build_step(keyword, step_match, status)
307
- end
308
- end
309
-
310
- def exception(exception, _status)
311
- return if @hide_this_step
312
- print_messages
313
- build_exception_detail(exception)
314
- end
315
-
316
- def extra_failure_content(file_colon_line)
317
- @snippet_extractor ||= SnippetExtractor.new
318
- "<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(file_colon_line)}</code></pre>"
319
- end
320
-
321
- def before_multiline_arg(multiline_arg)
322
- return if @hide_this_step || @skip_step
323
- if AST_DATA_TABLE === multiline_arg
324
- builder << '<table>'
325
- end
326
- end
327
-
328
- def after_multiline_arg(multiline_arg)
329
- return if @hide_this_step || @skip_step
330
- if AST_DATA_TABLE === multiline_arg
331
- builder << '</table>'
332
- end
333
- end
334
-
335
- def doc_string(string)
336
- return if @hide_this_step
337
- builder.pre(:class => 'val') do |pre|
338
- builder << h(string).gsub("\n", '&#x000A;')
339
- end
340
- end
341
-
342
- def before_table_row(table_row)
343
- @row_id = table_row.dom_id
344
- @col_index = 0
345
- return if @hide_this_step
346
- builder << "<tr class='step' id='#{@row_id}'>"
347
- end
348
-
349
- def after_table_row(table_row)
350
- return if @hide_this_step
351
- print_table_row_messages
352
- builder << '</tr>'
353
- if table_row.exception
354
- builder.tr do
355
- builder.td(:colspan => @col_index.to_s, :class => 'failed') do
356
- builder.pre do |pre|
357
- pre << h(format_exception(table_row.exception))
358
- end
359
- end
360
- end
361
- if table_row.exception.is_a? ::Cucumber::Pending
362
- set_scenario_color_pending
363
- else
364
- set_scenario_color_failed
365
- end
366
- end
367
- if @outline_row
368
- @outline_row += 1
369
- end
370
- @step_number += 1
371
- end
372
-
373
- def table_cell_value(value, status)
374
- return if @hide_this_step
375
-
376
- @cell_type = @outline_row == 0 ? :th : :td
377
- attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'step'}
378
- attributes[:class] += " #{status}" if status
379
- build_cell(@cell_type, value, attributes)
380
- set_scenario_color(status) if @inside_outline
381
- @col_index += 1
382
- end
383
-
384
- def puts(message)
385
- @delayed_messages << message
386
- #builder.pre(message, :class => 'message')
387
- end
388
-
389
- def print_messages
390
- return if @delayed_messages.empty?
391
-
392
- #builder.ol do
393
- @delayed_messages.each do |ann|
394
- builder.li(:class => 'message hidden') do
395
- builder << ann
396
- end
397
- end
398
- #end
399
- empty_messages
400
- end
401
-
402
- def print_table_row_messages
403
- return if @delayed_messages.empty?
404
-
405
- builder.td(:class => 'message') do
406
- builder << @delayed_messages.join(', ')
407
- end
408
- empty_messages
409
- end
410
-
411
- def empty_messages
412
- @delayed_messages = []
413
- end
414
-
415
- def after_test_case(_test_case, result)
416
- if result.failed? && !@scenario_red
417
- set_scenario_color_failed
418
- end
419
- end
420
-
421
- protected
422
-
423
- def build_exception_detail(exception)
424
- backtrace = Array.new
425
-
426
- builder.div(:class => 'message') do
427
- message = exception.message
428
-
429
- if defined?(RAILS_ROOT) && message.include?('Exception caught')
430
- matches = message.match(/Showing <i>(.+)<\/i>(?:.+) #(\d+)/)
431
- backtrace += ["#{RAILS_ROOT}/#{matches[1]}:#{matches[2]}"] if matches
432
- matches = message.match(/<code>([^(\/)]+)<\//m)
433
- message = matches ? matches[1] : ''
434
- end
435
-
436
- unless exception.instance_of?(RuntimeError)
437
- message = "#{message} (#{exception.class})"
438
- end
439
-
440
- builder.pre do
441
- builder.text!(message)
442
- end
443
- end
444
-
445
- builder.div(:class => 'backtrace') do
446
- builder.pre do
447
- backtrace = exception.backtrace
448
- backtrace.delete_if { |x| x =~ /\/gems\/(cucumber|rspec)/ }
449
- builder << backtrace_line(backtrace.join("\n"))
450
- end
451
- end
452
-
453
- extra = extra_failure_content(backtrace)
454
- builder << extra unless extra == ''
455
- end
456
-
457
- def set_scenario_color(status)
458
- if status.nil? || status == :undefined || status == :pending
459
- set_scenario_color_pending
460
- end
461
- if status == :failed
462
- set_scenario_color_failed
463
- end
464
- end
465
-
466
- def set_scenario_color_failed
467
- builder.script do
468
- builder.text!("makeRed('cucumber-header');") unless @header_red
469
- @header_red = true
470
- scenario_or_background = @in_background ? 'background' : 'scenario'
471
- builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
472
- @scenario_red = true
473
- if @options[:expand] && @inside_outline
474
- builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}_#{@outline_row}');")
475
- end
476
- end
477
- end
478
-
479
- def set_scenario_color_pending
480
- builder.script do
481
- builder.text!("makeYellow('cucumber-header');") unless @header_red
482
- scenario_or_background = @in_background ? 'background' : 'scenario'
483
- builder.text!("makeYellow('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
484
- end
485
- end
486
-
487
- def build_step(keyword, step_match, _status)
488
- keyword = display_keyword(keyword)
489
- step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
490
- builder.div(:class => 'step_name') do |div|
491
- builder.span(keyword, :class => 'keyword')
492
- builder.span(:class => 'step val') do |name|
493
- name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
494
- end
495
- end
496
-
497
- step_file = step_match.file_colon_line
498
- step_file.gsub(/^([^:]*\.rb):(\d*)/) do
499
- step_file = "<span class=\"pointer\" onclick=\"toggle_step_file(this); return false;\">#{step_file}</span>"
500
-
501
- builder.div(:class => 'step_file') do |div|
502
- builder.span do
503
- builder << step_file
504
- end
505
- end
506
- end
507
- end
508
-
509
- def build_cell(cell_type, value, attributes)
510
- builder.__send__(cell_type, attributes) do
511
- builder.div do
512
- builder.span(value,:class => 'step param')
513
- end
514
- end
515
- end
516
-
517
- def format_exception(exception)
518
- ([exception.message.to_s] + exception.backtrace).join("\n")
519
- end
520
-
521
- def backtrace_line(line)
522
- line.gsub(/^([^:]*\.(?:rb|feature|haml)):(\d*).*$/) do
523
- line
524
- end
525
- end
526
-
527
- def print_stats(features)
528
- builder << "<script>document.getElementById('duration').innerHTML = \"Finished in <strong>#{format_duration(features.duration)} seconds</strong>\";</script>"
529
- builder << "<script>document.getElementById('totals').innerHTML = \"#{print_stat_string(features)}\";</script>"
530
- end
531
-
532
- def print_stat_string(_features)
533
- string = String.new
534
- string << dump_count(@summary.test_cases.total, 'scenario')
535
- scenario_count = status_counts(@summary.test_cases)
536
- string << scenario_count if scenario_count
537
- string << '<br />'
538
- string << dump_count(@summary.test_steps.total, 'step')
539
- step_count = status_counts(@summary.test_steps)
540
- string << step_count if step_count
541
- end
542
-
543
- def status_counts(summary)
544
- counts = ::Cucumber::Core::Test::Result::TYPES.map { |status|
545
- count = summary.total(status)
546
- [status, count]
547
- }.select { |status, count|
548
- count > 0
549
- }.map { |status, count|
550
- "#{count} #{status}"
551
- }
552
- "(#{counts.join(", ")})" if counts.any?
553
- end
554
-
555
- def dump_count(count, what, state=nil)
556
- [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(' ')
557
- end
558
-
559
- def outline_step?(_step)
560
- not @step.step.respond_to?(:actual_keyword)
561
- end
562
-
563
- class SnippetExtractor #:nodoc:
564
- class NullConverter; def convert(code, _pre); code; end; end #:nodoc:
565
-
566
- begin
567
- require 'syntax/convertors/html'
568
- @@converter = Syntax::Convertors::HTML.for_syntax 'ruby'
569
- rescue LoadError
570
- @@converter = NullConverter.new
571
- end
572
-
573
- def snippet(error)
574
- raw_code, line = snippet_for(error[0])
575
- highlighted = @@converter.convert(raw_code, false)
576
- highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
577
- post_process(highlighted, line)
578
- end
579
-
580
- def snippet_for(error_line)
581
- if error_line =~ /(.*):(\d+)/
582
- file = $1
583
- line = $2.to_i
584
- [lines_around(file, line), line]
585
- else
586
- ["# Couldn't get snippet for #{error_line}", 1]
587
- end
588
- end
589
-
590
- def lines_around(file, line)
591
- if File.file?(file)
592
- begin
593
- lines = File.open(file).read.split("\n")
594
- rescue ArgumentError
595
- return "# Couldn't get snippet for #{file}"
596
- end
597
- min = [0, line-3].max
598
- max = [line+1, lines.length-1].min
599
- selected_lines = []
600
- selected_lines.join("\n")
601
- lines[min..max].join("\n")
602
- else
603
- "# Couldn't get snippet for #{file}"
604
- end
605
- end
606
-
607
- def post_process(highlighted, offending_line)
608
- new_lines = []
609
- highlighted.split("\n").each_with_index do |line, i|
610
- new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
611
- new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
612
- new_lines << new_line
613
- end
614
- new_lines.join("\n")
615
- end
616
-
617
- end
618
12
  end
619
13
  end
620
14
  end
@@ -39,7 +39,7 @@ module Closer
39
39
  image_tag = "<img #{attrs.map{|k, v| "#{k}=\"#{v}\"" }.join(' ')} />"
40
40
 
41
41
  if options[:flash]
42
- puts image_tag
42
+ log(image_tag)
43
43
  else
44
44
  @@_images << image_tag
45
45
  end
@@ -68,7 +68,7 @@ module Closer
68
68
 
69
69
  def flash_image_tags
70
70
  if @@_images.size > 0
71
- puts @@_images.join("\n")
71
+ log(@@_images.join("\n"))
72
72
  @@_images.clear
73
73
  end
74
74
  end