Skip to content

Commit

Permalink
Add HTML formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-psarga committed Mar 16, 2020
1 parent aa2306f commit c97d994
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 227 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ elsif !ENV['CUCUMBER_USE_RELEASED_GEMS']
end

gem 'cucumber-expressions', path: ENV['CUCUMBER_EXPRESSIONS_RUBY'] if ENV['CUCUMBER_EXPRESSIONS_RUBY']
gem 'cucumber-html-formatter', path: ENV['CUCUMBER_HTML_FORMATTER_RUBY'] if ENV['CUCUMBER_HTML_FORMATTER_RUBY']
gem 'cucumber-messages', path: ENV['CUCUMBER_MESSAGES_RUBY'] if ENV['CUCUMBER_MESSAGES_RUBY']
gem 'gherkin', path: ENV['GHERKIN_RUBY'] if ENV['GHERKIN_RUBY']

Expand Down
23 changes: 23 additions & 0 deletions features/docs/formatters/html.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Feature: html formatter

Background:
Given the standard step definitions
And a file named "features/my_feature.feature" with:
"""
Feature: Some feature
Scenario Outline: a scenario
Given a <status> step
Examples:
| status |
| passed |
| failed |
"""

Scenario: output html to stdout
When I run `cucumber features/my_feature.feature --format html`
Then it should pass with:
"""
<head>
"""
1 change: 1 addition & 0 deletions lib/cucumber/cli/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Options
'junit' => ['Cucumber::Formatter::Junit', 'Generates a report similar to Ant+JUnit.'],
'json' => ['Cucumber::Formatter::Json', '[DEPRECATED] Prints the feature as JSON'],
'message' => ['Cucumber::Formatter::Message', 'Outputs protobuf messages'],
'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'],
'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios']
}.freeze
max = BUILTIN_FORMATS.keys.map(&:length).max
Expand Down
24 changes: 24 additions & 0 deletions lib/cucumber/formatter/html.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'cucumber/formatter/io'
require 'cucumber/html_formatter'
require 'cucumber/formatter/message_builder'

module Cucumber
module Formatter
class HTML < MessageBuilder
include Io

def initialize(config)
@io = ensure_io(config.out_stream)
@html_formatter = Cucumber::HTMLFormatter::Formatter.new(@io)
@html_formatter.write_pre_message

super(config)
end

def output_envelope(envelope)
@html_formatter.write_message(envelope)
@html_formatter.write_post_message if envelope.test_run_finished
end
end
end
end
230 changes: 3 additions & 227 deletions lib/cucumber/formatter/message.rb
Original file line number Diff line number Diff line change
@@ -1,246 +1,22 @@
# frozen_string_literal: true

require 'base64'
require 'cucumber/formatter/io'
require 'cucumber/formatter/backtrace_filter'
require 'cucumber/formatter/query/hook_by_test_step'
require 'cucumber/formatter/query/pickle_by_test'
require 'cucumber/formatter/query/pickle_step_by_test_step'
require 'cucumber/formatter/query/step_definitions_by_test_step'
require 'cucumber/formatter/query/test_case_started_by_test_case'
require 'cucumber/formatter/message_builder'

module Cucumber
module Formatter
# The formatter used for <tt>--format message</tt>
class Message
class Message < MessageBuilder
include Io
include Cucumber::Messages::TimeConversion

def initialize(config)
@config = config
@hook_by_test_step = Query::HookByTestStep.new(config)
@pickle_by_test = Query::PickleByTest.new(config)
@pickle_step_by_test_step = Query::PickleStepByTestStep.new(config)
@step_definitions_by_test_step = Query::StepDefinitionsByTestStep.new(config)
@test_case_started_by_test_case = Query::TestCaseStartedByTestCase.new(config)

@io = ensure_io(config.out_stream)
config.on_event :envelope, &method(:on_envelope)
config.on_event :gherkin_source_read, &method(:on_gherkin_source_read)
config.on_event :test_case_ready, &method(:on_test_case_ready)
config.on_event :test_run_started, &method(:on_test_run_started)
config.on_event :test_case_started, &method(:on_test_case_started)
config.on_event :test_step_started, &method(:on_test_step_started)
config.on_event :test_step_finished, &method(:on_test_step_finished)
config.on_event :test_case_finished, &method(:on_test_case_finished)
config.on_event :test_run_finished, &method(:on_test_run_finished)

@test_case_by_step_id = {}
@current_test_case_started_id = nil
@current_test_step_id = nil
end

def attach(src, media_type)
attachment_data = {
test_step_id: @current_test_step_id,
test_case_started_id: @current_test_case_started_id,
media_type: media_type
}

if media_type.start_with?('text/')
attachment_data[:text] = src
elsif src.respond_to? :read
attachment_data[:binary] = Base64.encode64(src.read)
else
attachment_data[:binary] = Base64.encode64(src)
end

message = Cucumber::Messages::Envelope.new(
attachment: Cucumber::Messages::Attachment.new(**attachment_data)
)

