├── .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 |
--------------------------------------------------------------------------------