QA Co-pilot
91 subscribers
266 photos
1 video
44 links
QA Co-pilot 🚀

Ваш другий пілот у світі тестування.

👨‍💻 Для кого: Для тестувальників-практиків, які хочуть рости.
🎯 Про що: Делегуємо рутину нейромережам, прискорюємо роботу та звільняємо час на головне.
Чого тут немає: Нудної теорії та води.
Download Telegram
🎙 Рентген співбесіди: "Чорний ящик", або Питання, яке валить 80% Middle QA

Запускаємо нову рубрику «Рентген співбесіди», де ми розбираємо каверзні питання з технічних інтерв'ю та аналізуємо, що насправді хочуть почути від вас ліди. ☕️

Сьогодні на операційному столі класична пастка для перевірки архітектурного мислення.

Питання від інтерв'юера:
"Ви тестуєте наш бекенд. Він розраховує вартість доставки, звертаючись до стороннього API (наприклад, FedEx або Нової Пошти). Доступу до їхньої бази у вас немає, тестового середовища у них теж немає, а кожен ваш запит до їхнього API коштує бізнесу реальних грошей. Ваші дії?"


Як відповідає Junior / Слабкий Middle (Червоний прапорець):
1️⃣ "Ну, я попрошу розробників щось придумати або зробити мені тестовий енв." (Перекладання відповідальності).

2️⃣ "Буду тестувати обережно, щоб не витратити багато грошей." (Відсутність розуміння автоматизації та навантаження).

3️⃣ "Замокаю запити на фронтенді (в Cypress/Playwright), щоб фронт не смикав бекенд." (Катастрофа! Ви залишили бекенд узагалі без тестування).

Ліду не потрібен тестувальник, який боїться системи. Йому потрібен інженер, який вміє ізолювати систему.

Як відповідає Senior QA (Ідеальна відповідь):
"Я не буду чіпати фронтенд. Я ізолюю наш бекенд від зовнішнього світу за допомогою Mock-сервера (наприклад, WireMock або Mountebank)."


Далі ви добиваєте інтерв'юера трьома кроками:

1️⃣ Підміна реальності (Stubbing): Я попрошу DevOps (або сам через Docker) підняти локальний WireMock. Скажу бекенду: "Тепер ти ходиш не на api.fedex.com, а на localhost:8080". І на цьому локальному сервері я налаштую заглушки: якщо бекенд питає ціну для Києва — повертай JSON із цифрою 100.

2️⃣ Тестування хаосу (Fault Injection):
Реальне API може "впасти". Я налаштую свій WireMock так, щоб він повертав 500 Internal Server Error або, ще краще, робив затримку (Delay) у 15 секунд. І я перевірю, чи не "ляже" наш власний бекенд через таймаути і чи не заблокується наша база даних в очікуванні відповіді.

3️⃣ Контрактне тестування:
Щоб мої заглушки не застаріли, я ініціюю написання Contract Tests. Ми будемо раз на добу перевіряти структуру реального API постачальника, щоб переконатися, що вони не змінили поле price на cost без попередження.


Висновок: Цим питанням ліди перевіряють, чи розумієте ви різницю між клієнтськими моками (на фронті) та інфраструктурними моками (на рівні бекенду). Senior QAце ілюзіоніст, який може створити для бекенду ідеально контрольовану віртуальну реальність.

А як би ви відповіли на це питання до прочитання поста? 👇
🔥 — WireMock — наше все, завжди ізолюю сторонні API!
👀 — Я б сказав про моки на фронтенді... Тепер зрозумів помилку.
🤯 — Я б просто тестував на проді. Гуляти так гуляти!
1
💩 Код з душком: Асинхронна м'ясорубка, або Чому ваші тести "успішно" проходять за 0.1 секунди

Привіт, екіпаж! Рубрика «Код з душком» на зв'язку. Сьогодні препаруємо одну з найпідступніших пасток у TypeScript/JavaScript, у яку регулярно потрапляють як розробники, так і AQA-інженери при написанні API чи E2E тестів. ☕️

Подивіться на цей фрагмент коду (наприклад, у Playwright або Jest). Знайдіть причину, чому цей тест — це міна уповільненої дії:

//  Як часто пишуть (Антипатерн "Сліпий цикл")
test('Повинен створити трьох користувачів', async () => {
const users = ['Ivan', 'Petro', 'Anna'];

users.forEach(async (user) => {
const response = await api.createUser(user);
expect(response.status).toBe(200);
});

console.log(' Всі юзери успішно перевірені!');
});

Здається, все логічно: біжимо по масиву, чекаємо створення кожного юзера, перевіряємо статус.

Чому цей код тхне:
Ви запускаєте тест, і він проходить за феноменальні 50 мілісекунд. Ви відчуваєте себе генієм оптимізації. Але насправді тест взагалі нічого не перевірив.

Метод масиву forEach за своєю природою — синхронний. Він абсолютно ігнорує ключове слово await всередині свого колбеку. Він просто вистрілює три мережеві запити в порожнечу один за одним і миттєво йде далі. Тест завершується і світиться зеленим ЩЕ ДО ТОГО, як бекенд встигає відповісти.

А якщо через секунду бекенд поверне помилку 500? Ваші expect впадуть у фоні, викликавши Unhandled Promise Rejection, що зробить пайплайн нестабільним (flaky), але сам тест формально залишиться "зеленим".


Як виглядає код після рев'ю Senior-інженера:

Варіант 1: Послідовне виконання (Безпечно):
test('Повинен створити трьох користувачів', async () => {
const users = ['Ivan', 'Petro', 'Anna'];

// Цикл for...of поважає await і чекає завершення кожної ітерації
for (const user of users) {
const response = await api.createUser(user);
expect(response.status).toBe(200);
}
});


Варіант 2: Паралельне виконання (Швидко і правильно)

test('Повинен створити трьох користувачів', async () => {
const users = ['Ivan', 'Petro', 'Anna'];

// Запускаємо всі запити одночасно і чекаємо на їх РЕАЛЬНЕ завершення
await Promise.all(
users.map(async (user) => {
const response = await api.createUser(user);
expect(response.status).toBe(200);
})
);
});


