Ось твій урок у стилі CS50! Сідай зручніше, сьогодні ми будемо ламати (і рятувати) інтернет.
🎓 Урок: E2E Тестування (Playwright) — Ваш персональний робот-тестувальник
Привіт, друзі! Радий вас бачити.
Сьогодні ми поговоримо про те, що дозволяє розробникам спати спокійно, поки їхній код працює в продакшені. Ми поговоримо про End-to-End (E2E) тестування.
Для прикладів ми використаємо Playwright — сучасний, швидкий та потужний інструмент (хоча принципи, які ми розглянемо, так само чудово працюють і в Cypress).
1. 🔥 Вступ: Чому ми взагалі тут?
Уявіть ситуацію. Ви працювали всю ніч, дописуючи функцію "Купити квиток" для сайту кінотеатру. Логіка складна: перевірка місць, оплата карткою, відправка квитка на пошту. Ви написали купу юніт-тестів (unit tests), які перевіряють, чи правильно рахується сума. Все зелене. Ви деплоїте код.
І тут... катастрофа.
Користувач натискає кнопку "Купити", а нічого не відбувається. Чому? Тому що кнопка "Купити" випадково перекрита невидимим блоком реклами. Або тому, що база даних не встигла відповісти, а фронтенд вже показав помилку.
Питання до вас: Чи знають ваші юніт-тести (які перевіряють чи 2 + 2 = 4) про те, що кнопку на екрані неможливо натиснути?
Ні. Їм байдуже.
Навіщо нам E2E?
Ось тут на сцену виходить End-to-End тестування. Якщо юніт-тести — це перевірка окремих деталей двигуна на заводі, то E2E — це тест-драйв автомобіля. Ви сідаєте за кермо, заводите двигун, тиснете на газ і дивитесь, чи їде машина.
Ми створюємо робота, який прикидається реальною людиною: він відкриває браузер, клікає мишкою, вводить текст і перевіряє, чи сайт працює так, як очікує користувач.
Без цього ви — сапер, який сподівається на удачу. З E2E — ви інженер, який перевірив систему в бою.
2. 🧠 Теоретична база (Що там "під капотом"?)
Давайте розберемося, як це працює, без нудних визначень.
Як це виглядає фізично?
Коли ви запускаєте тест на Playwright, відбувається магія: 1. Скрипт запускає справжній браузер (Chromium, Firefox або WebKit). Він може бути видимим (headed) або невидимим (headless — для швидкості). 2. Він переходить на вашу сторінку. 3. Він шукає елементи (кнопки, поля) так само як око користувача. 4. Він взаємодіє з ними.
Три кити E2E тестування (Запам'ятайте це!):
-
Локатори (Locators/Selectors): Це спосіб вказати роботу, куди дивитися.
- Погано: "Знайди 3-й
divзгори, в якому єspan". (Це ламко). - Добре (CS50 Style): "Знайди кнопку з текстом 'Увійти'". Це стійко. Playwright любить семантику:
getByRole('button', { name: 'Log in' }).
- Погано: "Знайди 3-й
-
Дії (Actions): Що робити з елементом? Клікнути (
click), написати текст (fill), натиснути Enter (press). -
Асерти (Assertions/Expectations): Це момент істини. Ми кажемо роботу: "Очікуй, що після кліку заголовок сторінки стане 'Мої замовлення'". Якщо очікування не справдилось — тест падає, сирена виє, ви виправляєте баг.
Інтуїтивно: Уявіть, що ви пишете інструкцію для дуже слухняної, але дуже буквальної дитини: "Піди на кухню -> Візьми червону чашку -> Налий води -> Перевір, чи чашка повна".
3. 🧪 Приклади: Від "Hello World" до Реальності
Давайте подивимось на код. Я буду писати синтаксис Playwright, бо він зараз стандарт індустрії.
Рівень 1: Найпростіший тест
Завдання: Зайти на Google і перевірити, що в заголовку є слово "Google".
import { test, expect } from '@playwright/test';
test('Google має правильний заголовок', async ({ page }) => {
// 1. Відкриваємо сторінку
await page.goto('https://google.com');
// 2. Перевіряємо (Асерт)
await expect(page).toHaveTitle(/Google/);
});
Що тут відбулося? Ми використали await, тому що браузеру треба час, щоб завантажити інтернет. Ми не біжимо попереду батька в пекло.
Рівень 2: Типовий сценарій (Логін)
Чого ми очікуємо? Користувач вводить пошту, пароль, тисне кнопку і бачить свій профіль.
test('Користувач може увійти в систему', async ({ page }) => {
await page.goto('https://my-app.com/login');
// Знаходимо поля і заповнюємо їх
// Зверніть увагу: ми шукаємо "Поле", а не просто div з id
await page.getByLabel('Email').fill('student@cs50.harvard.edu');
await page.getByLabel('Password').fill('verystrongpassword');
// Клікаємо кнопку
await page.getByRole('button', { name: 'Увійти' }).click();
// МОМЕНТ ІСТИНИ: Чи перенаправило нас?
// Чи бачимо ми привітання?
await expect(page.getByText('Привіт, студенте!')).toBeVisible();
});
Чому результат такий?
Playwright розумний. Коли ми кажемо click, він чекає, поки кнопка перестане рухатись і стане клікабельною. Коли ми кажемо expect(...).toBeVisible(), він чекає кілька секунд, поки сервер відповість і з'явиться текст. Вам не треба писати sleep(5000). Це — антипатерн!
Рівень 3: Складний кейс (Кошик товарів)
Тут ми перевіряємо цілий потік (flow).
- Відкрити магазин.
- Знайти товар "CS50 Hoodie".
- Додати в кошик.
- Перейти в кошик.
- Перевірити, що там 1 товар.
test('Додавання товару в кошик', async ({ page }) => {
await page.goto('/shop');
// Шукаємо картку товару і кнопку всередині неї
const productCard = page.locator('.product-item').filter({ hasText: 'CS50 Hoodie' });
await productCard.getByRole('button', { name: 'Add to Cart' }).click();
// Переходимо в кошик
await page.getByTestId('cart-icon').click();
// Перевіряємо
await expect(page.locator('.cart-item')).toHaveCount(1);
await expect(page.getByText('CS50 Hoodie')).toBeVisible();
});
4. 🛠 Практична частина
Тепер ваша черга. Не бійтеся помилятися, компілятор не кусається.
Завдання 1: Реплікація
Напишіть тест для Вікіпедії. Зайти на wikipedia.org, ввести в пошук "Україна", натиснути Enter і перевірити, що заголовок статті містить слово "Україна".
Завдання 2: Негативний тест (Дуже важливо!)
Змініть тест логіна з Рівня 2. Введіть неправильний пароль.
Очікування: expect(page.getByText('Невірний пароль')).toBeVisible().
Чому це важливо? Ми маємо знати, що система не пускає хакерів.
Завдання 3: Мобільна версія
Playwright дозволяє емулювати iPhone.
Додайте налаштування ...devices['iPhone 12'] у конфіг (умовно). Як зміниться ваш тест, якщо на мобільному меню ховається під "бургер" (три смужки)?
Підказка: Вам доведеться спочатку клікнути на іконку меню, а потім на пункт меню.
Завдання 4: Міні-кейс У вас є форма "Підписатися на розсилку". Після вводу email і кліку, форма зникає і з'являється текст "Дякуємо!". Напишіть сценарій цього тесту (можна словами або псевдокодом).
Завдання 5: А що, якщо...
Що станеться з вашим тестом, якщо розробник змінить текст на кнопці з "Увійти" на "Log In"?
Відповідь: Тест впаде. Як зробити його стійкішим? (Можливо, використовувати data-testid атрибути, які не змінюються при редизайні).
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі в E2E?
❌ Помилки новачків:
- Тестувати все підряд. E2E тести повільні. Не перевіряйте ними колір кожної кнопки або валідацію кожного поля (це робота unit-тестів). Перевіряйте критичні шляхи користувача.
- Жорсткі прив'язки (XPath).
//*[@id="app"]/div[2]/div[5]/span. Якщо розробник додасть одинdiv, ваш тест зламається. Це "крихкі" тести. Профі думає: "Як користувач знайде цю кнопку? За текстом або функцією". - Фіксовані очікування (Sleep).
await page.waitForTimeout(5000). Ніколи так не робіть! Це робить тести повільними і ненадійними. Покладайтеся на розумніexpect.
🧠 Як думає профі:
- "Мій тест має бути ізольованим. Якщо тест А впав, тест Б має працювати".
- "Я тестую поведінку, а не реалізацію". Мені байдуже, чи написаний сайт на React чи Angular. Мені важливо, чи можу я купити товар.
- "Якщо тест падає іноді, а іноді проходить (flaky test) — це найгірше зло. Його треба виправити негайно або видалити".
6. 🧩 Підсумок
Отже, що ми сьогодні зробили? Ми не просто написали код. Ми створили віртуального користувача, який буде невтомно перевіряти ваш сайт 24/7, поки ви п'єте каву.
Тепер ви вмієте: 1. Розуміти, навіщо потрібні E2E тести (імітація реального користувача). 2. Знаходити елементи на сторінці так, як це роблять люди (за текстом, роллю). 3. Писати базові сценарії на Playwright. 4. Думати про надійність тестів.
Що далі? У наступних серіях ми поговоримо про CI/CD. Як зробити так, щоб ці роботи запускалися автоматично щоразу, коли ви завантажуєте код на GitHub. Але це вже зовсім інша історія.
А поки що — пишіть тести, ламайте їх, і нехай ваші білди завжди будуть зеленими!
Це був E2E. Побачимось!