Всем привет! Меня зовут Артём Соковец. Хочу поделиться переводом своей статьи об Atlas: реинкарнации фреймворка HTML Elements, где представлен совершенно иной подход работы с Page Object.

Перед тем, как перейти к деталям, хочу спросить: сколько обёрток для Page Object вы знаете? Page Element, ScreenPlay, Loadable Component, Chain of invocations…

А что будет, если взять Page Object с реализацией на интерфейсе, прикрутить Proxy Pattern и добавить немного функциональности Java 8?

Если интересно, предлагаю перейти под кат.



Введение


При использовании стандартного шаблона проектирования PageObject возникает ряд проблем:

Дублирование элементов

public class MainPage {
    @FindBy(xpath = ".//div[@class = 'header']")
    private Header header;
}

public class AnyOtherPage {
    @FindBy(xpath = ".//div[@class = 'header']")
    private Header header;
}

Здесь блок Header используется в различных классах PageObject.

Отсутствие параметризации у элементов

public class EditUserPage {  
    @FindBy(xpath = "//div[text()='Text_1']")
    private TextBlock lastActivity;

    @FindBy(xpath = "//div[text()='Text_2']")
    private TextBlock blockReason;
}

В этом примере описываются элементы страницы редактирования настроек пользователя. Два элемента TextBlock содержат практически идентичный локатор с разницей только в текстовом значении («Text_1» и «Text_2»).

Однотипный код

public class UserPage {	
    	@FindBy(xpath = "//div[text()='Телефон']/input")
    	private UfsTextInput innerPhone;
     
    	@FindBy(xpath = "//div[text()='Email']/input")
    	private UfsTextInput email;
     
    	@FindBy(xpath = "//button[text()='Сохранить']")
    	private UfsButton save;
     
    	@FindBy(xpath = "//button[text()='Список']")
    	private UfsButton toUsersList;
}

В повседневной работе можно встретить Page Object, состоящие из множества строк кода с однотипными элементами. В дальнейшем такие классы «неудобно» поддерживать.

Большой класс с шагами (steps)

public class MainSteps {
    	public void hasText(HtmlElement e, Matcher m)  
    	public void hasValue(HtmlElement e, Matcher m)  
    	public void linkContains(HtmlElement e, String s)
    	public void hasSize(List<HtmlElement> e, Matcher m)
    	public void hasItem(List<HtmlElement> e, Matcher m)      
    	//...  
   }

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

Ваш путеводитель в мире Page Object


Реинкарнация фреймворка HTML Elements направлена на решение вышеописанных проблем, уменьшение количество строк кода тестового проекта, более продуманную работу со списками и ожиданиями, а также тонкую настройку инструмента под себя благодаря системе расширений.

Atlas — Java-фреймворк нового поколения для разработки UI-автотестов с реализацией паттерна Page Object через интерфейсы. Данный подход предоставляет возможность множественного наследования при построении дерева элементов, что в итоге обеспечивает лаконичный код ваших автотестов.

Основным нововведением фреймворка является использование интерфейсов вместо стандартных классов.

Вот так выглядит описание главной страницы github.com:

public interface MainPage extends WebPage, WithHeader {
    @FindBy("//a[contains(text(), 'Or start a free)]")
    AtlasWebElement trial();
}

В приведённом коде описывается главная страница сайта GitHub с одним элементом и множественным наследованием от слоёв WebPage и WithHeader (пример дан исключительно для учебных целей, поэтому большинство веб-элементов опущено).

Архитектура фреймворка


На текущий момент Atlas состоит из трёх модулей:

  • atlas-core
  • atlas-webdriver
  • atlas-appium

В atlas-core описана основная функциональность обработки Page Object'ов с помощью интерфейсов. Сама идея использования интерфейсов была взята из известного инструмента Retrofit.



Два других модуля atlas-webdriver и atlas-appium используются для разработки автоматизированных скриптов UI web и UI mobile. Основной точкой входа для описания web-страниц является интерфейс WebPage, а для мобильных экранов — Screen. Концептуально atlas-webdriver и atlas-appium построены на расширениях (пакет *.extension).

Элементы




В поставке инструмента идут два специализированных класса для работы с UI-элементами (аналог класса WebElement).



AtlasWebElement и AtlasMobileElement дополнены методами should и waitUntil. (рассмотрение данных методов будет далее в статье).

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

Основные возможности


