QA Family by Alexey – Telegram
QA Family by Alexey
1.55K subscribers
99 photos
6 videos
219 links
Команда:
- Иванов Алексей 2ГИС @alexey_qa
- Иванова Ксения Wink

Этот канал из моего лично трансформируется в канал онлайн сообщества QA Family

👥 Делаем митап @moscowqa
🎙Подкаст family-qa.mave.digital
Download Telegram
🟢Что такое юнит тест? 🧩

🔎 Юнит тест — это тест, который проверяет правильность работы небольшого фрагмента кода (юнита).

🚩Особенности юнит тестов
1️⃣. Проверка небольших фрагментов кода.
2️⃣. Быстрая проверка.
3️⃣. Изоляция от остального кода.

🔍 Способы изоляции кода в юнит тестах
Тесты можно разделить на две школы по способу изоляции кода:
🔵Детройтская школа (классический подход)
🔵Лондонская школа

📘 Классический подход:
☑️Юнит тесты проверяют код с минимальной изоляцией.
☑️Интеграция с реальными зависимостями.

🎓 Лондонская школа:
☑️Сильная изоляция кода.
☑️Использование заглушек и моков для имитации зависимостей.


⚖️ Разница в подходах
🔵 Классический подход: Реальные зависимости, меньше моков.
🔵 Лондонская школа: Полная изоляция, много моков и заглушек.


💻 Примеры кода
Тестируемый код

// orderService.js
class OrderService {
constructor(paymentService) {
this.paymentService = paymentService;
}

placeOrder(order) {
if (this.paymentService.processPayment(order)) {
return 'Order placed';
} else {
return 'Payment failed';
}
}
}

// paymentService.js
class PaymentService {
processPayment(order) {
// Логика обработки платежа
return true; // Или false в случае ошибки
}
}


Классический подход 📘

test('placeOrder returns "Order placed" when payment is successful', () => {
const paymentService = new PaymentService();
const orderService = new OrderService(paymentService);
const order = { amount: 100 };

const result = orderService.placeOrder(order);

expect(result).toBe('Order placed');
});


Лондонская школа
🎓
test('placeOrder returns "Order placed" when payment is successful', () => {
const paymentService = { processPayment: jest.fn().mockReturnValue(true) };
const orderService = new OrderService(paymentService);
const order = { amount: 100 };

const result = orderService.placeOrder(order);

expect(result).toBe('Order placed');
expect(paymentService.processPayment).toHaveBeenCalledWith(order);
});


💡Вывод:
Выбор подхода зависит от конкретного проекта и предпочтений команды.

Теги: #unitTests #testing #cleanСode
Please open Telegram to view this post
VIEW IN TELEGRAM
👎63👍83
🤔А какой подход используете вы?
Anonymous Poll
74%
Классический подход 📘
31%
Лондонская школа 🎓
👎65
🍔Метрика покрытия кода: ограничения и примеры

Метрика покрытия кода показывает, какая доля исходного кода была выполнена в тестах.

Однако стопроцентное покрытие кода не гарантирует идеальное качество или отсутствие ошибок. Давайте рассмотрим два основных типа метрик покрытия кода и их ограничения.

Типы метрик покрытия кода
1️⃣ Code Coverage (Покрытие кода)
2️⃣ Branch Coverage (Покрытие ветвей)

Code Coverage
Code Coverage измеряет отношение количества выполненных строк кода к общему количеству строк кода. На первый взгляд, кажется логичным стремиться к максимальному покрытию, но этот подход имеет свои недостатки.

Пример:
const evaluate = (x) => {
if (x > 10) {
return "Greater";
} else {
return "Lesser";
}
};

// Тесты
assert(evaluate(5) == "Lesser");


В этом примере тесты покрывают 50% строк кода.


Теперь рассмотрим пример рефакторинга кода:
const evaluateRefactor = (x) => (x > 10) ? "Greater" : "Lesser";

// Тесты
assert(evaluateRefactor(5) == "Lesser");


В этом примере тесты покрывают только одну строку, что соответствует 100% покрытия


Проблемы с таким подходом:
1️⃣ Тесты могут выполнять строки кода без проверки их корректности.
2️⃣ Тесты не обязательно покрывают все возможные крайние случаи.
3️⃣ Высокий процент покрытия может создать иллюзию надежности.

Branch Coverage
Branch Coverage измеряет отношение количества покрытых ветвей к общему количеству ветвей в коде. Ветви – это управляющие структуры, такие как if, switch, и циклы.

Пример:
const evaluate = (x) => {
if (x > 10) {
return "Greater";
} else if (x < 5) {
return "Lesser";
} else {
return "Equal";
}
};

// Тесты
assert(evaluate(15) == "Greater");
assert(evaluate(3) == "Lesser");
assert(evaluate(7) == "Equal");


В этом примере тесты покрывают все возможные ветви: if (x > 10), else if (x < 5) и else. Таким образом, достигается стопроцентное покрытие ветвей. Однако это не гарантирует, что все граничные условия, такие как точные значения 10 и 5, были протестированы.

