From 207abac69301f40f86d036170d70747354924015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 9 Dec 2024 12:19:26 +0100 Subject: [PATCH 1/4] Init Behat Bridge package --- .github/workflows/ci.yaml | 13 ++ composer.json | 1 + config/bundles.php | 5 +- docs/README.md | 1 + docs/SUMMARY.md | 4 + docs/behat-bridge/getting-started.md | 14 ++ phpunit.xml.dist | 4 + src/BehatBridge/.gitattributes | 3 + src/BehatBridge/LICENSE | 19 ++ src/BehatBridge/LICENSE_OF_TRADEMARK_AND_LOGO | 162 ++++++++++++++++++ src/BehatBridge/README.md | 3 + src/BehatBridge/composer.json | 44 +++++ src/BehatBridge/config/services.php | 18 ++ src/BehatBridge/config/services/storage.php | 24 +++ src/BehatBridge/phpunit.xml.dist | 18 ++ .../src/Exception/ExceptionInterface.php | 18 ++ .../Exception/InvalidArgumentException.php | 18 ++ src/BehatBridge/src/Storage/SharedStorage.php | 58 +++++++ .../src/Storage/SharedStorageInterface.php | 30 ++++ .../src/Symfony/SyliusBehatBridgeBundle.php | 45 +++++ .../tests/Unit/Storage/SharedStorageTest.php | 64 +++++++ 21 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 docs/behat-bridge/getting-started.md create mode 100644 src/BehatBridge/.gitattributes create mode 100644 src/BehatBridge/LICENSE create mode 100644 src/BehatBridge/LICENSE_OF_TRADEMARK_AND_LOGO create mode 100644 src/BehatBridge/README.md create mode 100644 src/BehatBridge/composer.json create mode 100644 src/BehatBridge/config/services.php create mode 100644 src/BehatBridge/config/services/storage.php create mode 100644 src/BehatBridge/phpunit.xml.dist create mode 100644 src/BehatBridge/src/Exception/ExceptionInterface.php create mode 100644 src/BehatBridge/src/Exception/InvalidArgumentException.php create mode 100644 src/BehatBridge/src/Storage/SharedStorage.php create mode 100644 src/BehatBridge/src/Storage/SharedStorageInterface.php create mode 100644 src/BehatBridge/src/Symfony/SyliusBehatBridgeBundle.php create mode 100644 src/BehatBridge/tests/Unit/Storage/SharedStorageTest.php diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e833f128..4b3f8e533 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -107,6 +107,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/composer.json b/composer.json index dd7def1b6..8c62c61b1 100644 --- a/composer.json +++ b/composer.json @@ -69,6 +69,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/bundles.php b/config/bundles.php index 3b7656806..fc0356f37 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], 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/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/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/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..c3ab818bb --- /dev/null +++ b/src/BehatBridge/src/Storage/SharedStorageInterface.php @@ -0,0 +1,30 @@ + $clipboard + */ + 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()); + } +} From 9f66ae3e2a6a976ce69e48df1d1ac219f4875d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 9 Dec 2024 13:29:07 +0100 Subject: [PATCH 2/4] Add throws on PHPDoc --- src/BehatBridge/src/Storage/SharedStorageInterface.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/BehatBridge/src/Storage/SharedStorageInterface.php b/src/BehatBridge/src/Storage/SharedStorageInterface.php index c3ab818bb..e4af0a5f0 100644 --- a/src/BehatBridge/src/Storage/SharedStorageInterface.php +++ b/src/BehatBridge/src/Storage/SharedStorageInterface.php @@ -13,8 +13,13 @@ namespace Sylius\BehatBridge\Storage; +use Sylius\BehatBridge\Exception\InvalidArgumentException; + interface SharedStorageInterface { + /** + * @throws InvalidArgumentException + */ public function get(string $key): mixed; public function has(string $key): bool; @@ -25,6 +30,8 @@ public function getLatestResource(): mixed; /** * @param array $clipboard + * + * @throws InvalidArgumentException */ public function setClipboard(array $clipboard): void; } From 8e206854077d2d1e4ca73ba3f00d21db20eb8a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 9 Dec 2024 16:44:27 +0100 Subject: [PATCH 3/4] [BehatBridge] Add Shared storage context --- .github/workflows/ci.yaml | 3 ++ behat.dist.yaml | 8 ++++ composer.json | 2 + .../behat/suites/ui/book/managing_books.yaml | 12 ++++++ config/bundles.php | 1 + config/services_test.yaml | 8 ++++ .../book/managing_books/editing_books.feature | 12 ++++++ .../config/services/behat/context.php | 32 ++++++++++++++ .../Transform/SharedStorageContext.php | 42 +++++++++++++++++++ .../src/Formatter/StringInflector.php | 26 ++++++++++++ symfony.lock | 9 ++++ tests/Behat/Context/Setup/BookContext.php | 26 ++++++++++++ .../Behat/Context/Ui/ManagingBooksContext.php | 22 ++++++++++ 13 files changed, 203 insertions(+) create mode 100644 behat.dist.yaml create mode 100644 config/behat/suites/ui/book/managing_books.yaml create mode 100644 config/services_test.yaml create mode 100644 features/admin/book/managing_books/editing_books.feature create mode 100644 src/BehatBridge/config/services/behat/context.php create mode 100644 src/BehatBridge/src/Behat/Context/Transform/SharedStorageContext.php create mode 100644 src/BehatBridge/src/Formatter/StringInflector.php create mode 100644 tests/Behat/Context/Setup/BookContext.php create mode 100644 tests/Behat/Context/Ui/ManagingBooksContext.php diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4b3f8e533..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) diff --git a/behat.dist.yaml b/behat.dist.yaml new file mode 100644 index 000000000..5041adeb0 --- /dev/null +++ b/behat.dist.yaml @@ -0,0 +1,8 @@ +imports: + - config/behat/suites/ui/book/managing_books.yaml + +default: + extensions: + FriendsOfBehat\SymfonyExtension: + bootstrap: tests/bootstrap.php + diff --git a/composer.json b/composer.json index 8c62c61b1..f306b89b6 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,9 @@ "webmozart/assert": "^1.9" }, "require-dev": { + "behat/behat": "^3.16", "doctrine/doctrine-fixtures-bundle": "^3.6", + "friends-of-behat/symfony-extension": "^2.6", "matthiasnoback/symfony-config-test": "^5.1", "matthiasnoback/symfony-dependency-injection-test": "^5.1", "phpstan/phpstan": "^1.10", 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..0bcd6f7a2 --- /dev/null +++ b/config/behat/suites/ui/book/managing_books.yaml @@ -0,0 +1,12 @@ +default: + suites: + ui_managing_books: + contexts: + - Sylius\BehatBridge\Behat\Context\Transform\SharedStorageContext + + - MainTests\Sylius\Behat\Context\Setup\BookContext + + - MainTests\Sylius\Behat\Context\Ui\ManagingBooksContext + + filters: + tags: "@managing_books&&@ui" diff --git a/config/bundles.php b/config/bundles.php index fc0356f37..234219813 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -26,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/services_test.yaml b/config/services_test.yaml new file mode 100644 index 000000000..4af151cdb --- /dev/null +++ b/config/services_test.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + + + MainTests\Sylius\Behat\: + resource: '../tests/Behat/*' 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..22c06272e --- /dev/null +++ b/features/admin/book/managing_books/editing_books.feature @@ -0,0 +1,12 @@ +@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 "Shinning" + + @ui + Scenario: Renaming a book + When I want to edit this book diff --git a/src/BehatBridge/config/services/behat/context.php b/src/BehatBridge/config/services/behat/context.php new file mode 100644 index 000000000..e17c9abcf --- /dev/null +++ b/src/BehatBridge/config/services/behat/context.php @@ -0,0 +1,32 @@ +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'); +}; 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/Formatter/StringInflector.php b/src/BehatBridge/src/Formatter/StringInflector.php new file mode 100644 index 000000000..0a8e72a94 --- /dev/null +++ b/src/BehatBridge/src/Formatter/StringInflector.php @@ -0,0 +1,26 @@ +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..f9c251490 --- /dev/null +++ b/tests/Behat/Context/Ui/ManagingBooksContext.php @@ -0,0 +1,22 @@ + $book + */ + #[When('/^I want to edit (this book)$/')] + public function iWantToEditThisBook(Proxy $book): void + { + // For now, we are just testing the shared context transform + } +} From 9fe76b1793a28b9d633ba5b77ccccb246073294b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Tue, 10 Dec 2024 08:42:15 +0100 Subject: [PATCH 4/4] Update page --- behat.dist.yaml | 8 +++ composer.json | 4 ++ .../behat/suites/ui/book/managing_books.yaml | 5 +- config/packages/security.yaml | 1 + config/services_test.yaml | 5 ++ ecs.php | 1 + .../book/managing_books/adding_books.feature | 14 +++++ .../book/managing_books/editing_books.feature | 5 +- .../config/services/behat/context.php | 9 +++ .../config/services/behat/element/admin.php | 52 ++++++++++++++++ .../Behat/Context/Hook/DoctrineORMContext.php | 35 +++++++++++ .../Admin/Action/CancelActionElement.php | 31 ++++++++++ .../Action/CancelActionElementInterface.php | 19 ++++++ .../Admin/Action/CreateActionElement.php | 31 ++++++++++ .../Action/CreateActionElementInterface.php | 19 ++++++ .../Admin/Action/UpdateActionElement.php | 31 ++++++++++ .../Action/UpdateActionElementInterface.php | 19 ++++++ .../Page/Admin/Crud/AbstractCreatePage.php | 43 +++++++++++++ .../Page/Admin/Crud/AbstractUpdatePage.php | 43 +++++++++++++ tests/Behat/Context/Domain/BookContext.php | 47 ++++++++++++++ tests/Behat/Context/Setup/BookContext.php | 11 ++++ .../Behat/Context/Ui/ManagingBooksContext.php | 62 ++++++++++++++++++- tests/Behat/Page/Admin/Book/CreatePage.php | 42 +++++++++++++ tests/Behat/Page/Admin/Book/UpdatePage.php | 36 +++++++++++ tests/Functional/BookTest.php | 13 +++- tests/Functional/ConferenceTest.php | 9 +++ tests/Functional/DashboardTest.php | 11 +++- tests/Functional/LegacyBookTest.php | 9 +++ tests/Functional/LoginTest.php | 15 +++-- tests/Functional/SpeakerTest.php | 9 +++ tests/Functional/TalkTest.php | 9 +++ tests/Translations/FrenchTranslatedUiTest.php | 11 +++- tests/Translations/MarkTestSkippedTrait.php | 11 ++++ tests/bootstrap.php | 19 ++++-- 34 files changed, 671 insertions(+), 18 deletions(-) create mode 100644 features/admin/book/managing_books/adding_books.feature create mode 100644 src/BehatBridge/config/services/behat/element/admin.php create mode 100644 src/BehatBridge/src/Behat/Context/Hook/DoctrineORMContext.php create mode 100644 src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElement.php create mode 100644 src/BehatBridge/src/Behat/Element/Admin/Action/CancelActionElementInterface.php create mode 100644 src/BehatBridge/src/Behat/Element/Admin/Action/CreateActionElement.php create mode 100644 src/BehatBridge/src/Behat/Element/Admin/Action/CreateActionElementInterface.php create mode 100644 src/BehatBridge/src/Behat/Element/Admin/Action/UpdateActionElement.php create mode 100644 src/BehatBridge/src/Behat/Element/Admin/Action/UpdateActionElementInterface.php create mode 100644 src/BehatBridge/src/Behat/Page/Admin/Crud/AbstractCreatePage.php create mode 100644 src/BehatBridge/src/Behat/Page/Admin/Crud/AbstractUpdatePage.php create mode 100644 tests/Behat/Context/Domain/BookContext.php create mode 100644 tests/Behat/Page/Admin/Book/CreatePage.php create mode 100644 tests/Behat/Page/Admin/Book/UpdatePage.php diff --git a/behat.dist.yaml b/behat.dist.yaml index 5041adeb0..86f388389 100644 --- a/behat.dist.yaml +++ b/behat.dist.yaml @@ -3,6 +3,14 @@ imports: 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 f306b89b6..4fa535442 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,10 @@ "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", diff --git a/config/behat/suites/ui/book/managing_books.yaml b/config/behat/suites/ui/book/managing_books.yaml index 0bcd6f7a2..1bb55eac2 100644 --- a/config/behat/suites/ui/book/managing_books.yaml +++ b/config/behat/suites/ui/book/managing_books.yaml @@ -2,11 +2,10 @@ 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/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 index 4af151cdb..f4cb6c623 100644 --- a/config/services_test.yaml +++ b/config/services_test.yaml @@ -3,6 +3,11 @@ services: 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/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 index 22c06272e..8a1862020 100644 --- a/features/admin/book/managing_books/editing_books.feature +++ b/features/admin/book/managing_books/editing_books.feature @@ -5,8 +5,11 @@ Feature: Editing books I want to be able to edit the book Background: - Given there is a book "Shinning" + 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/src/BehatBridge/config/services/behat/context.php b/src/BehatBridge/config/services/behat/context.php index e17c9abcf..be2c36329 100644 --- a/src/BehatBridge/config/services/behat/context.php +++ b/src/BehatBridge/config/services/behat/context.php @@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Sylius\BehatBridge\Behat\Context\Hook\DoctrineORMContext; use Sylius\BehatBridge\Behat\Context\Transform\SharedStorageContext; return function (ContainerConfigurator $configurator): void { @@ -29,4 +30,12 @@ ]) ; $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/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/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/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 index 22c930f70..5d6827646 100644 --- a/tests/Behat/Context/Setup/BookContext.php +++ b/tests/Behat/Context/Setup/BookContext.php @@ -1,5 +1,16 @@ createPage->open(); + } + /** * @param Proxy $book */ #[When('/^I want to edit (this book)$/')] public function iWantToEditThisBook(Proxy $book): void { - // For now, we are just testing the shared context transform + $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'); }