Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions page_objects/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .account_button import AccountButton
from .account_menu import AccountMenu
from .add_or_update_wishlist_modal import AddOrUpdateWishlistModal
from .add_to_cart_button import AddToCartButton
from .add_to_wishlists_modal import AddToWishlistsModal
from .address import Address
from .address_form import AddressForm
from .category_view_switcher import CategoryViewSwitcher
Expand All @@ -9,6 +11,7 @@
from .clear_cart_modal import ClearCartModal
from .component import Component
from .currency_selector import CurrencySelector
from .delete_wishlist_modal import DeleteWishlistModal
from .dropdown_filter import DropdownFilter
from .edit_address_modal import EditAddressModal
from .language_selector import LanguageSelector
Expand All @@ -26,11 +29,14 @@
from .shipping_details_section import ShippingDetailsSection
from .slider_filter import SliderFilter
from .top_header import TopHeader
from .wishlist_card import WishlistCard

__all__ = [
"AccountButton",
"AccountMenu",
"AddOrUpdateWishlistModal",
"AddToCartButton",
"AddToWishlistsModal",
"Address",
"AddressForm",
"CategoryViewSwitcher",
Expand All @@ -39,6 +45,7 @@
"ClearCartModal",
"Component",
"CurrencySelector",
"DeleteWishlistModal",
"DropdownFilter",
"EditAddressModal",
"LanguageSelector",
Expand All @@ -56,4 +63,5 @@
"ShippingDetailsSection",
"SliderFilter",
"TopHeader",
"WishlistCard",
]
26 changes: 26 additions & 0 deletions page_objects/components/add_or_update_wishlist_modal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from playwright.sync_api import Locator

from .component import Component


class AddOrUpdateWishlistModal(Component):
@property
def name_input(self) -> Locator:
return self._root.locator("[data-test-id='wishlist-name-input']")

@property
def description_input(self) -> Locator:
return self._root.locator("[data-test-id='wishlist-description-input'] textarea")

@property
def sharing_scope_select(self) -> Locator:
return self._root.locator("[data-test-id='wishlist-sharing-scope-select']")

@property
def save_button(self) -> Locator:
return self._root.locator("[data-test-id='wishlist-settings-save-button']")

def select_scope(self, label: str) -> None:
self.sharing_scope_select.click()
# The dropdown option is rendered in a portal outside the modal root.
self._root.page.get_by_role("option", name=label).click()
15 changes: 15 additions & 0 deletions page_objects/components/add_to_wishlists_modal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from playwright.sync_api import Locator

from .component import Component


class AddToWishlistsModal(Component):
@property
def save_button(self) -> Locator:
return self._root.locator("[data-test-id='wishlist-modal-save-button']")

def list_checkbox(self, list_id: str) -> Locator:
return self._root.locator(f"[data-test-id='wishlist-modal-list-checkbox-{list_id}']")

def list_with_product_checkbox(self, list_id: str) -> Locator:
return self._root.locator(f"[data-test-id='wishlist-modal-list-with-product-checkbox-{list_id}']")
9 changes: 9 additions & 0 deletions page_objects/components/delete_wishlist_modal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from playwright.sync_api import Locator

from .component import Component


class DeleteWishlistModal(Component):
@property
def delete_button(self) -> Locator:
return self._root.locator("[data-test-id='delete-button']")
4 changes: 4 additions & 0 deletions page_objects/components/product_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def add_to_cart_button(self) -> AddToCartButton:
root=self._root.locator("[data-test-id='add-to-cart-button']")
)

@property
def add_to_list_button(self) -> Locator:
return self._root.locator("[data-test-id='add-to-list-button']")