Проблемы с таким подходом:
1️⃣ Неполное покрытие сценариев
2️⃣ Упущенные граничные случаи

Заключение:
➡️Метрика покрытия кода, будь то code coverage или branch coverage, является полезным инструментом, и они могут говорить о проблемах
➡️Низкие показатели говорят о проблема с тестами, однако высокие показатели не гарантируют что с тестами все в порядке
➡️Метрики покрытия - начальный этап на пути к хорошим тестам.

Ставьте 👍 👎

Теги: #code_coverage #branch_coverage #automation
Please open Telegram to view this post
VIEW IN TELEGRAM
👎56👍143
Cors и как его обойти

Представьте, что вы тестируете веб-приложение. Ваше приложение может обращаться к данным с другого сервера (например, API или статики). Однако, по умолчанию браузеры блокируют такие запросы, если они идут с одного домена на другой.
Access to fetch at 'https://api.example.com/data' from origin 'https://myapp.com' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.


🔎CORS (Cross-Origin Resource Sharing) позволяет настроить разрешения, чтобы ваш браузер мог безопасно запрашивать данные с других доменов.

Если CORS настроен неправильно, вы можете столкнуться с ситуацией, когда ваш тестируемый веб-сайт пытается получить данные с другого домена, а браузер блокирует запрос. Вы увидите ошибку в консоли разработчика, что "доступ запрещен из-за политики CORS".

Как обойти CORS при тестировании?
Иногда, для тестирования или разработки, нужно временно обойти CORS. Вот несколько способов:

1️⃣ Запуск браузера без проверки CORS:
🟣Вы можете запустить Chrome с флагом -disable-web-security, чтобы отключить проверку CORS. Это быстрое решение, когда вам нужно протестировать что-то без лишних препятствий.
🟣Например, запустите Chrome с командой:
➡️На Windows: start chrome --disable-web-security --user-data-dir="C:/chrome_dev"
➡️На macOS: open -na "Google Chrome" --args --disable-web-security --user-data-dir="/tmp/chrome_dev"

2️⃣ Использование прокси-сервера(fiddler, charles):
🟣Вы можете настроить прокси-сервер, который будет делать запросы к другому домену от имени вашего приложения. Таким образом, ваш браузер общается только с вашим сервером, и CORS не мешает.

3️⃣ Установка расширений для браузера:
🟣Есть расширения для Chrome и других браузеров, которые позволяют временно отключить проверку CORS. Однако их стоит использовать с осторожностью.

4️⃣Настройка CORS на сервере:
🟣Если у вас есть доступ к серверу, с которого делаются запросы, можно настроить его так, чтобы он разрешал запросы с вашего домена, добавив правильные заголовки CORS. Например, Access-Control-Allow-Origin: * разрешает запросы со всех доменов.

😉Важно помнить
Обходить CORS можно только в процессе тестирования или разработки. В реальных условиях его следует настраивать правильно, чтобы сохранить безопасность приложения и данных.

Ставьте 👍 👎
Теги: #cors #security #tools
Please open Telegram to view this post
VIEW IN TELEGRAM
👎61👍144
🖥Тестирование тригеров GTM

Если вы занимаетесь тестированием веб-сайтов, то, вероятно, вам приходилось тестировать триггеры в Google Tag Manager и отправлять события аналитики.

Почему это важно? Дело в том, что большинство таких событий имеют прямое отношение к маркетингу. Маркетологи используют различные метрики продаж, чтобы понять, как лучше продавать. В общем, это напрямую связано с деньгами.💸

Как тестировать события

1⃣ для ручного тестирования и самый удобный способ это использовать https://tagassistant.google.com/
Там просто вставляете url, и проходите тест кейсы, а в другом окне наблюдаете события (рекомендуем)

2⃣ Использовать консоли разработчика
все события пушится в переменную dataLayer

3⃣ Тестирование через Network-панель браузера, можно наблюдать был ли отправлен запрос на нужный сервер (например, на сервер Google Analytics).


Для автоматизированного тестирования на Playwright, вы можете использовать несколько подходов👇

*️⃣ отслеживание через запросы
test('Track network request and verify payload', async ({ page }) => {
await page.goto('https://your-website.com');

// Перехват и проверка сетевых запросов
const [response] = await Promise.all([
page.waitForResponse(response => response.url().includes('www.google-analytics.com/collect') && response.status() === 200),
// Выполняем действие на странице, которое должно вызвать запрос
page.click('button')
]);

const requestPayload = JSON.parse(response.request().postData());

expect(requestPayload).toHaveProperty('event', 'yourExpectedEvent');
expect(requestPayload).toHaveProperty('gtm', 'expectedGtmId');
});


*️⃣проверка данных в dataLayer
test('Check dataLayer for specific event', async ({ page }) => {
await page.goto('https://your-website.com');

// Выполняем действие на странице
await page.click('button#yourButton');

// Получаем значение dataLayer из контекста страницы
const dataLayer = await page.evaluate(() => window.dataLayer);

// Проверка, что dataLayer содержит ожидаемое событие
const eventData = dataLayer.find(event => event.event === 'yourExpectedEvent');

expect(eventData).not.toBeNull();
expect(eventData).toHaveProperty('gtm', 'yourExpectedGtm');
});

