Repository: Behat/Behat
Pull request #1611: feat: support providing multiple paths
Author: login: marmichalski
Created: 2025-03-29T14:45:26Z | Merged: 2025-05-08T13:02:28Z

Resolves #834.

This PR adds support for providing multiple paths to behat cli: `behat one.feature two.feature nested/features/`

There is a room for improvement to deduplicate the features, but I do not consider this a critical requirement, as this can be considered user error for now. The example of this would be: `behat features/a/one.feature features/a`

Linked issue #834: Running multiple *.feature files
Currently there is no way to run behat with multiple *.feature files in one execution. 
Use case is running suite in parallel. I want to run half my features on one server and another half on the other.

For example

``` bash
bin/behat $(find ./features -name "*.feature"  | sed -n '0~2p'  | tr '\n' ' ')
```

but currently it throws RuntimeException "Too many arguments."

Linked issue comments: user: login: avant1
created_at: 2016-01-26T19:06:29Z
body: Are you familiar with https://github.com/m00t/behat-partial-runner?
We are using this extension for parallel tests execution.

But I agree, passing more than one `.feature` file to be runned can be useful.

author_association: CONTRIBUTOR
user: login: nytche
created_at: 2016-01-27T08:32:54Z
body: And why don't you use tags?

author_association: NONE
user: login: eugene-dounar
created_at: 2016-01-27T08:46:57Z
body: Tags like @thread1 @thread2 are ugly and do not allow dynamically setting
number of parallel instances. Moreover - -tags option overwrite my suite
tags

ср, 27 янв. 2016, 11:33, Nitche notifications@github.com:

> And why don't you use tags?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/Behat/Behat/issues/834#issuecomment-175479483.

author_association: NONE
user: login: eugene-dounar
created_at: 2016-01-27T08:48:03Z
body: Thanks, looks like what I need

вт, 26 янв. 2016, 22:06, Kirill notifications@github.com:

> Are you familiar with https://github.com/m00t/behat-partial-runner?
> We are using this extension for parallel tests execution.
> 
> But I agree, passing more than one .feature file to be runned can be
> useful.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/Behat/Behat/issues/834#issuecomment-175181567.

author_association: NONE
user: login: frankcarey
created_at: 2016-01-27T22:46:04Z
body: While I agree that behat should support setting more than one feature file, we have a workaround for circleCI that nicely handles parallelism. 

See https://github.com/NuCivic/dkan/blob/7.x-1.x/test/circle-behat.sh
You all it like:

`bash circle-behat.sh some/folder some/file.feature some/other/folder`

and it should work.

Downside is that if you are interested in creating junit output, each call overwrites the previous call when creating the default.xml file.

So, I think this is an important feature request. Behat should be able to handle this.

Related issue: https://github.com/Behat/Behat/issues/808

author_association: NONE
user: login: TerjeBr
created_at: 2018-01-29T19:26:25Z
body: @everzet Why is this closed?

Has this feature been implemented?

author_association: NONE
user: login: spolischook
created_at: 2018-01-30T15:37:08Z
body: @TerjeBr here is possible solution https://github.com/oroinc/platform/tree/master/src/Oro/Bundle/TestFrameworkBundle/BehatStatisticExtension
author_association: NONE
user: login: webmozart
created_at: 2021-05-03T11:55:24Z
body: I too wonder why this has been closed?

This would be very useful for analyzing scoping issues where one feature affects another due to shared state. Temporarily setting a tag for that bears the risk of committing that tag. That doesn't sound like a solution to me.
author_association: NONE

Diff:
diff --git a/features/name_filters.feature b/features/name_filters.feature
index 33c97974c..e83a58eb6 100644
--- a/features/name_filters.feature
+++ b/features/name_filters.feature
@@ -1,7 +1,7 @@
 Feature: Name filters
   In order to run only needed features
   As a Behat user
-  I need to Behat support features & scenario/outline names filtering
+  I need Behat to support features & scenario/outline names filtering
 
   Background:
     Given a file named "features/bootstrap/FeatureContext.php" with:
