closer 0.12.0 → 0.13.0

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 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