Висновок для QA:
Якщо ваш API-тест, який має створити 10 сутностей у базі даних, виконується за долю секунди — ви не написали найшвидший фреймворк у світі. Ви написали мовчазну галюцинацію. Ніколи не використовуйте forEach разом із асинхронними операціями.

А вас колись кусав forEach з промісами? 👇
🔥 — О так, класика! Витратив години, поки зрозумів, чому тест "зелений" при мертвому бекенді.
👀 — Тільки for...of або Promise.all, я цю школу життя вже пройшов.
🤯 — Чекайте, я щойно зрозумів, чому мій скрипт для генерації тестових даних працює через раз... Пішов переписувати!
1
⚔️ Битва підходів: Junior vs. QA Architect (Як ми готуємо тестові дані)

Сьогодні в нашій "Битві підходів" розбираємо найдорожчий і найповільніший етап будь-якого E2E тестування — підготовку тестових даних (Data Setup або Preconditions). ☕️

Уявімо задачу: нам треба протестувати видалення користувача з таблиці.

Підхід Junior-автоматизатора (Через UI)
test('Повинен видаляти користувача', async ({ page }) => {
// 1. Спочатку створюємо юзера через інтерфейс (марнуємо 10 секунд)
await page.locator('#add-btn').click();
await page.locator('#name').fill('Test QA');
await page.locator('#save').click();
await expect(page.locator('.toast')).toHaveText('Створено!');

// 2. І тільки тепер починаємо сам тест на ВИДАЛЕННЯ
await page.locator('#delete-btn-Test-QA').click();
await expect(page.locator('.row')).not.toContainText('Test QA');
});


Чому це архітектурна катастрофа:
По-перше, ви спалюєте гроші компанії на інфраструктуру. Цей тест йтиме 15 секунд замість трьох.

По-друге, ви створюєте Каскадні падіння (Cascading Failures). Якщо завтра фронтендер випадково зламає форму створення користувача, у вас впаде тест на видалення! Ваш репорт покаже 50 червоних тестів, хоча насправді баг тільки в одному місці.


Підхід QA Architect (Через API)
test('Повинен видаляти користувача', async ({ page, request }) => {
// 1. Б'ємо напряму в бекенд. Створюємо юзера за 50 мілісекунд
const res = await request.post('/api/users', { data: { name: 'Test QA' } });
const user = await res.json();

// 2. Ізольовано тестуємо ТІЛЬКИ видалення через UI
await page.goto(`/users/${user.id}`);
await page.locator(`#delete-btn-${user.id}`).click();
await expect(page.locator('.row')).not.toContainText('Test QA');
});


Чому це шедевр:
Тест атомарний. Він перевіряє рівно одну фічу — видалення. Підготовка даних займає мілісекунди. Якщо форма створення зламана, цей тест все одно пройде і доведе, що механізм видалення працює ідеально.


Золоте правило: Інтерфейс користувача (UI) створений для тестування самого UI. Використовувати UI для підготовки даних (Preconditions) — це як забивати цвяхи мікроскопом. Для цього є API та прямі запити до бази даних.

А як у вас на проєкті генеруються дані перед E2E тестами? 👇
🔥 — Тільки API / База даних! Наші тести летять як ракета.
👀 — Робимо через UI... Так, тести йдуть по 2 години, але в нас "повна імітація юзера"!
🤬 — У нас взагалі один юзер admin@test.com на всю команду, хто перший зайшов, той і тестує!
1
📡 Tech Radar: Головне за тиждень без води (Випуск #2)

Привіт, QA Co-pilots! Понеділок — час тримати руку на пульсі. Ось що гриміло в IT минулого тижня 🔍

🔐 Масштабний злам Canvas LMS
Група ShinyHunters зламала Instructure — компанію, що стоїть за популярною платформою для навчання Canvas.

Постраждало до 275 мільйонів записів: імена студентів, ID, емейли та повідомлення. Платформа впала якраз під час фінальних іспитів у сотнях університетів та шкіл. Для QA-спільноти — черговий сигнал, що security testing — це не "потім", а "завжди".


🤖 Cloudflare скорочує 20% команди через AI
Cloudflare оголосила про скорочення понад
1 100 людей — 20% від усього штату.

Причина: внутрішнє використання AI зросло на 600% лише за три місяці. Того ж тижня Coinbase, Upwork та BILL анонсували аналогічні скорочення.

Тренд очевидний: AI-аугментація команд прискорюється. Питання до нас: як ми адаптуємо QA-процеси, якщо генерують код вже не тільки люди?


🧩 Alumnium — open-source AI-шар поверх Selenium і Playwright
8 травня вийшов Alumnium — open-source AI-інструмент, який працює одночасно з Selenium та Playwright.

Цікавий підхід: не замінювати фреймворки, а додати AI-шар зверху. Варто поглянути.


⚡️ Новий стандарт для GPU-кластерів
OpenAI, AMD, Broadcom, Intel, Microsoft та Nvidia спільно випустили специфікацію Multipath Reliable Connection (MRC) через Open Compute Project.

Відкритий протокол підвищує стійкість та продуктивність GPU-кластерів при масштабних AI-тренуваннях. Чим більше стандартизації — тим стабільніші середовища для тестування AI-систем.


🕵️ Критична вразливість у Palo Alto PAN-OS
CVE-2026-0300 — критична вразливість у PAN-OS від Palo Alto Networks. Дозволяє виконати код віддалено з правами root без жодної авторизації через компонент User-ID Authentication Portal. Якщо у вашій інфраструктурі є Palo Alto — апдейт поза чергою.


💡 Думка тижня
Головний бар'єр у тестуванні 2026 — не швидкість виконання, а якість сигналу. AI пришвидшує генерацію тестів, але нові помилки вже важче інтерпретувати. Більше тестів ≠ більше впевненості. Наша роль як QA — не просто запускати, а розуміти, що результат означає


А яка новина для вас найбільш резонує? 👇
#TechRadar #QACopilot #AITesting #Security #2026
👍2
⚔️ Битва підходів: Junior vs. QA Architect (Page Object Model — правильна архітектура)

Сьогодні розбираємо тему, яку всі "знають", але мало хто робить правильно — Page Object Model. Різниця між Junior і Architect тут не в тому, чи використовувати POM. А в тому, що саме ховати всередині. ☕️

Підхід Junior-автоматизатора (POM як "папка для локаторів")
// pages/LoginPage.ts
export class LoginPage {
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async fillEmail(email: string) {
await this.page.locator('#email').fill(email);
}

async fillPassword(password: string) {
await this.page.locator('#password').fill(password);
}

async clickSubmit() {
await this.page.locator('#submit').click();
}
}

// tests/login.spec.ts
test('Логін з валідними даними', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.fillEmail('user@test.com');
await loginPage.fillPassword('qwerty123');
await loginPage.clickSubmit();
await expect(page.locator('.dashboard-title')).toBeVisible();
});


