From 68529af5138b753c802c52e5fdac40a95c484537 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Tue, 23 Jun 2026 07:47:56 +0100 Subject: [PATCH 1/9] docs: Update README with current RTD versions (#229) * Fix the reference to the 3.0 docs version (=> 3.x) * Add the 4.x version * Update to show that `latest` (the default version) now points to 4.x --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ca0d8a4..cf9994b 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ can find build history for each version on RtD. | Version | Status | Docs URL | Build dashboard | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|---------------------------------------------------------------------------------------------------| | v2.5 | [![Documentation Status](https://readthedocs.org/projects/behat/badge/?version=v2.5&style=for-the-badge)](https://docs.behat.org/en/latest/?badge=v2.5) | https://docs.behat.org/en/v2.5/ | [v2.5 build history](https://app.readthedocs.org/projects/behat/builds/?version__slug=v2.5) | -| v3.0 | [![Documentation Status](https://readthedocs.org/projects/behat/badge/?version=v3.0&style=for-the-badge)](https://docs.behat.org/en/latest/?badge=v3.0) | https://docs.behat.org/en/v3.0/ | [v3.0 build history](https://app.readthedocs.org/projects/behat/builds/?version__slug=v3.0) | -| latest* | [![Documentation Status](https://readthedocs.org/projects/behat/badge/?version=latest&style=for-the-badge)](https://docs.behat.org/en/latest/?badge=v3.0&style=for-the-badge) | https://docs.behat.org/en/latest/ | ["latest" build history](https://app.readthedocs.org/projects/behat/builds/?version__slug=latest) | +| v3.x | [![Documentation Status](https://readthedocs.org/projects/behat/badge/?version=v3.x&style=for-the-badge)](https://docs.behat.org/en/latest/?badge=v3.x) | https://docs.behat.org/en/v3.x/ | [v3.x build history](https://app.readthedocs.org/projects/behat/builds/?version__slug=v3.x) | +| v4.x | [![Documentation Status](https://readthedocs.org/projects/behat/badge/?version=v4.x&style=for-the-badge)](https://docs.behat.org/en/latest/?badge=v4.x) | https://docs.behat.org/en/v4.x/ | [v4.x build history](https://app.readthedocs.org/projects/behat/builds/?version__slug=v4.x) | +| latest* | [![Documentation Status](https://readthedocs.org/projects/behat/badge/?version=latest&style=for-the-badge)](https://docs.behat.org/en/latest/?badge=latest&style=for-the-badge) | https://docs.behat.org/en/latest/ | ["latest" build history](https://app.readthedocs.org/projects/behat/builds/?version__slug=latest) | -> \* the "latest" version is currently also based off the v3.0 branch, but is a separate build on RTD. +> \* the "latest" version is currently also based off the v4.x branch, but is a separate build on RTD. ## Project structure From 6be107b58b8fcd4fbdbe89f747d66d241d8a9615 Mon Sep 17 00:00:00 2001 From: Pascal CESCON Date: Thu, 3 Sep 2026 17:01:26 +0200 Subject: [PATCH 2/9] docs: Document the DataTable and DocString step argument types (#231) --- user_guide/writing_scenarios.rst | 41 ++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/user_guide/writing_scenarios.rst b/user_guide/writing_scenarios.rst index 1db7175..9337513 100644 --- a/user_guide/writing_scenarios.rst +++ b/user_guide/writing_scenarios.rst @@ -321,22 +321,35 @@ usually as input to a ``Given`` or as expected output from a ``Then``: .. code-block:: php - use Behat\Gherkin\Node\TableNode; + use Behat\Step\DataTable; use Behat\Step\Given; // ... #[Given('the following people exist:')] - public function thePeopleExist(TableNode $table) + public function thePeopleExist(DataTable $table) { - foreach ($table as $row) { + foreach ($table->asMaps() as $row) { // $row['name'], $row['email'], $row['phone'] } } - A table is injected into a definition as a ``TableNode`` object, from - which you can get hash by columns (``TableNode::getHash()`` method) or by - rows (``TableNode::getRowsHash()``). + Type the parameter as ``DataTable`` and Behat passes the table as such. + It reads the table in whichever shape the step needs: + + * ``asMaps()`` uses the first row as keys, giving one associative array + per following row, which is what the example above iterates over; + * ``asLists()`` returns every row as a plain list of cells, header row + included, for a table with no header; + * ``asMap()`` turns a two-column table into a single associative array, + which suits a table of settings or of expected values; + * ``cell()``, ``row()``, ``column()``, ``height()``, ``width()``, + ``isEmpty()`` and ``transpose()`` cover the rest. + + ``DataTable`` was added in Behat 3.33. Typing the parameter as + ``Behat\Gherkin\Node\TableNode`` still works and is not deprecated, but + ``DataTable`` keeps your definitions decoupled from the Gherkin syntax + tree. Pystrings ^^^^^^^^^ @@ -371,20 +384,24 @@ three double-quote marks (``"""``), placed on their own line: .. code-block:: php - use Behat\Gherkin\Node\PyStringNode; + use Behat\Step\DocString; use Behat\Step\Given; // ... #[Given('a blog post named :title with:')] - public function blogPost($title, PyStringNode $markdown) + public function blogPost($title, DocString $markdown) { - $this->createPost($title, $markdown->getRaw()); + $this->createPost($title, $markdown->getContent()); } - PyStrings are stored in a ``PyStringNode`` instance, which you can simply - convert to a string with ``(string) $pystring`` or ``$pystring->getRaw()`` - as in the example above. + Type the parameter as ``DocString`` to receive the text. Read it with + ``getContent()``, or cast it with ``(string) $markdown``. + + ``DocString`` was added in Behat 3.33. Typing the parameter as + ``Behat\Gherkin\Node\PyStringNode`` still works and is not deprecated, + but ``DocString`` keeps your definitions decoupled from the Gherkin syntax + tree. .. note:: From 940c05d271a874fe53bf4f1685a2f8a5e4d8c88c Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Thu, 24 Sep 2026 17:28:45 +0100 Subject: [PATCH 3/9] ci: Fix actions builds (#233) * ci: crate-cy/typos action no longer has a`master` tag The project have renamed their default branch to `main`, so our build was breaking. It's better to pin something like this to a specific version anyway, so that any new typo detections happen in a version bump commit rather than an unrelated build. * ci: Run on push to all version branches The build was hardcoded to run only on 'v3.0', so wasn't running on push since we renamed the version branch to `v3.x`, or on the new `v4.x` branch (it was only running on PRs). * ci: Add dependabot config to bump Github actions --- .github/dependabot.yml | 18 ++++++++++++++++++ .github/workflows/build.yml | 3 ++- .github/workflows/check.yml | 7 +++++-- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3aa204b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# Dependabot configuration. +# +# Please see the documentation for all configuration options: +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + # Maintain dependencies for GitHub Actions. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + groups: + majors: + applies-to: version-updates + update-types: + - "major" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d9a285..9a2a23e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,8 @@ name: Build Docs on: push: - branches: [v3.0] + branches: + - "v*" pull_request: ~ jobs: diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1e575c6..97e0695 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -2,7 +2,8 @@ name: Check Docs on: push: - branches: [v3.0] + branches: + - "v*" pull_request: null jobs: @@ -28,4 +29,6 @@ jobs: steps: - uses: actions/checkout@v4 - name: Search for misspellings - uses: crate-ci/typos@master + # Pinned to a specific version to avoid unexpected build failures on changes + # to the typos dictionary. + uses: crate-ci/typos@v1.50.2 From a34beffe5a1ab01dda3ad799f97ff31f878acdb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:33:24 +0100 Subject: [PATCH 4/9] chore(deps): bump the majors group with 2 updates (#235) Bumps the majors group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/checkout` from 4 to 7 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v7) Updates `actions/upload-artifact` from 4 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: majors - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: majors ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- .github/workflows/check.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a2a23e..ebc7b24 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Run build command run: docker compose run --rm read-the-docs-builder build @@ -24,7 +24,7 @@ jobs: run: cat _build/html/guides.html - name: save site as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: docs path: _build/html diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 97e0695..3628026 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,7 +14,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install sphinx-lint run: | pip install --user sphinx-lint @@ -27,7 +27,7 @@ jobs: name: Typos runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Search for misspellings # Pinned to a specific version to avoid unexpected build failures on changes # to the typos dictionary. From 0db207aa558ec923f2c4da06e8c18ad7927256bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 17:27:22 +0200 Subject: [PATCH 5/9] chore(deps): bump setuptools from 78.1.1 to 83.0.0 (#230) Bumps [setuptools](https://github.com/pypa/setuptools) from 78.1.1 to 83.0.0. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v78.1.1...v83.0.0) --- updated-dependencies: - dependency-name: setuptools dependency-version: 83.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrew Coulton --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 926b4d4..7786107 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ MarkupSafe==3.0.2 packaging==24.1 Pygments==2.20.0 requests==2.33.0 -setuptools==78.1.1 +setuptools==83.0.0 sniffio==1.3.1 snowballstemmer==2.2.0 Sphinx==8.1.3 From 29dad8d98691a94bd3f6ae76d0226724d4a0107c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 17:27:52 +0200 Subject: [PATCH 6/9] chore(deps): bump anyio from 4.9.0 to 4.14.2 (#232) Bumps [anyio](https://github.com/agronholm/anyio) from 4.9.0 to 4.14.2. - [Release notes](https://github.com/agronholm/anyio/releases) - [Commits](https://github.com/agronholm/anyio/compare/4.9.0...4.14.2) --- updated-dependencies: - dependency-name: anyio dependency-version: 4.14.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrew Coulton --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7786107..70a2963 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ alabaster==1.0.0 -anyio==4.9.0 +anyio==4.14.2 babel==2.16.0 certifi==2024.8.30 charset-normalizer==3.4.0 From fc8376495d6cbb558e25515c502c81f18f94106d Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Fri, 25 Sep 2026 17:25:34 +0100 Subject: [PATCH 7/9] docs: Update assertion tools and PHPUnit assertions information (#234) Strengthen the recommendation against using PHPUnit, and add details of the PHPUnit assertions extension. Also gave a little more info on the well-known assertion libraries (matching what we now say in the PHPUnit assertions extension readme). --- useful_resources.rst | 65 ++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/useful_resources.rst b/useful_resources.rst index 8f9cbc2..06daa5f 100644 --- a/useful_resources.rst +++ b/useful_resources.rst @@ -23,53 +23,38 @@ More information on integrating Behat with PHPStorm can be found in this Assertion tools --------------- -A proper assertion tool is a library whose assertions throw exceptions on failure. +Behat does not officially recommend an assertion library - you can use any code that +throws an Exception on failure. You can use more than one library in parallel (or +no library, for simple assertions). -For example a list of the most known: +Some well-known options are: -- https://github.com/webmozarts/assert -- https://github.com/beberlei/assert -- https://github.com/zenstruck/assert +- `zenstruck/assert`_ - specifically designed for dependency-free test assertions, + which is reflected in the information it provides when assertions fail. +- `beberlei/assert`_ - primarily designed as a fast, lightweight input validation + library for business models and runtime code. +- `webmozarts/assert`_ - inspired by beberlei/assert and also designed for runtime + assertions, but with more control over failure messages. .. admonition:: Caution with PHPUnit :class: caution - If you are familiar with PHPUnit, you can use its assertion library + Behat docs used to suggest using PHPUnit's assertions. PHPUnit's author has + explicitly stated that using PHPUnit assertions outside of PHPUnit itself + is not supported or covered by any backwards compatibility promise. - .. code-block:: bash + We **strongly recommend** that you use a different assertion tool in new + projects. - $ php composer.phar require --dev phpunit/phpunit + If you have PHPUnit assertions in an existing project, we recommend + planning to migrate these. In the meantime, `behat/phpunit-assertions-extension`_ + provides the required bootstrapping on PHPUnit >= 11.3.0, and renders the + details of failing assertions in your Behat output. - and then by simply using assertions in your steps: - - .. code-block:: php - - \PHPUnit\Framework\Assert::assertCount( - intval($count), - $this->basket - ); - - **WARNING: using PHPUnit for assertions no longer works with PHP 11.3.0 and later out-of-the-box**. - - This is due to a change in how PHPUnit's internal components are initialized. The recommended workaround - to use the PHPUnit assertions is to bootstrap PHPUnit during Behat execution from a ``BeforeSuite`` hook: - - .. code-block:: php - - use Behat\Hook\BeforeSuite; - - class FeatureContext { - - #[BeforeSuite] - public static function initPhpunit() { - (new \PHPUnit\TextUI\Configuration\Builder())->build([]); - } - } - - If you have multiple suites, you may want to use a static variable in the hook to ensure the initialization only - runs once. - - Learn more at https://github.com/Behat/Behat/issues/1618. + Behat 3.x has partial support for older PHPUnit versions - we recommend + installing the extension for better compatibility. Behat 4.x does not + have any built-in PHPUnit support so you will need to add the extension + before upgrading. Behat cheat sheet @@ -79,5 +64,9 @@ An interesting `Behat and Mink cheat sheet`_ developed by `Jean-François Lépin .. _`most extensions can be found on GitHub`: https://github.com/search?o=desc&q=behat+extension+in%3Aname%2Cdescription&ref=searchresults&s=stars&type=Repositories&utf8=%E2%9C%93 .. _`blog post`: http://blog.jetbrains.com/phpstorm/2014/07/using-behat-in-phpstorm/ +.. _`webmozarts/assert`: https://github.com/webmozarts/assert +.. _`beberlei/assert`: https://github.com/beberlei/assert +.. _`zenstruck/assert`: https://github.com/zenstruck/assert +.. _`behat/phpunit-assertions-extension`: https://github.com/behat/PHPUnitAssertionsExtension .. _`Behat and Mink cheat sheet`: http://blog.lepine.pro/images/2012-04-behat-cheat-sheet1.pdf .. _`Jean-François Lépine`: http://blog.lepine.pro From b0797498ae5398aa3585f64f508cb26393b50301 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Sat, 26 Sep 2026 16:55:51 +0100 Subject: [PATCH 8/9] doc: Update GherkinCompatibilityMode documentation (#236) For the new mode, and to cover Rule and multiple step args support. --- user_guide/gherkin/parser_mode.rst | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/user_guide/gherkin/parser_mode.rst b/user_guide/gherkin/parser_mode.rst index 23885bb..2076bd4 100644 --- a/user_guide/gherkin/parser_mode.rst +++ b/user_guide/gherkin/parser_mode.rst @@ -12,11 +12,10 @@ To resolve this, we have added a ``GherkinCompatibilityMode`` setting to the par has two possible options: * ``GherkinCompatibilityMode::LEGACY`` - match our previous behaviour. This is the default in Behat 3.x. -* ``GherkinCompatibilityMode::GHERKIN_32`` - match the official parsers. This will become the default in Behat 4.0. - -.. caution:: - ``GherkinCompatibilityMode::GHERKIN_32`` is currently considered experimental. We expect that - there will be more changes to how the parser behaves in this mode before we mark it as stable. + but is not recommended for new projects. +* ``GherkinCompatibilityMode::GHERKIN_32`` - match the official parsers version >= 32.0 < 42.0 +* ``GherkinCompatibilityMode::GHERKIN_42`` - match the official parsers version >= 42.0. This is + the default in Behat 4.x. Configuring the parser mode --------------------------- @@ -159,6 +158,27 @@ In ``GHERKIN_32`` mode, if one of the elements listed above has multi-line text, left padding / indentation as the feature file. In legacy mode, we attempted to left-trim all lines to match the indentation of the keyword. +Rules +~~~~~ + +The Gherkin ``Rule`` keyword is not supported in ``LEGACY`` mode. Rule nodes will either be parsed as part of the +feature description, or cause a ParserException, depending on the nodes that come before them in the file. + +``GHERKIN_32`` mode introduces backwards-compatible support for Rules. Scenarios within Rules will be parsed, filtered, +and executed as expected with any caller. However, callers that have not been updated to support this feature will +receive a modified node tree with all Rule details stripped out. This will behave as though any Scenarios were a direct +child of the Feature - with any Rule Background steps repeated as the first steps of each Scenario. + +Steps with a DataTable **and** a DocString +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Historically, a step could only accept **either** a DataTable **or** a DocString. ``StepNode::getArguments()`` has +always been typed as returning an array but in practice could only return 0 or 1 elements. Therefore in ``LEGACY`` +and ``GHERKIN_32`` mode, we throw a ParserException if a step has more than one multiline argument. + +In ``GHERKIN_42`` mode, a step with a DataTable **and** a DocString is valid. ``StepNode::getArguments()`` will +return both nodes, in the order they appeared in the feature file. It is still not valid to have more than one +argument of any given type (e.g. two tables) - in this case we still throw a ParserException. .. _`behat/gherkin`: http://martinfowler.com/bliki/BusinessReadableDSL.html .. _`the official parsers provided by the Cucumber project`: https://github.com/cucumber/gherkin From ee44b4c185569f6ef018adc75e3d91c8dd556073 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Mon, 28 Sep 2026 18:03:24 +0100 Subject: [PATCH 9/9] docs: Set an EOL for v3.x series (#237) Our policy says that we will set an EOL for each major series at the release of the next major, and that we will give at least 12 months of bugfix and 24 months of security. Now that 4.0 is released, we can set the EOL for 3.x. We have intentionally restricted the scope of 4.0 so that it should be possible to upgrade most of the way with automated tools (Rector to convert to attributes, in-built tools to migrate to PHP config, etc). We also know that 3.x has been out for a long time and is generally stable - we don't get huge numbers of new bug reports. Therefore I think it's fine to offer our minimum 12 month / 24 month EOLs for 3.x. --- releases.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/releases.rst b/releases.rst index b837935..d35e8d2 100644 --- a/releases.rst +++ b/releases.rst @@ -6,12 +6,12 @@ Behat follows `Semantic Versioning`_ - breaking changes will only be made in a m Supported versions ------------------ -======= ========== ========== ============ ======================================================================= -Major Released Bugfix EOL Security EOL -======= ========== ========== ============ ======================================================================= -`v3.x`_ April 2014 See below See below `Changelog `__ -`v4.x`_ tbc 2025/6 See below See below `Changelog `__ -======= ========== ========== ============ ======================================================================= +======= =========== ============ ============ ======================================================================= +Major Released Bugfix EOL Security EOL +======= =========== ============ ============ ======================================================================= +`v3.x`_ April 2014 30 Sep 2027 30 Sep 2028 `Changelog `__ +`v4.x`_ 28 Sep 2026 See below See below `Changelog `__ +======= =========== ============ ============ ======================================================================= As a minimum, a major version series will receive: @@ -54,7 +54,7 @@ By "current", we mean: * Symfony versions that are listed as maintained or receiving security fixes on the `official Symfony releases page`_. Note that Symfony 8 introduces breaking changes to interfaces that Behat cannot support without ourselves making -breaking changes. Therefore, Symfony 8 will only be supported from Behat 4.0 onwards. +breaking changes. Therefore, Symfony 8 is only supported from Behat 4.0 onwards. Once a PHP or Symfony version reaches End of Life we will remove it from our composer.json and CI flows.