Отслеживание сетевых запросов через Network-панель полно и надежно, но тесты получаются менее стабильны. Проверка данных в dataLayer проста и быстро настраивается, но может пропускать события при перезагрузке страницы или изменениях в dataLayer.

Ставьте 👍 👎


Теги: #testing #playwright #automation #dataLayer #GTM #тестирование #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
👎66👍112
#дайджест 📢


⚡️ В nodeJS 22.6 добавили поддержку typenoscript

⚡️ Playwright в 👩‍💻 версии **1.46
🌟 позволяет указывать клиентские сертификаты, теперь не надо игнорировать https
🌟 новая опция CLI --only-changed позволяет запускать только тестовые файлы, которые изменились с момента последней фиксации git
🌟 Обновление UI Mode / Trace Viewer
🌟 В APIRequestContext.fetch() появилась новая опция maxRetries. Она позволяет повторять попытку при возникновении сетевой ошибки ECONNRESET.
🌟 Опция Box в фикстурах уменьшает мусор в отчетах

⚡️👩‍💻 k6 v0.53.0
Добавили поддержку нативных ECMAScript модулей
и другие улучшения

⚡️ WebdriverIO 👩‍💻 v9
Все сеансы будут автоматически использовать протокол Bidi
Подробнее

Теги: #updates
Please open Telegram to view this post
VIEW IN TELEGRAM
👎56👍64
👨‍💻Скрипты в консоль
В основном консоль используют для отслеживания ошибок на сайте.

Я предлагаю открыть консоль по новому для обычного QA.

Когда я только начинал свой путь в IT, я играл в одну браузерную игру. Тогда я и представить не мог, что существует автоматизация. Мой первый код на JavaScript был связан со сбором подарков в игре.

Чтобы собрать раньше всех в игре я использовал самый примитивный код, который вставлял в консоль
setInterval(()=> {
$('#gift').click()
}, 150)


Суть кода понятна каждому, где мы просто кликаем по селекту каждые 150 мс.


Давайте я подскажу кейсы где еще можно использовать простые скриптики для ускорения ручного регресса.

В основном консоль используют для отслеживания ошибок, но мало кто знает, что ее можно использовать для простой автоматизации или в качестве помощника.

Кейс 1: Подсчёт элементов на странице📝
Этот кейс вы уже упомянули, и он действительно полезен для быстрой оценки, например, количества отображаемых элементов.
const countElement = $$('[class^="_ObjectsListCard"]').length;
console.log(`Количество проектов: ${countElement}`);


Кейс 2: Парсинг данных со страницы (ссылки)🔗
Иногда нужно собрать все ссылки со страницы, например, для проверки их на работоспособность или для подготовки тест-кейсов.
const links = [...document.querySelectorAll('a')].map(link => link.href);
console.log('Все ссылки на странице:', links);


Кейс 3: Парсинг данных с таблицы (таблицу)🫥
Недавно я работал над задачей, связанной с таблицей, чтобы получить данные о списках поступающих и оценить вероятность поступления своего брата. Данные из таблицы для дальнейшего анализа, можно использовать следующий скрипт:
const tableRows = [...document.querySelectorAll('table tbody tr')];
const data = tableRows.map(row => {
const columns = row.querySelectorAll('td');
return {
column1: columns[0].innerText,
column2: columns[1].innerText,
column3: columns[2].innerText,
};
});
console.log('Данные из таблицы:', data);


Кейс 4: Парсинг данных и выполнение вычислений 🎙
Можно не только парсить данные, но и сразу выполнять вычисления. Например, если нужно суммировать значения в колонке:
const tableRows = [...document.querySelectorAll('table tbody tr')];
const total = tableRows.reduce((sum, row) => {
const value = parseFloat(row.querySelector('td:nth-child(3)').innerText);
return sum + (isNaN(value) ? 0 : value);
}, 0);
console.log(`Сумма значений в третьей колонке: ${total}`);


Кейс 5: Отправка запросов для подготовки данных🟢
Иногда требуется подготовить данные или проверить API без использования Postman или других инструментов. Это можно сделать прямо из консоли:
fetch('<https://example.com/api/data>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ key: 'value' }),
})
.then(response => response.json())
.then(data => console.log('Ответ от сервера:', data))
.catch(error => console.error('Ошибка:', error));


Кейс 6: Подключение к сокетам
Если ваш проект использует WebSocket, можно подключиться к сокету и прослушивать события прямо в консоли:
const socket = new WebSocket('wss://example.com/socket');

socket.onopen = () => {
console.log('Соединение установлено');
socket.send(JSON.stringify({ action: 'subscribe', channel: 'updates' }));
};

//далее отправляем различные запросы


Кейс 7: Заполнение форм
Для ускорения тестирования форм можно использовать автоматическое заполнение и отправку данных:
document.querySelector('#name').value = 'Тестовое имя';
document.querySelector('#email').value = 'test@example.com';
document.querySelector('form').submit();
console.log('Форма заполнена и отправлена');

