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
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Python
.venv/
__pycache__/
*.pyc

# Allure
allure_results/
allure_report/

# IDE
.vscode/
.idea/
Binary file modified README.md
Binary file not shown.
13 changes: 13 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

@pytest.fixture(scope="function")
def driver():
options = Options()
options.add_argument("--width=1920")
options.add_argument("--height=1080")
browser = webdriver.Firefox(options=options)
yield browser
browser.quit()

20 changes: 20 additions & 0 deletions data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class OrderData:
param = 'first_name, last_name, address, metro_station, phone, delivery_date, rental_period'
value = [
['Евгений', 'Петров', 'Каланчевская, 15', 'Комсомольская', '+78002228888', '11', 'двое суток'],
['Илья', 'Ильф', 'Брянская, 5', 'Киевская', '+79851150102', '10', 'трое суток']
]


class FAQData:
param = 'number, expected_answer'
value = [
(0, "Сутки — 400 рублей. Оплата курьеру — наличными или картой."),
(1, "Пока что у нас так: один заказ — один самокат. Если хотите покататься с друзьями, можете просто сделать несколько заказов — один за другим."),
(2, "Допустим, вы оформляете заказ на 8 мая. Мы привозим самокат 8 мая в течение дня. Отсчёт времени аренды начинается с момента, когда вы оплатите заказ курьеру. Если мы привезли самокат 8 мая в 20:30, суточная аренда закончится 9 мая в 20:30."),
(3, "Только начиная с завтрашнего дня. Но скоро станем расторопнее."),
(4, "Пока что нет! Но если что-то срочное — всегда можно позвонить в поддержку по красивому номеру 1010."),
(5, "Самокат приезжает к вам с полной зарядкой. Этого хватает на восемь суток — даже если будете кататься без передышек и во сне. Зарядка не понадобится."),
(6, "Да, пока самокат не привезли. Штрафа не будет, объяснительной записки тоже не попросим. Все же свои."),
(7, "Да, обязательно. Всем самокатов! И Москве, и Московской области.")
]
21 changes: 21 additions & 0 deletions locators/main_page_locators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from selenium.webdriver.common.by import By


class MainPageLocators:

@staticmethod
def question_locator(question_index: int):
return (By.ID, f"accordion__heading-{question_index}")

@staticmethod
def answer_locator(answer_index: int):
return (By.ID, f"accordion__panel-{answer_index}")



TOP_ORDER_BTN = (By.XPATH, "//div[contains(@class, 'Header_Nav')]/button[contains(@class, 'Button_Button')]")
BOTTOM_ORDER_BTN = (By.XPATH, "//div[contains(@class, 'Home_FinishButton')]/button[contains(@class, 'Button_Button')]")
FAQ_SECTION = (By.CLASS_NAME, "Home_FAQ__3uVm4")
SCOOTER_LOGO = (By.XPATH, "//img[@alt='Scooter']")
YANDEX_LOGO = (By.XPATH, "//img[@alt='Yandex']")
DZEN_NEWS = (By.XPATH, "//div[text() = 'Новости']")
30 changes: 30 additions & 0 deletions locators/order_page_locators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from selenium.webdriver.common.by import By

class OrderPageLocators:

NAME_INPUT = (By.XPATH, "//input[@placeholder='* Имя']")
LASTNAME_INPUT = (By.XPATH, "//input[@placeholder='* Фамилия']")
ADDRESS_INPUT = (By.XPATH, "//input[@placeholder='* Адрес: куда привезти заказ']")
METRO_FIELD = (By.XPATH, "//input[@placeholder='* Станция метро']")
PHONE_FIELD = (By.XPATH, "//input[@placeholder='* Телефон: на него позвонит курьер']")

NEXT_BTN = (By.XPATH, "//button[text()='Далее']")
DATE_INPUT = (By.XPATH, "//input[@placeholder='* Когда привезти самокат']")
RENTAL_PERIOD_DROPDOWN = (By.XPATH, "//div[text()='* Срок аренды']")
ORDER_BTN = (By.XPATH, "//div[contains(@class, 'Order_Buttons')]//button[text()='Заказать']")
CONFIRM_BTN = (By.XPATH, "//div[contains(@class, 'Order_Modal')]//button[text()='Да']")
STATUS_BTN = (By.XPATH, "//button[text()='Посмотреть статус']")

