Как вы объявляете веб-элементы пэдж-обжекта? (парочка примеров)

page-object
webdriver
selenium
Теги: #<Tag:0x00007fedbc05bcc8> #<Tag:0x00007fedbc05ba48> #<Tag:0x00007fedbc05b7f0>

(Eugene Moskalenko) #1

Всем привет, подскажите пожалуйста, кто как объявляет веб-элементы пейдж-обжекта? Есть на эту тему много мнений, но интересует как же лучше всего, плюсы и минусы. Я в своих тестах делаю через аннотация @FindBy, как делаете это вы, почему? Почему не используете @FindBy? :slight_smile:

Мне известны такие способы:

// # 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;

Спасибо :slight_smile:


(Vladislav Kulasov) #2
 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" - это парент элемент. таким образом строятся зависимости.


(Eugene Moskalenko) #3

Спасибо, пока не совсем понятно конечно как это реализовано у вас. Выходит вы в конструктор передаете все эелементы пейджа? А если их много очень будет?

Надо наверное и свою часть пейдж-обжекта показать, почему использую именно так, возможно в чем-то не прав:

В таком подходе, как использую я, все вроде как разделено и удобно переиспользовать.

Пример класа-пейджа:

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");
}

(Vladislav Kulasov) #4

Я создаю в дереве все элементы, но не пейджа, а части страницы. Т.е. у меня на странице динамически возникают карточки. Вот я их и подключаю каждую в отдельности если нужно. Я не забочусь как там элемент работает. Вот как пример (addElement(messageBox, "Yes", "buttontext='Yes'", ComponentType.BUTTON):wink:
Это кнопка с текстом Yes. Если бы я описывал каждую кнопку то получилось бы очень длинный Xpath К тому же кнопок "Yes" у меня может быть 10 на странице. И для каждой формировать полный Xpath очень нудно, а id нет.

Вот метод получает сгенерированный WebElement c полным путем к элементу getWebElementByName(name).click(); А путь может состоять из елементов с локаторами id, css, xpath, class, name. какой удобней тот и подсуну а мой фреймворк за меня все сделает.
Например есть метод setValue(name, value). Он может вызываться для разных компонентов combobox, input, textarea и др. Метод внутри сам знает как с компонентами работать.


(Eugene Moskalenko) #5

Спасибо, довольно интересный подход..


(Dmitry) #6

Просто, быстро, читабельно:

public class DictionaryModelsPage extends GlobalSettings {


    By addModelButtonLocator = By.cssSelector("a[href='/Dictionaries/AddModel']");

    By searchButtonLocator = By.id("btnFind");
    By brandDropdownLocator = By.id("BrandId");

    private WebDriver driver;

    public DictionaryModelsPage(WebDriver driver) {

        this.driver = driver;
    }

    public AddModelPage clickAddModelButton() {

        WebDriverWait wait = new WebDriverWait(driver, 30);
        wait.until(ExpectedConditions.elementToBeClickable(addModelButtonLocator));

        driver.findElement(addModelButtonLocator).click();
        driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS);

        return new AddModelPage(driver);
    }

}

(Sergey Korol) #7

Не учитывая того факта, что вам в каждом методе / каждой пейдже, где нужно выполнить какие-либо действия с драйвером, надо писать тонну boilerplate кода, дублируя driver.findElement, создавая множество ненужных объектов по типу WDWait, заваливать тесты необработанными эксепшенами и т.п. Фактически вы вынесли всю работу с driver на уровень пейджей. И тут в который раз всплывает вопрос - а зачем доменному слою драйвер, и что по-вашему тогда является фреймворком?


(Dmitry) #8

в данном случае речь идет об объявлении веб-элементов page object. А то, что у меня не очень оптимизировано остальное в методах, я в курсе.


(Sergey Korol) #9

Зачем тогда вставлять всю пейджу целиком? Чтобы учить остальных, как делать неправильно? Если вы хотели показать, как объявлять элементы, достаточно было вставить первые 3 строки страницы.


(Dmitry) #10

я делаю fluent page object в соответствии со всеми правилами, за исключением может только создания локальных webdriverwait’ов, но это не проблема для меня, может на рефакторинге поправлю, все остальное - это лучшие практики. Но как бы я вам свое решение не навязываю, и не считаю, что я делаю не правильно. Эксепшны я обрабатываю только там где надо, и waitы использую по делу, у меня ajax сайты.


(Sergey QA) #11

Объявляю элементы стандартно, правда предварительно написан мини-метод оболочку по типу:

private By customDataField = findByCss(".element");
private By isAdminCheckbox = findByXpath("//element");

Действия над элементами в виде оболочек для методов реализую в абстрактном классе Wrappers:

public By findByCss(String cssSelector){
return By.cssSelector(cssSelector);
}

public void waitUntilElement(By locator){
    WebDriverWait wait = new WebDriverWait(getWebDriver(), 5);
    WebElement element = wait.until(ExpectedConditions.elementToBeClickable(locator));
}

public void type(By field, String value){
    driver.findElement(field).sendKeys(value);
}

И вот как на пейдже уже это выглядит

public void printResult(String data){
clearAndType(nameField, data);
}

Так же я сильно против использования @FindBy, потому что просто By - это всего лишь ссылка на объект, а @FindBy - уже объект страницы.


(Eugene Moskalenko) #12

хм… интересно… Что-то подобное уже где-то видел. А подскажите пожалуйста, Wrappers - он наследуется от чего-то, или в него просто драйвер передается, и сами пейджы от чего наследуются, от этого Wrappers?

Хотя то что он абстрактный - уже говорит за то, что от него наследуются другие какие-то классы, а драйвер передается в него


(Oleg Kuzovkov) #13

Немного Питона вам в тему :slight_smile:

class NewCustomerPage(WebPage):
    web_page_id = (By.XPATH, '//*[text()="Customer: New"]')
    #Page elements
    page_title =        TextBlock(By.ID, "pageTitleText")
    first_name =        TextField(By.ID, "view_f_name")
    last_name =         TextField(By.ID, "view_l_name")
    email_1 =           TextField(By.ID, "view_contact_id_email")
    attach_to_sale =    Button(By.ID, "attachToSaleButton")
    save_changes =      Button(By.ID, "saveButton")

(Eugene Moskalenko) #14

А че это у вас там такое интересненькое? :slight_smile:

TextBlock(By.ID, "pageTitleText")

Button(By.ID, "attachToSaleButton")

Написали реализацию взаимодействия с входящими параметрами. Под разный тип элемента. И там скрыты клики и сендкеи и прочее прочее?


(Oleg Kuzovkov) #15

Основные методы типа клики и прочее вынесено в главный класс, все спицифическое, типа Селект, Чек реализовано внутри спицифического типа. К тому же, этот подход очень легко реализовывает динамическую инициализацию.


#16

можете пример скинуть?


(Oleg Kuzovkov) #17

Пример чего именно? :slight_smile:


#18

это


(Oleg Kuzovkov) #19

Для этого мне нада описать весь мой фреймворк :slight_smile:

Я Вам пришлю чуть позже, тема ведь просто о декларациях…


#20

да можно не присылать) просто тут примеры прикрепляют

хотел посмотреть