Page Objects, вероятно, самый известный на сегодня паттерн, используемый в автоматизации через UI. И самый простой, скажете вы? Не соглашусь, по моим наблюдением применение данного паттерна таит в себе подводные камни даже для наиболее типичных веб-приложений.
В данном докладе я познакомлю вас с тремя простыми и практичными видами PageObject архитектуры - Static, Void и Fluent, продемонстрирую использование кодом, сравню плюсы и минусы подходов. Также я расскажу о важных недостатках Yandex HTMLElements и о некоторых других неудачных подходах, которые повторяются из одного самодельного фреймворка в другой. Вы увидите на примерах, как простой код решает проблемы лучше, чем слишком умный. Примеры на языке Java/Selenide легко переносимы и на другие языки программирования (.NET, Python и другие).
28. Static Page Objects
Все элементы и методы - static
Методы возвращают void или сущности
Другое название PageModules*
* Яков Крамаренко, „KISS Automation“ (2015)
29. Static (Page Modules)
public class LoginPage {
public static SelenideElement welcomeMsg=$("#welcome"),
username=$("#username"),
password=$("#password"),
loginBtn=$("#login");
public static void login(String user, String pwd){
username.setValue(user);
password.setValue(pwd);
loginBtn.click();
}
}
public class HomePage {
public static SelenideElement welcomeMsg=$("#welcome");
}
30. Static (Page Modules)
public class LoginPage {
public static SelenideElement welcomeMsg=$("#welcome"),
username=$("#username"),
password=$("#password"),
loginBtn=$("#login");
public static void login(String user, String pwd){
username.setValue(user);
password.setValue(pwd);
loginBtn.click();
}
}
public class HomePage {
public static SelenideElement welcomeMsg=$("#welcome");
}
31. Static (Page Modules)
public class LoginTest {
@Test
public void testLogin(){
open("/");
LoginPage.welcomeMsg.shouldBe(visible);
LoginPage.login("admin","12345");
HomePage.welcomeMsg.shouldBe(visible);
}
}
32. Паттерн public/private (or default)
…
// elements to be used in tests
public static SelenideElement welcomeMsg = $("#welcome");
// elements to be used in pageObject only
static SelenideElement username = $("#username"),
password = $("#password"),
loginBtn = $("#login");
…
33. Антипаттерн Getter/Setter
public class LoginPage {
public static SelenideElement welcomeMsg=$("#welcome"),
username=$("#username"),
password=$("#password"),
loginBtn=$("#login");
public static SelenideElement getWelcomeMsg() {
return welcomeMsg;
}
public static SelenideElement getUsername() {
return username;
}
public static SelenideElement getPassword() {
return password;
}
public static SelenideElement getLoginBtn() {
return loginBtn;
}
public static void login(String username, String pwd){
getUsername().setValue(username);
getPassword().setValue(pwd);
getLoginBtn().click();
}
}
35. Static imports
import static com.app.pages.LoginPage.*;
public class LoginTest {
@Test
public void testLogin(){
open("/");
welcomeMsg.shouldBe(visible);
login("admin","12345");
HomePage.welcomeMsg.shouldBe(visible);
}
}
Не всегда хороши:
36. Static imports
import static com.app.pages.FormElement.*;
public class FormTest {
@Test
public void shouldBeInitiallyEmpty(){
firstname.shouldBe(empty);
lastname.shouldBe(empty);
addressLine1.shouldBe(empty);
addressLine2.shouldBe(empty);
mobilePhone.shouldBe(empty);
}
}
Иногда полезны:
37. Преимущества - недостатки
+ Очень просто и доступно для начинающих
- (очень редко) Проблемы при параллелизации
(может зависеть от фреймворка, из-за static)
? меньше гибкости:
не рекомендуется хранить состояние
нельзя сделать текучий (fluent) интерфейс
40. Void Page Objectspublic class LoginPage {
public SelenideElement welcomeMsg = $("#welcome");
SelenideElement username = $("#username"),
password = $("#password"),
loginBtn = $("#login");
public void login(String username, String pwd) {
…
}
public LoginPage(){
welcomeMsg.shouldBe(visible);
}
}
public class HomePage {
SelenideElement welcomeMsg=$("#welcome");
public SelenideElement loggedInUser=$("#loggedInUser");
public HomePage(){
welcomeMsg.shouldBe(visible);
}
}
41. Void Page Objects
public class LoginTest {
@Test
public void testLogin(){
open("/");
new LoginPage().login("admin","12345");
HomePage homePage=new HomePage();
homePage.loggedInUser.shouldHave(text("admin"));
}
}
42. Преимущества - недостатки
+ Очень просто и доступно для начинающих
+/? Проверки в конструкторе
+/? можно хранить состояние
(пример - выбранная строка таблицы)
43. Fluent Page Objects
Методы возвращают PageObject страницы перехода
Если перехода нет - возвращается текущий
PageObject
44. Fluent Page Objects
public class LoginPage {
public SelenideElement welcomeMsg = $("#welcome");
…
public HomePage login(String username, String pwd) {
this.username.setValue(username);
password.setValue(pwd);
loginBtn.click();
return new HomePage();
}
public LoginPage(){
welcomeMsg.shouldBe(visible);
}
}
46. Fluent Page Objects
public class HomePage {
SelenideElement welcomeMsg=$("#welcome"),
reloadBalanceBtn=$("#reloadBalance"),
balanceDetailsBtn=$("#balanceDetails");
…
}
public HomePage reloadBalance(){
reloadBalanceBtn.click();
return this; // return new HomePage();
}
public BalanceDetailsPage openBalanceDetails(){
balanceDetailsBtn.click();
return new BalanceDetailsPage();
}
}
47. Fluent Page Objects
public class LoginTest {
@Test
public void testLogin(){
open("/");
HomePage homePage=new LoginPage().login("admin","12345");
homePage.loggedInUser.shouldHave(text("admin"));
}
}
48. Fluent Page Objects
public class FlowTest {
@Test
public void fluentTest(){
new LoginPage().login("admin","12345")
.reloadBalance()
.openBalanceDetails();
}
@Test
public void nonfluentTest(){
HomePage homePage=new LoginPage().login("admin","12345");
homePage.reloadBalance();
BalanceDetailsPage balancePage=homePage.openBalanceDetails();
}
}
Абсолютно неоднозначно, какой из вариантов лучше
49. Преимущества - недостатки
? переходы между страницами определены в
Page Object - анализ тулзами
-/? некоторые сложности, если метод
возвращает сущности
51. Методы на распутье (Void)
public class LoginTest {
@Test
public void testLogin(){
new LoginPage().login("admin","12345");
HomePage homePage=new HomePage();
homePage.loggedInUser.shouldHave(text("admin"));
}
@Test
public void testBadLogin(){
LoginPage loginPage=new LoginPage();
loginPage.errorMsg.shouldNotBe(visible);
loginPage.login("admin","admin");
new LoginPage().errorMsg.shouldBe(visible);
//loginPage.errorMsg.shouldBe(visible);
}
}
52. Методы на распутье (Fluent)
…
public HomePage login(String username, String pwd) {
doLogin(username, pwd);
return new HomePage();
}
public LoginPage badLogin(String username, String pwd) {
doLogin(username, pwd);
return new LoginPage(); //return this;
}
private void doLogin(String username, String pwd) {
this.username.setValue(username);
password.setValue(pwd);
loginBtn.click();
}
…
}
53. Методы на распутье (Fluent)
public class LoginTest {
@Test
public void testLogin(){
HomePage homePage=new LoginPage().login("admin","12345");
homePage.loggedInUser.shouldHave(text("admin"));
}
@Test
public void testBadLogin(){
LoginPage loginPage=new LoginPage().badLogin("admin","admin");
loginPage.errorMsg.shouldHave(text("Error"));
}
}
59. Паттерн Page Elements (Page Blocks)
сложно или невозможно обозначить страницы
блоки элементов повторяются на разных страницах
поддержка в Yandex HTMLElements и др.
паттерн можно использовать без композиции
60. Тесты с Page Elements без композиции
public class NonCompositeTest {
@Test
public void elementTest(){
new LoginPage().login(„admin“,"12345");
HomePage page=new HomePage();
AddressElement address=new AddressElement();
page.userName.shouldBe(visible);
address.zipCode.shouldBe(visible);
}
}
61. Композиция с Page Elements
public class HomePage {
…
public AddressElement addressElement;
public HomePage(){
addressElement=new AddressElement();
…
}
}
62. Сложности с Page Elements
<div id=„address1“>
…
// code of addressElement
…
</div>
<div id=„address2“>
…
// code of addressElement
…
</div>
SavingAccountPage VisaAccountPage
Посмотрите Yandex HtmlElements
Или напишите своё решение
Или …
63. 4 стадии развития
1. Знаю как сделать
2. Знаю как сделать эффективно
3. Знаю как сделать изящно
4. Знаю как не делать
64. Дублирование кода
SavingAccountPage VisaAccountPage
public class SavingAccountPage {
SelenideElement page=$("#address1"),
street=page.$(„#street“),
city=page.$(„#city“),
zipCode=page.$(„#zipCode“)
…
}
public class VisaAccountPage {
SelenideElement page=$("#address2"),
street=page.$(„#street“),
city=page.$(„#city“),
zipCode=page.$(„#zipCode“)
…
}
65. Дублирование кода
Сopy & Paste безопаснее в Page Object классах,
чем в прочем коде
потому что тестовый код ПОСТОЯННО исполняется
68. Резюме
3 паттерна для написания Page Objects:
Вспомогательные паттерны:
Static
Void
Fluent
public/private доступ
Page Element (Page Blocks)
дублирование кода
69. Резюме
анти-паттерны для Page Objects:
типизированные объекты
наследование
getter/setter
чрезмерное распределение обязанностей
70. Резюме
темы, не попавшие в доклад
Возвращение методами сущностей
Паттерн DataTransferObjects
Сохранения состояния
Простая реализация паттерна композиции
71. Три простые мысли*
В нашем мире не всё, всегда и везде, а кое-что,
иногда и местами. Из любых правил есть
исключения. И при принятии решений нужно
всегда держать голову включенной.
* (с) Дорофеев „Вебинар: Джедайская техника доведения дел до конца“
72. Три простые мысли*
Сложность порождает проблемы, а простые
вещи зачастую самые действенные.
* (с) Дорофеев „Вебинар: Джедайская техника доведения дел до конца“
73. Три простые мысли*
Освоен метод или нет становится понятно,
только когда начинаешь его применять. Только
через практику можно освоить написание
хороших тестов.
* (с) Дорофеев „Вебинар: Джедайская техника доведения дел до конца“