Чому це антипатерн:
По-перше: Page Object перетворився на тонку обгортку над локаторами — і нічого більше. Вся логіка взаємодії досі живе в тесті.

По-друге, тест знає забагато: він знає послідовність дій, знає що перевіряти, знає як влаштована сторінка. Якщо завтра #submit стане [data-testid="login-btn"] — йдеш шукати всі місця в тестах руками.

По-третє: такий POM не дає жодної ізоляції — це просто перейменовані page.locator() виклики.


Підхід QA Architect (POM як сервісний шар)
// pages/LoginPage.ts
export class LoginPage {
private readonly emailInput = this.page.getByLabel('Email');
private readonly passwordInput = this.page.getByLabel('Password');
private readonly submitButton = this.page.getByRole('button', { name: 'Sign in' });
private readonly errorMessage = this.page.getByTestId('auth-error');

constructor(private readonly page: Page) {}

// Публічний API сторінки — одна дія, один метод
async login(email: string, password: string): Promise<void> {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}

async expectErrorMessage(text: string): Promise<void> {
await expect(this.errorMessage).toHaveText(text);
}
}

// pages/DashboardPage.ts
export class DashboardPage {
private readonly title = this.page.getByRole('heading', { name: 'Dashboard' });

constructor(private readonly page: Page) {}

async expectLoaded(): Promise<void> {
await expect(this.title).toBeVisible();
}
}

// tests/login.spec.ts
test('Логін з валідними даними', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);

await loginPage.login('user@test.com', 'qwerty123');
await dashboardPage.expectLoaded();
});

test('Логін з невалідним паролем', async ({ page }) => {
const loginPage = new LoginPage(page);

await loginPage.login('user@test.com', 'wrongpass');
await loginPage.expectErrorMessage('Invalid credentials');
});


Чому це шедевр:
Тест читається як специфікація, а не як інструкція для браузера. Page Object інкапсулює всю логіку взаємодії — тест не знає жодного локатора.

Локатори побудовані на семантиці (getByRole, getByLabel) — стійкі до рефакторингу верстки. Assertions теж живуть у Page Object — тест перевіряє поведінку, а не деталі реалізації. Зміна UI = правка в одному файлі, а не хірургія по всьому репозиторію.


Золоте правило: Page Object — це не папка для локаторів. Це публічний API вашої сторінки. Якщо ваш тест досі знає про #submit або .dashboard-title — у вас не POM, у вас ілюзія абстракції.

А який рівень POM у вас на проєкті?
🔥 — Повна інкапсуляція: тести не знають жодного локатора, тільки методи
👀 — Десь між: локатори в POM є, але логіка частково тече в тести
🤬 — POM є у назві папки, але по суті — просто locator() обгорнутий в клас
🧨 Руйнівники IT-міфів #1 МІФ: "QA гальмує розробку"

Привіт, екіпаж! Сьогодні беремо найпопулярніше виправдання для скорочення QA-команд і кладемо його на стіл розтину. Не думки, не відчуття — тільки цифри. ☕️


🧠 Звідки міф?
Продакт дивиться на спринт: розробники кодять, QA "знаходить проблеми" і повертає задачі назад. Здається, що QA — це та ланка, яка сповільнює потік. Логіка зрозуміла. Логіка хибна.


🔬 Що кажуть дані

1️⃣ Правило ×100 (IBM Systems Sciences Institute):
Виправлення дефекту на стадії дизайну коштує в 6 разів дешевше, ніж під час імплементації. Той самий баг, знайдений після релізу в продакшені — у 100 разів дорожче, ніж на стадії підтримки.

2️⃣ Простими числами: баг вартістю $100 на етапі планування → $10 000 у продакшені — бо він тягне за собою каскадні ефекти в суміжних системах, потребує координації між командами і затримує наступні релізи.

3️⃣ Вартість простою:
Для enterprise-компаній середня вартість однієї години критичного даунтайму перевищує $300 000. Деякі інциденти легко перетинають позначку $1 млн на годину.

4️⃣Реальний кейс з фінтеху:
Баг у заокругленні, знайдений QA на стадії тестування, обійшовся у 4 години роботи ($400). Той самий тип помилки, що потрапила у прод, зачепила 12 000 транзакцій — і потягнула за собою екстрений патч, звірку даних і регуляторне розкриття.

5️⃣ Де насправді губиться час розробників:
Dev-команди витрачають у середньому 30–50% свого часу на виправлення багів і незапланований rework. Це не QA гальмує — це баги без QA з'їдають половину capacity команди.


📐 Математика для скептиків
Правильно побудована QA-програма для команди з 20 розробників коштує $120 000–180 000 на рік. Математика зазвичай виправдовує себе вже в перший квартал.

Один інцидент з checkout-сторінкою на e-commerce з трафіком $500K/день при 2% відмов — це $10 000 на день прямих втрат. Тиждень без виявлення — $70 000.

Висновок: QA не уповільнює розробку. QA уповільнює потрапляння дефектів у прод — а це принципово різні речі.


💡 Як змінити фрейм в голові у стейкхолдера
Не "QA знайшов 12 багів і повернув задачу".

А — "QA зекономив команді 3 тижні рефакторингу і $200K потенційних втрат".

QA — це не гальмо. Це страховий поліс, який окупається щоразу, коли ти його не використовуєш. 🛡


А як у вас реагують на QA в команді?
🔥 — QA — повноцінні партнери, нас залучають з першого дня спринту
👀 — "Ну давайте швиденько протестуємо перед релізом"
🤯 — "Нащо QA, у нас розробники самі тестують"
🧩 Задачка з Senior-співбесіди: Мандрівки в часі

