АШШумилкин А. О.ОАиП · ПиТПМ
ПиТПМ · Лекция 07

Лекция №7. Автоматизированное тестирование веб-интерфейсов (E2E) и управление асинхронными операциями.

4 379 слов5 разделов2 иллюстраций
↓ Скачать Markdown

§7.1. Эволюция E2E-инструментов: от Selenium к Playwright

В первом семестре мы познакомились с Selenium WebDriver — классическим инструментом для автоматизации браузера. Мы научились:

  • Открывать браузер через new ChromeDriver().
  • Находить элементы по ID, CSS, XPath.
  • Кликать, вводить текст, читать значения.
  • Использовать WebDriverWait для ожидания загрузки элементов.

Selenium существует уже более 15 лет. Это проверенный, надежный инструмент, который используется в тысячах проектов по всему миру.

Но у него есть проблемы, и вы с ними уже столкнулись (или еще столкнетесь 😏).

Болевые точки Selenium (или почему индустрия ищет альтернативы) заключаются в проблемах, о которых мы сейчас поговорим.

В 2020 году команда Microsoft, которая до этого разрабатывала инструмент Puppeteer (для автоматизации Chrome), решила создать Playwright.

Их цель была амбициозной

  • сделать инструмент, который решает все перечисленные проблемы и работает одинаково хорошо со всеми современными браузерами.

Ключевая идея Playwright

  • звучит так, что вместо того, чтобы управлять браузером через внешний драйвер, Playwright общается с браузером напрямую через DevTools Protocol — тот самый протокол, который используют инструменты разработчика (F12) в браузере.

Вот как это выглядит в сравнении:

ХарактеристикаSeleniumPlaywright
Способ общенияЧерез внешний драйвер (WebDriver)Напрямую через DevTools Protocol
Количество посредниковТест → Драйвер → БраузерТест → Браузер
СкоростьМедленнее (лишний шаг)Быстрее (прямое общение)
Сложность настройкиНужно устанавливать драйвер для каждого браузераДостаточно установить браузеры через Playwright CLI
Работа с разными браузерамиРазные реализации WebDriverЕдиный API для Chrome, Firefox, Safari
Поддержка современных сайтовХуже (особенно SPA)Отличная (изначально создавался под SPA)

Давайте разберем архитектурное отличие CDP (Chrome DevTools Protocol, то, что использует Playwright) от WebDriver поподробнее, потому что это фундаментально меняет то, как работают тесты при работе с тестированием приложений.

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

  • Вы — ваш тест.
  • Курьер — WebDriver (chromedriver.exe).
  • Друг — браузер.

Каждое действие (клик, ввод текста) — это отдельное письмо. Курьер может задержаться, потерять письмо или перепутать адресата.

Теперь рассмотрим работу Playwright (новый подход). Представьте, что вы можете говорить напрямую по телефону с другом. Вы задаете вопрос, он отвечает мгновенно. Никаких посредников, никаких задержек.

  • Вы — ваш тест.
  • Телефонная связь — DevTools Protocol (CDP).
  • Друг — браузер.

Playwright использует тот же протокол, что и инструменты разработчика (DevTools). Когда вы открываете F12 в браузере, вы видите элементы, сетевые запросы, консоль — всё это работает через CDP. Playwright просто использует этот же канал для управления браузером.

Что это дает на практике:

1. Скорость. Команды выполняются быстрее, потому что нет лишнего посредника.

2. Надежность. Меньше точек отказа — меньше шансов, что что-то пойдет не так.

3. Больше возможностей. Playwright может перехватывать сетевые запросы, эмулировать мобильные устройства, подменять геолокацию — всё то, что Selenium делает с трудом или не делает вовсе.

Важный нюанс

Playwright управляет браузером снаружи, но имеет доступ к его внутреннему состоянию. Это позволяет ему:

  • Слушать сетевые запросы (перехватывать, подменять, проверять).
  • Внедрять JavaScript-код в контекст страницы.
  • Эмулировать разные устройства (iPhone, iPad, десктоп) без изменения кода.

Selenium тоже умеет выполнять JavaScript, но делает это через IJavaScriptExecutor, что менее удобно и медленнее.