diff --git a/features/path_filters.feature b/features/path_filters.feature
new file mode 100644
index 000000000..a37164605
--- /dev/null
+++ b/features/path_filters.feature
@@ -0,0 +1,167 @@
+Feature: Path filters
+  In order to run only needed features
+  As a Behat user
+  I need Behat to support path(s) filtering
+
+  Background:
+    Given I set the working directory to the "PathFilters" fixtures folder
+    And I provide the following options for all behat invocations:
+      | option      | value   |
+      | --no-colors |         |
+      | --format    | pretty  |
+
+  Scenario: First feature file only
+    When I run "behat features/a/feature1.feature"
+    Then it should pass with:
+      """
+      Feature: First Feature
+
+        Scenario: First Scenario   # features/a/feature1.feature:3
+          Given Some slow step N12 # FeatureContext::someSlowStepN()
+          And Some normal step N13 # FeatureContext::someNormalStepN()
+
+        Scenario: Second Scenario  # features/a/feature1.feature:7
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+
+      2 scenarios (2 passed)
+      3 steps (3 passed)
+      """
+
+  Scenario: Second feature file only
+    When I run "behat features/a/feature2.feature"
+    Then it should pass with:
+      """
+      Feature: Second Feature
+
+        Background:                  # features/a/feature2.feature:3
+          Given Some normal step N21 # FeatureContext::someNormalStepN()
+
+        Scenario: First Scenario   # features/a/feature2.feature:6
+          Given Some slow step N22 # FeatureContext::someSlowStepN()
+          And Some fast step N23   # FeatureContext::someFastStepN()
+
+      1 scenario (1 passed)
+      3 steps (3 passed)
+      """
+
+  Scenario: Both feature files
+    When I run "behat features/a/feature1.feature features/a/feature2.feature"
+    Then it should pass with:
+      """
+      Feature: First Feature
+
+        Scenario: First Scenario   # features/a/feature1.feature:3
+          Given Some slow step N12 # FeatureContext::someSlowStepN()
+          And Some normal step N13 # FeatureContext::someNormalStepN()
+
+        Scenario: Second Scenario  # features/a/feature1.feature:7
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+
+      Feature: Second Feature
+
+        Background:                  # features/a/feature2.feature:3
+          Given Some normal step N21 # FeatureContext::someNormalStepN()
+
+        Scenario: First Scenario   # features/a/feature2.feature:6
+          Given Some slow step N22 # FeatureContext::someSlowStepN()
+          And Some fast step N23   # FeatureContext::someFastStepN()
+
+      3 scenarios (3 passed)
+      6 steps (6 passed)
+      """
+
+    Scenario: Single nested directory
+      When I run "behat features/a"
+      Then it should pass with:
+      """
+      Feature: First Feature
+
+        Scenario: First Scenario   # features/a/feature1.feature:3
+          Given Some slow step N12 # FeatureContext::someSlowStepN()
+          And Some normal step N13 # FeatureContext::someNormalStepN()
+
+        Scenario: Second Scenario  # features/a/feature1.feature:7
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+
+      Feature: Second Feature
+
+        Background:                  # features/a/feature2.feature:3
+          Given Some normal step N21 # FeatureContext::someNormalStepN()
+
+        Scenario: First Scenario   # features/a/feature2.feature:6
+          Given Some slow step N22 # FeatureContext::someSlowStepN()
+          And Some fast step N23   # FeatureContext::someFastStepN()
+
+      3 scenarios (3 passed)
+      6 steps (6 passed)
+      """
+
+  Scenario: Directory with nested directories
+    When I run "behat features"
+    Then it should pass with:
+      """
+      Feature: First Feature
+
+        Scenario: First Scenario   # features/a/feature1.feature:3
+          Given Some slow step N12 # FeatureContext::someSlowStepN()
+          And Some normal step N13 # FeatureContext::someNormalStepN()
+
+        Scenario: Second Scenario  # features/a/feature1.feature:7
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+
+      Feature: Second Feature
+
+        Background:                  # features/a/feature2.feature:3
+          Given Some normal step N21 # FeatureContext::someNormalStepN()
+
+        Scenario: First Scenario   # features/a/feature2.feature:6
+          Given Some slow step N22 # FeatureContext::someSlowStepN()
+          And Some fast step N23   # FeatureContext::someFastStepN()
+
+      Feature: Third Feature
+
+        Scenario: First Scenario   # features/b/feature1.feature:3
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+
+        Scenario: Second Scenario  # features/b/feature1.feature:6
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+          And Some fast step N14   # FeatureContext::someFastStepN()
+
+      5 scenarios (5 passed)
+      9 steps (9 passed)
+      """
+
+    Scenario: Single scenario from single feature file
+      When I run "behat features/a/feature1.feature:7"
+      Then it should pass with:
+      """
+      Feature: First Feature
+
+        Scenario: Second Scenario  # features/a/feature1.feature:7
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+
+      1 scenario (1 passed)
+      1 step (1 passed)
+      """
+
+  Scenario: Single scenario from each feature file
+    When I run "behat features/a/feature1.feature:7 features/a/feature2.feature:6"
+    Then it should pass with:
+      """
+      Feature: First Feature
+
+        Scenario: Second Scenario  # features/a/feature1.feature:7
+          Given Some fast step N14 # FeatureContext::someFastStepN()
+
+      Feature: Second Feature
+
+        Background:                  # features/a/feature2.feature:3
+          Given Some normal step N21 # FeatureContext::someNormalStepN()
+
+        Scenario: First Scenario   # features/a/feature2.feature:6
+          Given Some slow step N22 # FeatureContext::someSlowStepN()
+          And Some fast step N23   # FeatureContext::someFastStepN()
+
+      2 scenarios (2 passed)
+      4 steps (4 passed)
+      """
diff --git a/features/tag_filters.feature b/features/tag_filters.feature
index e2a95b185..06eefc798 100644
--- a/features/tag_filters.feature
+++ b/features/tag_filters.feature
@@ -1,7 +1,7 @@
 Feature: Tags
   In order to run only needed features
   As a Behat user
