From 4c7af26060aba6cf044d2ecf4d1f0b1078888d8b Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Wed, 28 Jan 2026 13:07:31 +0200 Subject: [PATCH 01/24] fix: stabilize multiple search history items test --- tests_e2e/tests/test_e2e_search_bar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests_e2e/tests/test_e2e_search_bar.py b/tests_e2e/tests/test_e2e_search_bar.py index 0e391b94..0dcb5e97 100644 --- a/tests_e2e/tests/test_e2e_search_bar.py +++ b/tests_e2e/tests/test_e2e_search_bar.py @@ -46,18 +46,19 @@ def test_e2e_search_bar_user_search_history_multiple_items(config: Config, page: expect(page).to_have_url(f"{config['FRONTEND_BASE_URL']}/search?q=acer") + home_page.navigate() home_page.search_bar.search_input.fill("asus") home_page.search_bar.search_button.click() expect(page).to_have_url(f"{config['FRONTEND_BASE_URL']}/search?q=asus") + home_page.navigate() home_page.search_bar.search_input.fill("lenovo") home_page.search_bar.search_button.click() expect(page).to_have_url(f"{config['FRONTEND_BASE_URL']}/search?q=lenovo") home_page.navigate() - home_page.search_bar.search_input.focus() expect(home_page.search_bar.suggestions_dropdown.element).to_be_visible() From 9db671e17740c7e13affd6609dce6911a2a0e5e4 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Wed, 28 Jan 2026 17:09:53 +0200 Subject: [PATCH 02/24] feat: add nested categories with entities to dataset --- dataset/data/categories.json | 71 ++ dataset/data/prices.json | 639 +++++++++++++++ dataset/data/product_inventories.json | 200 +++++ dataset/data/products.json | 1085 ++++++++++++++++++++++++- dataset/data/properties.json | 72 +- 5 files changed, 2065 insertions(+), 2 deletions(-) diff --git a/dataset/data/categories.json b/dataset/data/categories.json index 518c8032..2736c7f8 100644 --- a/dataset/data/categories.json +++ b/dataset/data/categories.json @@ -105,6 +105,77 @@ "storeId": "store-acme" } ] + }, + { + "id": "category-acme-peripherals", + "code": "category-acme-peripherals", + "catalogId": "catalog-acme", + "name": "Peripherals", + "seoObjectType": "Category", + "seoInfos": [ + { + "id": "seo-info-acme-category-peripherals", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Peripherals", + "semanticUrl": "peripherals", + "storeId": "store-acme" + } + ] + }, + { + "id": "category-acme-keyboards", + "code": "category-acme-keyboards", + "catalogId": "catalog-acme", + "parentId": "category-acme-peripherals", + "name": "Keyboards", + "seoObjectType": "Category", + "seoInfos": [ + { + "id": "seo-info-acme-category-keyboards", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Keyboards", + "semanticUrl": "keyboards", + "storeId": "store-acme" + } + ] + }, + { + "id": "category-acme-mice", + "code": "category-acme-mice", + "catalogId": "catalog-acme", + "parentId": "category-acme-peripherals", + "name": "Mice", + "seoObjectType": "Category", + "seoInfos": [ + { + "id": "seo-info-acme-category-mice", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Mice", + "semanticUrl": "mice", + "storeId": "store-acme" + } + ] + }, + { + "id": "category-acme-headsets", + "code": "category-acme-headsets", + "catalogId": "catalog-acme", + "parentId": "category-acme-peripherals", + "name": "Headsets", + "seoObjectType": "Category", + "seoInfos": [ + { + "id": "seo-info-acme-category-headsets", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Headsets", + "semanticUrl": "headsets", + "storeId": "store-acme" + } + ] } ] } \ No newline at end of file diff --git a/dataset/data/prices.json b/dataset/data/prices.json index 3c075eb5..7147e984 100644 --- a/dataset/data/prices.json +++ b/dataset/data/prices.json @@ -1321,6 +1321,645 @@ "productId": "product-acme-product8-mfc-stock" } ] + }, + { + "productId": "product-acme-logitech-alto-keys-k98m", + "product": { + "id": "product-acme-logitech-alto-keys-k98m", + "name": "Logitech Alto Keys K98M" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 119.99, + "sale": 109.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-alto-keys-k98m" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 100.79, + "sale": 91.93, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-alto-keys-k98m" + } + ] + }, + { + "productId": "product-acme-logitech-ergo-k860", + "product": { + "id": "product-acme-logitech-ergo-k860", + "name": "Logitech ERGO K860" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 149.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-ergo-k860" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 125.36, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-ergo-k860" + } + ] + }, + { + "productId": "product-acme-logitech-signature-k650", + "product": { + "id": "product-acme-logitech-signature-k650", + "name": "Logitech Signature K650" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 59.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-signature-k650" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 50.14, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-signature-k650" + } + ] + }, + { + "productId": "product-acme-razer-ornata-v3", + "product": { + "id": "product-acme-razer-ornata-v3", + "name": "Razer Ornata V3" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 79.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-ornata-v3" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 66.86, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-ornata-v3" + } + ] + }, + { + "productId": "product-acme-razer-black-widow-v4", + "product": { + "id": "product-acme-razer-black-widow-v4", + "name": "Razer BlackWidow V4" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 179.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-black-widow-v4" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 150.44, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-black-widow-v4" + } + ] + }, + { + "productId": "product-acme-hyperx-alloy-rise", + "product": { + "id": "product-acme-hyperx-alloy-rise", + "name": "HyperX Alloy Rise" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 199.99, + "sale": 99.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-alloy-rise" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 167.15, + "sale": 83.57, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-alloy-rise" + } + ] + }, + { + "productId": "product-acme-hyperx-eve-1800", + "product": { + "id": "product-acme-hyperx-eve-1800", + "name": "HyperX Eve 1800" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 49.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-eve-1800" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 41.78, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-eve-1800" + } + ] + }, + { + "productId": "product-acme-hyperx-origins-2-65", + "product": { + "id": "product-acme-hyperx-origins-2-65", + "name": "HyperX Origins 2 65" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 119.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-origins-2-65" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 100.29, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-origins-2-65" + } + ] + }, + { + "productId": "product-acme-logitech-mx-master-4", + "product": { + "id": "product-acme-logitech-mx-master-4", + "name": "Logitech MX Master 4" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 119.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-mx-master-4" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 100.29, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-mx-master-4" + } + ] + }, + { + "productId": "product-acme-logitech-m720-triathlon", + "product": { + "id": "product-acme-logitech-m720-triathlon", + "name": "Logitech M720 Triathlon" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 44.99, + "sale": 41.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-m720-triathlon" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 37.60, + "sale": 35.10, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-m720-triathlon" + } + ] + }, + { + "productId": "product-acme-logitech-pro-x-superlight-2", + "product": { + "id": "product-acme-logitech-pro-x-superlight-2", + "name": "Logitech Pro X Superlight 2" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 179.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-pro-x-superlight-2" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 150.44, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-pro-x-superlight-2" + } + ] + }, + { + "productId": "product-acme-razer-cobra-pro", + "product": { + "id": "product-acme-razer-cobra-pro", + "name": "Razer Cobra Pro" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 129.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-cobra-pro" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 108.65, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-cobra-pro" + } + ] + }, + { + "productId": "product-acme-razer-basilisk-mobile", + "product": { + "id": "product-acme-razer-basilisk-mobile", + "name": "Razer Basilisk Mobile" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 89.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-basilisk-mobile" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 75.21, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-basilisk-mobile" + } + ] + }, + { + "productId": "product-acme-razer-viper-v3-pro", + "product": { + "id": "product-acme-razer-viper-v3-pro", + "name": "Razer Viper V3 Pro" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 159.99, + "sale": 129.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-viper-v3-pro" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 133.72, + "sale": 108.65, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-viper-v3-pro" + } + ] + }, + { + "productId": "product-acme-hyperx-pulsefire-haste-2", + "product": { + "id": "product-acme-hyperx-pulsefire-haste-2", + "name": "HyperX Pulsefire Haste 2" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 59.99, + "sale": 49.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-pulsefire-haste-2" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 50.14, + "sale": 41.78, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-pulsefire-haste-2" + } + ] + }, + { + "productId": "product-acme-hyperx-pulsefire-saga", + "product": { + "id": "product-acme-hyperx-pulsefire-saga", + "name": "HyperX Pulsefire Saga" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 79.99, + "sale": 59.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-pulsefire-saga" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 66.86, + "sale": 50.14, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-pulsefire-saga" + } + ] + }, + { + "productId": "product-acme-logitech-zone-wired-2", + "product": { + "id": "product-acme-logitech-zone-wired-2", + "name": "Logitech Zone Wired 2" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 129.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-zone-wired-2" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 108.65, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-zone-wired-2" + } + ] + }, + { + "productId": "product-acme-logitech-zone-950", + "product": { + "id": "product-acme-logitech-zone-950", + "name": "Logitech Zone 950" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 299.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-zone-950" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 250.73, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-zone-950" + } + ] + }, + { + "productId": "product-acme-logitech-h151-stereo-headset", + "product": { + "id": "product-acme-logitech-h151-stereo-headset", + "name": "Logitech H151 Stereo Headset" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 19.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-logitech-h151-stereo-headset" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 16.71, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-logitech-h151-stereo-headset" + } + ] + }, + { + "productId": "product-acme-razer-barracuda-x", + "product": { + "id": "product-acme-razer-barracuda-x", + "name": "Razer Barracuda X" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 99.99, + "sale": 79.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-barracuda-x" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 83.57, + "sale": 66.86, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-barracuda-x" + } + ] + }, + { + "productId": "product-acme-razer-kraken-v4-pro", + "product": { + "id": "product-acme-razer-kraken-v4-pro", + "name": "Razer Kraken V4 Pro" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 399.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-kraken-v4-pro" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 334.31, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-kraken-v4-pro" + } + ] + }, + { + "productId": "product-acme-razer-black-shark-v3-pro", + "product": { + "id": "product-acme-razer-black-shark-v3-pro", + "name": "Razer BlackShark V3 Pro" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 249.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-razer-black-shark-v3-pro" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 208.94, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-razer-black-shark-v3-pro" + } + ] + }, + { + "productId": "product-acme-hyperx-cloud-iii", + "product": { + "id": "product-acme-hyperx-cloud-iii", + "name": "HyperX Cloud III" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 99.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-cloud-iii" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 83.57, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-cloud-iii" + } + ] + }, + { + "productId": "product-acme-hyperx-cloud-gaming-headset", + "product": { + "id": "product-acme-hyperx-cloud-gaming-headset", + "name": "HyperX Cloud Gaming Headset" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 49.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-cloud-gaming-headset" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 41.78, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-cloud-gaming-headset" + } + ] + }, + { + "productId": "product-acme-hyperx-cloud-stringer-2-core", + "product": { + "id": "product-acme-hyperx-cloud-stringer-2-core", + "name": "HyperX Cloud Stringer 2 Core" + }, + "prices": [ + { + "catalog": "catalog-acme", + "currency": "USD", + "list": 29.99, + "minQuantity": 1, + "pricelistId": "pricelist-acme-usd", + "productId": "product-acme-hyperx-cloud-stringer-2-core" + }, + { + "catalog": "catalog-acme", + "currency": "EUR", + "list": 25.07, + "minQuantity": 1, + "pricelistId": "pricelist-acme-eur", + "productId": "product-acme-hyperx-cloud-stringer-2-core" + } + ] } ] } \ No newline at end of file diff --git a/dataset/data/product_inventories.json b/dataset/data/product_inventories.json index 80a854af..c844ba96 100644 --- a/dataset/data/product_inventories.json +++ b/dataset/data/product_inventories.json @@ -507,6 +507,206 @@ "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", "fulfillmentCenterName": "ACME USA Ohio FFC", "inStockQuantity": 44 + }, + { + "productId": "product-acme-logitech-alto-keys-k98m", + "sku": "product-acme-logitech-alto-keys-k98m", + "productName": "Logitech Alto Keys K98M", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-ergo-k860", + "sku": "product-acme-logitech-ergo-k860", + "productName": "Logitech ERGO K860", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-signature-k650", + "sku": "product-acme-logitech-signature-k650", + "productName": "Logitech Signature K650", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-ornata-v3", + "sku": "product-acme-razer-ornata-v3", + "productName": "Razer Ornata V3", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-black-widow-v4", + "sku": "product-acme-razer-black-widow-v4", + "productName": "Razer BlackWidow V4", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-alloy-rise", + "sku": "product-acme-hyperx-alloy-rise", + "productName": "HyperX Alloy Rise", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-eve-1800", + "sku": "product-acme-hyperx-eve-1800", + "productName": "HyperX Eve 1800", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-origins-2-65", + "sku": "product-acme-hyperx-origins-2-65", + "productName": "HyperX Origins 2 65", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-mx-master-4", + "sku": "product-acme-logitech-mx-master-4", + "productName": "Logitech MX Master 4", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-m720-triathlon", + "sku": "product-acme-logitech-m720-triathlon", + "productName": "Logitech M720 Triathlon", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-pro-x-superlight-2", + "sku": "product-acme-logitech-pro-x-superlight-2", + "productName": "Logitech Pro X Superlight 2", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-cobra-pro", + "sku": "product-acme-razer-cobra-pro", + "productName": "Razer Cobra Pro", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-basilisk-mobile", + "sku": "product-acme-razer-basilisk-mobile", + "productName": "Razer Basilisk Mobile", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-viper-v3-pro", + "sku": "product-acme-razer-viper-v3-pro", + "productName": "Razer Viper V3 Pro", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-pulsefire-haste-2", + "sku": "product-acme-hyperx-pulsefire-haste-2", + "productName": "HyperX Pulsefire Haste 2", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-pulsefire-saga", + "sku": "product-acme-hyperx-pulsefire-saga", + "productName": "HyperX Pulsefire Saga", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-zone-wired-2", + "sku": "product-acme-logitech-zone-wired-2", + "productName": "Logitech Zone Wired 2", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-zone-950", + "sku": "product-acme-logitech-zone-950", + "productName": "Logitech Zone 950", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-logitech-h151-stereo-headset", + "sku": "product-acme-logitech-h151-stereo-headset", + "productName": "Logitech H151 Stereo Headset", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-barracuda-x", + "sku": "product-acme-razer-barracuda-x", + "productName": "Razer Barracuda X", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-kraken-v4-pro", + "sku": "product-acme-razer-kraken-v4-pro", + "productName": "Razer Kraken V4 Pro", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-razer-black-shark-v3-pro", + "sku": "product-acme-razer-black-shark-v3-pro", + "productName": "Razer BlackShark V3 Pro", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-cloud-iii", + "sku": "product-acme-hyperx-cloud-iii", + "productName": "HyperX Cloud III", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-cloud-gaming-headset", + "sku": "product-acme-hyperx-cloud-gaming-headset", + "productName": "HyperX Cloud Gaming Headset", + "fulfillmentCenterId": "ffc-acme-usa-springfield-il", + "fulfillmentCenterName": "ACME USA Illinois FFC", + "inStockQuantity": 100000 + }, + { + "productId": "product-acme-hyperx-cloud-stringer-2-core", + "sku": "product-acme-hyperx-cloud-stringer-2-core", + "productName": "HyperX Cloud Stringer 2 Core", + "fulfillmentCenterId": "ffc-acme-usa-riverton-oh", + "fulfillmentCenterName": "ACME USA Ohio FFC", + "inStockQuantity": 100000 } ] } \ No newline at end of file diff --git a/dataset/data/products.json b/dataset/data/products.json index 8efb93d3..3fb7a944 100644 --- a/dataset/data/products.json +++ b/dataset/data/products.json @@ -10733,7 +10733,7 @@ ] } ] - }, + }, { "id": "product-acme-product1-track-inventory-false", "catalogId": "catalog-acme", @@ -11263,6 +11263,1089 @@ ] } ] + }, + { + "id": "product-acme-logitech-alto-keys-k98m", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "Logitech Alto Keys K98M", + "code": "product-acme-logitech-alto-keys-k98m", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-alto-keys-k98m", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech Alto Keys K98M", + "semanticUrl": "logitech-alto-keys-k98m", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-ergo-k860", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "Logitech ERGO K860", + "code": "product-acme-logitech-ergo-k860", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-ergo-k860", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech ERGO K860", + "semanticUrl": "logitech-ergo-k860", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": false + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-signature-k650", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "Logitech Signature K650", + "code": "product-acme-logitech-signature-k650", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-signature-k650", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech Signature K650", + "semanticUrl": "logitech-signature-k650", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": false + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-razer-ornata-v3", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "Razer Ornata V3", + "code": "product-acme-razer-ornata-v3", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-ornata-v3", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer Ornata V3", + "semanticUrl": "razer-ornata-v3", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-razer-black-widow-v4", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "Razer BlackWidow V4", + "code": "product-acme-razer-black-widow-v4", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-black-widow-v4", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer BlackWidow V4", + "semanticUrl": "razer-black-widow-v4", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-alloy-rise", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "HyperX Alloy Rise", + "code": "product-acme-hyperx-allow-rise", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-allow-rise", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Alloy Rise", + "semanticUrl": "hyperx-allow-rise", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-eve-1800", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "HyperX Eve 1800", + "code": "product-acme-hyperx-eve-1800", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-eve-1800", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Eve 1800", + "semanticUrl": "hyperx-eve-1800", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-origins-2-65", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "HyperX Origins 2 65", + "code": "product-acme-hyperx-origins-2-65", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-origins-2-65", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Origins 2 65", + "semanticUrl": "hyperx-origins-2-65", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-keyboard-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "name": "KeyboardConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-mx-master-4", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "Logitech MX Master 4", + "code": "product-acme-logitech-mx-master-4", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-mx-master-4", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech MX Master 4", + "semanticUrl": "logitech-mx-master-4", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": false + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-m720-triathlon", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "Logitech M720 Triathlon", + "code": "product-acme-logitech-m720-triathlon", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-m720-triathlon", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech M720 Triathlon", + "semanticUrl": "logitech-m720-triathlon", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": false + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-pro-x-superlight-2", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "Logitech Pro X Superlight 2", + "code": "product-acme-logitech-pro-x-superlight-2", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-pro-x-superlight-2", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech Pro X Superlight 2", + "semanticUrl": "logitech-pro-x-superlight-2", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-razer-cobra-pro", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "Razer Cobra Pro", + "code": "product-acme-razer-cobra-pro", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-cobra-pro", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer Cobra Pro", + "semanticUrl": "razer-cobra-pro", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-razer-basilisk-mobile", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "Razer Basilisk Mobile", + "code": "product-acme-razer-basilisk-mobile", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-basilisk-mobile", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer Basilisk Mobile", + "semanticUrl": "razer-basilisk-mobile", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-razer-viper-v3-pro", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "Razer Viper V3 Pro", + "code": "product-acme-razer-viper-v3-pro", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-viper-v3-pro", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer Viper V3 Pro", + "semanticUrl": "razer-viper-v3-pro", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-pulsefire-haste-2", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "HyperX Pulsefire Haste 2", + "code": "product-acme-hyperx-pulsefire-haste-2", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-pulsefire-haste-2", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Pulsefire Haste 2", + "semanticUrl": "hyperx-pulsefire-haste-2", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-pulsefire-saga", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "HyperX Pulsefire Saga", + "code": "product-acme-hyperx-pulsefire-saga", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-pulsefire-saga", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Pulsefire Saga", + "semanticUrl": "hyperx-pulsefire-saga", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-mice-backlight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceBacklight", + "type": "Product", + "valueType": "Boolean", + "values": [ + { + "value": true + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "name": "MiceConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-zone-wired-2", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "Logitech Zone Wired 2", + "code": "product-acme-logitech-zone-wired-2", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-zone-wired-2", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech Zone Wired 2", + "semanticUrl": "logitech-zone-wired-2", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-zone-950", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "Logitech Zone 950", + "code": "product-acme-logitech-zone-950", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-zone-950", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech Zone 950", + "semanticUrl": "logitech-zone-950", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-logitech-h151-stereo-headset", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "Logitech H151 Stereo Headset", + "code": "product-acme-logitech-h151-stereo-headset", + "vendor": "Logitech", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-logitech-h151-stereo-headset", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Logitech H151 Stereo Headset", + "semanticUrl": "logitech-h151-stereo-headset", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "3.5mm" + } + ] + } + ] + }, + { + "id": "product-acme-razer-barracuda-x", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "Razer Barracuda X", + "code": "product-acme-razer-barracuda-x", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-barracuda-x", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer Barracuda X", + "semanticUrl": "razer-barracuda-x", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-razer-kraken-v4-pro", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "Razer Kraken V4 Pro", + "code": "product-acme-razer-kraken-v4-pro", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-kraken-v4-pro", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer Kraken V4 Pro", + "semanticUrl": "razer-kraken-v4-pro", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-razer-black-shark-v3-pro", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "Razer BlackShark V3 Pro", + "code": "product-acme-razer-black-shark-v3-pro", + "vendor": "Razer", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-razer-black-shark-v3-pro", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "Razer BlackShark V3 Pro", + "semanticUrl": "razer-black-shark-v3-pro", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "Bluetooth" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-cloud-iii", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HyperX Cloud III", + "code": "product-acme-hyperx-cloud-iii", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-cloud-iii", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Cloud III", + "semanticUrl": "hyperx-cloud-iii", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-cloud-gaming-headset", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HyperX Cloud Gaming Headset", + "code": "product-acme-hyperx-cloud-gaming-headset", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-cloud-gaming-headset", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Cloud Gaming Headset", + "semanticUrl": "hyperx-cloud-gaming-headset", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] + }, + { + "id": "product-acme-hyperx-cloud-stringer-2-core", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HyperX Cloud Stringer 2 Core", + "code": "product-acme-hyperx-cloud-stringer-2-core", + "vendor": "HyperX", + "productType": "Physical", + "seoObjectType": "Product", + "seoInfos": [ + { + "id": "seo-info-acme-product-hyperx-cloud-stringer-2-core", + "isActive": true, + "languageCode": "en-US", + "pageTitle": "HyperX Cloud Stringer 2 Core", + "semanticUrl": "hyperx-cloud-stringer-2-core", + "storeId": "store-acme" + } + ], + "properties": [ + { + "id": "property-acme-headsets-connectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "name": "HeadsetsConnectivity", + "type": "Product", + "valueType": "ShortText", + "values": [ + { + "value": "USB-C" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/dataset/data/properties.json b/dataset/data/properties.json index bfdc787c..c06e06da 100644 --- a/dataset/data/properties.json +++ b/dataset/data/properties.json @@ -18,7 +18,7 @@ "multilanguage": false, "hidden": false, "valueType": "ShortText", - "type": "Product", + "type": "Product", "displayOrder": null, "values": [], "attributes": [], @@ -705,6 +705,76 @@ "languageCode": "en-US" } ] + }, + { + "id": "property-acme-keyboard-backlight", + "name": "KeyboardBacklight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "valueType": "Boolean", + "type": "Product", + "displayNames": [ + { + "name": "Keyboard Backlight", + "languageCode": "en-US" + } + ] + }, + { + "id": "property-acme-keyboard-connectivity", + "name": "KeyboardConnectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-keyboards", + "valueType": "ShortText", + "type": "Product", + "displayNames": [ + { + "name": "Keyboard Connectivity", + "languageCode": "en-US" + } + ] + }, + { + "id": "property-acme-mice-backlight", + "name": "MiceBacklight", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "valueType": "Boolean", + "type": "Product", + "displayNames": [ + { + "name": "Mice Backlight", + "languageCode": "en-US" + } + ] + }, + { + "id": "property-acme-mice-connectivity", + "name": "MiceConnectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-mice", + "valueType": "ShortText", + "type": "Product", + "displayNames": [ + { + "name": "Mice Connectivity", + "languageCode": "en-US" + } + ] + }, + { + "id": "property-acme-headsets-connectivity", + "name": "HeadsetsConnectivity", + "catalogId": "catalog-acme", + "categoryId": "category-acme-headsets", + "valueType": "ShortText", + "type": "Product", + "displayNames": [ + { + "name": "Headsets Connectivity", + "languageCode": "en-US" + } + ] } ] } \ No newline at end of file From 31e5a7bd548948052ec19c4e8824a539c7689f07 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Mon, 2 Feb 2026 10:26:01 +0200 Subject: [PATCH 03/24] feat: implement e2e tests for search bar category scopes --- tests_e2e/components/search_bar_component.py | 4 + tests_e2e/pages/category_page.py | 6 +- tests_e2e/tests/test_e2e_search_bar.py | 131 ++++++++++++++++++- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/tests_e2e/components/search_bar_component.py b/tests_e2e/components/search_bar_component.py index 8dd1ea0c..43dd24c7 100644 --- a/tests_e2e/components/search_bar_component.py +++ b/tests_e2e/components/search_bar_component.py @@ -17,6 +17,10 @@ def search_input(self) -> Locator: def search_button(self) -> Locator: return self.element.locator("[data-test-id='global-search-apply-button']") + @property + def category_scope_button(self) -> Locator: + return self.element.locator("[data-search-scope]") + @property def suggestions_dropdown(self) -> SearchSuggestionsDropdownComponent: return SearchSuggestionsDropdownComponent( diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index decdfff8..945c6f7d 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -57,13 +57,9 @@ def price_filter_slider(self) -> FilterSliderComponent | None: def price_filter_facets(self) -> FilterFacetComponent | None: return FilterFacetComponent(self.page.locator("[data-test-id='filter-price']")) - @property - def products_count(self) -> int: - return int(self.page.locator(".category__products-count .me-1").text_content()) - @property def products_count_locator(self) -> Locator: - return self.page.locator(".category__products-count .me-1") + return self.page.locator("[data-test-id='category-page.total-products-count']") def navigate(self) -> None: self.page.goto(f"{self.url}?sort=price-ascending") diff --git a/tests_e2e/tests/test_e2e_search_bar.py b/tests_e2e/tests/test_e2e_search_bar.py index 0dcb5e97..d8d27352 100644 --- a/tests_e2e/tests/test_e2e_search_bar.py +++ b/tests_e2e/tests/test_e2e_search_bar.py @@ -4,7 +4,7 @@ from fixtures import Auth, Config, GraphQLClient from graphql_operations.contact.contact_operations import ContactOperations from graphql_operations.user.user_operations import UserOperations -from tests_e2e.pages import HomePage +from tests_e2e.pages import CategoryPage, HomePage @pytest.mark.e2e @@ -340,3 +340,132 @@ def test_e2e_search_bar_history_different_users( ) auth.clear_token() + + +@pytest.mark.e2e +def test_e2e_search_bar_search_suggestions_in_category(config: Config, page: Page): + print( + "Running E2E test to check search in category...", + end=" ", + ) + + category_page = CategoryPage(config, page, "peripherals") + category_page.navigate() + + expect(category_page.products_count_locator).to_be_visible() + expect(category_page.search_bar.category_scope_button).to_be_visible() + expect(category_page.search_bar.category_scope_button).to_have_text("Peripherals") + + peripherals_count = int(category_page.products_count_locator.text_content()) + + assert peripherals_count == 25, "Peripherals count is not equal to 25" + + category_page.search_bar.search_input.fill("logitech") + + expect(category_page.search_bar.suggestions_dropdown.element).to_be_visible() + expect( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.element + ).to_be_visible() + assert ( + len( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.products + ) + >= 1 + ) + + category_page.search_bar.search_input.fill("acer") + + expect(category_page.search_bar.suggestions_dropdown.element).to_be_visible() + expect( + category_page.search_bar.suggestions_dropdown.not_found_section_element + ).to_be_visible() + + +@pytest.mark.e2e +def test_e2e_search_bar_search_suggestions_in_subcategory(config: Config, page: Page): + print( + "Running E2E test to check search in subcategory...", + end=" ", + ) + + category_page = CategoryPage(config, page, "peripherals/headsets") + category_page.navigate() + + expect(category_page.products_count_locator).to_be_visible() + expect(category_page.search_bar.category_scope_button).to_be_visible() + expect(category_page.search_bar.category_scope_button).to_have_text("Headsets") + + headsets_count = int(category_page.products_count_locator.text_content()) + + assert headsets_count == 9, "Headsets count is not equal to 9" + + category_page.search_bar.search_input.fill("zone wired") + + expect(category_page.search_bar.suggestions_dropdown.element).to_be_visible() + expect( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.element + ).to_be_visible() + assert ( + len( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.products + ) + >= 1 + ) + + category_page.search_bar.search_input.fill("ergo") + + expect(category_page.search_bar.suggestions_dropdown.element).to_be_visible() + expect( + category_page.search_bar.suggestions_dropdown.not_found_section_element + ).to_be_visible() + + +@pytest.mark.e2e +def test_e2e_search_bar_category_scope_button_operations(config: Config, page: Page): + print( + "Running E2E test to check category scope button operations...", + end=" ", + ) + + category_page = CategoryPage(config, page, "peripherals") + category_page.navigate() + + expect(category_page.search_bar.category_scope_button).to_be_visible() + expect(category_page.search_bar.category_scope_button).to_have_text("Peripherals") + + category_page.search_bar.search_input.fill("logitech") + + expect(category_page.search_bar.suggestions_dropdown.element).to_be_visible() + expect( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.element + ).to_be_visible() + assert ( + len( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.products + ) + >= 1 + ) + + category_page.search_bar.search_input.fill("acer") + + expect(category_page.search_bar.suggestions_dropdown.element).to_be_visible() + expect( + category_page.search_bar.suggestions_dropdown.not_found_section_element + ).to_be_visible() + + category_page.search_bar.category_scope_button.click() + + expect(category_page.search_bar.category_scope_button).not_to_be_visible() + + category_page.search_bar.search_input.fill("logitech") + + expect(category_page.search_bar.suggestions_dropdown.element).to_be_visible() + expect( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.element + ).to_be_visible() + assert ( + len( + category_page.search_bar.suggestions_dropdown.product_suggestions_section.products + ) + >= 1 + ) From d31c0463f6033d5569e3b056785277a083bee4af Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 3 Feb 2026 10:28:46 +0200 Subject: [PATCH 04/24] chore: output category products to debug e2e test --- tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py b/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py index d239dd22..9185d34b 100644 --- a/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py +++ b/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py @@ -29,6 +29,9 @@ def test_e2e_category_add_to_cart_component_viewport( category_page.view_switcher.switch_category_view("grid") + print(f"Product code: {product['code']}") + print(f"Product cards: {category_page.product_cards}") + product_card = category_page.get_product_card_by_sku(product["code"]) expect(product_card.element).to_be_visible(), "Product card is not visible" From c002a9db944f4704d23107a4d25154dc97b7e27d Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 3 Feb 2026 11:09:01 +0200 Subject: [PATCH 05/24] feat: method to search product card on category page within endless scroll --- tests_e2e/pages/category_page.py | 22 +++++++++++++++++++ .../test_e2e_category_add_to_cart_viewport.py | 3 --- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 945c6f7d..d4309a7a 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -22,6 +22,26 @@ def __init__( self.page = page self.seo_path = seo_path + def _fetch_all_product_cards(self, scroll_pause_ms: int = 500): + prev_count = 1 + + while True: + cards = self.products_grid_view.locator("[data-test-id='product-card']") + current_count = cards.count() + + if current_count == prev_count: + break + + prev_count = current_count + + self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)") + self.page.wait_for_timeout(scroll_pause_ms) + + try: + self.page.wait_for_load_state("networkidle", timeout=3000) + except: + pass + @property def url(self) -> str: return f"{self.config['FRONTEND_BASE_URL']}/{self.seo_path}" @@ -42,6 +62,8 @@ def products_list_view(self) -> Locator: @property def product_cards(self) -> list[ProductCardComponent]: + self._fetch_all_product_cards() + return [ ProductCardComponent(card) for card in self.products_grid_view.locator( diff --git a/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py b/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py index 9185d34b..d239dd22 100644 --- a/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py +++ b/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py @@ -29,9 +29,6 @@ def test_e2e_category_add_to_cart_component_viewport( category_page.view_switcher.switch_category_view("grid") - print(f"Product code: {product['code']}") - print(f"Product cards: {category_page.product_cards}") - product_card = category_page.get_product_card_by_sku(product["code"]) expect(product_card.element).to_be_visible(), "Product card is not visible" From ee06bc655a4c25ef04fbc0743d4acc2bd33a20c3 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 3 Feb 2026 11:37:30 +0200 Subject: [PATCH 06/24] refacotor: method to find product card on category page --- tests_e2e/pages/category_page.py | 43 +++++++++++++------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index d4309a7a..61b292d8 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -1,4 +1,4 @@ -from playwright.sync_api import Locator, Page +from playwright.sync_api import Locator, Page, expect from fixtures import Config from tests_e2e.components import ( @@ -22,26 +22,6 @@ def __init__( self.page = page self.seo_path = seo_path - def _fetch_all_product_cards(self, scroll_pause_ms: int = 500): - prev_count = 1 - - while True: - cards = self.products_grid_view.locator("[data-test-id='product-card']") - current_count = cards.count() - - if current_count == prev_count: - break - - prev_count = current_count - - self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)") - self.page.wait_for_timeout(scroll_pause_ms) - - try: - self.page.wait_for_load_state("networkidle", timeout=3000) - except: - pass - @property def url(self) -> str: return f"{self.config['FRONTEND_BASE_URL']}/{self.seo_path}" @@ -62,8 +42,6 @@ def products_list_view(self) -> Locator: @property def product_cards(self) -> list[ProductCardComponent]: - self._fetch_all_product_cards() - return [ ProductCardComponent(card) for card in self.products_grid_view.locator( @@ -87,8 +65,21 @@ def navigate(self) -> None: self.page.goto(f"{self.url}?sort=price-ascending") self.page.wait_for_load_state("networkidle") - def get_product_card_by_sku(self, sku: str) -> ProductCardComponent | None: - for product_card in self.product_cards: - if product_card.sku == sku: + def get_product_card_by_sku( + self, sku: str, max_pages_count: int = 5, scroll_pause_ms: int = 500 + ) -> ProductCardComponent | None: + for _ in range(max_pages_count): + product_card = next( + ( + product_card + for product_card in self.product_cards + if product_card.sku == sku + ), + None, + ) + if product_card: return product_card + + self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)") + self.page.wait_for_timeout(scroll_pause_ms) return None From be1923a3e40f563c0181e93cc957107949b1c508 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 3 Feb 2026 12:11:50 +0200 Subject: [PATCH 07/24] refactor: fetch all product cards on category page --- tests_e2e/pages/category_page.py | 42 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 61b292d8..f0754ec5 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -1,4 +1,4 @@ -from playwright.sync_api import Locator, Page, expect +from playwright.sync_api import Locator, Page from fixtures import Config from tests_e2e.components import ( @@ -40,8 +40,21 @@ def products_grid_view(self) -> Locator: def products_list_view(self) -> Locator: return self.page.locator("[data-test-id='category-page.products-list-view']") + @property + def end_list_label(self) -> Locator: + return self.page.locator("[data-test-id='end-list-label']") + + @property + def products_loader(self) -> Locator: + return self.page.locator("[data-test-id='category-products-loader']") + @property def product_cards(self) -> list[ProductCardComponent]: + while True: + self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)") + if self.end_list_label.is_visible(): + break + return [ ProductCardComponent(card) for card in self.products_grid_view.locator( @@ -65,21 +78,12 @@ def navigate(self) -> None: self.page.goto(f"{self.url}?sort=price-ascending") self.page.wait_for_load_state("networkidle") - def get_product_card_by_sku( - self, sku: str, max_pages_count: int = 5, scroll_pause_ms: int = 500 - ) -> ProductCardComponent | None: - for _ in range(max_pages_count): - product_card = next( - ( - product_card - for product_card in self.product_cards - if product_card.sku == sku - ), - None, - ) - if product_card: - return product_card - - self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)") - self.page.wait_for_timeout(scroll_pause_ms) - return None + def get_product_card_by_sku(self, sku: str) -> ProductCardComponent | None: + return next( + ( + product_card + for product_card in self.product_cards + if product_card.sku == sku + ), + None, + ) From 263bd370b0ce9e1af4729e70125b1526ac16386a Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 3 Feb 2026 13:03:48 +0200 Subject: [PATCH 08/24] refactor: get all product cards on category page --- tests_e2e/pages/category_page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index f0754ec5..63bfb9fa 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -52,6 +52,7 @@ def products_loader(self) -> Locator: def product_cards(self) -> list[ProductCardComponent]: while True: self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)") + self.page.expect_request_finished(lambda request: "/graphql" in request.url) if self.end_list_label.is_visible(): break From ee8c8972d2aae7b2a8f9ff513a31549a30f7f0b9 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Wed, 4 Feb 2026 12:20:02 +0200 Subject: [PATCH 09/24] feat: add debug workflow --- tests_e2e/pages/category_page.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 63bfb9fa..bd519dce 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -50,12 +50,6 @@ def products_loader(self) -> Locator: @property def product_cards(self) -> list[ProductCardComponent]: - while True: - self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)") - self.page.expect_request_finished(lambda request: "/graphql" in request.url) - if self.end_list_label.is_visible(): - break - return [ ProductCardComponent(card) for card in self.products_grid_view.locator( From 69d7f9962db48f6ce4651b2b8a64e1e0600b127e Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Wed, 4 Feb 2026 12:43:09 +0200 Subject: [PATCH 10/24] feat: add debug github workflow --- .github/workflows/e2e-single-test-docker.yml | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/e2e-single-test-docker.yml diff --git a/.github/workflows/e2e-single-test-docker.yml b/.github/workflows/e2e-single-test-docker.yml new file mode 100644 index 00000000..b49b4548 --- /dev/null +++ b/.github/workflows/e2e-single-test-docker.yml @@ -0,0 +1,27 @@ +name: E2E Debug Test Docker + +on: + workflow_dispatch: + inputs: + frontendZipUrl: + description: 'Frontend zip url' + required: true + type: string + default: 'latest' + +jobs: + e2e-debug-test: + uses: VirtoCommerce/.github/.github/workflows/e2e-autotests.yml@v3.800.23 + with: + installModules: 'false' + installCustomModule: 'false' + runTests: 'true' + vctestingRepoBranch: '${{ github.ref_name }}' + frontendZipUrl: '${{ inputs.frontendZipUrl }}' + customPackagesJsonUrl: 'https://github.com/${{ github.repository }}/raw/${{ github.ref_name }}/backend-packages.json' + vctestingPath: 'tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py' + + secrets: + envPAT: ${{ secrets.REPO_TOKEN }} + testSecretEnvFile: ${{ secrets.VC_TESTING_MODULE_ENV_FILE }} + sendgridApiKey: ${{ secrets.SENDGRID_APIKEY_4E2E_AUTOTESTS }} \ No newline at end of file From e58904ecfaf5281e51ea0b44c50a8cd58a55d26e Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Thu, 5 Feb 2026 08:43:00 +0200 Subject: [PATCH 11/24] chore: remove debug workflow --- .github/workflows/e2e-single-test-docker.yml | 27 -------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/workflows/e2e-single-test-docker.yml diff --git a/.github/workflows/e2e-single-test-docker.yml b/.github/workflows/e2e-single-test-docker.yml deleted file mode 100644 index b49b4548..00000000 --- a/.github/workflows/e2e-single-test-docker.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: E2E Debug Test Docker - -on: - workflow_dispatch: - inputs: - frontendZipUrl: - description: 'Frontend zip url' - required: true - type: string - default: 'latest' - -jobs: - e2e-debug-test: - uses: VirtoCommerce/.github/.github/workflows/e2e-autotests.yml@v3.800.23 - with: - installModules: 'false' - installCustomModule: 'false' - runTests: 'true' - vctestingRepoBranch: '${{ github.ref_name }}' - frontendZipUrl: '${{ inputs.frontendZipUrl }}' - customPackagesJsonUrl: 'https://github.com/${{ github.repository }}/raw/${{ github.ref_name }}/backend-packages.json' - vctestingPath: 'tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py' - - secrets: - envPAT: ${{ secrets.REPO_TOKEN }} - testSecretEnvFile: ${{ secrets.VC_TESTING_MODULE_ENV_FILE }} - sendgridApiKey: ${{ secrets.SENDGRID_APIKEY_4E2E_AUTOTESTS }} \ No newline at end of file From 123dfeaea06fac54f25c40067906abf6a83581d4 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 08:13:56 +0200 Subject: [PATCH 12/24] feat: e2e test for category page scroll to product card --- tests_e2e/pages/category_page.py | 11 ++++++ .../test_e2e_category_scroll_to_product.py | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests_e2e/tests/test_e2e_category_scroll_to_product.py diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index bd519dce..8941cc9d 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -82,3 +82,14 @@ def get_product_card_by_sku(self, sku: str) -> ProductCardComponent | None: ), None, ) + + def scroll_to_product_card( + self, sku: str, page_limit: int = 10 + ) -> ProductCardComponent | None: + for _ in range(page_limit): + product_card = self.get_product_card_by_sku(sku) + if product_card: + product_card.element.scroll_into_view_if_needed() + return product_card + self.page.evaluate("window.scrollBy(0, 100)") + return None diff --git a/tests_e2e/tests/test_e2e_category_scroll_to_product.py b/tests_e2e/tests/test_e2e_category_scroll_to_product.py new file mode 100644 index 00000000..a1955f8c --- /dev/null +++ b/tests_e2e/tests/test_e2e_category_scroll_to_product.py @@ -0,0 +1,35 @@ +import os +import time +from typing import Any + +import pytest +from playwright.sync_api import Page, expect + +from fixtures import Config +from tests_e2e.pages import CategoryPage + + +@pytest.mark.e2e +def test_e2e_category_scroll_to_product( + config: Config, page: Page, dataset: dict[str, Any] +): + print( + f"{os.linesep}Running E2E test to scroll to product on category page...", + end=" ", + ) + + page.set_viewport_size({"width": 1920, "height": 1080}) + + category = dataset["categories"][0] + product = dataset["products"][1] + + category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) + category_page.navigate() + + category_page.view_switcher.switch_category_view("grid") + + product_card = category_page.scroll_to_product_card(product["code"]) + + time.sleep(5) + + expect(product_card.element).to_be_visible(), "Product card is not visible" From 62a1d63b937dc5068222278ab637bc8dcff692ae Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 09:33:08 +0200 Subject: [PATCH 13/24] fix: wait for graphql response after category scroll --- tests_e2e/pages/category_page.py | 8 +++++++- tests_e2e/tests/test_e2e_category_scroll_to_product.py | 4 +--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 8941cc9d..d735a26e 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -1,4 +1,4 @@ -from playwright.sync_api import Locator, Page +from playwright.sync_api import Locator, Page, TimeoutError from fixtures import Config from tests_e2e.components import ( @@ -92,4 +92,10 @@ def scroll_to_product_card( product_card.element.scroll_into_view_if_needed() return product_card self.page.evaluate("window.scrollBy(0, 100)") + try: + self.page.expect_request_finished( + lambda request: "/graphql" in request.url + ) + except TimeoutError: + pass return None diff --git a/tests_e2e/tests/test_e2e_category_scroll_to_product.py b/tests_e2e/tests/test_e2e_category_scroll_to_product.py index a1955f8c..99f8f8e8 100644 --- a/tests_e2e/tests/test_e2e_category_scroll_to_product.py +++ b/tests_e2e/tests/test_e2e_category_scroll_to_product.py @@ -1,5 +1,4 @@ import os -import time from typing import Any import pytest @@ -30,6 +29,5 @@ def test_e2e_category_scroll_to_product( product_card = category_page.scroll_to_product_card(product["code"]) - time.sleep(5) - + assert product_card is not None, "Product card is not found" expect(product_card.element).to_be_visible(), "Product card is not visible" From 4da5d37a2355da2aa8e68752235860c5c61c694f Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 17:43:51 +0200 Subject: [PATCH 14/24] refactor: method to scroll category page --- tests_e2e/pages/category_page.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index d735a26e..9d260f13 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -91,11 +91,11 @@ def scroll_to_product_card( if product_card: product_card.element.scroll_into_view_if_needed() return product_card - self.page.evaluate("window.scrollBy(0, 100)") - try: - self.page.expect_request_finished( - lambda request: "/graphql" in request.url - ) - except TimeoutError: - pass + if self.end_list_label.is_visible(): + break + with self.page.expect_response( + lambda response: "/graphql" in response.url + and response.status == 200 + ): + self.products_loader.scroll_into_view_if_needed() return None From 65c01dc2ff04bfba3b21c7f43ceb5bbeaa300380 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 18:30:09 +0200 Subject: [PATCH 15/24] refcator: method to scroll to product card on category page --- tests_e2e/pages/category_page.py | 5 +++++ tests_e2e/tests/test_e2e_category_scroll_to_product.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 9d260f13..1e4942a9 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -86,6 +86,9 @@ def get_product_card_by_sku(self, sku: str) -> ProductCardComponent | None: def scroll_to_product_card( self, sku: str, page_limit: int = 10 ) -> ProductCardComponent | None: + product_card_locator = self.products_grid_view.locator( + "[data-test-id='product-card']" + ) for _ in range(page_limit): product_card = self.get_product_card_by_sku(sku) if product_card: @@ -93,9 +96,11 @@ def scroll_to_product_card( return product_card if self.end_list_label.is_visible(): break + current_count = product_card_locator.count() with self.page.expect_response( lambda response: "/graphql" in response.url and response.status == 200 ): self.products_loader.scroll_into_view_if_needed() + product_card_locator.nth(current_count).wait_for() return None diff --git a/tests_e2e/tests/test_e2e_category_scroll_to_product.py b/tests_e2e/tests/test_e2e_category_scroll_to_product.py index 99f8f8e8..a321c0a3 100644 --- a/tests_e2e/tests/test_e2e_category_scroll_to_product.py +++ b/tests_e2e/tests/test_e2e_category_scroll_to_product.py @@ -30,4 +30,4 @@ def test_e2e_category_scroll_to_product( product_card = category_page.scroll_to_product_card(product["code"]) assert product_card is not None, "Product card is not found" - expect(product_card.element).to_be_visible(), "Product card is not visible" + expect(product_card.element, "Product card is not visible").to_be_visible() From 88f16cf97046bea05ffc15ee0af94b4146d8c69f Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 18:59:49 +0200 Subject: [PATCH 16/24] refactor: method to scroll category page --- tests_e2e/pages/category_page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 1e4942a9..dce3f481 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -94,6 +94,7 @@ def scroll_to_product_card( if product_card: product_card.element.scroll_into_view_if_needed() return product_card + self.products_loader.or_(self.end_list_label).wait_for(state="visible") if self.end_list_label.is_visible(): break current_count = product_card_locator.count() From eb6403f411cccf435b88b8dc65615a06ea9357fd Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 20:12:34 +0200 Subject: [PATCH 17/24] refactor: category page scroll --- tests_e2e/pages/category_page.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index dce3f481..147341fd 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -1,4 +1,4 @@ -from playwright.sync_api import Locator, Page, TimeoutError +from playwright.sync_api import Locator, Page from fixtures import Config from tests_e2e.components import ( @@ -40,14 +40,6 @@ def products_grid_view(self) -> Locator: def products_list_view(self) -> Locator: return self.page.locator("[data-test-id='category-page.products-list-view']") - @property - def end_list_label(self) -> Locator: - return self.page.locator("[data-test-id='end-list-label']") - - @property - def products_loader(self) -> Locator: - return self.page.locator("[data-test-id='category-products-loader']") - @property def product_cards(self) -> list[ProductCardComponent]: return [ @@ -94,14 +86,11 @@ def scroll_to_product_card( if product_card: product_card.element.scroll_into_view_if_needed() return product_card - self.products_loader.or_(self.end_list_label).wait_for(state="visible") - if self.end_list_label.is_visible(): - break current_count = product_card_locator.count() with self.page.expect_response( lambda response: "/graphql" in response.url and response.status == 200 ): - self.products_loader.scroll_into_view_if_needed() + self.page.keyboard.press("PageDown") product_card_locator.nth(current_count).wait_for() return None From 3a5ffc2fb5d96be0ac0fd108a94b95ec668101c1 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 20:45:18 +0200 Subject: [PATCH 18/24] refactor: category page scroll --- tests_e2e/pages/category_page.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 147341fd..5659345b 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -1,4 +1,4 @@ -from playwright.sync_api import Locator, Page +from playwright.sync_api import Locator, Page, TimeoutError from fixtures import Config from tests_e2e.components import ( @@ -87,10 +87,14 @@ def scroll_to_product_card( product_card.element.scroll_into_view_if_needed() return product_card current_count = product_card_locator.count() - with self.page.expect_response( - lambda response: "/graphql" in response.url - and response.status == 200 - ): - self.page.keyboard.press("PageDown") - product_card_locator.nth(current_count).wait_for() + try: + with self.page.expect_response( + lambda response: "/graphql" in response.url + and response.status == 200, + timeout=5000, + ): + self.page.keyboard.press("PageDown") + product_card_locator.nth(current_count).wait_for() + except TimeoutError: + continue return None From be52f6c3a973cda0288c7cff4a33ab707fb56ff9 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 21:15:01 +0200 Subject: [PATCH 19/24] refactor: test for another product on another category page --- tests_e2e/tests/test_e2e_category_scroll_to_product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_e2e/tests/test_e2e_category_scroll_to_product.py b/tests_e2e/tests/test_e2e_category_scroll_to_product.py index a321c0a3..cdadfcce 100644 --- a/tests_e2e/tests/test_e2e_category_scroll_to_product.py +++ b/tests_e2e/tests/test_e2e_category_scroll_to_product.py @@ -20,7 +20,7 @@ def test_e2e_category_scroll_to_product( page.set_viewport_size({"width": 1920, "height": 1080}) category = dataset["categories"][0] - product = dataset["products"][1] + product = dataset["products"][14] category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) category_page.navigate() From 7a607eacf4f5d266011f733162384bb55477ba01 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 10 Feb 2026 21:47:39 +0200 Subject: [PATCH 20/24] fix: restore test to run in CI --- .../test_e2e_category_add_to_cart_viewport.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py b/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py index d239dd22..41bf0d16 100644 --- a/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py +++ b/tests_e2e/tests/test_e2e_category_add_to_cart_viewport.py @@ -22,36 +22,42 @@ def test_e2e_category_add_to_cart_component_viewport( page.set_viewport_size({"width": 1920, "height": 1080}) category = dataset["categories"][0] - product = dataset["products"][1] + product = dataset["products"][14] category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) category_page.navigate() category_page.view_switcher.switch_category_view("grid") - product_card = category_page.get_product_card_by_sku(product["code"]) + product_card = category_page.scroll_to_product_card(product["code"]) - expect(product_card.element).to_be_visible(), "Product card is not visible" + assert product_card is not None, "Product card is not found" + expect(product_card.element, "Product card is not visible").to_be_visible() if config["PRODUCT_QUANTITY_CONTROL"] == "stepper": expect( - product_card.quantity_stepper_component.element - ).to_be_visible(), "Quantity stepper component is not visible" + product_card.quantity_stepper_component.element, + "Quantity stepper component is not visible", + ).to_be_visible() if config["PRODUCT_QUANTITY_CONTROL"] == "button": expect( - product_card.add_to_cart_component.add_to_cart_text_button - ).to_be_visible(), "Add to cart text button is not visible" + product_card.add_to_cart_component.add_to_cart_text_button, + "Add to cart text button is not visible", + ).to_be_visible() expect( - product_card.add_to_cart_component.add_to_cart_icon_button - ).not_to_be_visible(), "Add to cart icon button is visible" + product_card.add_to_cart_component.add_to_cart_icon_button, + "Add to cart icon button is visible", + ).not_to_be_visible() page.set_viewport_size({"width": 800, "height": 600}) if config["PRODUCT_QUANTITY_CONTROL"] == "button": expect( - product_card.add_to_cart_component.add_to_cart_text_button - ).not_to_be_visible(), "Add to cart text button is visible" + product_card.add_to_cart_component.add_to_cart_text_button, + "Add to cart text button is visible", + ).not_to_be_visible() expect( - product_card.add_to_cart_component.add_to_cart_icon_button - ).to_be_visible(), "Add to cart icon button is not visible" + product_card.add_to_cart_component.add_to_cart_icon_button, + "Add to cart icon button is not visible", + ).to_be_visible() From 6e2f4eb16a538c0df632c3682e79a55cfec7e6fa Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Wed, 11 Feb 2026 07:14:19 +0200 Subject: [PATCH 21/24] refactor: category scroll method --- tests_e2e/pages/category_page.py | 10 +++- ...t_e2e_category_page_add_product_to_cart.py | 49 ++++++++++++++----- .../test_e2e_category_price_range_filter.py | 15 ++++-- tests_e2e/tests/test_e2e_change_cart_item.py | 12 +++-- .../test_e2e_checkout_add_shipping_address.py | 10 +++- 5 files changed, 74 insertions(+), 22 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 5659345b..a273c90d 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -49,6 +49,14 @@ def product_cards(self) -> list[ProductCardComponent]: ).all() ] + @property + def infinity_scroll_loader(self) -> Locator: + return self.page.locator("[data-test-id='category-endless-scroll-loader']") + + @property + def end_list_label(self) -> Locator: + return self.page.locator("[data-test-id='end-list-label']") + @property def price_filter_slider(self) -> FilterSliderComponent | None: return FilterSliderComponent(self.page.locator("[data-test-id='filter-price']")) @@ -93,7 +101,7 @@ def scroll_to_product_card( and response.status == 200, timeout=5000, ): - self.page.keyboard.press("PageDown") + self.infinity_scroll_loader.scroll_into_view_if_needed() product_card_locator.nth(current_count).wait_for() except TimeoutError: continue diff --git a/tests_e2e/tests/test_e2e_category_page_add_product_to_cart.py b/tests_e2e/tests/test_e2e_category_page_add_product_to_cart.py index 0103955e..7632f336 100644 --- a/tests_e2e/tests/test_e2e_category_page_add_product_to_cart.py +++ b/tests_e2e/tests/test_e2e_category_page_add_product_to_cart.py @@ -27,7 +27,7 @@ def test_e2e_category_add_product_to_cart_with_add_to_cart_button( ) category = dataset["categories"][0] - product = dataset["products"][1] + product = dataset["products"][14] quantity_to_add = "2" page.set_viewport_size({"width": 1920, "height": 1080}) @@ -42,13 +42,21 @@ def test_e2e_category_add_product_to_cart_with_add_to_cart_button( category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) category_page.navigate() - product_card = category_page.get_product_card_by_sku(product["code"]) + product_card = category_page.scroll_to_product_card(product["code"]) + + assert ( + product_card is not None + ), f"Product card with sku {product['code']} was not found on category page" + product_card.add_to_cart_component.quantity_input.fill(quantity_to_add) product_card.add_to_cart_component.add_to_cart_text_button.click() - expect(product_card.count_in_cart_label, "Count in cart label is not visible").to_be_visible() expect( - product_card.count_in_cart_label, "Count in cart label is not equal to product quantity to add" + product_card.count_in_cart_label, "Count in cart label is not visible" + ).to_be_visible() + expect( + product_card.count_in_cart_label, + "Count in cart label is not equal to product quantity to add", ).to_have_text(quantity_to_add) cart_page = CartPage(config, page) @@ -65,9 +73,15 @@ def test_e2e_category_add_product_to_cart_with_add_to_cart_button( try: line_item = cart_page.get_line_item_by_sku(product["code"]) - assert line_item.sku == product["code"], f"Line item sku is not equal to product sku: {product['code']}" assert ( - line_item.add_to_cart_component.quantity_input.input_value() == quantity_to_add + line_item is not None + ), f"Line item with sku {product['code']} was not found in cart" + assert ( + line_item.sku == product["code"] + ), f"Line item sku is not equal to product sku: {product['code']}" + assert ( + line_item.add_to_cart_component.quantity_input.input_value() + == quantity_to_add ), f"Line item quantity is not equal to product quantity to add: {quantity_to_add}" finally: cart_operations.remove_cart( @@ -97,7 +111,7 @@ def test_e2e_category_add_product_to_cart_with_quantity_stepper( page.set_viewport_size({"width": 1920, "height": 1080}) category = dataset["categories"][0] - product = dataset["products"][1] + product = dataset["products"][14] quantity_to_add = 2 user_operations = UserOperations(graphql_client) @@ -110,7 +124,12 @@ def test_e2e_category_add_product_to_cart_with_quantity_stepper( category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) category_page.navigate() - product_card = category_page.get_product_card_by_sku(product["code"]) + product_card = category_page.scroll_to_product_card(product["code"]) + + assert ( + product_card is not None + ), f"Product card with sku {product['code']} was not found on category page" + product_card.quantity_stepper_component.increment_button.click() product_card.quantity_stepper_component.increment_button.click() @@ -118,9 +137,12 @@ def test_e2e_category_add_product_to_cart_with_quantity_stepper( product_card.quantity_stepper_component.quantity_input, f"Quantity input is not equal to {quantity_to_add}", ).to_have_value(str(quantity_to_add)) - expect(product_card.count_in_cart_label, "Count in cart label is not visible").to_be_visible() expect( - product_card.count_in_cart_label, "Count in cart label is not equal to product quantity to add" + product_card.count_in_cart_label, "Count in cart label is not visible" + ).to_be_visible() + expect( + product_card.count_in_cart_label, + "Count in cart label is not equal to product quantity to add", ).to_have_text(str(quantity_to_add)) cart_page = CartPage(config, page) @@ -137,7 +159,12 @@ def test_e2e_category_add_product_to_cart_with_quantity_stepper( try: line_item = cart_page.get_line_item_by_sku(product["code"]) - assert line_item.sku == product["code"], f"Line item sku is not equal to product sku: {product['code']}" + assert ( + line_item is not None + ), f"Line item with sku {product['code']} was not found in cart" + assert ( + line_item.sku == product["code"] + ), f"Line item sku is not equal to product sku: {product['code']}" expect( line_item.quantity_stepper_component.quantity_input, f"Line item quantity is not equal to product quantity to add: {quantity_to_add}", diff --git a/tests_e2e/tests/test_e2e_category_price_range_filter.py b/tests_e2e/tests/test_e2e_category_price_range_filter.py index 124f16cd..35a867e3 100644 --- a/tests_e2e/tests/test_e2e_category_price_range_filter.py +++ b/tests_e2e/tests/test_e2e_category_price_range_filter.py @@ -29,6 +29,9 @@ def test_e2e_category_price_range_filter_slider( category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) category_page.navigate() + assert ( + category_page.price_filter_slider is not None + ), "Price filter slider is not present on the page" expect(category_page.price_filter_slider.element).to_be_visible() category_page.price_filter_slider.click_header() @@ -42,9 +45,10 @@ def test_e2e_category_price_range_filter_slider( page.locator("body").click() category_page.products_count_locator.wait_for(state="visible") - page.wait_for_timeout(2000) - assert category_page.products_count == 13, "Products count is not equal to 13" + expect( + category_page.products_count_locator, "Products count is not equal to 17" + ).to_have_text("17") @pytest.mark.e2e @@ -68,6 +72,9 @@ def test_e2e_category_price_range_filter_default( category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) category_page.navigate() + assert ( + category_page.price_filter_facets is not None + ), "Price filter facets are not present on the page" expect(category_page.price_filter_facets.element).to_be_visible() category_page.price_filter_facets.click_header() @@ -78,4 +85,6 @@ def test_e2e_category_price_range_filter_default( category_page.products_count_locator.wait_for(state="visible") - assert category_page.products_count == 13, "Products count is not equal to 13" + expect( + category_page.products_count_locator, "Products count is not equal to 17" + ).to_have_text("17") diff --git a/tests_e2e/tests/test_e2e_change_cart_item.py b/tests_e2e/tests/test_e2e_change_cart_item.py index e90ce96a..7c17acaf 100644 --- a/tests_e2e/tests/test_e2e_change_cart_item.py +++ b/tests_e2e/tests/test_e2e_change_cart_item.py @@ -1,14 +1,13 @@ import os from typing import Any -import allure import pytest from playwright.sync_api import Page, expect from fixtures import Auth, Config, GraphQLClient from graphql_operations.cart.cart_operations import CartOperations from graphql_operations.user.user_operations import UserOperations -from tests_e2e.pages import CartPage, CategoryPage +from tests_e2e.pages import CartPage @pytest.mark.e2e @@ -23,7 +22,7 @@ def test_e2e_change_cart_item( page.set_viewport_size({"width": 1920, "height": 1080}) - product = dataset["products"][1] + product = dataset["products"][14] user_operations = UserOperations(graphql_client) cart_operations = CartOperations(graphql_client) @@ -44,13 +43,16 @@ def test_e2e_change_cart_item( cart_page.navigate() line_item = cart_page.get_line_item_by_sku(product["code"]) + + assert ( + line_item is not None + ), f"Line item with SKU {product['code']} not found in cart." + if config["PRODUCT_QUANTITY_CONTROL"] == "stepper": line_item.quantity_stepper_component.increment_button.click() elif config["PRODUCT_QUANTITY_CONTROL"] == "button": line_item.add_to_cart_component.quantity_input.fill("3") - # requests_tracker.wait_for_all_requests() - if config["PRODUCT_QUANTITY_CONTROL"] == "stepper": expect(line_item.quantity_stepper_component.quantity_input).to_have_value("3") elif config["PRODUCT_QUANTITY_CONTROL"] == "button": diff --git a/tests_e2e/tests/test_e2e_checkout_add_shipping_address.py b/tests_e2e/tests/test_e2e_checkout_add_shipping_address.py index e98f64ec..2c87d2c2 100644 --- a/tests_e2e/tests/test_e2e_checkout_add_shipping_address.py +++ b/tests_e2e/tests/test_e2e_checkout_add_shipping_address.py @@ -34,7 +34,7 @@ def test_e2e_anonymous_single_page_checkout_add_shipping_address( user_operations = UserOperations(graphql_client) cart_operations = CartOperations(graphql_client) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -51,6 +51,9 @@ def test_e2e_anonymous_single_page_checkout_add_shipping_address( cart_page = CartPage(config, page) cart_page.navigate() + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component not found on cart page." expect( cart_page.shipping_details_section_component.address_selector_component.select_address_button ).to_be_visible() @@ -100,6 +103,7 @@ def test_e2e_anonymous_multi_step_checkout_add_shipping_address( auth: Auth, graphql_client: GraphQLClient, page: Page, + dataset: dict[str, Any], ): if config["CHECKOUT_MODE"] == "single-page": pytest.skip( @@ -116,12 +120,14 @@ def test_e2e_anonymous_multi_step_checkout_add_shipping_address( user_operations = UserOperations(graphql_client) cart_operations = CartOperations(graphql_client) + product = dataset["products"][14] + user = user_operations.get_me() cart = cart_operations.add_item_to_cart( payload={ "storeId": config["STORE_ID"], "userId": user["id"], - "productId": "product-acme-laptop-hp-pavilion-16-ag0087nr", + "productId": product["code"], "quantity": 2, } ) From 5d79a693e85426e55dfeb6452c9401466aef2e21 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Wed, 11 Feb 2026 07:52:47 +0200 Subject: [PATCH 22/24] refactor: category page scroll --- tests_e2e/pages/category_page.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index a273c90d..88b263e6 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -86,15 +86,12 @@ def get_product_card_by_sku(self, sku: str) -> ProductCardComponent | None: def scroll_to_product_card( self, sku: str, page_limit: int = 10 ) -> ProductCardComponent | None: - product_card_locator = self.products_grid_view.locator( - "[data-test-id='product-card']" - ) for _ in range(page_limit): product_card = self.get_product_card_by_sku(sku) if product_card: product_card.element.scroll_into_view_if_needed() return product_card - current_count = product_card_locator.count() + self.infinity_scroll_loader.scroll_into_view_if_needed() try: with self.page.expect_response( lambda response: "/graphql" in response.url @@ -102,7 +99,27 @@ def scroll_to_product_card( timeout=5000, ): self.infinity_scroll_loader.scroll_into_view_if_needed() - product_card_locator.nth(current_count).wait_for() except TimeoutError: continue return None + + # product_card_locator = self.products_grid_view.locator( + # "[data-test-id='product-card']" + # ) + # for _ in range(page_limit): + # product_card = self.get_product_card_by_sku(sku) + # if product_card: + # product_card.element.scroll_into_view_if_needed() + # return product_card + # current_count = product_card_locator.count() + # try: + # with self.page.expect_response( + # lambda response: "/graphql" in response.url + # and response.status == 200, + # timeout=5000, + # ): + # self.infinity_scroll_loader.scroll_into_view_if_needed() + # product_card_locator.nth(current_count).wait_for() + # except TimeoutError: + # continue + # return None From c843d8b5a6185397b0cccf53594c304e994a9159 Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 17 Feb 2026 10:57:16 +0200 Subject: [PATCH 23/24] fix: e2e tests --- tests_e2e/pages/category_page.py | 26 +- tests_e2e/pages/main_layout_page.py | 19 +- .../test_e2e_category_scroll_to_product.py | 23 ++ ...est_e2e_checkout_switch_shipping_option.py | 83 +++--- tests_e2e/tests/test_e2e_create_order.py | 163 +++++++---- .../tests/test_e2e_filter_pickup_locations.py | 254 +++++++++++++----- tests_e2e/tests/test_e2e_merge_carts.py | 145 ++++++---- 7 files changed, 498 insertions(+), 215 deletions(-) diff --git a/tests_e2e/pages/category_page.py b/tests_e2e/pages/category_page.py index 88b263e6..26868422 100644 --- a/tests_e2e/pages/category_page.py +++ b/tests_e2e/pages/category_page.py @@ -94,32 +94,12 @@ def scroll_to_product_card( self.infinity_scroll_loader.scroll_into_view_if_needed() try: with self.page.expect_response( - lambda response: "/graphql" in response.url - and response.status == 200, + lambda response: "/graphql" in response.url, timeout=5000, ): self.infinity_scroll_loader.scroll_into_view_if_needed() except TimeoutError: continue + if self.end_list_label.is_visible(): + return return None - - # product_card_locator = self.products_grid_view.locator( - # "[data-test-id='product-card']" - # ) - # for _ in range(page_limit): - # product_card = self.get_product_card_by_sku(sku) - # if product_card: - # product_card.element.scroll_into_view_if_needed() - # return product_card - # current_count = product_card_locator.count() - # try: - # with self.page.expect_response( - # lambda response: "/graphql" in response.url - # and response.status == 200, - # timeout=5000, - # ): - # self.infinity_scroll_loader.scroll_into_view_if_needed() - # product_card_locator.nth(current_count).wait_for() - # except TimeoutError: - # continue - # return None diff --git a/tests_e2e/pages/main_layout_page.py b/tests_e2e/pages/main_layout_page.py index 98178b74..72be3c85 100644 --- a/tests_e2e/pages/main_layout_page.py +++ b/tests_e2e/pages/main_layout_page.py @@ -1,4 +1,4 @@ -from playwright.sync_api import Page, expect +from playwright.sync_api import Locator, Page, expect from tests_e2e.components import ( AccountMenuComponent, @@ -25,6 +25,23 @@ def account_menu(self) -> AccountMenuComponent: def search_bar(self) -> SearchBarComponent: return SearchBarComponent(self.page.locator(".search-bar")) + @property + def cart_link(self) -> Locator: + return self.page.locator("[data-test-id='desktop-main-menu-cart-link']") + + @property + def cart_items_badge(self) -> Locator: + return self.cart_link.locator(".vc-badge") + + @property + def cart_items_count(self) -> int: + text_content = self.cart_items_badge.locator( + ".vc-badge__content" + ).text_content() + if text_content and text_content.isdigit(): + return int(text_content) + return 0 + def dismiss_blocking_modal(self) -> None: modal_wrapper = self.page.locator(".vc-modal__wrapper") try: diff --git a/tests_e2e/tests/test_e2e_category_scroll_to_product.py b/tests_e2e/tests/test_e2e_category_scroll_to_product.py index cdadfcce..7804c398 100644 --- a/tests_e2e/tests/test_e2e_category_scroll_to_product.py +++ b/tests_e2e/tests/test_e2e_category_scroll_to_product.py @@ -31,3 +31,26 @@ def test_e2e_category_scroll_to_product( assert product_card is not None, "Product card is not found" expect(product_card.element, "Product card is not visible").to_be_visible() + + +@pytest.mark.e2e +def test_e2e_category_scroll_to_unexisting_product( + config: Config, page: Page, dataset: dict[str, Any] +): + print( + f"{os.linesep}Running E2E test to scroll to unexisting product on category page...", + end=" ", + ) + + page.set_viewport_size({"width": 1920, "height": 1080}) + + category = dataset["categories"][0] + + category_page = CategoryPage(config, page, category["seoInfos"][0]["semanticUrl"]) + category_page.navigate() + + category_page.view_switcher.switch_category_view("grid") + + product_card = category_page.scroll_to_product_card("unexisting-product-sku") + + assert product_card is None, "Product card should not be found" diff --git a/tests_e2e/tests/test_e2e_checkout_switch_shipping_option.py b/tests_e2e/tests/test_e2e_checkout_switch_shipping_option.py index 5b4113ed..2d5b54d8 100644 --- a/tests_e2e/tests/test_e2e_checkout_switch_shipping_option.py +++ b/tests_e2e/tests/test_e2e_checkout_switch_shipping_option.py @@ -35,7 +35,7 @@ def test_e2e_checkout_multi_step_switch_shipping_option( auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -50,40 +50,47 @@ def test_e2e_checkout_multi_step_switch_shipping_option( checkout_shipping_page = CheckoutShippingPage(config, page) checkout_shipping_page.navigate() - expect(page).to_have_url( + expect(page, "Checkout shipping page is not loaded").to_have_url( checkout_shipping_page.url - ), "Checkout shipping page is not loaded" + ) expect( - checkout_shipping_page.shipping_details_section_component.element - ).to_be_visible(), "Shipping details section is not visible" + checkout_shipping_page.shipping_details_section_component.element, + "Shipping details section is not visible", + ).to_be_visible() expect( - checkout_shipping_page.shipping_details_section_component.pickup_delivery_option_switcher - ).to_be_visible(), "Pickup delivery option switcher is not visible" + checkout_shipping_page.shipping_details_section_component.pickup_delivery_option_switcher, + "Pickup delivery option switcher is not visible", + ).to_be_visible() expect( - checkout_shipping_page.shipping_details_section_component.shipping_delivery_option_switcher - ).to_be_visible(), "Shipping delivery option switcher is not visible" + checkout_shipping_page.shipping_details_section_component.shipping_delivery_option_switcher, + "Shipping delivery option switcher is not visible", + ).to_be_visible() checkout_shipping_page.shipping_details_section_component.switch_delivery_option( "pickup" ) expect( - checkout_shipping_page.shipping_details_section_component.pickup_point_section - ).to_be_visible(), "Pickup point section is not visible" + checkout_shipping_page.shipping_details_section_component.pickup_point_section, + "Pickup point section is not visible", + ).to_be_visible() expect( - checkout_shipping_page.shipping_details_section_component.shipping_method_selector - ).not_to_be_visible(), "Shipping method selector is visible" + checkout_shipping_page.shipping_details_section_component.shipping_method_selector, + "Shipping method selector is visible", + ).not_to_be_visible() checkout_shipping_page.shipping_details_section_component.switch_delivery_option( "shipping" ) expect( - checkout_shipping_page.shipping_details_section_component.pickup_point_section - ).not_to_be_visible(), "Pickup point section is visible" + checkout_shipping_page.shipping_details_section_component.pickup_point_section, + "Pickup point section is visible", + ).not_to_be_visible() expect( - checkout_shipping_page.shipping_details_section_component.shipping_method_selector - ).to_be_visible(), "Shipping method selector is not visible" + checkout_shipping_page.shipping_details_section_component.shipping_method_selector, + "Shipping method selector is not visible", + ).to_be_visible() cart_operations.remove_cart( payload={ @@ -118,7 +125,7 @@ def test_e2e_checkout_single_page_switch_shipping_option( auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -133,34 +140,44 @@ def test_e2e_checkout_single_page_switch_shipping_option( cart_page = CartPage(config, page) cart_page.navigate() - expect(page).to_have_url(cart_page.url), "Cart page is not loaded" + expect(page, "Cart page is not loaded").to_have_url(cart_page.url) + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component is not found" expect( - cart_page.shipping_details_section_component.element - ).to_be_visible(), "Shipping details section is not visible" + cart_page.shipping_details_section_component.element, + "Shipping details section is not visible", + ).to_be_visible() expect( - cart_page.shipping_details_section_component.pickup_delivery_option_switcher - ).to_be_visible(), "Pickup delivery option switcher is not visible" + cart_page.shipping_details_section_component.pickup_delivery_option_switcher, + "Pickup delivery option switcher is not visible", + ).to_be_visible() expect( - cart_page.shipping_details_section_component.shipping_delivery_option_switcher - ).to_be_visible(), "Shipping delivery option switcher is not visible" + cart_page.shipping_details_section_component.shipping_delivery_option_switcher, + "Shipping delivery option switcher is not visible", + ).to_be_visible() cart_page.shipping_details_section_component.switch_delivery_option("pickup") expect( - cart_page.shipping_details_section_component.pickup_point_section - ).to_be_visible(), "Pickup point section is not visible" + cart_page.shipping_details_section_component.pickup_point_section, + "Pickup point section is not visible", + ).to_be_visible() expect( - cart_page.shipping_details_section_component.shipping_method_selector - ).not_to_be_visible(), "Shipping method selector is visible" + cart_page.shipping_details_section_component.shipping_method_selector, + "Shipping method selector is visible", + ).not_to_be_visible() cart_page.shipping_details_section_component.switch_delivery_option("shipping") expect( - cart_page.shipping_details_section_component.pickup_point_section - ).not_to_be_visible(), "Pickup point section is visible" + cart_page.shipping_details_section_component.pickup_point_section, + "Pickup point section is visible", + ).not_to_be_visible() expect( - cart_page.shipping_details_section_component.shipping_method_selector - ).to_be_visible(), "Shipping method selector is not visible" + cart_page.shipping_details_section_component.shipping_method_selector, + "Shipping method selector is not visible", + ).to_be_visible() cart_operations.remove_cart( payload={ diff --git a/tests_e2e/tests/test_e2e_create_order.py b/tests_e2e/tests/test_e2e_create_order.py index 42c68f36..ff30d5df 100644 --- a/tests_e2e/tests/test_e2e_create_order.py +++ b/tests_e2e/tests/test_e2e_create_order.py @@ -28,7 +28,9 @@ def test_e2e_create_order_multi_step_checkout( page: Page, ): if config["CHECKOUT_MODE"] == "single-page": - pytest.skip("Checkout mode is a single-page, skipping test for multi-step checkout") + pytest.skip( + "Checkout mode is a single-page, skipping test for multi-step checkout" + ) print( f"{os.linesep}Running E2E test to create order in multi-step checkout...", @@ -42,7 +44,7 @@ def test_e2e_create_order_multi_step_checkout( auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart_operations.add_item_to_cart( @@ -57,56 +59,93 @@ def test_e2e_create_order_multi_step_checkout( checkout_shipping_page = CheckoutShippingPage(config, page) checkout_shipping_page.navigate() - expect(page).to_have_url(checkout_shipping_page.url), "Checkout shipping page is not loaded" + expect(page, "Checkout shipping page is not loaded").to_have_url( + checkout_shipping_page.url + ) - checkout_shipping_page.shipping_details_section_component.switch_delivery_option("shipping") + checkout_shipping_page.shipping_details_section_component.switch_delivery_option( + "shipping" + ) expect( - checkout_shipping_page.shipping_details_section_component.address_selector_component.element - ).to_be_visible(), "Shipping address section is not visible" + checkout_shipping_page.shipping_details_section_component.address_selector_component.element, + "Shipping address section is not visible", + ).to_be_visible() expect( - checkout_shipping_page.shipping_details_section_component.shipping_method_selector - ).to_be_visible(), "Shipping method selector is not visible" + checkout_shipping_page.shipping_details_section_component.shipping_method_selector, + "Shipping method selector is not visible", + ).to_be_visible() expect( - checkout_shipping_page.shipping_details_section_component.address_selector_component.selected_address_label - ).to_be_visible(), "Selected address label is not visible" + checkout_shipping_page.shipping_details_section_component.address_selector_component.selected_address_label, + "Selected address label is not visible", + ).to_be_visible() expect( - checkout_shipping_page.shipping_details_section_component.address_selector_component.selected_address_label - ).not_to_be_empty(), "Selected address label is empty" + checkout_shipping_page.shipping_details_section_component.address_selector_component.selected_address_label, + "Selected address label is empty", + ).not_to_be_empty() - checkout_shipping_page.shipping_details_section_component.select_shipping_method("FixedRate_Ground") + checkout_shipping_page.shipping_details_section_component.select_shipping_method( + "FixedRate_Ground" + ) - expect(checkout_shipping_page.billing_button).to_be_visible(), "Billing button is not visible" - expect(checkout_shipping_page.billing_button).to_be_enabled(), "Billing button is disabled" + expect( + checkout_shipping_page.billing_button, + "Billing button is not visible", + ).to_be_visible() + expect( + checkout_shipping_page.billing_button, + "Billing button is disabled", + ).to_be_enabled() checkout_shipping_page.billing_button.click() checkout_billing_page = CheckoutBillingPage(config, page) - expect(page).to_have_url(checkout_billing_page.url), "Checkout billing page is not loaded" + expect(page, "Checkout billing page is not loaded").to_have_url( + checkout_billing_page.url + ) expect( - checkout_billing_page.payment_details_section_component.element - ).to_be_visible(), "Payment details section is not visible" + checkout_billing_page.payment_details_section_component.element, + "Payment details section is not visible", + ).to_be_visible() - checkout_billing_page.payment_details_section_component.select_payment_method("DefaultManualPaymentMethod") + checkout_billing_page.payment_details_section_component.select_payment_method( + "DefaultManualPaymentMethod" + ) - expect(checkout_billing_page.review_order_button).to_be_visible(), "Review order button is not visible" - expect(checkout_billing_page.review_order_button).to_be_enabled(), "Review order button is disabled" + expect( + checkout_billing_page.review_order_button, + "Review order button is not visible", + ).to_be_visible() + expect( + checkout_billing_page.review_order_button, + "Review order button is disabled", + ).to_be_enabled() checkout_billing_page.review_order_button.click() checkout_review_order_page = CheckoutReviewOrderPage(config, page) - expect(page).to_have_url(checkout_review_order_page.url), "Checkout review order page is not loaded" - expect(checkout_review_order_page.place_order_button).to_be_visible(), "Place order button is not visible" - expect(checkout_review_order_page.place_order_button).to_be_enabled(), "Place order button is disabled" + expect(page, "Checkout review order page is not loaded").to_have_url( + checkout_review_order_page.url + ) + expect( + checkout_review_order_page.place_order_button, + "Place order button is not visible", + ).to_be_visible() + expect( + checkout_review_order_page.place_order_button, + "Place order button is disabled", + ).to_be_enabled() checkout_review_order_page.place_order_button.click() page.wait_for_load_state("networkidle") checkout_completed_page = CheckoutCompletedPage(config, page) - expect(page).to_have_url(checkout_completed_page.url), "Checkout completed page is not loaded" + expect(page, "Checkout completed page is not loaded").to_have_url( + checkout_completed_page.url + ) print(f"Order number: {checkout_completed_page.order_number}") assert checkout_completed_page.order_number is not None, "Order number is not found" @@ -121,7 +160,9 @@ def test_e2e_create_order_single_page_checkout( page: Page, ): if config["CHECKOUT_MODE"] == "multi-step": - pytest.skip("Checkout mode is a multi-step, skipping test for single-page checkout") + pytest.skip( + "Checkout mode is a multi-step, skipping test for single-page checkout" + ) print( f"{os.linesep}Running E2E test to create order in multi-step checkout...", @@ -135,7 +176,7 @@ def test_e2e_create_order_single_page_checkout( auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart_operations.add_item_to_cart( @@ -150,49 +191,77 @@ def test_e2e_create_order_single_page_checkout( cart_page = CartPage(config, page) cart_page.navigate() - expect(page).to_have_url(cart_page.url), "Cart page is not loaded" + expect(page, "Cart page is not loaded").to_have_url(cart_page.url) + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component is not found" expect( - cart_page.shipping_details_section_component.element - ).to_be_visible(), "Shipping details section is not visible" + cart_page.shipping_details_section_component.element, + "Shipping details section is not visible", + ).to_be_visible() cart_page.shipping_details_section_component.shipping_delivery_option_switcher.click() expect( - cart_page.shipping_details_section_component.address_selector_component.element - ).to_be_visible(), "Shipping address section is not visible" + cart_page.shipping_details_section_component.address_selector_component.element, + "Shipping address section is not visible", + ).to_be_visible() expect( - cart_page.shipping_details_section_component.shipping_method_selector - ).to_be_visible(), "Shipping method selector is not visible" + cart_page.shipping_details_section_component.shipping_method_selector, + "Shipping method selector is not visible", + ).to_be_visible() - if not cart_page.shipping_details_section_component.address_selector_component.selected_address_label.is_visible(): + if ( + not cart_page.shipping_details_section_component.address_selector_component.selected_address_label.is_visible() + ): cart_page.shipping_details_section_component.address_selector_component.select_address_button.click() - select_address_modal = SelectAddressModalComponent(page.locator("[data-test-id='select-address-modal']")) + select_address_modal = SelectAddressModalComponent( + page.locator("[data-test-id='select-address-modal']") + ) select_address_modal.items[0].click() select_address_modal.confirm_button.click() expect( - cart_page.shipping_details_section_component.address_selector_component.selected_address_label - ).to_be_visible(), "Selected address label is not visible" + cart_page.shipping_details_section_component.address_selector_component.selected_address_label, + "Selected address label is not visible", + ).to_be_visible() expect( - cart_page.shipping_details_section_component.address_selector_component.selected_address_label - ).not_to_be_empty(), "Selected address label is empty" + cart_page.shipping_details_section_component.address_selector_component.selected_address_label, + "Selected address label is empty", + ).not_to_be_empty() - cart_page.shipping_details_section_component.select_shipping_method("FixedRate_Ground") + cart_page.shipping_details_section_component.select_shipping_method( + "FixedRate_Ground" + ) + assert ( + cart_page.payment_details_section_component is not None + ), "Payment details section component is not found" expect( - cart_page.payment_details_section_component.element - ).to_be_visible(), "Payment details section is not visible" + cart_page.payment_details_section_component.element, + "Payment details section is not visible", + ).to_be_visible() - cart_page.payment_details_section_component.select_payment_method("DefaultManualPaymentMethod") + cart_page.payment_details_section_component.select_payment_method( + "DefaultManualPaymentMethod" + ) - expect(cart_page.place_order_button).to_be_visible(), "Place order button is not visible" - expect(cart_page.place_order_button).to_be_enabled(), "Place order button is disabled" + expect( + cart_page.place_order_button, + "Place order button is not visible", + ).to_be_visible() + expect( + cart_page.place_order_button, + "Place order button is disabled", + ).to_be_enabled() cart_page.place_order_button.click() page.wait_for_load_state("networkidle") checkout_completed_page = CheckoutCompletedPage(config, page) - expect(page).to_have_url(checkout_completed_page.url), "Checkout completed page is not loaded" + expect(page, "Checkout completed page is not loaded").to_have_url( + checkout_completed_page.url + ) assert checkout_completed_page.order_number is not None, "Order number is not found" diff --git a/tests_e2e/tests/test_e2e_filter_pickup_locations.py b/tests_e2e/tests/test_e2e_filter_pickup_locations.py index 977ef42d..8d79231f 100644 --- a/tests_e2e/tests/test_e2e_filter_pickup_locations.py +++ b/tests_e2e/tests/test_e2e_filter_pickup_locations.py @@ -40,7 +40,7 @@ def test_e2e_filter_pickup_locations_elements_single_page_checkout( auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -55,24 +55,48 @@ def test_e2e_filter_pickup_locations_elements_single_page_checkout( cart_page = CartPage(config, page) cart_page.navigate() + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component not found" + cart_page.shipping_details_section_component.pickup_delivery_option_switcher.click() cart_page.shipping_details_section_component.pickup_point_section.locator( "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) expect(select_pickup_location_modal.element).to_be_visible() expect(select_pickup_location_modal.pickup_locations_list).to_be_visible() - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) expect(select_pickup_location_modal.applied_filters_panel).to_be_visible() - expect(select_pickup_location_modal.get_applied_filter_chip_by_name(country_to_filter).element).to_be_visible() - expect(select_pickup_location_modal.get_applied_filter_chip_by_name(region_to_filter).element).to_be_visible() - expect(select_pickup_location_modal.get_applied_filter_chip_by_name(city_to_filter).element).to_be_visible() + expect( + select_pickup_location_modal.get_applied_filter_chip_by_name( + country_to_filter + ).element + ).to_be_visible() + expect( + select_pickup_location_modal.get_applied_filter_chip_by_name( + region_to_filter + ).element + ).to_be_visible() + expect( + select_pickup_location_modal.get_applied_filter_chip_by_name( + city_to_filter + ).element + ).to_be_visible() expect(select_pickup_location_modal.reset_filters_chip).to_be_visible() cart_operations.remove_cart( @@ -104,7 +128,7 @@ def test_e2e_filter_pickup_locations_country_region_keyword_found_single_page_ch user_operations = UserOperations(graphql_client) cart_operations = CartOperations(graphql_client) - product = dataset["products"][1] + product = dataset["products"][14] auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) @@ -125,15 +149,25 @@ def test_e2e_filter_pickup_locations_country_region_keyword_found_single_page_ch region_to_filter = "New York" keyword_to_search = "Empire" + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component is not found" + cart_page.shipping_details_section_component.pickup_delivery_option_switcher.click() cart_page.shipping_details_section_component.pickup_point_section.locator( "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") @@ -186,7 +220,7 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_found_single_pa city_to_filter = "Manhattan" keyword_to_search = "5th" - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -201,16 +235,28 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_found_single_pa cart_page = CartPage(config, page) cart_page.navigate() + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component is not found" + cart_page.shipping_details_section_component.pickup_delivery_option_switcher.click() cart_page.shipping_details_section_component.pickup_point_section.locator( "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") @@ -265,7 +311,7 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_not_found_singl city_to_filter = "Manhattan" keyword_to_search = "NonExistent" - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -280,16 +326,28 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_not_found_singl cart_page = CartPage(config, page) cart_page.navigate() + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component is not found" + cart_page.shipping_details_section_component.pickup_delivery_option_switcher.click() cart_page.shipping_details_section_component.pickup_point_section.locator( "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") @@ -344,7 +402,7 @@ def test_e2e_filter_pickup_locations_remove_filters_single_page_checkout( city_to_filter = "Manhattan" keyword_to_search = "Fifth" - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -359,25 +417,39 @@ def test_e2e_filter_pickup_locations_remove_filters_single_page_checkout( cart_page = CartPage(config, page) cart_page.navigate() + assert ( + cart_page.shipping_details_section_component is not None + ), "Shipping details section component is not found" + cart_page.shipping_details_section_component.pickup_delivery_option_switcher.click() cart_page.shipping_details_section_component.pickup_point_section.locator( "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) all_pickup_points_count = len(select_pickup_location_modal.pickup_locations) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") filtered_pickup_points_count = len(select_pickup_location_modal.pickup_locations) - select_pickup_location_modal.get_applied_filter_chip_by_name(region_to_filter).close_chip() + select_pickup_location_modal.get_applied_filter_chip_by_name( + region_to_filter + ).close_chip() page.wait_for_load_state("networkidle") updated_pickup_points_count = len(select_pickup_location_modal.pickup_locations) @@ -391,8 +463,12 @@ def test_e2e_filter_pickup_locations_remove_filters_single_page_checkout( restored_pickup_points_count = len(select_pickup_location_modal.pickup_locations) - assert updated_pickup_points_count == filtered_pickup_points_count, "Pickup points count not updated" - assert restored_pickup_points_count == all_pickup_points_count, "Pickup points count not restored" + assert ( + updated_pickup_points_count == filtered_pickup_points_count + ), "Pickup points count not updated" + assert ( + restored_pickup_points_count == all_pickup_points_count + ), "Pickup points count not restored" cart_operations.clear_cart( payload={ @@ -432,7 +508,7 @@ def test_e2e_filter_pickup_locations_elements_multi_step_checkout( auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -453,19 +529,39 @@ def test_e2e_filter_pickup_locations_elements_multi_step_checkout( "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) expect(select_pickup_location_modal.element).to_be_visible() expect(select_pickup_location_modal.pickup_locations_list).to_be_visible() - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) expect(select_pickup_location_modal.applied_filters_panel).to_be_visible() - expect(select_pickup_location_modal.get_applied_filter_chip_by_name(country_to_filter).element).to_be_visible() - expect(select_pickup_location_modal.get_applied_filter_chip_by_name(region_to_filter).element).to_be_visible() - expect(select_pickup_location_modal.get_applied_filter_chip_by_name(city_to_filter).element).to_be_visible() + expect( + select_pickup_location_modal.get_applied_filter_chip_by_name( + country_to_filter + ).element + ).to_be_visible() + expect( + select_pickup_location_modal.get_applied_filter_chip_by_name( + region_to_filter + ).element + ).to_be_visible() + expect( + select_pickup_location_modal.get_applied_filter_chip_by_name( + city_to_filter + ).element + ).to_be_visible() expect(select_pickup_location_modal.reset_filters_chip).to_be_visible() cart_operations.remove_cart( @@ -500,6 +596,8 @@ def test_e2e_filter_pickup_locations_country_region_keyword_found_multi_step_che user_operations = UserOperations(graphql_client) cart_operations = CartOperations(graphql_client) + product = dataset["products"][14] + auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) user = user_operations.get_me() @@ -507,7 +605,7 @@ def test_e2e_filter_pickup_locations_country_region_keyword_found_multi_step_che payload={ "storeId": config["STORE_ID"], "userId": user["id"], - "productId": "product-acme-laptop-hp-pavilion-16-ag0087nr", + "productId": product["code"], "quantity": 2, } ) @@ -525,10 +623,16 @@ def test_e2e_filter_pickup_locations_country_region_keyword_found_multi_step_che "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") @@ -584,7 +688,7 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_found_multi_ste city_to_filter = "Manhattan" keyword_to_search = "5th" - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -605,11 +709,19 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_found_multi_ste "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") @@ -667,7 +779,7 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_not_found_multi city_to_filter = "Manhattan" keyword_to_search = "NonExistent" - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -688,11 +800,19 @@ def test_e2e_filter_pickup_locations_country_region_city_keyword_not_found_multi "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") @@ -750,7 +870,7 @@ def test_e2e_filter_pickup_locations_remove_filters_multi_step_checkout( city_to_filter = "Manhattan" keyword_to_search = "Fifth" - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart( @@ -771,20 +891,30 @@ def test_e2e_filter_pickup_locations_remove_filters_multi_step_checkout( "[data-test-id='select-address-button']" ).click() - select_pickup_location_modal = SelectBopisMapModalComponent(page.locator(".select-address-map-modal")) + select_pickup_location_modal = SelectBopisMapModalComponent( + page.locator(".select-address-map-modal") + ) all_pickup_points_count = len(select_pickup_location_modal.pickup_locations) - select_pickup_location_modal.filter_countries_dropdown.select_item_by_name(country_to_filter) - select_pickup_location_modal.filter_regions_dropdown.select_item_by_name(region_to_filter) - select_pickup_location_modal.filter_cities_dropdown.select_item_by_name(city_to_filter) + select_pickup_location_modal.filter_countries_dropdown.select_item_by_name( + country_to_filter + ) + select_pickup_location_modal.filter_regions_dropdown.select_item_by_name( + region_to_filter + ) + select_pickup_location_modal.filter_cities_dropdown.select_item_by_name( + city_to_filter + ) select_pickup_location_modal.search_pickup_location_input.fill(keyword_to_search) select_pickup_location_modal.search_pickup_location_input.press("Enter") page.wait_for_load_state("networkidle") filtered_pickup_points_count = len(select_pickup_location_modal.pickup_locations) - select_pickup_location_modal.get_applied_filter_chip_by_name(region_to_filter).close_chip() + select_pickup_location_modal.get_applied_filter_chip_by_name( + region_to_filter + ).close_chip() updated_pickup_points_count = len(select_pickup_location_modal.pickup_locations) @@ -796,8 +926,12 @@ def test_e2e_filter_pickup_locations_remove_filters_multi_step_checkout( restored_pickup_points_count = len(select_pickup_location_modal.pickup_locations) - assert updated_pickup_points_count == filtered_pickup_points_count, "Pickup points count not updated" - assert restored_pickup_points_count == all_pickup_points_count, "Pickup points count not restored" + assert ( + updated_pickup_points_count == filtered_pickup_points_count + ), "Pickup points count not updated" + assert ( + restored_pickup_points_count == all_pickup_points_count + ), "Pickup points count not restored" cart_operations.remove_cart( payload={ diff --git a/tests_e2e/tests/test_e2e_merge_carts.py b/tests_e2e/tests/test_e2e_merge_carts.py index e3e4c44c..d0abb3a8 100644 --- a/tests_e2e/tests/test_e2e_merge_carts.py +++ b/tests_e2e/tests/test_e2e_merge_carts.py @@ -2,12 +2,12 @@ from typing import Any import pytest -from playwright.sync_api import Page +from playwright.sync_api import Page, expect from fixtures import Auth, Config, GraphQLClient from graphql_operations.cart.cart_operations import CartOperations from graphql_operations.user.user_operations import UserOperations -from tests_e2e.pages import CartPage, SignInPage +from tests_e2e.pages import CartPage, HomePage, SignInPage @pytest.mark.e2e @@ -25,54 +25,97 @@ def test_e2e_merge_carts( user_operations = UserOperations(graphql_client) cart_operations = CartOperations(graphql_client) - product = dataset["products"][1] + product = dataset["products"][14] quantity_to_add = 2 - auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"]) - - registered_user = user_operations.get_me() - cart_operations.clear_cart( - payload={ - "storeId": config["STORE_ID"], - "userId": registered_user["id"], - } - ) - auth.clear_token() - - page.goto(config["FRONTEND_BASE_URL"]) - page.wait_for_load_state("networkidle") - - anonymous_user_id = page.evaluate("() => localStorage.getItem('user-id')") - - cart = cart_operations.add_item_to_cart( - payload={ - "storeId": config["STORE_ID"], - "userId": anonymous_user_id, - "productId": product["code"], - "quantity": quantity_to_add, - } - ) - - sign_in_page = SignInPage(page, config) - sign_in_page.navigate() - sign_in_page.sign_in(dataset["users"][0]["userName"], config["USERS_PASSWORD"]) - - cart_page = CartPage(config, page) - cart_page.navigate() - - line_item = cart_page.get_line_item_by_sku(product["code"]) - - assert not cart_page.is_empty, "Cart is empty after sign in" - assert line_item.sku == product["code"], f"Line item sku is not equal to product sku: {product['code']}" - assert str(line_item.quantity_stepper_component.quantity_input.input_value()) == str( - quantity_to_add - ), f"Line item quantity is not equal to product quantity to add: {quantity_to_add}" - - auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"]) - - cart_operations.clear_cart( - payload={ - "storeId": config["STORE_ID"], - "userId": registered_user["id"], - } - ) + anonymous_cart = None + anonymous_user_id = None + + try: + # Step 1: Add item to cart as anonymous user + home_page = HomePage(page, config) + home_page.navigate() + + anonymous_user_id = page.evaluate("() => localStorage.getItem('user-id')") + + anonymous_cart = cart_operations.add_item_to_cart( + payload={ + "storeId": config["STORE_ID"], + "userId": anonymous_user_id, + "productId": product["code"], + "quantity": quantity_to_add, + } + ) + + page.reload() + + expect( + home_page.cart_items_badge, "Cart items badge is not visible" + ).to_be_visible() + + # Step 2: Sign in as registered user (triggers cart merge) + sign_in_page = SignInPage(page, config) + sign_in_page.navigate() + sign_in_page.sign_in(dataset["users"][0]["userName"], config["USERS_PASSWORD"]) + + # Step 3: Navigate to cart and verify anonymous cart was merged + cart_page = CartPage(config, page) + cart_page.navigate() + + assert not cart_page.is_empty, "Cart is empty after sign in - anonymous cart was not merged" + + line_item = cart_page.get_line_item_by_sku(product["code"]) + + assert line_item is not None, ( + f"Product {product['code']} not found in cart after merge" + ) + assert line_item.sku == product["code"], ( + f"Line item SKU mismatch: expected {product['code']}, got {line_item.sku}" + ) + assert str(line_item.quantity_stepper_component.quantity_input.input_value()) == str( + quantity_to_add + ), f"Line item quantity mismatch: expected {quantity_to_add}" + + finally: + # Teardown: Remove anonymous cart and registered user's cart + registered_user = None + registered_user_cart = None + + try: + auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"]) + registered_user = user_operations.get_me() + registered_user_cart = cart_operations.get_cart( + store_id=config["STORE_ID"], + user_id=registered_user["id"], + currency_code="USD", + culture_name="en-US", + ) + auth.clear_token() + except Exception: + pass + + auth.authenticate(config["ADMIN_USERNAME"], config["ADMIN_PASSWORD"]) + + if anonymous_cart and anonymous_user_id: + try: + cart_operations.remove_cart( + payload={ + "cartId": anonymous_cart["id"], + "userId": anonymous_user_id, + } + ) + except Exception: + pass # Anonymous cart may already be consumed by merge + + if registered_user_cart and registered_user: + try: + cart_operations.remove_cart( + payload={ + "cartId": registered_user_cart["id"], + "userId": registered_user["id"], + } + ) + except Exception: + pass + + auth.clear_token() From c39adbefcccaee0ef54c81369744e7d2621478bd Mon Sep 17 00:00:00 2001 From: Andrew Orlov Date: Tue, 17 Feb 2026 11:21:45 +0200 Subject: [PATCH 24/24] fix: e2e test --- tests_e2e/tests/test_e2e_shipping_cost.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests_e2e/tests/test_e2e_shipping_cost.py b/tests_e2e/tests/test_e2e_shipping_cost.py index 3bfcbef5..8a0a3b78 100644 --- a/tests_e2e/tests/test_e2e_shipping_cost.py +++ b/tests_e2e/tests/test_e2e_shipping_cost.py @@ -7,7 +7,7 @@ from fixtures import Auth, Config, GraphQLClient from graphql_operations.cart.cart_operations import CartOperations from graphql_operations.user.user_operations import UserOperations -from tests_e2e.pages import CartPage, CheckoutShippingPage +from tests_e2e.pages import CartPage, CategoryPage, CheckoutShippingPage @pytest.mark.e2e @@ -33,7 +33,7 @@ def test_e2e_shipping_cost_single_page_checkout( user_operations = UserOperations(graphql_client) cart_operations = CartOperations(graphql_client) - product = dataset["products"][1] + product = dataset["products"][14] auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) @@ -127,7 +127,7 @@ def test_e2e_shipping_cost_multi_step_checkout( auth.authenticate(dataset["users"][0]["userName"], config["USERS_PASSWORD"], page) - product = dataset["products"][1] + product = dataset["products"][14] user = user_operations.get_me() cart = cart_operations.add_item_to_cart(