Простая аналогия:

  • Selenium похож на управление роботом через пульт: вы нажимаете кнопку, он делает действие, но вы не знаете, что происходит внутри его «мозга».
  • Playwright — вы подключены к роботу через кабель и видите все его внутренние процессы. Вы можете не только давать команды, но и читать его мысли (состояние DOM, сетевые запросы, консоль).

§7.2. Асинхронность как фундамент: async/await в тестах

Когда в первом семестре мы писали тесты на Selenium, они выглядели следующим образом:

csharp

[Fact]
public void Login_ValidUser_ShowsDashboard()
{
    var driver = new ChromeDriver();
    driver.Navigate().GoToUrl("https://mysite.com/login");
    driver.FindElement(By.Id("username")).SendKeys("admin");
    driver.FindElement(By.Id("password")).SendKeys("12345");
    driver.FindElement(By.Id("login-btn")).Click();
    
    var welcome = driver.FindElement(By.CssSelector(".welcome"));
    Assert.True(welcome.Displayed);
    
    driver.Quit();
}

Это синхронный код. Каждая команда выполняется последовательно:

1. Открыли страницу — ждем, пока загрузится.

2. Нашли поле логина — нашли, идем дальше.

3. Ввели текст — ввели, идем дальше.

4. Нажали кнопку — нажали, идем дальше.

Код выполняется по шагам, один за другим. Пока одна команда не завершится, следующая не начнется.

Это просто и понятно. Мы все привыкли к такому стилю. Но у этого подхода есть проблема.

Когда ваш тест отправляет команду в браузер (например, «найти элемент» или «перейти по ссылке»), происходит вот что:

1. Ваш код отправляет команду.

2. Команда проходит через сеть/процесс к браузеру.

3. Браузер выполняет команду.

4. Результат возвращается обратно.

5. Ваш код получает результат и продолжает работу.

Это операция ввода-вывода (I/O). Такие операции, в отличие от вычислений (например, сложение чисел), занимают относительно много времени.

В чем же проблема синхронного подхода? Когда вы выполняете синхронную операцию, поток выполнения (thread) вашей программы блокируется. Он просто стоит и ждет, пока придет ответ от браузера. В это время он ничего не делает — просто тратит ресурсы впустую.

Представьте, что вы ждете доставку пиццы. Синхронный подход — вы стоите у двери и смотрите в глазок, ничего не делая, пока не приедет курьер.

Playwright с самого начала спроектирован как асинхронный. Это означает, что когда он отправляет команду в браузер, он не ждет в блокирующем режиме. Он говорит: «Я отправил команду, а когда придет ответ — я тогда продолжу». В это время поток может делать другие полезные вещи (в контексте тестов — готовить следующий запрос, обрабатывать другой тест и т.д.).

Давайте посмотрим на один и тот же тест, написанный в двух подходах.

Что изменилось:

1. Ключевое слово async в сигнатуре метода.

2. Возвращаемый тип Task (или Task<T> для методов с возвратом).

3. Ключевое слово await перед каждым асинхронным вызовом.

4. Все методы Playwright возвращают Task и требуют await.

На первый взгляд — просто добавили await перед каждой строкой. Но за этим стоят серьезные архитектурные изменения.

Когда вы пишете await page.GotoAsync("..."), происходит следующее:

1. Метод GotoAsync возвращает Task — объект, который представляет собой «обещание» выполнить действие в будущем.

2. Ключевое слово await говорит компилятору: «Если задача еще не завершена, верни управление вызывающему коду. Когда задача завершится, продолжай выполнение с этой точки».

3. Поток выполнения не блокируется. Он может обслуживать другие задачи (например, параллельные тесты).

Простая аналогия – представьте, что вы заказываете еду в ресторане.

  • Синхронный подход (Selenium): Вы стоите у стойки и смотрите, как повар готовит. Вы не можете отойти, пока еда не будет готова. Вы блокируете очередь.
  • Асинхронный подход (Playwright): Вы делаете заказ, получаете номерок и садитесь за столик. Повар готовит, а вы в это время можете полистать меню, ответить на сообщения или просто отдохнуть. Когда еда готова, вам приносят.

А как асинхронность борется с race conditions? Race condition (состояние гонки) — это ситуация, когда результат программы зависит от того, в каком порядке выполняются операции. В контексте E2E-тестов это часто выглядит так:

1. Тест нажал кнопку.

2. Сервер начал обрабатывать запрос.

