├── .gitignore ├── README.md ├── lesson1 ├── readme.md ├── requirements.txt ├── test-case.md └── test_auth.py ├── lesson2 ├── readme.md ├── registration_system.py ├── requirements.txt ├── saucedemo_update │ ├── conftest.py │ ├── data.py │ ├── locators.py │ └── test_auth.py ├── test_autouse_example.py ├── test_registration_system.py ├── test_scope_example.py ├── test_simple_example.py └── test_yield.py ├── lesson3 ├── readme.md ├── requirements.txt ├── test_expl.py ├── test_impl.py └── test_options.py ├── lesson4 ├── conftest.py ├── pages │ ├── auth_page.py │ └── base_page.py ├── readme.md ├── tests │ └── test_auth.py └── urls.py ├── lesson5 ├── conftest.py ├── pages │ └── auth.py ├── readme.md └── test_auth.py ├── lesson6 └── readme.md ├── lesson7 ├── readme.md ├── selenium_example │ ├── Dockerfile │ ├── main.py │ └── requirements.txt └── simple_example │ ├── Dockerfile │ ├── requirements.txt │ └── test_docker.py └── lesson8 ├── conftest.py ├── luma_project ├── __init__.py ├── pages │ ├── account.py │ └── sign_up.py └── test_data │ └── user_data.py ├── pytest.ini ├── structure.md └── tests └── test_sign_up.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Информация 2 | 3 | **Как будет построен процесс занятий?** 4 | 5 | 1. Урок 6 | 2. Домашнее задание 7 | 3. Форма с обратной связью и вопросами 8 | 4. Дополнительные материалы 9 | 10 | **Как будет построен урок?** 11 | 12 | 1. Решаем домашнее задание 13 | 2. Обьясняется новая тема 14 | 3. Все вопрсоы задаются в конце урока. 15 | 16 | Ссылка на базу знаний: 17 | 18 | Как работать с данным репозиторием? 19 | 20 | Для каждого урока существует своя директория. Шаблон: lesson_номер_урока. 21 | В директории урока можно найти: 22 | 23 | 1. Домашнее задание 24 | 2. Код написанный на уроке 25 | -------------------------------------------------------------------------------- /lesson1/readme.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к первому уроку 2 | 3 | **Необходимо написать автотесты для сайта saucedemo:** 4 | ***Ссылка на сайт: https://www.saucedemo.com/*** 5 | 6 | Функционал, который необходимо покрыть автотестами: 7 | 8 | **Авторизация** 9 | 10 | 1. Авторизация используя корректные данные (standard_user, secret_sauce) 11 | 2. Авторизация используя некорректные данные (user, user) 12 | 13 | **Корзина** 14 | 15 | 1. Добавление товара в корзину через каталог 16 | 2. Удаление товара из корзины через корзину 17 | 3. Добавление товара в корзину из карточки товара 18 | 4. Удаление товара из корзины через карточку товара 19 | 20 | **Карточка товара** 21 | 22 | 1. Успешный переход к карточке товара после клика на картинку товара 23 | 2. Успешный переход к карточке товара после клика на название товара 24 | 25 | **Оформление заказа** 26 | 27 | 1. Оформление заказа используя корректные данные 28 | 29 | **Фильтр** 30 | 31 | 1. Проверка работоспособности фильтра (A to Z) 32 | 2. Проверка работоспособности фильтра (Z to A) 33 | 3. Проверка работоспособности фильтра (low to high) 34 | 4. Проверка работоспособности фильтра (high to low) 35 | 36 | **Бургер меню** 37 | 38 | 1. Выход из системы 39 | 2. Проверка работоспособности кнопки "About" в меню 40 | 3. Проверка работоспособности кнопки "Reset App State" 41 | 42 | Чек лист достаточно примерный. Чуть позже во время практики над основным проектом мы сможем поработать с качественной документацией. 43 | 44 | Основная суть данного задания - попробовать Selenium и Pytest на практике. 45 | 46 | Для выполнения задания нужно создать новый репозиторий и написать некоторое количество автотестов. 47 | -------------------------------------------------------------------------------- /lesson1/requirements.txt: -------------------------------------------------------------------------------- 1 | selenium==4.18.1 2 | pytest==8.1.1 3 | -------------------------------------------------------------------------------- /lesson1/test-case.md: -------------------------------------------------------------------------------- 1 | # Авторизация 2 | 3 | 1. Открыть url: 4 | 2. Ввести "standard_user" в "Username" 5 | 3. Ввести "secret_sauce" в "Password" 6 | 4. Кликнуть "Login" 7 | 5. Проверить, что мы перешли на новый url: 8 | -------------------------------------------------------------------------------- /lesson1/test_auth.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.common.by import By 3 | 4 | browser = webdriver.Chrome() 5 | 6 | def test_auth_positive(): 7 | browser.get('https://www.saucedemo.com/v1/') 8 | 9 | browser.find_element('xpath', '//*[@id="user-name"]').send_keys('standard_user') 10 | browser.find_element(By.XPATH, '//*[@id="password"]').send_keys('secret_sauce') 11 | browser.find_element(By.XPATH, '//*[@id="login-button"]').click() 12 | assert browser.current_url == 'https://www.saucedemo.com/v1/inventory.html', 'url не соответствует ожидаемому' 13 | 14 | 15 | browser.quit() 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lesson2/readme.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание для второго урока 2 | 3 | **Необходимо отрефакторить автотесты для сайта saucedemo:** 4 | ***Ссылка на сайт: 5 | 6 | Как вы помните мы уже написали автотесты для данного сайта. Теперь с багажом новых знаний предлагаю провести рефакторинг новых тестов: 7 | 8 | 1. Разделяем локаторы, данные 9 | 2. Добавляем фикстуры 10 | 3. Если возможно добавляем pre, post conditions 11 | 4. Получаем и сравниваем информацию об элементах 12 | 13 | На сайте saucedemo нет чек бокса поэтому ChatGPT написал для вас небольшую форму регистрации 14 | Ссылка: 15 | 16 | Так же можно использовать любой сайт имеющий форму регистрации и чек бокс 17 | -------------------------------------------------------------------------------- /lesson2/registration_system.py: -------------------------------------------------------------------------------- 1 | class RegistrationSystem: 2 | def __init__(self): 3 | # Используем словарь для хранения данных пользователей. 4 | # Ключом будет email, значением - другой словарь с именем и номером телефона. 5 | self.users = {} 6 | 7 | def register(self, name, email, phone): 8 | if email in self.users: 9 | return "Ошибка: Пользователь с таким email уже существует!" 10 | 11 | # Сохраняем пользователя в формате {"email", "name", "phone"} 12 | self.users[email] = {'name': name, 'phone': phone, 'email': email} 13 | return "Пользователь успешно зарегистрирован!" 14 | 15 | def delete_all_users(self): 16 | self.users = {} 17 | return "Все пользователи успешно удалены!" 18 | 19 | def delete_user_by_email(self, email): 20 | if email not in self.users: 21 | return "Ошибка: Пользователь с таким email не найден!" 22 | 23 | del self.users[email] 24 | return f"Пользователь с email {email} успешно удален!" 25 | 26 | def view_all_users(self): 27 | return self.users 28 | 29 | 30 | # Пример использования 31 | system = RegistrationSystem() 32 | 33 | # Регистрация пользователя 34 | print(system.register("Алекс", "alex@example.com", "+1234567890")) 35 | # Просмотр всех пользователей 36 | print(system.view_all_users()) 37 | # Удаление пользователя по email 38 | print(system.delete_user_by_email("alex@example.com")) 39 | # Удаление всех пользователей 40 | print(system.delete_all_users()) -------------------------------------------------------------------------------- /lesson2/requirements.txt: -------------------------------------------------------------------------------- 1 | selenium==4.19.0 2 | pytest==8.1.1 3 | Faker==24.4.0 -------------------------------------------------------------------------------- /lesson2/saucedemo_update/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from selenium import webdriver 3 | 4 | @pytest.fixture() 5 | def driver(): 6 | driver = webdriver.Chrome() 7 | yield driver 8 | print('\nquit browser...') 9 | driver.quit() 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lesson2/saucedemo_update/data.py: -------------------------------------------------------------------------------- 1 | # AUTH 2 | 3 | LOGIN = 'standard_user' 4 | PASSWORD = 'secret_sauce' 5 | 6 | # URLS 7 | 8 | MAIN_PAGE = 'https://www.saucedemo.com/' 9 | -------------------------------------------------------------------------------- /lesson2/saucedemo_update/locators.py: -------------------------------------------------------------------------------- 1 | #AUTH 2 | 3 | USERNAME_FIELD = '//input[@data-test="username"]' 4 | PASSWORD_FIELD = '//input[@data-test="password"]' 5 | LOGIN_BUTTON = '//input[@data-test="login-button"]' -------------------------------------------------------------------------------- /lesson2/saucedemo_update/test_auth.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.common.by import By 3 | import time 4 | from locators import USERNAME_FIELD, PASSWORD_FIELD, LOGIN_BUTTON 5 | from data import LOGIN, PASSWORD, MAIN_PAGE 6 | 7 | def test_login_form(driver): 8 | driver.get(MAIN_PAGE) 9 | 10 | # вводим валидный логин в поле "Username" 11 | driver.find_element(By.XPATH, USERNAME_FIELD).send_keys(LOGIN) 12 | 13 | # вводим валидный пароль в поле "Password" 14 | driver.find_element(By.XPATH, PASSWORD_FIELD).send_keys(PASSWORD) 15 | 16 | # кликаем на кнопку "Login" 17 | driver.find_element(By.XPATH, LOGIN_BUTTON).click() 18 | 19 | time.sleep(5) 20 | assert driver.current_url == "https://www.saucedemo.com/inventory.html" 21 | 22 | -------------------------------------------------------------------------------- /lesson2/test_autouse_example.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # Autouse 4 | # Пример с использованием autouse 5 | @pytest.fixture(autouse=True) 6 | def print_before_tests(): 7 | print("\n--- Начало теста ---") 8 | 9 | def test_func1(): 10 | assert True 11 | 12 | def test_func2(): 13 | assert 1 == 1 14 | 15 | def test_func3(): 16 | assert "str" == "str" 17 | 18 | # Пример без использования autouse 19 | def test_func4(): 20 | print("\n--- Начало теста без autouse ---") 21 | assert True 22 | 23 | def test_func5(): 24 | print("\n--- Начало теста без autouse ---") 25 | assert 1 == 1 26 | 27 | def test_func6(): 28 | print("\n--- Начало теста без autouse ---") 29 | assert "str" == "str" 30 | 31 | -------------------------------------------------------------------------------- /lesson2/test_registration_system.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from registration_system import RegistrationSystem 3 | 4 | # Фикстура для инициализации системы перед каждым тестом 5 | @pytest.fixture 6 | def init_system(): 7 | system = RegistrationSystem() 8 | yield system 9 | system.delete_all_users() # постусловие: очистка базы данных после теста 10 | 11 | def test_registration_without_pre_post_conditions(): 12 | # Шаги тест кейса 001 13 | system = RegistrationSystem() 14 | system.register("Алекс", "alex@example.com", "+1234567890") 15 | users = system.view_all_users() 16 | assert "alex@example.com" in users # Ожидаемый результат 17 | 18 | def test_registration_with_pre_post_conditions(init_system): 19 | # Шаги тест кейса 002 20 | init_system.register("Алекс", "alex@example.com", "+1234567890") 21 | users = init_system.view_all_users() 22 | assert "alex@example.com" in users # Ожидаемый результат -------------------------------------------------------------------------------- /lesson2/test_scope_example.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | @pytest.fixture(scope="class") 4 | def class_fixture(): 5 | print("\nSetting up the class fixture") 6 | yield 7 | print("\nTearing down the class fixture") 8 | 9 | @pytest.mark.usefixtures("class_fixture") 10 | class TestClass: 11 | 12 | def test_one(self): 13 | print("\nExecuting test_one") 14 | assert True 15 | 16 | def test_two(self): 17 | print("\nExecuting test_two") 18 | assert 1 == 1 19 | 20 | def test_outside_class(): 21 | print("\nExecuting test outside of the class") 22 | assert "a" == "a" 23 | 24 | # для запуска необходимо использовать 25 | # pytest -s -v --setup-show test_scope_example.py 26 | 27 | -------------------------------------------------------------------------------- /lesson2/test_simple_example.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from faker import Faker 3 | 4 | # Фикстура для создания фейкового email 5 | @pytest.fixture 6 | def fake_email(): 7 | fake = Faker() 8 | return fake.email() 9 | 10 | def test_email_format(fake_email): 11 | # Проверка, что в email есть символ '@' 12 | assert "@" in fake_email 13 | 14 | # Проверка, что email содержит точку после символа '@' 15 | assert "." in fake_email.split('@')[1] 16 | 17 | # Проверка, что email не пустой 18 | assert len(fake_email) > 0 19 | 20 | -------------------------------------------------------------------------------- /lesson2/test_yield.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | @pytest.fixture 4 | def example_fixture(): 5 | print("\n--- Setup part of the fixture ---") 6 | yield 7 | print("\n--- Teardown part of the fixture ---") 8 | 9 | @pytest.fixture 10 | def sample_data(): 11 | data = {"key": "value"} 12 | yield data # передача данных в тестовую функцию 13 | print("\n--- Teardown part of the fixture ---") 14 | 15 | def test_example(example_fixture): 16 | assert 1 == 1 17 | 18 | def test_sample_data(sample_data): 19 | assert sample_data["key"] == "value" -------------------------------------------------------------------------------- /lesson3/readme.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание 2 | 3 | Суть данного домашнего задания - тренировка использования ожиданий в Selenium, повторение пройденных тем перед написанием полноценного тестового фреймворка. 4 | 5 | Для практики я попросил ChatGPT написть небольшой сайт с формой регистрации. 6 | Ссылка на сайт: 7 | 8 | Вам необходимо автоматизировать данный тест кейс используя ранее пройденные темы, включая ожидания. 9 | 10 | **Название Теста: Проверка функционала регистрации на сайте** 11 | *Предусловия: Браузер открыт, интернет-соединение стабильно.* 12 | Шаги: 13 | 14 | * Перейти по URL: Открыть в браузере указанный URL сайта 15 | * Проверить заголовок: Убедиться, что текст в теге

