Стандартный expect() останавливает тест при первом же падении. Форма с четырьмя сломанными сообщениями валидации потребует четырёх прогонов чтобы диагностировать всё. expect.soft() собирает все падения и сообщает о них вместе в конце, но тест всё равно считается проваленным если хоть один мягкий ассёрт не прошёл: выполнение продолжается, а не игнорируется. Эта статья разбирает API, критерии выбора между жёсткими и мягкими ассёртами, как проверять test.info().errors чтобы остановить тест досрочно когда предусловие не выполнено, и паттерн который сочетает жёсткие ассёрты для критического состояния страницы с мягкими для независимых свойств.
Как работают мягкие ассёрты
import { test, expect } from '@playwright/test';
test('user profile page has all required elements', async ({ page }) => {
await page.goto('/profile');
// Мягкие ассёрты — все выполнятся даже если некоторые упадут
await expect.soft(page.getByLabel('Full name')).toBeVisible();
await expect.soft(page.getByLabel('Email')).toBeVisible();
await expect.soft(page.getByLabel('Phone number')).toBeVisible();
await expect.soft(page.getByRole('button', { name: 'Save changes' })).toBeEnabled();
await expect.soft(page.getByRole('img', { name: 'Profile photo' })).toBeVisible();
// Тест падает здесь — если хотя бы один мягкий ассёрт провалился выше
expect(test.info().errors).toHaveLength(0);
});Каждый expect.soft() выполняется. Падения накапливаются. Тест продолжает работу. В конце (или при явной проверке test.info().errors) все падения выводятся сразу.
Жёсткие vs. мягкие ассёрты
Жёсткие ассёрты (expect) нужны когда падение делает остаток теста бессмысленным (например, логин не прошёл и всё остальное недоступно), когда проверяется предусловие, а не результат, или когда одно падение логически влечёт другие (навигация не сработала, всё на странице не имеет смысла).
Мягкие ассёрты (expect.soft) нужны когда проверяются несколько независимых свойств одной страницы, когда каждое падение независимо даёт полезную информацию для отладки, или когда верифицируется что у формы, строки таблицы или карточки есть все поля.
test('order confirmation displays all order details', async ({ page }) => {
await page.goto('/orders/12345');
// Жёсткий ассёрт — если упадёт, остальное не имеет смысла
await expect(page.getByRole('heading', { name: 'Order #12345' })).toBeVisible();
// Мягкие ассёрты — независимые проверки, каждая полезна
await expect.soft(page.getByText('Status: Confirmed')).toBeVisible();
await expect.soft(page.getByText('Delivery: 2–5 business days')).toBeVisible();
await expect.soft(page.getByText('Total: $149.99')).toBeVisible();
await expect.soft(page.getByRole('button', { name: 'Track order' })).toBeEnabled();
await expect.soft(page.getByRole('button', { name: 'Cancel order' })).toBeEnabled();
});Как читать падения мягких ассёртов
При падении мягкого ассёрта Playwright выводит все проблемы сразу:
Error: 2 soft assertion(s) failed.
1) Soft assertion failed: expect(locator).toBeVisible()
Call log:
- waiting for page.getByText('Status: Confirmed')
Error: expect(locator).toBeVisible() with timeout 5000ms
Received: hidden
2) Soft assertion failed: expect(locator).toBeEnabled()
Call log:
- waiting for page.getByRole('button', { name: 'Cancel order' })
Error: expect(locator).toBeEnabled() with timeout 5000ms
Received: disabledДве проблемы за один прогон, а не по одной за прогон.
Мягкие ассёрты в тестах валидации форм
Валидация форм: классический сценарий. Много состояний ошибок для проверки, все независимые.
test('form shows all validation errors on empty submit', async ({ page }) => {
await page.goto('/register');
await page.getByRole('button', { name: 'Create account' }).click();
// Проверяем что все сообщения валидации появились
await expect.soft(page.getByText('Email is required')).toBeVisible();
await expect.soft(page.getByText('Password is required')).toBeVisible();
await expect.soft(page.getByText('First name is required')).toBeVisible();
await expect.soft(page.getByText('Last name is required')).toBeVisible();
// Кнопка по-прежнему доступна после неудачной отправки
await expect.soft(page.getByRole('button', { name: 'Create account' })).toBeEnabled();
});Без мягких ассёртов ты узнаешь только о первом отсутствующем сообщении и потратишь ещё три прогона чтобы найти все четыре проблемы.
Явная проверка ошибок
Падения мягких ассёртов можно проверить в любой точке теста:
test('product page completeness check', async ({ page }) => {
await page.goto('/products/laptop-pro');
// Секция 1: hero-блок
await expect.soft(page.getByRole('heading', { level: 1 })).toBeVisible();
await expect.soft(page.getByRole('img', { name: /product/i })).toBeVisible();
await expect.soft(page.getByText(/\$\d+\.\d{2}/)).toBeVisible(); // Цена
// Если в hero-блоке есть падения, останавливаемся — проверять остальное нет смысла
if (test.info().errors.length > 0) {
test.fail();
return;
}
// Секция 2: выполняется только если hero прошёл
await expect.soft(page.getByRole('button', { name: 'Add to cart' })).toBeEnabled();
await expect.soft(page.getByRole('tab', { name: 'Reviews' })).toBeVisible();
await expect.soft(page.getByRole('tab', { name: 'Specifications' })).toBeVisible();
});Сочетание жёстких и мягких ассёртов
Самый эффективный паттерн: жёсткие ассёрты для предусловий и критического состояния, мягкие для отдельных свойств:
test('dashboard loads all widgets', async ({ page }) => {
await page.goto('/dashboard');
// Жёсткие — если упадут, страница вообще не загрузилась
await expect(page).toHaveTitle(/Dashboard/);
await expect(page.getByRole('main')).toBeVisible();
// Мягкие — каждый виджет независим
await expect.soft(page.getByTestId('revenue-widget')).toBeVisible();
await expect.soft(page.getByTestId('orders-widget')).toBeVisible();
await expect.soft(page.getByTestId('users-widget')).toBeVisible();
await expect.soft(page.getByTestId('activity-feed')).toBeVisible();
// Мягкие — каждая метрика независима
await expect.soft(page.getByTestId('revenue-amount')).toContainText('$');
await expect.soft(page.getByTestId('orders-count')).toContainText(/\d+/);
});Когда мягкие ассёрты не нужны
Мягкие ассёрты накапливают падения и дают тесту продолжать работу. Это может скрывать проблемы состояния:
// Опасно — если клик не сработал, ассёрты ниже проверяют неверное состояние
await expect.soft(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
await page.getByRole('button', { name: 'Submit' }).click();
await expect.soft(page.getByText('Order confirmed')).toBeVisible(); // может пройти случайноЕсли действия зависят от прохождения предыдущих проверок, используй жёсткие ассёрты. Мягкие ассёрты подходят для наблюдения (что есть на странице), а не для управления потоком выполнения.
→ See also: Assertions в Playwright: полное руководство | Структура тестов Playwright: describe, beforeEach, afterEach и хуки | Как читать сообщения об ошибках Playwright