Repository: Behat/Behat (PHP)

Issue:
Provide an option not to display "skipped" steps in the pretty formatter
When a step fails, the pretty formatter prints the complete scenario including all the following skipped steps (and, by default, multiline arguments).

While this is sometimes useful, it can create a lot of noise during development, forcing users to scroll up the output to see the actual failing step and assertion. The impact is often worst early in development (when often the failure will be early in a scenario, due to the order of implementation).

In discussion on #1612, @jdeniau mentioned that it would be good to have an option not to print skipped steps. I'd also find that useful.


Discussion:

Patch:
diff --git a/features/print_skipped_steps.feature b/features/print_skipped_steps.feature
new file mode 100644
index 000000000..e082add9c
--- /dev/null
+++ b/features/print_skipped_steps.feature
@@ -0,0 +1,59 @@
+Feature: Pretty formatter print_skipped_steps option
+  In order to control whether skipped steps are printed
+  As a Behat user
+  I need a pretty formatter option to hide skipped steps
+
+  Background:
+    Given I initialise the working directory from the "PrintSkippedSteps" fixtures folder
+    And I provide the following options for all behat invocations:
+      | option      | value |
+      | --no-colors |       |
+
+  Scenario: Pretty prints skipped steps by default
+    When I run behat with the following additional options:
+      | option    | value   |
+      | --profile | default |
+    Then it should fail with:
+      """
+      Feature: Skipped steps printing
+        In order to test the print_skipped_steps option
+        As a behat developer
+        I need a scenario with a skipped step
+
+        Scenario: Skips after a failure
+          When a passing step
+          And a failing step
+            step failed as supposed (Exception)
+          Then a skipped step
+
+      --- Failed scenarios:
+
+          features/skip.feature:6 (on line 8)
+
+      1 scenario (1 failed)
+      3 steps (1 passed, 1 failed, 1 skipped)
+      """
+
+  Scenario: Pretty does not print skipped steps when disabled
+    When I run behat with the following additional options:
+      | option    | value        |
+      | --profile | hide_skipped |
+    Then it should fail with:
+      """
+      Feature: Skipped steps printing
+        In order to test the print_skipped_steps option
+        As a behat developer
+        I need a scenario with a skipped step
+
+        Scenario: Skips after a failure
+          When a passing step
+          And a failing step
+            step failed as supposed (Exception)
+
+      --- Failed scenarios:
+
+          features/skip.feature:6 (on line 8)
+
+      1 scenario (1 failed)
+      3 steps (1 passed, 1 failed, 1 skipped)
+      """
diff --git a/src/Behat/Behat/Output/Node/Printer/Pretty/PrettyStepPrinter.php b/src/Behat/Behat/Output/Node/Printer/Pretty/PrettyStepPrinter.php
index 31fca8f2a..651c90396 100644
--- a/src/Behat/Behat/Output/Node/Printer/Pretty/PrettyStepPrinter.php
+++ b/src/Behat/Behat/Output/Node/Printer/Pretty/PrettyStepPrinter.php
@@ -16,6 +16,7 @@
 use Behat\Behat\Tester\Result\DefinedStepResult;
 use Behat\Behat\Tester\Result\ExecutedStepResult;
 use Behat\Behat\Tester\Result\StepResult;
+use Behat\Config\Formatter\PrettyFormatter;
 use Behat\Config\Formatter\ShowOutputOption;
 use Behat\Gherkin\Node\ArgumentInterface;
 use Behat\Gherkin\Node\PyStringNode;
@@ -26,6 +27,7 @@
 use Behat\Testwork\Output\Formatter;
 use Behat\Testwork\Output\Printer\OutputPrinter;
 use Behat\Testwork\Tester\Result\ExceptionResult;
