class Cucumber::Formatter::Pretty


If the output is STDOUT (and not a file), there are bright colours to watch too.
This formatter prints the result of the feature executions to plain text - exactly how they were parsed.
The formatter used for --format pretty (the default formatter).

def attach(src, media_type, filename)

def attach(src, media_type, filename)
  return unless media_type == 'text/x.cucumber.log+plain'
  if filename
    @test_step_output.push("#{filename}: #{src}")
  else
    @test_step_output.push(src)
  end
end

def bind_events(config)

def bind_events(config)
  config.on_event :gherkin_source_read, &method(:on_gherkin_source_read)
  config.on_event :step_activated, &method(:on_step_activated)
  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)
  config.on_event :undefined_parameter_type, &method(:collect_undefined_parameter_type_names)
end

def calculate_source_indent(test_case)

def calculate_source_indent(test_case)
  scenario = scenario_source(test_case).scenario
  @source_indent = calculate_source_indent_for_ast_node(scenario)
end

def calculate_source_indent_for_ast_node(ast_node)

def calculate_source_indent_for_ast_node(ast_node)
  indent = 4 + ast_node.keyword.length
  indent += 1 + ast_node.name.length
  ast_node.steps.each do |step|
    step_indent = 5 + step.keyword.length + step.text.length
    indent = step_indent if step_indent > indent
  end
  indent
end

def calculate_source_indent_for_expanded_test_case(test_case, scenario_keyword, expanded_name)

def calculate_source_indent_for_expanded_test_case(test_case, scenario_keyword, expanded_name)
  indent = 7 + scenario_keyword.length
  indent += 2 + expanded_name.length
  test_case.test_steps.each do |step|
    if !step.hook? && step.location.lines.max >= test_case.location.lines.max
      step_indent = 9 + test_step_keyword(step).length + step.text.length
      indent = step_indent if step_indent > indent
    end
  end
  indent
end

def feature_has_background?

def feature_has_background?
  feature_children = gherkin_document.feature.children
  return false if feature_children.empty?
  !feature_children.first.background.nil?
end

def find_exception_to_be_printed(result)

def find_exception_to_be_printed(result)
  return nil if result.ok?(strict: options[:strict])
  result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
  exception = result.failed? ? result.exception : result
  return nil if @exceptions.include?(exception)
  exception
end

def first_feature?

def first_feature?
  @first_feature
end

def first_step_after_printing_background_steps?(test_step)

def first_step_after_printing_background_steps?(test_step)
  return false unless print_background_steps
  return false unless test_step.location.lines.max >= current_test_case.location.lines.max
  @print_background_steps = false
  true
end

def from_scenario_outline?(test_case)

def from_scenario_outline?(test_case)
  scenario = scenario_source(test_case)
  scenario.type != :Scenario
end

def gherkin_document

def gherkin_document
  @ast_lookup.gherkin_document(current_feature_uri)
end

def gherkin_source

def gherkin_source
  @gherkin_sources[current_feature_uri]
end

def initialize(config)

def initialize(config)
  @io = ensure_io(config.out_stream, config.error_stream)
  @config = config
  @options = config.to_hash
  @snippets_input = []
  @undefined_parameter_types = []
  @total_duration = 0
  @exceptions = []
  @gherkin_sources = {}
  @step_matches = {}
  @ast_lookup = AstLookup.new(config)
  @counts = ConsoleCounts.new(config)
  @issues = ConsoleIssues.new(config, @ast_lookup)
  @first_feature = true
  @current_feature_uri = ''
  @current_scenario_outline = nil
  @current_examples = nil
  @current_test_case = nil
  @in_scenario_outline = false
  @print_background_steps = false
  @test_step_output = []
  @passed_test_cases = []
  @source_indent = 0
  @next_comment_to_be_printed = 0
  bind_events(config)
end

def on_gherkin_source_read(event)

def on_gherkin_source_read(event)
  @gherkin_sources[event.path] = event.body
end