Теги: #tools
Please open Telegram to view this post
VIEW IN TELEGRAM
👎58👍166
Важное замечание👆
Однако стоит отметить, что не всегда удается просто кликнуть или заполнить поля формы через консоль. Это может быть связано с использованием сложных UI-фреймворков, которые добавляют валидацию или управляют состоянием элементов на странице. Например, в некоторых случаях элемент может не воспринимать ввод значения через value, так как внутренние скрипты фреймворка могут ожидать определенных событий или использовать собственные механизмы управления состоянием.

В таких ситуациях можно попытаться вызвать соответствующие события вручную:
const nameInput = document.querySelector('#name');
nameInput.value = 'Тестовое имя';
nameInput.dispatchEvent(new Event('input', { bubbles: true }));
nameInput.dispatchEvent(new Event('change', { bubbles: true }));


Если и это не помогает, стоит рассмотреть использование других инструментов для автоматизации, таких как Selenium или Cypress, которые могут эмулировать взаимодействие пользователя с элементами страницы на более глубоком уровне.

О хранении и управлении скриптами будет завтра. Было полезно? Ставьте 👍👎

Теги: #automation #tools
Please open Telegram to view this post
VIEW IN TELEGRAM
2👎60👍152
⚡️Хранение и управление скриптами, которые используются для ускорения тестирования в DevTool

Для хранения и удобного использования таких скриптов есть несколько подходов, каждый из которых имеет свои преимущества и недостатки. Вот несколько вариантов:

1️⃣ Закладки в браузере
Вы можете хранить небольшие скрипты прямо в закладках браузера. Я использую такой метод для запросов и авторизации. Это удобно для простых и часто используемых скриптов.

🚩Как сохранить: Создайте новую закладку и вместо URL вставьте ваш JavaScript-код, предварительно добавив префикс javanoscript:. Например:

javanoscript:(function(){
// Ваш код здесь
const countElement = $$('[class^="_ObjectsListCard"]').length;
console.log(`Количество проектов: ${countElement}`);
})();

Быстрый доступ
Ограничено простыми скриптами, неудобно для управления большим количеством кода.

2️⃣ GitHub/GitLab репозиторий
Если скрипты регулярно обновляются и их нужно делиться с коллегами, использование репозитория на GitHub или GitLab.

3️⃣ Chrome DevTools Snippets
Chrome DevTools имеет встроенную возможность хранения скриптов в виде сниппетов. Это отличный способ хранить и запускать скрипты непосредственно в инструментах разработчика.

Как использовать: В Chrome DevTools перейдите на вкладку "Sources" и откройте раздел "Snippets". Здесь вы можете создавать и хранить свои скрипты. Запускать их можно прямо из этого раздела.
Прямой доступ в DevTools, легкость использования, подходит для часто используемых скриптов.
Скрипты сохраняются локально и не синхронизируются между устройствами.

4️⃣ Написание своего расширения
Если у вас есть набор скриптов, которые используются регулярно и в разных проектах, создание собственного расширения для Chrome может быть удобным решением.

Как использовать: Создайте простое расширение для Chrome, которое будет содержать ваши скрипты и интерфейс для их управления (например, кнопки для запуска).
Полный контроль над функциональностью, возможность автоматизации многих задач, синхронизация через учетную запись Google.
Требуется знание разработки расширений, больше времени на разработку и поддержку.


😉Заключение
Выбор способа хранения зависит от ваших потребностей и частоты использования скриптов. Для простых задач подойдут закладки или сниппеты в DevTools. Если скрипты требуют совместной работы или версионирования, лучше использовать GitHub/GitLab. Если же у вас сложные и регулярные задачи, имеет смысл рассмотреть создание собственного расширения для Chrome.

Ставьте 👍 👎
Теги: #DevTools #tools
Please open Telegram to view this post
VIEW IN TELEGRAM
2👎56👍14🔥21
Видео с гейзенбага 🪲

Выложили мой доклад с последнего heisenbug.

В прошлом году я рассказывал о проблемах перфоманса, а в весной сделал акцент на проблемы рендеринга. Всем любителям девтулзов и не равнодушным к веб-тестированию рекомендую к просмотру🖥

Теги: #heisenbug #доклад #tools
2👎63👍93🔥3
👁Захват и анализ сетевого трафика прямо в браузере

Захват и анализ сетевого трафика через chrome://net-export. Это может быть полезно ⬇️
🔎Если требуется захватить сетевой трафик в течение длительного периода времен
🔎Когда нужно анализировать трафик, связанный с несколькими сетями или интерфейсами (например, при переключении между Wi-Fi и сотовой сетью)
🔎Если DevTools не дает всей необходимой информации о сетевых ошибках или задержках, chrome://net-export/ может предоставить более подробные данные, включая информацию о сетевых пакетах, DNS-запросах и внутренней работе браузера.
🔎Для отслеживание 2 вкладок.

