Октябрь и ноябрь выдался на события
Прямо на конференции получил новость о том, что мою должность сокращают
Планы на оставшийся ноябрь также грандиозные:
Какие темы было бы интересно поднять?
#breakingnews
Здравствуйте, друзья! 🕵️ Я QA инженер из самолета ✨ Вашего внимания представляется канал от пользователя @dev_qa. Кто это такой? Что это за канал? Alexey QA note - это канал, где QA инженер по имени Алексей делится своими заметками, наработками и интересными статьями. Он также выступает на конференциях и митапах, делая митап от сообщества в Москве @moscowqa. Если у вас есть вопросы или предложения, вы всегда можете обратиться к нему по юзернейму @alexey_qa. Присоединяйтесь к каналу, чтобы быть в курсе последних новостей и разработок в области QA тестирования! 🧐
13 Nov, 15:35
07 Oct, 08:39
playwright-core/
playwright-test/
playwright/
protocol/
playwright-test
— это просто обёртка для связывания playwright-core
и playwright
вместе в пакете @playwright/test,
устанавливаемом через npm. Если вы посмотрите в файл index.js
, вы увидите только экспорт из playwright/test
, который находится в пакете playwright
.protocol
, который содержит автоматически сгенерированный код через некоторые скрипты сборки в директории utils
корневой директории проекта Playwright. Он содержит YAML-файл protocol.yml
, который перечисляет все интерфейсы, используемые протоколом Playwright. Если вы когда-либо захотите создать драйвер для Playwright на другом языке, это один из основных файлов, к которому стоит обратиться при создании этой библиотеки.playwright
и playwright-core
. Первый из них является обёрткой вокруг playwright-core
и отвечает за управление всей логикой тестирования. Это включает запуск рабочих процессов тестирования, выполнение тестов, отчёты о результатах, управление повторными запусками и утверждениями тестов. Хорошим ориентиром для кода, содержащегося в этом пакете, является любая конфигурационная логика, которую вы найдёте в файле playwright.config.ts
, или любой связанный с запуском тестов код, например:// my-test.spec.ts
import { test } from "@playwright/test";
playwright
.playwright-core
, который содержит всю функциональность автоматизации браузера. Эта функциональность разделена на два основных компонента: клиентскую библиотеку и серверную библиотеку. Серверная библиотека, находящаяся в packages/playwright-core/src/server
, содержит всю логику, отвечающую за действия автоматизации браузера. Это означает, что она отправляет команды через Chrome DevTools Protocol (CDP) или аналогичные протоколы, чтобы сообщить браузеру выполнить такие действия, как "нажать кнопку входа", "ввести 'Hello' в текстовое поле" или "перейти на 'https://news.ycombinator.com'". Серверная библиотека содержит реализации для каждого из поддерживаемых браузеров и предоставляет унифицированный API через WebSocket-сервер для доступа к этой функциональности из других процессов.// my-automation-script.ts
import { chromium } from "@playwright/test";
(async function () {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto("https://playwright.dev");
})();
chromium
из экземпляра класса в playwright-core/client/playwright
. browser.newPage
и page.goto
делают запросы к интерфейсам в серверной библиотеке, которая взаимодействует с браузерами через CDP или аналогичные протоколы. Это означает, что если вы когда-либо захотите написать собственный инструмент автоматизации браузера, независимый от конкретного браузера, полезной отправной точкой будет использование серверной библиотеки из пакета playwright-core
, поскольку она предоставляет всесторонний и проверенный API для такой функциональности.ChannelOwner
), что и в клиентской библиотеке, найденной в playwright-core
.07 Oct, 08:39
playwright/
browser_patches/
docs/
examples/
packages/
tests/
utils/
package.json
, который включает следующие ключи:{
"name": "playwright-internal",
"private": true,
"workspaces": ["packages/*"]
}
package.json
намекает, что основная функциональность находится внутри директории packages/
, поскольку корневой файл помечен как private
, имеет в названии "internal" и использует ключ "workspaces"
, который сообщает Node.js, что проект имеет несколько пакетов. Давайте быстро рассмотрим другие директории верхнего уровня перед тем, как углубиться в папку packages/
browser_patches
— эта папка содержит патчи для браузеров, добавляющие дополнительные элементы управления и функции к некоторым движкам браузеров, используемых в этом проекте. Интересно, что здесь есть патч для WebKit, позволяющий ему работать на Windows.docs
— папка docs
содержит файлы Markdown, которые компилируются в документацию, представленную на сайте Playwright.examples
— здесь находятся примеры тестовых скриптов, к которым можно обратиться при начале работы с проектом Playwright.tests
— даже библиотеки для тестирования должны иметь автоматизированные тесты, которые написаны с использованием Playwright.utils
— директория utils
содержит различные инструменты, используемые во время сборки, Docker-образы, функциональность для Azure, генератор файлов типов и многое другое.packages
содержит более 20 отдельных пакетов, что усложняет понимание. Мы разделим этот список на понятные категории.html-reporter/
recorder/
trace-viewer/
trace/
web/
recorder
. Кроме того, есть trace
и trace-viewer
, которые могут записываться во время выполнения тестов Playwright. Пакет web/
предоставляет утилиты, общие для различных веб-инструментов, перечисленных здесь.playwright-core
вместе со скриптом установки только для соответствующего браузера. Вы можете найти их на npmjs, например, playwright-chromium
— это отдельный пакет, который имеет Playwright, специализированный только для автоматизации Chromium.playwright-chromium/
playwright-firefox/
playwright-webkit/
playwright-browser-chromium/
playwright-browser-firefox/
playwright-browser-webkit/
playwright-ct-core/
playwright-ct-react/
playwright-ct-react17/
playwright-ct-solid/
playwright-ct-svelte/
playwright-ct-vue/
playwright-ct-vue2/
05 Oct, 09:26
25 Sep, 10:57
noWaitAfter
помогает создавать быстрые и надёжные тесты, оптимизируя время ожидания в зависимости от типа действия.25 Sep, 10:57
noWaitAfter
в Playwright позволяет настроить, как обрабатываются действия, такие как клики по ссылкам, отправка форм или запуск сетевых запросов. Он определяет, должен ли Playwright ждать завершения вызванного события (навигации или перехода на другую страницу) перед тем, как перейти к следующему шагу.noWaitAfter: false
(по умолчанию): Playwright ждёт завершения действия перед переходом к следующему шагу.noWaitAfter: true:
Playwright немедленно переходит к следующему шагу, даже если действие ещё не завершено.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
гарантирует, что 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);
});
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("Действие завершено!")');
});
noWaitAfter: false
гарантирует, что Playwright будет ждать завершения действий, требующих сетевых запросов или навигации, что снижает риск ложноположительных результатов. noWaitAfter: false
для действий, связанных с навигацией или загрузкой данных.noWaitAfter: true
для манипуляций с интерфейсом и других действий, не требующих ожидания.17 Sep, 08:49
13 Sep, 08:38
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');
});
});
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');
});
});
13 Sep, 08:38
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);
});
});
06 Sep, 10:41
npx playwright test --tsconfig tsconfig.test.json
const response = await request.get('url', { params: 'userId=1' });
02 Sep, 09:43
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();
});
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();
});
29 Aug, 09:02
pre-commit
. Этот хук запускает проверки до того, как изменения будут зафиксированы, и позволяет автоматизировать запуск линтера и тестов перед каждым коммитом.{
"scripts": {
"lint": "eslint .",
"format": "prettier . --write",
"test": "jest"
}
}
pre-commit
в .husky/ и обновляет сценарий для подготовки в package.json.npx husky init
echo "npm prettier && npm lint && npm test" > .husky/pre-commit
git commit -m "..." -n
28 Aug, 06:49
page.getByRole()
для поиска по явным и неявным атрибутам доступности.page.getByText()
для поиска по текстовому содержимому.page.getByLabel()
для поиска по лейблуpage.getByPlaceholder()
для поиска поля ввода по плейсхолдеру.page.getByAltText()
для поиска элемента, по альтернативному тексту.page.getByTitle()
для поиска элемента по его атрибуту title.page.getByTestId()
для поиска элемента по атрибуту data-testid.26 Aug, 13:09
chrome://net-export/
может предоставить более подробные данные, включая информацию о сетевых пакетах, DNS-запросах и внутренней работе браузера.net-export
, которая проста и удобна в использовании:chrome://net-export/
чаще используется для более глубокого и продолжительного анализа сетевой активности, когда стандартных возможностей DevTools недостаточно для решения задач.23 Aug, 15:00
22 Aug, 07:12
javascript:
. Например:
javascript:(function(){
// Ваш код здесь
const countElement = $$('[class^="_ObjectsListCard"]').length;
console.log(`Количество проектов: ${countElement}`);
})();
21 Aug, 08:39
value
, так как внутренние скрипты фреймворка могут ожидать определенных событий или использовать собственные механизмы управления состоянием.const nameInput = document.querySelector('#name');
nameInput.value = 'Тестовое имя';
nameInput.dispatchEvent(new Event('input', { bubbles: true }));
nameInput.dispatchEvent(new Event('change', { bubbles: true }));
21 Aug, 08:39
setInterval(()=> {
$('#gift').click()
}, 150)
const countElement = $$('[class^="_ObjectsListCard"]').length;
console.log(`Количество проектов: ${countElement}`);
const links = [...document.querySelectorAll('a')].map(link => link.href);
console.log('Все ссылки на странице:', links);
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);
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}`);
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));
const socket = new WebSocket('wss://example.com/socket');
socket.onopen = () => {
console.log('Соединение установлено');
socket.send(JSON.stringify({ action: 'subscribe', channel: 'updates' }));
};
//далее отправляем различные запросы
document.querySelector('#name').value = 'Тестовое имя';
document.querySelector('#email').value = '[email protected]';
document.querySelector('form').submit();
console.log('Форма заполнена и отправлена');
17 Aug, 10:13