-  I need to Behat support features & scenario/outline tags
+  I need Behat to support features & scenario/outline tags
 
   Background:
     Given a file named "features/bootstrap/FeatureContext.php" with:
diff --git a/src/Behat/Behat/Tester/Cli/RerunController.php b/src/Behat/Behat/Tester/Cli/RerunController.php
index d654f73e6..1b9e61c79 100644
--- a/src/Behat/Behat/Tester/Cli/RerunController.php
+++ b/src/Behat/Behat/Tester/Cli/RerunController.php
@@ -97,7 +97,7 @@ public function execute(InputInterface $input, OutputInterface $output)
             return null;
         }
 
-        $input->setArgument('paths', $this->getFileName());
+        $input->setArgument('paths', [$this->getFileName()]);
 
         return null;
     }
@@ -155,7 +155,7 @@ private function generateKey(InputInterface $input)
             implode(' ', $input->getOption('name')) .
             implode(' ', $input->getOption('tags')) .
             $input->getOption('role') .
-            $input->getArgument('paths') .
+            \implode($input->getArgument('paths')) .
             $this->basepath
         );
     }
diff --git a/src/Behat/Testwork/Specification/SpecificationFinder.php b/src/Behat/Testwork/Specification/SpecificationFinder.php
index faaf63e9c..0dfe2f231 100644
--- a/src/Behat/Testwork/Specification/SpecificationFinder.php
+++ b/src/Behat/Testwork/Specification/SpecificationFinder.php
@@ -56,11 +56,10 @@ public function getExampleLocators()
      * Finds all specifications for all provided suites matching provided locator and wraps them into a spec iterator.
      *
      * @param Suite[]     $suites
-     * @param string|null $locator
      *
      * @return list<SpecificationIterator<T>>
      */
