NullPointerException при переборе вебэлементов

Здравствуйте!

Есть некий лист с элементами:

@FindBy(xpath = "//th[@class='z-listheader z-listheader-sort' and not(@style)]/div")
private List<WebElement> headersFilter;

Прохожу по листу в цикле:

(внешний цикл условно обозначен как итерация по массиву объектов)

for (Object object : objects) {

              for (int i = 0; i < headersFilter.size(); i++){
                        
                        // Беру текст из элемента в листе по его индексу
                        String s = headersFilter.get(i).getText();
                        
                        // Последующие операции с другими элементами в этой же форме
                         ...

Беру текст из элемента в листе по его индексу и сохраняю в строку s, затем проделываю некую операцию в этой же форме, в котрой находятся данные элементы

Первая итерация по внешнему циклу срабатывает нормально, но вторая итерация затыкается на строчке:

String s = headersFilter.get(i).getText();

Выдается NullPointerException

Получается , что после того как я в первой итерации проделываю какую-то операцию - эти элементы пересоздаются что ли и не успевают загрузиться?.

Вот тут headersFilter.get(i).getText(); я обращуюсь к еще не загруженному элементу?

… и кстати эта ошибка проявляется невсегда

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

Вообще говоря NPE тут может означать как и отсутствие headersFilter, так и null-элемент, взятый по i-му индексу. Тут следует прежде всего сузить поиск и определить точное местоположение null. Разбейте операцию взятия текста на 2: получение элемента и вызов getText(). Посмотрите в дебаге, кто из них бросает NPE. Дальше уже будет более понятно, в чем может быть проблема.

2 лайка

Внешний цикл - это перебор массив строк

for (String[] condition : conditions)

Первая строка в массиве - идентифицирует хедер, вторую строку надо ввести в инпут

@FindBy(xpath = "//th[@class='z-listheader z-listheader-sort' and not(@style)]/div")
private List<WebElement> headersFilter;
@FindBy(xpath = "//th[@class='z-auxheader']//input")
private List<WebElement> inputsFilter;


public void setFilter(String []... conditions) throws InterruptedException {

    resetFilter();
    expandFilter();

    WebElement input = null;

    for (String[] condition : conditions) {

        for (int i = 0; i < headersFilter.size(); i++){

            String s = headersFilter.get(i).getText();

            if (s.equals(condition[0])) {
                input = inputsFilter.get(headersFilter.indexOf(headersFilter.get(i)));
                input.sendKeys(condition[1]);
                break;
            }
        }
    }assert input != null;
    input.sendKeys(Keys.ENTER);
}

И в тесте используется так.

scrollerForm.setFilter(new String[][]{
             {"Код ТОФК", TOFK},
             {"Дата Описи", dateOfInventory}});

Выяснил, что вводя в инпуты значения в этой форме и нажимая на Enter - форма перересовывалась.
То есть, если в этом месте input.sendKeys(condition[1]); добавить Keys.ENTER (input.sendKeys(condition[1] + Keys.ENTER); то форма перересуется и уже headersFilter будет новый
Поэтому вынес нажатие на ENTER, когда уже вышли из цикла (получается, что Keys.ENTER нажимаем в последнем заполненном инпуте и весь фильтр применяется)

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

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

Динамической инициализацией :slight_smile:
Мне даже интерестно как такие детали решают другие автоматизаторы.

1 лайк

Чего и следовало ожидать. Шагать в цикле по динамическим компонентам - плохая практика. Вот вам и живой пример, где WebDriverWait + ExpectedConditions будет максимально эффективен.

И кстати, вы бы почитали о различных структурах данных в java. Зачем вам двумерный массив для задачи фильтрации? Исходя из предложенного формата, это чистейший Map. Но опять-таки, даже Map здесь лишний. Сам фильтр логичней было бы описать через enum, ввиду статичности его значений. Ну а компонент, в который необходимо ввести данные наверняка можно описать одним динамическим локатором, в который будет в runtime инжектиться какое-то значение.

П.С. Покажите, как выглядит ваш фильтр. Возможно это какой-нибудь Select2, где можно все сделать еще проще при помощи native api.

1 лайк

@ArtOfLife согласен насчет структуры данных. Вообще в таких случаях лучше использовать DTO классы, тогда код становится намного понятнее и устойчивее к изменениям.

1 лайк

Значения в шапке фильтра имеют разные значения от формы к форме.

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

После применения фильтра, что именно с фильтрами происходит? К ним добавляется style какой-то?
В любом случае после ввода значений надо нажать энтер или по другому подтвердить ввод. Сделай отдельной операцией внутри цикла безовсяких плюсов. Подтверждение операции всегда делай отдельной командой, а не составной с предыдущим заполнением/очищение/кликом/прочее.

Тут тебе нужно при каждой итерации фильтровать изменения на странице, типа если у вебэлемента появляется стайл какой-то, то пропускать его

Покажите замапленный с хэдером инпут в верстке. Хотя бы 1 пример. Чтобы было видно, как хэдер, так и инпут.

Хедер до ввода значения в инпут

Инпут до ввода в него значения

Хедер после ввода значения в инпут

Инпут после ввода в него значения

Вроде бы ничего не поменялось, кроме того, что в инпуте параметр value стал равен 555.
Кстати, видимо в xpath описывающем хедеры, я ошибочно вставил not(@style), потому что их со стайлом вообще нет.

Вот вот, у меня подобное было с коллекцией элементов, решение простое, фильтрация новых/изменённых элементов на странице после перезагрузки, так и уходит заветный NullPointerExeption

1 лайк

немного не понял…
сможете прояснить плиз?

Ну если у тебя новый элемент на странице применяет новое значение атрибута style или если применяется дополнительный класс - который визуально выделяет “Это новый/изменённый элемент на странице”

Например: есть локатор “div.addToCompare”, у которого есть только 1 класс: “addToCompare” и после клика и добавления товара к сравнению у этого локатора добавляется новый класс “added” или у тега появляется style: color=“33333f”
То в теле цикла сделай проверку чтобы, у элемента div.addToCompare есть стайл или класс тот, то не выполнять с ним манипуляций

1 лайк

И таким образом ты исключишь nullPointerExeption при перезагрузке страницы

1 лайк

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

Надо разбить:

  • Сначала перебираем элементы и запоминаем их уникальные признаки (например, текст).
  • Затем заново перебираем элементы, подставляя динамический локатор с этим текстом. То есть, каждый раз ищем элемент заново, и тогда уже с ним что-то делаем. (Просто не нужно фанатично привязываться к локаторам в аннотациях, их не всегда хватает)
1 лайк

так ведь при обращении к локатору, идентифицируемому аннотацией, ведь драйвер каждый раз ищет элемент. ?
Чем аннотации плохи?

К элементу с ленивой инициализацией ты не сможешь добавить дополнительный атрибут/приставку чтобы именно с этим дополнением искался элемент. Для этого делают типа так:
public ProductsLineBlock findProductByIDInTags(String id, String tagName) { return $(String.format("%s[data-id='%s']", tagName, id));
это самый динамичный локатор который у меня есть, но универсальный
Но и это можно обойти, можно искать на странице [data-id] атрибут и отфильтровать по нужному тегу или/и id(переменчивый), но так дольше будет выполняться операция - не 100 миллисекунд, а уже целые 1.5 секунды. Потому что при поиске одного нужного элемента происходит поиск всех элементов с соответствующим идентификатором и из этой коллекции берётся нулевой, поэтому и будет занимать дольше времени поиск элемента

1 лайк

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

Дополнительный минус ленивой инициализации, при первом обращении к объекту происходит более длинная задержка чем при последующих вызовах

1 лайк