Как бы разбил стартовую страницу Яндекс на паттерн Page Object?

«Как бы разбил стартовую страницу Яндекс на паттерн Page Object?» — вопрос из категории Selenium и Selenide, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Я бы разбил страницу на главный класс YandexMainPage и, при необходимости, вложенные классы для компонентов. Вот реализация с использованием Selenide (более лаконичный аналог Selenium для Java), который я часто применяю в проектах.

1. Базовый класс страницы:

import com.codeborne.selenide.SelenideElement;
import static com.codeborne.selenide.Selenide.*;
import static com.codeborne.selenide.Condition.*;

public class YandexMainPage {

    // Локаторы элементов страницы
    private SelenideElement searchInput = $("input[name='text']");
    private SelenideElement searchButton = $("button[type='submit']");
    private SelenideElement imagesLink = $x("//a[contains(text(),'Картинки')]");
    private SelenideElement suggestPopup = $("div[role='listbox']");

    // Компонент: блок сервисов (можно вынести в отдельный класс)
    private ServicesBlock servicesBlock = new ServicesBlock();

    // Методы для взаимодействия с элементами
    public YandexMainPage open() {
        open("https://ya.ru");
        return this;
    }

    public YandexSearchResultsPage searchFor(String query) {
        searchInput.setValue(query);
        searchButton.click();
        // Возвращаем объект следующей страницы (Page Object)
        return page(YandexSearchResultsPage.class);
    }

    public YandexImagesPage goToImages() {
        imagesLink.click();
        return page(YandexImagesPage.class);
    }

    public void verifySuggestIsVisible() {
        suggestPopup.shouldBe(visible);
    }

    // Внутренний класс для компонента "Сервисы"
    public class ServicesBlock {
        private SelenideElement marketLink = $("a[data-id='market']");
        private SelenideElement mapsLink = $("a[data-id='maps']");

        public void goToMarket() {
            marketLink.click();
            switchTo().window(1); // Переключение на новую вкладку
        }

        public void goToMaps() {
            mapsLink.click();
        }
    }

    // Геттер для доступа к компоненту из тестов
    public ServicesBlock services() {
        return servicesBlock;
    }
}

2. Пример теста, использующего этот Page Object:

import org.junit.jupiter.api.Test;
import static com.codeborne.selenide.Condition.*;

public class YandexSearchTest {

    @Test
    public void userCanSearchFromMainPage() {
        YandexMainPage mainPage = new YandexMainPage().open();

        // Используем метод поиска, который возвращает следующую страницу
        YandexSearchResultsPage resultsPage = mainPage.searchFor("Selenium testing");

        // Проверяем результаты на новой странице
        resultsPage.getFirstResult().shouldHave(text("Selenium"));
    }

    @Test
    public void userCanNavigateToServices() {
        YandexMainPage mainPage = new YandexMainPage().open();

        // Взаимодействуем с компонентом через геттер
        mainPage.services().goToMarket();
        // ... дальнейшие проверки на странице Маркета
    }
}

Ключевые принципы, которые я соблюдаю:

  • Инкапсуляция: Все локаторы и низкоуровневые методы взаимодействия (click, setValue) скрыты внутри класса страницы.
  • Возвращение других Page Object: Методы, ведущие на другую страницу (например, searchFor), возвращают объект этой новой страницы.
  • Композиция: Сложные повторяющиеся блоки (хедер, футер, виджеты) выносятся в отдельные классы-компоненты.
  • Удобство для тестов: API Page Object должен быть читаемым и отражать действия пользователя (mainPage.searchFor("query")).