COOKIE_BTN = (By.ID, "rcc-confirm-button")

@staticmethod
def period_locator(period: str):
return (By.XPATH, f"//div[contains(text(),'{period}')]")

@staticmethod
def date_locator(day_number: str):
return (By.XPATH, f"//div[contains(text(),'{day_number}')]")

@staticmethod
def station_locator(station: str):
return (By.XPATH, f"//div[contains(text(),'{station}')]")
44 changes: 44 additions & 0 deletions pages/base_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import allure
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from locators.order_page_locators import OrderPageLocators



class BasePage:

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

@allure.step('Закрыть cookies-баннер')
def close_cookie_banner(self):
try:
WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable(OrderPageLocators.COOKIE_BTN)
).click()
except:
pass

@allure.step('Открыть страницу')
def open_page(self, url):
self.driver.get(url)
self.close_cookie_banner()

@allure.step('Поиск элемента')
def wait_and_find_element(self, locator, timeout=10):
element = WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located(locator))
return element

@allure.step('Скролл до элемента')
def scroll_to_element(self, locator):
element = self.driver.find_element(*locator)
self.driver.execute_script("arguments[0].scrollIntoView();", element)

@allure.step("Переключиться на окно {window_index}")
def switch_to_window(self, window_index: int):
WebDriverWait(self.driver, 10).until(lambda d: len(d.window_handles) > window_index)
self.driver.switch_to.window(self.driver.window_handles[window_index])

@allure.step('Получить текущий URL')
def get_current_url(self):
return self.driver.current_url
46 changes: 46 additions & 0 deletions pages/main_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from locators.main_page_locators import MainPageLocators
from pages.base_page import BasePage
from urls import Urls
import allure


class MainPage(BasePage):

@allure.step("Клик по верхней кнопке заказа")
def click_top_order_btn(self):
self.wait_and_find_element(MainPageLocators.TOP_ORDER_BTN).click()

@allure.step("Клик по нижней кнопке заказа")
def click_bottom_order_btn(self):
self.scroll_to_element(MainPageLocators.BOTTOM_ORDER_BTN)
self.wait_and_find_element(MainPageLocators.BOTTOM_ORDER_BTN).click()

@allure.step("Скролл к разделу FAQ")
def scroll_to_faq(self):
self.scroll_to_element(MainPageLocators.FAQ_SECTION)

@allure.step("Клик по логотипу Яндекса")
def click_to_yandex_logo(self):
self.wait_and_find_element(MainPageLocators.YANDEX_LOGO).click()

@allure.step("Клик по лого самоката")
def click_to_scooter(self):
self.wait_and_find_element(MainPageLocators.SCOOTER_LOGO).click()

@allure.step("Получаем текст вопроса и ответ по локаторам")
def get_question_and_answer(self, question_index, answer_index):
self.scroll_to_faq()
question_loc = MainPageLocators.question_locator(question_index)
answer_loc = MainPageLocators.answer_locator(answer_index)
question_text = self.wait_and_find_element(question_loc).text
self.wait_and_find_element(question_loc).click()
answer_text = self.wait_and_find_element(answer_loc).text
return question_text, answer_text

@allure.step("Проверка редиректа на страницу заказа")
def check_redirect_to_order_page(self):
return self.get_current_url() == Urls.ORDER_PAGE

@allure.step("Дзен новости")
def dzen_news_(self):
return self.wait_and_find_element(MainPageLocators.DZEN_NEWS)
66 changes: 66 additions & 0 deletions pages/order_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import allure

from pages.base_page import BasePage
from locators.order_page_locators import OrderPageLocators


class OrderPage(BasePage):
@allure.step('Заполняем поле Имя')
def set_name(self, name):
self.wait_and_find_element(OrderPageLocators.NAME_INPUT).send_keys(name)

@allure.step('Заполняем поле Фамилия')
def set_second_name(self, lastname):
self.wait_and_find_element(OrderPageLocators.LASTNAME_INPUT).send_keys(lastname)