+use Behat\Testwork\Tester\Result\TestResult;
 
 /**
  * Prints step.
@@ -63,6 +65,14 @@ public function __construct(
 
     public function printStep(Formatter $formatter, Scenario $scenario, StepNode $step, StepResult $result)
     {
+        if ($result->getResultCode() === TestResult::SKIPPED) {
+            $printSkipped = $formatter->getParameter(PrettyFormatter::PRINT_SKIPPED_STEPS_SETTING);
+
+            if ($printSkipped === false) {
+                return;
+            }
+        }
+
         $this->printText($formatter->getOutputPrinter(), $step->getKeyword(), $step->getText(), $result);
         $this->pathPrinter->printStepPath($formatter, $scenario, $step, $result, mb_strlen($this->indentText, 'utf8'));
         $this->printArguments($formatter, $step->getArguments(), $result);
diff --git a/src/Behat/Config/Formatter/Formatter.php b/src/Behat/Config/Formatter/Formatter.php
index d602d472e..59a1a374d 100644
--- a/src/Behat/Config/Formatter/Formatter.php
+++ b/src/Behat/Config/Formatter/Formatter.php
@@ -121,6 +121,9 @@ protected function toPhpExprForNamedFormatter(): Expr
             if ($name === self::SHORT_SUMMARY_SETTING) {
                 $name = self::SHORT_SUMMARY_PARAMETER_NAME;
             }
+            if ($name === PrettyFormatter::PRINT_SKIPPED_STEPS_SETTING) {
+                $name = PrettyFormatter::PRINT_SKIPPED_STEPS_PARAMETER_NAME;
+            }
             $arguments[$name] = $value;
         }
         if ($arguments !== []) {
diff --git a/src/Behat/Config/Formatter/PrettyFormatter.php b/src/Behat/Config/Formatter/PrettyFormatter.php
index 95c7cbc97..107c622ba 100644
--- a/src/Behat/Config/Formatter/PrettyFormatter.php
+++ b/src/Behat/Config/Formatter/PrettyFormatter.php
@@ -10,11 +10,14 @@ final class PrettyFormatter extends Formatter
 {
     public const NAME = 'pretty';
 
+    public const PRINT_SKIPPED_STEPS_PARAMETER_NAME = 'printSkippedSteps';
+
     private const TIMER_SETTING = 'timer';
     private const EXPAND_SETTING = 'expand';
     private const PATHS_SETTING = 'paths';
     private const MULTILINE_SETTING = 'multiline';
     private const SHORT_SUMMARY_SETTING = 'short_summary';
+    public const PRINT_SKIPPED_STEPS_SETTING = 'print_skipped_steps';
 
     /**
      * @param bool $timer show time and memory usage at the end of the test run
@@ -26,6 +29,7 @@ final class PrettyFormatter extends Formatter
      *                                     formatter output (yes, no, on-fail)
      * @param bool $shortSummary if we should print the short summary which just lists scenarios
      *                           or the long summary which lists steps
+     * @param bool $printSkippedSteps if we should print skipped steps in the output
      */
     public function __construct(
         bool $timer = true,
@@ -34,6 +38,7 @@ public function __construct(
         bool $multiline = true,
         ShowOutputOption $showOutput = ShowOutputOption::Yes,
         bool $shortSummary = true,
+        bool $printSkippedSteps = true,
         ...$baseOptions,
     ) {
         $settings = [
@@ -43,6 +48,7 @@ public function __construct(
             self::MULTILINE_SETTING => $multiline,
             ShowOutputOption::OPTION_NAME => $showOutput->value,
             self::SHORT_SUMMARY_SETTING => $shortSummary,
+            self::PRINT_SKIPPED_STEPS_SETTING => $printSkippedSteps,
         ];
         $settings = [...$settings, ...$baseOptions];
         parent::__construct(name: self::NAME, settings: $settings);


Test patch:
diff --git a/tests/Behat/Tests/Config/ProfileTest.php b/tests/Behat/Tests/Config/ProfileTest.php
index 3ef8471f5..388050a43 100644
--- a/tests/Behat/Tests/Config/ProfileTest.php
+++ b/tests/Behat/Tests/Config/ProfileTest.php
@@ -147,6 +147,7 @@ public function testAddingFormatters(): void
                     'output_verbosity' => 2,
                     'show_output' => 'yes',
                     'short_summary' => true,
+                    'print_skipped_steps' => true,
                 ],
                 'progress' => [
                     'timer' => false,
diff --git a/tests/Fixtures/PrintSkippedSteps/behat.php b/tests/Fixtures/PrintSkippedSteps/behat.php
new file mode 100644
index 000000000..59f29fa55
--- /dev/null
+++ b/tests/Fixtures/PrintSkippedSteps/behat.php
@@ -0,0 +1,21 @@
+<?php
+
+use Behat\Config\Config;
+use Behat\Config\Formatter\PrettyFormatter;
+use Behat\Config\Profile;
+
+return (new Config())
+    ->withProfile((new Profile('default'))
+        ->withFormatter(new PrettyFormatter(
+            timer: false,
+            paths: false,
+        ))
+    )
+    ->withProfile((new Profile('hide_skipped'))
+        ->withFormatter(new PrettyFormatter(
+            printSkippedSteps: false,
+            timer: false,
+            paths: false,
+        ))
+    )
+;
diff --git a/tests/Fixtures/PrintSkippedSteps/features/bootstrap/FeatureContext.php b/tests/Fixtures/PrintSkippedSteps/features/bootstrap/FeatureContext.php
new file mode 100644
index 000000000..eaea4734b
--- /dev/null
+++ b/tests/Fixtures/PrintSkippedSteps/features/bootstrap/FeatureContext.php
@@ -0,0 +1,24 @@
+<?php
+
+use Behat\Behat\Context\Context;
+use Behat\Step\Then;
+use Behat\Step\When;
+
+class FeatureContext implements Context
+{
+    #[When('a passing step')]
+    public function passingStep(): void
+    {
+    }
+
+    #[When('a failing step')]
+    public function failingStep(): void
+    {
+        throw new Exception('step failed as supposed');
+    }
+
+    #[Then('a skipped step')]
+    public function skippedStep(): void
+    {
+    }
+}
diff --git a/tests/Fixtures/PrintSkippedSteps/features/skip.feature b/tests/Fixtures/PrintSkippedSteps/features/skip.feature
new file mode 100644
index 000000000..2c4ed5ce2
--- /dev/null
+++ b/tests/Fixtures/PrintSkippedSteps/features/skip.feature
@@ -0,0 +1,9 @@
+Feature: Skipped steps printing
+  In order to test the print_skipped_steps option
+  As a behat developer
+  I need a scenario with a skipped step
+
+  Scenario: Skips after a failure
+    When a passing step
+    And a failing step
+    Then a skipped step