Рассмотрим подробнее функциональность инструмента:



Интерфейсы вместо классов

При описании стандартных PageObject используются интерфейсы вместо классов.

public interface MainPage extends WebPage, WithHeader {
    @FindBy("//a[contains(text(), 'Or start a free trial of Enterprise Server')]")
    AtlasWebElement trial();
}

В данном примере описывается ссылка на стартовой странице GitHub.

Параметризация элементов

Представим, что у нас есть форма с полями:



Чтобы её описать, требуется создать 11 переменных с аннотацией @FindBy и, при необходимости, объявить getter.

Используя Atlas, потребуется лишь один параметризованный элемент AtlasWebElement.

public interface MainPage extends WebPage {
    @FindBy("//div[text()='{{ text }}']/input")
    AtlasWebElement input(@Param("text") String text);
}

Код автоматизированного теста выглядит следующим образом:

@Test
public void simpleTest() {
    onMainPage().input("First Name").sendKeys("*");
    onMainPage().input("Postcode").sendKeys("*");
    onMainPage().input("Email").sendKeys("*");
}

Обращаемся к нужной странице, вызываем метод с параметром и выполняем требуемые действия с элементом. Метод с параметром описывает конкретное поле.

Множественное наследование

Ранее упоминалось, что блок (например, Header), который используется в разных Page Object — это дублирование кода.

Есть header GitHub.



Опишем данный блок (большинство веб-элементов опущено):

public interface Header extends AtlasWebElement {
    @FindBy(".//input[contains(@class,'header-search-input')]")
    AtlasWebElement searchInput();
}

Далее создадим слой, который можно подключить к любой странице:

public interface WithHeader {
    @FindBy("//header[contains(@class,'Header')]")
    Header header();
}

Расширяем главную страницу блоком header.

public interface MainPage extends WebPage, WithHeader {
    @FindBy("//a[contains(text(), 'Or start a)]")
    AtlasWebElement trial();
}

В целом можно создать больше слоёв и подключить их к нужной странице. В примере ниже подключаем с главной странице слои header, footer, sidebar.

public interface MainPage extends WithHeader, WithFooter, WithSidebar {}

Пойдём дальше. Header содержит 4 кнопки, 3 выпадающих меню и одно поле поиска:



Создадим собственный элемент Button, и одним элементом опишем четыре кнопки.

public interface Button extends AtlasWebElement {
    @FindBy(".//a[contains(., '{{ value }}')]")
    AtlasWebElement selectButton(@Param("value") String value);
}

Подключим кнопку button к слою header. Таким образом расширим функциональность шапки GitHub.

public interface Header extends WithButton {
   …
}

Отдельный элемент Button можно подключать к различным слоям веб-сайта и быстро получить на нужной странице требуемый элемент.

Пример:

@Test
public void simpleTest() {
    onMainPage().open("https://github.com");
    onMainPage().header().button("Priсing").click();
}

Во второй строке теста происходит обращение к шапке сайта, далее вызываем параметризированную кнопку со значением «Pricing» и выполняем клик.

На тестируемом сайте может быть довольно много элементов, которые повторяются от страницы к странице. Чтобы не описывать их все с помощью стандартного подхода Page Object, можно описать их один раз и подключать там, где требуется. Экономия времени и количества строк кода налицо.



Методы по умолчанию

В Java 8 появились методы по умолчанию, которые используются, чтобы заранее определить нужную функциональность.

Допустим, у нас есть «вредный» элемент: например, чекбокс, который то включен, то выключен. Через него проходит много сценариев. Требуется включать чекбокс, если он выключен:

if(onMainPage().rentFilter().checkbox("Кирпич").getAttribute("class").contains("disabled")) {
    onMainPage().rentFilter().checkbox("Кирпич").click();
}

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

public interface Checkbox extends AtlasWebElement {
    @FindBy("//...")
    AtlasWebElement checkBox((@Param("value") String value);

    default void selectCheckbox(String value) {
        if (checkBox(value).getAttribute("class").contains("disabled")) {
            checkBox(value).click();
        }
    }
}

Теперь шаг в тесте будет выглядеть так:

onMainPage().rentFilter().selectCheckbox("Кирпич");

Другой пример, в котором требуется совместить очистку и ввод символов в поле.

onMainPage().header().input("GitHub").clear();
onMainPage().header().input("GitHub").sendKeys("*");

Определим метод, который очищает поле и возвращает сам элемент:

public interface Input extends AtlasWebElement {
   
