Урок по треку Автоматизация тестирования 7 мин

Page Object и архитектура фреймворка за 7 минут

Page Object, компоненты, слои фреймворка, билдеры тестовых данных.

Когда UI-тестов десять, можно писать локаторы прямо в тестах. Когда их пятьсот и меняется кнопка входа, приходится править пятьсот файлов. Page Object решает именно эту проблему.

Идея

Каждая страница (или крупный компонент) представлена классом. Класс знает локаторы и умеет действия на языке предметной области. Тест знает сценарий и проверки.

class LoginPage:
    def __init__(self, page):
        self.page = page
        self.email = page.get_by_label("Email")
        self.password = page.get_by_label("Пароль")
        self.submit = page.get_by_role("button", name="Войти")

    def open(self):
        self.page.goto("/login")
        return self

    def login(self, email, password):
        self.email.fill(email)
        self.password.fill(password)
        self.submit.click()
        return DashboardPage(self.page)


def test_login(page, user):
    dashboard = LoginPage(page).open().login(user.email, user.password)
    expect(dashboard.greeting).to_have_text(f"Привет, {user.name}")

Локатор кнопки поменялся, правка в одном месте. Тест читается как сценарий.

Правила хорошего Page Object

  • Методы описывают действия пользователя (login, add_to_cart), а не технику (click_button_3).
  • Метод, который ведёт на другую страницу, возвращает её объект: получается fluent-цепочка.
  • Проверки чаще держат в тестах. Page Object отдаёт состояние (greeting, items_count()), а тест решает, что ожидать. Так один объект служит и позитивным, и негативным сценариям.
  • Никаких sleep и магических индексов внутри: ожидания и локаторы такие же аккуратные, как в тестах.

Компоненты

Шапка, корзина-виджет, таблица, модалка встречаются на многих страницах. Их выносят в компоненты (Page Component), а страница их собирает:

class Header {
  constructor(private root: Locator) {}
  cartBadge = () => this.root.getByTestId('cart-count');
}
class ProductPage {
  readonly header: Header;
  constructor(private page: Page) {
    this.header = new Header(page.getByRole('banner'));
  }
}

Компонент ищет элементы внутри своего корня, поэтому две таблицы на странице не путаются.

Page Factory и Screenplay

  • Page Factory (Java): аннотации @FindBy и PageFactory.initElements создают ленивые прокси элементов. Удобно, но в современных проектах часто обходятся без неё.
  • Screenplay: тест описан через актёров, их возможности и задачи (actor.attemptsTo(Login.with(user))). Масштабируется на большие наборы, но порог входа выше.

Слои фреймворка

tests/            сценарии и проверки
pages/, components/  Page Object
api/              клиенты для подготовки данных
data/             билдеры и фабрики тестовых данных
fixtures/         conftest или test.extend
config/           окружения, URL, учётки из переменных

Тестовые данные создаются билдерами с разумными значениями по умолчанию: тест переопределяет только то, что важно для сценария (build_user(role="admin")). Для случайных реалистичных данных берут Faker, но с фиксированным seed, если важна воспроизводимость.

Частые ошибки

  • «Божественный» Page Object на 2000 строк для всего приложения.
  • Проверки внутри Page Object, из-за которых метод нельзя использовать в негативном тесте.
  • Наследование страниц ради переиспользования вместо композиции компонентов.
  • Хранить найденные элементы, а не локаторы (протухшие ссылки).

Что спросят на собесе

  • Что такое Page Object и какую проблему он решает?
  • Где держать проверки: в тестах или в Page Object?
  • Как организовать повторяющиеся элементы, например шапку и таблицы?
  • Из каких слоёв состоит ваш фреймворк?
  • Как вы готовите тестовые данные?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.