From ff434528534e0702a968fffbf18661b0e20579db Mon Sep 17 00:00:00 2001 From: Kamil Grygierzec <kam.grygierzec@gmail.com> Date: Mon, 23 Sep 2024 14:44:46 +0200 Subject: [PATCH] Cover behat tests --- .../reordering_product_images.feature | 23 ++++++++ .../show/viewing_product_image.feature | 16 ------ .../show/viewing_product_images.feature | 28 +++++++++ .../Api/Admin/ManagingProductsContext.php | 57 +++++++++++++++++++ .../Behat/Context/Api/Shop/ProductContext.php | 34 +++++++++++ .../Behat/Context/Setup/ProductContext.php | 10 ++++ .../Ui/Admin/ManagingProductsContext.php | 35 ++++++++++++ ...gBetweenProductShowAndEditPagesContext.php | 8 +++ .../Behat/Context/Ui/Shop/ProductContext.php | 30 +++++++++- .../Admin/Product/MediaFormElement.php | 54 ++++++++++++++++++ .../Product/MediaFormElementInterface.php | 8 +++ .../Behat/Page/Shop/Product/ShowPage.php | 33 +++++++++-- .../Page/Shop/Product/ShowPageInterface.php | 8 ++- .../config/services/contexts/api/shop.xml | 1 + 14 files changed, 321 insertions(+), 24 deletions(-) create mode 100644 features/admin/product/managing_products/reordering_product_images.feature delete mode 100644 features/shop/product/show/viewing_product_image.feature create mode 100644 features/shop/product/show/viewing_product_images.feature diff --git a/features/admin/product/managing_products/reordering_product_images.feature b/features/admin/product/managing_products/reordering_product_images.feature new file mode 100644 index 000000000000..77acab81382c --- /dev/null +++ b/features/admin/product/managing_products/reordering_product_images.feature @@ -0,0 +1,23 @@ +@managing_products +Feature: Reordering product images + In order to have the most important product image displayed first + As an Administrator + I want to be able to reorder product images + + Background: + Given the store operates on a channel named "Web-US" in "USD" currency + And the store has a product "Dice Brewing" priced at "$10.00" in "Web-US" channel + And this product has an image "ford.jpg" with "small" type at position 0 + And this product has an image "ford.jpg" with "medium" type at position 1 + And this product has an image "ford.jpg" with "large" type at position 2 + And I am logged in as an administrator + + @api @ui @mink:chromedriver + Scenario: Reordering product images + When I want to modify the images of "Dice Brewing" product + And I change the "small" image position to 2 + And I change the "medium" image position to 0 + And I change the "large" image position to 1 + Then I save my changes to the images + And the one before last image on the list should have type "large" with position 1 + And the last image on the list should have type small with position 2 diff --git a/features/shop/product/show/viewing_product_image.feature b/features/shop/product/show/viewing_product_image.feature deleted file mode 100644 index f76bb5d4912b..000000000000 --- a/features/shop/product/show/viewing_product_image.feature +++ /dev/null @@ -1,16 +0,0 @@ -@viewing_products -Feature: Viewing a product's image on a product details page - In order to see images of a product - As a Visitor - I want to be able to view an image of a single product - - Background: - Given the store operates on a single channel in "United States" - And the store has a product "Lamborghini Gallardo Model" - And this product has an image "lamborghini.jpg" with "main" type - - @api @ui @javascript - Scenario: Viewing a product's image on a product details page - When I check this product's details - Then I should see the product name "Lamborghini Gallardo Model" - And I should see a main image diff --git a/features/shop/product/show/viewing_product_images.feature b/features/shop/product/show/viewing_product_images.feature new file mode 100644 index 000000000000..bee896ed9c43 --- /dev/null +++ b/features/shop/product/show/viewing_product_images.feature @@ -0,0 +1,28 @@ +@viewing_products +Feature: Viewing a product's images on a product details page + In order to see images of a product + As a Visitor + I want to be able to view a product's images + + Background: + Given the store operates on a single channel in "United States" + + @api @ui @mink:chromedriver + Scenario: Viewing a product's main image on a product details page + Given the store has a product "Lamborghini Gallardo Model" + And this product has an image "lamborghini.jpg" with "other" type at position 2 + And this product has an image "lamborghini.jpg" with "main" type at position 1 + When I check this product's details + Then I should see the product name "Lamborghini Gallardo Model" + And I should see a main image of type "main" + + @api @ui @mink:chromedriver + Scenario: Viewing a products images in correct order + Given the store has a product "Lamborghini Gallardo Model" + And this product has an image "lamborghini.jpg" with "other" type at position 2 + And this product has an image "lamborghini.jpg" with "thumbnail" type at position 1 + When I check this product's details + Then I should see the product name "Lamborghini Gallardo Model" + And the main image should be of type "thumbnail" + And the first thumbnail image should be of type "thumbnail" + And the second thumbnail image should be of type "other" diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php index 87307d5c8e28..f63c3079bbd6 100644 --- a/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php +++ b/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php @@ -15,6 +15,7 @@ use Behat\Behat\Context\Context; use Sylius\Behat\Client\ApiClientInterface; +use Sylius\Behat\Client\RequestBuilder; use Sylius\Behat\Client\ResponseCheckerInterface; use Sylius\Behat\Context\Api\Admin\Helper\ValidationTrait; use Sylius\Behat\Context\Api\Resources; @@ -32,6 +33,7 @@ use Sylius\Component\Product\Model\ProductAssociationTypeInterface; use Sylius\Component\Product\Model\ProductAttributeInterface; use Sylius\Component\Product\Model\ProductOptionInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Webmozart\Assert\Assert; @@ -343,6 +345,39 @@ public function iSetTheInvalidStringValueOfTheNonTranslatableAttributeTo(Product ); } + /** + * @When I want to modify the images of :product product + */ + public function iWantToModifyTheImagesOfProduct(ProductInterface $product): void + { + $this->sharedStorage->set('productIri', $this->iriConverter->getIriFromResource($product)); + } + + /** + * @When I change the :type image position to :position + */ + public function iChangeTheImagePositionTo(string $imageType, int $position): void + { + $images = $this->responseChecker->getValue($this->client->showByIri($this->sharedStorage->get('productIri')), 'images'); + $productCode = $this->responseChecker->getValue($this->client->getLastResponse(), 'code'); + + foreach ($images as $key => $imageData) { + if ($imageData['type'] === $imageType) { + $imageId = $imageData['id']; + } + } + + $builder = RequestBuilder::create( + sprintf('/api/v2/admin/products/%s/images/%s', $productCode, $imageId), + Request::METHOD_PUT, + ); + $builder->withContent(['position' => $position]); + $builder->withHeader('HTTP_Authorization', 'Bearer ' . $this->sharedStorage->get('token')); + $builder->withHeader('CONTENT_TYPE', 'application/ld+json'); + + $this->client->request($builder->build()); + } + /** * @When I set its :attribute attribute to :value * @When I set its :attribute attribute to :value in :localeCode locale @@ -616,6 +651,28 @@ public function iShouldBeNotifiedThatIsRequired(string $element): void ); } + /** + * @Then the one before last image on the list should have type :type with position :position + */ + public function theOneBeforeLastImageOnTheListShouldHaveNameWithPosition(string $imageType, int $position): void + { + $images = $this->responseChecker->getValue($this->client->showByIri($this->sharedStorage->get('productIri')), 'images'); + + Assert::same($images[count($images) - 2]['type'], $imageType); + Assert::same($images[count($images) - 2]['position'], $position); + } + + /** + * @Then the last image on the list should have type :type with position :position + */ + public function theLastImageOnTheListShouldHaveNameWithPosition(string $imageType, int $position): void + { + $images = $this->responseChecker->getValue($this->client->showByIri($this->sharedStorage->get('productIri')), 'images'); + + Assert::same($images[count($images) - 1]['type'], $imageType); + Assert::same($images[count($images) - 1]['position'], $position); + } + /** * @Then I should be notified that meta keywords are too long */ diff --git a/src/Sylius/Behat/Context/Api/Shop/ProductContext.php b/src/Sylius/Behat/Context/Api/Shop/ProductContext.php index 4c1acb0a412b..9c7ba5fe104f 100644 --- a/src/Sylius/Behat/Context/Api/Shop/ProductContext.php +++ b/src/Sylius/Behat/Context/Api/Shop/ProductContext.php @@ -16,6 +16,7 @@ use ApiPlatform\Api\IriConverterInterface; use Behat\Behat\Context\Context; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Persistence\ObjectManager; use Sylius\Behat\Client\ApiClientInterface; use Sylius\Behat\Client\RequestFactoryInterface; use Sylius\Behat\Client\ResponseCheckerInterface; @@ -41,6 +42,7 @@ public function __construct( private IriConverterInterface $iriConverter, private ChannelContextSetterInterface $channelContextSetter, private RequestFactoryInterface $requestFactory, + private ObjectManager $objectManager, private string $apiUrlPrefix, ) { } @@ -52,6 +54,7 @@ public function __construct( */ public function iViewProduct(ProductInterface $product): void { + $this->objectManager->clear(); // it's needed to clear the entity manager to receive the product images in correct order, as the images are using fallback order when added programmatically $this->client->show(Resources::PRODUCTS, $product->getCode()); /** @var ProductVariantInterface $productVariant */ @@ -473,6 +476,37 @@ public function iShouldSeeProductName(string $name): void Assert::true($this->responseChecker->hasValue($this->client->getLastResponse(), 'name', $name)); } + /** + * @Then the main image should be of type :type + * @Then I should see a main image of type :type + */ + public function theMainImageShouldBeOfType(string $type): void + { + $images = $this->responseChecker->getValue($this->client->getLastResponse(), 'images'); + + Assert::same($images[0]['type'], $type); + } + + /** + * @Then the first thumbnail image should be of type :type + */ + public function theFirstThumbnailImageShouldBeOfType(string $type): void + { + $images = $this->responseChecker->getValue($this->client->getLastResponse(), 'images'); + + Assert::same($images[0]['type'], $type); + } + + /** + * @Then the second thumbnail image should be of type :type + */ + public function theSecondThumbnailImageShouldBeOfType(string $type): void + { + $images = $this->responseChecker->getValue($this->client->getLastResponse(), 'images'); + + Assert::same($images[1]['type'], $type); + } + /** * @Then /^I should not be able to view (this product) in the ("([^"]+)" locale)$/ */ diff --git a/src/Sylius/Behat/Context/Setup/ProductContext.php b/src/Sylius/Behat/Context/Setup/ProductContext.php index 8bda9f883d40..ea63a0e4ca2f 100644 --- a/src/Sylius/Behat/Context/Setup/ProductContext.php +++ b/src/Sylius/Behat/Context/Setup/ProductContext.php @@ -862,6 +862,14 @@ public function thisProductHasAnImageWithType(ProductInterface $product, $imageP $this->createProductImage($product, $imagePath, $imageType); } + /** + * @Given /^(this product) has an image "([^"]+)" with "([^"]+)" type at position (\d+)$/ + */ + public function thisProductHasAnImageWithTypeAtPosition(ProductInterface $product, string $imagePath, string $imageType, int $position): void + { + $this->createProductImage($product, $imagePath, $imageType, null, $position); + } + /** * @Given /^(this product) has an image "([^"]+)" with "([^"]+)" type for ("[^"]+" variant)$/ */ @@ -1585,6 +1593,7 @@ private function createProductImage( string $imagePath, string $imageType, ?ProductVariantInterface $variant = null, + ?int $position = null, ): void { $filesPath = $this->getParameter('files_path'); @@ -1592,6 +1601,7 @@ private function createProductImage( $productImage = $this->productImageFactory->createNew(); $productImage->setFile(new UploadedFile($filesPath . $imagePath, basename($imagePath))); $productImage->setType($imageType); + $productImage->setPosition($position); if (null !== $variant) { $productImage->addProductVariant($variant); diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php index 6a93ef9b8231..67c6cc453c20 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php @@ -401,6 +401,32 @@ public function theOneBeforeLastProductOnTheListShouldHaveNameWithPosition(strin $this->sharedStorage->set('product_taxon_name', $productName); } + /** + * @Then the one before last image on the list should have type :type with position :position + */ + public function theOneBeforeLastImageOnTheListShouldHaveNameWithPosition(string $imageType, int $position): void + { + $images = $this->mediaFormElement->getImages(); + if (count($images) < 2) { + throw new \Exception('There are less than two images on the list.'); + } + + $oneBeforeLastImage = $images[count($images) - 2]; + + $this->mediaFormElement->assertImageTypeAndPosition($oneBeforeLastImage, $imageType, $position); + } + + /** + * @Then the last image on the list should have type :type with position :position + */ + public function theLastImageOnTheListShouldHaveNameWithPosition(string $imageType, int $position): void + { + $images = $this->mediaFormElement->getImages(); + $lastImage = end($images); + + $this->mediaFormElement->assertImageTypeAndPosition($lastImage, $imageType, $position); + } + /** * @Then the last product on the list should have :field :value * @Then the last product on the list within this taxon should have :field :value @@ -492,6 +518,7 @@ public function productShouldExistInTheProductCatalog(ProductInterface $product) * @When /^I want to modify (this product)$/ * @When /^I want to edit (this product)$/ * @When I modify the :product product + * @When I want to modify the images of :product product */ public function iWantToModifyAProduct(ProductInterface $product): void { @@ -1000,6 +1027,14 @@ public function iChangeTheFirstImageTypeTo(string $type): void $this->mediaFormElement->modifyFirstImageType($type); } + /** + * @When I change the :type image position to :position + */ + public function iChangeTheImagePositionTo(string $image, int $position): void + { + $this->mediaFormElement->modifyPositionOfImageWithType($image, $position); + } + /** * @Then /^(this product) should not have any images$/ */ diff --git a/src/Sylius/Behat/Context/Ui/Admin/NavigatingBetweenProductShowAndEditPagesContext.php b/src/Sylius/Behat/Context/Ui/Admin/NavigatingBetweenProductShowAndEditPagesContext.php index d3034c34d7f7..a10680d67f30 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/NavigatingBetweenProductShowAndEditPagesContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/NavigatingBetweenProductShowAndEditPagesContext.php @@ -92,6 +92,14 @@ public function iWantToModifyAProduct(ProductInterface $product): void $this->updateSimpleProductPage->open(['id' => $product->getId()]); } + /** + * @When I change position of the :image image to :position + */ + public function iChangePositionOfTheImageTo(string $image, int $position): void + { + $this->updateSimpleProductPage->changeImagePosition($image, $position); + } + /** * @Then I should be on :product product edit page */ diff --git a/src/Sylius/Behat/Context/Ui/Shop/ProductContext.php b/src/Sylius/Behat/Context/Ui/Shop/ProductContext.php index 5f78239b98c9..a89fdb6d4207 100644 --- a/src/Sylius/Behat/Context/Ui/Shop/ProductContext.php +++ b/src/Sylius/Behat/Context/Ui/Shop/ProductContext.php @@ -591,11 +591,35 @@ public function iShouldBeNotifiedThatTheQuantityOfThisProductMustBeBetween1And99 } /** - * @Then I should see a main image + * @Then I should see a main image of type :type */ - public function iShouldSeeAMainImage(): void + public function iShouldSeeAMainImageOfType(string $type): void { - Assert::true($this->showPage->isMainImageDisplayed()); + Assert::true($this->showPage->isMainImageOfTypeDisplayed($type)); + } + + /** + * @Then the main image should be of type :type + */ + public function theMainImageShouldBeOfType(string $type): void + { + Assert::true($this->showPage->isMainImageOfType($type)); + } + + /** + * @Then the first thumbnail image should be of type :type + */ + public function theFirstThumbnailImageShouldBeOfType(string $type): void + { + Assert::true($this->showPage->getFirstThumbnailsImageType() === $type); + } + + /** + * @Then the second thumbnail image should be of type :type + */ + public function theSecondThumbnailImageShouldBeOfType(string $type): void + { + Assert::true($this->showPage->getSecondThumbnailsImageType() === $type); } /** diff --git a/src/Sylius/Behat/Element/Admin/Product/MediaFormElement.php b/src/Sylius/Behat/Element/Admin/Product/MediaFormElement.php index 39fd95ba7561..81da8cd8f3a9 100644 --- a/src/Sylius/Behat/Element/Admin/Product/MediaFormElement.php +++ b/src/Sylius/Behat/Element/Admin/Product/MediaFormElement.php @@ -119,6 +119,32 @@ public function countImages(): int return count($imageSubforms); } + public function getImages(): array + { + $images = $this->getElement('images'); + $imageSubforms = $images->findAll('css', '[data-test-image-subform]'); + + return $imageSubforms; + } + + public function assertImageTypeAndPosition($image, string $expectedType, int $expectedPosition): void + { + $type = $image->find('css', 'input[data-test-type]')->getValue(); + $position = $image->find('css', 'input[data-test-position]')->getValue(); + + if (!$type || !$position) { + throw new \Exception('Type or position element not found in the image subform.'); + } + + if ($type !== $expectedType) { + throw new \Exception(sprintf('Expected type "%s", but got "%s".', $expectedType, $type)); + } + + if ((int) $position !== $expectedPosition) { + throw new \Exception(sprintf('Expected position "%d", but got "%d".', $expectedPosition, $position)); + } + } + public function modifyFirstImageType(string $type): void { $this->changeTab(); @@ -128,6 +154,34 @@ public function modifyFirstImageType(string $type): void $firstImageSubform->find('css', 'input[data-test-type]')->setValue($type); } + public function modifyFirstImagePosition(int $position): void + { + $this->changeTab(); + + $firstImageSubform = $this->getFirstImageSubform(); + + $firstImageSubform->find('css', 'input[data-test-position]')->setValue($position); + } + + public function modifyPositionOfImageWithType(string $type, int $position): void + { + $this->changeTab(); + + $imageSubform = $this->getElement('image_subform_with_type', ['%type%' => $type]); + + $imageSubform->find('css', 'input[data-test-position]')->setValue($position); + } + + public function hasImageWithTypeOnPosition(string $type, int $position): bool + { + $this->changeTab(); + + $imageSubform = $this->getElement('image_subform_with_type', ['%type%' => $type]); + $imagePosition = $imageSubform->find('css', 'input[data-test-position]')->getValue(); + + return $imagePosition === (string) $position; + } + public function selectVariantForFirstImage(ProductVariantInterface $productVariant): void { $this->changeTab(); diff --git a/src/Sylius/Behat/Element/Admin/Product/MediaFormElementInterface.php b/src/Sylius/Behat/Element/Admin/Product/MediaFormElementInterface.php index 9ceb8b150e48..f160d1d2742f 100644 --- a/src/Sylius/Behat/Element/Admin/Product/MediaFormElementInterface.php +++ b/src/Sylius/Behat/Element/Admin/Product/MediaFormElementInterface.php @@ -31,7 +31,15 @@ public function hasImageWithVariant(ProductVariantInterface $productVariant): bo public function countImages(): int; + public function getImages(): array; + + public function assertImageTypeAndPosition($image, string $expectedType, int $expectedPosition): void; + public function modifyFirstImageType(string $type): void; + public function modifyFirstImagePosition(int $position): void; + + public function modifyPositionOfImageWithType(string $type, int $position): void; + public function selectVariantForFirstImage(ProductVariantInterface $productVariant): void; } diff --git a/src/Sylius/Behat/Page/Shop/Product/ShowPage.php b/src/Sylius/Behat/Page/Shop/Product/ShowPage.php index 6f77db73db33..07a6e21d163a 100644 --- a/src/Sylius/Behat/Page/Shop/Product/ShowPage.php +++ b/src/Sylius/Behat/Page/Shop/Product/ShowPage.php @@ -230,13 +230,20 @@ public function isOutOfStock(): bool return $this->hasElement('out_of_stock'); } - public function isMainImageDisplayed(): bool + public function isMainImageOfType(string $type): bool { - if (!$this->hasElement('main_image')) { + $mainImage = $this->getElement('main_image', ['%type%' => $type]); + + return $mainImage !== null; + } + + public function isMainImageOfTypeDisplayed(string $type): bool + { + if (!$this->hasElement('main_image', ['%type%' => $type])) { return false; } - $imageUrl = $this->getElement('main_image')->getAttribute('src'); + $imageUrl = $this->getElement('main_image', ['%type%' => $type])->getAttribute('src'); $this->getDriver()->visit($imageUrl); $pageText = $this->getDocument()->getText(); $this->getDriver()->back(); @@ -244,6 +251,22 @@ public function isMainImageDisplayed(): bool return false === stripos($pageText, '404 Not Found'); } + public function getFirstThumbnailsImageType(): string + { + $thumbnails = $this->getElement('thumbnails'); + $images = $thumbnails->findAll('css', 'img'); + + return $images[0]->getAttribute('data-test-thumbnail-image'); + } + + public function getSecondThumbnailsImageType(): string + { + $thumbnails = $this->getElement('thumbnails'); + $images = $thumbnails->findAll('css', 'img'); + + return $images[1]->getAttribute('data-test-thumbnail-image'); + } + public function countReviews(): int { return count($this->getElement('reviews')->findAll('css', '[data-test-title]')); @@ -340,7 +363,9 @@ protected function getDefinedElements(): array 'catalog_promotion' => '[data-test-promotion-label]', 'current_variant_input' => '[data-test-product-variants] td input:checked', 'details' => '[data-test-product-details]', - 'main_image' => '[data-test-main-image]', + 'main_image' => '[data-test-main-image="%type%"]', + 'thumbnail_image' => '[data-test-thumbnail-image="%type%"]', + 'thumbnails' => '[data-test-thumbnails]', 'name' => '[data-test-product-name]', 'option_select' => '#sylius_shop_add_to_cart_cartItem_variant_%optionCode%', 'out_of_stock' => '[data-test-product-out-of-stock]', diff --git a/src/Sylius/Behat/Page/Shop/Product/ShowPageInterface.php b/src/Sylius/Behat/Page/Shop/Product/ShowPageInterface.php index 5ae1c45f8f39..bddc35c5d6d5 100644 --- a/src/Sylius/Behat/Page/Shop/Product/ShowPageInterface.php +++ b/src/Sylius/Behat/Page/Shop/Product/ShowPageInterface.php @@ -77,7 +77,13 @@ public function hasReviewTitled(string $title): bool; public function isOutOfStock(): bool; - public function isMainImageDisplayed(): bool; + public function isMainImageOfTypeDisplayed(string $type): bool; + + public function isMainImageOfType(string $type): bool; + + public function getFirstThumbnailsImageType(): string; + + public function getSecondThumbnailsImageType(): string; public function countReviews(): int; diff --git a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml index 3290b4ee6287..59c7c131fb96 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml @@ -111,6 +111,7 @@ <argument type="service" id="api_platform.iri_converter" /> <argument type="service" id="sylius.behat.channel_context_setter" /> <argument type="service" id="sylius.behat.request_factory" /> + <argument type="service" id="doctrine.orm.entity_manager" /> <argument>%sylius.security.new_api_route%</argument> </service>