    @FindBy("//xpath")
    AtlasWebElement input(@Param("value") String value);

    default AtlasWebElement withClearInput(String value) {
        input(value).clear();
        return input(value);
    }
}

В тестовом методе шаг выглядит следующим образом:

onMainPage().header().withClearInput("GitHub").sendKeys("Atlas");

Таким образом можно запрограммировать требуемое поведение в элементе.

Повторные попытки (Retry)

В Atlas есть встроенные повторные попытки. Вам не нужно заботиться о таких исключениях, как NotFoundException, StaleElementReferenceException и WebDriverException, а также можно забыть о применении явных и неявных ожиданий Selenium API.

onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10));

Если на каком-то этапе цепочки вы поймали исключение, фаза повторяется с начала.

Есть возможность самостоятельно настраивать интервал времени, в течение которого можно выполнять повторение, или частоту повторения.

Atlas atlas = new Atlas(new WebDriverConfiguration(driver))
        .context(new RetryerContext(new DefaultRetryer(3000L, 1000L, Collections.singletonList(Throwable.class))));

Ожидаем в течение трёх секунд c частотой опроса раз в секунду.

Также можем настроить ожидание для конкретного элемента с помощью аннотации Retry. Для всех элементов поиск будет происходить в течение 3 секунд, а в случае с одним составит 20.

@Retry(timeout = 20_000L, polling = 2000L)
@IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']")
@AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]")
AtlasMobileElement searchWikipedia();

Работа со списками

Из коробки инструмент предоставляет работу со списками. Что это значит? Есть поле с тегом input, куда вводим текст, далее появляется выпадающий список, элементы появляются не сразу.



Для таких случаев есть сущность ElementsCollection. С её помощью происходит работа со списками.

public interface ContributorsPage extends WebPage, WithHeader {
    @FindBy(".//ol[contains(@class, 'contrib-data')]//li[contains(@class, 'contrib-person')]")
    ElementsCollection<RepositoryCard> hovercards();
}

Использование:

onSite().onContributorsPage().hovercards().waitUntil(hasSize(4));

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

Smart Assertions

Как ранее упоминалось, в сущностях AtlasWebElement и AtlasMobileElement используются методы should, waitUntil для работы с проверками (утверждениями).

Метод Описание
should(…) Выполняет различные утверждения (проверки) с элементом. Бросает AssertionError
waitUntil(…) Выполняет различные утверждения (проверки) с элементом. Бросает RuntimeException

Для чего это сделано? Чтобы сэкономить время при разборе отчётов прогона автоматизированных сценариев. Большинство функциональных проверок выполняются в конце сценария: они интересны специалисту функционального тестирования, а промежуточные проверки – специалисту автоматизированного тестирования. Следовательно, если функциональность продукта не работает, логично бросать исключение AssertationError, в ином случае — RuntimeException.





В Allure сразу будет видно, с чем мы имеем дело: либо у нас дефект продукта (в работу берёт специалист ФТ), либо сломался автотест (разбирается специалист АТ).

Модель расширений



У пользователя есть возможность переопределить базовый функционал инструмента либо внедрить свой функционал. Модель расширения Atlas похожа на модель расширения JUnit 5. Модули atlas-webdriver и atlas-appium построены на расширениях. Если вам интересно, посмотрите исходный код.

Разберём абстрактный пример: требуется разработать UI-автотесты для браузера Internet Explorer 11 (кое-где он ещё используется). Бывают моменты, когда стандартный клик по элементам не отрабатывает, тогда можно воспользоваться JS-кликом. Вы решаете на время переопределить клик на всём тестовом проекте.

onMainPage().header().button("en").click();

Как это сделать?

Создаём расширение, которое реализует интерфейс MethodExtension.

public class JSClickExt implements MethodExtension {

    @Override
    public Object invoke(Object proxy, MethodInfo methodInfo, Configuration config) {
        final WebDriver driver = config.getContext(WebDriverContext.class)
                .orElseThrow(() -> new AtlasException("Context doesn't exist")).getValue();
        final JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript("arguments[0].click();", proxy);
        return proxy;
    }

    @Override
    public boolean test(Method method) {
        return method.getName().equals("click");
    }
}

Переопределяем два метода. В методе test() задаём, что переопределяем метод click. Метод invoke реализует требуемую логику. Теперь клик по элементу будет происходить через JavaScript.

