Всем привет, подскажите пожалуйста, кто как объявляет веб-элементы пейдж-обжекта? Есть на эту тему много мнений, но интересует как же лучше всего, плюсы и минусы. Я в своих тестах делаю через аннотация @FindBy, как делаете это вы, почему? Почему не используете @FindBy?
Мне известны такие способы:
// # 1
WebElement loginField = driver.findElement(By.xpath("//*[@id='email']"));
// #2 Selenide
SelenideElement loginField = $("//*[@id='email']");
// #3 Selenide, но не объявляете веб-элементы, пишите локаторы прямо в методе
public void login(String user, String pass) {
$("//*[@id='email']").setValue(user);
$("//*[@id='password']").setValue(pass);
$("//*[@id='submit-button']").click;
}
// # 4
@FindBy(xpath = "//*[@id='email']")
public WebElement loginField;
// #5 собственные интерфейсы, используя @FindBy
@FindBy(xpath = "//*[@id='email']")
public Field login;
@FindBy(xpath = "//*[@id='send-button']")
public Button send;
// # 6
@AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"com.facebook.orca:id/up\")")
public WebElement navigateUpButton;
В вебе использую:
@FindBy(xpath = "//*[@id='email']")
public WebElement loginField;
Для Android (Appium), использую:
@AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"com.facebook.orca:id/up\")")
public WebElement navigateUpButton;
public class NewStandardB extends BaseCard {
public NewStandardB(WebDriver driver) {
super(driver);
addElement(body, "Code", "id='Code-inputEl'", ComponentType.INPUT);
addElement(body, "Preview", "xpath=descendant::div[@id='ImageFileContainer-targetEl']/descendant::span[contains(@class,'icon-download')]/ancestor::a", ComponentType.BUTTON);
}
public NewStandardB clickOnPreview(){
clickOn("Preview");
return this;
}
public NewStandardB setCode(String code){
clear("Code");
setValue("Code", code);
return this;
}
`
Я сделал один раз работу с элементами. И только делаю локейшены без разницы id, class, css, xpath все происходит внутри и меня не сильно волнует. Можно даже по списку элементов генерировать код работы работы автоматом не заморачиваясь. addElement добавляет элемент в дерево. “body” - это парент элемент. таким образом строятся зависимости.
Спасибо, пока не совсем понятно конечно как это реализовано у вас. Выходит вы в конструктор передаете все эелементы пейджа? А если их много очень будет?
Надо наверное и свою часть пейдж-обжекта показать, почему использую именно так, возможно в чем-то не прав:
В таком подходе, как использую я, все вроде как разделено и удобно переиспользовать.
Пример класа-пейджа:
public class LoginPage extends BasePage<LoginPage> {
public LoginPage open(String url) {
return init(LoginPage.class, Constants.BASE_URL + url);
}
@FindBy(xpath = "//*[@id='email']")
public WebElement loginField;
@FindBy(xpath = "//*[@id='password']")
public WebElement passwordField;
@FindBy(xpath = "//*[@class='button-sumbit']/button")
public WebElement submitBtn;
@FindBy(xpath = "//*[@class='bg-danger']")
public WebElement invalidSignInMessages;
@Step("Login with name & password")
public PopUp loginWith(String name, String password) {
typeIn(loginField, name);
typeIn(passwordField, password);
clickOn(submitBtn);
return init(PopUp.class);
}
public String getInvalidSignInMessages() {
return invalidSignInMessages.getText();
}
}
Пример класа BasePage:
public abstract class BasePage<T extends BasePage<T>> {
private static final Logger logger = LogManager.getLogger(BasePage.class);
public T then() {
return (T) this;
}
public T thenGoTo(Class<T> clazz) {
return init(clazz);
}
@Step("Type value \"{1}\" in field \"{0}\"")
protected T typeIn(WebElement element, String text) {
element.clear();
element.sendKeys(text);
return (T) this;
}
@Step("Click element: \"{0}\"")
protected T clickOn(WebElement element) {
element.click();
return (T) this;
}
}
Пример теста:
@Title("Main logIn form")
@Test(enabled = true, groups = "login", priority = 10)
public void signIn() {
//------------------- Test Data -------------------//
String name = Constants.TestData.LOGIN;
String pass = Constants.TestData.PASS;
// --------------------- Test Case ----------------------//
LoginPage loginPage = init(LoginPage.class);
PopUp popUp = loginPage.open("/login.html").then().loginWith(name, pass).thenGoTo(PopUp.class);
String popUpTitleMessage = popUp.getTitlePopUpMessages();
String popUpBodyMessage = popUp.getBodyPopUpMessages();
assertThat(popUpTitleMessage).isEqualTo("some text");
assertThat(popUpBodyMessage).isEqualTo("some text");
}
Я создаю в дереве все элементы, но не пейджа, а части страницы. Т.е. у меня на странице динамически возникают карточки. Вот я их и подключаю каждую в отдельности если нужно. Я не забочусь как там элемент работает. Вот как пример (addElement(messageBox, “Yes”, “buttontext=‘Yes’”, ComponentType.BUTTON)
Это кнопка с текстом Yes. Если бы я описывал каждую кнопку то получилось бы очень длинный Xpath К тому же кнопок “Yes” у меня может быть 10 на странице. И для каждой формировать полный Xpath очень нудно, а id нет.
Вот метод получает сгенерированный WebElement c полным путем к элементу getWebElementByName(name).click(); А путь может состоять из елементов с локаторами id, css, xpath, class, name. какой удобней тот и подсуну а мой фреймворк за меня все сделает.
Например есть метод setValue(name, value). Он может вызываться для разных компонентов combobox, input, textarea и др. Метод внутри сам знает как с компонентами работать.
Не учитывая того факта, что вам в каждом методе / каждой пейдже, где нужно выполнить какие-либо действия с драйвером, надо писать тонну boilerplate кода, дублируя driver.findElement, создавая множество ненужных объектов по типу WDWait, заваливать тесты необработанными эксепшенами и т.п. Фактически вы вынесли всю работу с driver на уровень пейджей. И тут в который раз всплывает вопрос - а зачем доменному слою драйвер, и что по-вашему тогда является фреймворком?
Зачем тогда вставлять всю пейджу целиком? Чтобы учить остальных, как делать неправильно? Если вы хотели показать, как объявлять элементы, достаточно было вставить первые 3 строки страницы.
я делаю fluent page object в соответствии со всеми правилами, за исключением может только создания локальных webdriverwait’ов, но это не проблема для меня, может на рефакторинге поправлю, все остальное - это лучшие практики. Но как бы я вам свое решение не навязываю, и не считаю, что я делаю не правильно. Эксепшны я обрабатываю только там где надо, и waitы использую по делу, у меня ajax сайты.
хм… интересно… Что-то подобное уже где-то видел. А подскажите пожалуйста, Wrappers - он наследуется от чего-то, или в него просто драйвер передается, и сами пейджы от чего наследуются, от этого Wrappers?
Хотя то что он абстрактный - уже говорит за то, что от него наследуются другие какие-то классы, а драйвер передается в него
Основные методы типа клики и прочее вынесено в главный класс, все спицифическое, типа Селект, Чек реализовано внутри спицифического типа. К тому же, этот подход очень легко реализовывает динамическую инициализацию.