-    public function findSuitesSpecifications(array $suites, $locator = null)
+    public function findSuitesSpecifications(array $suites, ?string $locator = null)
     {
         $iterators = [];
         foreach ($suites as $suite) {
diff --git a/src/Behat/Testwork/Tester/Cli/ExerciseController.php b/src/Behat/Testwork/Tester/Cli/ExerciseController.php
index 966bb9d71..e92b166e1 100644
--- a/src/Behat/Testwork/Tester/Cli/ExerciseController.php
+++ b/src/Behat/Testwork/Tester/Cli/ExerciseController.php
@@ -87,8 +87,8 @@ function ($locator) {
         $command
             ->addArgument(
                 'paths',
-                InputArgument::OPTIONAL,
-                'Optional path(s) to execute. Could be:' . PHP_EOL . $locatorsExamples
+                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
+                'Optional path(s) to execute. Could be:' . PHP_EOL . $locatorsExamples,
             )
             ->addOption(
                 '--dry-run',
@@ -109,16 +109,17 @@ function ($locator) {
      */
     public function execute(InputInterface $input, OutputInterface $output)
     {
-        $specs = $this->findSpecifications($input);
+        $paths = $this->extractUniquePaths($input);
+        $specs = $this->findSpecifications($paths);
         $result = $this->testSpecifications($input, $specs);
 
-        if ($input->getArgument('paths') && !$input->getOption('allow-no-tests') && TestResults::NO_TESTS === $result->getResultCode()) {
+        if ($paths !== null && !$input->getOption('allow-no-tests') && TestResults::NO_TESTS === $result->getResultCode()) {
             throw new WrongPathsException(
                 sprintf(
                     'No specifications found at path(s) `%s`. This might be because of incorrect paths configuration in your `suites`.',
-                    $input->getArgument('paths')
+                    implode(', ', $paths)
                 ),
-                $input->getArgument('paths')
+                implode(', ', $paths)
             );
         }
 
@@ -128,11 +129,24 @@ public function execute(InputInterface $input, OutputInterface $output)
     /**
      * Finds exercise specifications.
      *
+     * @param list<string>|null $paths
+     *
      * @return SpecificationIterator[]
      */
-    private function findSpecifications(InputInterface $input)
+    private function findSpecifications(?array $paths): array
     {
-        return $this->findSuitesSpecifications($this->getAvailableSuites(), $input->getArgument('paths'));
+        $availableSuites = $this->getAvailableSuites();
+        if ($paths === null) {
+            return $this->findSuitesSpecifications($availableSuites, null);
+        }
+
+        $specifications = [];
+
+        foreach ($paths as $path) {
+            $specifications = array_merge($specifications, $this->findSuitesSpecifications($availableSuites, $path));
+        }
+
+        return $specifications;
     }
 
     /**
@@ -170,12 +184,24 @@ private function getAvailableSuites()
      * Finds specification iterators for all provided suites using locator.
      *
      * @param Suite[]     $suites
-     * @param string|null $locator
      *
      * @return SpecificationIterator[]
      */
-    private function findSuitesSpecifications($suites, $locator)
+    private function findSuitesSpecifications(array $suites, ?string $locator): array
     {
         return $this->specificationFinder->findSuitesSpecifications($suites, $locator);
     }
+
+    /**
+     * Extracts unique paths from input argument. Returns null if no paths were supplied.
+     */
+    private function extractUniquePaths(InputInterface $input): ?array
+    {
+        $paths = $input->getArgument('paths') ?: null;
+        if ($paths === null) {
+            return null;
+        }
+
+        return array_values(array_unique($paths));
+    }
 }
diff --git a/tests/Fixtures/PathFilters/features/a/feature1.feature b/tests/Fixtures/PathFilters/features/a/feature1.feature
new file mode 100644
index 000000000..93b4a7ac0
--- /dev/null
+++ b/tests/Fixtures/PathFilters/features/a/feature1.feature
@@ -0,0 +1,8 @@
+Feature: First Feature
+
+  Scenario: First Scenario
+    Given Some slow step N12
+    And Some normal step N13
+
+  Scenario: Second Scenario
+    Given Some fast step N14
diff --git a/tests/Fixtures/PathFilters/features/a/feature2.feature b/tests/Fixtures/PathFilters/features/a/feature2.feature
new file mode 100644
index 000000000..6badf3911
--- /dev/null
+++ b/tests/Fixtures/PathFilters/features/a/feature2.feature
@@ -0,0 +1,8 @@
+Feature: Second Feature
+
+  Background:
+    Given Some normal step N21
+
+  Scenario: First Scenario
+    Given Some slow step N22
+    And Some fast step N23
diff --git a/tests/Fixtures/PathFilters/features/b/feature1.feature b/tests/Fixtures/PathFilters/features/b/feature1.feature
new file mode 100644
index 000000000..4b5ddafc8
--- /dev/null
+++ b/tests/Fixtures/PathFilters/features/b/feature1.feature
@@ -0,0 +1,8 @@
+Feature: Third Feature
+
+  Scenario: First Scenario
+    Given Some fast step N14
+
+  Scenario: Second Scenario
+    Given Some fast step N14
+    And Some fast step N14
diff --git a/tests/Fixtures/PathFilters/features/bootstrap/FeatureContext.php b/tests/Fixtures/PathFilters/features/bootstrap/FeatureContext.php
new file mode 100644
index 000000000..8f4d77cb1
--- /dev/null
+++ b/tests/Fixtures/PathFilters/features/bootstrap/FeatureContext.php
@@ -0,0 +1,22 @@
+<?php
+
+use Behat\Behat\Context\Context;
+use Behat\Step\Given;
+
+class FeatureContext implements Context
+{
+    #[Given('/^Some slow step N(\d+)$/')]
+    public function someSlowStepN($num)
+    {
+    }
+
+    #[Given('/^Some normal step N(\d+)$/')]
+    public function someNormalStepN($num)
+    {
+    }
+
+    #[Given('/^Some fast step N(\d+)$/')]
+    public function someFastStepN($num)
+    {
+    }
+}


Reviews:
user: login: marmichalski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T14:45:40Z
user: login: acoulton
body: @marmichalski this looks good to me, and very helpful, thanks.

I agree that it's probably not necessary to try and detect / warn about overlapping paths.

Only question is about the feature coverage - I think it would probably also be worth specifying behaviour when providing directories, and also when specifying specific scenarios e.g. `features/feature1.feature:7`? I had a quick look and I can't see existing features that cover these so it would be good to make sure we're not breaking anything.

I'd suggest restructuring from the current "everything in one .feature file" to the newer approach with a fixtures directory that @carlos-granados used in e.g. #1593  - that would make it easier to have more than 2 feature files in nested directories and reduce the verbosity of the setup in the `.feature` file itself.

Are you happy to take a look at that?

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T10:06:15Z
user: login: carlos-granados
body: Overall the functionality seems correct and well tested, I just added a couple of small comments/questions
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T17:40:52Z
user: login: marmichalski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:02:27Z
user: login: marmichalski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:03:10Z
user: login: carlos-granados
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:03:45Z
user: login: carlos-granados
body: Good job @marmichalski we'll let you know once we have been able to solve the failing test
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:04:28Z
user: login: acoulton
body: I've merged master into this to pick up the gherkin fix and it's now green. The PR looks good to me, thanks @marmichalski :-)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T10:42:13Z
user: login: carlos-granados
body: Looks good to me as well, thanks @marmichalski 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T11:11:38Z

Review comments:
diff_hunk: @@ -75,7 +75,7 @@
         ],
         "behat": "LANG=C bin/behat  --rerun",
         "behat-progress": "LANG=C bin/behat --format=progress",
-        "phpstan": "phpstan",
+        "phpstan": "phpstan --memory-limit=-1",
path: composer.json
user: login: marmichalski
body: was failing for me locally, can revert as unrelated
created_at: 2025-03-29T14:45:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,7 +75,7 @@
         ],
         "behat": "LANG=C bin/behat  --rerun",
         "behat-progress": "LANG=C bin/behat --format=progress",
-        "phpstan": "phpstan",
+        "phpstan": "phpstan --memory-limit=-1",
path: composer.json
user: login: acoulton
body: Yes, please revert
created_at: 2025-04-02T09:48:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+Feature: First Feature
+
+  Scenario: First Scenario
+    Given Some slow step N12
path: tests/Fixtures/PathFilters/features/a/feature1.feature
user: login: carlos-granados
body: What are these NX numbers? Do they serve any purpose? They seem weird
created_at: 2025-05-07T17:36:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+<?php
+
+use Behat\Behat\Context\Context;
+use Behat\Step\Given;
+
+class FeatureContext implements Context
+{
+    #[Given('/^Some slow step N(\d+)$/')]
+    public function someSlowStepN($num)
path: tests/Fixtures/PathFilters/features/bootstrap/FeatureContext.php
user: login: carlos-granados
body: I understand that the name of these steps does not actually matter, but naming them slow, normal and fast may induce users to think that they have something to do with the speed of the test, when it doesn't. I would probable name them something more neutral like `firstStep`, `secondStep` or something like that
created_at: 2025-05-07T17:38:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+Feature: First Feature
+
+  Scenario: First Scenario
+    Given Some slow step N12
path: tests/Fixtures/PathFilters/features/a/feature1.feature
user: login: marmichalski
body: I just copied the features you guys use:
https://github.com/search?q=repo%3ABehat%2FBehat%20N14&type=code
created_at: 2025-05-07T18:02:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+<?php
+
+use Behat\Behat\Context\Context;
+use Behat\Step\Given;
+
+class FeatureContext implements Context
+{
+    #[Given('/^Some slow step N(\d+)$/')]
+    public function someSlowStepN($num)
path: tests/Fixtures/PathFilters/features/bootstrap/FeatureContext.php
user: login: marmichalski
body: https://github.com/search?q=repo%3ABehat%2FBehat%20someSlowStepN&type=code

Happy to change it to whatever though...
created_at: 2025-05-07T18:03:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+Feature: First Feature
+
+  Scenario: First Scenario
+    Given Some slow step N12
path: tests/Fixtures/PathFilters/features/a/feature1.feature
user: login: carlos-granados
body: Ah, yeah 🤣 then ignore me
created_at: 2025-05-07T18:03:45Z
author_association: CONTRIBUTOR

PR comments:
user: login: marmichalski
created_at: 2025-04-02T14:04:06Z
body: > @marmichalski this looks good to me, and very helpful, thanks.
> 
> Are you happy to take a look at that?

Yea, sure. I'll see what I can do
author_association: CONTRIBUTOR
user: login: marmichalski
created_at: 2025-05-06T18:00:46Z
body: > @marmichalski this looks good to me, and very helpful, thanks.
> 
> Only question is about the feature coverage - I think it would probably also be worth specifying behaviour when providing directories, and also when specifying specific scenarios e.g. `features/feature1.feature:7`? I had a quick look and I can't see existing features that cover these so it would be good to make sure we're not breaking anything.

This has been covered, also covered directories filtering.

> I'd suggest restructuring from the current "everything in one .feature file" to the newer approach with a fixtures directory that @carlos-granados used in e.g. #1593 - that would make it easier to have more than 2 feature files in nested directories and reduce the verbosity of the setup in the `.feature` file itself.
> 

I've had a look at what you are proposing here, but all of the scenarios in this file require quite the same setup - we assert that the features were executed only from properly filtered paths I am not sure if it's worth splitting them out to separate feature files? I believe this setup adds more confidence in the filtering too, eg when you provide `features/a` as path that `features/b` is not picked up
author_association: CONTRIBUTOR
user: login: acoulton
created_at: 2025-05-07T06:28:32Z
body: @marmichalski thanks for this - it looks good to me. The test failures are due to #1635 - we'll get that figured out and then we will be able to see this green.

>> I'd suggest restructuring from the current "everything in one .feature file" to the newer approach with a fixtures directory...

> I've had a look at what you are proposing here, but all of the scenarios in this file require quite the same setup...

I probably wasn't very clear. I was suggesting that instead of the current "Given a file named ..." in the background, you would have those as actual files in a structure like:

```
tests\Fixtures\PathFilters:
  - features
    - bootstrap
       - FeatureContext.php
    - a
      - feature1.feature
      - feature2.feature
    - b
      - feature1.feature
```

Then this feature would look more like:

```gherkin
  Background:
    Given I set the working directory to the "PathFilters" fixtures folder
    And I provide the following options for all behat invocations:
      | option      | value  |
      | --no-colors |        |
      | --f         | pretty |

  Scenario: First feature file only
    When  I run behat with the following additional options:
      | option                      | value   |
      | features/a/feature1.feature |         |
```

This makes it easier to see the actual directory structure of the files in the fixtures directory. It also reduces the noise from the top of the feature and the "when I run behat" steps (things like the FeatureContext implementation, or the `-f pretty`, all of which are required but not directly relevant to what we're testing here).

If you'd like to have a go at that that would be great, if not I'd be happy with this as-is and we can migrate it to the newer feature / fixtures approach in due course.
author_association: CONTRIBUTOR
user: login: marmichalski
created_at: 2025-05-07T06:41:01Z
body: > @marmichalski thanks for this - it looks good to me. The test failures are due to #1635 - we'll get that figured out and then we will be able to see this green.
> 
> > > I'd suggest restructuring from the current "everything in one .feature file" to the newer approach with a fixtures directory...
> 
> > I've had a look at what you are proposing here, but all of the scenarios in this file require quite the same setup...
> 
> ...
> I probably wasn't very clear. I was suggesting that instead of the current "Given a file named ..." in the background, you would have those as actual files in a structure like:
> This makes it easier to see the actual directory structure of the files in the fixtures directory. It also reduces the noise from the top of the feature and the "when I run behat" steps (things like the FeatureContext implementation, or the `-f pretty`, all of which are required but not directly relevant to what we're testing here).
> If you'd like to have a go at that that would be great, if not I'd be happy with this as-is and we can migrate it to the newer feature / fixtures approach in due course.

Ah sorry! Added fixtures.

The CI failure seems unrelated - the translations are different when you run the tests with latest dependencies 😢 
author_association: CONTRIBUTOR