Пример 1
Начинать всегда проще с простых примеров.
Давайте для начала рассмотрим один из простейших сценариев, такой как заполнение примитивной формы, с разными типами полей ввода. Код страницы лежит в проекте с примерами. Форма выглядит так:
Будем использовать JUnit, т.к. он максимально прост, а сложный функционал здесь не требуется.
1. Нужно создать Page Object и описать имеющиеся на странице элементы.
@PageObject
@Getter
@Accessors(fluent = true)
public class FormPage extends Page {
protected TopBar topBar = new TopBar();
protected SelenideElement fieldLogin = $("#login").as("Textfield Login");
protected SelenideElement fieldEmail = $("#email").as("Textfield Email");
protected SelenideElement fieldPassword = $("#password").as("Textfield Password");
protected SelenideElement fieldRank = $("#rank").as("Textfield rank");
protected SelenideElement fieldDate = $x("//input[@id='date']").as("Textfield Date");
protected SelenideElement fieldTelephone = $("#tel").as("Textfield Telephone");
protected SelenideElement uploadAvatar = $("#file").as("File input Avatar");
protected SelenideElement radioBtnMale = $("#male").as("Radiobutton Gender Male");
protected SelenideElement radioBtnFemale = $("#female").as("Radiobutton Gender Female");
protected SelenideElement ckbMorning = $("#checkbox1").as("Checkbox Preferable time - Morning");
protected SelenideElement ckbEvening = $("#checkbox2").as("Checkbox Preferable time - Evening");
protected SelenideElement ddExperience = $("#experience").as("Dropdown list Experience");
protected SelenideElement selectWorkingDays = $("#multiple-select").as("Select Working Days");
protected SelenideElement btnReset = $(".btn-secondary").as("Button Reset");
protected SelenideElement btnSubmit = $(".btn-primary").as("Button Submit");
}
2. Написать сам тестовый сценарий в соответствующем классе.
@Epic("Selenide style")
public class ITExampleSelenideTest extends TestBaseSelenide {
@Test
@Feature("Form")
@DisplayName("Filling the example form")
protected void fillTheForm() {
URL imageUrl = this.getClass().getClassLoader().getResource("img/testicon.png");
Selenide.open(formPageUrl);
formPage.fieldLogin().shouldBe(Condition.visible);
formPage.fieldLogin().sendKeys("John");
formPage.fieldLogin().shouldHave(Condition.cssClass("form-control"));
formPage.fieldLogin().shouldHave(Condition.value("John"));
formPage.fieldEmail().sendKeys("john.doe@gmail.com");
formPage.fieldEmail().shouldHave(Condition.value("john.doe@gmail.com"));
formPage.fieldPassword().sendKeys("Password12345!");
formPage.fieldRank().sendKeys("10");
formPage.fieldDate().sendKeys("11.11.2011");
formPage.fieldTelephone().sendKeys("199887688");
formPage.uploadAvatar().uploadFile(new File(imageUrl.getFile()));
formPage.radioBtnMale().click();
formPage.radioBtnMale().shouldBe(Condition.enabled);
formPage.radioBtnFemale().shouldBe(Condition.disabled);
formPage.ckbMorning().click();
formPage.ckbMorning().shouldBe(Condition.checked);
formPage.ckbEvening().shouldNotBe(Condition.checked);
formPage.ddExperience().selectOption("2 years");
formPage.selectWorkingDays().selectOption("Monday", "Friday");
formPage.btnSubmit().click();
}
}
Скрипт прост и понятен. Мы обозначили имена полей и заполнили их. Казалось бы, нечего улучшать. Давайте теперь сгенерируем отчёт Allure и посмотрим:
Из этого отчёта можно понять, что происходит, но даже в таком экстримально простом кейсе для того, кто его не писал, многое режет глаз. А уж для не кодеров и тех, кто привык к BDD, тем более.
К тому же, что делать, если, как в одной из компаний, отчёт хотят видеть на другом языке? С этим есть проблема.
Испытав все подходы, я больше всего не люблю писать тесты с использованием BDD, где на каждый простейший клик приходится создавать несколько уровней классов, или, не дай бог, работать с Serenity. Хотя BDD-отчёты мне в принципе нравились, я думал, как сделать так, чтобы получать удобные и читаемые репорты, схожие с BDD, но без самой архитектуры BDD. Либо найти компромиссное решение. Плюс к этому должна быть возможность составлять отчёт автоматически на любом языке, а не только на английском.
Я хотел автоматически получать примерно такой отчёт:
В этом отчёте обозначены все типы элементов, а человекопонятным языком описывается совершаемое действие. Видны все применённые ассерты, но отсутствует лишний мусор. Такой отчёт я получаю автоматически, если тест составлен похожим образом:
@Epic("Selenide style")
public class ITExampleAlluriumTest extends TestBaseAllurium {
@Test
@Feature("Form")
@DisplayName("Filling the example form")
public void fillTheForm() {
URL imageUrl = this.getClass().getClassLoader().getResource("img/testicon.png");
UiSteps.openBrowser(formPageUrl);
formPage.fieldLogin().assertVisible();
formPage.fieldLogin().write("John");
formPage.fieldLogin().assertHasCssClass("form-control");
formPage.fieldLogin().assertCurrentValue("John");
formPage.fieldEmail().write("john.doe@gmail.com");
formPage.fieldEmail().assertCurrentValue("john.doe@gmail.com");
formPage.fieldPassword().write("Password12345!");
formPage.fieldRank().write("10");
formPage.fieldDate().clearAndWrite("11.11.2011");
formPage.fieldTelephone().write("199887688");
formPage.uploadAvatar().uploadFile(new File(imageUrl.getFile()));
formPage.radioBtnMale().click();
formPage.radioBtnMale().assertEnabled();
formPage.radioBtnFemale().assertDisabled();
formPage.ckbMorning().check();
formPage.ckbMorning().assertChecked();
formPage.ckbEvening().assertUnchecked();
formPage.ddExperience().select("2 years");
formPage.selectWorkingDays().select("Monday");
formPage.selectWorkingDays().select("Friday");
formPage.btnSubmit().click();
}
}
В этом примере тест не выглядит короче, но, на мой взгляд, значительно читабельнее. Ассерты вызываются прямо на элементе, а фреймворк самостоятельно проверяет их и записывает отчёт в удобном формате.
Для создания PageObject в случае использования Allurium подход не сильно меняется. Всё должно быть чётко структурировано и типизировано, чтобы каждый вид элемента имел свой правильный тип, а фреймворк понимал, как с ним работать, и составлял корректное описание в отчёте.
@PageObject
@Getter
@Accessors(fluent = true)
public class FormPage extends Page {
@Name("Login")
@Locator(css = "#login")
protected TextField fieldLogin;
@Name("Email")
@Locator(css = "#email")
protected TextField fieldEmail;
@Name("Password")
@Locator(css = "#password")
protected TextField fieldPassword;
@Name("Rank")
protected TextField fieldRank = $textField("#rank");
@Name("Date")
protected TextField fieldDate = _$textField("//input[@id='date']");
@Name("Telephone")
@Locator(css = "#tel")
protected TextField fieldTelephone;
@Name("Avatar")
@Locator(css = "#file")
protected UploadField uploadAvatar;
@Name("Gender Male")
@Locator(css = "#male")
protected Button radioBtnMale;
@Name("Gender Female")
@Locator(css = "#female")
protected Button radioBtnFemale;
@Name("Preferable time - Morning")
@Locator(css = "#checkbox1")
protected CheckBox ckbMorning;
@Name("Preferable time - Evening")
@Locator(css = "#checkbox2")
protected CheckBox ckbEvening;
@Name("Experience")
@Locator(css = "#experience")
protected DropdownSelect ddExperience;
@Name("Working Days")
@Locator(css = "#multiple-select")
protected Select selectWorkingDays;
@Name("Reset")
@Locator(css = ".btn-secondary")
protected Button btnReset;
@Name("Submit")
@Locator(css = ".btn-primary")
protected Button btnSubmit;
}
В данном случае PageObject выглядит объёмнее, но количество строк можно сделать одинаковым, всё зависит от выбранного подхода. Я предпочитаю оставлять отступы между полями с аннотациями — это улучшает читаемость.
В структуре Allurium фундаментальными являются классы, отмеченные аннотацией @PageObject
. Они служат контейнерами, в которых располагаются виджеты и простые элементы. Одна страница = один PageObject. Объекты страниц могут быть наследуемыми и расширяемыми без ограничений.
Внутри нашего PageObject FormPage располагаются простые элементы. Не все элементы имеют тип SelenideElement
— каждый элемент имеет соответствующий ему тип:
При объявлении элемента, кроме типа, нужно всегда присваивать ему имя. Я предпочитаю использовать аннотацию @Name("name")
вместо вызова .as("name")
. Это позволяет создавать экземпляры в нужный момент без лишних сложностей и выглядит более эстетично. Однако можно присваивать имя и через конструктор объекта.
Например, вместо:
@Name("Submit")
@Locator(css = ".btn-primary")
protected Button btnSubmit;
можно написать:
protected Button btnSubmit = $button(".btn-primary", "Submit");
Либо, если экземпляр уже создан, через setName("some-name")
:
Button btnSubmit = $button(".btn-primary");
btnSubmit.setName("Submit");
Далее необходимо указать локатор. Это можно сделать разными способами:
Подобно тому, как делали это с SelenideElement:
@Name("Submit")
protected Button btnSubmit = $button(".btn-primary");
Где ".btn-primary"
— CSS-селектор кнопки. Примитивные элементы, такие как кнопки, текстовые поля и блоки текста, уже описаны во фреймворке. Экземпляр создаётся статическим методом, аналогично созданию SelenideElement:
protected SelenideElement btnSubmit = Selenide.$(".btn-primary").as("Submit");
При этом Selenide.$
заменяется на просто $
(импортируется как статический метод).
Длинный вариант был бы:
@Name("Submit")
protected Button btnSubmit = Button.$button(".btn-primary");
А короткий вариант через static метод:
@Name("Submit")
protected Button btnSubmit = $button(".btn-primary");
Если перед символом $
поставить нижнее подчёркивание (_
), как в примере ниже, элемент будет искаться по XPath:
@Name("Date")
protected TextField fieldDate = _$textField("//input[@id='date']");
Можно создать экземпляр через конструктор:
@Name("Submit")
protected Button btnSubmit = new Button(".btn-primary");
Или с помощью аннотации @Locator
, аналогично @FindBy
в Selenium:
@Name("Submit")
@Locator(css = ".btn-primary")
protected Button btnSubmit;
В этом случае экземпляр объявлять не обязательно — он автоматически создаётся фреймворком, если у элемента присутствует аннотация @Locator
.
@Locator
может принимать следующие типы локаторов:
By.cssSelector(".btn-primary")
By.xpath("//*[@class='btn-primary']")
By.className("btn-primary")
By.id("#id")
Для открытия браузера и загрузки страницы вместо Selenide.open("url");
используется UiSteps.openBrowser(url);
— этот шаг также записывается в отчёт. UiSteps
— класс со статическими методами, не привязанными к конкретной странице или элементу, которые фреймворк трактует как шаги и логирует в отчёт.
В данной системе каждый элемент имеет набор базовых и специфичных методов (шагов), которые при вызове логируются фреймворком как шаги отчёта. Например, методы contextClick()
и doubleClick()
доступны для любого элемента, а метод write("some text")
присутствует только там, где возможен ввод текста (например, у TextField
или TextArea
).
Все шаги являются публичными, и при их вызове фреймворк автоматически записывает информацию в отчёт, делая его максимально читаемым.