Где хранить локаторы? Новое пришествие!!

Прошло уже столько времени, столько воды утекло и все же где хранить локаторы в Page Object ?
Армия автоматизаторов разбилась на 3 лагеря.

  1. Константы
  2. @FindBy
  3. Внешний файл

Вот мои размышления :

  1. Если хранить веб елементы то их может и не быть на странице и значит что тест может упасть по причине не описанной в ТС.
  2. Самый оптимальный
  3. Удобно но для каждого класса плодить файлы тоже не хотелось

А как свои локаторы храните ВЫ ?

Update: Где вы храните временные элементы?
Припустим : Зайти на сайт, перейти в список товаров, запомнить первый товар, вернуться на главную, проверить есть ли этот товар на домашней странице.
Вопрос: Где хранить название товара?

Можно хранить или локаторы (By), или тот же SelenideElement создание объекта которого не приводит непосредственно к поиску элемента

2 - просто лишний код в виде аннотации и работы с PageFactory

2 лайка

Фабрика с @FindBy это рудимент в Селениуме.

1 лайк

Update: Где вы храните временные элементы?
Припустим : Зайти на сайт, перейти в список товаров, запомнить первый товар, вернуться на главную, проверить есть ли этот товар на домашней странице.
Вопрос: Где хранить название товара?

Хороший вопрос. Тоже интересно.

Object goods for example

Чаще всего (всегда) локаторы нигде не хранятся. Прямо в коде используются.

driver.first(:css, 'div[class="ref-button"]').click

Псевдо пример теста про товар из списка и на главной.

# encoding: utf-8

require 'selenium-webdriver'

task_id = 'foo-42'

describe task_id do
  context :item_from_list_on_main do
    before :all do
      driver = Selenium::WebDriver.for :phantomjs
      begin
        driver.manage.timeouts.implicit_wait = 10
        driver.manage.window.resize_to 1920, 1080
        driver.get 'http://example.com/auth'
        
        # /auth
        # email
        driver.first(:css, 'input[name="email"]').send_keys 'foo@example.com'

        # /auth
        # password
        driver.first(:css, 'input[name="password"]').send_keys 'pa$$word'
        
        # /auth
        # submit
        driver.first(:css, 'input[type="submit"]').click

        # /
        # item
        @item_from_main = driver.first(:css, 'div[class="item"]').text

        # /items
        # list of items
        driver.first(:css, 'a[href="/items"]').click

        # /items
        # item_from_list  
        @item_from_list = driver.first(:css, 'div[class="item"]').text
      ensure
        driver.close
      end
    end
    it(:item) { expect(@item_from_main).to eq @item_from_main }
  end
end

А если нужно кликнуть на определенную кнопку в разных тестах?

Ничего страшного, если в двух разных тестах будет прописан одинаковый локатор.
В случае смены локатора (класс у кнопки поменялся), можно сделать поиск/замену в IDE по всему проекту с тестами.
Представь, если у тебя Single Page Application, где кнопок может быть сотня.
Не все сразу видны на странице.
Например, на главной странице есть список категорий, внутри каждой своя форма, внутри формы куча кнопок.
Как ты назовёшь переменную в ObjectPage для такой вложенной кнопки?

MAIN_CATEGORY_FOO_FORM_A_SUBFORM_B_BUTTON_SUBMIT: 'button[type="submit"]'

При этом сам локатор короткий лаконичный.
Плюс надо подгружать PageObject со списком всех локаторов в каждый тест.

И ещё пример. Как быть, когда одна кнопка может иметь разные локаторы, в зависимости от каких-либо условий?
В тесте А ты авторизован пользователем и кнопка имеет класс user-button.
В тест Б ты авторизован админом и эта же кнопка имеет класс admin-button.
Как быть с PageObject в таком случае?
В решении, которое я предлагаю, об этом думать не нужно.
В одном тесте используем статичный локатор с одним классом, а в другом тесте с другим.
Нет?

  1. Если у вас локатор повторяется хотя бы два раза в одном классе, то его можно вынести по принципу
    private By buttonLocator = By.cssSelector("cssSelector");

  2. Если у вас Angular приложение, то вы можете создавать Пейдж Классы под каждую страницу, даже если урл при этом не меняется. И даже если у вас не сингл пейдж приложение, то в пределах одной страница вы можете создавать отдельный классы для компонентов на странице. Кто вам сказал что Одна страница = Один Пейдж Класс?

  3. Подгружать все локаторы каждый тест не надо. Вы знаете что такое ленивая инициализация?

  4. Если одна и та же кнопка имеет разные локаторы при разных условиях, то да вам необходимо держать два локатора.

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

3 лайка

Я храню в файлах ресурсов (шарп). Довольно удобно. А при тестировании локализаций вообще вещь незаменимая.

Не вижу тэга какого-то конкретного инструментария (кроме webdriver), поэтому спрошу - а как с этим обстоит дело в codeception? Есть ли там аналог аннотаций @FindBy? Как в целом codeception предлагает оптимально в своих рамках хранить локаторы и работать с ними? @davert, расскажите, как codeception позиционирует себя в этом ключе?)

ИМХО 3-й способ - наиболее оптимален, поскольку позволяет не перекомпиливать тесты в случае изменения локаторов, а решать вопрос изменением ресурсного файла.

3 лайка

Я упоролся по ленивым элементам - пока нет обращения к элементу - поиск на странице не происходит. То же самое при вылетающем StaleReferenceException или NoSuchElementException - нужно просто попытатся переискать элемент, и только потом падать. Брат жив зависимость есть. Код намного проще.

Такой подход используется в ProtractorJS, Selenide, и куче других.

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

2 - ФайндБай - полный мрак и ужас. Монструозные абстракции просто для того чтобы обьявить элемент? Плюс проблемы когда хочешь возвращать свои типизированные элементы. Плюс проблемы когда хочешь искать от элемента к элементу. Не мой выбор.

3 - Внешний файл точно не вариант - очень замедляет разработку и рефакторинг - приходится свичится между файлами туда сюда что очень утомляет. Да и всеравно сам по себе не решение - так же приходится в классе использовать пейдж фактори или константы. Не мой выбор

Да я тоже понял что оптимальней всего хранить всё в “By”, но сейчас пошли еще дальше, написаны обьекты типа Button, Label, CheckBox, etc… в которые передаются By.
В итоге переменная выглядит как

private static final InputBox NAME_FIELD = controlFactory().inputBox(By.id("createGroupName"));

Храню тупо в ямле, а распределением локаторов по классам страниц занимается базовый класс (PageObject). Когда страница наследуется от base_page, она забирает свою часть локаторов. Ну и конечно это все xpath и держать некоторые длинные пути в коде - не кошерно.