@allure.step('Заполняем поле Адрес')
def set_address(self, address):
self.wait_and_find_element(OrderPageLocators.ADDRESS_INPUT).send_keys(address)

@allure.step('Заполняем поле Станция метро')
def set_metro_station(self, metro_station):
self.wait_and_find_element(OrderPageLocators.METRO_FIELD).click()
self.wait_and_find_element(OrderPageLocators.station_locator(metro_station)).click()

@allure.step('Заполняем поле Номер телефона')
def set_phone_number(self, phone):
self.wait_and_find_element(OrderPageLocators.PHONE_FIELD).send_keys(phone)

@allure.step('Кликаем кнопку далее')
def click_continue_button(self):
self.wait_and_find_element(OrderPageLocators.NEXT_BTN).click()

@allure.step('Выбираем дату доставки')
def set_delivery_date(self, delivery_day):
self.wait_and_find_element(OrderPageLocators.DATE_INPUT).click()
self.wait_and_find_element(OrderPageLocators.date_locator(delivery_day)).click()

@allure.step('Заполняем поле Срок Аренды')
def set_rental_period(self, rental_period):
self.wait_and_find_element(OrderPageLocators.RENTAL_PERIOD_DROPDOWN).click()
self.wait_and_find_element(OrderPageLocators.period_locator(rental_period)).click()

@allure.step('Кликаем кнопку заказать')
def click_order_button(self):
self.wait_and_find_element(OrderPageLocators.ORDER_BTN).click()

@allure.step('Подтверждаем заказ')
def click_confirm_button(self):
self.wait_and_find_element(OrderPageLocators.CONFIRM_BTN).click()

@allure.step('Находим кнопку Статус Заказа')
def find_status_button(self):
return self.wait_and_find_element(OrderPageLocators.STATUS_BTN).is_displayed()

@allure.step('Заказываем самокат')
def order_scooter(self, name, second_name, address, metro_station, phone_number, day_number, period):
self.set_name(name)
self.set_second_name(second_name)
self.set_address(address)
self.set_metro_station(metro_station)
self.set_phone_number(phone_number)
self.click_continue_button()
self.set_delivery_date(day_number)
self.set_rental_period(period)
self.click_order_button()
self.click_confirm_button()
Copy link

Choose a reason for hiding this comment

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

Отлично: конкретные шаги в тестах реализованы через методы пейджей - теперь каждый шаг можно переиспользовать, а код стало легче понимать

27 changes: 27 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
allure-pytest==2.15.0
allure-python-commons==2.15.0
attrs==25.3.0
certifi==2025.10.5
cffi==2.0.0
colorama==0.4.6
Faker==37.11.0
h11==0.16.0
idna==3.10
iniconfig==2.1.0
outcome==1.3.0.post0
packaging==25.0
pluggy==1.6.0
pycparser==2.23
Pygments==2.19.2
PySocks==1.7.1
pytest==8.4.2
selenium==4.36.0
sniffio==1.3.1
sortedcontainers==2.4.0
trio==0.31.0
trio-websocket==0.12.2
typing_extensions==4.15.0
tzdata==2025.2
urllib3==2.5.0
websocket-client==1.8.0
wsproto==1.2.0
23 changes: 23 additions & 0 deletions tests/test_faq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import allure
import pytest

from data import FAQData
from pages.main_page import MainPage
from urls import Urls

@allure.feature('FAQ')
class TestFAQ:

@allure.title('Проверка ответа на вопрос #{number}')
@allure.description('Проверяем соответствие ответа на выбранный вопрос')
@pytest.mark.parametrize(FAQData.param, FAQData.value)
def test_question_and_answer(self, driver, number, expected_answer):
main_page = MainPage(driver)
main_page.open_page(Urls.MAIN_PAGE)
question_text, answer_text = main_page.get_question_and_answer(number, number)
assert answer_text == expected_answer, (
f"Неверный ответ для вопроса #{number}\n"
f"Вопрос: {question_text}\n"
f"Ожидаемый ответ: {expected_answer}\n"
f"Фактический ответ: {answer_text}"
)
Loading