3. Тест пытается найти элемент, который появится после обработки запроса.

4. Элемент еще не появился → тест падает.

В Selenium вы решали эту проблему с помощью WebDriverWait:

csharp

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
var button = wait.Until(
   ExpectedConditions.ElementToBeClickable(
      By.Id("submit")
   )
);

Вы вручную говорили: «Подожди, пока кнопка станет кликабельной». Это работало, но требовало дисциплины. Забыли написать WebDriverWait — получили flaky-тест.

В Playwright асинхронность тесно связана с механизмом автоматических ожиданий. Поскольку Playwright изначально асинхронный, он может «подождать» асинхронно, не блокируя поток, пока элемент не будет готов.

csharp

// Playwright автоматически ждет, 
// пока элемент станет готов для действия
// Не нужно писать явный WebDriverWait!
await page.ClickAsync("#submit");

Что происходит внутри:

1. Playwright получает команду «кликнуть по #submit».

2. Он асинхронно проверяет: «Этот элемент видим? Стабилен? Не перекрыт другим элементом?».

3. Если нет — он ждет (не блокируя поток) и проверяет снова.

4. Как только элемент становится готов — выполняет клик.

5. Если элемент не появился за таймаут — бросает понятную ошибку.

В Selenium вы ждали вручную. В Playwright ожидание встроено в каждый метод.

Что это дает на практике? Меньше кода для стабильности:

ХарактеристикаSeleniumPlaywright
Ожидания элементовНужно явно писать WebDriverWait для каждого действияОжидания встроены в методы, не нужно писать дополнительный код
Риск flaky-тестовЕсли забыли WebDriverWait — тест может стать flakyFlaky-тесты встречаются реже, потому что ожидания работают автоматически
Настройка таймаутовСложно настроить правильное время ожиданияТаймаут настраивается глобально для всего теста

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

Правило для запоминания

В Playwright каждое действие с браузером — асинхронное. Всегда используйте await перед вызовом методов Playwright. Если забыли — тест упадет

§7.3. «Умные» ожидания: уход от Thread.Sleep и WebDriverWait

В лекции №4 первого семестра мы подробно разбирали антипаттерн «Спящий тест»:

csharp

// ❌ ПЛОХО: гадаем, сколько времени нужно ждать
Thread.Sleep(3000); // Ждем 3 секунды "на всякий случай"
driver.FindElement(By.Id("submit")).Click();

И даже правильный вариант с WebDriverWait выглядел громоздко:

csharp

// ⚠️ ЛУЧШЕ, но все равно приходится писать лишний код
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
var button = wait.Until(
   ExpectedConditions.ElementToBeClickable(
      By.Id("submit")
   )
);
button.Click();

Проблема Selenium в том, что он не знает, готов ли элемент к взаимодействию. Он просто ищет элемент в DOM (структуре страницы) и пытается с ним работать. А если элемент есть в DOM, но еще не отрисовался или перекрыт другим блоком — Selenium падает.

Вы, как разработчик теста, вынуждены вручную объяснять Selenium: «Подожди, пока кнопка станет кликабельной». И если вы забыли это сделать — тест становится нестабильным (flaky).

В Playwright подход кардинально другой. Перед тем как выполнить любое действие (клик, ввод текста, наведение), Playwright автоматически выполняет серию проверок. Это называется Actionability Checks (проверки готовности к действию).

Что именно проверяет Playwright перед кликом или вводом текста:

ПроверкаЧто это значитПример проблемы
Видим ли элемент?Элемент не скрыт CSS-свойством display: none или visibility: hiddenЭлемент есть в коде, но еще не появился на экране
Стабилен ли элемент?Элемент не двигается по странице (например, не анимируется)Анимация появления, слайдер, бегущая строка
Не перекрыт ли элемент?Никакой другой элемент не находится поверх негоВсплывающая реклама, модальное окно, загрузочный спиннер
Не отключен ли элемент?У элемента нет атрибута disabledКнопка "Отправить" заблокирована, пока не заполнены все поля

Что это значит для вас, как для разработчика тестов? Вы просто пишете:

csharp

// Всего одна строка. Playwright сам сделает все проверки!
await page.ClickAsync("#submit");

Playwright не просто кликает. Он:

1. Ждет, пока элемент появится в DOM.

2. Проверяет, что он видим.