Подключаем расширение следующим образом:

atlas = new Atlas(new WebDriverConfiguration(driver, "https://github.com"))
        .extension(new JSClickExt());

С помощью расширений возможно создать поиск локаторов для элементов в БД и реализовать другие интересные возможности — всё зависит от вашей фантазии и потребностей.

Единая точка входа к PageObject'ам (WebSite)

Инструмент позволяет хранить все ваши Pages в одном месте и в дальнейшем работать только через сущность Site.

public interface GitHubSite extends WebSite {
    @Page
    MainPage onMainPage();

    @Page(url = "search")
    SearchPage onSearchPage(@Query("q") String value);

    @Page(url = "{profile}/{project}/tree/master/")
    ProjectPage onProjectPage(@Path("profile") String profile, @Path("project") String project);

    @Page
    ContributorsPage onContributorsPage();
}

Дополнительно Page'ам возможно задавать быстрый url, query-параметры и path-сегменты.

onSite().onProjectPage("qameta", "atlas").contributors().click();

В строчке выше передаются два path-сегмента (qameta и atlas), что преобразовывается в адрес github.com/qameta/atlas/tree/master. Основное преимущество такого подхода в том, что возможно сразу открыть требуемую страницу без прокликивания до неё.

@Test
public void usePathWebSiteTest() {
    onSite().onProjectPage("qameta", "atlas").contributors().click();
    onSite().onContributorsPage().hovercards().waitUntil(hasSize(4));
}

Работа с мобильным элементом

Работа с мобильным элементом (AtlasMobileElement) происходит аналогично работе с веб-элементом AtlasWebElement. Дополнительно в AtlasMobileElement добавлены три метода: скролл экрана вверх/вниз (scrollUp/scrollDown) и клик на элемент с удержанием (longPress).

Приведу пример главного экрана приложения Wikipedia. Один элемент описывается как для платформы iOS, так и для Android. Также описывают параметризованную кнопку.

public interface MainScreen extends Screen {
    @Retry(timeout = 20_000L, polling = 2000L)
    @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']")
    @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]")
    AtlasMobileElement searchWikipedia();

    @IOSFindBy(id = "{{ value }}")
    AtlasMobileElement button(@Param("value") String value);
}

Тесты выглядят аналогичным образом:

public void simpleExample() {
    onMainScreen().searchWikipedia().click();
    onSearchScreen().search().sendKeys("Atlas");
    onSearchScreen().item("Atlas LV-3B").swipeDownOn().click();
    onArticleScreen().articleTitle().should(allOf(displayed(), text("Atlas LV-3B")));
}

В примере выше мы открываем главную страницу Wikipedia, щёлкаем по поисковой строке, вводим текст Atlas, далее прокручиваем до элемента списка со значением Atlas LV-3B и переходим в его представление. Последняя строчка проверяет, что заголовок отображается и содержит требуемое значение.

Listener

Логирование событий возможно реализовать с помощью специального листенера (интерфейс Listener). Каждый метод при вызове имеет четыре события: Before, Pass, Fail. After.



Используя данный интерфейс, можно организовать отчётность. Ниже представлен пример Allure Listener, который можно найти по ссылке.



Далее подключаем слушатель при инициализации класса Atlas.

atlas = new Atlas(new WebDriverConfiguration(driver)).listener(new AllureListener());


Вышеуказанным способом возможно создать listener для различных систем репортинга (например, для ReportPortal).

Подключение



Для автоматизации UI web достаточно прописать зависимость atlas-webdriver и указать последнюю актуальную версию (на момент написания этого текста актуальна версия 1.6.0).

Maven:
 <dependency>
    <groupId>io.qameta.atlas</groupId>
    <artifactId>atlas-webdriver</artifactId>
    <version>${atlas.version}</version>
</dependency>


Gradle:
dependencies { сompile 'io.qameta.atlas:atlas-webdriver:1.+' }


Аналогичным образом поступаем, если требуется автоматизировать UI Mobile.

Maven:
<dependency>
    <groupId>io.qameta.atlas</groupId>
    <artifactId>atlas-appium</artifactId>
    <version>${atlas.version}</version>
</dependency>


Gradle:
dependencies { сompile 'io.qameta.atlas:atlas-appium:1.+' }


Использование



После подключения зависимости в свой проект необходимо инициализировать инстанс класса Atlas.