Сьогодні говоримо про фічі, які змушують нас чекати. Час — наш головний ворог у CI/CD, особливо коли бізнес-логіка жорстко зав'язана на таймери, календарі або терміни дії токенів. ☕️

Умова задачі:
Маємо Angular-додаток зі сторінкою акцій.

Бізнес-вимога: "Користувач бачить банер 'Знижка 50%', який діє рівно 24 години з моменту реєстрації".

Логіка відліку реалізована на фронтенді — компонент звіряє поточний час браузера з датою закінчення акції. Якщо минуло 24 години і 1 секунда — банер має зникнути.


Питання:
Як перевірити зникнення банера через 24 години в E2E-тесті на Playwright, щоб тест пройшов за секунду, був стабільним і не зламав інфраструктуру?


Голосуйте нижче! А розгорнуте пояснення, чому перші два варіанти гарантовано призведуть до "почервоніння" сусідніх тестів або витоків пам'яті, я залишу в коментарях ввечері. 👇
Як перевірити зникнення банера через 24 години в E2E-тесті на Playwright, щоб тест пройшов за секунду, був стабільним і не зламав інфраструктуру?
Anonymous Quiz
20%
1️⃣ Змінити системний час на самій віртуальній машині в CI/CD
10%
2️⃣ Заінжектити кастомний JS-мок для window.Date через addInitScript
70%
3️⃣ Використати нативний API Playwright: page.clock.advance()
🕵️‍♂️ Інженерний детектив: Справа про прозорий бар'єр (або Гріх force: true)

Привіт, екіпаж! Середа — час нашої рубрики «Інженерний детектив». Сьогодні ми розслідуємо "злочин", через який автоматизатори втрачають нерви, а девелопери божаться, що «на локалці все працює ідеально». ☕️

Сцена злочину (Симптоми):
Ви написали базовий тест на авторизацію. У вас локально все літає. Закидаєте в CI/CD — тест періодично падає на елементарному кроці:

Error: locator.click(): Element <button id="submit">Login</button> is intercepted by <div class="spinner-overlay"></div>

Ви відкриваєте Playwright Trace Viewer, дивитесь на скріншот у момент падіння... і не розумієте. Екран абсолютно чистий. Кнопка Login ідеально видима, ніякого лоадера (spinner-overlay) поверх неї немає. Містика?


Хибний слід (Як робить Джун):
"Дурний фреймворк" — думає джун і вписує магічну пігулку:

await page.locator('#submit').click({ force: true }).

Тест миттєво стає зеленим. Джун задоволено закриває таску. А через тиждень сапорт розривається від скарг реальних користувачів, які не можуть натиснути кнопку логіну.


🔍 Хід розслідування (Що сталося насправді):
Playwright ніколи не бреше. На відміну від старого Selenium, він намагається діяти як жива людина з реальною мишкою. Якщо він каже, що клік перехоплено, значить кнопка накрита чимось фізичним.

Ми йдемо у вихідний код фронтенду (у наш улюблений Angular) і дивимось на CSS цього лоадера. Знаходимо там такий код:

.spinner-overlay.hidden {
opacity: 0;
transition: opacity 0.5s ease;
}


Ось він, вбивця! Фронтендер зробив плавне зникнення лоадера. Візуально він став повністю прозорим (opacity: 0), але фізично він залишився висіти в DOM-дереві, перекриваючи весь екран як невидиме броньоване скло!

Коли джун використав force: true, він змусив Playwright проігнорувати закони фізики і відправити JavaScript-івент click напряму в кнопку крізь скло. Але реальний користувач пальцем чи курсором так не зможе — він клікатиме по прозорому лоадеру.

Вирок та Рішення:
1️⃣ Лікуємо фронтенд: Йдемо до розробника і просимо додати класу .hidden властивість pointer-events: none; (щоб кліки проходили крізь нього) або взагалі прибирати лоадер з DOM за допомогою *ngIf.

2️⃣ Лікуємо тести: Ніколи не використовуйте force: true, щоб обійти помилку intercepted. Замість цього явно дочекайтеся, поки оверлей зникне фізично:
await expect(page.locator('.spinner-overlay')).toBeHidden();


Золоте правило: Якщо Playwright не може кудись клікнути — у 99% випадків реальний юзер теж не зможе. Автотест знайшов реальний баг UI/UX, а ви ледь не сховали його за force: true.

Зізнавайтесь, часто грішили форсованими кліками, щоб "швидко пофіксити" падаючий тест? 👇
🔥 — Було діло, грішний.
👀 — Тільки toBeHidden(), я за чистий код!
🤯 — Пішов перевіряти свої тести, здається, я пропустив прозорий банер на проді...
1
🗂 Ультимативна шпаргалка: Локатори Playwright (2018 vs 2026)

Коротка шпаргалка про те, як відучити себе писати "крихкі" тести. Зберігайте в обране і кидайте на код-рев'ю. ☕️

Застарілий підхід (CSS / XPath) Сучасний підхід (User-Facing API)

1️⃣ Кнопки та посилання
page.locator('.btn-primary.submit')

page.getByRole('button', { name: 'Зберегти' })

Чому: Перевіряє не лише текст, а й доступність (Accessibility). Якщо розробник випадково зробить кнопку div-ом, тест впаде.

2️⃣ Пошук статичного тексту
page.locator('//div/span[contains(text(), "Успіх")]')

page.getByText('Успіх', { exact: true })

Чому: Працює незалежно від того, як фронтендер перетасує div та span у компоненті.

3️⃣ Специфічні блоки (картки, аватари)
page.locator('.user-card .avatar-img')

page.getByTestId('user-avatar')

Чому: CSS-класи належать дизайнерам (вони їх змінюють). Атрибути data-testid належать QA — їх ніхто не чіпає.

4️⃣ Точковий пошук у списках/таблицях (Chaining)
page.locator('.table-row:has-text("Ivan") >> .delete-btn')

page.getByRole('row', { name: 'Ivan' }).getByRole('button', { name: 'Видалити' })

Чому: Читається як звичайна англійська мова, нуль магії.

Золоте правило: Шукайте елементи так, як їх шукає реальний юзер (за текстом та роллю), а не так, як їх бачить браузер (за селекторами).