3. Проверяет, что он стабилен (не двигается).

4. Проверяет, что он не перекрыт.

5. Проверяет, что он активен.

6. И только после этого выполняет клик.

Если элемент не готов в течение таймаута (по умолчанию 30 секунд), Playwright выбросит понятную ошибку с указанием, какая именно проверка не прошла.

Простая аналогия:

  • Selenium — это как человек, который пытается пожать руку, даже не посмотрев, есть ли перед ним человек. Он просто машет рукой в пустоту и удивляется, почему никто не ответил.
  • Playwright — это как человек, который сначала смотрит на собеседника, убеждается, что он смотрит в ответ, протягивает руку, ждет ответного рукопожатия и только потом здоровается.

Вторая гениальная фишка Playwright — это Web-First Assertions (утверждения, ориентированные на веб).

В Selenium вы писали так:

csharp

var message = driver.FindElement(By.Id("success"));
// Если сообщение еще не появилось — тест упадет
Assert.True(message.Displayed);

Вы делали проверку один раз. Если сообщение появлялось через 2 секунды, а вы проверили через 1 секунду — тест падал. Выход — снова использовать WebDriverWait:

csharp

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
var message = wait.Until(d => d.FindElement(By.Id("success")));
Assert.True(message.Displayed);

В Playwright проверки работают по-другому. Они автоматически повторяются (retry), пока условие не выполнится или не истечет таймаут.

csharp

// Playwright будет проверять это условие снова и снова
// пока сообщение не появится или не пройдет 5 секунд
await Expect(page.Locator("#success")).ToBeVisibleAsync();

Что происходит внутри:

1. Playwright проверяет, видим ли элемент.

2. Если нет — он ждет короткое время (например, 100 мс).

3. Проверяет снова.

4. И так до тех пор, пока элемент не появится или не истечет таймаут.

Это называется автоматическим ретраем (auto-retry). Именно это делает тесты на Playwright стабильными без лишнего кода.

Давайте посмотрим на один и тот же сценарий (клик по кнопке и проверка появления сообщения) в двух подходах.

Вот полный пример теста на Playwright для .NET, в котором нет ни одного Thread.Sleep или явного WebDriverWait:

csharp

[Fact]
public async Task UserCanAddProductToCart()
{
    // 1. Настраиваем Playwright
    using var playwright = await Playwright.CreateAsync();
    await using var browser = await playwright.Chromium.LaunchAsync();
    var page = await browser.NewPageAsync();
    
    // 2. Переходим на сайт (ждет загрузки автоматически)
    await page.GotoAsync("https://myshop.com");
    
    // 3. Ищем товар (ждет, пока появится поле поиска)
    await page.FillAsync("#search-input", "iPhone 15");
    await page.ClickAsync("#search-button");
    
    // 4. Ждем, пока появится список товаров (автоматически)
    // Кликаем по кнопке "В корзину" у первого товара
    await page.ClickAsync(".product-card:first-child .add-to-cart");
    
    // 5. Переходим в корзину (ждет, пока кнопка станет кликабельной)
    await page.ClickAsync("#cart-icon");
    
    // 6. Проверяем, что товар в корзине (автоматический retry)
    await Expect(page.Locator(".cart-item"))
                     .ToHaveTextAsync("iPhone 15");
    
    // 7. Оформляем заказ (ждет, пока кнопка станет активной)
    await page.ClickAsync("#checkout-button");
    
    // 8. Проверяем, что заказ оформлен (автоматический retry)
    await Expect(page.Locator(".order-confirmation"))
                     .ToBeVisibleAsync();
}

Где здесь ожидания?

  • Нет Thread.Sleep — мы не гадаем, сколько времени нужно ждать.
  • Нет WebDriverWait — не нужно писать сложные условия.
  • Нет ручных ожиданий — Playwright сам решает, когда элемент готов.

Если страница загрузилась за 0.5 секунды — тест продолжается сразу. Если за 5 секунд — Playwright подождет 5 секунд. Если за 31 секунду — тест упадет с ошибкой, и вы точно будете знать, что проблема в производительности, а не в тесте.

Почему это работает. Самое основное, это встроенные таймауты. У Playwright есть разумные таймауты по умолчанию:

  • Таймаут для действий (ClickAsync, FillAsync, GotoAsync) — 30 секунд.
  • Таймаут для проверок (Expect) — 5 секунд.

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