на странице соответствует "Практика с ожиданиями в Selenium". 16 | * Дождаться появления кнопки "Начать тестирование" 17 | * Найти кнопку: Найти на странице кнопку с текстом "Начать тестирование". 18 | * Начать тестирование: Кликнуть по кнопке "Начать тестирование". 19 | * Ввод логина: Ввести "login" в поле для логина. 20 | * Ввод пароля: Ввести "password" в поле для пароля. 21 | * Согласие с правилами: Установить флажок в чекбокс "Согласен со всеми правилами". 22 | * Подтвердить регистрацию: Нажать кнопку "Зарегистрироваться". 23 | * Проверка загрузки: Удостовериться, что появился индикатор загрузки. 24 | * Проверка сообщения: Убедиться, что после завершения загрузки появилось сообщение "Вы успешно зарегистрированы". 25 | 26 | **Ожидаемый результат:** Пользователь успешно проходит процесс регистрации, видит индикатор загрузки и получает сообщение об успешной регистрации. 27 | **Критерии успешности:** Сообщение "Вы успешно зарегистрированы" отображается на экране. 28 | 29 | Необходимо написать 3 автотеста для данной страницы: 30 | 31 | 1. С использованием Explicit waits и Expected Conditions 32 | 2. С использованием Implicit waits 33 | 3. С использованием time.sleep() 34 | 35 | Так же делюсь несколькими дополнительными сайтами для практики ожиданий: 36 | 37 | 1. 38 | 2. 39 | 3. 40 | 41 | Все сайты достаточно примитивные, но надеюсь у всех получится уловить основную суть использования ожиданий и попрактиковаться в их написании. 42 | 43 | Так же необходимо написать несколько автотестов для сайта 44 | 45 | Ссылки на упражнения: 46 | 47 | 1. (Необходимо создать и удалить элемент) 48 | 2. (Необходимо пройти базовую авторизацию) 49 | 3. (Необходимо найти сломанные изображения) 50 | 4. (Практика с чек боксами) 51 | 52 | Так же важно помнить что мы должны получать информацию об вебэлементах и сравнивать ее с ожидаемым результатом. 53 | 54 | Например: текст, цвет, расположение, отображение, выбор чекбокса и так далее. 55 | Ссылка на страницу с документацией: -------------------------------------------------------------------------------- /lesson3/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==7.4.2 2 | selenium==4.14.0 3 | -------------------------------------------------------------------------------- /lesson3/test_expl.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.options import Options 3 | from selenium.webdriver.common.by import By 4 | from selenium.webdriver.support.ui import WebDriverWait 5 | from selenium.webdriver.support import expected_conditions as EC 6 | import pytest 7 | 8 | @pytest.fixture 9 | def chrome_options(): 10 | options = Options() 11 | options.add_argument('--start-maximized') 12 | return options 13 | 14 | @pytest.fixture 15 | def driver(chrome_options): 16 | driver = webdriver.Chrome(options=chrome_options) 17 | return driver 18 | 19 | @pytest.fixture 20 | def wait(driver): 21 | wait = WebDriverWait(driver, timeout=10) 22 | return wait 23 | 24 | def test_visible_after_with_explicit_waits(driver, wait): 25 | driver.get('https://demoqa.com/dynamic-properties') 26 | visible_after_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[text()='Visible After 5 Seconds']"))) 27 | assert visible_after_button.text == 'Visible After 5 Seconds' 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /lesson3/test_impl.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.options import Options 3 | from selenium.webdriver.common.by import By 4 | import pytest 5 | 6 | @pytest.fixture 7 | def chrome_options(): 8 | options = Options() 9 | options.add_argument('--start-maximized') 10 | return options 11 | 12 | @pytest.fixture 13 | def driver(chrome_options): 14 | driver = webdriver.Chrome(options=chrome_options) 15 | driver.implicitly_wait(10) 16 | yield driver 17 | driver.quit() 18 | 19 | def test_visible_after_with_implicit_wait(driver): 20 | driver.get('https://demoqa.com/dynamic-properties') 21 | vissible_after_button = driver.find_element(By.XPATH, "//button[text()='Visible After 5 Seconds']") 22 | assert vissible_after_button.is_displayed() 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lesson3/test_options.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.options import Options 3 | import pytest 4 | 5 | @pytest.fixture 6 | def chrome_options(): 7 | options = Options() 8 | options.add_argument('--window-size=100,100') 9 | options.add_argument('--incognito') 10 | options.add_argument('--headless') 11 | return options 12 | 13 | @pytest.fixture 14 | def driver(chrome_options): 15 | driver = webdriver.Chrome(options=chrome_options) 16 | yield driver 17 | driver.quit() 18 | 19 | def test_example(driver): 20 | driver.get('https://www.saucedemo.com/') 21 | assert driver.current_url == 'https://www.saucedemo.com/' 22 | 23 | -------------------------------------------------------------------------------- /lesson4/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from selenium import webdriver 3 | from pages.auth_page import LoginPage 4 | from urls import base_url 5 | 6 | @pytest.fixture 7 | def driver(): 8 | options = webdriver.ChromeOptions() 9 | # options.add_argument('--headless') 10 | driver = webdriver.Chrome() 11 | yield driver 12 | driver.quit() 13 | 14 | @pytest.fixture() 15 | def login_page(driver): 16 | page = LoginPage(driver, base_url) 17 | return page 18 | 19 | -------------------------------------------------------------------------------- /lesson4/pages/auth_page.py: -------------------------------------------------------------------------------- 1 | from pages.base_page import BasePage 2 | from selenium.webdriver.common.by import By 3 | 4 | 5 | class LoginPage(BasePage): 6 | 7 | def start_button(self): 8 | return self.is_visible((By.XPATH, '//*[@id="startTest"]')) 9 | 10 | def login_field(self): 11 | self.is_visible((By.XPATH, '//*[@id="login"]')).send_keys('login') 12 | return self 13 | 14 | def password_field(self): 15 | self.is_visible((By.XPATH, '//*[@id="password"]')).send_keys('password') 16 | return self 17 | 18 | def agree_button(self): 19 | self.is_visible((By.XPATH, '//*[@id="agree"]')).click() 20 | return self 21 | 22 | def registration_button(self): 23 | self.is_visible((By.XPATH, '//*[@id="register"]')).click() 24 | return 25 | 26 | def loader(self): 27 | return self.is_visible((By.XPATH, '//*[@id="loader"]')) 28 | 29 | def success_message(self): 30 | return self.is_visible((By.XPATH, '//*[@id="successMessage"]')) 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lesson4/pages/base_page.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.chrome.webdriver import WebDriver 2 | from selenium.webdriver.support.ui import WebDriverWait 3 | from selenium.webdriver.support import expected_conditions as ec 4 | from selenium.webdriver.remote.webelement import WebElement 5 | 6 | 7 | class BasePage(): 8 | 9 | def __init__(self, driver: WebDriver, url): 10 | self.driver = driver 11 | self.url = url 12 | self.wait = WebDriverWait(self.driver, 10) 13 | 14 | def open(self): 15 | self.driver.get(self.url) 16 | 17 | def is_visible(self, locator) -> WebElement: 18 | return self.wait.until(ec.visibility_of_element_located(locator)) 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /lesson4/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victoretc/selenium_automation_course/90ad1f942ca9f11f1f86f3398bee5556138de285/lesson4/readme.md -------------------------------------------------------------------------------- /lesson4/tests/test_auth.py: -------------------------------------------------------------------------------- 1 | def test_auth_positive(login_page): 2 | login_page.open() 3 | 4 | login_page.start_button().click() 5 | 6 | login_page.login_field()\ 7 | .password_field()\ 8 | .agree_button()\ 9 | .registration_button() 10 | 11 | assert login_page.loader().is_displayed() 12 | assert login_page.success_message().text == 'Вы успешно зарегистрированы!' 13 | -------------------------------------------------------------------------------- /lesson4/urls.py: -------------------------------------------------------------------------------- 1 | base_url = 'https://victoretc.github.io/selenium_waits/' -------------------------------------------------------------------------------- /lesson5/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from selene import browser, support 3 | import allure_commons 4 | import allure 5 | from selenium import webdriver 6 | 7 | @pytest.fixture(autouse=True) 8 | def browser_management(): 9 | 10 | options = webdriver.ChromeOptions() 11 | # options.add_argument('--headless') 12 | browser.config.driver_options = options 13 | browser.config.window_height = 100 14 | browser.config.window_width = 500 15 | browser.config.timeout = 10 16 | 17 | browser.config._wait_decorator = support._logging.wait_with( 18 | context=allure_commons._allure.StepContext 19 | ) 20 | 21 | yield 22 | 23 | allure.attach( 24 | browser.driver.get_screenshot_as_png(), 25 | name='screenshot', 26 | attachment_type=allure.attachment_type.PNG, 27 | ) 28 | 29 | browser.quit() -------------------------------------------------------------------------------- /lesson5/pages/auth.py: -------------------------------------------------------------------------------- 1 | from selene import browser, by, have 2 | from selene.support.shared.jquery_style import s 3 | 4 | 5 | def visit(url): 6 | browser.open(url) 7 | 8 | def start(): 9 | return s('//*[@id="startTest"]') 10 | 11 | def login(): 12 | s('#login').type('login') 13 | s('#password').type('password') 14 | s('#agree').click() 15 | browser.element(by.text('Зарегистрироваться')).click() 16 | 17 | def success_message_have_text(text): 18 | s('#successMessage').should(have.text(text)) 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /lesson5/readme.md: -------------------------------------------------------------------------------- 1 | # Пригодится 2 | 3 | база знаний (code): 4 | практика с ожиданиями: 5 | selene: 6 | база знаний (url): 7 | allure-pytest (дока): 8 | conftest: 9 | 10 | 38 | 39 | Как внести свой вклад: 40 | -------------------------------------------------------------------------------- /lesson5/test_auth.py: -------------------------------------------------------------------------------- 1 | from pages import auth 2 | import allure 3 | 4 | url = 'https://victoretc.github.io/selenium_waits/' 5 | 6 | @allure.title('Авторизация') 7 | def test_login(): 8 | auth.visit(url) 9 | auth.start().click() 10 | auth.login() 11 | auth.success_message_have_text('Вы успешно зарегистрированы!') -------------------------------------------------------------------------------- /lesson6/readme.md: -------------------------------------------------------------------------------- 1 | **Ссылки** 2 | 3 | 1. 4 | 2. 5 | 3. 6 | 4. 7 | 5. 8 | 6. 9 | 7. 10 | -------------------------------------------------------------------------------- /lesson7/readme.md: -------------------------------------------------------------------------------- 1 | Введение в Docker: 2 | 3 | 1. Установка Docker: 4 | 2. Начиная: 5 | 3. Основные команды Docker: 6 | 7 | **Использованные команды для первичной настройки Ubuntu 22.04** 8 | 9 | ```python 10 | cat /etc/*release 11 | apt-get upgrade 12 | apt-get update 13 | apt-get install -y vim htop git curl wget 14 | ``` 15 | 16 | Настройка входа по ssh 17 | 18 | ```python 19 | vim /etc/ssh/sshd_config 20 | service ssh restart 21 | vim ~/.ssh/authorized_keys 22 | chmod 700 ~/.ssh 23 | chmod 600 ~/.ssh/authorized_keys 24 | ``` 25 | 26 | Cоздаем новый терминал и проверяем что работает подключение без пароля 27 | 28 | Почитать про ssh авторизацию можно тут: 29 | Установка Docker: 30 | Selenium manager: 31 | Chrome для тестирования: 32 | -------------------------------------------------------------------------------- /lesson7/selenium_example/Dockerfile: -------------------------------------------------------------------------------- 1 | # Используем базовый образ Python 3.11-slim-bookworm 2 | FROM python:3.11-slim-bookworm 3 | 4 | # Установка переменной среды PYTHONUNBUFFERED для Python, чтобы вывод был буферизованным 5 | # Полезно когда вывод необходимо получать в реальном времени. Отрицательно влияет на производительность 6 | ENV PYTHONUNBUFFERED=1 7 | 8 | # Установка переменной среды PYTHONDONTWRITEBYTECODE для Python, чтобы избежать создания .pyc файлов 9 | # Полезно когда не хочется отвлекаться на .pyc файлы. Данный параметр устанавливается для того чтобы они не создавались. Отрицательно влияет на производительность 10 | ENV PYTHONDONTWRITEBYTECODE=1 11 | 12 | # Устанавливаем рабочий каталог внутри контейнера 13 | WORKDIR /tests 14 | 15 | # Обновляем пакеты и устанавливаем несколько системных зависимостей и утилит 16 | RUN apt-get update && apt-get install -y \ 17 | curl \ 18 | software-properties-common \ 19 | python3-launchpadlib \ 20 | fonts-liberation \ 21 | jq \ 22 | libasound2 \ 23 | libatk-bridge2.0-0 \ 24 | libatk1.0-0 \ 25 | libatspi2.0-0 \ 26 | libcups2 \ 27 | libdbus-1-3 \ 28 | libdrm2 \ 29 | libgbm1 \ 30 | libgtk-3-0 \ 31 | libnspr4 \ 32 | libnss3 \ 33 | libwayland-client0 \ 34 | libxcomposite1 \ 35 | libxdamage1 \ 36 | libxfixes3 \ 37 | libxkbcommon0 \ 38 | libxrandr2 \ 39 | xdg-utils \ 40 | libu2f-udev \ 41 | libvulkan1 \ 42 | unzip \ 43 | && rm -rf /var/lib/apt/lists/* 44 | 45 | # Скачиваем и устанавливаем Google Chrome и ChromeDriver 46 | RUN curl -L 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json' \ 47 | | jq -r '.channels.Stable.downloads|.chrome,.chromedriver|.[]|select(.platform=="linux64").url|"curl -LO \(.)"' \ 48 | | bash \ 49 | && unzip -d /usr/local/share 'chrome-linux64.zip' \ 50 | && ln -s /usr/local/share/chrome-linux64/chrome /usr/bin/chrome \ 51 | && unzip -d /usr/local/share 'chromedriver-linux64.zip' \ 52 | && ln -s /usr/local/share/chromedriver-linux64/chromedriver /usr/bin/chromedriver \ 53 | && rm 'chrome-linux64.zip' && rm 'chromedriver-linux64.zip' 54 | 55 | # curl -L 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json': 56 | # 57 | # Эта часть команды использует curl для загрузки файла JSON с информацией о версиях Google Chrome и ChromeDriver с веб-сайта https://googlechromelabs.github.io. Флаг -L указывает на переход по перенаправлениям, если они есть. 58 | # | jq -r '.channels.Stable.downloads|.chrome,.chromedriver|.[]|select(.platform=="linux64").url|"curl -LO \(.)"': 59 | # После загрузки JSON-файла команда передает его на вход утилите jq, которая используется для обработки JSON-данных. 60 | # jq выполняет следующие операции: 61 | # 62 | # 1. Извлекает информацию о версиях Chrome и ChromeDriver для канала "Stable". 63 | # 2. Выбирает URL-адреса для Linux (.platform=="linux64"). 64 | # 3. Генерирует команды curl для скачивания файлов Chrome и ChromeDriver. 65 | # 66 | # | bash: 67 | # Результат команды jq, который содержит команды curl для скачивания файлов, передается на вход команды bash. Это приводит к выполнению сгенерированных команд curl для загрузки файлов Chrome и ChromeDriver в контейнер. 68 | # && unzip -d /usr/local/share 'chrome-linux64.zip': 69 | # После скачивания файлов Chrome и ChromeDriver, команда unzip извлекает их содержимое из ZIP-архивов в каталог /usr/local/share. 70 | # && ln -s /usr/local/share/chrome-linux64/chrome /usr/bin/chrome: 71 | # Эта часть команды создает символическую ссылку (ln -s) для исполняемого файла Google Chrome (chrome) в каталоге /usr/bin, что позволяет запускать Chrome из любой директории, добавив "chrome" к команде. 72 | # && unzip -d /usr/local/share 'chromedriver-linux64.zip': 73 | # Аналогично Chrome, команда unzip извлекает содержимое ZIP-архива ChromeDriver в каталог /usr/local/share. 74 | # && ln -s /usr/local/share/chromedriver-linux64/chromedriver /usr/bin/chromedriver: 75 | # Эта часть команды создает символическую ссылку для исполняемого файла ChromeDriver (chromedriver) в каталоге /usr/bin, что позволяет запускать ChromeDriver из любой директории, добавив "chromedriver" к команде. 76 | # && rm 'chrome-linux64.zip' && rm 'chromedriver-linux64.zip': 77 | # Наконец, после установки Chrome и ChromeDriver, файлы ZIP-архивов удаляются с помощью команд rm, чтобы освободить дисковое пространство. 78 | # В результате выполнения этой строки команды в контейнере Docker, Google Chrome и ChromeDriver устанавливаются и готовы к использованию. 79 | 80 | 81 | # Копируем файл requirements.txt и устанавливаем зависимости Python 82 | COPY requirements.txt requirements.txt 83 | RUN pip install --no-cache-dir -r requirements.txt 84 | 85 | # Копируем все остальные файлы из текущего контекста сборки в рабочий каталог 86 | COPY . . 87 | 88 | # Запускаем проект 89 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /lesson7/selenium_example/main.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.options import Options 3 | 4 | options = Options() 5 | options.add_argument('--headless') 6 | options.add_argument('--no-sandbox') 7 | options.add_argument('--disable-dev-shm-usage') 8 | 9 | driver = webdriver.Chrome(options=options) 10 | driver.get('https://www.selenium.dev/blog/2023/whats-new-in-selenium-manager-with-selenium-4.11.0/') 11 | 12 | assert driver.current_url == 'https://www.selenium.dev/blog/2023/whats-new-in-selenium-manager-with-selenium-4.11.0/' -------------------------------------------------------------------------------- /lesson7/selenium_example/requirements.txt: -------------------------------------------------------------------------------- 1 | selenium==4.15.0 2 | -------------------------------------------------------------------------------- /lesson7/simple_example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim-bookworm 2 | 3 | WORKDIR /tests 4 | 5 | COPY requirements.txt requirements.txt 6 | 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | COPY . . 10 | 11 | CMD ["pytest -s -v"] -------------------------------------------------------------------------------- /lesson7/simple_example/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==7.4.3 2 | -------------------------------------------------------------------------------- /lesson7/simple_example/test_docker.py: -------------------------------------------------------------------------------- 1 | def test_1_eq_1(): 2 | assert 1 == 1 3 | 4 | def test_2_eq_2(): 5 | assert 2 == 2 6 | -------------------------------------------------------------------------------- /lesson8/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from selene import browser, support 3 | import allure_commons 4 | import allure 5 | from selenium import webdriver 6 | 7 | @pytest.fixture(autouse=True) 8 | def browser_management(): 9 | 10 | options = webdriver.ChromeOptions() 11 | # options.add_argument('--headless') 12 | browser.config.driver_options = options 13 | browser.config.window_height = 1920 14 | browser.config.window_width = 1080 15 | browser.config.timeout = 6 16 | 17 | browser.config._wait_decorator = support._logging.wait_with( 18 | context=allure_commons._allure.StepContext 19 | ) 20 | 21 | yield 22 | 23 | allure.attach( 24 | browser.driver.get_screenshot_as_png(), 25 | name='screenshot', 26 | attachment_type=allure.attachment_type.PNG, 27 | ) 28 | 29 | browser.quit() -------------------------------------------------------------------------------- /lesson8/luma_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victoretc/selenium_automation_course/90ad1f942ca9f11f1f86f3398bee5556138de285/lesson8/luma_project/__init__.py -------------------------------------------------------------------------------- /lesson8/luma_project/pages/account.py: -------------------------------------------------------------------------------- 1 | from selene import browser, have 2 | 3 | url='https://magento.softwaretestingboard.com/customer/account/' 4 | 5 | def open(): 6 | browser.open(url) 7 | 8 | def should_be_opened(): 9 | browser.should(have.url(url)) 10 | 11 | -------------------------------------------------------------------------------- /lesson8/luma_project/pages/sign_up.py: -------------------------------------------------------------------------------- 1 | from selene import browser, have 2 | from selene.support.shared.jquery_style import s 3 | 4 | url='https://magento.softwaretestingboard.com/customer/account/create/' 5 | 6 | def open(): 7 | browser.open(url) 8 | 9 | def type_first_name(name): 10 | s('#firstname').type(name) 11 | 12 | def type_last_name(name): 13 | s('#lastname').type(name) 14 | 15 | def type_email(email): 16 | s('#email_address').type(email) 17 | 18 | def type_password(password): 19 | s('#password').type(password) 20 | 21 | def type_password_confirm(password): 22 | s('#password-confirmation').type(password) 23 | 24 | def create_account(): 25 | return s('[title="Create an Account"]') 26 | 27 | def should_be_opened(): 28 | browser.should(have.url(url)) -------------------------------------------------------------------------------- /lesson8/luma_project/test_data/user_data.py: -------------------------------------------------------------------------------- 1 | from faker import Faker 2 | fake = Faker() 3 | 4 | first_name=fake.first_name() 5 | last_name=fake.last_name() 6 | email=fake.email() 7 | password=f"sjdfj##_{fake.name()}HH{fake.email()}" 8 | 9 | 10 | -------------------------------------------------------------------------------- /lesson8/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | 3 | addopts = -s -v --alluredir=allure-results -------------------------------------------------------------------------------- /lesson8/structure.md: -------------------------------------------------------------------------------- 1 | # "Архитектура" UI тестов 2 | 3 | План: 4 | 5 | 1. Пишем простой тест 6 | 2. Как выносим локаторы? 7 | 3. Page Objects (Page Elements, Page Components ...) 8 | 4. Каким может быть Page Objects (Page Elements, Page Components ...)? 9 | 5. Делаем просто 10 | 11 | ## Материалы 12 | 13 | Что такое POM: 14 | 15 | 1. 16 | 2. 17 | 18 | Определение POM: Шаблон проектирования, который позволяет скрывать ненужную для написания тестов информацию. 19 | 20 | Каким бывает POM: 21 | 22 | 1. Перевод статьи Black Norrish: 23 | 2. Прекрасный репозиторий с примерами для UI тестов создателя Selene (Яков Крамаренко): 24 | 3. Прекрасный доклад Андрея Солнцева: 25 | 4. Прекрасный доклад Алексея Виноградова: 26 | 5. Еще один пример UI архитектуры: 27 | -------------------------------------------------------------------------------- /lesson8/tests/test_sign_up.py: -------------------------------------------------------------------------------- 1 | from luma_project.pages import sign_up, account 2 | from luma_project.test_data import user_data 3 | 4 | def test_sign_up(): 5 | sign_up.open() 6 | sign_up.type_first_name("first_name") 7 | sign_up.type_last_name("last_name") 8 | sign_up.type_email(user_data.email) 9 | sign_up.type_password("Password123$$__") 10 | sign_up.type_password_confirm("Password123$$__") 11 | sign_up.create_account().click() 12 | account.should_be_opened() 13 | 14 | def test_sign_up_with_invalid_email(): 15 | sign_up.open() 16 | sign_up.type_first_name(user_data.first_name) 17 | sign_up.type_last_name(user_data.last_name) 18 | sign_up.type_email(user_data.password) 19 | sign_up.type_password(user_data.password) 20 | sign_up.type_password_confirm(user_data.password) 21 | sign_up.create_account().click() 22 | sign_up.should_be_opened() 23 | --------------------------------------------------------------------------------