А на чому сидите ви? 👇
🔥 — Тільки getByRole та getByText, класика.
👀 — Мій бестфренд — це data-testid.
🤬 — Я фанат XPath, мене вже не перевчити!
🔥 Прожарка інструментів: Cucumber (і казки про BDD)

Привіт, екіпаж! Четвер — час нашої безжальної прожарки. Сьогодні кидаємо на гриль інструмент, який обіцяв нам ідеальний світ, де нетехнічні менеджери самі пишуть автотести зрозумілою мовою. Зустрічайте головного пожирача часу — Cucumber (Gherkin). ☕️

🟢 Як нам це продавали (Очікування):
"Ви пишете тести у форматі Given / When / Then. Бізнес-аналітики, мануальники та клієнти читають їх, як книгу, і всі щасливі!"

🥩 Прожарка (Сувора реальність):
Бізнесу байдуже: Давайте чесно. Жоден продакт-менеджер у здоровому глузді не відкриває ваш репозиторій на GitHub. Їм потрібен зелений статус у пайплайні, а не ваші поеми.

Подвійний податок на підтримку: Щоб клікнути одну кнопку, ви пишете текст у .feature файлі. Потім пишете регулярний вираз (@When("^I click...$")), щоб зв'язати текст із кодом. І лише потім — сам код кліку. Змінився дизайн? Переписуйте всі три шари.

Жах зі складними даними: Спробуйте передати багаторівневий JSON або масив динамічних об'єктів через текстовий Gherkin-файл. Ваш код перетвориться на гігантські милиці з нечитабельними таблицями.


⚖️ Вердикт QA Co-pilot:
Якщо ви стартуєте проєкт у 2026-му — залиште Cucumber в епосі 2015-го. Хочете, щоб бізнес розумів, що перевіряє тест? Використовуйте test.step() у Playwright та прикрутіть Allure Report. Пишіть код кодом, а документацію залиште для Confluence.


А ви були в секті BDD? 👇
🔥 — На жаль, так... Пишу Given/When/Then і плачу.
👀 — Випиляли Cucumber рік тому, пайплайни стали літати!
🤬 — Не згоден, у нас замовник РЕАЛЬНО сам читає тести! (Ви взагалі існуєте?)
💯1
🔍 Рентген співбесіди: «Ми вам передзвонимо» або що насправді шукає рекрутер

Буває так: ти розклав по поличках роботу Playwright, пояснив різницю між Promise.all та Promise.allSettled, і навіть задизайнив фреймворк на ходу. Але в кінці отримуєш стандартне «дякуємо, ми на зв'язку».

Давайте «просвітимо» рентгеном, які приховані патології в софт-скілах або технічних відповідях найчастіше стають причиною відмови.

🦴 «Скелет в шафі» технічного боргу
Коли тебе питають про архітектуру, а ти розповідаєш лише про те, як писати селектори. Рекрутер бачить не Middle AQA, а людину, яка просто автоматизує ручні кейси без розуміння стратегії.

🔹Діагноз: Відсутність системного мислення.

🔹Лікування: Говори про CI/CD, звіти, інтеграцію з Test IT та обробку флекі-тестів за допомогою ретраїв.


🧪 «Магічне» тестування (No-Code ілюзія)
Якщо ти занадто сильно покладаєшся на ШІ-інструменти або no-code рішення, не розуміючи, як вони працюють «під капотом», на рентгені це виглядає як купа іржавих шестерень, заклеєних синьою ізолентою.

🔹Діагноз: Низька технічна експертиза.

🔹Лікування: Покажи, що ти знаєш DOM-дерево Angular краще за будь-який дрон, і можеш написати чистий код навіть на серветці.


🧬 Ізоляція замість інтеграції
Кандидат ідеально знає свій «чорний ящик» API, але поняття не має, як дані потрапляють з фронтенда в БД або як працює аутентифікація через Bearer токени.

🔹Діагноз: Обмежений кругозір.

🔹Лікування: Вивчай DevTools як свої п'ять пальців — від нетворк-графів до спуфінгу геолокації.


🚩 Red Flags на знімку:
🔹Хардкод таймаути: Якщо на питання про очікування ти кажеш await page.waitForTimeout(5000), десь у світі сумує один лід.

🔹Ігнорування витоків пам'яті: Тести проходять, але браузер «зжирає» всю RAM на сервері? Рентген покаже це як «цифрових привидів», що душать твоє залізо.


Порада дня: Співбесіда — це не допит, а перевірка сумісності твого «коду» з культурою команди. Будь як той архітектор з майбутнього: спокійним, впевненим і завжди з чітким планом автоматизації.

А які «діагнози» ставили вам на співбесідах? Пишіть у коментарях! 👇

#QA #AQA #Testing #Interview #Career #Playwright #Angular
💯2
💩 Код з душком: Перехресне запилення, або Чому ваші тести падають у CI/CD

Привіт, екіпаж! П'ятниця — час для генерального прибирання у ваших репозиторіях. Сьогодні ми препаруємо одну з найнебезпечніших інженерних хвороб — sharing state (обмін станом) між тестами. Це той випадок, коли ви використовуєте глобальні змінні для передачі даних від одного тесту до іншого. ☕️

Знайдіть проблему в цьому коді:
//  Як пишуть джуни (Антипатерн "Глобальний наркоман")

// Десь у глобальному скоупі файлу
let lastCreatedUserId: string;

test('Тест 1: Створити юзера', async ({ request }) => {
const newUser = await request.post('/api/users', { data: { name: 'Bro' } });
// Зберігаємо ID у глобальну змінну для наступного тесту
lastCreatedUserId = (await newUser.json()).id;
});

test('Тест 2: Видалити створеного юзера', async ({ request }) => {
// Використовуємо ID, який створив ПОПЕРЕДНІЙ тест
await request.delete(`/api/users/${lastCreatedUserId}`);
});


Чому цей код тхне:
На вашій локальній машині це може працювати. Але в CI/CD Playwright за замовчуванням запускає тести паралельно (в різних воркерах/процесах).