В Chrome есть встроенная функция net-export, которая проста и удобна в использовании:
1️⃣ Откройте страницу chrome://net-export.
2️⃣Нажмите «Start Logging to Disk».
3️⃣Укажите расположение и название файла, который будет сохранен в формате JSON.
4️⃣После завершения работы нажмите «Stop Logging to Disk»

В итоге, chrome://net-export/ чаще используется для более глубокого и продолжительного анализа сетевой активности, когда стандартных возможностей DevTools недостаточно для решения задач.

Дальше открываешь полученный JSON netlog-viewer.appspot.com

Ставьте 👍👎
Теги: #chrome #netexport #devtools
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
1👎65👍13🤯41
"Что лежит в его мерзких грязных карманцах?"
(с) Голлум, Властелин колец


Пороемся в карманах у браузера - где и что он хранит - и разберем cookies, sessionStorage & localStorage.

Про куки мы уже говорили в одном из постов выше. Вкратце - это небольшие файлы, которые сервер подкладывает в наш браузер что бы помнить кто ты, что ты, и какие настройки используешь. Куки отправляются сайту с каждым запросом к этому сайту.

📦localStorage
- долгосрочное хранилище данных. Данные из localStorage доступны из любого окна/вкладки и хранятся до тех пор пока ты сам или веб приложение их не удалит. Т.е. localStorage похож на браузерный кеш - только хранит не скрипты, стили и картинки - а данные.
🖼Пример из жизни - Youtube хранит уровень громкости видео в localStorage (по ключу yt-player-volume), а раз localStorage доступен для всех вкладок и окон - то поэтому по умолчанию каждое новое открытое окно ютюба будет иметь одинаково выставленный уровень громкости.
🔒Доступ - через DevTools (вкладка Application) или через JS:
localStorage.setItem('имяКлюча', 'значение'); //запись
localStorage.getItem('имяКлюча'); //чтение



📦sessionStorage - временное хранилище данных. Данные хранятся только пока ты находишься на конкретном сайте в конкретной вкладке. Как только ты покинешь сайт или закроешь вкладку - пиши пропало. Это типа буфера обмена, или временных заметок на полях для веб-приложений.
🖼Пример из жизни - Amazon при оформлении заказа может хранить данные из заполненных форм именно тут - что бы если пользаку после заполнения адреса доставки вдруг приспичило вернуться на предыдущий шаг - данные уже заполненного адреса не оказались потеряны. А Netflix - сохранит в sessionStorage текущую отметку времени просмотра видоса, так что при обновлении страницы ты не упадешь в самое начало серии а продолжишь просмотр оттуда где прервался. А еще с помощью sessionStorage запомнит что показывал те или иные рекомендации что бы не бесить по 10 раз подряд всплывашкой с рекомендацией одного и того же сериала.
🔒Доступ - через DevTools (вкладка Application) или через JS:
sessionStorage.setItem('имяКлюча', 'значение'); //запись
sessionStorage.getItem('имяКлюча'); //чтение


В основе sessionStorage & localStorage так же как и у кук - лежит доменная модель. Т.е. каждому сайту - свой огород, свое пространство. Поэтому не удивляйся когда увидишь во вкладке дев тулов Local storage и Session storage по несколько хранилищ каждого из типов - каждый из сайтов будет иметь свое и в то же время не будет иметь доступа к "чужим" хранилищам, хранилищам других сайтов.


📦Для особо тяжелых (ресурсоемких) случаев - есть еще и IndexedDB - встроенная в браузер база данных (присутствует и в Chrome, и в Firefox, и в Safari). Там размер не фиксирован 5 или 10 мегабайтами и может достигать до 20% от свободного дискового пространства, а иногда и больше.
🖼Пример из жизни - та же гугл почта или гугл доки будут хранить копию данных в indexedDB, что позволит продолжить с ними работу даже при потере интернет-соединения. А при возврате коннекта - локально внесенные изменения в indexedDB тут же подтянутся на сервер, в облако.

___
P.S. огоньки🔥, сердечки❤️ и репосты📩 очень помогают каналу и заряжают мою батарейку на дальнейшее творчество и контент😉😇
2👎62👍19🔥157
Локаторы vs селекторы🤓💪

Что такое селекторы и локаторы в Playwright?

📌 Селекторы — это строки, которые используются для сопоставления элементов в DOM страницы. Playwright поддерживает различные типы селекторов, включая поиск по тексту, CSS, XPath и другие.

📌 Локаторы — это абстракция, построенная на основе селекторов, которая позволяет находить любые элементы на странице, а также обеспечивает "умные" ожидания, являющиеся ключевой функцией Playwright.

Я планирую написать подробное руководство о том, как и какой локатор лучше использовать. Вот выдержка из документации по рекомендуемым локаторам. Если ни одна из функций не подходит, можно добавить атрибут data-testid:
🔵page.getByRole() для поиска по явным и неявным атрибутам доступности.
🔵page.getByText() для поиска по текстовому содержимому.
🔵page.getByLabel() для поиска по лейблу
🔵page.getByPlaceholder() для поиска поля ввода по плейсхолдеру.
🔵page.getByAltText() для поиска элемента, по альтернативному тексту.
🔵page.getByTitle() для поиска элемента по его атрибуту noscript.
🔵page.getByTestId() для поиска элемента по атрибуту data-testid.

