← Назад к вопросам

Как описываешь структуру классов для UI тестов?

1.8 Middle🔥 151 комментариев
#Selenium и UI автоматизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Структура классов для UI-тестов: паттерн Page Object и его эволюция

Организация классов в UI-автоматизации критически важна для поддерживаемости, читаемости и повторного использования кода. За 10+ лет практики я прошел эволюцию от линейных скриптов до современных архитектурных паттернов. Основой остается Page Object Model (POM), но сегодня мы говорим о его расширенной версии.

Базовый паттерн Page Object Model (POM)

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

// Базовый пример Page Object для страницы логина
public class LoginPage {
    private WebDriver driver;
    
    // Локаторы элементов
    private By usernameField = By.id("username");
    private By passwordField = By.cssSelector(".password-input");
    private By loginButton = By.xpath("//button[text()='Войти']");
    
    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }
    
    // Методы взаимодействия
    public void enterUsername(String username) {
        driver.findElement(usernameField).sendKeys(username);
    }
    
    public void enterPassword(String password) {
        driver.findElement(passwordField).sendKeys(password);
    }
    
    public HomePage clickLogin() {
        driver.findElement(loginButton).click();
        return new HomePage(driver);
    }
    
    // Композитный метод для частого сценария
    public HomePage loginAs(String username, String password) {
        enterUsername(username);
        enterPassword(password);
        return clickLogin();
    }
}

Расширенная архитектура: многоуровневая структура

Современный подход предполагает разделение на несколько слоев:

1. Слой страниц (Page Objects)

  • Базовый класс страницы - содержит общие методы (ожидания, скроллинг, логирование)
  • Конкретные классы страниц - наследники базового класса
// Базовый класс страницы
public abstract class BasePage {
    protected WebDriver driver;
    protected WebDriverWait wait;
    
    public BasePage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }
    
    protected void click(By locator) {
        wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
    }
    
    protected void type(By locator, String text) {
        WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
        element.clear();
        element.sendKeys(text);
    }
}

// Конкретная страница
public class ProductPage extends BasePage {
    private By addToCartButton = By.id("add-to-cart");
    private By productTitle = By.cssSelector(".product-name");
    
    public ProductPage(WebDriver driver) {
        super(driver);
    }
    
    public void addProductToCart() {
        click(addToCartButton);
    }
    
    public String getProductTitle() {
        return driver.findElement(productTitle).getText();
    }
}

2. Слой компонентов (Component Objects)

Для повторяющихся элементов (хедер, футер, модальные окна):

// Компонент - хедер сайта
public class HeaderComponent extends BasePage {
    private By cartIcon = By.className("cart-icon");
    private By searchInput = By.name("search");
    
    public HeaderComponent(WebDriver driver) {
        super(driver);
    }
    
    public CartPage openCart() {
        click(cartIcon);
        return new CartPage(driver);
    }
    
    public SearchResultsPage searchFor(String query) {
        type(searchInput, query);
        driver.findElement(searchInput).sendKeys(Keys.ENTER);
        return new SearchResultsPage(driver);
    }
}

3. Слой фасадных сервисов (Business Layer)

Для объединения действий в бизнес-сценарии:

// Сервис для тестов корзины покупок
public class ShoppingCartService {
    private ProductPage productPage;
    private HeaderComponent header;
    private CartPage cartPage;
    
    public ShoppingCartService(WebDriver driver) {
        this.productPage = new ProductPage(driver);
        this.header = new HeaderComponent(driver);
        this.cartPage = new CartPage(driver);
    }
    
    public void addProductToCartAndVerify(String productName, double expectedPrice) {
        // Поиск товара
        SearchResultsPage results = header.searchFor(productName);
        results.selectFirstProduct();
        
        // Добавление в корзину
        productPage.addProductToCart();
        
        // Переход и проверка
        CartPage cart = header.openCart();
        assert cart.getTotalPrice() == expectedPrice;
    }
}

4. Слой утилит и хелперов

  • DriverManager - управление экземплярами WebDriver
  • ConfigReader - чтение конфигурационных файлов
  • ScreenshotUtil - создание скриншотов при падении
  • Logger - кастомное логирование

Ключевые принципы организации

  1. Принцип единственной ответственности - каждый класс решает одну задачу
  2. Инкапсуляция локаторов - селекторы не должны "протекать" в тестовые методы
  3. Возвращение следующих страниц - методы возвращают объекты следующей страницы
  4. Именование методов как действий - loginAsUser(), а не fillForm()
  5. Отделение тестовой логики от инфраструктуры - тесты не должны управлять драйвером напрямую

Современные улучшения паттерна

Page Factory с аннотациями (в Selenium):

public class LoginPage {
    @FindBy(id = "username")
    @CacheLookup
    private WebElement usernameField;
    
    @FindBy(how = How.CSS, using = ".password")
    private WebElement passwordField;
    
    public LoginPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }
}

Page Elements паттерн для лучшей читаемости локаторов:

public class LoginPage extends BasePage {
    public InputElement username = new InputElement(By.id("username"));
    public InputElement password = new InputElement(By.cssSelector(".password"));
    public ButtonElement loginButton = new ButtonElement(By.xpath("//button[@type='submit']"));
}

Преимущества такой структуры

  • Упрощение поддержки: изменения в UI требуют правки в одном месте
  • Улучшение читаемости тестов: тестовые сценарии выглядят как бизнес-операции
  • Повторное использование кода: компоненты используются в разных тестах
  • Надежность: автоматические ожидания и retry-логика встроены в базовые методы
  • Параллельный запуск: изоляция состояний между тестами

Практические рекомендации

  1. Начинайте с простого POM, затем расширяйте по мере роста проекта
  2. Используйте DI-контейнеры (Spring, Guice) в больших проектах для управления зависимостями
  3. Создавайте BaseTest класс с инициализацией драйвера и cleanup-методами
  4. Применяйте Fluent Interface для цепочечных вызовов: loginPage.withUsername("test").withPassword("pass").submit()
  5. Реализуйте LoadableComponent паттерн для явной проверки загрузки страницы

Эта структура проверена временем на крупных проектах с тысячами тестов. Она масштабируется от простых приложений до сложных enterprise-систем, обеспечивая стабильность автотестов при частых изменениях UI.

Как описываешь структуру классов для UI тестов? | PrepBro