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..ebc7b24 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: @@ -14,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 @@ -23,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 1e575c6..3628026 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: @@ -13,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 @@ -26,6 +27,8 @@ jobs: name: Typos runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - 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 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 diff --git a/releases.rst b/releases.rst index c9da00a..7891a88 100644 --- a/releases.rst +++ b/releases.rst @@ -8,12 +8,12 @@ can read more in :doc:`the Backward Compatibility documentation `__ -`v4.x`_ tbc Q3/26 See below See below `4.x Changelog`_ :doc:`Upgrading ` -======= ========== ========== ============ ==================================================================== +======= =========== ============ ============ ======================================================================= +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 `4.x Changelog`_ :doc:`Upgrading ` +======= =========== ============ ============ ======================================================================= As a minimum, a major version series will receive: @@ -56,7 +56,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. diff --git a/requirements.txt b/requirements.txt index 926b4d4..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 @@ -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 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 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 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::