@Before
public void startDriver() {
    driver = new ChromeDriver();
    atlas = new Atlas(new WebDriverConfiguration(driver));
}

В конструктор Atlas передаем инстанс конфигураци, а также драйвер.

На текущий момент есть две конфигурации: WebDriverConfiguration и AppiumDriverConfiguration. Каждая конфигурация содержит определенные расширения по умолчанию.

Далее определим метод, который будет создавать все PageObject.

private <T extends WebPage> T onPage(Class<T> page) {
    return atlas.create(driver, page);
}


Пример простенького тестового сценария:

@Test
public void simpleTest()  {
    onPage(MainPage.class).open("https://github.com");
    onPage(MainPage.class).header().searchInput().sendKeys("Atlas");
    onPage(MainPage.class).header().searchInput().submit();
}

Открываем сайт, обращаемся к слою header, в нём ищем текстовое поле (search input), вводим текст и нажимаем ввод.

Итоги


В заключение хочу отметить, что Atlas — это гибкий инструмент с большими возможностями. Его можно настроить под конкретный тестовый проект так, как удобно вашей команде и вам. Заняться разработкой кроссплатформенных тестов и т.д.

Доступны видеозаписи докладов о нём с конференций Heisenbug, Selenium Camp и Nexign QA Meetup. Есть Telegram-чат @atlashelp.

С помощью данного инструмента вы сможете сократить значительное количество строк кода (проверено на проектах таких компаний, как Яндекс, СберТех и Тинькофф).

