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

Спасибо огромное, было бы здорово взглянуть :slight_smile: И вот это еще, если будет время, пожалуйста - “С рефлексией все будет гораздо компактней. Может на днях залью новую версию.” - Какой должна быть идеальная структура проекта автотестов? - #33 от пользователя adv

:slight_smile:

Ну у меня из-за специфики проекта, как плюс это считаю, повторяются многие элементы и локаторы на страницах, но может быть такое что в разных блоках страницы то уже структурирую по компонентам и степам к ним и объединяю в страницы это всё чудо.
Т.к. есть кастомные решения для выпадаек и для кнопок, то есть объекты со специфическими методами для их манипуляций.

public class ProductButton extends AbstractElement{
    @FindBy(css = "a.ga-add-to-cart")
    public SelenideElement buttonAddToCart;
    @FindBy(css = "a.button-preorder")
    public SelenideElement buttonPreOrder;
    @FindBy(css = "span.in-cart > a")
    public SelenideElement buttonInCart;
    @FindBy(name = "btn-minus")
    public SelenideElement buttonMinus;
    @FindBy(name = "quantity")
    public SelenideElement fieldQuantity;
    @FindBy(name = "btn-plus")
    public SelenideElement buttonPlus;
    @FindBy(css = "span.icon")
    public SelenideElement labelStockWithIcon;}

Это всё может находится на разных страницах и может в разных блоках, поэтому есть такая штука в объекте какого-то блока
@FindBy(css = "div.product-button-wrapper") public ProductButton productButton;

Далее, эту кнопку мне нужно найти для определённого товара на странице по id, к примеру на странице категорий. А страница категорий состоит из “строк/блоков” каждой единицы товара и чтобы взять определённого товара блок и только в нём работать есть акая штука:

public class ProductList extends AbstractElement {
    public ProductsLineBlock findProductByIDInTags(String id, String tagName) {
        return initContainer($(String.format("%s[data-id='%s']", tagName, id)),
                ProductsLineBlock.class);
    }
}

Именно так потому что вид страницы может быть в виде таблицы, строк с расширенным описанием или строк без описание. И таким способом я дальше могу работать с локаторами которые содержатся в ProductsLineBlock. В котором есть особенная кнопка “Купить”

Вот что содержится в AbstractElement
public abstract class AbstractElement extends ElementsContainer{

protected <T extends AbstractElement> T initContainer(SelenideElement element, Class<? extends AbstractElement> type) {
    T result;
    try {
        result = (T) type.newInstance();
    } catch (Exception e) {
        throw new RuntimeException("Failed to create elements container for element", e);
    }
    PageFactory.initElements(new SelenideFieldDecorator(element), result);
    result.setSelf(element);
    return result;
}

Вот к примеру кастомная выпадайка и метод для неё
public class CustomDropdown extends AbstractElement {

public void selectItem(String nameOfItem){
    getSelf().click();
    $(byXpath(String.format("//span[.='%s']", nameOfItem))).scrollTo().click();
}}

Таких кастомных 3 штуки есть, + есть аналог для номера телефона и выбора кода страны, составной элемент с инпутом и выпадайкой

Также есть кастомные элементы это поиск, у него есть выпадайка с перечнем категорий в которых искать, + составной из двух блоков быстрый поиск.

Это всё классы компоненты страниц и из них я уже леплю степы для них, которые объединяю в пакеты страниц.

Пример теста: public class TestTest extends BaseTest {

@Test
public void olo(){
    Configuration.baseUrl = TestData.urlMainPageGSMCom;
    addProductViaAjaxUrl(simpleProduct);
    open(urlCheckout);
    CheckoutContactInformationSteps checkoutContactInformationSteps = getStepsFrom(CheckoutContactInformationSteps.class);
    checkoutContactInformationSteps.testMethod();
}

}

Где getSteps это:

protected static <T> T getStepsFrom(Class<? extends AbstractSteps> pageObjectClass) {
    return (T) page(pageObjectClass);
}

ну и testMethod

public CheckoutContactInformationSteps testMethod(){
    this.checkoutContactInformation.customCountry.selectItem(TURKEY);
    this.checkoutContactInformation.customPhone.setPhoneByCountry(TURKEY, TestData.CONTACT_NUMBER);
    return this;
}
1 лайк

аа ну для этого делаю сайта автоматизацию: gsmserver.com, можете увидеть какая кнопка “Купить” и “Поиск” о которых говорил

1 лайк

И метод getStepsFrom в тестовых класса вызываю в основном в методах с анотациями @BeforeClass или @Before
А конфигурации уже в таске gradle

я вот сейчас именно эту штуку активно использую, если речь идет о дефолтных интерфейсах, в которых можно имплементировать только реализацию, без состояния :slight_smile:

Речь шла о дефолтных и статических методах в интерфейсах. В девятке еще и приватные методы добавят.

2 лайка

ага, я эт и имел ввиду. Сейчас стабильно в своих автотестах юзаю эти вещи. :slight_smile: Очень сильно улучшает фреймворк…

В восьмой джаве очень много фишек, значительно улучшающих дизайн архитектуры.

Это точно :slight_smile:

Интересно: а как у Вас описаны классы страниц?
В частности интерес представляет расположение веб-айтемов в классе (атрибуты класса или атрибуты инстанса)

Столкнулся на днях с некоторыми проблемами при определении веб-элементов как атрибутов класса, интересно как подобные проблемы решаются другими людьми.

И реализовывали ли веб-айтемы вида “контейнер”? Т.е. группа элементов связанная логически (input, div с эррор-меседжем, etc), для которой длеается отдельный реюзабельный класс, дабы каждый раз не описывать по 3-5 веб-айтемов на странице.

Примерно так:

class TagLoginPage(WebPage):
    web_page_id = (By.ID, 'login')

    def __init__(self):
        super().__init__()
        # Page items
        self.email = Input(By.XPATH, '//input[@type="email"]', self)
        self.password = Input(By.XPATH, '//input[@type="password"]', self)
        self.submit = Button(By.XPATH, '//input[@type="submit"]', self)

    def invoke_actions(self):
        self.browser_session.open(JSONReader.get_data_from_string("environment_links", "tag", json_string=Vars().env_config))

    @allure.step
    def login(self):
        self.invoke().email.send_keys(Vars().retailer_email)
        self.password.send_keys(Vars().retailer_password)
        self.submit.click()
1 лайк

Спасибо, полезно. Жаль что я выбрал более заковыристый путь, в угоду красоте и юзабельности “не программистами”

как по мне - самый удобный вариант ,
не лучший пример, но SelenideElement emailInputField= $("#email"); выглядит вполне щикарно и просто

Только работать не будет так как по дефолту идет CSS, а у Вас написан xPath.
вот так будет правильней.
SelenideElement loginField = $(By.xpath("//*[@id='email']"));

Та да, то я затупил, когда писал, у меня так и написано :slight_smile: Просто когда копировал с первого примера, то копипастил все что в кавычках :slight_smile: к сожалению позже уже поправить не смог, поскольку первое сообщение из темы, сейчас тоже поправить не могу

Что скажите о таком подходе?

Сделайте more DRY and KISS - в текущей реализации на 3 с минусом.

Не могли бы вы привести свой вариант? Интересно, как вы видите вариант на 5.

1 лайк

А вы тесты покажите - будет точка отсчёта.

Там в ветке есть тесты берите “точку отсчёта”. В принципе для PO не нужна логика тестов. Возьмите любую страницу и сделайте PO, а мы посмотрим, поучимся.