diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 5e833f128..40a4f8659 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -94,6 +94,9 @@ jobs:
run: |
APP_LOCALE=fr vendor/bin/phpunit --testsuite="TranslationsTestSuite"
+ - name: Run Behat
+ run: vendor/bin/behat --format=progress --strict
+
- name: "Restrict packages' versions (Admin Ui)"
run: |
(cd src/AdminUi/ && composer global config --no-plugins allow-plugins.symfony/flex true)
@@ -107,6 +110,19 @@ jobs:
- name: Run PHPUnit (Admin Ui)
run: (cd src/AdminUi/ && vendor/bin/phpunit)
+ - name: "Restrict packages' versions (Behat Bridge)"
+ run: |
+ (cd src/BehatBridge/ && composer global config --no-plugins allow-plugins.symfony/flex true)
+ (cd src/BehatBridge/ && composer global config --no-plugins allow-plugins.symfony/runtime true)
+ (cd src/BehatBridge/ && composer global require --no-progress --no-scripts --no-plugins "symfony/flex")
+ (cd src/BehatBridge/ && composer config extra.symfony.require "${{ matrix.symfony }}")
+
+ - name: "Install dependencies (Behat Bridge)"
+ run: (cd src/BehatBridge/ && composer update --no-interaction --no-scripts)
+
+ - name: Run PHPUnit (Behat Bridge)
+ run: (cd src/BehatBridge/ && vendor/bin/phpunit)
+
- name: "Restrict packages' versions (Bootstrap Admin Ui)"
run: |
(cd src/BootstrapAdminUi/ && composer global config --no-plugins allow-plugins.symfony/flex true)
diff --git a/behat.dist.yaml b/behat.dist.yaml
new file mode 100644
index 000000000..86f388389
--- /dev/null
+++ b/behat.dist.yaml
@@ -0,0 +1,16 @@
+imports:
+ - config/behat/suites/ui/book/managing_books.yaml
+
+default:
+ extensions:
+ Behat\MinkExtension:
+ base_url: "https://127.0.0.1:8080/"
+ default_session: symfony
+ sessions:
+ symfony:
+ symfony: ~
+ show_auto: false
+
+ FriendsOfBehat\SymfonyExtension:
+ bootstrap: tests/bootstrap.php
+
diff --git a/composer.json b/composer.json
index dd7def1b6..4fa535442 100644
--- a/composer.json
+++ b/composer.json
@@ -42,7 +42,13 @@
"webmozart/assert": "^1.9"
},
"require-dev": {
+ "behat/behat": "^3.16",
"doctrine/doctrine-fixtures-bundle": "^3.6",
+ "friends-of-behat/mink": "^1.11",
+ "friends-of-behat/mink-browserkit-driver": "^1.6",
+ "friends-of-behat/mink-extension": "^2.7",
+ "friends-of-behat/page-object-extension": "^0.3.2",
+ "friends-of-behat/symfony-extension": "^2.6",
"matthiasnoback/symfony-config-test": "^5.1",
"matthiasnoback/symfony-dependency-injection-test": "^5.1",
"phpstan/phpstan": "^1.10",
@@ -69,6 +75,7 @@
"autoload": {
"psr-4": {
"Sylius\\AdminUi\\": "src/AdminUi/src/",
+ "Sylius\\BehatBridge\\": "src/BehatBridge/src/",
"Sylius\\BootstrapAdminUi\\": "src/BootstrapAdminUi/src/",
"Sylius\\TwigExtra\\": "src/TwigExtra/src/",
"Sylius\\TwigHooks\\": "src/TwigHooks/src/",
diff --git a/config/behat/suites/ui/book/managing_books.yaml b/config/behat/suites/ui/book/managing_books.yaml
new file mode 100644
index 000000000..1bb55eac2
--- /dev/null
+++ b/config/behat/suites/ui/book/managing_books.yaml
@@ -0,0 +1,11 @@
+default:
+ suites:
+ ui_managing_books:
+ contexts:
+ - Sylius\BehatBridge\Behat\Context\Hook\DoctrineORMContext
+ - Sylius\BehatBridge\Behat\Context\Transform\SharedStorageContext
+ - MainTests\Sylius\Behat\Context\Setup\BookContext
+ - MainTests\Sylius\Behat\Context\Ui\ManagingBooksContext
+ - MainTests\Sylius\Behat\Context\Domain\BookContext
+ filters:
+ tags: "@managing_books&&@ui"
diff --git a/config/bundles.php b/config/bundles.php
index 3b7656806..234219813 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -4,11 +4,12 @@
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true],
+ Sylius\AdminUi\Symfony\SyliusAdminUiBundle::class => ['all' => true],
+ Sylius\BehatBridge\Symfony\SyliusBehatBridgeBundle::class => ['test' => true],
+ Sylius\BootstrapAdminUi\Symfony\SyliusBootstrapAdminUiBundle::class => ['all' => true],
Sylius\TwigHooks\SyliusTwigHooksBundle::class => ['all' => true],
Sylius\TwigExtra\Symfony\SyliusTwigExtraBundle::class => ['all' => true],
- Sylius\AdminUi\Symfony\SyliusAdminUiBundle::class => ['all' => true],
Sylius\UiTranslations\Symfony\SyliusUiTranslationsBundle::class => ['all' => true],
- Sylius\BootstrapAdminUi\Symfony\SyliusBootstrapAdminUiBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true],
@@ -25,4 +26,5 @@
Symfony\UX\Autocomplete\AutocompleteBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\UX\Icons\UXIconsBundle::class => ['all' => true],
+ FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true],
];
diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index 455e23d85..b595e56d6 100644
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -37,6 +37,7 @@ security:
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin/logout, roles: PUBLIC_ACCESS }
+ - { path: ^/admin/books, roles: PUBLIC_ACCESS } # TODO: to remove, just to simplify for now
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: PUBLIC_ACCESS }
# - { path: ^/profile, roles: ROLE_USER }
diff --git a/config/services_test.yaml b/config/services_test.yaml
new file mode 100644
index 000000000..f4cb6c623
--- /dev/null
+++ b/config/services_test.yaml
@@ -0,0 +1,13 @@
+services:
+ _defaults:
+ autowire: true
+ autoconfigure: true
+
+ # TODO should be autoconfigured on friends-of-behat/page-object-extension
+ _instanceof:
+ FriendsOfBehat\PageObjectExtension\Page\SymfonyPage:
+ bind:
+ $minkParameters: '@behat.mink.parameters'
+
+ MainTests\Sylius\Behat\:
+ resource: '../tests/Behat/*'
diff --git a/docs/README.md b/docs/README.md
index d631f64e5..4eace97e6 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,6 +9,7 @@ The Sylius stack is a set of tools for your Symfony projects:
* [**TwigExtra:** Additional Twig extensions for your Symfony projects](twig-extra/getting-started.md)
* [**TwigHooks:** Composable Twig layouts](twig-hooks/getting-started.md)
* [**UiTranslations:** Basic UI translations](ui-translations/getting-started.md)
+* [..BehatBridge:** Additional Behat helpers for your Symfony projects](behat-bridge/getting-started.md)
đź“– Documentation
----------------
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 8b27b0eec..a7319fbe6 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -69,3 +69,7 @@
* [Metadata objects](twig-hooks/advanced/metadata-objects.md)
* [Multiple hooks inside a single template](twig-hooks/advanced/multiple-hooks-inside-a-single-template.md)
* [Overriding hookables](twig-hooks/advanced/overriding-hookables.md)
+
+## Behat Bridge
+
+* [Getting started](behat-bridge/getting-started.md)
diff --git a/docs/behat-bridge/getting-started.md b/docs/behat-bridge/getting-started.md
new file mode 100644
index 000000000..04eff47ba
--- /dev/null
+++ b/docs/behat-bridge/getting-started.md
@@ -0,0 +1,14 @@
+---
+description: >-
+ Behat bridge facilitates Behat usage on your Symfony projects.
+---
+
+# Getting started
+
+## Installation
+
+Install the package using Composer and Symfony Flex:
+
+```bash
+composer require sylius/behat-bridge
+```
diff --git a/ecs.php b/ecs.php
index f05b3cb3d..b4bc16d2f 100644
--- a/ecs.php
+++ b/ecs.php
@@ -9,6 +9,7 @@
$ecsConfig->paths([
__DIR__ . '/app',
__DIR__ . '/src',
+ __DIR__ . '/tests',
]);
$ecsConfig->import('vendor/sylius-labs/coding-standard/ecs.php');
diff --git a/features/admin/book/managing_books/adding_books.feature b/features/admin/book/managing_books/adding_books.feature
new file mode 100644
index 000000000..c76a38ee2
--- /dev/null
+++ b/features/admin/book/managing_books/adding_books.feature
@@ -0,0 +1,14 @@
+@managing_books
+Feature: Adding a new book
+ In order to manage the library
+ As an Administrator
+ I want to add a new book
+
+ @ui
+ Scenario: Adding a new book
+ When I want to create a new book
+ And I name it "Carrie"
+ And I specify its author as "Stephen King"
+ And I add it
+ Then the book "Carrie" should be added
+ And the book "Carrie" should appear in the list
diff --git a/features/admin/book/managing_books/editing_books.feature b/features/admin/book/managing_books/editing_books.feature
new file mode 100644
index 000000000..8a1862020
--- /dev/null
+++ b/features/admin/book/managing_books/editing_books.feature
@@ -0,0 +1,15 @@
+@managing_books
+Feature: Editing books
+ In order to change information about a book
+ As an Administrator
+ I want to be able to edit the book
+
+ Background:
+ Given there is a book "The Shining"
+
+ @ui
+ Scenario: Renaming a book
+ When I want to edit this book
+ And I rename it to "Carrie"
+ And I save my changes
+ Then this book title should be "Carrie"
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 57dd47cab..fa2a798cc 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -31,6 +31,10 @@
src/AdminUi/tests/Functional
+
+ src/BehatBridge/tests
+
+
src/TwigExtra/tests
src/TwigExtra/tests/Functional
diff --git a/src/BehatBridge/.gitattributes b/src/BehatBridge/.gitattributes
new file mode 100644
index 000000000..c62cbb88c
--- /dev/null
+++ b/src/BehatBridge/.gitattributes
@@ -0,0 +1,3 @@
+/.gitattributes export-ignore
+/phpunit.xml.dist export-ignore
+/tests export-ignore
diff --git a/src/BehatBridge/LICENSE b/src/BehatBridge/LICENSE
new file mode 100644
index 000000000..258b7d7cf
--- /dev/null
+++ b/src/BehatBridge/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2024-present Sylius Sp. z o.o.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/BehatBridge/LICENSE_OF_TRADEMARK_AND_LOGO b/src/BehatBridge/LICENSE_OF_TRADEMARK_AND_LOGO
new file mode 100644
index 000000000..e8a331f66
--- /dev/null
+++ b/src/BehatBridge/LICENSE_OF_TRADEMARK_AND_LOGO
@@ -0,0 +1,162 @@
+Encourage widespread and fair use of Sylius logo and brand identity.
+
+This Trademarks and Logos use policy (the “Policy”) is based on the Ubuntu
+and Symfony trademark policy and published under the CC-BY-SA license. You
+are welcome to base your own project trademark policies off it, just let
+others use your changes and give credit to the Ubuntu and Symfony projects
+as the original source!
+
+Version n°1. Published on April 5th 2019.
+
+The objective of the Policy is to encourage widespread use of the Sylius
+trademarks by the Sylius community while controlling that use in order to
+avoid confusion on the part of Sylius users and the general public, to
+maintain the value of the image and reputation of the trademarks and to
+protect them from inappropriate or unauthorised use.
+
+The sections below describe what is allowed, what isn’t allowed, and cases
+in which you should ask permission.
+If you have any doubt, please contact us and a member of our legal
+representative will be in touch with you shortly.
+If you are aware a breach or misuse of the Sylius trademarks in any
+way, we would appreciate you bringing this to our attention. Please
+contact us so that we can investigate this further.
+
+The Trademarks and Logos
+Sylius sp. z o.o. owns the verbal trademark containing
+in whole or part of the word “Sylius”.
+
+Any verbal mark starting with the letters “Sylius” is sufficiently
+similar to one or more of the trademarks that permission will be
+needed in order to use it.
+
+All verbal trademarks of Sylius sp. z o.o., all distinctive signs used in
+commerce by Sylius sp. z o.o. to designate his products or services related
+to Sylius are collectively referred to as the “Trademarks”.
+
+Permitted use of the Trademarks
+Certain usages of the Trademarks are fine and no specific permission
+from us is needed.
+
+Community advocacy. Sylius is built by its community. We share access to
+the Trademarks with the entire community for the purposes of discussion,
+development and advocacy. We recognise that most of the open source discussion
+and development areas are for non-commercial purposes and will allow the
+use of the Trademarks in this context, provided:
+
+the Trademark is used in a manner consistent with this Policy;
+there is no commercial intent behind the use;
+what you are referring to is in fact Sylius. If someone is confused into
+thinking that what isn’t Sylius is, in fact, Sylius, you are probably doing
+something wrong;
+there is no suggestion (through words or appearance) that your project is
+approved, sponsored, or affiliated with Sylius, Sylius sp. z o.o. or its
+related projects unless it actually has been approved by and is accountable
+to Sylius sp. z o.o. and the Sylius Project.
+Building on Sylius or for Sylius. If you are producing new software which is
+intended for use with or on Sylius, you may use the Trademark in a way which
+indicates the intent of your product. For example, if you are developing a
+system management tool for Sylius, acceptable project titles would be
+“System Management for Sylius” or “Sylius Based Systems Management”. We would
+strongly discourage, and likely would consider to be problematic, a name such
+as SyliusMan, Sylius Management, etc. Furthermore, you may not use the
+Trademarks in a way which implies an endorsement where that doesn’t exist,
+or which attempts to unfairly or confusingly capitalise on the goodwill
+or brand of the project.
+
+Commentary and parody. The Trademarks and Logos are designed to cover use of
+a mark to imply origin or endorsement by the project. When a user downloads
+something called Sylius, they should know it comes from the Sylius project.
+This helps Sylius build a reputation that will not be damaged by confusion
+around what is, and isn’t, Sylius. Using the Trademarks in your discussion,
+commentary, criticism or parody, in ways that unequivocally do not imply
+endorsement, is permissible. Anyone is free to write articles, create
+websites, blog about, or talk about Sylius — as long as it’s clear to
+everyone — including people completely unfamiliar with Sylius — that they
+are simply referring to Sylius and in no way speaking for the Sylius
+project and/or for Sylius sp. z o.o.
+
+We reserve the right to review all usage within the open source community,
+and to object to any usage that appears to overstep the bounds of discussion
+and good-faith non-commercial development. In any event, once a project has
+left the open source project phase or otherwise become a commercial project,
+this Policy does not authorise any use of the Trademarks in connection to
+that project.
+
+Restricted use that requires a trademark licence
+Permission from us is necessary to use any of the Trademarks under any
+circumstances other than those specifically permitted above.
+
+These include but are not limited to:
+
+Any commercial use including for any services related to Sylius such as
+providing training services, conference services, or design services (should
+you wish to provide such services, please contact us beforehand to explore
+Sylius Solution Partner Program);
+Use on or in relation to a software product that includes or is built on top
+of a product supplied by us, if there is any commercial intent associated
+with that product;
+Use in a domain name or URL;
+Use for merchandising purposes, e.g. on t-shirts and the like.
+If you wish to have permission for any of the uses above or for any other use
+which is not specifically referred to in this Policy, please contact us and
+we’ll let you know as soon as possible if your proposed use is permissible.
+Permission may only be granted subject to certain conditions and these may
+include the requirement that you enter into an agreement with us to maintain
+the quality of the product and/or service which you intend to supply at a
+prescribed level.
+
+While there may be exceptions, it is very unlikely that we will approve
+Trademark use in the following cases:
+
+Use of a Trademark in a company name;
+Use of a Trademark in a domain name which has a commercial intent. The
+commercial intent can range from promotion of a company or product, to
+collecting revenue generated by advertising;
+The calling of any software or product by the name Sylius (or another
+related Trademark);
+Use in combination with any other marks or logos. This include use of
+a Trademark in a manner that creates a “combined mark,” or use that
+integrates other wording with the Trademark in a way that the public may
+think of the use as a new mark (for example Club Sylius or SyliusBooks, or
+in a way that by use of special fonts or presentation with nearby words or
+images conveys an impression that the two are tied in some way);
+Use in combination with any product or service which is presented as being
+Certified or Official or formally associated with us or our products or
+services;
+Use in a way which implies an endorsement where that doesn’t exist, or which
+attempts to unfairly or confusingly capitalise on the goodwill or brand of
+the project;
+Use of a Trademark in a manner that disparages Sylius, or Sylius sp. z o.o.;
+or its products and is not clearly third-party parody;
+Use of a Trademark on or in relation to a software product which constitutes
+a substantially modified version of a product supplied by the Sylius project,
+that is to say with material changes to the code, or services relating to
+such a product; and
+Use of a Trademark in a title or metatag of a web page whose sole intention or
+result is to influence search engine rankings or result listings (for example
+use as keyword for advertising purposes), rather than for discussion,
+development or advocacy of the Trademarks.
+Logo usage guidelines
+Except otherwise agreed, any use of Logos shall be expressly authorized by
+writing by Sylius sp. z o.o.. To get any authorization to use any Logo,
+please contact us and a member of our team will be in touch with you shortly.
+
+Our logos are presented in multiple colours and it is important that their
+visual integrity be maintained.
+
+Therefore, when use of Logos is authorized, it is therefore preferable that
+the logos only be used in their standard form but if you should feel the need
+to alter them in any way you should keep the following guidelines in mind.
+
+It should also be borne in mind that the more you wish to vary our logos
+from their standard form the smaller is the chance that we will be able to
+approve your proposed use.
+
+If presented in multiple colours, the logo should only use the “official”
+logo colours.
+You may use transparency and gradient/depth tools but should retain the
+“official” colours.
+Any scaling must retain the original proportions of the logo.
+In case of non-compliance with Trademarks and Logos’ Use Policy or
+applicable law, any use of the Trademarks and/or Logos will be prohibited.
diff --git a/src/BehatBridge/README.md b/src/BehatBridge/README.md
new file mode 100644
index 000000000..ba507d784
--- /dev/null
+++ b/src/BehatBridge/README.md
@@ -0,0 +1,3 @@
+# Sylius Behat Bridge
+
+The Behat Bridge component is part of the [Sylius stack](https://github.com/Sylius/Stack) initiative.
diff --git a/src/BehatBridge/composer.json b/src/BehatBridge/composer.json
new file mode 100644
index 000000000..c4ee923c9
--- /dev/null
+++ b/src/BehatBridge/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "sylius/behat-bridge",
+ "description": "Additional Behat helpers for your Symfony projects",
+ "type": "library",
+ "require": {
+ "php": "^8.1",
+ "behat/behat": "^3.16"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6"
+ },
+ "autoload": {
+ "psr-4": {
+ "Sylius\\BehatBridge\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\Sylius\\BehatBridge\\": "tests/"
+ }
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../**"
+ }
+ ],
+ "extra": {
+ "symfony": {
+ "require": "7.1.*"
+ },
+ "branch-alias": {
+ "dev-main": "0.6-dev"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "symfony/runtime": true
+ },
+ "sort-packages": true
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/src/BehatBridge/config/services.php b/src/BehatBridge/config/services.php
new file mode 100644
index 000000000..e2411bef4
--- /dev/null
+++ b/src/BehatBridge/config/services.php
@@ -0,0 +1,18 @@
+import('./services/**/**.php');
+};
diff --git a/src/BehatBridge/config/services/behat/context.php b/src/BehatBridge/config/services/behat/context.php
new file mode 100644
index 000000000..be2c36329
--- /dev/null
+++ b/src/BehatBridge/config/services/behat/context.php
@@ -0,0 +1,41 @@
+services();
+
+ $services->defaults()
+ ->public()
+ ;
+
+ $services
+ ->set('sylius_behat_bridge.behat.context.transform.shared_storage', SharedStorageContext::class)
+ ->args([
+ service('sylius_behat_bridge.storage.shared'),
+ ])
+ ;
+ $services->alias(SharedStorageContext::class, 'sylius_behat_bridge.behat.context.transform.shared_storage');
+
+ $services
+ ->set('sylius_behat_bridge.behat.context.hook.doctrine.orm', DoctrineORMContext::class)
+ ->args([
+ service('doctrine.orm.entity_manager'),
+ ])
+ ;
+ $services->alias(DoctrineORMContext::class, 'sylius_behat_bridge.behat.context.hook.doctrine.orm');
+};
diff --git a/src/BehatBridge/config/services/behat/element/admin.php b/src/BehatBridge/config/services/behat/element/admin.php
new file mode 100644
index 000000000..19fdec7d1
--- /dev/null
+++ b/src/BehatBridge/config/services/behat/element/admin.php
@@ -0,0 +1,52 @@
+services();
+
+ $services
+ ->set('sylius_behat_bridge.behat.element.admin.action.cancel', CancelActionElement::class)
+ ->args([
+ service('behat.mink.default_session'),
+ service('behat.mink.parameters'),
+ ])
+ ;
+ $services->alias(CancelActionElementInterface::class, 'sylius_behat_bridge.behat.element.admin.action.cancel');
+
+ $services
+ ->set('sylius_behat_bridge.behat.element.admin.action.create', CreateActionElement::class)
+ ->args([
+ service('behat.mink.default_session'),
+ service('behat.mink.parameters'),
+ ])
+ ;
+ $services->alias(CreateActionElementInterface::class, 'sylius_behat_bridge.behat.element.admin.action.create');
+
+ $services
+ ->set('sylius_behat_bridge.behat.element.admin.action.update', UpdateActionElement::class)
+ ->args([
+ service('behat.mink.default_session'),
+ service('behat.mink.parameters'),
+ ])
+ ;
+ $services->alias(UpdateActionElementInterface::class, 'sylius_behat_bridge.behat.element.admin.action.update');
+};
diff --git a/src/BehatBridge/config/services/storage.php b/src/BehatBridge/config/services/storage.php
new file mode 100644
index 000000000..87929864b
--- /dev/null
+++ b/src/BehatBridge/config/services/storage.php
@@ -0,0 +1,24 @@
+services();
+
+ $services->set('sylius_behat_bridge.storage.shared', SharedStorage::class);
+ $services->alias(SharedStorageInterface::class, 'sylius_behat_bridge.storage.shared');
+};
diff --git a/src/BehatBridge/phpunit.xml.dist b/src/BehatBridge/phpunit.xml.dist
new file mode 100644
index 000000000..7c805a472
--- /dev/null
+++ b/src/BehatBridge/phpunit.xml.dist
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
diff --git a/src/BehatBridge/src/Behat/Context/Hook/DoctrineORMContext.php b/src/BehatBridge/src/Behat/Context/Hook/DoctrineORMContext.php
new file mode 100644
index 000000000..ca14d5acf
--- /dev/null
+++ b/src/BehatBridge/src/Behat/Context/Hook/DoctrineORMContext.php
@@ -0,0 +1,35 @@
+entityManager);
+ $purger->purge();
+ $this->entityManager->clear();
+ }
+}
diff --git a/src/BehatBridge/src/Behat/Context/Transform/SharedStorageContext.php b/src/BehatBridge/src/Behat/Context/Transform/SharedStorageContext.php
new file mode 100644
index 000000000..ec92c89d4
--- /dev/null
+++ b/src/BehatBridge/src/Behat/Context/Transform/SharedStorageContext.php
@@ -0,0 +1,42 @@
+sharedStorage->getLatestResource();
+ }
+
+ /**
+ * @Transform /^(?:this|that|the|my|his|her) ([^"]+)$/
+ */
+ public function getResource(mixed $resource): mixed
+ {
+ return $this->sharedStorage->get(StringInflector::nameToCode($resource));
+ }
+}
diff --git a/src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElement.php b/src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElement.php
new file mode 100644
index 000000000..00574c02d
--- /dev/null
+++ b/src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElement.php
@@ -0,0 +1,31 @@
+getElement('cancel_button')->click();
+ }
+
+ protected function getDefinedElements(): array
+ {
+ return [
+ 'cancel_button' => '[data-test-cancel-changes-button]',
+ ];
+ }
+}
diff --git a/src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElementInterface.php b/src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElementInterface.php
new file mode 100644
index 000000000..12e521313
--- /dev/null
+++ b/src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElementInterface.php
@@ -0,0 +1,19 @@
+getElement('create_button')->click();
+ }
+
+ protected function getDefinedElements(): array
+ {
+ return [
+ 'create_button' => '[type=submit]:contains("Create")',
+ ];
+ }
+}
diff --git a/src/BehatBridge/src/Behat/Element/Admin/Action/CreateActionElementInterface.php b/src/BehatBridge/src/Behat/Element/Admin/Action/CreateActionElementInterface.php
new file mode 100644
index 000000000..07e085ce7
--- /dev/null
+++ b/src/BehatBridge/src/Behat/Element/Admin/Action/CreateActionElementInterface.php
@@ -0,0 +1,19 @@
+getElement('update_button')->click();
+ }
+
+ protected function getDefinedElements(): array
+ {
+ return [
+ 'update_button' => '[data-test-update-changes-button]',
+ ];
+ }
+}
diff --git a/src/BehatBridge/src/Behat/Element/Admin/Action/UpdateActionElementInterface.php b/src/BehatBridge/src/Behat/Element/Admin/Action/UpdateActionElementInterface.php
new file mode 100644
index 000000000..03eebb591
--- /dev/null
+++ b/src/BehatBridge/src/Behat/Element/Admin/Action/UpdateActionElementInterface.php
@@ -0,0 +1,19 @@
+cancelActionElement->cancel();
+ }
+
+ public function create(): void
+ {
+ $this->createActionElement->create();
+ }
+}
diff --git a/src/BehatBridge/src/Behat/Page/Admin/Crud/AbstractUpdatePage.php b/src/BehatBridge/src/Behat/Page/Admin/Crud/AbstractUpdatePage.php
new file mode 100644
index 000000000..8d16e2062
--- /dev/null
+++ b/src/BehatBridge/src/Behat/Page/Admin/Crud/AbstractUpdatePage.php
@@ -0,0 +1,43 @@
+cancelActionElement->cancel();
+ }
+
+ public function update(): void
+ {
+ $this->updateActionElement->update();
+ }
+}
diff --git a/src/BehatBridge/src/Exception/ExceptionInterface.php b/src/BehatBridge/src/Exception/ExceptionInterface.php
new file mode 100644
index 000000000..24e2a248b
--- /dev/null
+++ b/src/BehatBridge/src/Exception/ExceptionInterface.php
@@ -0,0 +1,18 @@
+ */
+ private array $clipboard = [];
+
+ private string|null $latestKey = null;
+
+ public function get(string $key): mixed
+ {
+ if (!isset($this->clipboard[$key])) {
+ throw new InvalidArgumentException(sprintf('There is no current resource for "%s"!', $key));
+ }
+
+ return $this->clipboard[$key];
+ }
+
+ public function has(string $key): bool
+ {
+ return isset($this->clipboard[$key]);
+ }
+
+ public function set(string $key, mixed $resource): void
+ {
+ $this->clipboard[$key] = $resource;
+ $this->latestKey = $key;
+ }
+
+ public function getLatestResource(): mixed
+ {
+ if (!isset($this->clipboard[$this->latestKey])) {
+ throw new InvalidArgumentException('There is no latest resource!');
+ }
+
+ return $this->clipboard[$this->latestKey];
+ }
+
+ public function setClipboard(array $clipboard): void
+ {
+ $this->clipboard = $clipboard;
+ }
+}
diff --git a/src/BehatBridge/src/Storage/SharedStorageInterface.php b/src/BehatBridge/src/Storage/SharedStorageInterface.php
new file mode 100644
index 000000000..e4af0a5f0
--- /dev/null
+++ b/src/BehatBridge/src/Storage/SharedStorageInterface.php
@@ -0,0 +1,37 @@
+ $clipboard
+ *
+ * @throws InvalidArgumentException
+ */
+ public function setClipboard(array $clipboard): void;
+}
diff --git a/src/BehatBridge/src/Symfony/SyliusBehatBridgeBundle.php b/src/BehatBridge/src/Symfony/SyliusBehatBridgeBundle.php
new file mode 100644
index 000000000..dfab8e165
--- /dev/null
+++ b/src/BehatBridge/src/Symfony/SyliusBehatBridgeBundle.php
@@ -0,0 +1,45 @@
+path)) {
+ $reflected = new \ReflectionObject($this);
+ $this->path = \dirname($reflected->getFileName(), 3);
+ }
+
+ return $this->path;
+ }
+
+ public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void
+ {
+ $bundles = $builder->getParameter('kernel.bundles');
+
+ $loader = new PhpFileLoader(
+ $builder,
+ new FileLocator(dirname(__DIR__, 2) . '/config'),
+ );
+
+ $loader->load('services.php');
+ }
+}
diff --git a/src/BehatBridge/tests/Unit/Storage/SharedStorageTest.php b/src/BehatBridge/tests/Unit/Storage/SharedStorageTest.php
new file mode 100644
index 000000000..53b47af63
--- /dev/null
+++ b/src/BehatBridge/tests/Unit/Storage/SharedStorageTest.php
@@ -0,0 +1,64 @@
+assertInstanceOf(SharedStorageInterface::class, new SharedStorage());
+ }
+
+ public function testItReturnsAnElementFromTheStorageWhenItExists(): void
+ {
+ $storage = new SharedStorage();
+ $storage->set('foo', 'fighters');
+
+ $this->assertSame('fighters', $storage->get('foo'));
+ }
+
+ public function testItThrowsAnExceptionWhenTheElementKeyDoesNotExists(): void
+ {
+ $storage = new SharedStorage();
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('There is no current resource for "foo"!');
+
+ $this->assertSame('fighters', $storage->get('foo'));
+ }
+
+ public function testItReturnsTheLatestElement(): void
+ {
+ $storage = new SharedStorage();
+ $storage->set('foo', 'bar');
+ $storage->set('foo', 'fighters');
+
+ $this->assertSame('fighters', $storage->getLatestResource());
+ }
+
+ public function testItThrowsAnExceptionWhenGettingLatestResourceButNoElementsHaveBeenStoredYet(): void
+ {
+ $storage = new SharedStorage();
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('There is no latest resource!');
+
+ $this->assertSame('fighters', $storage->getLatestResource());
+ }
+}
diff --git a/symfony.lock b/symfony.lock
index 0dc8cc327..c07f43c13 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -37,6 +37,15 @@
"src/DataFixtures/AppFixtures.php"
]
},
+ "friends-of-behat/symfony-extension": {
+ "version": "2.6",
+ "recipe": {
+ "repo": "github.com/symfony/recipes-contrib",
+ "branch": "main",
+ "version": "2.0",
+ "ref": "1e012e04f573524ca83795cd19df9ea690adb604"
+ }
+ },
"knplabs/knp-menu-bundle": {
"version": "v3.4.2"
},
diff --git a/tests/Behat/Context/Domain/BookContext.php b/tests/Behat/Context/Domain/BookContext.php
new file mode 100644
index 000000000..68ab878d0
--- /dev/null
+++ b/tests/Behat/Context/Domain/BookContext.php
@@ -0,0 +1,47 @@
+ $title]);
+ $exist = true;
+ } catch (\RuntimeException) {
+ }
+
+ Assert::true($exist);
+ }
+
+ /**
+ * @param Proxy $book
+ */
+ #[Then('/^(this book) title should be "([^"]+)"$/')]
+ public function thisBookTitleShouldBe(Proxy $book, string $title): void
+ {
+ Assert::eq($book->getTitle(), $title);
+ }
+}
diff --git a/tests/Behat/Context/Setup/BookContext.php b/tests/Behat/Context/Setup/BookContext.php
new file mode 100644
index 000000000..5d6827646
--- /dev/null
+++ b/tests/Behat/Context/Setup/BookContext.php
@@ -0,0 +1,37 @@
+withTitle($title)->create()
+ ;
+
+ $this->sharedStorage->set('book', $book);
+ }
+}
diff --git a/tests/Behat/Context/Ui/ManagingBooksContext.php b/tests/Behat/Context/Ui/ManagingBooksContext.php
new file mode 100644
index 000000000..20e610bdf
--- /dev/null
+++ b/tests/Behat/Context/Ui/ManagingBooksContext.php
@@ -0,0 +1,82 @@
+createPage->open();
+ }
+
+ /**
+ * @param Proxy $book
+ */
+ #[When('/^I want to edit (this book)$/')]
+ public function iWantToEditThisBook(Proxy $book): void
+ {
+ $this->updatePage->open(['id' => $book->getId()]);
+ }
+
+ #[When('I name it :title')]
+ public function iNameIt(string $title): void
+ {
+ $this->createPage->specifyTitle($title);
+ }
+
+ #[When('I specify its author as :author')]
+ public function iSpecifyItsAuthorAs(string $author): void
+ {
+ $this->createPage->specifyAuthor($author);
+ }
+
+ #[When('I rename it to :title')]
+ public function iRenameItTo(string $title): void
+ {
+ $this->updatePage->changeTitle($title);
+ }
+
+ #[When('I add it')]
+ public function iAddIt(): void
+ {
+ $this->createPage->create();
+ }
+
+ #[When('I save my changes')]
+ public function iSaveMyChanges(): void
+ {
+ $this->updatePage->update();
+ }
+
+ #[Then('the book :title should appear in the list')]
+ public function theBookShouldAppearInTheList(string $title): void
+ {
+ // TODO We need an index page
+ }
+}
diff --git a/tests/Behat/Page/Admin/Book/CreatePage.php b/tests/Behat/Page/Admin/Book/CreatePage.php
new file mode 100644
index 000000000..0f53a86cd
--- /dev/null
+++ b/tests/Behat/Page/Admin/Book/CreatePage.php
@@ -0,0 +1,42 @@
+getElement('title')->setValue($title);
+ }
+
+ public function specifyAuthor(string $author): void
+ {
+ $this->getElement('author')->setValue($author);
+ }
+
+ protected function getDefinedElements(): array
+ {
+ return [
+ 'author' => '#sylius_resource_authorName',
+ 'title' => '#sylius_resource_title',
+ ];
+ }
+}
diff --git a/tests/Behat/Page/Admin/Book/UpdatePage.php b/tests/Behat/Page/Admin/Book/UpdatePage.php
new file mode 100644
index 000000000..5d1f1587c
--- /dev/null
+++ b/tests/Behat/Page/Admin/Book/UpdatePage.php
@@ -0,0 +1,36 @@
+getElement('title')->setValue($title);
+ }
+
+ protected function getDefinedElements(): array
+ {
+ return [
+ 'title' => '#sylius_resource_title',
+ ];
+ }
+}
diff --git a/tests/Functional/BookTest.php b/tests/Functional/BookTest.php
index e76e60da4..699953ba8 100644
--- a/tests/Functional/BookTest.php
+++ b/tests/Functional/BookTest.php
@@ -1,5 +1,14 @@
assertCount(0, BookFactory::all());
+ $this->assertCount(0, BookFactory::all());
}
}
diff --git a/tests/Functional/ConferenceTest.php b/tests/Functional/ConferenceTest.php
index 56b4de2da..514e04c2a 100644
--- a/tests/Functional/ConferenceTest.php
+++ b/tests/Functional/ConferenceTest.php
@@ -1,5 +1,14 @@
bootEnv(dirname(__DIR__).'/.env');
+ (new Dotenv())->bootEnv(dirname(__DIR__) . '/.env');
}