Комментарии (13)


  1. cashby
    29.11.2019 15:56
    +1

    Выглядит классно, и идеи интересные.
    Получилась эдакая реализация того, что в руби делается через модули. Конечно, некоторые моменты достаточно спорны (получилась-то по сути композиция через наследование интерфейсов), но все равно зачетно.


    1. dopefish_hunters Автор
      02.12.2019 12:06

      Спасибо за обратную связь. А какие спорные моменты вы нашли? Распишите, пожалуйста.


  1. DSLow
    29.11.2019 20:08

    Привет. Спасибо за статью Артему и автору поста за перевод (если это разные люди), я надеюсь он где-то тут будет и сможет прочитать этот коммент.

    Не совсем уверен, что я уловил все изменения, которые вы добавили по сравнению с прошлым HtmlElements, на который я когда ориентировался по репозиторию Артема Ерошенко webtests-example (которому уже почти 5 лет).
    Вы добавили к нему еще мобилку, листенер и кое-какие фичи с селениде.

    Есть пара вопросов:
    1) Статья появилась сегодня (оригинал 7 ноября, что впринципе то же самое), а презентации Атласа датируются аж год назад. Были какие-то кардинальные изменения с того времени или почему статья выходит только сейчас?
    2) Вроде как сам Артем Е. говорил, что на некоторых своих или других проектах использует selenide, как ваши ретраи, ожидания и условия совмещаются с ним? Или от чего-то в «идеальном» подходе проектирования проекта отказываетесь?
    3) Есть разница какой Listener подключать? Который сейчас используется с аллюром — это обычно TestListenerAdapter + например, листенер степов io.qameta.allure.model.StepResult; Или лучше сразу нативный Atlas'а?
    4) И касательно параметризации элементов, наверное более личный интерес:
    Если на странице чередуются различные элементы и раз через раз это инпуты, селекторы со списками, чекбоксы и т.д. — не вызывает ли это какой-то фрустрации когда смотришь на описание локаторов в классе? Т.е. обычно это описывается «как есть» на странице сверху-вниз, но в случае с использованием Атласа это будет:

    @FindBy("//div[text()='{{ text }}']/input")
    AtlasWebElement input(@Param("text") String text);
    
    @FindBy("xpath")
    AtlasWebElement selectorField(@Param("idk") String text);
    
    @FindBy("xpath")
    ElementsCollection<selectorDropdownList> selectorsList();
    
    @FindBy("xpath")
    AtlasWebElement randomElement();
    


    И заполнение страницы будет как:
    public void fillAnket() {
     onPage().input("Заголовок").sendKeys("Что-то");
     onPage().selectorField().click();
     onPage().selectorsList().get(0).click();
     onPage().input("Опять инпут").sendKeys(" ");
     onPage().randomElement().getAttribute("");
    }
    

    Т.е. простота бектрекинга теряется, нет? Что уже описано, что нет становится не так очевидно. И какие-то элементы возможно будет стандартизировать под один формат параметризации, а какие-то нет на одной и той же странице. Если элемент фронтом описан «криво-косо» (часто с таким сталкиваюсь).

    И есть может быть какие-то проекты как всё это вместе может выглядеть?


    1. dopefish_hunters Автор
      02.12.2019 12:27
      +1

      Привет!

      Отвечаю на вопросы (оригинал и перевод, делал один человек).

      1. Кардинальных перемен не было, добавился новый функционал: поддержка работы с Appium, исправлены баги, добавлены расширения. Весь перечень изменений, можно найти здесь.

      2. Если правильно понял вопрос – возможно ли использовать selenide и atlas вместе? – ответ не пробовал. Можете поисследовать, наверное получится интересный гибрид.

      3. Лучше сразу нативный, на который идет ссылка в статье.

      4. Скорее поначалу будет не привычно, но со временем вы почувствуйте разницу. Например: у вас будет десять элементов на весь проект, чем 100 параметризированных. Понятно, текст может поменяться, но так у вас будет все в одном месте в тесте, чем ходить и править по каждому PO. Нужно попробовать, дальше сделаете вывод — нравится или нет.

      5. “Если элемент фронтом описан «криво-косо» (часто с таким сталкиваюсь)” – сталкивался, приходилось делать костыли и договариваться с разработкой на нормальный элемент.

      6. Примеры, можно найти в исходниках.


  1. TheKnight
    02.12.2019 13:11

    Глобальные параметры из HtmlElements 2.0 уже поддержали?


    1. dopefish_hunters Автор
      15.12.2019 00:03

      Привет. Еще не делали.


      1. TheKnight
        15.12.2019 18:11

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


  1. tundrawolf_kiba
    18.12.2019 17:34

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

            onMainPage().menu().menuLink("Форум").click();
            onMainPage().menu().menuLink("Сообщество").click();
            onMainPage().menu().menuLink("English").click(); 

    При этом если между ними поставить задержку хотя бы 0,5 секунды между каждым — все отрабатывает нормально. Вот и хочу учтонить — это я где-то что-то не учел или это баг?


    1. tundrawolf_kiba
      18.12.2019 19:32

      Прошу прощения, оказывается проблема где-то в ChromeDriver, на FirefoxDriver все нормально отрабатывает.


  1. sanyaya1984
    30.12.2019 19:04

    Привет! для .NET такого пока нет?
    Видимо можно просто повторить какие-то идеи в своих тестах.

    Считается, что литералы вообще и локаторы (индентификаторы контролов) не должны повторяться. А эта идея с одним input и локаторами в виде параметров заставляет их повторять получается?


    1. TheKnight
      31.12.2019 00:17

      Считается, что литералы вообще и локаторы (индентификаторы контролов) не должны повторяться.
      Кем считается? О_О

      Из моей практики, параметризованные локаторы очень нужны для обработки ситуаций, где у вас есть повторяющиеся элементы.
      Примером этого может служить список файлов, где каждый элемент идентифицируется двумя признаками — идентификатором контрола файла и конкретным именем файла.
      @FindBy("//[id='file' and text='{{ fileName }}']")

      Если вы будете прописывать каждый возможный файл в виде отдельного элемента без параметризации — тесты станут более сложно поддерживаемыми.
      Кроме того, в некоторых тестах есть смысл генерить литералы случайным образом и здесь параметризация выручает еще сильнее.
      Опять же в моей практике встречалась ситуация, где мне требовалось подтянуть часть локатора из какого то внешнего источника — например, имя пользователя на странице приветствия.

      На мой взгляд, автор загнался и начал унифицировать ту часть Page Object, которую унифицировать не стоило бы.
      P.S.: Я не настоящий автоматизатор, так, погулять вышел, ногами сильно не бейте. Atlas не использовал, использовал более старые HtmlElements 2.0 от того же автора.


      1. sanyaya1984
        31.12.2019 09:31

        Привет!

        Кем считается? О_О


        Ну вот принцип «Не повтряйтесь» — если использовать один локатор несколько раз, то и менять его придется в нескольких местах.


  1. jreznot
    30.12.2019 21:21
    +1

    Кстати, для Atlas есть IntelliJ IDEA плагин, позволяющий писать селекторы с автодополнением, подсветкой и валидацией: plugins.jetbrains.com/plugin/13267-qa-lithium