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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
.idea
68 changes: 68 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from urls.urls import REGISTER_URL, LOGIN_URL, BASE_URL
from faker import Faker

ANY_INPUT_LOCATOR = ".//main//form//input"
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Нужно исправить: здесь и далее: использование в локаторе порядкового номера элемента в dom, поиск от html, root, main и абсолютного пути порождает нестабильность локаторов (необходимо привязываться к атрибутам конкретного элемента)

EMAIL_OR_NAME_LOCATOR = ".//main//form//input[@name='name']"
PASSWORD_LOCATOR = ".//main//form//input[@name='Пароль']"
REGISTER_BUTTON_LOCATOR = ".//main//form/button"
EMAIL_LOCATOR = ".//main//form//input[@name='name']"
LOGIN_BUTTON_LOCATOR = ".//button[text()='Войти']"
Comment on lines +9 to +14
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Нужно исправить: здесь и далее: локаторы должны быть записаны в переменные и хранится отдельно от тестов в модуле locators для удобства дальнейшего переиспользования.



@pytest.fixture()
def random_email():
return Faker().email()


@pytest.fixture()
def random_password():
return Faker().password()
Comment on lines +17 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Нужно исправить: фикстуры не занимаются прокидыванием данных в тест и выполнением примитивной логики, они выполняют сложную логику предусловий\постусловий. Эти методы необходимо описать в модуле helpers и оттуда вызывать в тестах и методах



@pytest.fixture(params=["chrome"])
def driver(request):
browser = request.param
if browser == "chrome":
driver = webdriver.Chrome()
elif browser == "firefox":
driver = webdriver.Firefox()
else:
raise ValueError()
yield driver
driver.quit()


@pytest.fixture()
def driver_registered(driver, random_email, random_password):
driver.get(REGISTER_URL)
WebDriverWait(driver, 3).until(
Copy link
Copy Markdown
Owner Author

@ShamilovR ShamilovR Jan 12, 2025

Choose a reason for hiding this comment

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

Нужно исправить: здесь и далее: в тестах, фисктурах и методах пейджей не должно быть обращений к драйверу и использований WebdriverWait. Все подобные реализации должны находиться в рамках базового класса пейджей.

expected_conditions.visibility_of_element_located((By.XPATH, ANY_INPUT_LOCATOR)))
for input_element in driver.find_elements(By.XPATH, EMAIL_OR_NAME_LOCATOR):
input_element.send_keys(random_email)
driver.find_element(By.XPATH, PASSWORD_LOCATOR).send_keys(random_password)
WebDriverWait(driver, 3).until(
expected_conditions.visibility_of_element_located((By.XPATH, ANY_INPUT_LOCATOR)))
driver.find_element(By.XPATH, REGISTER_BUTTON_LOCATOR).click()
WebDriverWait(driver, 3).until(
expected_conditions.url_to_be(LOGIN_URL))
yield driver
driver.quit()
Comment on lines +53 to +54
Copy link
Copy Markdown
Owner Author

@ShamilovR ShamilovR Jan 12, 2025

Choose a reason for hiding this comment

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

Нужно исправить: здесь и далее: закрытие драйвера должно происходить в фикстуре, где инициализируется драйвер



@pytest.fixture()
def driver_logged_in(driver_registered, random_email, random_password):
driver_registered.get(LOGIN_URL)
WebDriverWait(driver_registered, 3).until(
expected_conditions.url_to_be(LOGIN_URL))
driver_registered.find_element(By.XPATH, EMAIL_LOCATOR).send_keys(random_email)
driver_registered.find_element(By.XPATH, PASSWORD_LOCATOR).send_keys(random_password)
driver_registered.find_element(By.XPATH, LOGIN_BUTTON_LOCATOR).click()
WebDriverWait(driver_registered, 3).until(
expected_conditions.url_to_be(BASE_URL))
yield driver_registered
driver_registered.quit()
68 changes: 68 additions & 0 deletions pages/base_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
import allure
from urls.urls import LOGIN_URL, FORGOT_PASSWORD_URL, BASE_URL


class BasePage:

def __init__(self, driver):
self.driver = driver

@allure.step('Скролл до нужного элемента')
def scroll_to(self, element):
self.driver.execute_script("arguments[0].scrollIntoView();", element)

@allure.step('Поиск элемента среди массива по индексу')
def find_element_by_index(self, locator, index):
return self.driver.find_elements(*locator)[index]

@allure.step('Поиск всех элементов')
def find_all_elements(self, locator):
return self.driver.find_elements(*locator)

@allure.step('Поиск элемента')
def find_element(self, locator):
return self.driver.find_element(*locator)

@allure.step('Перетаскивание элемента')
def drag_and_drop(self, from_element, to_element):
ActionChains(self.driver).drag_and_drop(from_element, to_element).perform()

@allure.step('Ожидание кликабельности элемента')
def wait_to_be_clickable(self, locator):
WebDriverWait(self.driver, 3).until(
expected_conditions.element_to_be_clickable(tuple(locator)))

@allure.step('Ожидание видимости элемента')
def wait_for_visibility(self, locator):
WebDriverWait(self.driver, 3).until(
expected_conditions.visibility_of_element_located(tuple(locator)))

@allure.step('Ожидание исчезновения элемента')
def wait_for_invisibility(self, locator):
WebDriverWait(self.driver, 3).until(
expected_conditions.invisibility_of_element_located(tuple(locator)))

@allure.step('Ожидание смены URL')
def wait_for_url_change(self, url):
WebDriverWait(self.driver, 3).until(
expected_conditions.url_to_be(url))

@allure.step('Открытие страницы логина')
def open_login_page(self):
self.driver.get(LOGIN_URL)
self.wait_for_url_change(LOGIN_URL)

@allure.step('Открытие страницы восстановления пароля')
def open_forgot_password_page(self):
self.driver.get(FORGOT_PASSWORD_URL)
self.wait_for_url_change(FORGOT_PASSWORD_URL)

@allure.step('Открытие страницы конструктора')
def open_main_page(self):
self.driver.get(BASE_URL)
self.wait_for_url_change(BASE_URL)

Comment on lines +53 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Нужно исправить: эти методы необходимо разместить в соответствующих пейджах а не в base


71 changes: 71 additions & 0 deletions pages/constructor_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from selenium.webdriver.common.by import By
import allure
from urls.urls import BASE_URL
from pages.base_page import BasePage


class ConstructorPage(BasePage):

constructor_link = (By.XPATH, ".//p[text()='Конструктор']//parent::a[@href='/']")
ingredient_link = (By.XPATH, ".//a[starts-with(@href,'/ingredient')]")
ingredient_counter = (By.XPATH, ".//p[starts-with(@class,'counter_counter__num')]")
close_ingredient_modal = (By.XPATH, ".//h2[text()='Детали ингредиента']/parent::div/following-sibling::button")
basket_element = (By.XPATH, ".//ul[starts-with(@class,'BurgerConstructor_basket')]")
create_order_button = (By.XPATH, ".//button[text()='Оформить заказ']")
order_id_header = (By.XPATH, ".//h2[starts-with(@class, 'Modal_modal__title')]")

@allure.step('Открытие страницы конструктора')
def click_to_open_constructor_page(self):
self.wait_to_be_clickable(self.constructor_link)
self.find_element(self.constructor_link).click()
self.wait_for_url_change(BASE_URL)

@allure.step('Проверка открытия страницы конструктора')
def check_constructor_page_opens(self):
assert self.driver.current_url == BASE_URL

@allure.step('Открытие попапа ингредиента')
def click_to_open_ingredient_popup(self):
self.wait_to_be_clickable(self.ingredient_link)
self.find_element_by_index(self.ingredient_link, 0).click()
self.wait_for_visibility(self.close_ingredient_modal)

@allure.step('Проверка открытия попапа ингредиента')
def check_ingredient_popup_open(self):
assert self.find_element(self.close_ingredient_modal).is_displayed()