@property
def variations_button(self) -> Locator:
return self._root.locator(
Expand Down
19 changes: 19 additions & 0 deletions page_objects/components/wishlist_card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from playwright.sync_api import Locator

from .component import Component


class WishlistCard(Component):
@property
def menu_button(self) -> Locator:
return self._root.locator("[data-test-id='wishlist-card-menu-button']")

# Menu items are rendered in a portal outside the card root; the :visible
# filter narrows to the menu of the card that was just opened.
@property
def edit_menu_item(self) -> Locator:
return self._root.page.locator("[data-test-id='wishlist-card-edit-menu-item'] button:visible")

@property
def remove_menu_item(self) -> Locator:
return self._root.page.locator("[data-test-id='wishlist-card-remove-menu-item'] button:visible")
6 changes: 6 additions & 0 deletions page_objects/pages/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .account_list_details import AccountListDetailsPage
from .account_lists import AccountListsPage
from .account_saved_for_later import AccountSavedForLaterPage
from .cart import CartPage
from .category import CategoryPage
Expand All @@ -6,9 +8,12 @@
from .checkout_review_order import CheckoutReviewOrderPage
from .checkout_shipping import CheckoutShippingPage
from .home import HomePage
from .product import ProductPage
from .sign_in import SignInPage

__all__ = [
"AccountListDetailsPage",
"AccountListsPage",
"AccountSavedForLaterPage",
"CartPage",
"CategoryPage",
Expand All @@ -17,5 +22,6 @@
"CheckoutReviewOrderPage",
"CheckoutShippingPage",
"HomePage",
"ProductPage",
"SignInPage",
]
34 changes: 34 additions & 0 deletions page_objects/pages/account_list_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from playwright.sync_api import Locator, Page

from core.global_settings import GlobalSettings
from page_objects.components.line_item import LineItem
from page_objects.layouts.main import MainLayout


class AccountListDetailsPage(MainLayout):
def __init__(
self,
global_settings: GlobalSettings,
page: Page,
list_id: str,
) -> None:
super().__init__(global_settings=global_settings, page=page)
self._list_id = list_id

@property
def url(self) -> str:
return f"{self._global_settings.frontend_base_url}/account/lists/{self._list_id}"

@property
def line_items(self) -> Locator:
return self._page.locator("[data-product-sku]")

@property
def add_all_to_cart_button(self) -> Locator:
return self._page.locator("[data-test-id='add-all-to-cart-button']")

def find_line_item(self, sku: str) -> LineItem:
return LineItem(root=self._page.locator(f"[data-product-sku='{sku}']"))

def navigate(self) -> None:
self._page.goto(url=self.url, wait_until="load")
36 changes: 36 additions & 0 deletions page_objects/pages/account_lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from playwright.sync_api import Locator

from page_objects.components.add_or_update_wishlist_modal import (
AddOrUpdateWishlistModal,
)
from page_objects.components.delete_wishlist_modal import DeleteWishlistModal
from page_objects.components.wishlist_card import WishlistCard
from page_objects.layouts.main import MainLayout


class AccountListsPage(MainLayout):
@property
def url(self) -> str:
return f"{self._global_settings.frontend_base_url}/account/lists"

@property
def create_list_button(self) -> Locator:
return self.root.locator("[data-test-id='create-wishlist-button']")

@property
def cards(self) -> Locator:
return self._page.locator("[data-test-id='wishlist-card']")

@property
def settings_modal(self) -> AddOrUpdateWishlistModal:
return AddOrUpdateWishlistModal(root=self._page.locator("[data-test-id='add-or-update-wishlist-modal']"))

@property
def delete_modal(self) -> DeleteWishlistModal:
return DeleteWishlistModal(root=self._page.locator("[data-test-id='delete-wishlist-modal']"))

def find_card(self, name: str) -> WishlistCard:
return WishlistCard(root=self.cards.filter(has_text=name).first)

def navigate(self) -> None:
self._page.goto(url=self.url, wait_until="load")
26 changes: 26 additions & 0 deletions page_objects/pages/product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from playwright.sync_api import Locator, Page

from core.global_settings import GlobalSettings
from page_objects.layouts.main import MainLayout


class ProductPage(MainLayout):
def __init__(
self,
global_settings: GlobalSettings,
page: Page,
product_id: str,
) -> None:
super().__init__(global_settings=global_settings, page=page)
self._product_id = product_id

@property
def url(self) -> str:
return f"{self._global_settings.frontend_base_url}/product/{self._product_id}"

@property
def add_to_list_button(self) -> Locator:
return self.root.locator("[data-test-id='add-to-list-button']")

def navigate(self) -> None:
self._page.goto(url=self.url, wait_until="load")
105 changes: 105 additions & 0 deletions tests/e2e/test_wishlist_add_all_to_cart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from uuid import uuid4

import allure
import pytest
from core.clients import GraphQLClient
from core.global_settings import GlobalSettings
from gql.operations import CartOperations, ShoppingListOperations
from gql.types.cart_item_input import CartItemInput
from page_objects.pages import AccountListDetailsPage, CartPage
from playwright.sync_api import Page, Response, expect
from tests.context import Context
from utils.polling_utils import poll_until

_USERNAME = "acme_store_employee_1@acme.com"
_PHYSICAL_PRODUCT_ID = "smartphone-samsung-galaxy-a57-5g"
_PHYSICAL_PRODUCT_SKU = "smartphone-samsung-galaxy-a57-5g"
_VARIATION_PRODUCT_ID = "smartphone-google-pixel-10-indigo"
_VARIATION_PRODUCT_SKU = "smartphone-google-pixel-10-indigo"


def _is_cart_from_wishlist_mutation(response: Response) -> bool:
if "/graphql" not in response.url:
return False
post = (response.request.post_data or "").lower()
return "mutation" in post and "cart" in post


@pytest.mark.e2e
@pytest.mark.with_user(_USERNAME)
@allure.feature("Wishlist / Add products to cart (E2E)")
@allure.title("Add all wishlist products to cart from list details")
def test_wishlist_add_all_products_to_cart(
page: Page,
global_settings: GlobalSettings,
graphql_client: GraphQLClient,
ctx: Context,
) -> None:
ops = ShoppingListOperations(client=graphql_client)
cart_ops = CartOperations(client=graphql_client)
wishlist = ops.create_shopping_list(
store_id=ctx.store_id,
user_id=ctx.user_id,
name=f"E2E WL Cart {uuid4().hex[:6]}",
currency_code=ctx.currency_code,
culture_name=ctx.culture_name,
description="Created by wishlist add-to-cart E2E flow",
)
try:
expected_skus = {_PHYSICAL_PRODUCT_SKU, _VARIATION_PRODUCT_SKU}
ops.add_items_to_shopping_list(
list_id=wishlist.id,
items=[
CartItemInput(product_id=_PHYSICAL_PRODUCT_ID, quantity=1),
CartItemInput(product_id=_VARIATION_PRODUCT_ID, quantity=1),
],
)
seeded = poll_until(
fetch=lambda: ops.get_shopping_list(list_id=wishlist.id, culture_name=ctx.culture_name),
predicate=lambda wl: expected_skus.issubset({item.sku for item in wl.items}),
attempts=global_settings.poll_attempts,
interval=global_settings.poll_interval,
)
assert seeded is not None, "Seeded products did not appear in wishlist"

with allure.step("Open wishlist details and verify seeded products are shown"):
details_page = AccountListDetailsPage(global_settings=global_settings, page=page, list_id=wishlist.id)
details_page.navigate()
expect(details_page.line_items).to_have_count(2)
expect(details_page.find_line_item(_PHYSICAL_PRODUCT_SKU).root).to_be_visible()
expect(details_page.find_line_item(_VARIATION_PRODUCT_SKU).root).to_be_visible()

with allure.step("Add all wishlist products to cart and verify cart contents"):
with page.expect_response(_is_cart_from_wishlist_mutation):
details_page.add_all_to_cart_button.click()
expect(details_page.cart_quantity_label).to_have_text("2")

cart_page = CartPage(global_settings=global_settings, page=page)
cart_page.navigate()
expect(cart_page.line_items).to_have_count(2)
expect(cart_page.find_line_item(_PHYSICAL_PRODUCT_SKU).root).to_be_visible()
expect(cart_page.find_line_item(_VARIATION_PRODUCT_SKU).root).to_be_visible()
finally:
try:
ops.delete_shopping_list(list_id=wishlist.id)
except Exception as exc:
allure.attach(
f"Teardown of wishlist {wishlist.id} skipped: {exc}",
name=f"wishlist-teardown-{wishlist.id}",
attachment_type=allure.attachment_type.TEXT,
)
try:
cart = cart_ops.get_cart(
store_id=ctx.store_id,
user_id=ctx.user_id,
currency_code=ctx.currency_code,
culture_name=ctx.culture_name,
)
if cart:
cart_ops.delete_cart(cart_id=cart.id, user_id=ctx.user_id)
except Exception as exc:
allure.attach(
f"Cart teardown skipped: {exc}",
name="cart-teardown",
attachment_type=allure.attachment_type.TEXT,
)
Loading