Skip to content

Commit

Permalink
#938 #939 test order page remove goal (#943)
Browse files Browse the repository at this point in the history
* Create Positions selenium element

* Reuse Positions for Cart class

* Create OrderPositions class

* Adopt classes to Positions

* Implement OrderPage class

* Create YandexEcommerce.remove_from_order_page test. Reduce code duplication

* Apply linter rules

* Fix typo

* Review fixes

* Edit todo to fix assertion for cart clear goal

* Apply linter rules
  • Loading branch information
ArtemijRodionov authored Jul 12, 2019
1 parent db12eb7 commit 4ddb192
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 143 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ database/
.DS_store
*.orig
venv/
.sublime-project
.sublime-workspace
*.sublime-project
*.sublime-workspace

3 changes: 3 additions & 0 deletions shopelectro/selenium/analytics_goals.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def __iter__(self):
def __getitem__(self, index: int):
raise NotImplementedError

def __bool__(self):
return bool(list(self))


class YandexEcommerceGoals(Goals): # Ignore PyDocStyleBear
"""
Expand Down
5 changes: 3 additions & 2 deletions shopelectro/selenium/elements/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .button import Button
from .exceptions import Unavailable
from .button import Button
from .input import Input
from .product import CatalogCard, ProductCard, CartPosition
from .product import *
from .cart import Cart
from .positions import Positions
38 changes: 7 additions & 31 deletions shopelectro/selenium/elements/cart.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from contextlib import contextmanager

from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions as EC
Expand All @@ -15,6 +12,11 @@ class Cart:

def __init__(self, driver: SiteDriver):
self.driver = driver
self.positions = elements.Positions(
driver,
elements.CartPosition,
(By.CLASS_NAME, 'basket-item'),
)

def _hover(self):
cart = self.driver.wait.until(EC.visibility_of_element_located(
Expand All @@ -25,36 +27,10 @@ def _hover(self):
(By.CLASS_NAME, 'js-cart-wrapper')
))

# @todo #920:15m Document the Cart.wait_changes.
# Cover corner cases with TimeoutException.

@contextmanager
def wait_changes(self):
def wait_changes(browser):
try:
return positions_before != self.positions()
except TimeoutException:
return False

positions_before = self.positions()
yield
self.driver.wait.until(wait_changes)

def positions(self) -> [elements.CartPosition]:
try:
# use short_wait to avoid long pauses in case of the empty cart
positions_count = len(self.driver.short_wait.until(EC.presence_of_all_elements_located(
(By.CLASS_NAME, 'basket-item')
)))
except TimeoutException:
positions_count = 0

return [elements.CartPosition(self.driver, i) for i in range(positions_count)]

def remove(self, position: elements.CartPosition):
with self.wait_changes():
with self.positions.wait_changes():
self._hover()
position.remove_from_cart()
position.remove()

def clear(self):
self._hover()
Expand Down
45 changes: 45 additions & 0 deletions shopelectro/selenium/elements/positions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from contextlib import contextmanager

from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support import expected_conditions as EC

from shopelectro.selenium import SiteDriver, elements


class Positions:

def __init__(self, driver: SiteDriver, position_type: elements.Product, locator):
self.driver = driver
self.position_type = position_type
self.condition = EC.presence_of_all_elements_located(locator)

@contextmanager
def wait_changes(self):
def are_changed(_):
try:
return positions_before != self.all()
except TimeoutException:
"""
An exception can be raised from a position's equality method.
In most cases this means that some positions are stale,
so we continue waiting changes.
"""
return False

positions_before = self.all()
yield
self.driver.wait.until(are_changed)

def first(self) -> elements.Product:
return self.position_type(self.driver, 0)

def all(self) -> [elements.Product]:
try:
# use short_wait to avoid long pauses in case of the empty cart
positions_count = len(self.driver.short_wait.until(
self.condition
))
except TimeoutException:
positions_count = 0

return [self.position_type(self.driver, i) for i in range(positions_count)]
57 changes: 49 additions & 8 deletions shopelectro/selenium/elements/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ def price(self):
def quantity(self):
raise Unavailable('determine the product quantity.')

def add_to_cart(self):
def add(self):
raise Unavailable('add the product to the card.')

def remove_from_cart(self):
def remove(self):
raise Unavailable('remove the product from the card.')

def __hash__(self):
raise NotImplementedError('Provide __hash__ implementation for the class.')

def __eq__(self, other: 'Product'):
return hash(self) == hash(other)


class CatalogCard(Product):

Expand Down Expand Up @@ -79,7 +85,7 @@ def vendor_code(self):
(By.XPATH, self._build_xpath('div[2]/div[1]'))
)).text.split(' ')[1]

def add_to_cart(self):
def add(self):
Button(self.driver, (By.XPATH, self._build_xpath('div[2]/div[5]/button'))).click()


Expand All @@ -88,7 +94,7 @@ class ProductCard(Product):
def __init__(self, driver: SiteDriver):
self.driver = driver

def add_to_cart(self):
def add(self):
Button(self.driver, (By.CLASS_NAME, 'js-to-cart-on-product-page')).click()


Expand All @@ -107,9 +113,6 @@ def __hash__(self):
+ el.get_attribute('data-product-count')
)

def __eq__(self, other: 'CartPosition'):
return hash(self) == hash(other)

def _data_element(self):
# use short_wait, because a position could be stale
return self.driver.short_wait.until(EC.presence_of_element_located(
Expand All @@ -125,5 +128,43 @@ def price(self):
def quantity(self):
return self._data_element().get_attribute('data-product-count')

def remove_from_cart(self):
def remove(self):
Button(self.driver, (By.XPATH, f'{self.xpath}i')).click()


class OrderPosition(Product):
"""Represent a product position on order page."""

def __init__(self, driver: SiteDriver, index: int):
self.driver = driver
# xpath indexes starts from 1
self.xpath = f'//div[@id="js-order-list"]/div[2]/div[{index + 1}]/'

def __hash__(self):
return hash(
self.vendor_code()
+ '/'
+ self.quantity()
)

def vendor_code(self):
return self.driver.short_wait.until(EC.visibility_of_element_located(
(By.XPATH, f'{self.xpath}div[1]')
)).text

def quantity(self):
return self.driver.short_wait.until(EC.visibility_of_element_located(
(By.XPATH, f'{self.xpath}//input')
)).value

def set(self, quantity: int):
raise NotImplementedError

def increase(self, times=1):
raise NotImplementedError

def decrease(self, times=1):
raise NotImplementedError

def remove(self):
Button(self.driver, (By.XPATH, f'{self.xpath}div[6]/div')).click()
4 changes: 2 additions & 2 deletions shopelectro/selenium/pages/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ def add_to_cart(self, products: typing.List[elements.CatalogCard] = None):
default = [elements.CatalogCard.with_index(self.driver, i) for i in range(6)]
products = products or default

with self.cart().wait_changes():
with self.cart().positions.wait_changes():
for product in products:
product.add_to_cart()
product.add()
36 changes: 28 additions & 8 deletions shopelectro/selenium/pages/order.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
from shopelectro.models import PaymentOptions
from shopelectro.selenium.elements import Input, Button
from shopelectro.selenium.pages import Page

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

from pages.models import CustomPage
from shopelectro.models import PaymentOptions
from shopelectro.selenium import elements, SiteDriver
from shopelectro.selenium.pages import Page

# @todo #682:120m Implement and reuse shopelectro.selenium.OrderPage for selenium tests.


class OrderPage(Page):

def __init__(self, driver):
def __init__(self, driver: SiteDriver):
super().__init__(driver)
self.submit_button = Button(self.driver, (By.ID, 'submit-order'))
self.submit_button = elements.Button(self.driver, (By.ID, 'submit-order'))
self.positions = elements.Positions(
driver,
elements.OrderPosition,
(By.XPATH, '//div[@id="js-order-list"]/div[2]/div'),
)

@property
def path(self):
return CustomPage.objects.get(slug='order').url

def set(self, position: elements.OrderPosition, quantity: int):
with self.positions.wait_changes():
position.set(quantity)

def increase(self, position: elements.OrderPosition):
with self.positions.wait_changes():
position.increase()

def decrease(self, position: elements.OrderPosition):
with self.positions.wait_changes():
position.decrease()

def remove(self, position: elements.OrderPosition):
with self.positions.wait_changes():
position.remove()

def fill_contacts(
self, name='Name', city='Санкт-Петербург', phone='2222222222', email='[email protected]',
):
Expand All @@ -31,7 +51,7 @@ def fill_contacts(
}

for id_, value in contacts.items():
Input(self.driver, (By.ID, id_)).send_keys(value)
elements.Input(self.driver, (By.ID, id_)).send_keys(value)

def make_order(self):
self.submit_button.click()
Expand All @@ -44,7 +64,7 @@ def select_payment_type(self, payment_option: PaymentOptions):
f'It should be one of: {PaymentOptions}'
)

item = Button(
item = elements.Button(
self.driver,
(By.CSS, f'input[name="payment_type"][value="{payment_option.name}"]'),
)
Expand Down
4 changes: 2 additions & 2 deletions shopelectro/selenium/pages/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ def path(self):
return reverse('product', args=(self.vendor_code,))

def add_to_cart(self):
with self.cart().wait_changes():
elements.ProductCard(self.driver).add_to_cart()
with self.cart().positions.wait_changes():
elements.ProductCard(self.driver).add()
Loading

3 comments on commit 4ddb192

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 4ddb192 Jul 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 920-cb7dcfc8 disappeared from shopelectro/selenium/elements/cart.py, that's why I closed #938. Please, remember that the puzzle was not necessarily removed in this particular commit. Maybe it happened earlier, but we discovered this fact only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 4ddb192 Jul 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 820-7b702e9a disappeared from shopelectro/tests/tests_js_analytics.py, that's why I closed #939. Please, remember that the puzzle was not necessarily removed in this particular commit. Maybe it happened earlier, but we discovered this fact only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on 4ddb192 Jul 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 939-a904eb4b discovered in shopelectro/tests/tests_js_analytics.py and submitted as #944. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

Please sign in to comment.