csharp

// Увеличиваем таймаут для конкретной проверки
await Expect(page.Locator(".slow-element"))
    .ToBeVisibleAsync(new() { Timeout = 15000 });

Но в большинстве случаев стандартные настройки работают отлично.

А что делать, если элемент все равно не находится? Иногда Playwright не может найти элемент, потому что он появляется только после выполнения JavaScript-кода на странице. В этом случае вы можете использовать дополнительные методы ожидания:

csharp

// Ждем, пока элемент появится в DOM
await page.WaitForSelectorAsync("#dynamic-element");

// Или ждем, пока исчезнет загрузочный спиннер
await page.WaitForSelectorAsync(".loader", new() { 
   State = WaitForSelectorState.Hidden 
});

Но даже в этом случае вы не пишете Thread.Sleep — вы ждете конкретное условие, а не произвольное время.

В заключение, хочется подытожить ключевыми отличиями двух подходов, а именно Selenium и Playwright.

Что делаемSeleniumPlaywright
Ожидание кликабельностиПишем WebDriverWait + ExpectedConditionsВстроено в ClickAsync()
Ожидание появления элементаПишем WebDriverWait + FindElementВстроено в Expect().ToBeVisibleAsync()
Количество строк кода для ожиданий3-4 строки на каждое действие0 строк — всё встроено
Риск забыть ожиданиеВысокий (тест станет flaky)Низкий (ожидания всегда работают)

§7.4. Современный Page Object Model для Playwright: Locators и Page Factory

В лекции №4 первого семестра мы разбирали Page Object Model для Selenium. Класс страницы выглядел примерно так:

csharp

public class LoginPage
{
    private readonly IWebDriver _driver;
    
    // Локаторы через By.Id, By.XPath, By.CssSelector
    private By UserNameField => By.Id("username");
    private By PasswordField => By.Id("password");
    private By LoginButton => By.XPath("//button[text()='Войти']");
    
    public LoginPage(IWebDriver driver)
    {
        _driver = driver;
    }
    
    public HomePage LoginAs(string username, string password)
    {
        _driver.FindElement(UserNameField).SendKeys(username);
        _driver.FindElement(PasswordField).SendKeys(password);
        _driver.FindElement(LoginButton).Click();
        return new HomePage(_driver);
    }
}

Проблемы этого подхода:

1. Локаторы хрупкие. Если дизайнер поменял структуру HTML — XPath ломается.

2. Сложно читать. By.XPath("//div[3]/form/button[1]") — это магия, а не инструкция.

3. Найдет что угодно. XPath может найти неправильный элемент, если структура страницы изменилась.

4. Нет приоритетов. Selenium не подсказывает, какой локатор лучше использовать.

В Playwright полностью отказались от старого подхода с By.Id, By.XPath и FindElement. Вместо этого используется Locators API — новый, более умный способ поиска элементов.

Locator

— это объект, который представляет собой инструкцию по поиску элемента на странице. Но в отличие от Selenium, Locator в Playwright — это ленивый объект. Он не ищет элемент сразу, а только запоминает, как его найти.

Реальный поиск происходит только в момент действия (клик, ввод текста).

csharp

// Locator — это просто инструкция, элемент еще не найден
var submitButton = page.Locator("#submit");

// Только здесь Playwright реально ищет элемент 
// и проверяет его готовность
await submitButton.ClickAsync();

Почему это круто:

1. Автоматические ожидания. Locator сам ждет, пока элемент появится и станет готов к действию.

2. Переиспользование. Один Locator можно использовать в разных методах.

3. Читаемость. Locator строится как цепочка понятных методов.

В Playwright есть четкая иерархия того, как искать элементы. Разработчики Playwright рекомендуют использовать локаторы в следующем порядке приоритета:

1. GetByRole (САМЫЙ ПРИОРИТЕТНЫЙ)

Ищет элемент по его роли в доступности (ARIA). Это самый надежный способ, потому что роли редко меняются, даже если дизайн перерисовывают.

csharp

// Найти кнопку по роли "button" и тексту "Войти"
var loginButton = page.GetByRole(AriaRole.Button, new() { 
   Name = "Войти" 
});