Коли воркер №2 почне виконувати «Тест 2», глобальна змінна lastCreatedUserId у його процесі буде порожньою (undefined), бо «Тест 1» виконувався в іншому воркері №1. У вас "червоний" пайплайн, а ви витрачаєте пів дня, намагаючись зрозуміти, чому тест на видалення не бачить ID.


Як це виглядає після код-рев'ю Senior-інженера:
// Ідеально чистий код (Повна атомарність)

test('Повинен видаляти користувача', async ({ request }) => {
// 1. Створюємо юзера ІЗОЛЬОВАНО всередині одного тесту
const newUser = await request.post('/api/users', { data: { name: 'IsolatedBro' } });
const userId = (await newUser.json()).id;

// 2. Використовуємо ID тут же
await request.delete(`/api/users/${userId}`);
});


Золоте правило: Кожен тест має бути повністю атомарним. Тест має сам створювати потрібні дані, виконувати дію і сам за собою прибирати (якщо це необхідно). Жодних глобальних змінних для передачі даних!

А ваші тести бігають у паралель, чи ви боїтесь, що вони "перепиляться" даними? 👇
🔥 — Тільки атомарні тести, тільки паралельний запуск!
👀 — Грішимо глобальними змінними, тому запускаємо по одному...
🤯 — Мої тести впали вчора, тепер я знаю чому!
🔥21
П'ятниця: Час деплоїти... себе в паб! 🍻

Робочий тиждень офіційно переходить у статус «Done». Реліз-кандидат тижня готовий, баги дбайливо перенесені в беклог на понеділок, а це значить — час для справжнього офлайн-нетворкінгу. ☕️

Якраз до п'ятниці я випустив трек про легендарний Портер Паб — місце, де було випито тисячі літрів пива під обговорення архітектури та падаючих пайплайнів. 🎸

Слухаємо, заряджаємось і йдемо відпочивати. Зустрінемось за баром!

https://youtu.be/95i7fPiNaZE?si=n4TCjaD4swKUqEeK
🧨 Руйнівники IT-міфів: "Покриємо все E2E тестами і будемо спати спокійно"

Привіт, екіпаж! Сьогодні розбираємо найдорожчу ілюзію, яку часто намагаються продати бізнесу, або в яку вірять автоматизатори на початку кар'єри. ☕️

Міф: "Нам треба стовідсоткове покриття! Давайте напишемо автотести на Playwright/Cypress для всіх можливих сценаріїв: кожну валідацію поля, кожну помилку сервера, кожен тултип. Чим більше UI-тестів, тим якісніший продукт!"

💥 Реальність (Бум!):
Вітаю, ви щойно побудували антипатерн «Ріжок морозива» (Ice Cream Cone) замість Піраміди тестування. Це інфраструктурне самогубство.

Повільність: 500 важких UI-тестів будуть крутитися в CI/CD годинами, блокуючи релізи та з'їдаючи гроші на сервери.

Крихкість: UI — це найнестабільніший шар. Дизайнер змінив відступ, фронтендер переписав компонент — і у вас «почервоніло» 50 тестів, хоча бізнес-логіка працює.

Пекельна підтримка: Ви будете витрачати 80% часу на лагодження старих тестів, а не на покриття нових фіч.


Як має бути (Підхід Архітектора):
E2E (UI) тести — це снайперська гвинтівка, а не кулемет. Через UI покриваються тільки критичні бізнес-шляхи (Happy Paths): реєстрація, чекаут, оплата.
Усі перевірки граничних значень (50 варіантів невалідного пароля, розрахунки знижок чи статуси помилок) мають тестуватися на рівнях API та Unit-тестів. Вони надійні і виконуються за мілісекунди.


Золоте правило: E2E тестування створене не для глибокого пошуку багів у логіці, а лише для перевірки того, чи правильно "склеїлися" фронтенд і бекенд.

А як виглядає ваша стратегія тестування? 👇
🔥 — Тільки критичні флоу в UI, решта — на API/Unit!
👀 — У нас "ріжок морозива", пайплайн іде дві години...
🤯 — Але ж тільки UI тестує так, як бачить реальний юзер!
1
🕵️‍♂️ Інженерний детектив: Справа про "Баг Попелюшки" (або Тести, що перетворюються на гарбуз)

Привіт, екіпаж! Сьогодні розслідуємо містику найвищого рівня. Це баг, через який сивіють навіть досвідчені ліди, бо він ламає всі закони логіки і змушує сумніватися у власній адекватності. ☕️

Сцена злочину (Симптоми):
Ви написали автотест, який перевіряє створення звіту за "сьогодні" або дату народження у профілі. Запускаєте локально — працює ідеально.

Пушите в репозиторій, CI/CD світиться зеленим. Ви спокійно йдете спати.
Але о 23:00 (чи о 01:00 ночі) пайплайн раптом починає горіти червоним! А наступного ранку ви запускаєте його знову без жодної зміни в коді — і він знову стає зеленим. Що за полтергейст?


Хибний слід (Як робить Джун):
Джун дивиться в логи і бачить:
Expected: "20-05-2026", Received: "19-05-2026".

Він вирішує, що бекенд просто лагає при зміні діб, додає в Playwright retries: 3 або, що ще гірше, хардкодить +1 день у розрахунок дати, сподіваючись, що "тепер точно зійдеться". Спойлер: вдень тест впаде знову.


🔍 Що сталося насправді (Timezone Mismatch):
Ваші тести стали жертвою часових поясів. Ви сидите за ноутом і ваш локальний час — за Києвом (EEST, UTC+3). Ваша функція new Date() бере поточну дату вашої машини.

Але ваш CI/CD сервер (наприклад, GitHub Actions) за замовчуванням крутиться в UTC+0!
Коли у вас 01:00 ночі 20 травня, на сервері ще тільки 22:00 попереднього дня (19 травня).

Сервер генерує вчорашню дату, а UI-додаток (який часто бере локаль браузера) намагається порівняти її з сьогоднішньою. Відбувається розсинхрон, і тест падає.


Вирок та Рішення:
Автотести не повинні залежати від того, де фізично запущений браузер — у Києві, Лондоні чи Токіо.

Часовий пояс має бути захардкоджений на рівні всього фреймворку.
Йдемо в playwright.config.ts і жорстко фіксуємо таймзону та локаль:

use: {
// Тепер ваш браузер у CI/CD завжди "думатиме", що він у Києві
timezoneId: 'Europe/Kyiv',
locale: 'uk-UA',
}


Альтернатива: Перевести і локальні тести, і CI/CD у суворий UTC. Головне — синхронізація.

А ваші тести вже ставали "гарбузом" опівночі? 👇
🔥 — Давно захардкодив таймзону в конфігу, сплю спокійно!
👀 — Було таке... Довго не міг зрозуміти, чому падає тільки вночі.
🤯 — Пішов перевіряти свій playwright.config.ts від гріха подалі.
1
🔥 Прожарка інструментів: Cypress (або Життя в одній вкладці)

Привіт, екіпаж! Четвер — час розпалювати гриль. Сьогодні на нашій решітці інструмент, який колись врятував нас від жахіть старого Selenium, але у 2026 році сам поступово перетворюється на неповоротке легасі. Зустрічайте — Cypress. ☕️

🟢 Як нам це продавали (Очікування):
"Ми працюємо прямо всередині браузера! Більше ніяких WebDriver-ів! У нас найзручніший синтаксис та красивий Dashboard. Автоматизація тепер — це суцільне свято!"

🥩 Прожарка (Сувора реальність):
🔹Ілюзія асинхронності: Ви дивитесь на код і бачите cy.get(). Хочете просто витягнути текст у змінну через сучасний await? Забудьте. Cypress використовує власну "магічну" чергу команд. Замість чистого TypeScript ви отримуєте "Callback-пекло" з нескінченними .then(), які неможливо нормально дебажити нативними засобами вашої IDE.

🔹Браузерна клаустрофобія: Ваш юзер клікає на посилання, яке відкриває платіжний шлюз у новій вкладці? Cypress каже: "Вибач, я живу тільки в одній табі. Просто видали атрибут target="_blank" через JS і роби вигляд, що все нормально". А тестування складних крос-доменних iframe досі нагадує шаманські обряди.

🔹Монетизація повітря: Хочете паралельний запуск тестів (sharding) у CI/CD, щоб пайплайн не йшов годину? Cypress Cloud виставить вам такий рахунок за інфраструктуру, що бізнес заплаче. І це в той час, коли Playwright шардить тести безкоштовно "з коробки".


⚖️ Вердикт QA Co-pilot:
Cypress був справжньою революцією 5 років тому і зробив багато хорошого для індустрії. Він досі може бути зручним для компонентного тестування (наприклад, якщо розробники пишуть тести прямо поруч зі своїм Angular-кодом).

Але для складного E2E-тестування сучасних додатків його архітектурні обмеження — це зашморг на шиї вашого CI/CD.


Зізнавайтесь, як у вас стосунки з "Кіпарисом"? 👇
🔥 — Давно мігрував на Playwright, вільно дихаю у багатьох вкладках!
👀 — У нас гігантський фреймворк на Cypress, переписати це вже нереально...
🤬 — Не чіпайте Cypress, його .then() — це мистецтво!
👍1
💩 Код з душком: If/Else у тестах (або Тест із роздвоєнням особистості)

Привіт, екіпаж! П'ятниця — традиційний час вивітрювати "смердючий" код з репозиторіїв. Сьогодні препаруємо гріх, який перетворює ваші автотести на непередбачуваний хаос. Поговоримо про умовну логіку (Conditional Testing). ☕️

Знайдіть проблему в цьому тесті:
//  Як пишуть джуни (Антипатерн "Ворожка")
test('Повинен додати товар у кошик', async ({ page }) => {
await page.goto('/product/123');

// "Якщо раптом вилізе промо-банер, то закриємо його..."
if (await page.locator('.promo-popup').isVisible()) {
await page.locator('.close-promo').click();
}

await page.locator('.add-to-cart').click();
});


Чому цей код тхне:
Ви щойно вбили детермінованість тесту (його передбачуваність). Метод isVisible() у Playwright не чекає! Він стріляє миттєво.

Якщо ваш фронтенд на Angular рендерить цей поп-ап за 300 мілісекунд, на момент перевірки isVisible() поверне false. Тест проігнорує блок if і піде клікати на кнопку кошика. АЛЕ саме в цю мілісекунду поп-ап з'являється на екрані, перекриває кнопку, і ваш тест падає з помилкою Element is intercepted.

Ви перезапускаєте тест — сервер відповідає швидше, поп-ап з'являється миттєво, if спрацьовує, тест "зелений". Вітаю, ви створили еталонний Flaky-тест!


Як це виглядає після код-рев'ю Senior-інженера:
Тест має бути прямою лінією. Якщо банер можна відключити (через cookie або API-мок) — відключаємо. Якщо ж це неконтрольований поп-ап, використовуємо нативну "магію" Playwright 1.42+:

// Ідеально чистий код (Playwright addLocatorHandler)
test('Повинен додати товар у кошик', async ({ page }) => {
// 1. Вчимо Playwright автоматично реагувати на перешкоду, ЯКЩО вона з'явиться
await page.addLocatorHandler(
page.locator('.promo-popup'),
async () => {
await page.locator('.close-promo').click();
}
);

await page.goto('/product/123');

// 2. Тест залишається абсолютно лінійним, ніяких if/else!
await page.locator('.add-to-cart').click();
});


Золоте правило: Ніколи не використовуйте if / else для синхронізації UI чи обробки випадкових елементів на сторінці. Автотест — це не алгоритм пошуку шляху, це жорсткий сценарій. Контролюйте стан додатка, а для асинхронних перешкод делегуйте роботу фреймворку.

А скільки if'ів зараз заховано у вашому фреймворку? 👇
🔥 — Використовую addLocatorHandler, мої тести прямі як стріла!
👀 — Грішу іф-елсами, бо на проді постійно лізуть рандомні банери...
🤯 — Тепер я зрозумів, чому мої тести періодично падають на кліках!
👍1
📡 Tech Radar: Головне за тиждень (18.05 – 24.05.2026) від QA Co-pilot

Привіт, екіпаж! Новий тиждень — нові виклики. Минулого тижня індустрія видала кілька потужних релізів, які прямо зараз змушують команди переглядати свої пайплайни. Зібрали для вас найважливіше без води і маркетингового шуму. ☕️

