E2E тестирование C#: гайд для тех, кто устал от багов в продакшне

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему юнит-тесты не спасают, и когда нужны 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()                

Блоги

Рекомендаций по книгам