diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 7628614bec20a..8fcb71a54b72a 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -344,6 +344,7 @@ GitHub contributors:
- Georges Cubas
- Gerdus van Zyl
- ggedamed
+- Giuseppe Tripiciano
- Giant Leap Lab
- Ginkosama
- ginodev
@@ -399,6 +400,7 @@ GitHub contributors:
- indesign47
- Ines Sallemi
- Inetbiz
+- Inform-All
- ironwo0d
- Ish Gupta
- Ishiki
@@ -852,6 +854,7 @@ GitHub contributors:
- Salim Benouamer
- Sam Berry
- sallemiines
+- Salvo Passaro
- Sam
- Sam Sanchez
- Samir Shah
diff --git a/classes/Image.php b/classes/Image.php
index 1d791d61fd054..3fe710dccd011 100644
--- a/classes/Image.php
+++ b/classes/Image.php
@@ -24,7 +24,6 @@
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
-use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use PrestaShop\PrestaShop\Core\Exception\InvalidArgumentException;
use PrestaShop\PrestaShop\Core\Image\ImageFormatConfiguration;
@@ -930,8 +929,6 @@ public function getPathForCreation()
*/
private function deleteAutoGeneratedImage(array $imageType, string $imageFormat, array $filesToDelete): array
{
- $configuration = SymfonyContainer::getInstance()->get('prestashop.adapter.legacy.configuration');
-
// Regular thumbnail
$filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '.' . $imageFormat;
@@ -939,8 +936,8 @@ private function deleteAutoGeneratedImage(array $imageType, string $imageFormat,
$filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '2x.' . $imageFormat;
// Watermarked thumbnail, if present
- if ($configuration->get('WATERMARK_HASH')) {
- $filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '-' . $configuration->get('WATERMARK_HASH') . '.' . $imageFormat;
+ if (Configuration::get('WATERMARK_HASH')) {
+ $filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '-' . Configuration::get('WATERMARK_HASH') . '.' . $imageFormat;
}
return $filesToDelete;
diff --git a/composer.json b/composer.json
index 46c8ab91be371..4279736c730f5 100644
--- a/composer.json
+++ b/composer.json
@@ -58,7 +58,7 @@
"prestashop/blockreassurance": "^5",
"prestashop/blockwishlist": "^3.0",
"prestashop/circuit-breaker": "^4.0",
- "prestashop/classic": "^3",
+ "prestashop/classic": "~3.1.0",
"prestashop/contactform": "^4",
"prestashop/dashactivity": "^2",
"prestashop/dashgoals": "^2",
@@ -71,7 +71,7 @@
"prestashop/hummingbird": "^2.0",
"prestashop/pagesnotfound": "^3",
"prestashop/productcomments": "^8.0",
- "prestashop/ps_apiresources": "dev-add-combinations-endpoint",
+ "prestashop/ps_apiresources": "dev-add-discountType-resource",
"prestashop/ps_banner": "^2",
"prestashop/ps_bestsellers": "^1.0",
"prestashop/ps_brandlist": "^1.0",
@@ -212,7 +212,7 @@
},
"ps_apiresources": {
"type": "vcs",
- "url": "https://github.com/sebajps/ps_apiresources"
+ "url": "https://github.com/PrestaShop/ps_apiresources"
}
},
"minimum-stability": "dev",
diff --git a/composer.lock b/composer.lock
index cf75a70b67569..bf1d03bf5b622 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3d51d2e720417b2662928b43e3a23ad7",
+ "content-hash": "a432fafce6e80f362185427cad5f4b8d",
"packages": [
{
"name": "api-platform/core",
@@ -4662,16 +4662,16 @@
},
{
"name": "prestashop/classic",
- "version": "3.0.5",
+ "version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/PrestaShop/classic-theme.git",
- "reference": "09a8934bcb5e8f0cc0a3e234d5e9b18a61c27ec2"
+ "reference": "9af074d6cdbb8002323780e85b11e30d210f002b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PrestaShop/classic-theme/zipball/09a8934bcb5e8f0cc0a3e234d5e9b18a61c27ec2",
- "reference": "09a8934bcb5e8f0cc0a3e234d5e9b18a61c27ec2",
+ "url": "https://api.github.com/repos/PrestaShop/classic-theme/zipball/9af074d6cdbb8002323780e85b11e30d210f002b",
+ "reference": "9af074d6cdbb8002323780e85b11e30d210f002b",
"shasum": ""
},
"require-dev": {
@@ -4696,9 +4696,9 @@
"description": "Classic theme for develop PrestaShop version",
"support": {
"issues": "https://github.com/PrestaShop/classic-theme/issues",
- "source": "https://github.com/PrestaShop/classic-theme/tree/3.0.5"
+ "source": "https://github.com/PrestaShop/classic-theme/tree/3.1.0"
},
- "time": "2025-12-15T16:49:50+00:00"
+ "time": "2026-01-16T10:02:47+00:00"
},
{
"name": "prestashop/contactform",
@@ -5083,16 +5083,16 @@
},
{
"name": "prestashop/hummingbird",
- "version": "v2.0.0-beta.2",
+ "version": "v2.0.0-beta.3",
"source": {
"type": "git",
"url": "https://github.com/PrestaShop/hummingbird.git",
- "reference": "d2576caa72ca08468ea372ce6f133f8ac52dacb2"
+ "reference": "9732762a0d7b88b6b40a4c9d36b299e52deedaff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PrestaShop/hummingbird/zipball/d2576caa72ca08468ea372ce6f133f8ac52dacb2",
- "reference": "d2576caa72ca08468ea372ce6f133f8ac52dacb2",
+ "url": "https://api.github.com/repos/PrestaShop/hummingbird/zipball/9732762a0d7b88b6b40a4c9d36b299e52deedaff",
+ "reference": "9732762a0d7b88b6b40a4c9d36b299e52deedaff",
"shasum": ""
},
"require-dev": {
@@ -5117,9 +5117,9 @@
"description": "Hummingbird development theme for PrestaShop",
"support": {
"issues": "https://github.com/PrestaShop/hummingbird/issues",
- "source": "https://github.com/PrestaShop/hummingbird/tree/v2.0.0-beta.2"
+ "source": "https://github.com/PrestaShop/hummingbird/tree/v2.0.0-beta.3"
},
- "time": "2025-12-15T17:19:33+00:00"
+ "time": "2026-01-21T14:00:19+00:00"
},
{
"name": "prestashop/pagesnotfound",
@@ -5208,16 +5208,16 @@
},
{
"name": "prestashop/ps_apiresources",
- "version": "dev-add-combinations-endpoint",
+ "version": "dev-add-discountType-resource",
"source": {
"type": "git",
- "url": "https://github.com/sebajps/ps_apiresources.git",
- "reference": "86e47474aa9004dfeaf7b726bfb192e66ab15779"
+ "url": "https://github.com/PrestaShop/ps_apiresources.git",
+ "reference": "f7d72022404d9c03691e3d63bde7ccae2f26a04a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebajps/ps_apiresources/zipball/86e47474aa9004dfeaf7b726bfb192e66ab15779",
- "reference": "86e47474aa9004dfeaf7b726bfb192e66ab15779",
+ "url": "https://api.github.com/repos/PrestaShop/ps_apiresources/zipball/f7d72022404d9c03691e3d63bde7ccae2f26a04a",
+ "reference": "f7d72022404d9c03691e3d63bde7ccae2f26a04a",
"shasum": ""
},
"require": {
@@ -5281,9 +5281,10 @@
"description": "PrestaShop - API Resources",
"homepage": "https://github.com/PrestaShop/ps_apiresources",
"support": {
- "source": "https://github.com/sebajps/ps_apiresources/tree/add-combinations-endpoint"
+ "source": "https://github.com/PrestaShop/ps_apiresources/tree/add-discountType-resource",
+ "issues": "https://github.com/PrestaShop/ps_apiresources/issues"
},
- "time": "2026-01-21T07:13:47+00:00"
+ "time": "2026-01-28T06:41:30+00:00"
},
{
"name": "prestashop/ps_banner",
@@ -6002,16 +6003,16 @@
},
{
"name": "prestashop/ps_facetedsearch",
- "version": "v4.0.2",
+ "version": "v4.0.3",
"source": {
"type": "git",
"url": "https://github.com/PrestaShop/ps_facetedsearch.git",
- "reference": "8ebc5f07be387e378baf907a02f53f1496e6347b"
+ "reference": "bd9f09e81d1d49a936b4c0c3fa6b7e846bc53e76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PrestaShop/ps_facetedsearch/zipball/8ebc5f07be387e378baf907a02f53f1496e6347b",
- "reference": "8ebc5f07be387e378baf907a02f53f1496e6347b",
+ "url": "https://api.github.com/repos/PrestaShop/ps_facetedsearch/zipball/bd9f09e81d1d49a936b4c0c3fa6b7e846bc53e76",
+ "reference": "bd9f09e81d1d49a936b4c0c3fa6b7e846bc53e76",
"shasum": ""
},
"require": {
@@ -6047,9 +6048,10 @@
"description": "PrestaShop module ps_facetedsearch",
"homepage": "https://github.com/PrestaShop/ps_facetedsearch",
"support": {
- "source": "https://github.com/PrestaShop/ps_facetedsearch/tree/v4.0.2"
+ "issues": "https://github.com/PrestaShop/ps_facetedsearch/issues",
+ "source": "https://github.com/PrestaShop/ps_facetedsearch/tree/v4.0.3"
},
- "time": "2025-11-21T13:36:43+00:00"
+ "time": "2025-12-31T13:27:28+00:00"
},
{
"name": "prestashop/ps_faviconnotificationbo",
@@ -18271,5 +18273,5 @@
"platform-overrides": {
"php": "8.1.0"
},
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.9.0"
}
diff --git a/controllers/front/ProductController.php b/controllers/front/ProductController.php
index c7cadb3cd2bc0..732d0b5f5cd83 100644
--- a/controllers/front/ProductController.php
+++ b/controllers/front/ProductController.php
@@ -454,11 +454,11 @@ public function displayAjaxQuickview(): void
public function displayAjaxRefresh(): void
{
$product = $this->getTemplateVarProduct();
- $minimalProductQuantity = $this->getProductMinimalQuantity($product);
- // If the product is already in the cart, we can set the minimal quantity to 1
- if ($product['cart_quantity'] >= $minimalProductQuantity) {
- $minimalProductQuantity = 1;
+ // After refresh, we will show the customer a new quantity he has to use
+ $newMinimalQuantity = $product['quantity_required'];
+ if (empty($newMinimalQuantity)) {
+ $newMinimalQuantity = 1;
}
ob_end_clean();
@@ -496,7 +496,8 @@ public function displayAjaxRefresh(): void
'adtoken' => Tools::getValue('adtoken'),
] : []
),
- 'product_minimal_quantity' => $minimalProductQuantity,
+ // A new minimal quantity to use on the input after refresh
+ 'product_minimal_quantity' => $newMinimalQuantity,
'product_has_combinations' => !empty($this->combinations),
'id_product_attribute' => $product['id_product_attribute'],
'id_customization' => $product['id_customization'],
@@ -1182,33 +1183,57 @@ private function getIdProductAttributeByGroup(): ?int
public function getTemplateVarProduct(): ProductLazyArray
{
- $productSettings = $this->getProductPresentationSettings();
- // Hook displayProductExtraContent
- $extraContentFinder = new ProductExtraContentFinder();
-
+ // Convert product object into array
$product = $this->objectPresenter->present($this->product);
+
+ // Assign several product properties to the array
$product['description'] = $this->transformDescriptionWithImg($this->product->description);
+
+ /*
+ * This property is not a product property, but value from stock_available table. It must be here because on this page,
+ * it's not initialized in any other way. On listings, it goes through ProductAssembler and the property is included.
+ * In cart, it's also in the selected fields. But not here.
+ *
+ * We could centralize it right now, but the call StockAvailable::outOfStock($this->id) would still be called
+ * when constructing a Product object, so, let's just migrate it all at once later.
+ */
$product['out_of_stock'] = (int) $this->product->out_of_stock;
$product['id_product_attribute'] = $this->getIdProductAttributeByGroupOrRequestOrDefault();
+
+ // @todo These three properties should be migrated into the lazy array, so they are available also in listings
+ // Minimal quantity setting of this product or combination
$product['minimal_quantity'] = $this->getProductMinimalQuantity($product);
+
+ // Quantity of this product in the current cart
$product['cart_quantity'] = $this->context->cart->getProductQuantity((int) $this->product->id, $product['id_product_attribute'])['quantity'];
- $product['quantity_wanted'] = $this->getRequiredQuantity($product);
- $product['extraContent'] = $extraContentFinder->addParams(['product' => $this->product])->present();
+
+ // Quantity requested by the customer by the quantity input on product page - may be force-altered by us
+ // @todo - a centralized version of this method is implemented in ProductLazyArray - migrate to it when migrating this code
+ $product['quantity_wanted'] = $this->getWantedQuantity($product);
+
+ // Required quantity to add to cart to reach minimal quantity
+ // @todo - a centralized version of this method is implemented in ProductLazyArray - migrate to it when migrating this code
+ $product['quantity_required'] = $this->getRequiredQuantity($product);
+
+ // Render hook displayProductExtraContent
+ $product['extraContent'] = (new ProductExtraContentFinder())->addParams(['product' => $this->product])->present();
$product['ecotax_tax_inc'] = $this->product->getEcotax(null, true, true);
$product['ecotax'] = Tools::convertPrice($this->getProductEcotax($product), $this->context->currency, true, $this->context);
+ // Enrich the product array
$product_full = Product::getProductProperties($this->context->language->id, $product, $this->context);
+ // Add possible customizations
$product_full = $this->addProductCustomizationData($product_full);
$product_full['show_quantities'] = (bool) (
Configuration::get('PS_DISPLAY_QTIES')
&& Configuration::get('PS_STOCK_MANAGEMENT')
- && $this->product->quantity > 0
+ && $product_full['quantity'] > 0
&& $this->product->available_for_order
&& !Configuration::isCatalogMode()
);
- $product_full['quantity_label'] = ($this->product->quantity > 1) ? $this->trans('Items', [], 'Shop.Theme.Catalog') : $this->trans('Item', [], 'Shop.Theme.Catalog');
+ $product_full['quantity_label'] = ($product_full['quantity'] > 1) ? $this->trans('Items', [], 'Shop.Theme.Catalog') : $this->trans('Item', [], 'Shop.Theme.Catalog');
$product_full['quantity_discounts'] = $this->quantity_discounts;
$group_reduction = GroupReduction::getValueForProduct($this->product->id, (int) Group::getCurrent()->id);
@@ -1218,32 +1243,42 @@ public function getTemplateVarProduct(): ProductLazyArray
$product_full['customer_group_discount'] = $group_reduction;
$product_full['title'] = $this->getProductPageTitle();
+ // And finally, present it in the modern way
return $this->getProductPresenter()->present(
- $productSettings,
+ $this->getProductPresentationSettings(),
$product_full,
$this->context->language
);
}
/**
+ * Gets the minimal quantity allowed for the product or its combination. With no adjustments
+ * by the current context.
+ *
+ * @todo This method should be migrated to ProductLazyArray, so it's available also in listings.
+ *
* @param array $product
*
- * @return int
+ * @return int Minimal quantity of product from it's settings, always a positive integer
*/
protected function getProductMinimalQuantity(ProductLazyArray|array $product)
{
- $minimal_quantity = 1;
+ $minimalQuantity = 1;
if ($product['id_product_attribute']) {
$combination = $this->findProductCombinationById($product['id_product_attribute']);
if ($combination['minimal_quantity']) {
- $minimal_quantity = $combination['minimal_quantity'];
+ $minimalQuantity = (int) $combination['minimal_quantity'];
}
} else {
- $minimal_quantity = $this->product->minimal_quantity;
+ $minimalQuantity = (int) $this->product->minimal_quantity;
}
- return $minimal_quantity;
+ if ($minimalQuantity < 1) {
+ $minimalQuantity = 1;
+ }
+
+ return $minimalQuantity;
}
/**
@@ -1294,22 +1329,62 @@ public function findProductCombinationById(int $combinationId)
}
/**
+ * Gets the minimal quantity the customer has to purchase. We cannot just let him buy 1 piece
+ * if the minimal quantity is higher. Also, we adjust it by the quantity already in cart.
+ *
+ * @todo This method should be migrated to ProductLazyArray, so it's available also in listings.
+ * It's already implemented there.
+ *
* @param array $product
*
- * @return int
+ * @return int Minimal quantity of product the customer buy right now, always a positive integer
*/
- protected function getRequiredQuantity(array $product)
+ protected function getRequiredQuantity(ProductLazyArray|array $product)
{
- $requiredQuantity = (int) Tools::getValue('quantity_wanted', $this->getProductMinimalQuantity($product));
- if ($requiredQuantity < $product['minimal_quantity']) {
- $requiredQuantity = $product['minimal_quantity'];
+ // For the required quantity, we will need to limit it by the minimal quantity on the low side.
+ $requiredQuantityForPurchase = $this->getProductMinimalQuantity($product);
+
+ /*
+ * We reduce it by the quantity we already have in cart. If the user already has a sufficient
+ * quantity in the cart, we don't need to add more. Although it may seem that we can just reset
+ * the minimal quantity to one in that case, we must not do that, because the quantity in the cart
+ * may not be the correct one.
+ */
+ if (!empty($product['cart_quantity'])) {
+ $requiredQuantityForPurchase -= $product['cart_quantity'];
+ if ($requiredQuantityForPurchase < 1) {
+ $requiredQuantityForPurchase = 1;
+ }
}
- if ($product['cart_quantity'] >= $requiredQuantity) {
- return 0;
+ return $requiredQuantityForPurchase;
+ }
+
+ /**
+ * Gets the quantity wanted by the customer for the product. We will take his request,
+ * but we will adjust it if it's lower than the required quantity.
+ *
+ * @todo This method should be migrated to ProductLazyArray, so it's available also in listings.
+ * It's already implemented there.
+ *
+ * @param array $product
+ *
+ * @return int Quantity of product requested by the customer, altered if needed, always a positive integer
+ */
+ public function getWantedQuantity(ProductLazyArray|array $product): int
+ {
+ // Get the quantity wanted from the request
+ $quantityWantedByTheCustomer = (int) Tools::getValue('quantity_wanted', 1);
+
+ // Get minimal required quantity for purchase
+ $requiredQuantityForPurchase = $this->getRequiredQuantity($product);
+
+ // If the wanted quantity is lower than the required, we adjust it
+ if ($quantityWantedByTheCustomer < $requiredQuantityForPurchase) {
+ $quantityWantedByTheCustomer = $requiredQuantityForPurchase;
}
- return $requiredQuantity;
+ return $quantityWantedByTheCustomer;
}
/**
diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt
index 0028e29091236..a4b0604eda890 100644
--- a/docs/CHANGELOG.txt
+++ b/docs/CHANGELOG.txt
@@ -510,6 +510,89 @@ Changelog for PrestaShop 9
- #40298: Functional tests - Fix create account in FO classic theme test (by @nesrineabdmouleh)
- #39166: Refactor manufacturer behat tests (by @jolelievre)
+####################################
+# v9.0.3 - (2026-01-22)
+####################################
+
+- Back Office:
+ - Improvement:
+ - #40517: Add some help boxes to product page (by @Hlavtox)
+ - #39923: Improve wording of some settings, better explain the meaning of them (by @Hlavtox)
+ - #40230: Adds help text to product page fields (by @Hlavtox)
+ - Bug fix:
+ - #40563: Admin API improvements for combination endpoints (by @jolelievre)
+ - #40556: Fix: CsvFileReader service fails with "You have requested a non-existent service 'session'" (by @Codencode)
+ - #38775: Fix: Multishop - error loading CMS pages removed from the default shop (by @Codencode)
+ - #40499: Fix: When saving an Attribute is_color_group is not updated. (by @Codencode)
+ - #40554: Fix: always display taxes total in order summary (by @Codencode)
+ - #40532: BO Product page, fix feature value collection indexes (by @jolelievre)
+ - #40433: Fix: Module update problem (by @Codencode)
+ - #40054: Use URL when building urls to prevent subtle mistakes with &? (by @tswfi)
+ - #40036: BO - Product : Fixed feature display in multishop (by @Progi1984)
+ - #39854: Fix: Quick Access links redirect to root instead of subdirectory, causing 404 (by @Codencode)
+ - #40050: Fix: Incorrect redirect from HTTP to HTTPS on the admin login page when PrestaShop is in a subfolder (by @Codencode)
+ - #40475: Fix link for redirection on country BO page (by @jolelievre)
+ - #40066: Fix: [BO] Admin Countries page redirect issue with multishop after changing shop (by @Codencode)
+ - #40329: Prevent NoResultException when checking for existing translations (by @ChillCode)
+ - #39926: Fix: handle SELECT fields without no_quotes in getSensitiveAttributes (by @Codencode)
+ - #40001: Fix redirect after editing root category to use current categoryId in stead of PS_HOME_CATEGORY (by @Codencode)
+ - #39869: Update monologger to v3 (by @NKoonen)
+ - #40256: Fix: Issue retrieving product price when adding the first specific_price (by @Codencode)
+ - #40243: Admin API handle position update (by @jolelievre)
+ - #40257: Prevent saving ajax URL for future login redirection, or the redirect… (by @jolelievre)
+ - #40112: Bump prestakit to v2.0.5 (by @Quetzacoalt91)
+ - Refactoring:
+ - #37667: Apply backoffice optimizations (by @Hlavtox)
+- Front Office:
+ - New feature:
+ - #40403: Allow easily hooking into country and currency selection logic (by @Hlavtox)
+ - Improvement:
+ - #40537: Remove unnecessary force refresh in checkout (by @Hlavtox)
+ - #40248: Prevent Exposure of Sensitive Product Attributes in Front Office (by @M0rgan01)
+ - Bug fix:
+ - #40246: Fix preview in multilang (by @tleon)
+ - #39582: Fix: Product customization text field bug with using symbol {} (by @Codencode)
+ - #40117: Fix cart rule validation in front office (by @Hlavtox)
+ - #40262: Allow access to customized files preview without multi-lang enabled (by @kpodemski)
+ - #40137: Updated zxcvbn to a maintained version for consistency with backend checks (by @tleon)
+ - Refactoring:
+ - #40406: Comment how language setting works in FO (by @Hlavtox)
+- Core:
+ - Improvement:
+ - #40269: Prevent database inconsistencies by preventing faulty group delete calls (by @Hlavtox)
+ - #40133: Update ca-bundle from 1.3.7 to 1.5.9 (by @tswfi)
+ - #40350: Remove the composer config to ignore audit (by @jolelievre)
+ - #40332: Restore original repository for ps_apiresources (by @nicosomb)
+ - #40312: Bump to `9.0.3` (by @boherm)
+ - #40134: Docker : Fixed Install of xdebug (by @Progi1984)
+ - Bug fix:
+ - #40585: Preliminary tasks for patch version 9.0.3 (by @jolelievre)
+ - #40479: Fix legacy profiler in the back office (by @kpodemski)
+ - #40562: Update VAT rates for Estonia and Romania (by @Codencode)
+ - #40496: Fix some type issues related to carriers (by @Hlavtox)
+ - #40423: Fix empty extra vars / product list when using the new automatic text email option (by @matrixino)
+ - #40040: Always request a non cached result in Order::getIdByCartId (by @ilsalvopss)
+ - #40400: Simplify cart rule minimal value by avoiding subtracting values (by @Hlavtox)
+ - Refactoring:
+ - #40389: Comment logic related to addresses and their initialization (by @Hlavtox)
+- Installer:
+ - Bug fix:
+ - #40574: Fix Makefile to prevent build assets twice (by @jolelievre)
+ - #40077: Install Console : Allow characters "<" & ">" in admin password (by @Progi1984)
+ - #40114: Chore(Makefile): fix Makefile shell detection issue (by @tyloo)
+- Localization:
+ - Bug fix:
+ - #40521: Fix default fixtures translation (by @jolelievre)
+- Tests:
+ - Improvement:
+ - #40512: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984)
+ - #40458: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984)
+ - #40434: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984)
+ - #40363: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984)
+ - #40138: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984)
+ - Refactoring:
+ - #40298: Functional tests - Fix create account in FO classic theme test (by @nesrineabdmouleh)
+
####################################
# v9.0.2 - (2025-12-01)
####################################
diff --git a/install-dev/data/xml/hook.xml b/install-dev/data/xml/hook.xml
index 19e1fd6d7dcce..69cfa5da19657 100644
--- a/install-dev/data/xml/hook.xml
+++ b/install-dev/data/xml/hook.xml
@@ -5638,5 +5638,30 @@
Action after initializing context currency
Allows modules to modify the context currency after it has been initialized.
+
+ actionFacetedSearchSetSupportedControllers
+
+
+
+
+ actionFacetedSearchFilters
+
+
+
+
+ actionMainMenuModifier
+
+
+
+
+ actionFacetedSearchCacheKeyGeneration
+
+
+
+
+ gSitemapAppendUrls
+
+
+
diff --git a/localization/at.xml b/localization/at.xml
index bddb55d99d1d0..08d288a829bfa 100644
--- a/localization/at.xml
+++ b/localization/at.xml
@@ -17,7 +17,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/be.xml b/localization/be.xml
index af8eaf8eef586..0c8f73aeea99c 100644
--- a/localization/be.xml
+++ b/localization/be.xml
@@ -18,7 +18,7 @@
-
+
@@ -36,7 +36,7 @@
-
+
diff --git a/localization/bg.xml b/localization/bg.xml
index f21a4792b7b9b..ba5eb11e6fcbb 100644
--- a/localization/bg.xml
+++ b/localization/bg.xml
@@ -16,7 +16,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
diff --git a/localization/cy.xml b/localization/cy.xml
index 6c47114287fb3..24229872e562d 100644
--- a/localization/cy.xml
+++ b/localization/cy.xml
@@ -17,7 +17,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/cz.xml b/localization/cz.xml
index 013465de2d293..f3e2437a60407 100644
--- a/localization/cz.xml
+++ b/localization/cz.xml
@@ -15,7 +15,7 @@
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/localization/de.xml b/localization/de.xml
index 79b0869791e83..99311233fba80 100644
--- a/localization/de.xml
+++ b/localization/de.xml
@@ -15,7 +15,7 @@
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/localization/dk.xml b/localization/dk.xml
index 86eb42805bb2f..57e37c4878dbf 100644
--- a/localization/dk.xml
+++ b/localization/dk.xml
@@ -14,7 +14,7 @@
-
+
@@ -32,7 +32,7 @@
-
+
diff --git a/localization/ee.xml b/localization/ee.xml
index dd6da169f1227..3f7ffa8f02d2b 100644
--- a/localization/ee.xml
+++ b/localization/ee.xml
@@ -7,7 +7,7 @@
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/localization/es.xml b/localization/es.xml
index b92ed8b9b9c49..bbd1ab2120b11 100644
--- a/localization/es.xml
+++ b/localization/es.xml
@@ -17,7 +17,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
diff --git a/localization/fi.xml b/localization/fi.xml
index e773a9199fd71..38a7ce834dfce 100644
--- a/localization/fi.xml
+++ b/localization/fi.xml
@@ -17,7 +17,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
diff --git a/localization/fr.xml b/localization/fr.xml
index c6116bec6605b..1eb9498f49473 100644
--- a/localization/fr.xml
+++ b/localization/fr.xml
@@ -27,7 +27,7 @@
-
+
@@ -44,7 +44,7 @@
-
+
diff --git a/localization/gb.xml b/localization/gb.xml
index d4a400197ac0b..306e7dfbe3609 100644
--- a/localization/gb.xml
+++ b/localization/gb.xml
@@ -16,7 +16,7 @@
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/localization/gr.xml b/localization/gr.xml
index 95c4e7205fc8f..290d8edc30a7f 100644
--- a/localization/gr.xml
+++ b/localization/gr.xml
@@ -17,7 +17,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
diff --git a/localization/hr.xml b/localization/hr.xml
index 48bebe4e8257f..2ca3218f35f0b 100644
--- a/localization/hr.xml
+++ b/localization/hr.xml
@@ -18,7 +18,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/hu.xml b/localization/hu.xml
index 289c8664eaafe..d7117a1f4a329 100644
--- a/localization/hu.xml
+++ b/localization/hu.xml
@@ -17,7 +17,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
diff --git a/localization/ie.xml b/localization/ie.xml
index 9a312508f6509..866f0ea137ea2 100644
--- a/localization/ie.xml
+++ b/localization/ie.xml
@@ -18,7 +18,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/it.xml b/localization/it.xml
index 8aa930b098883..d6d6bb20593de 100644
--- a/localization/it.xml
+++ b/localization/it.xml
@@ -19,7 +19,7 @@
-
+
@@ -36,7 +36,7 @@
-
+
diff --git a/localization/lt.xml b/localization/lt.xml
index d4a3093ac826f..1eac5bed69ff5 100644
--- a/localization/lt.xml
+++ b/localization/lt.xml
@@ -17,7 +17,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
diff --git a/localization/lu.xml b/localization/lu.xml
index 439865a7152dd..7067c5a8c7da4 100644
--- a/localization/lu.xml
+++ b/localization/lu.xml
@@ -19,7 +19,7 @@
-
+
@@ -36,7 +36,7 @@
-
+
diff --git a/localization/lv.xml b/localization/lv.xml
index f31ce948c01ee..5d523284bf355 100644
--- a/localization/lv.xml
+++ b/localization/lv.xml
@@ -18,7 +18,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/mc.xml b/localization/mc.xml
index 3cec58a64764d..e029d66825d43 100644
--- a/localization/mc.xml
+++ b/localization/mc.xml
@@ -18,7 +18,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/mt.xml b/localization/mt.xml
index 020fe41a97f4a..25d91193fa1a5 100644
--- a/localization/mt.xml
+++ b/localization/mt.xml
@@ -18,7 +18,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/nl.xml b/localization/nl.xml
index 048117e70846f..e2373b25a0416 100644
--- a/localization/nl.xml
+++ b/localization/nl.xml
@@ -16,7 +16,7 @@
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/localization/pl.xml b/localization/pl.xml
index 39c6913b9b802..b6e2032308a79 100644
--- a/localization/pl.xml
+++ b/localization/pl.xml
@@ -18,7 +18,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/pt.xml b/localization/pt.xml
index 1b23812edfa02..5e6bca8e92ac6 100644
--- a/localization/pt.xml
+++ b/localization/pt.xml
@@ -18,7 +18,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/localization/ro.xml b/localization/ro.xml
index 94c218288d0d6..ef4633b3730de 100644
--- a/localization/ro.xml
+++ b/localization/ro.xml
@@ -7,7 +7,7 @@
-
+
@@ -18,7 +18,7 @@
-
+
diff --git a/localization/se.xml b/localization/se.xml
index 76c2be015b7f6..4c67f75be4662 100644
--- a/localization/se.xml
+++ b/localization/se.xml
@@ -18,7 +18,7 @@
-
+
@@ -36,7 +36,7 @@
-
+
diff --git a/localization/si.xml b/localization/si.xml
index 61e76b6d83741..2131efc4c8d9e 100644
--- a/localization/si.xml
+++ b/localization/si.xml
@@ -16,7 +16,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
diff --git a/localization/sk.xml b/localization/sk.xml
index ef0f82f1d749d..0fbd32040d69a 100644
--- a/localization/sk.xml
+++ b/localization/sk.xml
@@ -17,7 +17,7 @@
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/src/Adapter/Debug/DebugMode.php b/src/Adapter/Debug/DebugMode.php
index f199043a0dc4a..8f574c3814f77 100644
--- a/src/Adapter/Debug/DebugMode.php
+++ b/src/Adapter/Debug/DebugMode.php
@@ -221,7 +221,11 @@ public function changePsModeDevValue($value)
{
// Check custom defines file first
if ($this->isCustomDefinesReadable()) {
- return $this->updateDebugModeValueInCustomFile($value);
+ $result = $this->updateDebugModeValueInCustomFile($value);
+ // If the constant is not found in custom file, fallback to main file
+ if ($result !== self::DEBUG_MODE_ERROR_NO_DEFINITION_FOUND) {
+ return $result;
+ }
}
if ($this->isMainDefinesReadable()) {
diff --git a/src/Adapter/Debug/DebugProfiling.php b/src/Adapter/Debug/DebugProfiling.php
index cea5254a9f795..a74e84fa0dbc1 100644
--- a/src/Adapter/Debug/DebugProfiling.php
+++ b/src/Adapter/Debug/DebugProfiling.php
@@ -175,7 +175,11 @@ private function changeProfilingValue(string $value): int
{
// Check custom defines file first
if ($this->isCustomDefinesReadable()) {
- return $this->updateProfilingValueInCustomFile($value);
+ $result = $this->updateProfilingValueInCustomFile($value);
+ // If the constant is not found in custom file, fallback to main file
+ if ($result !== self::DEBUG_PROFILING_ERROR_NO_DEFINITION_FOUND) {
+ return $result;
+ }
}
if ($this->isMainDefinesReadable()) {
diff --git a/src/Adapter/Discount/QueryHandler/GetDiscountTypesHandler.php b/src/Adapter/Discount/QueryHandler/GetDiscountTypesHandler.php
new file mode 100644
index 0000000000000..f2c184e4df092
--- /dev/null
+++ b/src/Adapter/Discount/QueryHandler/GetDiscountTypesHandler.php
@@ -0,0 +1,62 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ */
+
+namespace PrestaShop\PrestaShop\Adapter\Discount\QueryHandler;
+
+use PrestaShop\PrestaShop\Adapter\Discount\Repository\DiscountTypeRepository;
+use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsQueryHandler;
+use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountTypes;
+use PrestaShop\PrestaShop\Core\Domain\Discount\QueryHandler\GetDiscountTypesHandlerInterface;
+use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountType;
+
+#[AsQueryHandler]
+class GetDiscountTypesHandler implements GetDiscountTypesHandlerInterface
+{
+ public function __construct(
+ private readonly DiscountTypeRepository $discountTypeRepository,
+ ) {
+ }
+
+ /**
+ * @return DiscountType[]
+ */
+ public function handle(GetDiscountTypes $query): array
+ {
+ $groupedTypes = $this->discountTypeRepository->getAllTypes();
+
+ return array_map(
+ fn (array $type) => new DiscountType(
+ $type['id_cart_rule_type'],
+ $type['discount_type'],
+ $type['names'],
+ $type['descriptions'],
+ $type['is_core'],
+ $type['enabled']
+ ),
+ array_values($groupedTypes)
+ );
+ }
+}
diff --git a/src/Adapter/Discount/Repository/DiscountTypeRepository.php b/src/Adapter/Discount/Repository/DiscountTypeRepository.php
index fc7856007e852..63b32eaec22a3 100644
--- a/src/Adapter/Discount/Repository/DiscountTypeRepository.php
+++ b/src/Adapter/Discount/Repository/DiscountTypeRepository.php
@@ -40,6 +40,52 @@ public function __construct(
) {
}
+ /**
+ * Get all discount types grouped by discount type ID with translations
+ *
+ * @return array, descriptions: array}>
+ */
+ public function getAllTypes(): array
+ {
+ $qb = $this->connection->createQueryBuilder();
+ $qb
+ ->select('crt.id_cart_rule_type', 'crt.discount_type', 'crt.is_core', 'crt.active', 'crtl.name', 'crtl.description', 'crtl.id_lang')
+ ->from($this->dbPrefix . 'cart_rule_type', 'crt')
+ ->leftJoin('crt', $this->dbPrefix . 'cart_rule_type_lang', 'crtl', 'crt.id_cart_rule_type = crtl.id_cart_rule_type')
+ ->orderBy('crt.id_cart_rule_type', 'ASC')
+ ->addOrderBy('crtl.id_lang', 'ASC')
+ ;
+
+ $allTypes = $qb->executeQuery()->fetchAllAssociative();
+ $groupedTypes = [];
+
+ foreach ($allTypes as $type) {
+ $discountTypeId = (int) $type['id_cart_rule_type'];
+ $langId = !empty($type['id_lang']) ? (int) $type['id_lang'] : null;
+
+ if (!isset($groupedTypes[$discountTypeId])) {
+ $groupedTypes[$discountTypeId] = [
+ 'id_cart_rule_type' => $discountTypeId,
+ 'discount_type' => $type['discount_type'],
+ 'is_core' => (bool) $type['is_core'],
+ 'enabled' => (bool) $type['active'],
+ 'names' => [],
+ 'descriptions' => [],
+ ];
+ }
+
+ // Add translations if available
+ if ($langId !== null && !empty($type['name'])) {
+ $groupedTypes[$discountTypeId]['names'][$langId] = $type['name'];
+ }
+ if ($langId !== null && !empty($type['description'])) {
+ $groupedTypes[$discountTypeId]['descriptions'][$langId] = $type['description'];
+ }
+ }
+
+ return $groupedTypes;
+ }
+
/**
* Get all active discount types
*
diff --git a/src/Adapter/Discount/Validate/DiscountValidator.php b/src/Adapter/Discount/Validate/DiscountValidator.php
index 6eb3fa63688dc..b47c54c357a38 100644
--- a/src/Adapter/Discount/Validate/DiscountValidator.php
+++ b/src/Adapter/Discount/Validate/DiscountValidator.php
@@ -28,6 +28,7 @@
namespace PrestaShop\PrestaShop\Adapter\Discount\Validate;
use CartRule;
+use Customer;
use PrestaShop\Decimal\DecimalNumber;
use PrestaShop\PrestaShop\Adapter\AbstractObjectModelValidator;
use PrestaShop\PrestaShop\Adapter\Attribute\Repository\AttributeRepository;
@@ -96,6 +97,7 @@ public function setDiscountRepository(DiscountRepository $discountRepository): v
public function validate(CartRule $cartRule): void
{
$this->validateCartRuleProperty($cartRule, 'id_customer', DiscountConstraintException::INVALID_CUSTOMER_ID);
+ $this->assertCustomerIsNotGuest($cartRule);
$this->validateCartRuleProperty($cartRule, 'date_from', DiscountConstraintException::INVALID_DATE_FROM);
$this->validateCartRuleProperty($cartRule, 'date_to', DiscountConstraintException::INVALID_DATE_TO);
$this->validateCartRuleProperty($cartRule, 'description', DiscountConstraintException::INVALID_DESCRIPTION);
@@ -318,4 +320,19 @@ private function assertDateRangeIsCorrect(CartRule $cartrule): void
throw new DiscountConstraintException('Date from cannot be greater than date to.', DiscountConstraintException::DATE_FROM_GREATER_THAN_DATE_TO);
}
}
+
+ private function assertCustomerIsNotGuest(CartRule $cartRule): void
+ {
+ if (empty($cartRule->id_customer)) {
+ return;
+ }
+
+ $customer = new Customer((int) $cartRule->id_customer);
+ if ($customer->isGuest()) {
+ throw new DiscountConstraintException(
+ sprintf('Cannot assign discount to guest customer with ID %d', $cartRule->id_customer),
+ DiscountConstraintException::INVALID_GUEST_CUSTOMER
+ );
+ }
+ }
}
diff --git a/src/Adapter/Presenter/Product/ProductLazyArray.php b/src/Adapter/Presenter/Product/ProductLazyArray.php
index 384d8b210d493..286922a8090f1 100644
--- a/src/Adapter/Presenter/Product/ProductLazyArray.php
+++ b/src/Adapter/Presenter/Product/ProductLazyArray.php
@@ -1199,12 +1199,8 @@ protected function shouldEnableAddToCartButton(
}
// Disable because of stock management
- if (
- $settings->stock_management_enabled
- && !$product['allow_oosp']
- && ($product['quantity'] <= 0
- || $product['quantity'] - $this->getQuantityWanted() < 0
- || $product['quantity'] - $this->getMinimalQuantity() < 0)
+ if ($settings->stock_management_enabled && !$product['allow_oosp']
+ && ($product['quantity'] <= 0 || $product['quantity'] - $this->getQuantityWanted() < 0)
) {
$shouldEnable = false;
}
@@ -1213,22 +1209,77 @@ protected function shouldEnableAddToCartButton(
}
/**
- * @return int Quantity of product requested by the customer
+ * Gets the quantity wanted by the customer for the product. We will take his request,
+ * but we will adjust it if it's lower than the required quantity.
+ *
+ * @return int Quantity of product requested by the customer, altered if needed, always a positive integer
*/
- private function getQuantityWanted()
+ #[LazyArrayAttribute(arrayAccess: true)]
+ public function getQuantityWanted()
{
- return (int) Tools::getValue(
- 'quantity_wanted',
- $this->product['quantity_wanted'] ?? 1
- );
+ if (empty($this->product['quantity_wanted'])) {
+ // Get the quantity wanted from the request
+ $quantityWantedByTheCustomer = (int) Tools::getValue('quantity_wanted', 1);
+
+ // Get minimal required quantity for purchase
+ $requiredQuantityForPurchase = $this->getQuantityRequired();
+
+ if ($quantityWantedByTheCustomer < $requiredQuantityForPurchase) {
+ $quantityWantedByTheCustomer = $requiredQuantityForPurchase;
+ }
+
+ $this->product['quantity_wanted'] = $quantityWantedByTheCustomer;
+ }
+
+ return $this->product['quantity_wanted'];
}
/**
- * @return int Minimal quantity of product requested by the customer
+ * Gets the minimal quantity the customer has to purchase. We cannot just let him buy 1 piece
+ * if the minimal quantity is higher. Also, we adjust it by the quantity already in cart.
+ *
+ * @return int Minimal quantity of product the customer buy right now, always a positive integer
*/
- private function getMinimalQuantity()
+ #[LazyArrayAttribute(arrayAccess: true)]
+ public function getQuantityRequired()
{
- return (int) $this->product['minimal_quantity'];
+ // For the required quantity, we will need to limit it by the minimal quantity on the low side.
+ $requiredQuantityForPurchase = $this->getMinimalQuantity();
+
+ /*
+ * We reduce it by the quantity we already have in cart. If the user already has a sufficient
+ * quantity in the cart, we don't need to add more. Although it may seem that we can just reset
+ * the minimal quantity to one in that case, we must not do that, because the quantity in the cart
+ * may not be the correct one.
+ */
+ if (!empty($this->product['cart_quantity'])) {
+ $requiredQuantityForPurchase -= $this->product['cart_quantity'];
+ if ($requiredQuantityForPurchase < 1) {
+ $requiredQuantityForPurchase = 1;
+ }
+ }
+
+ return $requiredQuantityForPurchase;
+ }
+
+ /**
+ * Gets the minimal quantity allowed for the product or its combination. With no adjustments
+ * by the current context. The builder of this object is responsible for passing the correct
+ * minimal quantity depending on the combination selected.
+ *
+ * @return int Minimal quantity of product from it's settings, always a positive integer
+ */
+ #[LazyArrayAttribute(arrayAccess: true)]
+ public function getMinimalQuantity()
+ {
+ $minimalQuantity = (int) $this->product['minimal_quantity'];
+
+ // If we received faulty data, we correct it to 1
+ if ($minimalQuantity < 1) {
+ $minimalQuantity = 1;
+ }
+
+ return $minimalQuantity;
}
/**
diff --git a/src/Core/Domain/Discount/Exception/DiscountConstraintException.php b/src/Core/Domain/Discount/Exception/DiscountConstraintException.php
index 0ba9f77a85bda..cd862a948f532 100644
--- a/src/Core/Domain/Discount/Exception/DiscountConstraintException.php
+++ b/src/Core/Domain/Discount/Exception/DiscountConstraintException.php
@@ -78,4 +78,5 @@ class DiscountConstraintException extends DiscountException
public const INVALID_PRODUCT_DISCOUNT_MISSING_TARGET = 48;
public const INVALID_PRODUCT_DISCOUNT_INCOMPATIBLE_TARGETS = 49;
public const INVALID_PRODUCT_DISCOUNT_INCOMPATIBLE_REDUCTIONS = 50;
+ public const INVALID_GUEST_CUSTOMER = 51;
}
diff --git a/src/Core/Domain/Discount/Query/GetDiscountTypes.php b/src/Core/Domain/Discount/Query/GetDiscountTypes.php
new file mode 100644
index 0000000000000..472e4a8528f9b
--- /dev/null
+++ b/src/Core/Domain/Discount/Query/GetDiscountTypes.php
@@ -0,0 +1,31 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ */
+
+namespace PrestaShop\PrestaShop\Core\Domain\Discount\Query;
+
+class GetDiscountTypes
+{
+}
diff --git a/src/Core/Domain/Discount/QueryHandler/GetDiscountTypesHandlerInterface.php b/src/Core/Domain/Discount/QueryHandler/GetDiscountTypesHandlerInterface.php
new file mode 100644
index 0000000000000..e2cca3a665c57
--- /dev/null
+++ b/src/Core/Domain/Discount/QueryHandler/GetDiscountTypesHandlerInterface.php
@@ -0,0 +1,38 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ */
+
+namespace PrestaShop\PrestaShop\Core\Domain\Discount\QueryHandler;
+
+use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountTypes;
+use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountType;
+
+interface GetDiscountTypesHandlerInterface
+{
+ /**
+ * @return DiscountType[]
+ */
+ public function handle(GetDiscountTypes $query): array;
+}
diff --git a/src/Core/Domain/Discount/QueryResult/DiscountType.php b/src/Core/Domain/Discount/QueryResult/DiscountType.php
new file mode 100644
index 0000000000000..df4470f224660
--- /dev/null
+++ b/src/Core/Domain/Discount/QueryResult/DiscountType.php
@@ -0,0 +1,78 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ */
+
+namespace PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult;
+
+class DiscountType
+{
+ /**
+ * @param int $discountTypeId
+ * @param string $type
+ * @param array $localizedNames indexed by language ID
+ * @param array $localizedDescriptions indexed by language ID
+ * @param bool $isCore
+ * @param bool $enabled
+ */
+ public function __construct(
+ private readonly int $discountTypeId,
+ private readonly string $type,
+ private readonly array $localizedNames,
+ private readonly array $localizedDescriptions,
+ private readonly bool $isCore = false,
+ private readonly bool $enabled = true
+ ) {
+ }
+
+ public function getDiscountTypeId(): int
+ {
+ return $this->discountTypeId;
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getLocalizedNames(): array
+ {
+ return $this->localizedNames;
+ }
+
+ public function getLocalizedDescriptions(): array
+ {
+ return $this->localizedDescriptions;
+ }
+
+ public function isCore(): bool
+ {
+ return $this->isCore;
+ }
+
+ public function isEnabled(): bool
+ {
+ return $this->enabled;
+ }
+}
diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml b/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml
index 913d739603617..3b5233fbae96c 100644
--- a/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml
+++ b/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml
@@ -40,6 +40,9 @@ services:
PrestaShop\PrestaShop\Adapter\Discount\QueryHandler\GetDiscountForEditingHandler:
autoconfigure: true
+ PrestaShop\PrestaShop\Adapter\Discount\QueryHandler\GetDiscountTypesHandler:
+ autoconfigure: true
+
PrestaShop\PrestaShop\Adapter\Discount\Validate\DiscountValidator:
calls:
- setDiscountRepository: [ '@PrestaShop\PrestaShop\Adapter\Discount\Repository\DiscountRepository' ]
diff --git a/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php b/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php
index 9d6ae9592989e..eeea34c2d872e 100644
--- a/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php
+++ b/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php
@@ -94,6 +94,7 @@ class ProductLazyArrayTest extends TestCase
'out_of_stock' => OutOfStockType::OUT_OF_STOCK_DEFAULT,
'customizable' => 0,
'active' => 1,
+ 'minimal_quantity' => 1,
];
private const PRODUCT_DISCONTINUED = 'This product is no longer available for sale.';
diff --git a/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php b/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php
index fdf79d4d05264..ef997c08d0a00 100644
--- a/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php
+++ b/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php
@@ -65,6 +65,26 @@ public function createCustomer($customerName, $customerEmail)
SharedStorage::getStorage()->set($customerName, $customer->id);
}
+ /**
+ * @Given /^there is a guest customer named "(.+)" whose email is "(.+)"$/
+ */
+ public function createGuestCustomer($customerName, $customerEmail)
+ {
+ /** @var Hashing $crypto */
+ $crypto = ServiceLocator::get(Hashing::class);
+
+ $customer = new Customer();
+ $customer->firstname = 'fake';
+ $customer->lastname = 'fake';
+ $customer->passwd = $crypto->hash('Correct Horse Battery Staple');
+ $customer->email = $customerEmail;
+ $customer->is_guest = true;
+ $customer->id_shop = Context::getContext()->shop->id;
+ $customer->add();
+ $this->customers[$customerName] = $customer;
+ SharedStorage::getStorage()->set($customerName, $customer->id);
+ }
+
/**
* @Given /^customer "(.+)" belongs to group "(.+)"$/
*/
diff --git a/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php
index 2e4aa6235c7ee..3c89d3cc8e64f 100644
--- a/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php
+++ b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php
@@ -136,6 +136,14 @@ public function assertDiscountIncompatibleReductions(): void
$this->assertLastErrorIs(DiscountConstraintException::class, DiscountConstraintException::INVALID_PRODUCT_DISCOUNT_INCOMPATIBLE_REDUCTIONS);
}
+ /**
+ * @Then I should get an error that discount cannot be assigned to guest customers
+ */
+ public function assertDiscountCannotBeAssignedToGuestCustomers(): void
+ {
+ $this->assertLastErrorIs(DiscountConstraintException::class, DiscountConstraintException::INVALID_GUEST_CUSTOMER);
+ }
+
/**
* @Then discount :discountReference should have the following properties:
*
diff --git a/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountTypesFeatureContext.php b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountTypesFeatureContext.php
new file mode 100644
index 0000000000000..366f4267b3313
--- /dev/null
+++ b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountTypesFeatureContext.php
@@ -0,0 +1,121 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ */
+
+namespace Tests\Integration\Behaviour\Features\Context\Domain\Discount;
+
+use Behat\Gherkin\Node\TableNode;
+use PHPUnit\Framework\Assert;
+use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountTypes;
+use RuntimeException;
+use Tests\Integration\Behaviour\Features\Context\Domain\AbstractDomainFeatureContext;
+
+class DiscountTypesFeatureContext extends AbstractDomainFeatureContext
+{
+ /**
+ * @Then I should receive the following discount types:
+ *
+ * @param TableNode $table
+ */
+ public function assertDiscountTypes(TableNode $table): void
+ {
+ $queryBus = $this->getQueryBus();
+ $query = new GetDiscountTypes();
+ $discountTypes = $queryBus->handle($query);
+
+ Assert::assertNotNull($discountTypes, 'No discount types received');
+ Assert::assertIsArray($discountTypes, 'Discount types should be an array');
+
+ $expectedDiscountTypes = $this->localizeByColumns($table);
+
+ Assert::assertGreaterThanOrEqual(
+ count($expectedDiscountTypes),
+ count($discountTypes),
+ sprintf('Expected %d discount types but got %d', count($expectedDiscountTypes), count($discountTypes))
+ );
+
+ foreach ($expectedDiscountTypes as $expectedDiscountType) {
+ $foundDiscountType = null;
+ foreach ($discountTypes as $discountType) {
+ if ($discountType->getType() === $expectedDiscountType['type']) {
+ $foundDiscountType = $discountType;
+ break;
+ }
+ }
+
+ if (null === $foundDiscountType) {
+ throw new RuntimeException(sprintf('Couldnt find discount type "%s"', $expectedDiscountType['type']));
+ }
+
+ if (isset($expectedDiscountType['discountTypeId'])) {
+ Assert::assertEquals(
+ (int) $expectedDiscountType['discountTypeId'],
+ $foundDiscountType->getDiscountTypeId(),
+ sprintf('Unexpected discountTypeId for type "%s"', $expectedDiscountType['type'])
+ );
+ }
+
+ if (isset($expectedDiscountType['core'])) {
+ $expectedCore = filter_var($expectedDiscountType['core'], FILTER_VALIDATE_BOOLEAN);
+ Assert::assertEquals(
+ $expectedCore,
+ $foundDiscountType->isCore(),
+ sprintf('Unexpected core value for type "%s"', $expectedDiscountType['type'])
+ );
+ }
+
+ if (isset($expectedDiscountType['enabled'])) {
+ $expectedEnabled = filter_var($expectedDiscountType['enabled'], FILTER_VALIDATE_BOOLEAN);
+ Assert::assertEquals(
+ $expectedEnabled,
+ $foundDiscountType->isEnabled(),
+ sprintf('Unexpected enabled value for type "%s"', $expectedDiscountType['type'])
+ );
+ }
+
+ if (isset($expectedDiscountType['names'])) {
+ $actualNames = $foundDiscountType->getLocalizedNames();
+ foreach ($expectedDiscountType['names'] as $langId => $expectedName) {
+ Assert::assertEquals(
+ $expectedName,
+ $actualNames[$langId],
+ sprintf('Unexpected name for language ID %d in type "%s"', $langId, $expectedDiscountType['type'])
+ );
+ }
+ }
+
+ if (isset($expectedDiscountType['descriptions'])) {
+ $actualDescriptions = $foundDiscountType->getLocalizedDescriptions();
+ foreach ($expectedDiscountType['descriptions'] as $langId => $expectedDescription) {
+ Assert::assertEquals(
+ $expectedDescription,
+ $actualDescriptions[$langId],
+ sprintf('Unexpected description for language ID %d in type "%s"', $langId, $expectedDiscountType['type'])
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Integration/Behaviour/Features/Scenario/Discount/BO/add_discount_excludes_guest_customers.feature b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/add_discount_excludes_guest_customers.feature
new file mode 100644
index 0000000000000..7b009a8d1317a
--- /dev/null
+++ b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/add_discount_excludes_guest_customers.feature
@@ -0,0 +1,55 @@
+# ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s discount --tags add-discount-excludes-guests
+@restore-all-tables-before-feature
+@restore-languages-after-feature
+@add-discount-excludes-guests
+Feature: Add discount with single customer eligibility excludes guest customers
+ PrestaShop should prevent BO users from creating discounts for guest customers
+ As a BO user
+ I should not be able to create discounts that are assigned to guest customers
+ Guest customers should be excluded from the customer search when creating discounts
+
+ Background:
+ Given shop "shop1" with name "test_shop" exists
+ Given there is a currency named "usd" with iso code "USD" and exchange rate of 0.92
+ Given currency "usd" is the default one
+ And language with iso code "en" is the default one
+ And language "french" with locale "fr-FR" exists
+ Given there is a customer named "john_doe" whose email is "john.doe@example.com"
+ And there is a guest customer named "guest_customer" whose email is "guest@example.com"
+
+ Scenario: Cannot create a discount limited to a guest customer
+ When I create a "cart_level" discount "discount_for_guest" with following properties:
+ | name[en-US] | Guest Discount |
+ | name[fr-FR] | Réduction Invité |
+ | active | true |
+ | valid_from | 2025-01-01 00:00:00 |
+ | valid_to | 2025-12-31 23:59:59 |
+ | code | GUEST_2025 |
+ | reduction_percent | 20.0 |
+ | customer | guest_customer |
+ | total_quantity | 100 |
+ | quantity_per_user | 1 |
+ | minimum_product_quantity | 0 |
+ Then I should get an error that discount cannot be assigned to guest customers
+
+ Scenario: Can create a discount for a regular customer but not for a guest
+ When I create a "cart_level" discount "discount_for_regular" with following properties:
+ | name[en-US] | Regular Customer Discount |
+ | name[fr-FR] | Réduction Client |
+ | active | true |
+ | valid_from | 2025-01-01 00:00:00 |
+ | valid_to | 2025-12-31 23:59:59 |
+ | code | REGULAR_2025 |
+ | reduction_percent | 15.0 |
+ | customer | john_doe |
+ | total_quantity | 100 |
+ | quantity_per_user | 1 |
+ | minimum_product_quantity | 0 |
+ Then discount "discount_for_regular" should have the following properties:
+ | name[en-US] | Regular Customer Discount |
+ | name[fr-FR] | Réduction Client |
+ | customer | john_doe |
+ # This should fail if we try to update to guest customer
+ When I update discount "discount_for_regular" with the following properties:
+ | customer | guest_customer |
+ Then I should get an error that discount cannot be assigned to guest customers
diff --git a/tests/Integration/Behaviour/Features/Scenario/Discount/BO/discount_types.feature b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/discount_types.feature
new file mode 100644
index 0000000000000..845d5ee59a35f
--- /dev/null
+++ b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/discount_types.feature
@@ -0,0 +1,18 @@
+# ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s discount --tags discount-types
+@discount-types
+Feature: Discount Types
+ PrestaShop provides discount types
+ As a developer
+ I must be able to retrieve the list of available discount types
+
+ Background:
+ Given I enable feature flag "discount"
+
+ Scenario: List all discount types
+ Then I should receive the following discount types:
+ | type | core | enabled | names[en-US] | descriptions[en-US] |
+ | cart_level | true | true | On cart amount | Discount applied to cart |
+ | product_level | true | true | On catalog products | Discount applied to specific products |
+ | free_shipping | true | true | On free shipping | Discount that provides free shipping to the order |
+ | free_gift | true | true | On free gift | Discount that provides a free gift product |
+ | order_level | true | true | On total order | Discount applied to the order |
diff --git a/tests/Integration/Behaviour/behat.yml b/tests/Integration/Behaviour/behat.yml
index d3673cef112e8..86ae0b80742dd 100644
--- a/tests/Integration/Behaviour/behat.yml
+++ b/tests/Integration/Behaviour/behat.yml
@@ -322,6 +322,7 @@ default:
- Tests\Integration\Behaviour\Features\Context\Domain\CustomerFeatureContext
- Tests\Integration\Behaviour\Features\Context\Domain\CustomerGroupFeatureContext
- Tests\Integration\Behaviour\Features\Context\Domain\Discount\DiscountFeatureContext
+ - Tests\Integration\Behaviour\Features\Context\Domain\Discount\DiscountTypesFeatureContext
- Tests\Integration\Behaviour\Features\Context\Domain\ProductFeatureContext
- Tests\Integration\Behaviour\Features\Context\Domain\Product\AddProductFeatureContext
- Tests\Integration\Behaviour\Features\Context\Domain\Product\Combination\CombinationAssertionFeatureContext
diff --git a/tests/UI/campaigns/functional/API/02_checkEndpoints.ts b/tests/UI/campaigns/functional/API/02_checkEndpoints.ts
index 8f226ef627dd3..5834997582ddb 100644
--- a/tests/UI/campaigns/functional/API/02_checkEndpoints.ts
+++ b/tests/UI/campaigns/functional/API/02_checkEndpoints.ts
@@ -204,6 +204,8 @@ describe('API : Check endpoints', async () => {
// @todo: add tests
'/customers: POST',
// @todo: add tests
+ '/discount-types: GET',
+ // @todo: add tests
'/discounts/bulk-delete: DELETE',
// @todo: add tests
'/discounts/bulk-update-status: PATCH',
diff --git a/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts b/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts
index ebe27ec07ee79..22806b0cf22be 100644
--- a/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts
+++ b/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts
@@ -432,12 +432,11 @@ describe('BO - Catalog - Products : Details tab', async () => {
expect(pageTitle).to.contains(newProductData.name);
});
- // @todo : https://github.com/PrestaShop/hummingbird/pull/898
- it.skip('should check the product condition', async function () {
+ it('should check the product condition', async function () {
await testContext.addContextItem(this, 'testIdentifier', 'checkProductCondition', baseContext);
const productCondition = await foHummingbirdProductPage.getProductCondition(page);
- expect(productCondition).to.eq(`Condition ${editProductData.condition}`);
+ expect(productCondition).to.eq(editProductData.condition);
});
it('should go back to BO', async function () {
diff --git a/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts b/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts
index 946e09dae8606..4667986e028f1 100644
--- a/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts
+++ b/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts
@@ -304,8 +304,7 @@ describe('BO - Catalog - Products : Pricing tab', async () => {
expect(pageTitle).to.contains(newProductData.name);
});
- // @todo : https://github.com/PrestaShop/hummingbird/issues/879
- it.skip('should check the on sale flag', async function () {
+ it('should check the on sale flag', async function () {
await testContext.addContextItem(this, 'testIdentifier', 'checkOnSaleFlag', baseContext);
const flagText = await foHummingbirdProductPage.getProductTag(page);
diff --git a/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts b/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts
index 6b27e38181751..85128a313ec76 100644
--- a/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts
+++ b/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts
@@ -20,7 +20,6 @@ describe('BO - Catalog - Products list : Bulk actions, Enable/Disable, 3-dot but
let numberOfProducts: number = 0;
let productName: string = '';
- // before and after functions
before(async function () {
browserContext = await utilsPlaywright.createBrowserContext(this.browser);
page = await utilsPlaywright.newTab(browserContext);
diff --git a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts
index 17e04c46b073e..eaa24fd3d2b17 100644
--- a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts
+++ b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts
@@ -173,7 +173,7 @@ describe('BO - Catalog - Categories : CRUD Category in BO', async () => {
// Check category name
const pageTitle = await foHummingbirdCategoryPage.getHeaderPageName(page);
- expect(pageTitle).to.contains(createCategoryData.name.toUpperCase());
+ expect(pageTitle).to.contains(createCategoryData.name);
// Check category description
const categoryDescription = await foHummingbirdCategoryPage.getCategoryDescription(page);
@@ -258,7 +258,7 @@ describe('BO - Catalog - Categories : CRUD Category in BO', async () => {
// Check subcategory name
const pageTitle = await foHummingbirdCategoryPage.getHeaderPageName(page);
- expect(pageTitle).to.contains(createSubCategoryData.name.toUpperCase());
+ expect(pageTitle).to.contains(createSubCategoryData.name);
// Check subcategory description
const subcategoryDescription = await foHummingbirdCategoryPage.getCategoryDescription(page);
@@ -431,7 +431,7 @@ describe('BO - Catalog - Categories : CRUD Category in BO', async () => {
// Check if it is redirected to the category
const categoryName = await foHummingbirdCategoryPage.getHeaderPageName(page);
- expect(categoryName).to.contains(arg.category.redirectedCategory!.name.toUpperCase());
+ expect(categoryName).to.contains(arg.category.redirectedCategory!.name);
});
it('should go back to BO', async function () {
diff --git a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts
index 3d1af7b91e0d8..1a4e3aead6cfe 100644
--- a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts
+++ b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts
@@ -109,7 +109,7 @@ describe('BO - Catalog - Categories : Edit home category', async () => {
// Check category name
const pageTitle = await foHummingbirdCategoryPage.getHeaderPageName(page);
- expect(pageTitle).to.contains(editCategoryData.name.toUpperCase());
+ expect(pageTitle).to.contains(editCategoryData.name);
// Check category description
const categoryDescription = await foHummingbirdCategoryPage.getCategoryDescription(page);
diff --git a/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts b/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts
index 565df8a86278f..80e4b00aabb87 100644
--- a/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts
+++ b/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts
@@ -20,6 +20,8 @@ describe('BO - Design - Positions : Filter module', async () => {
const hooks: string[] = [
'displayAdminCustomers',
+ 'displayCustomerAccount',
+ 'displayFooter',
'displayMyAccountBlock',
'displayProductActions',
];
diff --git a/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts b/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts
index def0c665a0a72..3f0571a00bcf5 100644
--- a/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts
+++ b/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts
@@ -282,8 +282,7 @@ describe('BO - Design - Image Settings : Check category image format', async ()
expect(isCategoryPageVisible, 'Home category page was not opened').to.eq(true);
});
- // @todo : https://github.com/PrestaShop/hummingbird/issues/874
- it.skip('should check that the main image of the quick view is a WebP', async function () {
+ it('should check that the main image of the quick view is a WebP', async function () {
await testContext.addContextItem(this, 'testIdentifier', `checkMainImageQuickView${argExtension}`, baseContext);
const categoryImage = await foHummingbirdCategoryPage.getCategoryImageMain(page, arg.category.name);
diff --git a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts
index 91473749e9c7a..723694c43b44f 100644
--- a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts
+++ b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts
@@ -24,7 +24,6 @@ describe('BO - Shop Parameters - Product Settings : Update separator of attribut
const productAttributes: string[] = ['1', 'size', 's/8', 'color', 'white'];
- // before and after functions
before(async function () {
browserContext = await utilsPlaywright.createBrowserContext(this.browser);
page = await utilsPlaywright.newTab(browserContext);
@@ -57,18 +56,16 @@ describe('BO - Shop Parameters - Product Settings : Update separator of attribut
expect(pageTitle).to.contains(boProductSettingsPage.pageTitle);
});
- const tests = [
- {args: {option: ',', attributesInProductLink: productAttributes.join(',')}},
- {args: {option: '-', attributesInProductLink: productAttributes.join('-')}},
- ];
-
- tests.forEach((test, index: number) => {
- it(`should choose the separator option '${test.args.option}'`, async function () {
+ [
+ {option: ',', attributesInProductLink: productAttributes.join(',')},
+ {option: '-', attributesInProductLink: productAttributes.join('-')},
+ ].forEach((arg, index: number) => {
+ it(`should choose the separator option '${arg.option}'`, async function () {
await testContext.addContextItem(this, 'testIdentifier', `chooseOption_${index}`, baseContext);
const result = await boProductSettingsPage.setSeparatorOfAttributeOnProductLink(
page,
- test.args.option,
+ arg.option,
);
expect(result).to.contains(boProductSettingsPage.successfulUpdateMessage);
});
@@ -97,7 +94,7 @@ describe('BO - Shop Parameters - Product Settings : Update separator of attribut
await testContext.addContextItem(this, 'testIdentifier', `checkAttributeSeparator_${index}`, baseContext);
const currentURL = await foHummingbirdProductPage.getProductPageURL(page);
- expect(currentURL).to.contains(test.args.attributesInProductLink);
+ expect(currentURL).to.contains(arg.attributesInProductLink);
});
it('should close the page and go back to BO', async function () {
diff --git a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts
index 1184d2a11bd23..55917e2f243cf 100644
--- a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts
+++ b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts
@@ -20,7 +20,6 @@ describe('BO - Shop Parameters - Product Settings : Update label of in-stock pro
let browserContext: BrowserContext;
let page: Page;
- // before and after functions
before(async function () {
browserContext = await utilsPlaywright.createBrowserContext(this.browser);
page = await utilsPlaywright.newTab(browserContext);
@@ -87,11 +86,13 @@ describe('BO - Shop Parameters - Product Settings : Update label of in-stock pro
it('should check the label of in-stock product in FO product page', async function () {
await testContext.addContextItem(this, 'testIdentifier', `checkLabelInStock_${index}`, baseContext);
- const isVisible = await foHummingbirdProductPage.isAvailabilityQuantityDisplayed(page);
+ const isVisible = await foHummingbirdProductPage.hasProductAvailabilityLabel(page);
expect(isVisible).to.be.equal(test.args.exist);
- const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page);
- expect(availabilityLabel).to.contains(test.args.labelToCheck);
+ if (test.args.exist) {
+ const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page);
+ expect(availabilityLabel).to.contains(test.args.labelToCheck);
+ }
});
it('should go back to BO', async function () {
diff --git a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts
index b1d818611e69d..6ee9878e4b117 100644
--- a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts
+++ b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts
@@ -18,8 +18,7 @@ import {
const baseContext: string = 'functional_BO_shopParameters_productSettings_productsStock_labelOutOfStock';
-describe('BO - Shop Parameters - product Settings : Set label out-of-stock with '
- + 'allowed/denied backorders', async () => {
+describe('BO - Shop Parameters - product Settings : Set label out-of-stock with allowed/denied backorders', async () => {
let browserContext: BrowserContext;
let page: Page;
@@ -30,7 +29,6 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with
labelWhenInStock: ' ',
});
- // before and after functions
before(async function () {
browserContext = await utilsPlaywright.createBrowserContext(this.browser);
page = await utilsPlaywright.newTab(browserContext);
@@ -101,49 +99,45 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with
const tests = [
{
- args: {
- action: 'enable',
- enable: true,
- backordersAction: 'allowed',
- label: 'You can order',
- labelToCheck: 'You can order',
- },
+ action: 'enable',
+ enable: true,
+ backordersAction: 'allowed',
+ label: 'You can order',
},
{
- args: {
- action: 'enable', enable: true, backordersAction: 'allowed', label: ' ', labelToCheck: '',
- },
+ action: 'enable',
+ enable: true,
+ backordersAction: 'allowed',
+ label: ' ',
},
{
- args: {
- action: 'disable', enable: false, backordersAction: 'denied', label: ' ', labelToCheck: '',
- },
+ action: 'disable',
+ enable: false,
+ backordersAction: 'denied',
+ label: ' ',
},
{
- args: {
- action: 'disable',
- enable: false,
- backordersAction: 'denied',
- label: 'Out-of-Stock',
- labelToCheck: 'Out-of-Stock',
- },
+ action: 'disable',
+ enable: false,
+ backordersAction: 'denied',
+ label: 'Out-of-Stock',
},
];
- tests.forEach((test, index: number) => {
- it(`should ${test.args.action} allow ordering of out-of-stock products`, async function () {
+ tests.forEach((arg, index: number) => {
+ it(`should ${arg.action} allow ordering of out-of-stock products`, async function () {
await testContext.addContextItem(
this,
'testIdentifier',
- `${test.args.action}AllowOrderingOutOfStock${index}`,
+ `${arg.action}AllowOrderingOutOfStock${index}`,
baseContext,
);
- const result = await boProductSettingsPage.setAllowOrderingOutOfStockStatus(page, test.args.enable);
+ const result = await boProductSettingsPage.setAllowOrderingOutOfStockStatus(page, arg.enable);
expect(result).to.contains(boProductSettingsPage.successfulUpdateMessage);
});
- it(`should set Label of out-of-stock products with ${test.args.backordersAction} backorders`, async function () {
+ it(`should set Label of out-of-stock products with ${arg.backordersAction} backorders`, async function () {
await testContext.addContextItem(
this,
'testIdentifier',
@@ -153,10 +147,10 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with
let result;
- if (test.args.enable) {
- result = await boProductSettingsPage.setLabelOosAllowedBackorders(page, test.args.label);
+ if (arg.enable) {
+ result = await boProductSettingsPage.setLabelOosAllowedBackorders(page, arg.label);
} else {
- result = await boProductSettingsPage.setLabelOosDeniedBackorders(page, test.args.label);
+ result = await boProductSettingsPage.setLabelOosDeniedBackorders(page, arg.label);
}
expect(result).to.contains(boProductSettingsPage.successfulUpdateMessage);
@@ -166,7 +160,7 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with
await testContext.addContextItem(
this,
'testIdentifier',
- `viewMyShop${test.args.action}${index}`,
+ `viewMyShop${arg.action}${index}`,
baseContext,
);
@@ -180,7 +174,7 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with
await testContext.addContextItem(
this,
'testIdentifier',
- `goToProductPage${test.args.action}${index}`,
+ `goToProductPage${arg.action}${index}`,
baseContext,
);
@@ -196,16 +190,23 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with
await testContext.addContextItem(
this,
'testIdentifier',
- `checkOrderingOutOfStock${test.args.action}${index}`,
+ `checkOrderingOutOfStock${arg.action}${index}`,
baseContext,
);
+ const hasLabel: boolean = arg.label.trim() !== '';
+
// Check quantity and availability label
const lastQuantityIsVisible = await foHummingbirdProductPage.isAddToCartButtonEnabled(page);
- expect(lastQuantityIsVisible).to.be.equal(test.args.enable);
+ expect(lastQuantityIsVisible).to.be.equal(arg.enable);
+
+ const hasProductAvailabilityLabel = await foHummingbirdProductPage.hasProductAvailabilityLabel(page);
+ expect(hasProductAvailabilityLabel).to.equals(hasLabel);
- const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page);
- expect(availabilityLabel).to.contains(test.args.labelToCheck);
+ if (hasLabel) {
+ const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page);
+ expect(availabilityLabel).to.contains(arg.label);
+ }
});
it('should go back to BO', async function () {
diff --git a/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts b/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts
index 108d681b32f6c..6447c6ec69a91 100644
--- a/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts
+++ b/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts
@@ -252,7 +252,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
true,
'utf16le',
);
- expect(isVisible, 'General info is not correct!').to.eq(true);
+ expect(isVisible).to.eq(true);
});
it('should check that Addresses table is empty', async function () {
@@ -265,7 +265,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
true,
'utf16le',
);
- expect(isVisible, 'Addresses table is not empty!').to.eq(true);
+ expect(isVisible).to.eq(true);
});
it('should check that Orders table is empty', async function () {
@@ -278,7 +278,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
true,
'utf16le',
);
- expect(isVisible, 'Orders table is not empty!').to.eq(true);
+ expect(isVisible).to.eq(true);
});
it('should check that Carts table is empty', async function () {
@@ -291,7 +291,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
true,
'utf16le',
);
- expect(isVisible, 'Carts table is not empty!').to.eq(true);
+ expect(isVisible).to.eq(true);
});
it('should check that Messages table is empty', async function () {
@@ -326,7 +326,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
const isVisible = await utilsFile.isTextInFile(
filePath,
- '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered.""',
+ '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered."',
true,
true,
'utf16le',
@@ -339,7 +339,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
const isVisible = await utilsFile.isTextInFile(
filePath,
- '""MODULE:PRODUCTCOMMENTS""MODULE:MAILALERTS"',
+ '""MODULE:PRODUCTCOMMENTS""MODULE:NEWSLETTERSUBSCRIPTION"',
true,
true,
'utf16le',
@@ -352,7 +352,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
const isVisible = await utilsFile.isTextInFile(
filePath,
- 'MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."',
+ '"MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."',
true,
true,
'utf16le',
diff --git a/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts b/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts
index 4d3c26aadfad3..fc56ee9ebb28a 100644
--- a/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts
+++ b/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts
@@ -87,7 +87,6 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
const createCustomerName: string = `${customerData.firstName[0]}. ${customerData.lastName}`;
- // before and after functions
before(async function () {
browserContext = await utilsPlaywright.createBrowserContext(this.browser);
page = await utilsPlaywright.newTab(browserContext);
@@ -319,7 +318,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
const isVisible = await utilsFile.isTextInFile(
filePath,
- '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered.""',
+ '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered."',
true,
true,
'utf16le',
@@ -332,12 +331,12 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
const isVisible = await utilsFile.isTextInFile(
filePath,
- '""MODULE:PRODUCTCOMMENTS""MODULE:MAILALERTS"',
+ '""MODULE:PRODUCTCOMMENTS""MODULE:NEWSLETTERSUBSCRIPTION"',
true,
true,
'utf16le',
);
- expect(isVisible, 'Products comments is not empty!').to.eq(true);
+ expect(isVisible).to.eq(true);
});
it('should check that mail alerts table is empty', async function () {
@@ -345,12 +344,12 @@ describe('FO - Account : Get GDPR data in CSV', async () => {
const isVisible = await utilsFile.isTextInFile(
filePath,
- 'MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."',
+ '"MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."',
true,
true,
'utf16le',
);
- expect(isVisible, 'Mail alert table is not empty!').to.eq(true);
+ expect(isVisible).to.eq(true);
});
});
});
diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts
index 82d4dfcca7e89..dcafebe02fd9c 100644
--- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts
+++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts
@@ -104,7 +104,7 @@ describe('FO - Product page - Quick view : Change combination', async () => {
await foHummingbirdModalQuickViewPage.setAttribute(page, secondAttributes[1]);
const quickViewImageMain = await foHummingbirdModalQuickViewPage.getQuickViewImageMain(page);
- expect(quickViewImageMain).to.contains('1-home_default');
+ expect(quickViewImageMain).to.contains('1-default_xl');
});
it('should select the color white and check the cover image', async function () {
@@ -113,7 +113,7 @@ describe('FO - Product page - Quick view : Change combination', async () => {
await foHummingbirdModalQuickViewPage.setAttribute(page, firstAttributes[1]);
const quickViewImageMain = await foHummingbirdModalQuickViewPage.getQuickViewImageMain(page);
- expect(quickViewImageMain).to.contains('2-home_default');
+ expect(quickViewImageMain).to.contains('2-default_xl');
});
it('should close the quick view modal', async function () {
diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts
index fc14c5ee7fcdd..2add99348973c 100644
--- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts
+++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts
@@ -57,13 +57,13 @@ describe('FO - Product page : Change quantity', async () => {
it('should change the quantity by using the arrow \'Down\' button', async function () {
await testContext.addContextItem(this, 'testIdentifier', 'decrement', baseContext);
- await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 1, 'down');
+ await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 1, 'decrement');
const productQuantity = await foHummingbirdProductPage.getProductQuantity(page);
expect(productQuantity).to.equal(1);
});
- it('should change the quantity by using the arrow \'UP\' button', async function () {
+ it('should change the quantity by using the arrow \'Up\' button', async function () {
await testContext.addContextItem(this, 'testIdentifier', 'incrementQuantity', baseContext);
await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 2, 'increment');
diff --git a/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts b/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts
index 07a556aee7ec2..3deb31d8d493a 100644
--- a/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts
+++ b/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts
@@ -14,7 +14,7 @@ import {
dataProducts,
foHummingbirdHomePage,
foHummingbirdLoginPage,
- foClassicModalWishlistPage,
+ foHummingbirdModalWishlistPage,
foHummingbirdMyWishlistsViewPage,
foHummingbirdProductPage,
foHummingbirdSearchResultsPage,
@@ -161,14 +161,14 @@ describe('Wishlist module - Reset module', async () => {
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const textResult = await foClassicModalWishlistPage.getModalAddToCreateWislistLabel(page);
+ const textResult = await foHummingbirdModalWishlistPage.getModalAddToCreateWislistLabel(page);
expect(textResult).to.contains(labelButton);
});
it('should go to \'Modules > Module Manager\' page', async function () {
await testContext.addContextItem(this, 'testIdentifier', 'goToModuleManagerPageForReset', baseContext);
- page = await foClassicModalWishlistPage.changePage(browserContext, 0);
+ page = await foHummingbirdModalWishlistPage.changePage(browserContext, 0);
await boDashboardPage.goToSubMenu(
page,
boDashboardPage.modulesParentLink,
@@ -201,7 +201,7 @@ describe('Wishlist module - Reset module', async () => {
await foHummingbirdProductPage.reloadPage(page);
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const textResult = await foClassicModalWishlistPage.getModalAddToCreateWislistLabel(page);
+ const textResult = await foHummingbirdModalWishlistPage.getModalAddToCreateWislistLabel(page);
expect(textResult).to.contains(modBlockwishlistBoMain.defaultValueCreateButtonLabel);
});
});
diff --git a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts
index 15d8b0734be5b..acea9ecea4753 100644
--- a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts
+++ b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts
@@ -7,7 +7,7 @@ import {
dataModules,
foHummingbirdHomePage,
foHummingbirdLoginPage,
- foClassicModalWishlistPage,
+ foHummingbirdModalWishlistPage,
foHummingbirdMyAccountPage,
foHummingbirdMyWishlistsPage,
foHummingbirdMyWishlistsViewPage,
@@ -90,10 +90,10 @@ describe('Wishlist module - Share a list', async () => {
await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 1);
- const hasModalShare = await foClassicModalWishlistPage.hasModalShare(page);
+ const hasModalShare = await foHummingbirdModalWishlistPage.hasModalShare(page);
expect(hasModalShare).to.equal(true);
- const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalShare(page);
+ const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalShare(page);
expect(isModalVisible).to.equal(false);
});
@@ -102,11 +102,11 @@ describe('Wishlist module - Share a list', async () => {
await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 1);
- const hasModalLogin = await foClassicModalWishlistPage.hasModalShare(page);
+ const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalShare(page);
expect(hasModalLogin).to.equal(true);
- const textToast = await foClassicModalWishlistPage.clickShareOnModalShare(page);
- expect(textToast).to.equal(foClassicModalWishlistPage.messageLinkSharedWishlist);
+ const textToast = await foHummingbirdModalWishlistPage.clickShareOnModalShare(page);
+ expect(textToast).to.equal(foHummingbirdModalWishlistPage.messageLinkSharedWishlist);
});
it('should click on the Create new list link and cancel', async function () {
@@ -114,10 +114,10 @@ describe('Wishlist module - Share a list', async () => {
await foHummingbirdMyWishlistsPage.clickCreateWishlistButton(page);
- const hasModalCreate = await foClassicModalWishlistPage.hasModalCreate(page);
+ const hasModalCreate = await foHummingbirdModalWishlistPage.hasModalCreate(page);
expect(hasModalCreate).to.equal(true);
- const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalCreate(page);
+ const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalCreate(page);
expect(isModalVisible).to.equal(false);
});
@@ -126,13 +126,13 @@ describe('Wishlist module - Share a list', async () => {
await foHummingbirdMyWishlistsPage.clickCreateWishlistButton(page);
- const hasModalCreate = await foClassicModalWishlistPage.hasModalCreate(page);
+ const hasModalCreate = await foHummingbirdModalWishlistPage.hasModalCreate(page);
expect(hasModalCreate).to.equal(true);
- await foClassicModalWishlistPage.setNameOnModalCreate(page, wishlistName);
+ await foHummingbirdModalWishlistPage.setNameOnModalCreate(page, wishlistName);
- const textToast = await foClassicModalWishlistPage.clickCreateOnModalCreate(page);
- expect(textToast).to.equal(foClassicModalWishlistPage.messageWishlistCreated);
+ const textToast = await foHummingbirdModalWishlistPage.clickCreateOnModalCreate(page);
+ expect(textToast).to.equal(foHummingbirdModalWishlistPage.messageWishlistCreated);
});
it('should click on the share icon (in dropdown) and cancel the modal', async function () {
@@ -140,10 +140,10 @@ describe('Wishlist module - Share a list', async () => {
await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 2);
- const hasModalShare = await foClassicModalWishlistPage.hasModalShare(page);
+ const hasModalShare = await foHummingbirdModalWishlistPage.hasModalShare(page);
expect(hasModalShare).to.equal(true);
- const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalShare(page);
+ const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalShare(page);
expect(isModalVisible).to.equal(false);
});
@@ -152,11 +152,11 @@ describe('Wishlist module - Share a list', async () => {
await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 2);
- const hasModalLogin = await foClassicModalWishlistPage.hasModalShare(page);
+ const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalShare(page);
expect(hasModalLogin).to.equal(true);
- const textToast = await foClassicModalWishlistPage.clickShareOnModalShare(page);
- expect(textToast).to.equal(foClassicModalWishlistPage.messageLinkSharedWishlist);
+ const textToast = await foHummingbirdModalWishlistPage.clickShareOnModalShare(page);
+ expect(textToast).to.equal(foHummingbirdModalWishlistPage.messageLinkSharedWishlist);
wishlistUrl = await foHummingbirdMyWishlistsPage.getClipboardText(page);
expect(wishlistUrl).to.be.a('string');
diff --git a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts
index 108698fca8f27..edf813fd9633f 100644
--- a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts
+++ b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts
@@ -12,7 +12,7 @@ import {
FakerProduct,
foHummingbirdHomePage,
foHummingbirdLoginPage,
- foClassicModalWishlistPage,
+ foHummingbirdModalWishlistPage,
foHummingbirdMyAccountPage,
foHummingbirdMyWishlistsPage,
foHummingbirdMyWishlistsViewPage,
@@ -90,10 +90,10 @@ describe('Wishlist module - Add a product to a list', async () => {
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const hasModalLogin = await foClassicModalWishlistPage.hasModalLogin(page);
+ const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalLogin(page);
expect(hasModalLogin).to.equal(true);
- const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalLogin(page);
+ const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalLogin(page);
expect(isModalVisible).to.equal(false);
});
@@ -102,10 +102,10 @@ describe('Wishlist module - Add a product to a list', async () => {
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const hasModalLogin = await foClassicModalWishlistPage.hasModalLogin(page);
+ const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalLogin(page);
expect(hasModalLogin).to.equal(true);
- await foClassicModalWishlistPage.clickLoginOnModalLogin(page);
+ await foHummingbirdModalWishlistPage.clickLoginOnModalLogin(page);
const pageTitle = await foHummingbirdLoginPage.getPageTitle(page);
expect(pageTitle).to.contains(foHummingbirdLoginPage.pageTitle);
@@ -165,7 +165,7 @@ describe('Wishlist module - Add a product to a list', async () => {
const pageTitle = await foHummingbirdProductPage.getPageTitle(page);
expect(pageTitle).to.equal(dataProducts.demo_3.name);
- await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 5, 'up');
+ await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 5, 'increment');
});
it('should add to the wishlist and select the first wishlist', async function () {
@@ -173,8 +173,8 @@ describe('Wishlist module - Add a product to a list', async () => {
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const textResult = await foClassicModalWishlistPage.addWishlist(page, 1);
- expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist);
+ const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1);
+ expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist);
});
it('should go to "My Account" page', async function () {
@@ -213,8 +213,9 @@ describe('Wishlist module - Add a product to a list', async () => {
const nameProduct = await foHummingbirdMyWishlistsViewPage.getProductName(page, 1);
expect(nameProduct).to.equal(dataProducts.demo_3.name);
- const qtyProduct = await foHummingbirdMyWishlistsViewPage.getProductQuantity(page, 1);
- expect(qtyProduct).to.equal(5);
+ // @todo : https://github.com/PrestaShop/hummingbird/issues/908
+ //const qtyProduct = await foHummingbirdMyWishlistsViewPage.getProductQuantity(page, 1);
+ //expect(qtyProduct).to.equal(5);
const sizeProduct = await foHummingbirdMyWishlistsViewPage.getProductAttribute(page, 1, 'Size');
expect(sizeProduct).to.equal('S');
@@ -235,8 +236,8 @@ describe('Wishlist module - Add a product to a list', async () => {
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const textResult = await foClassicModalWishlistPage.addWishlist(page, 1);
- expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist);
+ const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1);
+ expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist);
});
it('should go to "My Account" page', async function () {
@@ -300,8 +301,8 @@ describe('Wishlist module - Add a product to a list', async () => {
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const textResult = await foClassicModalWishlistPage.addWishlist(page, 1);
- expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist);
+ const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1);
+ expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist);
});
it('should go to "My Account" page', async function () {
@@ -357,17 +358,22 @@ describe('Wishlist module - Add a product to a list', async () => {
expect(pageTitle).to.equal(dataProducts.demo_1.name);
});
- it('should select the size \'M\' / color "Black" and check it', async function () {
- await testContext.addContextItem(this, 'testIdentifier', 'selectSizeColor', baseContext);
+ it('should select the size \'M\' and check it', async function () {
+ await testContext.addContextItem(this, 'testIdentifier', 'selectSize', baseContext);
await foHummingbirdProductPage.selectAttributes(page, 'select', [{name: 'size', value: 'M'}]);
- await foHummingbirdProductPage.selectAttributes(page, 'radio', [{name: 'Color', value: 'Black'}], 2);
const selectedAttributeSize = await foHummingbirdProductPage.getSelectedAttribute(page, 1, 'select');
expect(selectedAttributeSize).to.equal('M');
+ });
+
+ it('should select the color "Black" and check it', async function () {
+ await testContext.addContextItem(this, 'testIdentifier', 'selectColor', baseContext);
+
+ await foHummingbirdProductPage.selectAttributes(page, 'radio', [{name: 'Color', value: 'Black'}], 2);
const selectedAttributeColor = await foHummingbirdProductPage.getSelectedAttribute(page, 2, 'radio');
- expect(selectedAttributeColor).to.equal('Black');
+ expect(selectedAttributeColor).to.equal('Color - Black');
});
it('should add to the wishlist and select the first wishlist', async function () {
@@ -375,8 +381,8 @@ describe('Wishlist module - Add a product to a list', async () => {
await foHummingbirdProductPage.clickAddToWishlistButton(page);
- const textResult = await foClassicModalWishlistPage.addWishlist(page, 1);
- expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist);
+ const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1);
+ expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist);
});
it('should go to "My Account" page', async function () {
diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts
index 21b877b34e5a1..f2b1cf48d810d 100644
--- a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts
+++ b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts
@@ -174,9 +174,8 @@ describe('Mail alerts module - Uninstall and install module', async () => {
const pageTitle = await foHummingbirdProductPage.getPageTitle(page);
expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase());
- // @todo : https://github.com/PrestaShop/hummingbird/issues/879
- // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
- // expect(hasFlagOutOfStock).to.be.equal(true);
+ const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
+ expect(hasFlagOutOfStock).to.be.equal(true);
const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page);
expect(hasBlockMailAlert).to.be.equal(false);
@@ -235,9 +234,8 @@ describe('Mail alerts module - Uninstall and install module', async () => {
const pageTitle = await foHummingbirdProductPage.getPageTitle(page);
expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase());
- // @todo : https://github.com/PrestaShop/hummingbird/issues/879
- // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
- // expect(hasFlagOutOfStock).to.be.equal(true);
+ const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
+ expect(hasFlagOutOfStock).to.be.equal(true);
const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page);
expect(hasBlockMailAlert).to.be.equal(true);
diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts
index ba6880916e4c5..b18dd28ee789d 100644
--- a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts
+++ b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts
@@ -179,9 +179,8 @@ describe('Mail alerts module - Uninstall and delete module', async () => {
const pageTitle = await foHummingbirdProductPage.getPageTitle(page);
expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase());
- // @todo : https://github.com/PrestaShop/hummingbird/issues/879
- // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
- // expect(hasFlagOutOfStock).to.be.equal(true);
+ const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
+ expect(hasFlagOutOfStock).to.be.equal(true);
const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page);
expect(hasBlockMailAlert).to.be.equal(false);
diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts
index c67f9846e03c8..270e5de844aaa 100644
--- a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts
+++ b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts
@@ -166,9 +166,8 @@ describe('Mail alerts module - Disable/Enable module', async () => {
const pageTitle = await foHummingbirdProductPage.getPageTitle(page);
expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase());
- // @todo : https://github.com/PrestaShop/hummingbird/issues/879
- // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
- // expect(hasFlagOutOfStock).to.be.equal(true);
+ const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock');
+ expect(hasFlagOutOfStock).to.be.equal(true);
const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page);
expect(hasBlockMailAlert).to.be.equal(test.state);
diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts
index 37d278d1ed384..f78e84227f1a4 100644
--- a/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts
+++ b/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts
@@ -321,8 +321,9 @@ describe('Mail alerts module - Customer notifications - Enable/Disable product a
await page.reload();
- const textMessage = await foHummingbirdProductPage.getBlockMailAlertNotification(page);
- expect(textMessage).to.be.equal(foHummingbirdProductPage.messageAlertNotificationAlreadyRegistered);
+ // @todo : https://github.com/PrestaShop/hummingbird/issues/910
+ //const textMessage = await foHummingbirdProductPage.getBlockMailAlertNotification(page);
+ //expect(textMessage).to.be.equal(foHummingbirdProductPage.messageAlertNotificationAlreadyRegistered);
});
});
diff --git a/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts b/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts
index e7d712ea6fd05..e263e6fc72c33 100644
--- a/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts
+++ b/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts
@@ -114,7 +114,7 @@ describe('Faceted search module - Reset module', async () => {
it('should check the "All products" page', async function () {
await testContext.addContextItem(this, 'testIdentifier', 'goToAllProductsPage', baseContext);
- await foHummingbirdHomePage.goToAllProductsBlockPage(page, 1);
+ await foHummingbirdHomePage.goToAllProductsPage(page);
const isCategoryPageVisible = await foHummingbirdCategoryPage.isCategoryPage(page);
expect(isCategoryPageVisible).to.be.eq(true);
@@ -203,7 +203,7 @@ describe('Faceted search module - Reset module', async () => {
it('should check the "All products" page', async function () {
await testContext.addContextItem(this, 'testIdentifier', 'goToAllProductsPage1', baseContext);
- await foHummingbirdHomePage.goToAllProductsBlockPage(page, 1);
+ await foHummingbirdHomePage.goToAllProductsPage(page);
const isCategoryPageVisible = await foHummingbirdCategoryPage.isCategoryPage(page);
expect(isCategoryPageVisible).to.be.eq(true);
diff --git a/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts b/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts
index f283adb335809..ff5bed55e7244 100644
--- a/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts
+++ b/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts
@@ -1,6 +1,6 @@
import testContext from '@utils/testContext';
import {expect} from 'chai';
-import {faker, fakerFR} from '@faker-js/faker';
+import {faker} from '@faker-js/faker';
// Import commonTests
import {createProductTest, deleteProductTest} from '@commonTests/BO/catalog/product';
@@ -37,13 +37,14 @@ const baseContext: string = 'modules_psgdpr_configuration_consentCheckboxCustomi
describe('GDPR : Consent checkbox customization', async () => {
let browserContext: BrowserContext;
let page: Page;
- const messageAccountCreation: string = faker.lorem.sentence();
- const messageCustomerAccount: string = faker.lorem.sentence();
- const messageNewsletter: string = faker.lorem.sentence();
- const messageContactForm: string = faker.lorem.sentence();
- const messageProductComments: string = faker.lorem.sentence();
- const messageMailAlerts: string = faker.lorem.sentence();
- const messageMailAlertsFR: string = fakerFR.lorem.sentence();
+ const messageBase: string = faker.lorem.sentence();
+ const messageAccountCreation: string = `Account Creation - ${messageBase}`;
+ const messageCustomerAccount: string = `Customer Account - ${messageBase}`;
+ const messageNewsletter: string = `Newsletter - ${messageBase}`;
+ const messageContactForm: string = `Contact Form - ${messageBase}`;
+ const messageProductComments: string = `Product Comments - ${messageBase}`;
+ const messageMailAlerts: string = `Mail Alerts EN - ${messageBase}`;
+ const messageMailAlertsFR: string = `Mail Alerts FR - ${messageBase}`;
const customerData: FakerCustomer = new FakerCustomer();
const productOutOfStock: FakerProduct = new FakerProduct({
quantity: 0,
@@ -52,7 +53,6 @@ describe('GDPR : Consent checkbox customization', async () => {
createProductTest(productOutOfStock, `${baseContext}_preTest_0`);
describe('Consent checkbox customization', async () => {
- // before and after functions
before(async function () {
browserContext = await utilsPlaywright.createBrowserContext(this.browser);
page = await utilsPlaywright.newTab(browserContext);
@@ -452,6 +452,11 @@ describe('GDPR : Consent checkbox customization', async () => {
const successMessage = await modPsGdprBoTabDataConsent.saveForm(page);
expect(successMessage).to.be.contains(modPsGdprBoTabDataConsent.saveFormMessage);
+
+ await page.screenshot({
+ path: `${global.SCREENSHOT.FOLDER}/gdpr_00.png`,
+ fullPage: true,
+ });
});
it('should check on Contact Form the GDPR Label', async function () {
diff --git a/tests/UI/package-lock.json b/tests/UI/package-lock.json
index 5fb0ef2ef3269..28c19221b6d29 100644
--- a/tests/UI/package-lock.json
+++ b/tests/UI/package-lock.json
@@ -534,7 +534,7 @@
},
"node_modules/@prestashop-core/ui-testing": {
"version": "0.0.12",
- "resolved": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#e0c93bbbdee39d0415b96ff4ea0c1cc9b6cbf2cc",
+ "resolved": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#8e86150dfb5ec99980384e33ccd0ad5217ce123b",
"license": "MIT",
"dependencies": {
"@faker-js/faker": "^10.1.0",
@@ -8704,7 +8704,7 @@
}
},
"@prestashop-core/ui-testing": {
- "version": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#e0c93bbbdee39d0415b96ff4ea0c1cc9b6cbf2cc",
+ "version": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#8e86150dfb5ec99980384e33ccd0ad5217ce123b",
"from": "@prestashop-core/ui-testing@https://github.com/PrestaShop/ui-testing-library#main",
"requires": {
"@faker-js/faker": "^10.1.0",
diff --git a/tools/profiling/Profiler.php b/tools/profiling/Profiler.php
index 9e6ec900b0854..3f20a90d8088e 100644
--- a/tools/profiling/Profiler.php
+++ b/tools/profiling/Profiler.php
@@ -213,7 +213,9 @@ public function processData()
{
// Including a lot of files uses memory
foreach (get_included_files() as $file) {
- $this->totalFilesize += filesize($file);
+ if (file_exists($file)) {
+ $this->totalFilesize += filesize($file);
+ }
}
foreach ($GLOBALS as $key => $value) {