// Найти поле ввода по роли "textbox"
var usernameField = page.GetByRole(AriaRole.Textbox, new() { 
   Name = "Имя пользователя" 
});

Когда использовать

Всегда, когда элемент имеет правильную ARIA-роль. Это делает тесты устойчивыми к изменениям вёрстки.

2. GetByText

Ищет элемент по тексту, который видит пользователь.

csharp

// Найти кнопку с текстом "Добавить в корзину"
var addToCartButton = page.GetByText("Добавить в корзину");

// Найти заголовок с текстом "Добро пожаловать"
var welcomeHeader = page.GetByText("Добро пожаловать");

Когда использовать

Для ссылок, кнопок, заголовков — всего, что имеет понятный текстовый контент.

3. GetByLabel

Ищет элемент по связанному с ним тексту label (для полей ввода).

csharp

// Найти поле ввода, рядом с которым есть label "Email"
var emailField = page.GetByLabel("Email");

// Найти поле ввода, рядом с которым есть label "Пароль"
var passwordField = page.GetByLabel("Пароль");

Когда использовать

Для полей ввода, которые имеют HTML-тег <label> или атрибут aria-label.

4. GetByTestId

Ищет элемент по специальному атрибуту data-testid.

csharp

// Найти элемент с атрибутом data-testid="submit-button"
var submitButton = page.GetByTestId("submit-button");

Когда использовать

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

5. GetByPlaceholder

Ищет поле ввода по placeholder (подсказке внутри поля).

csharp

var searchField = page.GetByPlaceholder("Поиск товаров...");

6. CSS-селекторы (В ПОСЛЕДНЮЮ ОЧЕРЕДЬ)

Только если ничего из перечисленного выше не подходит.

csharp

var element = page.Locator(".product-card .price");

var element2 = page.Locator("#main-content");

Теперь давайте сравним старый и новый подход.

Теперь давайте посмотрим, как выглядит Page Object Model с использованием Locators API в Playwright.

ХарактеристикаSelenium (By.Id / XPath)Playwright (Locators API)
ЧитаемостьBy.XPath("//div[3]/form/button[1]") — магияGetByRole(AriaRole.Button, "Войти") — понятно
УстойчивостьЛомается при изменении структуры HTMLУстойчив, если не меняется роль или текст
ПриоритетыНет подсказок, какой локатор лучшеЧеткая иерархия (Role > Text > Label > TestId)
АвтоожиданияНужно писать WebDriverWait отдельноВстроены в Locator
РекомендацииВсе локаторы считаются равнымиЕсть четкие рекомендации, что использовать
Поддержка доступностиНе учитывает ARIA-ролиСоздан с учетом доступности (ARIA)

Важные правила для начинающих специалистов:

1. Никогда не используйте XPath, если можно использовать Role, Text или Label. XPath — это крайний случай.

2. Если разработчики добавили data-testid — используйте его. Это самый надежный способ.

3. Храните все локаторы внутри Page Object'ов. Никаких локаторов в тестах!

4. Locator живет до момента действия. Он не ищет элемент сразу, а только при клике или вводе текста. Это нормально.

§7.5. Расширенные возможности Playwright: Фикстуры, Трейсы и Сниппеты

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

csharp

[Fact]
public async Task SomeTest()
{
    // Один и тот же код в каждом тесте!
    using var playwright = await Playwright.CreateAsync();
    await using var browser = await playwright.Chromium.LaunchAsync();
    var page = await browser.NewPageAsync();
    
    // ... сам тест ...
}

Это дублирование. Если у вас 20 тестов — этот код повторяется 20 раз. Если нужно изменить настройки браузера (например, запускать в headless-режиме) — придется править все 20 тестов.

Headless-режим

— это запуск веб-браузера без графического пользовательского интерфейса (GUI).

Браузер работает в фоновом режиме, как консольное приложение:

  • Нет окон — вы не видите, как открываются вкладки.
  • Нет анимаций — страницы рендерятся в памяти, но не выводятся на экран.
  • Все функции сохраняются — браузер умеет выполнять JavaScript, загружать CSS, обрабатывать клики, отправлять запросы.

Playwright предлагает элегантное решение — фикстуры (Fixtures).

Что такое фикстуры?

Фикстура (Fixture)

— это просто «заготовка» или «шаблон», который подготавливает окружение для тестов.

Представьте, что вы каждый день готовите завтрак. Каждый раз вы:

1. Достаете сковороду.

2. Наливаете масло.

3. Разбиваете яйца.

4. Жарите.

Это одни и те же действия каждый день. Было бы удобно иметь готовый «набор для завтрака», где сковорода уже на плите, масло налито, а яйца разбиты. Вы просто приходите и начинаете жарить.

Фикстуры в Playwright — это такой же «набор для завтрака». Они автоматически:

  • Создают экземпляр Playwright.
  • Запускают браузер.
  • Создают контекст (изолированное окружение для теста).
  • Создают страницу.

И всё это — без дублирования кода в каждом тесте!

В Playwright для .NET есть специальный базовый класс — PageTest. Если ваш тестовый класс наследуется от него, вы получаете готовые объекты Page, Browser и Context без лишнего кода.

Как это выглядит:

csharp

using Microsoft.Playwright.NUnit;
using NUnit.Framework;

// Наследуемся от PageTest — получаем всё готовое!
public class LoginTests : PageTest
{
    [Test]
    public async Task Login_ValidCredentials_ShowsDashboard()
    {
        // Page уже создан! 
        // Не нужно писать Playwright.CreateAsync() и т.д.
        await Page.GotoAsync("https://mysite.com/login");
        await Page.FillAsync("#username", "admin");
        await Page.FillAsync("#password", "12345");
        await Page.ClickAsync("#login-btn");
        
        await Expect(Page.Locator(".welcome")).ToBeVisibleAsync();
    }
    
    [Test]
    public async Task Login_InvalidPassword_ShowsError()
    {
        // Тот же Page, но в другом тесте — новый, чистый экземпляр!
        await Page.GotoAsync("https://mysite.com/login");
        await Page.FillAsync("#username", "admin");
        await Page.FillAsync("#password", "wrong");
        await Page.ClickAsync("#login-btn");
        
        await Expect(Page.Locator(".error")).ToBeVisibleAsync();
    }
}

Что мы получили:

1. Никакого дублирования. Не нужно писать Playwright.CreateAsync() в каждом тесте.

2. Автоматическая изоляция. Каждый тест получает новую страницу. Тесты не влияют друг на друга.

3. Автоматическая очистка. После теста браузер закрывается автоматически.

В Selenium мы сами отвечали за то, чтобы тесты не влияли друг на друга. Нужно было создавать новый драйвер для каждого теста, закрывать его после теста, чистить cookies и localStorage.

В Playwright с PageTest это работает автоматически:

Что делает SeleniumЧто делает Playwright с PageTest
Вы сами создаете ChromeDriverPageTest создает IPage автоматически
Вы сами закрываете драйвер (driver.Quit())PageTest закрывает всё автоматически
Нужно чистить cookies вручнуюКаждый тест получает новую страницу — чистую
Тесты могут влиять друг на другаТесты полностью изолированы

Простая аналогия:

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

Перейдем к следующем части. Представьте, что =у вас есть много Page Object'ов (LoginPage, HomePage, CartPage, PaymentPage). Каждый раз создавать их вручную — утомительно:

csharp

var loginPage = new LoginPage(Page);
var homePage = new HomePage(Page);
var cartPage = new CartPage(Page);

Page Factory

— это паттерн, который централизует создание Page Object'ов.

csharp

public class PageFactory
{
    private readonly IPage _page;
    
    public PageFactory(IPage page)
    {
        _page = page;
    }
    
    public LoginPage LoginPage 
       => new LoginPage(_page);

    public HomePage HomePage 
       => new HomePage(_page);

    public CartPage CartPage 
       => new CartPage(_page);

    public PaymentPage PaymentPage 
       => new PaymentPage(_page);
}

Теперь тест выглядит так:

csharp

public class ShopTests : PageTest
{
    [Test]
    public async Task UserCanBuyProduct()
    {
        // Один раз создаем фабрику
        var factory = new PageFactory(Page);
        
        // Используем готовые объекты страниц
        await factory.LoginPage.NavigateToAsync();
        await factory.LoginPage.LoginAsAsync("user", "pass");
        
        await factory.HomePage.SearchForProductAsync("iPhone");

        await factory.CartPage.AddToCartAsync();

        await factory.CartPage.ProceedToCheckoutAsync();
        
        await factory.PaymentPage.PayWithCardAsync(
           "1234", "12/25", "123"
        );
        
        var isConfirmed = await factory.PaymentPage
                                       .IsOrderConfirmedAsync();
        Assert.True(isConfirmed);
    }
}