@allure.step('Закрытие попапа ингредиента')
def click_to_close_ingredient_popup(self):
self.wait_to_be_clickable(self.close_ingredient_modal)
self.find_element(self.close_ingredient_modal).click()
self.wait_for_invisibility(self.close_ingredient_modal)

@allure.step('Проверка закрытия попапа ингредиента')
def check_ingredient_popup_close(self):
assert self.find_element(self.close_ingredient_modal).is_displayed() is False
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Можно лучше: assert not self.find_element(self.close_ingredient_modal).is_displayed()


@allure.step('Выбор ингредиента')
def move_ingredient_to_basket(self):
self.wait_to_be_clickable(self.ingredient_link)
ingredient = self.find_element_by_index(self.ingredient_link, 0)
basket = self.find_element(self.basket_element)
self.drag_and_drop(ingredient, basket)

@allure.step('Проерка выбора ингредиента')
def check_ingredient_counter_increases(self):
counter = self.find_element_by_index(self.ingredient_counter, 0)
assert int(counter.text) > 0

@allure.step('Оформить заказ')
def create_order(self):
self.wait_to_be_clickable(self.create_order_button)
self.find_element(self.create_order_button).click()

@allure.step('Проерка оформления заказа')
def check_create_order(self):
self.wait_for_visibility(self.order_id_header)
order_id = self.find_element(self.order_id_header)
assert int(order_id.text) > 0



59 changes: 59 additions & 0 deletions pages/feed_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from selenium.webdriver.common.by import By
import allure
from urls.urls import FEED_URL
from pages.base_page import BasePage


class FeedPage(BasePage):

feed_lnk = (By.XPATH, ".//a[@href='/feed']")
history_item = (By.XPATH, ".//li[starts-with(@class, 'OrderHistory_listItem')]")
history_id = (By.XPATH, ".//p[starts-with(@class, 'text text_type_digits-default')]")
item_info_header = (By.XPATH, ".//p[text()='Cостав']")
order_counter = (By.XPATH, ".//p[starts-with(@class, 'OrderFeed_number__')]")
not_ready_orders = (By.XPATH, ".//ul[starts-with(@class, 'OrderFeed_orderListReady__')]/li[starts-with(@class, 'text text_type_digits-default')]")

@allure.step('Открытие страницы ленты заказа')
def click_to_open_feed_page(self):
self.wait_to_be_clickable(self.feed_lnk)
self.find_element(self.feed_lnk).click()
self.wait_for_url_change(FEED_URL)

@allure.step('Проверка открытия страницы ленты заказов')
def check_feed_page_opens(self):
assert self.driver.current_url == FEED_URL

@allure.step('Открытие информации о заказе')
def click_to_open_order_info(self):
self.wait_to_be_clickable(self.history_item)
self.find_element_by_index(self.history_item, 0).click()

@allure.step('Проверка открытия информации о заказе')
def check_open_order_info(self):
self.wait_for_visibility(self.item_info_header)
assert self.find_element(self.item_info_header).is_displayed()

@allure.step('Проверка наличия своего заказа в общем списке')
def check_order_id_in_all_orders(self, order_id):
self.wait_for_visibility(self.history_id)
for item in self.find_all_elements(self.history_id):
if item.text == order_id:
break
else:
assert False
assert True

@allure.step('Получение счетчиков заказов')
def get_order_counters(self):
self.wait_for_visibility(self.order_counter)
counters = self.find_all_elements(self.order_counter)
return int(counters[0].text), int(counters[1].text)

@allure.step('Получение счетчиков заказов')
def get_not_ready_order_ids(self):
self.wait_for_visibility(self.not_ready_orders)
elements = self.find_all_elements(self.not_ready_orders)
return list(map(lambda e: e.text, elements))



38 changes: 38 additions & 0 deletions pages/forgot_password_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from selenium.webdriver.common.by import By
import allure
from urls.urls import FORGOT_PASSWORD_URL
from pages.base_page import BasePage
from faker import Faker


class ForgotPasswordPage(BasePage):