def on_step_activated(event)

def on_step_activated(event)
  test_step, step_match = *event.attributes
  @step_matches[test_step.to_s] = step_match
end

def on_test_case_finished(event)

def on_test_case_finished(event)
  @total_duration += DurationExtractor.new(event.result).result_duration
  @passed_test_cases << event.test_case if config.wip? && event.result.passed?
  if in_scenario_outline && !options[:expand]
    print_row_data(event.test_case, event.result)
  else
    exception_to_be_printed = find_exception_to_be_printed(event.result)
    return unless exception_to_be_printed
    print_exception(exception_to_be_printed, event.result.to_sym, 6)
    @exceptions << exception_to_be_printed
  end
end

def on_test_case_started(event)

def on_test_case_started(event)
  if !same_feature_as_previous_test_case?(event.test_case.location)
    if first_feature?
      @first_feature = false
      print_profile_information
    else
      print_comments(gherkin_source.split("\n").length, 0)
      @io.puts
    end
    @current_feature_uri = event.test_case.location.file
    @exceptions = []
    print_feature_data
    if feature_has_background?
      print_background_data
      @print_background_steps = true
      @in_scenario_outline = false
    end
  else
    @print_background_steps = false
  end
  @current_test_case = event.test_case
  print_step_header(current_test_case) unless print_background_steps
end

def on_test_run_finished(_event)

def on_test_run_finished(_event)
  print_comments(gherkin_source.split("\n").length, 0) unless current_feature_uri.empty?
  @io.puts
  print_summary
end

def on_test_step_finished(event)

def on_test_step_finished(event)
  collect_snippet_data(event.test_step, @ast_lookup) if event.result.undefined?
  return if in_scenario_outline && !options[:expand]
  exception_to_be_printed = find_exception_to_be_printed(event.result)
  print_step_data(event.test_step, event.result) if print_step_data?(event, exception_to_be_printed)
  print_step_output
  return unless exception_to_be_printed
  print_exception(exception_to_be_printed, event.result.to_sym, 6)
  @exceptions << exception_to_be_printed
end

def on_test_step_started(event)

def on_test_step_started(event)
  return if event.test_step.hook?
  print_step_header(current_test_case) if first_step_after_printing_background_steps?(event.test_step)
end

def print_background_data

def print_background_data
  @io.puts
  background = gherkin_document.feature.children.first.background
  @source_indent = calculate_source_indent_for_ast_node(background) if options[:source]
  print_comments(background.location.line, 2)
  print_background_line(background)
  print_description(background.description)
  @io.flush
end

def print_background_line(background)

def print_background_line(background)
  print_keyword_name(background.keyword, background.name, 2, "#{current_feature_uri}:#{background.location.line}")
end

def print_comments(up_to_line, indent_amount)

def print_comments(up_to_line, indent_amount)
  comments = gherkin_document.comments
  return if comments.empty? || comments.length <= @next_comment_to_be_printed
  comments[@next_comment_to_be_printed..].each do |comment|
    if comment.location.line <= up_to_line
      @io.puts(indent(format_string(comment.text.strip, :comment), indent_amount))
      @next_comment_to_be_printed += 1
    end
    break if @next_comment_to_be_printed >= comments.length
  end
end

def print_data_table(data_table, status, indent_amount)

def print_data_table(data_table, status, indent_amount)
  data_table.rows.each do |row|
    print_comments(row.location.line, indent_amount)
    @io.puts indent(format_string(gherkin_source.split("\n")[row.location.line - 1].strip, status), indent_amount)
  end
end

def print_description(description)

def print_description(description)
  return unless description
  description.split("\n").each do |line|
    @io.puts(line)
  end
end

def print_doc_string(content, status, indent_amount)

def print_doc_string(content, status, indent_amount)
  s = indent(%("""\n#{content}\n"""), indent_amount)
  s = s.split("\n").map { |l| l =~ /^\s+$/ ? '' : l }.join("\n")
  @io.puts(format_string(s, status))
