Масштабируемая автоматизация с Playwright: от линейных скриптов до продвинутого Page Object Model

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

1. Введение в автоматизацию тестирования и фундаментальная концепция Page Object Model

Введение в автоматизацию тестирования и фундаментальная концепция Page Object Model

Представьте, что вы написали 500 тестов для интернет-магазина, и внезапно разработчики решили изменить ID кнопки «Купить» на всех страницах. Если ваш код написан линейно, вам придется вручную исправлять этот локатор в 500 местах. Это «кошмар поддержки», который убивает автоматизацию на корню. Как сделать так, чтобы изменение интерфейса требовало правки всего в одной строке кода?

Почему автоматизация — это не просто запись действий

Автоматизация тестирования часто воспринимается новичками как «запись и воспроизведение» (Record & Playback). Однако в профессиональной разработке тест — это полноценный программный продукт. Главная проблема автоматизации не в том, чтобы заставить браузер кликнуть по кнопке, а в том, чтобы этот скрипт продолжал работать через месяц, когда дизайн сайта изменится.

Существует прямая зависимость между структурой кода и стоимостью владения автотестами. Чем больше в коде «магических строк» (прямых путей к элементам, зашитых в логику теста), тем выше вероятность поломки всей системы при малейшем обновлении фронтенда.

Концепция Page Object Model (POM)

Для решения проблемы хрупкости тестов в 2009 году был популяризирован паттерн Page Object Model. Его суть проста: мы разделяем логику теста (что мы проверяем) и структуру страницы (как мы находим элементы и взаимодействуем с ними).

> Page Object Model — это шаблон проектирования, в котором каждая веб-страница или значимая часть интерфейса представляется в виде отдельного класса. Локаторы элементов и методы взаимодействия с ними инкапсулируются внутри этого класса.

Представьте архитектуру теста как слоеный пирог:

| Уровень | Ответственность | Пример | | :--- | :--- | :--- | | Тестовый сценарий | Описывает бизнес-логику и проверки (assertions). | «Зайти в корзину и проверить, что товар там». | | Page Object | Содержит описание элементов и действия. | «Метод clickBuyButton() и локатор #buy-btn». | | Драйвер (Playwright) | Выполняет низкоуровневые команды в браузере. | page.click(), page.goto(). |

Как POM меняет подход к коду

В линейном подходе (без POM) ваш тест выглядит как набор инструкций, понятных только машине. В объектно-ориентированном подходе тест читается как описание действий пользователя.