Было полезно? Ставьте 👍 👎

Теги: #playwright
Please open Telegram to view this post
VIEW IN TELEGRAM
1👎64👍202
Как сделать так, чтобы в ветку попадал только чистый и рабочий код?👩‍🍳

В разработке важно, чтобы в ветку попадал код, который соответствует стандартам и не ломает приложение или на код-ревью не писали 💩 Один из эффективных способов это обеспечить — использование Git хуков, особенно pre-commit. Этот хук запускает проверки до того, как изменения будут зафиксированы, и позволяет автоматизировать запуск линтера и тестов перед каждым коммитом.

Для упрощения этого процесса есть масса различных библиотек📖:
📍husky (nodejs)
📍pre-commit (python)
📍использовать нативные (для любого проекта)

Пример настройки для nodejs husky📝
Прежде чем приступать к настройки у нас должны быть установлены linter, prettier

Пример package.json
{ 
"noscripts": {
"lint": "eslint .",
"format": "prettier . --write",
"test": "jest"
}
}

Команда init упрощает настройку husky в проекте. Он создает pre-commit в .husky/ и обновляет сценарий для подготовки в package.json.
 npx husky init

Дальше просто добавляем наши скрипты проверки качества кода
echo "npm prettier && npm lint && npm test" > .husky/pre-commit

После можно попробовать сделать коммит и наши скрипты запустятся

Если вам надо закоммитить быстро можно отключить запуск хуков
git commit -m "..." -n


Было полезно? Ставьте 👍 👎

Теги: #husky #lint #prettier #git #commit
Please open Telegram to view this post
VIEW IN TELEGRAM
1👎67👍152🔥2
🖥Структура юнит тестов

Меня читают много разработчиков и скорее всего это пост для них.

При написании юнит тестов возникает необходимость структурировать код таким образом, чтобы он был понятным, легко поддерживаемым и эффективно выявлял ошибки. Один из наиболее популярных подходов для достижения этих целей — паттерн AAA (Arrange-Act-Assert).

Патерн делит процесс написание тестов на 3 логические части:
⚪️ Arrange (Подготовка) — вы готовите все необходимые данные и зависимости для теста
⚪️ Act (Действие) — выполняете основное действие, которое хотите протестировать
⚪️ Assert (Проверка) — на заключительном этапе вы проверяете результаты действия.

Пример прям из тестов текущего проекта:
test('Закрывает Drawer при вызове onClose', () => {
// Arrange
render(<Comments />);
const closeButton = screen.getByRole('button', { name: 'close' });

// Act
fireEvent.click(closeButton);
TypeScript
// Assert
expect(mockedUseStore().services.comments.close).toHaveBeenCalled();
});


Рекомендации🤔
Почти все эти рекомендации можно соблюдать автоматически настроив правила
eslint-a с плагином eslint-plugin-jest.

✖️ Избегайте тесты где используются несколько секций Arrange, Act, Assert.
Например:
 test('Закрывает и открывает Drawer при вызове onClose', () => {
// Arrange
render(<Comments />);
const closeButton = screen.getByRole('button', { name: 'close' });

// Act
fireEvent.click(closeButton);

// Assert
expect(mockedUseStore().services.comments.close).toHaveBeenCalled();

// Arrange
const openCommentButton = screen.getByRole('button', { name: 'Комментарии' });

// Act
fireEvent.click(openCommentButton);

// Assert
expect(modal).toBeVisible();
});


✖️ Избегайте if в тестах.
Тесты должны быть последовательны и без ветвлений.
Включите правило линтера: no-conditional-in-test.

✔️ Один тест — одно действие
✔️ Один тест — одно утверждение, это не всегда возможно, старайтесь, чтобы в каждом тесте проверялось одно конкретное поведение. Это упрощает диагностику при возникновении ошибок.
Можно использовать правила линтера:
➡️ expect-expect — правило следит, что во всех тестах есть expect
➡️ max-expects — максимальное число expect в тестах

По мотивам книги Принципы юнит-тестирования📖

Теги: #unitTests #cleanСode #testing #TDD
Please open Telegram to view this post
VIEW IN TELEGRAM
1👎61👍8🔥4
Выход playwright 1.47

Обновления Playwright:

1⃣ Network Tab (небольшие улучшения в UI mode и trace viewer)
Фильтрация по типу запроса и URL
Улучшенное отображения query parameters
Превью для шрифтов

2⃣ Опция — tsconfig
Теперь можно указать конкретный tsconfig для всех импортируемых файлов:
npx playwright test --tsconfig tsconfig.test.json


3⃣ APIRequestContext
Поддержка URLSearchParams в query параметрах:
const response = await request.get('url', { params: 'userId=1' });


https://github.com/microsoft/playwright/releases/tag/v1.47.0