end

def print_examples_data(examples)

def print_examples_data(examples)
  print_comments(examples.location.line, 4)
  print_tags(examples.tags, 4)
  print_keyword_name(examples.keyword, examples.name, 4)
  print_description(examples.description)
  unless options[:expand]
    print_comments(examples.table_header.location.line, 6)
    @io.puts(indent(gherkin_source.split("\n")[examples.table_header.location.line - 1].strip, 6))
  end
  @io.flush
end

def print_expanded_row_data(test_case)

def print_expanded_row_data(test_case)
  feature = gherkin_document.feature
  language_code = feature.language || 'en'
  language = ::Gherkin::Dialect.for(language_code)
  scenario_keyword = language.scenario_keywords[0]
  row = scenario_source(test_case).row
  expanded_name = "| #{row.cells.map(&:value).join(' | ')} |"
  @source_indent = calculate_source_indent_for_expanded_test_case(test_case, scenario_keyword, expanded_name)
  @io.puts
  print_keyword_name(scenario_keyword, expanded_name, 6, test_case.location)
end

def print_feature_data

def print_feature_data
  feature = gherkin_document.feature
  print_language_comment(feature.location.line)
  print_comments(feature.location.line, 0)
  print_tags(feature.tags, 0)
  print_feature_line(feature)
  print_description(feature.description)
  @io.flush
end

def print_feature_line(feature)

def print_feature_line(feature)
  print_keyword_name(feature.keyword, feature.name, 0)
end

def print_keyword_name(keyword, name, indent_amount, location = nil)

def print_keyword_name(keyword, name, indent_amount, location = nil)
  line = "#{keyword}:"
  line += " #{name}"
  @io.print(indent(line, indent_amount))
  if location && options[:source]
    line_comment = indent(format_string("# #{location}", :comment), @source_indent - line.length - indent_amount)
    @io.print(line_comment)
  end
  @io.puts
end

def print_language_comment(feature_line)

def print_language_comment(feature_line)
  gherkin_source.split("\n")[0..feature_line].each do |line|
    @io.puts(format_string(line, :comment)) if /# *language *:/ =~ line
  end
end

def print_multiline_argument(test_step, result, indent)

def print_multiline_argument(test_step, result, indent)
  step = step_source(test_step).step
  if !step.doc_string.nil?
    print_doc_string(step.doc_string.content, result.to_sym, indent)
  elsif !step.data_table.nil?
    print_data_table(step.data_table, result.to_sym, indent)
  end
end

def print_outline_data(scenario_outline)

def print_outline_data(scenario_outline)
  print_comments(scenario_outline.location.line, 2)
  print_tags(scenario_outline.tags, 2)
  @source_indent = calculate_source_indent_for_ast_node(scenario_outline) if options[:source]
  print_scenario_line(scenario_outline, "#{current_feature_uri}:#{scenario_outline.location.line}")
  print_description(scenario_outline.description)
  scenario_outline.steps.each do |step|
    print_comments(step.location.line, 4)
    step_line = "    #{step.keyword}#{step.text}"
    @io.print(format_string(step_line, :skipped))
    if options[:source]
      comment_line = format_string("# #{current_feature_uri}:#{step.location.line}", :comment)
      @io.print(indent(comment_line, @source_indent - step_line.length))
    end
    @io.puts
    next if options[:no_multiline]
    print_doc_string(step.doc_string.content, :skipped, 6) unless step.doc_string.nil?
    print_data_table(step.data_table, :skipped, 6) unless step.data_table.nil?
  end
  @io.flush
end

def print_row_data(test_case, result)

def print_row_data(test_case, result)
  print_comments(test_case.location.lines.max, 6)
  @io.print(indent(format_string(gherkin_source.split("\n")[test_case.location.lines.max - 1].strip, result.to_sym), 6))
  @io.print(indent(format_string(@test_step_output.join(', '), :tag), 2)) unless @test_step_output.empty?
  @test_step_output = []
  @io.puts
  if result.failed? || result.pending?
    result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
    exception = result.failed? ? result.exception : result
    unless @exceptions.include?(exception)
      print_exception(exception, result.to_sym, 6)
      @exceptions << exception
    end
  end
  @io.flush