Рассмотрим разницу в поддержке:

  • Линейный скрипт: Тест напрямую обращается к DOM-дереву. Если кнопка переместилась или сменила селектор, тест падает. Вам нужно искать этот селектор по всем файлам проекта.
  • Использование POM: Тест вызывает метод LoginPage.login(user). Если селектор поля ввода изменился, вы правите его только в классе LoginPage. Сам тест при этом остается неизменным.
  • Принципы чистого Page Object

    Чтобы POM действительно работал на масштабируемость, важно соблюдать три правила:

  • Инкапсуляция локаторов: Тест не должен знать, какой селектор используется для кнопки. Он должен лишь вызывать метод «нажать».
  • Методы-действия: Вместо того чтобы предоставлять тесту прямой доступ к элементам, создавайте осмысленные методы, например, searchForProduct(name) вместо fill('#search', name) и press('Enter').
  • Отсутствие проверок (Assertions) в Page Object: Задача страницы — предоставить данные или совершить действие. Проверка результата (ожидаемый результат) — это зона ответственности самого теста.
  • В контексте Playwright этот паттерн раскрывается особенно эффективно благодаря встроенной поддержке ожидания элементов и мощному API, который мы начнем превращать в гибкие классы уже в ближайших главах.

    10. Финальное проектирование масштабируемой и поддерживаемой архитектуры тестового фреймворка

    Финальное проектирование масштабируемой и поддерживаемой архитектуры тестового фреймворка

    Как превратить набор разрозненных файлов в систему, которая не «сломается» под весом 500+ тестов? Мы прошли путь от линейного скрипта до продвинутых Page Objects с использованием Fluent API и фикстур. Теперь пришло время собрать эти элементы в единую архитектурную экосистему, готовую к промышленной эксплуатации.

    Архитектурный каркас: от хаоса к иерархии

    Масштабируемость фреймворка зависит от того, насколько четко разделены ответственности между его слоями. Если вы решите сменить библиотеку для работы с API или обновить способ получения данных из базы, это не должно затронуть сами тесты.

    В основе нашей финальной архитектуры лежит принцип «слоеного пирога»:

    | Слой | Ответственность | Содержимое | | :--- | :--- | :--- | | Data Layer | Хранение состояния | JSON-файлы, переменные окружения (.env). | | Page Layer | Описание интерфейса | Классы страниц (POM) и компонентов (Component Objects). | | Infrastructure Layer | Управление средой | Фикстуры, конфигурация Playwright, глобальные хуки. | | Test Layer | Бизнес-логика | Тестовые сценарии (спеки), использующие фикстуры. |

    Сборка воедино: паттерны в действии

    В предыдущих главах мы создали мощные инструменты. Давайте посмотрим, как они взаимодействуют в рамках одного сценария. Главная цель — сделать тест максимально читаемым, скрывая всю «кухню» автоматизации под капотом.

    > Хороший тест должен читаться как спецификация. Если для понимания шага нужно лезть в реализацию метода — архитектура требует доработки.

    Рассмотрим пример финальной реализации теста оформления заказа, где объединяются фикстуры, Fluent API и Loadable Component:

    В этом примере loginPage.login() возвращает экземпляр InventoryPage, который уже прошел проверку isLoaded(). Это гарантирует, что тест не упадет из-за того, что страница еще не отрисовалась.

    Масштабирование через абстракцию

    Когда проект растет, дублирование кода начинает проявляться не в локаторах, а в логике. Например, удаление сущности, создание пользователя через API для подготовки данных или работа с модальными окнами.

  • Utility Classes: Выносите вспомогательные функции (генерация случайных строк, работа с датами) в отдельную директорию utils/.
  • API Helpers: Интегрируйте Playwright APIRequestContext в ваши фикстуры. Это позволит подготавливать состояние системы (например, создавать корзину с товарами) через быстрые API-запросы, минуя UI там, где это не критично.
  • Global Setup: Используйте globalSetup в playwright.config.js для авторизации один раз перед всеми тестами. Это экономит до - времени выполнения регрессионного набора.
  • Чек-лист зрелости фреймворка

    Прежде чем считать работу завершенной, проверьте ваш проект на соответствие критериям профессиональной автоматизации:

  • Изоляция: Тесты не зависят друг от друга. Падение одного не вызывает «эффект домино».
  • Отсутствие Hardcode: Все URL, таймауты и учетные данные вынесены в конфиги или JSON.
  • Информативность: В случае падения Trace Viewer и скриншоты дают полный ответ на вопрос «почему?», не требуя локального перезапуска.
  • Стабильность: Использование Loadable Component и встроенных ожиданий Playwright сводит количество «флакающих» тестов к минимуму.
  • Мы прошли путь от записи действий в браузере до создания гибкой, объектно-ориентированной системы. Помните: архитектура — это не застывшая структура, а живой организм, который должен расти вместе с вашим приложением.

    2. Развертывание окружения Playwright и написание первого линейного теста на JavaScript

    Развертывание окружения Playwright и написание первого линейного теста на JavaScript

    Знаете ли вы, что установка полноценного фреймворка для автоматизации может занять меньше времени, чем заваривание чашки кофе? В 2024 году порог входа в индустрию тестирования радикально снизился благодаря инструментам вроде Playwright, которые берут на себя всю «грязную работу» по настройке браузеров и драйверов. Сегодня мы пройдем путь от пустой папки до первого работающего скрипта, чтобы увидеть ту самую проблему «хрупкости», которую нам предстоит решать в следующих главах.

    Подготовка фундамента: установка Playwright

    Для работы нам понадобятся Node.js и редактор кода (рекомендую VS Code). Playwright поставляется с мощным CLI-инструментом, который инициализирует проект одной командой.

    В терминале вашей рабочей директории выполните:

    В процессе установки выберите JavaScript в качестве основного языка и согласитесь на установку браузеров. Эта команда создаст структуру проекта, где ключевым файлом является playwright.config.js — «мозг» вашего фреймворка, определяющий таймауты, репортеры и настройки окружения.

    Анатомия линейного теста

    Прежде чем внедрять сложные архитектурные паттерны вроде POM, необходимо понять, как выглядит «сырой» или линейный тест. Линейный тест — это последовательность команд, где логика проверки и детали реализации (селекторы) перемешаны в одном файле.

    Рассмотрим сценарий авторизации на учебном сайте. Создайте файл tests/login.spec.js:

    В этом примере используется объект page — это ваша «вкладка» в браузере. Все действия выполняются асинхронно, поэтому ключевое слово await обязательно перед каждой операцией.

    Почему линейный подход — это «бомба замедленного действия»

    На первый взгляд, тест выглядит понятным. Однако в профессиональной разработке такие скрипты быстро становятся обузой. Проблема кроется в жесткой привязке к DOM-дереву.

    Представьте, что разработчики изменили идентификатор поля ввода с #username на #user-login. Если у вас 50 тестов, использующих это поле, вам придется вручную исправлять каждый из них.

    > Линейный тест нарушает принцип DRY (Don't Repeat Yourself). Селекторы и логика навигации дублируются в каждом файле, что ведет к экспоненциальному росту затрат на поддержку при любом изменении UI.

    Инструменты разработчика: Codegen и Inspector

    Playwright предлагает встроенный генератор кода, который помогает быстро находить локаторы. Попробуйте запустить:

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

    Запуск и отладка

    Чтобы запустить ваш первый тест, используйте команду:

    Если вы хотите увидеть, как именно браузер выполняет команды, добавьте флаг --headed. Для детального разбора упавших тестов Playwright автоматически генерирует Trace Viewer — интерактивный отчет, где можно «отмотать» время назад и посмотреть состояние страницы на каждом шаге выполнения.

    Мы создали работающий скрипт, но он «хрупок» и трудно поддается масштабированию. В следующей главе мы начнем превращать этот процедурный код в надежную объектно-ориентированную структуру, вынося локаторы в отдельные классы.

    3. Проектирование базового класса страницы и инкапсуляция локаторов внутри объектов

    Проектирование базового класса страницы и инкапсуляция локаторов внутри объектов

    Представьте, что в вашем приложении изменился дизайн шапки профиля. Если у вас сто тестов, использующих эту шапку, в линейном подходе вам придется вручную исправить сто файлов. Но что, если бы существовало «единое место правды», где изменение одной строки кода автоматически обновляло бы все тесты? Именно здесь начинается переход от простых скриптов к профессиональной архитектуре.

    Наследование и создание Base Page

    В Playwright каждый Page Object взаимодействует с объектом page. Чтобы не дублировать инициализацию этого объекта в каждом новом классе, мы создаем «родителя» — Base Page. Это фундамент, который содержит общие методы (например, навигацию или проверку URL) и передает экземпляр браузерной страницы своим «наследникам».

    В JavaScript это реализуется через механизм классов и конструктор.

    Здесь this.page становится доступным для любого класса, который расширяет BasePage. Это избавляет нас от избыточности и подготавливает почву для масштабирования.

    Инкапсуляция локаторов в конструкторе

    Главное правило Page Object Model: тест не должен знать о селекторах. Тест оперирует бизнес-логикой («введи логин», «нажми кнопку»), а Page Object берет на себя поиск элементов.

    Лучшее место для объявления локаторов — конструктор класса. В Playwright мы используем метод page.locator(), который создает ленивый указатель на элемент. Он не ищет элемент сразу, а делает это только в момент взаимодействия.

    Сравнение подходов

    | Характеристика | Линейный подход (в тесте) | Инкапсуляция в POM | | :--- | :--- | :--- | | Локация селектора | Внутри await page.click('.btn-save') | В свойстве класса this.saveButton | | Читаемость | Низкая (нужно знать HTML) | Высокая (методы называются по смыслу) | | Поддержка | Сложная (правки во всех тестах) | Легкая (правка в одном классе) | | Повторное использование | Копипаст кода | Вызов метода объекта |

    Практическая реализация страницы авторизации

    Давайте создадим класс для страницы логина, наследовав его от нашей BasePage. Обратите внимание, как локаторы отделены от методов взаимодействия.

    > Инкапсуляция — это не просто перенос строк. Это создание интерфейса, который защищает тест от изменений в DOM-дереве. Если разработчики заменят ID #user-name на класс .login-field, вы измените только одну строку в конструкторе LoginPage. > > Martin Fowler: PageObject

    Почему page.locator() лучше строк

    Использование this.usernameInput = page.locator(...) вместо хранения просто строк-селекторов (например, this.usernameSelector = '#user-name') дает три важных преимущества:

  • Типизация и автодополнение: IDE понимает, что это объект локатора, и предлагает методы .click(), .fill() и т.д.
  • Устойчивость: Локаторы Playwright автоматически ожидают появления элемента (Auto-waiting).
  • Читаемость кода: В методах страницы вы пишете await this.loginButton.click(), что выглядит гораздо чище, чем await this.page.click(this.loginButtonSelector).
  • Такая структура делает ваш код похожим на документацию: глядя на конструктор, любой инженер сразу понимает, из каких элементов состоит страница, не заглядывая в инструменты разработчика в браузере.

    4. Рефакторинг тестовых сценариев: переход от процедурных скриптов к объектно-ориентированным моделям

    Рефакторинг тестовых сценариев: переход от процедурных скриптов к объектно-ориентированным моделям

    Представьте, что в вашем приложении изменился всего один селектор кнопки «Войти». Если у вас 50 тестов, написанных линейно, вам придется вручную исправить 50 строк кода. Это не автоматизация, а долговая яма. Сегодня мы превратим хрупкий процедурный скрипт в гибкую объектно-ориентированную систему, используя созданные ранее классы страниц.

    От «спагетти-кода» к чистому сценарию

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

    Ранее мы подготовили классы BasePage и LoginPage. Теперь посмотрим, как меняется сам файл теста при переходе на POM.

    | Характеристика | Линейный тест (Процедурный) | Тест на базе POM (Объектный) | | :--- | :--- | :--- | | Читаемость | Низкая (видим селекторы #login-btn) | Высокая (видим метод login()) | | Дублирование | Высокое (копируем логику в каждый тест) | Отсутствует (логика в классе страницы) | | Поддержка | Сложная (правки во всех файлах) | Легкая (правка в одном методе класса) |

    Практический процесс рефакторинга

    Возьмем наш линейный тест авторизации из второй главы и перепишем его.

    Шаг 1: Инициализация объектов

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

    Шаг 2: Замена прямых вызовов методами

    Вместо page.fill() и page.click() мы вызываем методы, которые инкапсулировали в главе 3.

    Почему это работает: принцип единственной ответственности

    В обновленном тесте соблюдается четкое разделение:

  • Тестовый файл знает что делать (сценарий: открыть, ввести данные, проверить).
  • Page Object знает как это сделать (какой селектор найти, какую задержку подождать).
  • > Рефакторинг — это не просто «причесывание» кода. Это перенос знаний о структуре DOM из тестов в единое хранилище (классы страниц). > > Refactoring: Improving the Design of Existing Code

    Проблема избыточности и первый шаг к фикстурам

    Вы могли заметить, что в каждом новом тесте нам приходится писать const loginPage = new LoginPage(page). Если тестов в файле десять — это десять одинаковых строк.

    В Playwright эту проблему можно решить двумя способами:

  • Использовать хук test.beforeEach, где будут инициализироваться объекты.
  • Использовать механизм фикстур (fixtures), который мы подробно разберем в главе 7.
  • На текущем этапе мы будем использовать beforeEach, чтобы подготовить почву для более сложных архитектурных решений. Это позволит нам сосредоточиться на логике взаимодействия страниц, не отвлекаясь на повторную инициализацию в каждом тесте.

    5. Применение паттерна Component Object для декомпозиции и переиспользования элементов интерфейса

    Применение паттерна Component Object для декомпозиции и переиспользования элементов интерфейса

    Что общего между навигационной панелью, формой поиска и футером сайта? Они встречаются почти на каждой странице. Если мы будем описывать их локаторы в каждом классе Page Object (например, в HomePage, ProductPage и CartPage), мы создадим сотни строк дублирующего кода. Как только дизайнер изменит иконку корзины в шапке, нам придется вносить правки в десятки файлов. Решение этой проблемы кроется в декомпозиции — разделении страницы на независимые компоненты.

    От монолитных страниц к компонентной архитектуре

    До этого момента мы рассматривали страницу как единое целое. Однако современный веб-интерфейс строится по принципу матрешки: страница состоит из секций, а секции — из мелких виджетов. Паттерн Component Object (иногда называемый Fragment Object) позволяет выделить логику взаимодействия с повторяющимися частями интерфейса в отдельные классы.

    Сравним два подхода к организации кода:

    | Характеристика | Классический POM (Монолит) | Component Object Model | | :--- | :--- | :--- | | Локаторы | Все локаторы страницы в одном классе. | Локаторы распределены по компонентам. | | Дублирование | Высокое (шапка и подвал в каждом классе). | Отсутствует (компонент описывается один раз). | | Масштабируемость | Сложно поддерживать огромные файлы. | Легко добавлять новые страницы из кирпичиков. | | Пример | HomePage.searchField | HomePage.header.searchField |

    Реализация базового компонента

    Чтобы компонент был по-настоящему автономным, он должен знать только о своей области видимости. В Playwright это реализуется через передачу объекта Locator или Page в конструктор.

    > Паттерн Component Object — это логическое расширение POM, где вместо всей страницы объектом моделирования выступает её часть (виджет, меню, форма).

    Создадим базовый класс для компонентов, который обеспечит нам доступ к методам Playwright внутри узкой области видимости:

    Практический пример: Создание HeaderComponent

    Представим интернет-магазин, где в верхней части страницы (Header) есть поле поиска и кнопка профиля. Вместо того чтобы описывать их в каждой странице, создадим отдельный класс.

    Обратите внимание на использование this.container.locator(). Это гарантирует, что Playwright будет искать поле поиска именно внутри тега header, а не по всей странице. Это критически важно, если на странице есть несколько похожих инпутов (например, в хедере и в футере).

    Интеграция компонентов в Page Objects

    Теперь мы можем подключить наш компонент к любой странице как обычное свойство класса. Это и есть композиция — создание сложного объекта из более простых.

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

    Когда стоит выделять компонент?

    Не стоит превращать каждый мелкий элемент в отдельный класс — это приведет к избыточности. Профессорский совет: применяйте декомпозицию, если выполняется хотя бы одно из условий:

  • Повторяемость: Элемент встречается более чем на двух разных страницах (меню, футер, модальные окна).
  • Сложность: Элемент имеет сложную внутреннюю логику (например, календарь-пикер или динамическая таблица с фильтрами).
  • Автономность: Элемент разрабатывается или тестируется как отдельный модуль (например, виджет чата поддержки).
  • Такой подход превращает поддержку тестов в сборку конструктора: вы правите локатор в одном компоненте, и он автоматически обновляется во всём тестовом фреймворке.

    6. Реализация паттерна Loadable Component для обеспечения стабильности и проверки состояний страниц

    Реализация паттерна Loadable Component для обеспечения стабильности и проверки состояний страниц

    Почему тесты падают с ошибкой «элемент не найден», хотя на скриншоте страница выглядит загруженной? В 90% случаев проблема кроется в гонке условий (race conditions): тест начинает кликать по кнопкам до того, как JavaScript-фреймворк отрисовал интерфейс или загрузил данные с сервера. Паттерн Loadable Component превращает «надежду на удачу» в строгую архитектурную проверку готовности страницы к работе.

    Проблема неявного ожидания

    В предыдущих главах мы научились выносить локаторы в классы и создавать методы-действия. Однако простая навигация page.goto() не гарантирует, что страница перешла в валидное состояние.

    > Паттерн Loadable Component — это расширение Page Object Model, которое вводит обязательную проверку состояния страницы перед тем, как тест начнет с ней взаимодействовать.

    Без этого паттерна тесты становятся «флакающими» (нестабильными): они проходят локально, но падают в CI/CD из-за задержек сети. Мы должны научить наши объекты страниц самостоятельно отвечать на вопрос: «Ты готова к работе?».

    Интеграция в базовый класс

    Чтобы не дублировать логику ожидания в каждом классе, мы внедрим механизм проверки в наш BasePage. Идея проста: каждый наследник должен реализовать метод, описывающий его «состояние готовности».

    Реализация проверок в Page Objects

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

    Сравнение подходов к ожиданию

    Важно понимать разницу между стандартными ожиданиями Playwright и паттерном Loadable Component.

    | Характеристика | Стандартный Playwright (Auto-waiting) | Loadable Component | | :--- | :--- | :--- | | Объект ожидания | Конкретный элемент перед действием (click, fill). | Состояние всей страницы или крупного компонента. | | Точка вызова | Внутри каждого метода действия. | При инициализации или сразу после перехода на страницу. | | Надежность | Высокая для простых элементов. | Максимальная для сложных SPA-приложений. | | Диагностика | Ошибка «Timeout» на конкретной строке клика. | Четкая ошибка «Страница X не загрузилась». |

    Применение в тестах

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

    Если сервер задержит ответ, expect(this.title).toBeVisible() внутри метода isLoaded будет ждать до истечения таймаута, обеспечивая стабильность теста без использования «грязных» пауз вроде page.waitForTimeout(5000).

    Loadable Component для компонентов

    Этот же принцип применим и к Component Object. Если у вас есть сложный компонент (например, корзина, которая подгружается асинхронно), добавьте ему метод isLoaded(). Это позволит основной странице дождаться готовности всех своих составных частей, прежде чем передать управление тесту.

    В следующей главе мы автоматизируем процесс создания этих объектов, чтобы не писать new PageObject(page) вручную в каждом тесте, используя мощный механизм фикстур Playwright.

    7. Глубокая интеграция Page Objects с механизмом фикстур Playwright для изоляции тестов

    Глубокая интеграция Page Objects с механизмом фикстур Playwright для изоляции тестов

    Задумывались ли вы, почему в крупных проектах файлы тестов выглядят как чистые спецификации, где нет ни одной строчки с new LoginPage(page)? Пока новички загромождают каждый beforeEach ручной инициализацией десятка страниц, профессионалы используют механизм фикстур, чтобы превратить инфраструктурный код в "невидимый" сервис. Сегодня мы избавимся от бойлерплейта и научим наш фреймворк автоматически поставлять нужные Page Objects прямо в аргументы теста.

    Проблема явной инициализации

    До этого момента мы создавали экземпляры классов внутри каждого теста или в блоке beforeEach. На первый взгляд, это логично, но при масштабировании до 100+ тестов возникают две проблемы:

  • Дублирование кода (DRY): В каждом файле приходится импортировать классы страниц и инициализировать их.
  • Сложность поддержки: Если конструктор базового класса изменится, вам придется переписывать инициализацию во всех тестах проекта.
  • В Playwright есть элегантное решение — расширение стандартных фикстур. Это позволяет внедрять зависимости (Dependency Injection) на уровне ядра тестового движка.

    Создание кастомной фикстуры

    Чтобы интегрировать Page Objects, нам нужно создать файл (обычно fixtures.js или baseTest.js), который расширит стандартную функциональность test из @playwright/test.

    Как это работает?

  • Аргумент { page }: Фикстура сама запрашивает стандартный объект страницы Playwright.
  • Функция use(): Это критически важный момент. Код до use выполняется до начала теста (Setup), а код после useпосле завершения теста (Teardown).
  • Изоляция: Для каждого теста Playwright создаст новый экземпляр страницы и, соответственно, новые объекты Page Objects.
  • Сравнение подходов

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

    | Характеристика | Ручная инициализация (new Page) | Использование Fixtures | | :--- | :--- | :--- | | Импорты в тесте | Все классы страниц | Только один файл с фикстурами | | Инициализация | Внутри beforeEach или test | Автоматически через аргументы | | Читаемость | Засорена техническими деталями | Фокус на бизнес-логике | | Масштабируемость | Низкая (много правок при рефакторинге) | Высокая (правки в одном месте) |

    Использование фикстур в тестах

    Теперь наш тест избавляется от лишнего шума. Нам больше не нужно импортировать LoginPage или InventoryPage напрямую. Мы импортируем наш расширенный test.

    Обратите внимание: Playwright анализирует аргументы функции теста. Если вы указали loginPage, он запустит соответствующую фикстуру. Если не указали — лишние объекты создаваться не будут. Это делает выполнение тестов максимально эффективным.

    Интеграция с Loadable Component

    Фикстуры идеально сочетаются с паттерном Loadable Component, который мы изучили ранее. Мы можем вынести проверку состояния страницы прямо в фикстуру, чтобы тест начинался только тогда, когда страница гарантированно готова.

    > Инъекция зависимостей через фикстуры позволяет отделить "что мы тестируем" от "как мы готовим среду". Это фундамент для построения фреймворков, которые могут поддерживать сотни сценариев без деградации скорости разработки.

    В следующей главе мы разберем, как организовать структуру папок и управлять конфигурацией, чтобы наш проект превратился в полноценный промышленный фреймворк.

    8. Оптимизация структуры проекта и эффективное управление тестовыми данными в POM

    Оптимизация структуры проекта и эффективное управление тестовыми данными в POM

    Представьте, что ваш проект вырос с 10 до 500 тестов. Если логин пользователя «test_user» жестко прописан (захардкожен) в каждом сценарии, то простая смена пароля в системе превращается в многочасовой поиск и замену строк по всему репозиторию. Как организовать файлы и данные так, чтобы фреймворк не превратился в «спагетти-код» при масштабировании?

    Архитектура директорий: от хаоса к системе

    Когда мы используем Page Object Model и фикстуры, количество файлов быстро растет. Правильная структура проекта — это первый шаг к тому, чтобы новый разработчик в команде мог найти нужный класс страницы за 5 секунд.

    Типовая структура масштабируемого проекта на Playwright выглядит так:

    Такое разделение позволяет четко разграничить ответственность: тесты знают «что» проверять, страницы знают «как» взаимодействовать, а папка data хранит «с чем» мы работаем.

    Отказ от «хардкода»: внешние тестовые данные

    Хардкод — это главная причина хрупкости тестов. Мы уже вынесли локаторы в классы страниц, теперь пора вынести значения (логины, адреса, названия товаров) во внешние файлы.

    Для этого идеально подходит формат JSON. Создадим файл data/users.json:

    Теперь в тесте или в Page Object мы просто импортируем этот файл. Это позволяет менять окружение или учетные данные, не прикасаясь к логике кода.

    Интеграция данных в Page Object Model

    Как лучше передавать данные в методы страниц? Существует два основных подхода.

    | Подход | Описание | Когда использовать | | :--- | :--- | :--- | | Параметризация метода | Данные передаются как аргументы: login(user, pass) | Если данные меняются от теста к тесту (разные пользователи). | | Использование констант | Данные импортируются внутри Page Object | Если данные статичны для конкретной страницы (например, текст заголовка). |

    Пример использования внешних данных в тесте с использованием фикстур:

    Управление конфигурацией через playwright.config.js

    Оптимизация структуры — это не только папки, но и глобальные настройки. Playwright позволяет вынести общие параметры (URL стенда, таймауты, браузеры) в центральный конфиг.

    > Хорошей практикой считается использование переменной окружения baseURL, чтобы тесты могли запускаться на разных стендах (dev, staging, prod) без изменения кода. > > Документация Playwright: Configuration

    Пример базовой настройки в playwright.config.js:

    Очистка тестов: DRY в действии

    Принцип DRY (Don't Repeat Yourself) в масштабируемом фреймворке реализуется через:

  • Базовые классы: Общие методы (например, ожидание исчезновения лоадера) живут в BasePage.
  • Фикстуры: Инициализация страниц происходит «под капотом», тесты остаются чистыми.
  • Утилиты: Если вам нужно генерировать случайные email-адреса, создайте папку utils/ и вынесите логику туда.
  • Связность всех этих элементов создает надежный фундамент. Теперь, когда структура проекта оптимизирована, а данные отделены от кода, мы готовы к реализации более сложных сценариев взаимодействия, которые мы разберем в следующей главе.

    9. Обработка сложных пользовательских сценариев и реализация цепочек вызовов в методах страниц

    Обработка сложных пользовательских сценариев и реализация цепочек вызовов в методах страниц

    Представьте, что вы пишете тест для интернет-магазина: нужно залогиниться, найти товар, добавить его в корзину и оформить заказ. В обычном стиле ваш тест превращается в список из 10-15 строк кода, где вы постоянно вызываете разные объекты страниц. А что, если тест мог бы читаться как естественное предложение: «Страница логина, войди как админ, затем на главной найди 'iPhone', добавь в корзину и перейди к оплате»? Сегодня мы научим наши Page Objects «общаться» друг с другом, используя паттерн Fluent API.

    Проблема прерывистых сценариев

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

    Рассмотрим стандартный подход:

  • Вызвать метод на loginPage.
  • Вручную обратиться к inventoryPage.
  • Вызвать метод на inventoryPage.
  • Это создает лишний «шум» в коде теста. В сложных сценариях, где пользователь проходит через 5-7 экранов, тест становится перегруженным переменными и повторяющимися вызовами. Наша задача — сделать так, чтобы методы страниц сами возвращали объект следующей страницы, на которую попадает пользователь.

    Реализация цепочек вызовов (Fluent API)

    Паттерн Fluent API (текучий интерфейс) позволяет объединять вызовы методов в одну цепочку. Ключевой механизм здесь — возврат объекта из метода.

    > Fluent API — это способ проектирования объектно-ориентированного API, при котором каждый метод возвращает контекст (обычно this или объект другого класса), позволяя вызывать методы один за другим без промежуточных переменных.

    В контексте Page Object Model у нас есть два варианта возврата:

  • Тот же объект (return this): если действие не уводит нас со страницы (например, заполнение полей формы).
  • Новый объект (return new NextPage(page)): если действие инициирует переход на другую страницу.
  • Пример реализации в LoginPage

    Сравнение подходов

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

    | Характеристика | Обычный POM | Fluent POM | | :--- | :--- | :--- | | Читаемость | Средняя (много переменных) | Высокая (похоже на DSL) | | Навигация | Управляется в тесте | Описана внутри Page Object | | Количество кода в тесте | Больше строк | Компактные цепочки |

    Обработка сложных сценариев: Композиция и Fluent API

    Сложные сценарии часто включают взаимодействие с компонентами. Благодаря тому, что наши компоненты уже инкапсулированы (как мы учили в главе 5), мы можем встраивать их в цепочки вызовов.

    Представим сценарий: «Добавить товар в корзину и проверить счетчик в хедере».

    Тонкости асинхронности в цепочках

    Поскольку почти все действия в Playwright асинхронны, каждый шаг в цепочке должен предваряться await. В JavaScript вы не можете написать await page.method1().method2(), если method1 возвращает Promise. Вам придется либо разрешать каждый промис в цепочке, либо (что чаще практикуется в JS-автоматизации) использовать цепочки для логически связанных блоков.

    Важное правило: Если метод возвращает новую страницу, он должен дождаться её загрузки (метод isLoaded), прежде чем вернуть объект. Это гарантирует, что следующий метод в цепочке не упадет из-за того, что элементы еще не отрисовались.

    Когда не стоит использовать цепочки?

    Несмотря на красоту, у Fluent API есть ограничения:

  • Сложное ветвление: Если кнопка «Login» может вести либо на «Dashboard», либо на «ChangePasswordPage» (в зависимости от данных), возвращать жестко заданный объект в методе login() становится трудно.
  • Отладка: Если цепочка из 10 методов падает в середине, Trace Viewer покажет ошибку, но визуально в коде сложнее сразу понять, какой именно . вызвал проблему, если всё записано в одну строку.
  • Старайтесь объединять в цепочки только те действия, которые логически неразрывны в рамках одного бизнес-сценария.