Урок поєднує дві теми: табличне покриття login form через `pytest.mark.parametrize` і керування життєвим циклом Playwright через fixtures різного scope. На практичному рефакторингу показано, як reuse браузера пришвидшує багато ітерацій, але водночас вимагає явного очищення cookies, local storage і сторінки, щоб один test case не забруднював наступний.
Примітка: конспект укладено за автоматичними українськими субтитрами; назви API та технічні терміни нормалізовано за контекстом відео. Значна частина коду генерується LLM наживо, тому конспект відокремлює навчальні принципи від невдалих проміжних реалізацій.
Після цього уроку ви зможете
Пояснити scope і setup/teardown pytest fixture.
Створити параметризовані cases із читабельними IDs.
Простежити ownership Playwright, browser, context і page.
Оцінити ризик state leakage під час reuse page/context.
Pytest fixture може мати scope session, package, module, class або function. Чим ширший scope, тим рідше створюється ресурс: session fixture — один раз на весь test run, function fixture — окремо для кожного тесту. Код до yield виконує setup, а після yield — teardown відповідного scope.
Ціль уроку — перетворити один негативний login test на параметризований. Тіло сценарію залишається одним, а різні пари email/password та читабельні case IDs передаються як дані.
Термін
fixture scope
Межа кешування й teardown fixture: function, class, module, package або session. Залежність може використовувати fixture того самого або ширшого scope, але не вужчого.
LLM спочатку отримує вузьку задачу: додати pytest.mark.parametrize та згенерувати невалідні значення через Faker. Після запуску результат уточнюється окремими кроками. Такий цикл «малий prompt — diff — запуск — наступне уточнення» краще локалізує помилки, ніж один запит на повний рефакторинг fixtures, даних і тестів одночасно.
Пари даних виносяться з decorator у окрему змінну. Це скорочує тіло тесту, дає таблиці предметну назву й дозволяє за потреби перенести test data в окремий модуль. Важливо не генерувати випадкові значення без мети: кожен case має представляти конкретний клас поведінки.
Таблиця розширюється через equivalence partitioning і boundary value analysis: валідний формат незареєстрованого email, відсутня локальна або domain-частина, пробіли, порожній пароль, занадто короткі чи довгі значення. Окремо LLM пропонує рядки, схожі на XSS і SQL injection payloads.
Ці payloads є лише вхідними прикладами, а не доказом захищеності: security check повинен мати конкретний expected result і виконуватися в безпечному тестовому середовищі. Для кожної параметризованої ітерації задається id, щоб у pytest output і CI report було видно не номер набору, а зміст failed case.
Запуск виявляє як помилки з Faker/API, так і server-side rate limit. Це корисний сигнал: параметризація збільшує кількість однотипних запитів, тому test data й темп запуску мають враховувати обмеження системи.
Термін
parameter ID
Читабельний identifier конкретного parameter set, заданий через ids, callable або pytest.param(..., id=...). Він з'являється у collection і failure output.
Приклад коду
Параметризовані cases з IDs
import pytest
LOGIN_CASES = [
pytest.param("", "secret", id="missing-email"),
pytest.param("user@example.com", "", id="missing-password"),
]
@pytest.mark.parametrize(("email", "password"), LOGIN_CASES)
def test_invalid_login_data(email: str, password: str) -> None:
assert not email or not password
Дані відокремлені від test body, а кожна ітерація має предметний ID.
Очікуваний результат: Дві ітерації проходять і відображають IDs missing-email та missing-password.
Потрібно: pytest
Практика
Перевірка fixture isolation
Створіть параметризовані negative login cases з IDs.
Запустіть їх із function-scoped BrowserContext.
Окремо спробуйте shared page із явним cleanup.
Додайте check, що authorization state попередньої ітерації не доступний наступній, і порівняйте час запуску.
Результат: Є вимірювання speed trade-off і test, який падає при state leakage.
Стандартні pytest-playwright fixtures добре ізолюють тести: нові context/page створюються автоматично, а cleanup виконує plugin. Для великої таблиці негативних логінів це створює помітні накладні витрати, тому у відео будується власний lifecycle: один Playwright/browser instance на ширший scope і спеціальні fixtures для clean app, logged-in app та shared page.
Ключова ієрархія залежностей: Playwright instance запускає browser; browser створює context; context створює page. Scope залежної fixture не може бути ширшим за ресурс, від якого вона залежить. yield повинен закрити рівно ті ресурси, які fixture створила.
Це optimization з ціною: повторне використання page/context послаблює ізоляцію. Починати безпечніше зі стандартних function-scoped fixtures, а reuse додавати лише після виміряного bottleneck і разом із перевіреним cleanup.
Увага
Shared page послаблює default isolation
Ручне очищення cookies і local storage не дорівнює новому BrowserContext. Якщо reuse справді потрібен після вимірювання, fixture повинна мати явний contract стану й окрему regression check на leakage.
Уточнення
Default pytest-playwright lifecycle уже reuse browser
Офіційний plugin тримає playwright, browser_type і browser на session scope, але context, page і new_context — на function scope. Shared context/page з уроку є custom ownership, а не default plugin fixture lifecycle.
Термін
BrowserContext isolation
Окремий incognito-like browser profile з власними cookies, local storage і session storage. Playwright використовує новий context як надійну межу ізоляції тесту.
Перші версії fixture щоразу відкривають нову вкладку або неправильно закривають page. Реалізацію уточнюють так, щоб параметризовані cases працювали на тій самій page, але перед новою ітерацією очищали cookies і local storage та поверталися у відомий початковий стан.
Простого localStorage.clear() недостатньо для універсальної ізоляції: стан також може бути в session storage, IndexedDB, service workers або server-side session. Тому shared context має сенс лише тоді, коли перелік станів відомий і cleanup перевірений. Новий BrowserContext залишається надійнішою межею ізоляції.
Після прискорення login cases нові fixtures пробують застосувати до решти suite. Для сценаріїв, яким уже потрібен авторизований користувач, planned fixture має один раз виконати login і віддати готову page. Для негативного login test, навпаки, потрібна неавторизована shared page.
Автоматичний рефакторинг змінює більше тестів, ніж очікувалося, і частково плутає їх передумови. Це демонструє практичне правило: fixture називається за гарантованим станом (anonymous_page, authenticated_page), а migration робиться по одному behavior з повторним запуском, а не одним глобальним LLM-edit.
Після рефакторингу suite падає через змішування plugin-managed fixtures та вручну створеного sync Playwright instance. Додатково частина launch/context options дублюється в dictionary і кількох fixtures, що робить конфігурацію непослідовною.
Рішення у відео — перейти на один спосіб володіння lifecycle: custom fixtures запускають Playwright і browser, а конфігурація збирається в одному місці. Частину pytest-playwright параметрів прибирають, бо custom launcher їх уже не читає. Загальний висновок: не можна одночасно очікувати, що plugin і власний код керуватимуть тим самим browser lifecycle.
Наступні failures виявляються не проблемою браузера, а конфігурацією: fixture використовує неправильний base URL. Після виправлення route тести знову доходять до цільової сторінки.
Також виправляється неправильний виклик pytest.skip() на рівні module collection. Для декларативного пропуску тесту потрібен @pytest.mark.skip або module-level marker; інакше pytest може пропустити весь модуль або зупинити collection не так, як очікувалося.
Негативний login test відкриває sign-in route напряму. Перехід через home page і клік Login не є частиною його контракту, лише додає час і ще одну можливу причину падіння.
Після прямої навігації та reuse page параметризований тест виконується швидше, але все одно впирається в rate limit. У демонстрації додається двосекундна пауза. Це придатна тимчасова діагностика, але стабільне рішення має узгодити навантаження з test environment: контрольовані test accounts, documented quota, backoff або окремий seam для form validation.
Наприкінці ще раз простежується lifecycle: browser може жити всю session, context — module або function, page — відповідно до вимог тесту. Чим довше живе ресурс, тим вища швидкість і тим більший ризик state leakage. Scope обирають не за принципом «найширший = найкращий», а за найдовшою безпечною межею.
Увага
Для HTTP 429 немає універсальної двосекундної паузи
429 означає забагато запитів за визначений сервером період. Response може містити Retry-After; quota й backoff визначає test environment, а не pytest або Playwright.
Перед комітом переглядаються видалені plugin parameters, custom fixtures, requirements і параметризовані cases. У підсумку кожна ітерація має читабельний ID, test data винесені з тіла сценарію, а browser reuse виконується через явний fixture graph.
Практика після уроку — додати власні cases до таблиці, дати їм змістовні IDs, намалювати залежності fixtures і перевірити setup/teardown кожного scope. Окрема перевірка має довести, що попередня ітерація не залишає авторизацію або client-side state наступній.