end

def print_scenario_data(test_case)

def print_scenario_data(test_case)
  scenario = scenario_source(test_case).scenario
  print_comments(scenario.location.line, 2)
  print_tags(scenario.tags, 2)
  print_scenario_line(scenario, test_case.location)
  print_description(scenario.description)
  @io.flush
end

def print_scenario_line(scenario, location = nil)

def print_scenario_line(scenario, location = nil)
  print_keyword_name(scenario.keyword, scenario.name, 2, location)
end

def print_step_data(test_step, result)

def print_step_data(test_step, result)
  base_indent = options[:expand] && in_scenario_outline ? 8 : 4
  step_keyword = test_step_keyword(test_step)
  indent = options[:source] ? @source_indent - step_keyword.length - test_step.text.length - base_indent : nil
  print_comments(test_step.location.lines.max, base_indent)
  name_to_report = format_step(step_keyword, @step_matches.fetch(test_step.to_s) { NoStepMatch.new(test_step, test_step.text) }, result.to_sym, indent)
  @io.puts(indent(name_to_report, base_indent))
  print_multiline_argument(test_step, result, base_indent + 2) unless options[:no_multiline]
  @io.flush
end

def print_step_data?(event, exception_to_be_printed)

def print_step_data?(event, exception_to_be_printed)
  !event.test_step.hook? && (
    print_background_steps ||
    event.test_step.location.lines.max >= current_test_case.location.lines.max ||
    exception_to_be_printed
  )
end

def print_step_header(test_case)

def print_step_header(test_case)
  if from_scenario_outline?(test_case)
    @in_scenario_outline = true
    unless same_outline_as_previous_test_case?(test_case)
      @current_scenario_outline = scenario_source(test_case).scenario_outline
      @io.puts
      print_outline_data(current_scenario_outline)
    end
    unless same_examples_as_previous_test_case?(test_case)
      @current_examples = scenario_source(test_case).examples
      @io.puts
      print_examples_data(current_examples)
    end
    print_expanded_row_data(current_test_case) if options[:expand]
  else
    @in_scenario_outline = false
    @current_scenario_outline = nil
    @current_examples = nil
    @io.puts
    @source_indent = calculate_source_indent(current_test_case)
    print_scenario_data(test_case)
  end
end

def print_step_output

def print_step_output
  @test_step_output.each { |message| @io.puts(indent(format_string(message, :tag), 6)) }
  @test_step_output = []
end

def print_summary

def print_summary
  print_statistics(@total_duration, config, @counts, @issues)
  print_snippets(options)
  print_passing_wip(config, @passed_test_cases, @ast_lookup)
end

def print_tags(tags, indent_amount)

def print_tags(tags, indent_amount)
  return if !tags || tags.empty?
  @io.puts(indent(tags.map { |tag| format_string(tag.name, :tag) }.join(' '), indent_amount))
end

def same_examples_as_previous_test_case?(test_case)

def same_examples_as_previous_test_case?(test_case)
  scenario_source(test_case).examples == current_examples
end

def same_feature_as_previous_test_case?(location)

def same_feature_as_previous_test_case?(location)
  location.file == current_feature_uri
end

def same_outline_as_previous_test_case?(test_case)

def same_outline_as_previous_test_case?(test_case)
  scenario_source(test_case).scenario_outline == current_scenario_outline
end

def scenario_source(test_case)

def scenario_source(test_case)
  @ast_lookup.scenario_source(test_case)
end

def step_source(test_step)

def step_source(test_step)
  @ast_lookup.step_source(test_step)
end

def test_step_keyword(test_step)

def test_step_keyword(test_step)
  step = step_source(test_step).step
  step.keyword
end