output_envelope(message)
super(config)
end

private

def output_envelope(envelope)
envelope.write_ndjson_to(@io)
end

def on_envelope(event)
output_envelope(event.envelope)
end

def on_gherkin_source_read(event)
message = Cucumber::Messages::Envelope.new(
source: Cucumber::Messages::Source.new(
uri: event.path,
data: event.body,
media_type: 'text/x.cucumber.gherkin+plain'
)
)

output_envelope(message)
end

def on_test_case_ready(event)
event.test_case.test_steps.each do |step|
@test_case_by_step_id[step.id] = event.test_case
end

message = Cucumber::Messages::Envelope.new(
test_case: Cucumber::Messages::TestCase.new(
id: event.test_case.id,
pickle_id: @pickle_by_test.pickle_id(event.test_case),
test_steps: event.test_case.test_steps.map { |step| test_step_to_message(step) }
)
)

output_envelope(message)
end

def test_step_to_message(step)
return hook_step_to_message(step) if step.hook?

Cucumber::Messages::TestCase::TestStep.new(
id: step.id,
pickle_step_id: @pickle_step_by_test_step.pickle_step_id(step),
step_definition_ids: @step_definitions_by_test_step.step_definition_ids(step),
step_match_arguments_lists: step_match_arguments_lists(step)
)
end

def hook_step_to_message(step)
Cucumber::Messages::TestCase::TestStep.new(
id: step.id,
hook_id: @hook_by_test_step.hook_id(step)
)
end

def step_match_arguments_lists(step)
match_arguments = step_match_arguments(step)
[Cucumber::Messages::TestCase::TestStep::StepMatchArgumentsList.new(
step_match_arguments: match_arguments
)]
rescue Cucumber::Formatter::TestStepUnknownError
[]
end

def step_match_arguments(step)
@step_definitions_by_test_step.step_match_arguments(step).map do |argument|
Cucumber::Messages::StepMatchArgument.new(
group: argument_group_to_message(argument.group),
parameter_type_name: argument.parameter_type.name
)
end
end

def argument_group_to_message(group)
Cucumber::Messages::StepMatchArgument::Group.new(
start: group.start,
value: group.value,
children: group.children.map { |child| argument_group_to_message(child) }
)
end

def on_test_run_started(*)
message = Cucumber::Messages::Envelope.new(
test_run_started: Cucumber::Messages::TestRunStarted.new(
timestamp: time_to_timestamp(Time.now)
)
)

output_envelope(message)
end

def on_test_case_started(event)
@current_test_case_started_id = test_case_started_id(event.test_case)

message = Cucumber::Messages::Envelope.new(
test_case_started: Cucumber::Messages::TestCaseStarted.new(
id: test_case_started_id(event.test_case),
test_case_id: event.test_case.id,
timestamp: time_to_timestamp(Time.now),
attempt: @test_case_started_by_test_case.attempt_by_test_case(event.test_case)
)
)

output_envelope(message)
end

def on_test_step_started(event)
@current_test_step_id = event.test_step.id
test_case = @test_case_by_step_id[event.test_step.id]

message = Cucumber::Messages::Envelope.new(
test_step_started: Cucumber::Messages::TestStepStarted.new(
test_step_id: event.test_step.id,
test_case_started_id: test_case_started_id(test_case),
timestamp: time_to_timestamp(Time.now)
)
)

output_envelope(message)
end

def on_test_step_finished(event)
test_case = @test_case_by_step_id[event.test_step.id]
result = event
.result
.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)

result_message = result.to_message
if result.failed? || result.pending?
result_message = Cucumber::Messages::TestStepResult.new(
status: result_message.status,
duration: result_message.duration,
message: create_error_message(result)
)
end

message = Cucumber::Messages::Envelope.new(
test_step_finished: Cucumber::Messages::TestStepFinished.new(
test_step_id: event.test_step.id,
test_case_started_id: test_case_started_id(test_case),
test_step_result: result_message,
timestamp: time_to_timestamp(Time.now)
)
)

output_envelope(message)
end

def create_error_message(result)
message_element = result.failed? ? result.exception : result
message = "#{message_element.message} (#{message_element.class})"
([message] + message_element.backtrace).join("\n")
end

def on_test_case_finished(event)
message = Cucumber::Messages::Envelope.new(
test_case_finished: Cucumber::Messages::TestCaseFinished.new(
test_case_started_id: test_case_started_id(event.test_case),
timestamp: time_to_timestamp(Time.now)
)
)

output_envelope(message)
end

def on_test_run_finished(*)
message = Cucumber::Messages::Envelope.new(
test_run_finished: Cucumber::Messages::TestRunFinished.new(
timestamp: time_to_timestamp(Time.now)
)
)

output_envelope(message)
end

def test_case_started_id(test_case)
@test_case_started_by_test_case.test_case_started_id_by_test_case(test_case)
end
end
end
end
Loading

0 comments on commit c97d994

Please sign in to comment.