diff --git a/shopelectro/selenium/__init__.py b/shopelectro/selenium/__init__.py index 9bfee511..d90dc6b2 100644 --- a/shopelectro/selenium/__init__.py +++ b/shopelectro/selenium/__init__.py @@ -13,4 +13,4 @@ """ from .driver import SiteDriver -from .pages import CategoryPage, OrderPage, SuccessPage +from .pages import * diff --git a/shopelectro/selenium/elements/__init__.py b/shopelectro/selenium/elements/__init__.py index efa2973b..004c04ed 100644 --- a/shopelectro/selenium/elements/__init__.py +++ b/shopelectro/selenium/elements/__init__.py @@ -1,3 +1,4 @@ -from .input import Input from .button import Button -from .product_card import ProductCard +from .exceptions import Unavailable +from .input import Input +from .product import CatalogCard, CartPosition diff --git a/shopelectro/selenium/elements/cart.py b/shopelectro/selenium/elements/cart.py new file mode 100644 index 00000000..e17ec154 --- /dev/null +++ b/shopelectro/selenium/elements/cart.py @@ -0,0 +1,23 @@ +from shopelectro.selenium import SiteDriver +from shopelectro.selenium.elements import Unavailable + +# @todo #799:120m Reuse shopelectro.selenium.elements.cart.Cart for selenium tests. + + +class Cart: + """"Represent the cart at the site.""" + + def __init__(self, driver: SiteDriver): + self.driver = driver + + def positions(self): + raise Unavailable('get positions from cart.') + + def clear(self): + raise Unavailable('clear cart.') + + def total(self): + raise Unavailable('get total count of positions from cart.') + + def is_empty(self): + raise Unavailable('determine emptiness of cart.') diff --git a/shopelectro/selenium/elements/exceptions.py b/shopelectro/selenium/elements/exceptions.py new file mode 100644 index 00000000..a87ed009 --- /dev/null +++ b/shopelectro/selenium/elements/exceptions.py @@ -0,0 +1,4 @@ +class Unavailable(NotImplementedError): + + def __init__(self, msg, *args, **kwargs): + super().__init__(f'The element doesn\'t provide ability to {msg}', *args, **kwargs) diff --git a/shopelectro/selenium/elements/product.py b/shopelectro/selenium/elements/product.py new file mode 100644 index 00000000..8f315048 --- /dev/null +++ b/shopelectro/selenium/elements/product.py @@ -0,0 +1,61 @@ +import abc + +from shopelectro.selenium.elements import Button, Unavailable +from shopelectro.selenium.driver import SiteDriver + +from selenium.webdriver.common.by import By + + +class Product(abc.ABC): + + def name(self): + raise Unavailable('determine the product name.') + + def price(self): + raise Unavailable('determine the product price.') + + def quantity(self): + raise Unavailable('determine the product quantity.') + + def add_to_cart(self): + raise Unavailable('add the product to the card.') + + def remove_from_cart(self): + raise Unavailable('remove the product from the card.') + + +class CatalogCard(Product): + + def __init__(self, driver: SiteDriver, card_index: int): + """ + Ctor. + + :param int card_index: The index number of the product card at a category page + """ + self.driver = driver + self.button = Button( + self.driver, + (By.XPATH, f'//*[@id="products-wrapper"]/div[{card_index}]/div[2]/div[5]/button') + ) + + def add_to_cart(self): + self.button.click() + + +class CartPosition(Product): + + def __init__(self, driver: SiteDriver, pos_index: int): + self.driver = driver + self.xpath = f'//ul[@id="basket-list"]/li[{pos_index}]/' + + def name(self): + raise Unavailable('determine the position name.') + + def price(self): + raise Unavailable('determine the position price.') + + def quantity(self): + raise Unavailable('determine the position quantity.') + + def remove_from_cart(self): + raise Unavailable('remove the position from the card.') diff --git a/shopelectro/selenium/elements/product_card.py b/shopelectro/selenium/elements/product_card.py deleted file mode 100644 index 8c79233f..00000000 --- a/shopelectro/selenium/elements/product_card.py +++ /dev/null @@ -1,23 +0,0 @@ -from shopelectro.selenium.elements import Button -from shopelectro.selenium.driver import SiteDriver - -from selenium.webdriver.common.by import By - - -class ProductCard: - """"Represent a product card from category page.""" - - def __init__(self, driver: SiteDriver, card_index: int): - """ - Ctor. - - :param int card_index: The index number of the product card at a category page - """ - self.driver = driver - self.button = Button( - self.driver, - (By.XPATH, f'//*[@id="products-wrapper"]/div[{card_index}]/div[2]/div[5]/button') - ) - - def add_to_cart(self): - self.button.click() diff --git a/shopelectro/selenium/pages/__init__.py b/shopelectro/selenium/pages/__init__.py index c3812923..f0fbe883 100644 --- a/shopelectro/selenium/pages/__init__.py +++ b/shopelectro/selenium/pages/__init__.py @@ -2,4 +2,5 @@ from .category import CategoryPage from .order import OrderPage +from .product import Product from .success import SuccessPage diff --git a/shopelectro/selenium/pages/category.py b/shopelectro/selenium/pages/category.py index b7e9c9d7..e40a70df 100644 --- a/shopelectro/selenium/pages/category.py +++ b/shopelectro/selenium/pages/category.py @@ -1,6 +1,6 @@ import typing -from shopelectro.selenium.elements import ProductCard +from shopelectro.selenium.elements import CatalogCard from shopelectro.selenium.pages import Page from django.urls import reverse @@ -18,11 +18,11 @@ def __init__(self, driver, slug): def path(self): return reverse('category', args=(self.slug,)) - def product_cards(self) -> typing.List[ProductCard]: + def product_cards(self) -> typing.List[CatalogCard]: raise NotImplementedError - def add_to_cart(self, product_cards: typing.List[ProductCard]=None): - default_cards = [ProductCard(self.driver, i) for i in range(1, 7)] - product_cards = product_cards or default_cards - for card in product_cards: - card.add_to_cart() + def add_to_cart(self, products: typing.List[CatalogCard]=None): + default = [CatalogCard(self.driver, i) for i in range(1, 7)] + products = products or default + for product in products: + product.add_to_cart() diff --git a/shopelectro/selenium/pages/product.py b/shopelectro/selenium/pages/product.py new file mode 100644 index 00000000..14268d50 --- /dev/null +++ b/shopelectro/selenium/pages/product.py @@ -0,0 +1,17 @@ +from django.urls import reverse + +from shopelectro.selenium.pages import Page + + +# @todo #682:120m Implement and reuse shopelectro.selenium.Product for selenium tests. + + +class Product(Page): + + def __init__(self, driver, vendor_code): + super().__init__(driver) + self.vendor_code = vendor_code + + @property + def path(self): + return reverse('product', args=(self.vendor_code,)) diff --git a/shopelectro/tests/tests_js_analytics.py b/shopelectro/tests/tests_js_analytics.py index 0d76ba44..3faf75b8 100644 --- a/shopelectro/tests/tests_js_analytics.py +++ b/shopelectro/tests/tests_js_analytics.py @@ -79,29 +79,41 @@ def test_purchase(self): @helpers.disable_celery @override_settings(DEBUG=True, INTERNAL_IPS=tuple()) class YandexEcommerce(Ecommerce): + """ + Test reaching of goals. + + Match submitted results with Yandex spec for message representation. + Docs with spec: https://yandex.com/support/metrica/data/e-commerce.html + """ fixtures = ['dump.json'] - # @todo #785:60m Test Yandex ecommerce goals. + # @todo #799:120m Test Yandex ecommerce goals. # Here are goals left to test: # - onCartClear from cart # - onProductAdd from catalog, product and order pages # - onProductRemove from cart and order page - # - onProductDetail from product page + @property def reached_goals(self): return self.browser.execute_script('return window.dataLayer.results;') + def get_first(self, reached): + """Get the first reached goal and unfold it.""" + return reached[0][0]['ecommerce'] + def test_purchase(self): self.buy() order = self.last_order() positions = order.positions.all() - reached = self.reached_goals()[0][0] - self.assertIn('ecommerce', reached) - self.assertEqual(reached['ecommerce']['currencyCode'], 'RUB') + reached_goals = self.reached_goals + self.assertTrue(reached_goals) - reached_purchase = reached['ecommerce']['purchase'] + reached = self.get_first(reached_goals) + self.assertEqual(reached['currencyCode'], 'RUB') + + reached_purchase = reached['purchase'] self.assertEqual( reached_purchase['actionField'], {'id': order.fake_order_number, 'revenue': order.revenue}, @@ -117,6 +129,32 @@ def test_purchase(self): }, ) + def test_product_detail(self): + product = Product.objects.first() + selenium.Product(self.browser, product.vendor_code).load() + + reached_goals = self.reached_goals + self.assertTrue(reached_goals) + + reached = self.get_first(reached_goals) + self.assertEqual(reached['currencyCode'], 'RUB') + self.assertEqual( + len(reached['detail']['products']), + 1, + ) + + product_detail = reached['detail']['products'][0] + self.assertEqual( + product_detail, + { + 'id': product.id, + 'name': product.name, + 'brand': product.get_brand_name(), + 'quantity': 1, + 'category': product.category.name, + } + ) + @tag('slow') @helpers.disable_celery diff --git a/shopelectro/tests/tests_selenium.py b/shopelectro/tests/tests_selenium.py index 979a6cb6..fd995e4d 100644 --- a/shopelectro/tests/tests_selenium.py +++ b/shopelectro/tests/tests_selenium.py @@ -21,9 +21,6 @@ from pages.models import FlatPage, CustomPage from pages.urls import reverse_custom_page -# @todo #783:60m Create Cart class for tests. -# The class should replace batch of methods from this file. - def add_to_cart(browser): browser.get(Product.objects.first().url)