🅰️ Реліз Angular 22: Прощання з Zone.js
Минулого тижня сталася епохальна подія для фронтенду — офіційний вихід Angular 22. Фреймворк остаточно перейшов на повністю Zoneless архітектуру за замовчуванням (реактивність тепер тримається виключно на Signals).

Як це вплине на QA: Додатки почнуть рендеритися кардинально швидше. Але є нюанс: якщо ваші старі E2E-тести під капотом покладалися на очікування стабілізації Zone.js (або ви використовували специфічні бібліотеки-хелпери), вони можуть почати "сипатися" через мікро-гонки станів (race conditions). Час провести ревізію ваших waitFor та API-перехоплень!


📱 Telegram викотив пісочницю для E2E тестування Mini Apps (TMA)
Довгоочікувана новина для всіх, хто розробляє та тестує екосистему в Telegram. Нарешті з'явився офіційний інструментарій (tma-test-utils), який дозволяє ізольовано монтувати Telegram Web Apps у Playwright.

Як це вплине на нас: Більше жодного болю з авторизацією через реальні телефонні номери, 2FA та милиці з iframe! Тепер можна легко мокувати об'єкт Telegram.WebApp (теми, юзер-дані, initData) і проганяти повноцінні UI-тести ваших ботів та міні-додатків у CI/CD за лічені секунди.


🤖 Playwright 1.60 Beta: Перші кроки до Self-Healing локаторів
Команда Microsoft тихо викотила бету 1.60, де засвітився експериментальний підхід до пошуку елементів — інтеграція легких LLM безпосередньо у рушій (page.getByPrompt()).

Суть: якщо ваш жорсткий локатор ламається через редизайн, ШІ "на льоту" аналізує DOM і намагається врятувати клік, знаходячи потрібний елемент за контекстом.

Як це вплине на нас: Це крутий зсув парадигми, але впроваджувати треба обережно. ШІ-локатори повільніші за нативні і можуть додати тестам непередбачуваності. Поки що це інструмент для "м'якого" fallback-у, а не повної заміни getByRole.


Що з цього апдейту випробуєте першим? 👇
🔥 — Telegram Mini Apps пісочниця! Нарешті покрию свого бота нормальними тестами.
👀 — Angular 22: Пішов перевіряти, чи не "почервоніли" мої пайплайни.
🤯 — Self-healing у Playwright: звучить хайпово, але я поки ШІ в DOM не пущу!
⚔️ Битва підходів: Junior vs. QA Architect (Цикли проти Нативних ассертів)

Сьогодні розберемо класичну ситуацію: вам треба перевірити масив елементів на сторінці (наприклад, список повідомлень у чаті або таблицю). Як витягнути та перевірити дані, не змусивши ваш тест "гальмувати"? ☕️

Підхід Junior-автоматизатора
const texts = [];
const messages = page.locator('.chat-message');
const count = await messages.count();

for (let i = 0; i < count; i++) {
// Повільно витягуємо текст по одному...
texts.push(await messages.nth(i).textContent());
}

expect(texts).toContain('Замовлення успішне');

Чому це антипатерн: Це повільно і крихко. По-перше, count() стріляє миттєво і не чекає на рендер (часто повертає 0). По-друге, кожна ітерація циклу for робить окремий асинхронний запит до браузера. Якщо у чаті 50 повідомлень — тест гарантовано "зависне" на кілька секунд.

Підхід QA Architect
// Варіант 1: Одразу перевіряємо наявність тексту у списку (з auto-retry)
await expect(page.locator('.chat-message')).toContainText(['Замовлення успішне']);

// Варіант 2: Якщо масив реально потрібен для хитрої логіки
const allTexts = await page.locator('.chat-message').allTextContents();

Чому це шедевр: Швидкість світла і стабільність. Playwright виконує allTextContents() або toContainText() за одну мілісекунду на рівні браузерного рушія, перехоплюючи весь масив одразу. Крім того, веб-ассерт автоматично зачекає (до 5 секунд), поки потрібне повідомлення не з'явиться в DOM, що рятує від Flaky-тестів.

Золоте правило: Якщо ви пишете цикл for для перебору UI-елементів у Playwright — зупиніться. З імовірністю 99% ви робите щось не так і для цього вже є нативний оптимізований метод.

А як ви працюєте зі списками елементів? 👇
🧨 Руйнівники IT-міфів: "Record & Playback напише тести за нас"

Привіт, екіпаж! Сьогодні розбираємо класичний міф, який дуже люблять продавати менеджерам творці платформ для тестування (і в який часто вірять початківці). ☕️

Міф: "Навіщо писати код руками і вчити архітектуру? Вмикаємо Playwright Codegen (або Cypress Studio), проклікуємо флоу в браузері — і фреймворк генерує ідеальні автотести за 5 хвилин! QA може просто записувати свої дії."

💥 Реальність (Бум!):
Вітаю, ви щойно згенерували найгірший вид легасі. Record & Playback — це пастка для великих проєктів:

1️⃣Смертельний хардкод: Генератор запише ваші дії буквально. await page.fill('#email', 'test@test.com'). На наступному запуску тест впаде, бо цей email вже зайнятий у базі.

2️⃣Жодної архітектури: Згенерований код — це "простирадло" з локаторів. Там немає ні Page Object, ні Fixtures, ні перевикористання кроків. Змінився селектор однієї кнопки? Будете руками правити 100 згенерованих файлів.

3️⃣Сліпота до асинхронності: Рекордер не знає, що після кліку треба дочекатися відповіді бекенду або закінчення анімації. Він просто запише клік, а ваш пайплайн потім буде "блимати" червоним через мікро-затримки мережі (Flaky tests).


Як має бути (Підхід Архітектора):
Codegen — це лише допоміжний інструмент (як пінцет). Його використовують виключно для того, щоб швидко знайти оптимальний локатор (getByRole) на складній сторінці, або накидати "чернетку" скрипта.

Але справжній автотест завжди проєктується інженером: з ізоляцією даних, динамічними моками та правильною інфраструктурою.


Золоте правило: Тести, які легко і швидко записуються через Record, найважче і найдорожче підтримувати в майбутньому.

А як ви ставитесь до автогенерації коду? 👇