Теги: #playwright #updates
Please open Telegram to view this post
VIEW IN TELEGRAM
1👎63🔥13👍53
💻Антипаттерны юнит тестов

Продолжаем тему с юнит тестами, и поговорим какие существуют антипаттерны при написании юнит тестов.

🔎Тестирование приватных методов
У вас есть класс PaymentProcessor, который обрабатывает платежи и содержит приватный метод для валидации транзакций.
class PaymentProcessor {
private validateTransaction(transaction: Transaction): boolean {
// сложная логика
return transaction.amount > 0 && transaction.currency === 'USD';
}

processPayment(transaction: Transaction): string {
if (this.validateTransaction(transaction)) {
return 'Payment processed';
} else {
return 'Invalid transaction';
}
}
}

Антипаттерн:
Делаем метод публичным и тестирование метода validateTransaction напрямую.
Почему это плохо:
Нарушает принцип инкапсуляции.
Тесты становятся хрупкими при изменении внутренней реализации.
Решение:
Тестировать публичный метод processPayment, который использует приватный метод.
Или провести рефакторинг и вынести validateTransaction в отдельный класс.


🔎Раскрытие приватного состояния объекта
Класс Inventory управляет списком товаров на складе.
class Inventory {
private items: Item[] = [];

addItem(item: Item): void {
this.items.push(item);
}
getItems(): ReadonlyArray<Item> {
return this.items;
}

// Другие методы...
}

Антипаттерн:
Чтобы упростить тестирование, приватное свойство items делается публичным.
Решение:
Тестировать публичный метод processPayment, который использует приватный метод.


🔎Утечка доменных знаний в тесты
Система рассчитывает налог в зависимости от категории продукта.
class TaxCalculator {
calculateTax(product: Product): number {
if (product.category === 'Food') {
return product.price * 0.05;
} else if (product.category === 'Electronics') {
return product.price * 0.2;
}
return product.price * 0.1;
}
}

Антипаттерн:
Тест дублирует бизнес-логику при расчете ожидаемого результата.
describe('TaxCalculator', () => {
it('should calculate tax for food category', () => {
const calculator = new TaxCalculator();
const product = { price: 100, category: 'Food' };
// тут дублируется логика
const expectedTax = product.price * 0.05;
const tax = calculator.calculateTax(product);
expect(tax).toBe(expectedTax);
});
});

Почему это плохо:
Тест повторяет логику, которую должен проверять.
При изменении логики необходимо менять и код теста.
Решение:
Использовать конкретные значения для ожидаемых результатов.
describe('TaxCalculator', () => {
it('should calculate 5% tax for food category', () => {
const calculator = new TaxCalculator();
const product = { price: 100, category: 'Food' };
const tax = calculator.calculateTax(product);
expect(tax).toBe(5);
});

it('should calculate 20% tax for electronics category', () => {
const calculator = new TaxCalculator();
const product = { price: 200, category: 'Electronics' };
const tax = calculator.calculateTax(product);
expect(tax).toBe(40);
});
});

Теги: #unitTests #cleanСode #TDD #BDD #learning #recommendation #practices
Please open Telegram to view this post
VIEW IN TELEGRAM
👎58👍73🔥2
🔎Мокирование конкретных классов вместо интерфейсов
Приложение отправляет уведомления пользователям через SMS.
class SmsService {
sendSms(number: string, message: string): void {
// Логика отправки SMS
}
}

class NotificationManager {
constructor(private smsService: SmsService) {}

notifyUser(user: User, message: string): void {
this.smsService.sendSms(user.phoneNumber, message);
}
}

Антипаттерн:
В тестах мокируется конкретный класс SmsService.
describe('NotificationManager', () => {
it('should send SMS notification', () => {
const smsService = new SmsService();
spyOn(smsService, 'sendSms');
const manager = new NotificationManager(smsService);
manager.notifyUser({ phoneNumber: '1234567890' }, 'Test Message');
expect(smsService.sendSms).toHaveBeenCalledWith('1234567890', 'Test Message');
});
});

Почему это плохо:
Привязывает тест к конкретной реализации.
Трудно заменить SmsService на другую реализацию (например, для разных стран).
Решение:
Использовать интерфейс для абстракции сервиса отправки сообщений.
interface IMessageService {
sendMessage(recipient: string, message: string): void;
}

class SmsService implements IMessageService {
sendMessage(recipient: string, message: string): void {
// Логика отправки SMS
}
}

class NotificationManager {
constructor(private messageService: IMessageService) {}

notifyUser(user: User, message: string): void {
this.messageService.sendMessage(user.phoneNumber, message);
}
}



🔎Тестирование с мокированием интерфейса:

describe('NotificationManager', () => {
it('should send message notification', () => {
const messageService: IMessageService = {
sendMessage: jasmine.createSpy('sendMessage'),
};
const manager = new NotificationManager(messageService);
manager.notifyUser({ phoneNumber: '1234567890' }, 'Test Message');
expect(messageService.sendMessage).toHaveBeenCalledWith('1234567890', 'Test Message');
});
});