Что дает Page Factory:

1. Централизованное создание. Все страницы создаются в одном месте.

2. Читаемость. В тесте не видно new LoginPage(Page) — только factory.LoginPage.

3. Легкость поддержки. Если нужно изменить способ создания страниц — правим только фабрику.

Самая большая проблема в E2E-тестировании

— понять, почему упал тест. В Selenium вы видели только лог ошибки. Чтобы понять, что произошло, нужно было запускать тест заново с дебаггером или добавлять скриншоты вручную.

Playwright предлагает три мощных инструмента для отладки:

1. Скриншоты (Screenshots)

Playwright может автоматически делать скриншот в момент падения теста.

csharp

// В настройках теста
[Test]
public async Task MyTest()
{
    // Если тест упадет, Playwright сделает скриншот
    // и сохранит его в папке test-results
}

Или вы можете сделать скриншот вручную в любом месте теста:

csharp

await Page.ScreenshotAsync(new()
{
    Path = "screenshot.png",
    FullPage = true // Скриншот всей страницы
});

2. Видеозапись (Videos)

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

csharp

// В настройках браузера
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
    Headless = false
});

var context = await browser.NewContextAsync(new()
{
    RecordVideoDir = "videos/" // Куда сохранять видео
})

После теста у вас будет MP4-файл с записью всего, что делал браузер.

3. Трейсы (Trace Viewer) — самый мощный инструмент

Трейс (Trace)

— это запись всего, что происходило в тесте: каждый клик, каждый ввод текста, каждое изменение DOM, сетевые запросы, консольные ошибки. Это как «черный ящик» для теста.

csharp

// Включаем запись трейса
await context.Tracing.StartAsync(new()
{
    Screenshots = true,
    Snapshots = true,
    Sources = true
});
// ... выполняем тест ...

// Сохраняем трейс
await context.Tracing.StopAsync(new()
{
    Path = "trace.zip"
});

После теста у вас получится файл trace.zip. Вы открываете его в Trace Viewer (встроенный инструмент Playwright) и видите:

1. Пошаговую запись всех действий теста.

2. Скриншоты каждого шага.

3. DOM-дерево в каждый момент времени.

4. Сетевые запросы (что и когда отправлялось на сервер).

5. Консоль (ошибки JavaScript, логи).

6. Временную шкалу — что происходило в какой момент.

Простая аналогия:

  • Лог ошибки в Selenium — это как получить СМС: «Что-то пошло не так». И вы гадаете, что именно.
  • Трейс в Playwright — это как просмотр видео с камер наблюдения, где видно каждое движение, каждое действие, каждую ошибку.

Сравнение отладки в двух подходах, а именно Selenium и Playwright, выглядит следующим образом:

Возможность / ИнструментSeleniumPlaywright
ОшибкаStackTrace + сообщениеStackTrace + сообщение
СкриншотНужно добавлять вручнуюДелается автоматически при падении
ВидеоНет встроенной поддержкиЕсть встроенная запись видео
Что произошло до ошибкиНеизвестноТрейс показывает все шаги
Сетевые запросыНе видноВидно в трейсе
Состояние DOMНеизвестноВидно в трейсе (снимки DOM)

Практический пример: как выглядит отладка с трейсом. Представьте, что у вас упал тест. Вы открываете Trace Viewer и видите:

1. Шаг 1: Переход на страницу логина — скриншот страницы.

2. Шаг 2: Ввод логина «admin» — поле подсвечено.

3. Шаг 3: Ввод пароля «12345» — поле подсвечено.

4. Шаг 4: Клик по кнопке «Войти» — кнопка подсвечена.

5. Шаг 5: Ожидание появления сообщения «Добро пожаловать» — здесь ошибка!

Вы видите, что после клика по кнопке страница не загрузилась, а вместо сообщения «Добро пожаловать» появилась ошибка «Сервер недоступен».

Вы сразу понимаете: проблема не в тесте, а в сервере.

Без трейса вы бы гадали: «Может, локатор неправильный? Может, кнопка не нажалась? Может, страница не загрузилась?», что занимает крайне большое количество времени.