Почему юнит-тесты не спасают, и когда нужны E2E-тесты
Представьте: вы написали идеальный код на C#. Юнит-тесты зелёные, покрытие — 95%, код-ревью прошло. Выкатываете релиз, а пользователи пишут: «Кнопка "Оформить заказ" не работает, если в корзине больше 3 товаров». Знакомо?
Юнит-тесты проверяют методы изолированно. Интеграционные — проверяют связку базы и API. Но E2E тестирование C# приложений (End-to-End) — это единственный способ убедиться, что вся система работает как единое целое: браузер, сервер, база данных, внешние API. Сквозные тесты имитируют действия реального пользователя: клики, ввод данных, ожидание загрузки страниц.
В этой статье мы не просто расскажем теорию. Мы покажем, как писать E2E-тесты на C# с использованием Selenium и Playwright, как организовать код через Page Object Model и как встроить автотесты в CI/CD. Поехали.
Что такое E2E тестирование C# на практике
E2E-тест — это сценарий, который проходит весь путь пользователя: от открытия страницы до завершения целевого действия (покупка, регистрация, отправка формы). Для C#-разработчика это обычно означает:
- Запуск браузера (Chrome, Firefox, Edge) через WebDriver
- Поиск элементов на странице (кнопки, поля ввода, ссылки)
- Совершение действий (клик, ввод текста, выбор из списка)
- Проверка ожидаемого результата (появилось сообщение, изменился URL, обновились данные)
- Закрытие браузера и очистка тестовых данных
Главное отличие от юнит-тестов: E2E-тесты работают с реальным UI. Они медленные, хрупкие (зависят от CSS-селекторов), но бесценные для проверки критических бизнес-сценариев.
Инструменты для E2E тестирования C#: Selenium vs Playwright
Выбор инструмента — это первый вопрос, который встаёт перед командой. Рассмотрим два самых популярных варианта.
Selenium WebDriver
Стандарт индустрии. Работает через WebDriver — отдельный процесс, который управляет браузером. Поддерживает все браузеры, огромное комьюнити, тысячи готовых решений. Минус: настройка требует драйверов (ChromeDriver, GeckoDriver), и тесты могут быть медленными из-за ожиданий.
Playwright для .NET
Более молодой, но уже очень популярный инструмент от Microsoft. Работает напрямую через протокол DevTools, без лишних прослоек. Быстрее, стабильнее, автоматически ждёт загрузки элементов (нет явных Thread.Sleep). Поддерживает не только браузеры, но и мобильные эмуляторы.
В этой статье мы покажем примеры на обоих фреймворках, чтобы вы могли сравнить.
Пишем первый E2E-тест на C#: Selenium
Начнём с классики. Установим пакет через NuGet:
dotnet add package Selenium.WebDriver
dotnet add package Selenium.Support
dotnet add package Selenium.WebDriver.ChromeDriver
Теперь напишем тест для сайта-примера (предположим, это интернет-магазин). Мы хотим проверить, что после добавления товара в корзину на странице появляется значок с количеством товаров.
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
public class CartE2ETests
{
[Fact]
public void AddItemToCart_ShouldUpdateCartBadge()
{
using var driver = new ChromeDriver();
// Arrange
driver.Navigate().GoToUrl("https://demo-store.com");
driver.Manage().Window.Maximize();
// Act
var addToCartButton = driver.FindElement(By.CssSelector(".product-card .add-to-cart"));
addToCartButton.Click();
// Wait for badge to appear
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
var cartBadge = wait.Until(drv =>
drv.FindElement(By.CssSelector(".cart-badge")));
// Assert
Assert.Equal("1", cartBadge.Text);
driver.Quit();
}
}
Этот код делает следующее:
- Открывает браузер Chrome и переходит на сайт
- Находит кнопку "Добавить в корзину" по CSS-селектору
- Кликает по ней
- Ожидает появления бейджа корзины (до 5 секунд)
- Проверяет, что в бейдже отображается "1"
Обратите внимание на WebDriverWait — без него тест упадёт, если элемент не успел загрузиться. Никогда не используйте Thread.Sleep в E2E-тестах, это антипаттерн.
Тот же тест на Playwright: быстрее и чище
Установим Playwright для .NET:
dotnet add package Microsoft.Playwright
dotnet build
pwsh bin/Debug/net8.0/playwright.ps1 install
А вот код теста:
using Microsoft.Playwright;
public class PlaywrightCartTests
{
[Fact]
public async Task AddItemToCart_ShouldUpdateBadge()
{
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
Headless = false
});
var page = await browser.NewPageAsync();
// Arrange
await page.GotoAsync("https://demo-store.com");
// Act
await page.ClickAsync(".product-card .add-to-cart");
// Wait and assert - Playwright ждёт автоматически
var badgeText = await page.TextContentAsync(".cart-badge");
// Assert
Assert.Equal("1", badgeText);
}
}
Сразу заметны преимущества Playwright:
- Асинхронность — тесты не блокируют поток
- Автоматическое ожидание:
TextContentAsyncне вернёт значение, пока элемент не появится в DOM - Не нужно явно управлять WebDriverWait
- Меньше кода для того же функционала
Архитектура E2E-тестов: Page Object Model
Если вы напишете 50 тестов с прямыми селекторами в каждом, поддерживать это станет адом. При изменении вёрстки придётся править каждый тест. Решение — Page Object Model (POM).
Идея: для каждой страницы создаётся класс, который инкапсулирует логику поиска элементов и действий. Тесты работают с методами этого класса, а не с CSS-селекторами напрямую.
Пример Page Object на C# для страницы корзины
public class CartPage
{
private readonly IPage _page;
// Локаторы — только здесь
private const string CartBadgeSelector = ".cart-badge";
private const string CheckoutButtonSelector = "#checkout-btn";
public CartPage(IPage page)
{
_page = page;
}
public async Task GetCartBadgeTextAsync()