From 404a1f405de5a0957edbe2930123735741fea8fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:44:25 +0000 Subject: [PATCH 1/9] Initial plan From 348f0c4eee4fdd2877aacac143a0cd43614548a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:01:42 +0000 Subject: [PATCH 2/9] Add diff output for failed Behat comparisons Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- composer.json | 1 + src/Context/Support.php | 37 +++++++++++++++++++++++-- src/Context/ThenStepDefinitions.php | 43 ++++++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 8e9091d7a..6811cc8aa 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "phpstan/phpstan-deprecation-rules": "^1.2 || ^2.0", "phpstan/phpstan-phpunit": "^1.4 || ^2.0", "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", "swissspidy/phpstan-no-private": "^0.2.1 || ^1.0", "szepeviktor/phpstan-wordpress": "^v1.3.5", "wp-cli/config-command": "^1 || ^2", diff --git a/src/Context/Support.php b/src/Context/Support.php index cd3f34ca6..ca72f6584 100644 --- a/src/Context/Support.php +++ b/src/Context/Support.php @@ -8,6 +8,8 @@ use Behat\Behat\Exception\PendingException; use Exception; use Mustangostang\Spyc; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; trait Support { @@ -120,6 +122,15 @@ protected function check_string( $output, $expected, $action, $message = false, if ( false === $message ) { $message = $output; } + + // Add diff for 'be' comparisons to show exact differences. + if ( 'be' === $action ) { + $diff = $this->generate_diff( $expected, rtrim( $output, "\n" ) ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; + } + } + throw new Exception( $message ); } } @@ -133,7 +144,10 @@ protected function check_string( $output, $expected, $action, $message = false, protected function compare_tables( $expected_rows, $actual_rows, $output ): void { // The first row is the header and must be present. if ( $expected_rows[0] !== $actual_rows[0] ) { - throw new Exception( $output ); + $expected_table = implode( "\n", $expected_rows ); + $actual_table = implode( "\n", $actual_rows ); + $diff = $this->generate_diff( $expected_table, $actual_table ); + throw new Exception( $output . "\n\n" . $diff ); } unset( $actual_rows[0] ); @@ -141,7 +155,10 @@ protected function compare_tables( $expected_rows, $actual_rows, $output ): void $missing_rows = array_diff( $expected_rows, $actual_rows ); if ( ! empty( $missing_rows ) ) { - throw new Exception( $output ); + $expected_table = implode( "\n", $expected_rows ); + $actual_table = implode( "\n", $actual_rows ); + $diff = $this->generate_diff( $expected_table, $actual_table ); + throw new Exception( $output . "\n\n" . $diff ); } } @@ -290,4 +307,20 @@ protected function check_that_yaml_string_contains_yaml_string( $actual_yaml, $e return $this->compare_contents( $expected_value, $actual_value ); } + + /** + * Generate a unified diff between two strings. + * + * @param string $expected The expected string. + * @param string $actual The actual string. + * @return string The unified diff output. + */ + protected function generate_diff( $expected, $actual ) { + $builder = new UnifiedDiffOutputBuilder( + "--- Expected\n+++ Actual\n", + false + ); + $differ = new Differ( $builder ); + return $differ->diff( $expected, $actual ); + } } diff --git a/src/Context/ThenStepDefinitions.php b/src/Context/ThenStepDefinitions.php index 9dc877b8e..4c90c0746 100644 --- a/src/Context/ThenStepDefinitions.php +++ b/src/Context/ThenStepDefinitions.php @@ -215,7 +215,17 @@ public function then_stdout_should_be_json_containing( PyStringNode $expected ): $expected = $this->replace_variables( (string) $expected ); if ( ! $this->check_that_json_string_contains_json_string( $output, $expected ) ) { - throw new Exception( $this->result ); + $message = (string) $this->result; + // Pretty print JSON for better diff readability. + $expected_json = json_encode( json_decode( $expected ), JSON_PRETTY_PRINT ); + $actual_json = json_encode( json_decode( $output ), JSON_PRETTY_PRINT ); + if ( $expected_json && $actual_json ) { + $diff = $this->generate_diff( $expected_json, $actual_json ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; + } + } + throw new Exception( $message ); } } @@ -246,7 +256,17 @@ public function then_stdout_should_be_a_json_array_containing( PyStringNode $exp $missing = array_diff( $expected_values, $actual_values ); if ( ! empty( $missing ) ) { - throw new Exception( $this->result ); + $message = (string) $this->result; + // Pretty print JSON arrays for better diff readability. + $expected_json = json_encode( $expected_values, JSON_PRETTY_PRINT ); + $actual_json = json_encode( $actual_values, JSON_PRETTY_PRINT ); + if ( $expected_json && $actual_json ) { + $diff = $this->generate_diff( $expected_json, $actual_json ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; + } + } + throw new Exception( $message ); } } @@ -276,7 +296,17 @@ public function then_stdout_should_be_csv_containing( TableNode $expected ): voi } if ( ! $this->check_that_csv_string_contains_values( $output, $expected_rows ) ) { - throw new Exception( $this->result ); + $message = (string) $this->result; + // Convert expected rows to CSV format for diff. + $expected_csv = ''; + foreach ( $expected_rows as $row ) { + $expected_csv .= implode( ',', array_map( 'trim', $row ) ) . "\n"; + } + $diff = $this->generate_diff( trim( $expected_csv ), trim( $output ) ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; + } + throw new Exception( $message ); } } @@ -303,7 +333,12 @@ public function then_stdout_should_be_yaml_containing( PyStringNode $expected ): $expected = $this->replace_variables( (string) $expected ); if ( ! $this->check_that_yaml_string_contains_yaml_string( $output, $expected ) ) { - throw new Exception( $this->result ); + $message = (string) $this->result; + $diff = $this->generate_diff( $expected, $output ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; + } + throw new Exception( $message ); } } From 6d5bc893ffaa76aeab7f34a9d1cea1e11ffc0e4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:06:58 +0000 Subject: [PATCH 3/9] Add return type hint to generate_diff method Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Context/Support.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Context/Support.php b/src/Context/Support.php index ca72f6584..041b02f8a 100644 --- a/src/Context/Support.php +++ b/src/Context/Support.php @@ -315,7 +315,7 @@ protected function check_that_yaml_string_contains_yaml_string( $actual_yaml, $e * @param string $actual The actual string. * @return string The unified diff output. */ - protected function generate_diff( $expected, $actual ) { + protected function generate_diff( $expected, $actual ): string { $builder = new UnifiedDiffOutputBuilder( "--- Expected\n+++ Actual\n", false From c73108ea47ba048076a0ba8b95ac0886cb06f695 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:08:41 +0000 Subject: [PATCH 4/9] Address code review feedback - add error checking and type hints Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Context/Support.php | 2 +- src/Context/ThenStepDefinitions.php | 30 +++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Context/Support.php b/src/Context/Support.php index 041b02f8a..3e2669acb 100644 --- a/src/Context/Support.php +++ b/src/Context/Support.php @@ -315,7 +315,7 @@ protected function check_that_yaml_string_contains_yaml_string( $actual_yaml, $e * @param string $actual The actual string. * @return string The unified diff output. */ - protected function generate_diff( $expected, $actual ): string { + protected function generate_diff( string $expected, string $actual ): string { $builder = new UnifiedDiffOutputBuilder( "--- Expected\n+++ Actual\n", false diff --git a/src/Context/ThenStepDefinitions.php b/src/Context/ThenStepDefinitions.php index 4c90c0746..2d6ef9913 100644 --- a/src/Context/ThenStepDefinitions.php +++ b/src/Context/ThenStepDefinitions.php @@ -217,12 +217,16 @@ public function then_stdout_should_be_json_containing( PyStringNode $expected ): if ( ! $this->check_that_json_string_contains_json_string( $output, $expected ) ) { $message = (string) $this->result; // Pretty print JSON for better diff readability. - $expected_json = json_encode( json_decode( $expected ), JSON_PRETTY_PRINT ); - $actual_json = json_encode( json_decode( $output ), JSON_PRETTY_PRINT ); - if ( $expected_json && $actual_json ) { - $diff = $this->generate_diff( $expected_json, $actual_json ); - if ( ! empty( $diff ) ) { - $message .= "\n\n" . $diff; + $expected_decoded = json_decode( $expected ); + $actual_decoded = json_decode( $output ); + if ( null !== $expected_decoded && null !== $actual_decoded ) { + $expected_json = json_encode( $expected_decoded, JSON_PRETTY_PRINT ); + $actual_json = json_encode( $actual_decoded, JSON_PRETTY_PRINT ); + if ( false !== $expected_json && false !== $actual_json ) { + $diff = $this->generate_diff( $expected_json, $actual_json ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; + } } } throw new Exception( $message ); @@ -258,12 +262,14 @@ public function then_stdout_should_be_a_json_array_containing( PyStringNode $exp if ( ! empty( $missing ) ) { $message = (string) $this->result; // Pretty print JSON arrays for better diff readability. - $expected_json = json_encode( $expected_values, JSON_PRETTY_PRINT ); - $actual_json = json_encode( $actual_values, JSON_PRETTY_PRINT ); - if ( $expected_json && $actual_json ) { - $diff = $this->generate_diff( $expected_json, $actual_json ); - if ( ! empty( $diff ) ) { - $message .= "\n\n" . $diff; + if ( null !== $expected_values && null !== $actual_values ) { + $expected_json = json_encode( $expected_values, JSON_PRETTY_PRINT ); + $actual_json = json_encode( $actual_values, JSON_PRETTY_PRINT ); + if ( false !== $expected_json && false !== $actual_json ) { + $diff = $this->generate_diff( $expected_json, $actual_json ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; + } } } throw new Exception( $message ); From 511948acd0259bdd4fad36d2dabadf4d9b4fb44a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:11:24 +0000 Subject: [PATCH 5/9] Fix typo and correct variable usage in CSV comparison Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Context/ThenStepDefinitions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Context/ThenStepDefinitions.php b/src/Context/ThenStepDefinitions.php index 2d6ef9913..bc2cb3f5b 100644 --- a/src/Context/ThenStepDefinitions.php +++ b/src/Context/ThenStepDefinitions.php @@ -295,7 +295,7 @@ public function then_stdout_should_be_csv_containing( TableNode $expected ): voi $output = $this->result->stdout; $expected_rows = $expected->getRows(); - foreach ( $expected as &$row ) { + foreach ( $expected_rows as &$row ) { foreach ( $row as &$value ) { $value = $this->replace_variables( $value ); } @@ -317,7 +317,7 @@ public function then_stdout_should_be_csv_containing( TableNode $expected ): voi } /** - * Expect STDOUT to be YAML containig certain content. + * Expect STDOUT to be YAML containing certain content. * * ``` * Scenario: My example scenario From 15994e654f9686c556e58e4d004f11d4834a175d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:32:43 +0000 Subject: [PATCH 6/9] Use sebastian/diff v3 for PHP 7.2+ compatibility Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- composer.json | 2 +- src/Context/Support.php | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 6811cc8aa..6e26468e0 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "phpstan/phpstan-deprecation-rules": "^1.2 || ^2.0", "phpstan/phpstan-phpunit": "^1.4 || ^2.0", "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "sebastian/diff": "^3.0", "swissspidy/phpstan-no-private": "^0.2.1 || ^1.0", "szepeviktor/phpstan-wordpress": "^v1.3.5", "wp-cli/config-command": "^1 || ^2", diff --git a/src/Context/Support.php b/src/Context/Support.php index 3e2669acb..65d672de4 100644 --- a/src/Context/Support.php +++ b/src/Context/Support.php @@ -9,7 +9,6 @@ use Exception; use Mustangostang\Spyc; use SebastianBergmann\Diff\Differ; -use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; trait Support { @@ -316,11 +315,7 @@ protected function check_that_yaml_string_contains_yaml_string( $actual_yaml, $e * @return string The unified diff output. */ protected function generate_diff( string $expected, string $actual ): string { - $builder = new UnifiedDiffOutputBuilder( - "--- Expected\n+++ Actual\n", - false - ); - $differ = new Differ( $builder ); + $differ = new Differ( "--- Expected\n+++ Actual\n" ); return $differ->diff( $expected, $actual ); } } From 28c1ca93ed89c938863bec4c9a03c27599d80875 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 14 Nov 2025 10:17:58 +0100 Subject: [PATCH 7/9] Revert "Use sebastian/diff v3 for PHP 7.2+ compatibility" This reverts commit 15994e654f9686c556e58e4d004f11d4834a175d. --- composer.json | 2 +- src/Context/Support.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 6e26468e0..6811cc8aa 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "phpstan/phpstan-deprecation-rules": "^1.2 || ^2.0", "phpstan/phpstan-phpunit": "^1.4 || ^2.0", "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", - "sebastian/diff": "^3.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", "swissspidy/phpstan-no-private": "^0.2.1 || ^1.0", "szepeviktor/phpstan-wordpress": "^v1.3.5", "wp-cli/config-command": "^1 || ^2", diff --git a/src/Context/Support.php b/src/Context/Support.php index 65d672de4..3e2669acb 100644 --- a/src/Context/Support.php +++ b/src/Context/Support.php @@ -9,6 +9,7 @@ use Exception; use Mustangostang\Spyc; use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; trait Support { @@ -315,7 +316,11 @@ protected function check_that_yaml_string_contains_yaml_string( $actual_yaml, $e * @return string The unified diff output. */ protected function generate_diff( string $expected, string $actual ): string { - $differ = new Differ( "--- Expected\n+++ Actual\n" ); + $builder = new UnifiedDiffOutputBuilder( + "--- Expected\n+++ Actual\n", + false + ); + $differ = new Differ( $builder ); return $differ->diff( $expected, $actual ); } } From 03e5fdbda80c8c78a2066426ece07033f86493f0 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 14 Nov 2025 10:19:21 +0100 Subject: [PATCH 8/9] Remove explicit dependency --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 6811cc8aa..8e9091d7a 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,6 @@ "phpstan/phpstan-deprecation-rules": "^1.2 || ^2.0", "phpstan/phpstan-phpunit": "^1.4 || ^2.0", "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", "swissspidy/phpstan-no-private": "^0.2.1 || ^1.0", "szepeviktor/phpstan-wordpress": "^v1.3.5", "wp-cli/config-command": "^1 || ^2", From 4941f64e07cdaf6cf8f27832c9927ec5c56cb3e1 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 14 Nov 2025 10:22:40 +0100 Subject: [PATCH 9/9] Show diff for 'contain' and 'not contain' as well --- src/Context/Support.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Context/Support.php b/src/Context/Support.php index 3e2669acb..c68e2879c 100644 --- a/src/Context/Support.php +++ b/src/Context/Support.php @@ -123,12 +123,9 @@ protected function check_string( $output, $expected, $action, $message = false, $message = $output; } - // Add diff for 'be' comparisons to show exact differences. - if ( 'be' === $action ) { - $diff = $this->generate_diff( $expected, rtrim( $output, "\n" ) ); - if ( ! empty( $diff ) ) { - $message .= "\n\n" . $diff; - } + $diff = $this->generate_diff( $expected, rtrim( $output, "\n" ) ); + if ( ! empty( $diff ) ) { + $message .= "\n\n" . $diff; } throw new Exception( $message );