😉Что-то вроде выводов:
Стремитесь писать тесты, которые проверяют поведение через публичный интерфейс и фокусируются на результатах, а не на деталях реализации. Это сделает ваш код более надежным, а тесты — более ценными инструментами в процессе разработки.

Теги: #cleanСode #unitTests #TDD #BDD
Please open Telegram to view this post
VIEW IN TELEGRAM
4👎63👍123
Декораторы в Playwrightт для мобильного тестирования📲

Нашел классную статью, где автор предлагает избавиться от дублирования кода и упростить поддержку тестов в Playwright. Как? С помощью декораторов TypeScript инкапсулировать платформенно-зависимую логику и создать единый тест для мобильных и настольных платформ.

Читать здесь📖

Теги: #playwright #cleanСode
Please open Telegram to view this post
VIEW IN TELEGRAM
4👎61👍93
Тестирование с параметром noWaitAfter в Playwright🕐

Параметр noWaitAfter в Playwright позволяет настроить, как обрабатываются действия, такие как клики по ссылкам, отправка форм или запуск сетевых запросов. Он определяет, должен ли Playwright ждать завершения вызванного события (навигации или перехода на другую страницу) перед тем, как перейти к следующему шагу.
➡️ noWaitAfter: false (по умолчанию): Playwright ждёт завершения действия перед переходом к следующему шагу.
➡️ noWaitAfter: true: Playwright немедленно переходит к следующему шагу, даже если действие ещё не завершено.

Сценарии использования:
💙 noWaitAfter: true в обработчиках локаторов
С версии Playwright 1.44 параметр noWaitAfter: true можно использовать с обработчиками локаторов. Это особенно полезно при работе с оверлеями или другими элементами, которые могут мешать выполнению действий.
test('Закрытие оверлея с noWaitAfter: true', async ({ page }) => {
await page.goto('http://localhost:3000');

// Локатор для оверлея
const overlayLocator = page.locator('#overlay');

// Добавляем обработчик локатора для закрытия оверлея
await page.addLocatorHandler(overlayLocator, async overlay => {
await overlay.locator('#closeButton').click(); // Закрытие оверлея
}, { times: 3, noWaitAfter: true }); // Макс. 3 раза, без ожидания после клика

// Взаимодействие с кнопкой после закрытия оверлея
await page.click('#fetchWeatherButton');

// Проверка отображения прогноза погоды
await page.waitForSelector('#weatherDisplay:has-text("Прогноз погоды для Chennai")');
});

В этом примере обработчик кликает на кнопку закрытия оверлея, и благодаря noWaitAfter: true, тест продолжает выполнение, не дожидаясь исчезновения оверлея.

💙 noWaitAfter: false для получения данных
Когда требуется подождать завершения сетевого запроса, например, получения прогноза погоды, параметр noWaitAfter: false гарантирует, что Playwright дождётся завершения запроса.
test('Получение данных погоды с noWaitAfter: false', async ({ page }) => {
await page.goto('http://localhost:3000');

// Клик по кнопке "Получить прогноз погоды" и ожидание завершения запроса
await page.click('#fetchWeatherButton', { noWaitAfter: false });

// Проверка отображения данных после загрузки
await page.waitForSelector('#weatherDisplay:has-text("Прогноз погоды для Chennai")');
const weatherText = await page.textContent('#weatherDisplay');
console.log(weatherText);
});

Здесь Playwright ждёт, пока данные полностью загрузятся, чтобы тест был надёжным.

💙 noWaitAfter: true для UI-манипуляций
Для операций, таких как переключение видимости элементов или обновление интерфейса, нет необходимости ждать завершения действий. Параметр noWaitAfter: true позволяет пропускать лишние ожидания.
test('UI-манипуляции с noWaitAfter: true', async ({ page }) => {
await page.goto('http://localhost:3000');

// Клик по кнопке без ожидания
await page.click('#toggleButton', { noWaitAfter: true });
await page.waitForSelector('#toggleElement.visible');

// Симуляция быстрых кликов без ожидания
await page.click('.rapidButton', { noWaitAfter: true });

// Кастомное действие, активируемое таймером
await page.click('#customActionButton', { noWaitAfter: true });
await page.waitForSelector('#customActionResult:has-text("Действие завершено!")');
});

В этом тесте различные UI-операции выполняются без необходимости ждать завершения каждого действия, что ускоряет тест.

Зачем использовать noWaitAfter?
➡️ Ускорение тестов. Пропуская ненужные ожидания, можно значительно повысить скорость выполнения тестов.
➡️ Обеспечение точности. Использование noWaitAfter: false гарантирует, что Playwright будет ждать завершения действий, требующих сетевых запросов или навигации, что снижает риск ложноположительных результатов.

Когда использовать noWaitAfter?
➡️ Используйте noWaitAfter: false для действий, связанных с навигацией или загрузкой данных.
➡️ Используйте noWaitAfter: true для манипуляций с интерфейсом и других действий, не требующих ожидания.

Теги: #playwright
Please open Telegram to view this post
VIEW IN TELEGRAM
2👎58👍64🔥3