Настоящие пэдж-объекты

Недавно я писал о том, что на самом деле нельзя хардкодить.

Давайте рассмотрим этот принцип на примере пэдж объектов.

Классический пэдж обжект

У тестировщиков-автоматизаторов очень популярен паттерн “Пэдж Обжект”. Собственно, это самый популярный в мире объект поклонения после статуи Будды в Лэшане и Шестого Айфона.

Суть его в том, что все селекторы, по которым ищутся элементы на веб-страничке тестируемого приложения, выносятся… точно, в константы:

public class RegistrationPage {
  @FindBy(name = "firstName")
  public WebElement firstName;

  @FindBy(name = "lastName")
  public WebElement lastName;

  @FindBy(name = "birthday")
  public WebElement birthday;
}

Красиво, да? Такие пэдж объекты хороши тем, что один и тот же локатор не приходится дублировать в сотне разных тестов. Когда локатор меняется, его нужно поменять всего в одном месте.

А тесты не нужно часто менять, потому что они используют пэдж объект:

  @Test
  public void userCanRegister() {
    RegistrationPage page = new RegistrationPage(webdriver);
    firstName.sendKeys("Bruce");
    lastName.sendKeys("Willis");
    birthday.sendKeys("19.03.1955");
  }

Такой подход стал особенно популярен с лёгкой руки Мартина Фаулера, который выдумал Золотое Правило Автоматизаторов:

В тесте не должно быть ни одного локатора. Локаторы должны быть спрятаны в пэдж объектах. Если в тесте виден локатор - это негодный тест.

В чём же подвох?

Мы забываем, что меняются не только локаторы. Как я рассказывал в изначальной статье, меняется логика работы с элементами. Допустим, есть у вас сотня тестов, в которых есть такая строчка:

birthday.sendKeys("19.03.1955");

Да, типа хорошо, если меняется локатор - сотня тестов останется нетронутой.

Но что если этот элемент из <input> вдруг превратиться в контрол типа “календарь”? Знаете, в котором нужно сначала кликнуть иконку календаря, потом выбрать год, потом месяц, и только потом день.

Упс!

Теперь вам придётся перелопатить всю сотню тестов. Да ещё и разобраться хорошенько, в каких нужно выбирать год, а в каких не нужно. В каких нужно менять месяц, а в каких не нужно. И как эти тесты будут работать в последний день месяца или года.

Упс! Всё пропало, шеф!

Что же делать?

Вот тут и вступает в силу правило: выносить нужно не константы, а логику. В пэдж объекте должно быть не поле birthday, а метод

public class RegistrationPage {
  public void enterBirthday(String birthday) {
  
    $(By.name("birthday")).sendKeys(birthday);
  
    // а потом - вся логика календаря здесь
  }
}

Вот теперь действительно, если поменяется логика работы с элементом “birthday” (в том число локатор!), ни один тест не поменяется - только пэдж объект.

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

Ну а в случае Selenide поля так и вовсе становятся ненужными, ведь гораздо легче использовать доллар (как в примере выше).

Вот это труёвый пэдж объект!

Контрольный в мозг

Давайте проверим, что мы понимаем друг друга правильно. Если вы согласны с приведёнными выше доводами, то вы тоже считаете, что аннотации @FindBy не нужны, PageFactory не нужна. И инифиализировать пэдж-обжект нужно только с помощью конструктора:

MyPage page = new MyPage();

Никаких фабрик, аннотаций и другой чёртовой магии. Она точно добавляет сложности и точно не решает никаких проблем.

А если уж по чесноку, я думаю, что пэдж обжекты вообще не нужны. Но об этом отдельная статья.

Автоматизируйте с умом, други, и не забывайте главное правило из книжки “Design patterns”:

Объект - это не данные и операции с ними (как учат в школе).
Объект - это поведение.

Андрей Солнцев

asolntsev.github.io