restore_password_button = [By.XPATH, ".//button[text()='Восстановить']"]
restore_email_input = [By.XPATH, ".//input[@name='name']"]
restore_password_div_input = [By.XPATH, ".//input[@name='Введите новый пароль']/parent::div"]
show_hide_password_button = [By.XPATH, ".//input[@name='Введите новый пароль']/following-sibling::div[starts-with(@class,'input__icon')]"]

@allure.step('Отправка запроса на восстановление пароля')
def send_password_restore_request(self):
fake = Faker("ru_RU")
self.wait_to_be_clickable(self.restore_password_button)
self.find_element(self.restore_email_input).send_keys(fake.email())
self.find_element(self.restore_password_button).click()
self.wait_for_visibility(self.restore_password_div_input)

@allure.step('Проверка отправки запроса на восстановление пароля')
def check_password_restore_request_sent(self):
self.wait_for_visibility(self.restore_password_div_input)
assert self.find_element(self.restore_password_div_input).is_displayed()

@allure.step('Нажатие на кнопку показать пароль')
def click_on_focus_password_input_icon(self):
self.wait_to_be_clickable(self.show_hide_password_button)
self.find_element(self.show_hide_password_button).click()

@allure.step('Проверка фокуса на поле пароля')
def check_password_input_in_active_state(self):
cls = self.find_element(self.restore_password_div_input).get_attribute('class')
assert "input_status_active" in cls


18 changes: 18 additions & 0 deletions pages/login_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from selenium.webdriver.common.by import By
import allure
from urls.urls import FORGOT_PASSWORD_URL
from pages.base_page import BasePage


class LoginPage(BasePage):

forgot_password_page_link = [By.XPATH, ".//a[@href='/forgot-password']"]

@allure.step('Проверка открытия страницы восстановления пароля')
def check_forgot_password_page_opens(self):
self.wait_to_be_clickable(self.forgot_password_page_link)
self.find_element(self.forgot_password_page_link).click()
self.wait_for_url_change(FORGOT_PASSWORD_URL)
assert self.driver.current_url == FORGOT_PASSWORD_URL
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Нужно исправить: здесь и далее по проекту: все прямые обращения к driver необходимо вынести в base, там описать методы взаимодействия с ними и оттуда за счет наследования классов страниц от base переиспользовать



50 changes: 50 additions & 0 deletions pages/profile_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from selenium.webdriver.common.by import By
import allure
from urls.urls import PROFILE_URL, ORDER_HISTORY_URL, LOGIN_URL
from pages.base_page import BasePage


class ProfilePage(BasePage):

profile_page_link = [By.XPATH, ".//a[@href='/account']"]
order_history_link = [By.XPATH, ".//a[@href='/account/order-history']"]
logout_button = [By.XPATH, ".//button[text()='Выход']"]
order_id = [By.XPATH, ".//p[starts-with(@class, 'text text_type_digits-default')]"]

@allure.step('Открытие страницы профиля')
def open_profile_page(self):
self.wait_to_be_clickable(self.profile_page_link)
self.find_element(self.profile_page_link).click()
self.wait_for_url_change(PROFILE_URL)

@allure.step('Проверка открытия страницы профиля')
def check_profile_page_opens(self):
assert self.driver.current_url == PROFILE_URL

@allure.step('Открытие страницы истории заказов')
def open_order_history_page(self):
self.wait_to_be_clickable(self.order_history_link)
self.find_element(self.order_history_link).click()
self.wait_for_url_change(ORDER_HISTORY_URL)

@allure.step('Получение последнего id заказа')
def get_last_order_id(self):
self.wait_for_visibility(self.order_id)
return self.find_element_by_index(self.order_id, 0).text

@allure.step('Проверка открытия страницы истории заказов')
def check_order_history_page_opens(self):
assert self.driver.current_url == ORDER_HISTORY_URL

@allure.step('Выход из ЛК')
def logout(self):
self.wait_to_be_clickable(self.logout_button)
self.find_element(self.logout_button).click()
self.wait_for_url_change(LOGIN_URL)

@allure.step('Проверка выхода из ЛК')
def check_logout(self):
assert self.driver.current_url == LOGIN_URL



2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = .
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
faker
pytest
allure-pytest
selenium
Loading