Удаленка для jenkins+selenide+selenoid+allure+docker спецов на 2-3 часа в день. 100% remote! Присоединиться к проекту

“element is not attached to page document” при клике чекбокса

bdd
java
webdriver
selenium
Теги: #<Tag:0x00007fedc7b23268> #<Tag:0x00007fedc7b23128> #<Tag:0x00007fedc7b22f98> #<Tag:0x00007fedc7b22e58>

(Максим Деменко) #1

Добрый день, всем.

Есть такой метод, который перестал работать

	public void selectAllСheckboxes() {
		createOrEditMonitorForm.findElements(By.xpath(".//table//input")).
			forEach(e -> e.click());
	}

Тест падает с “element is not attached to page document” при клике второго чекбокса. Сама причина падения понятна - после нажатия первого чекбокса, страница перерендеривается и второй чекбокс просто не находит. Но как это исправить я пока не могу понять


(Maxim Andryushchenkov) #2

Сейчас вам тут начнут советовать Selenide) Но вы можете просто переписать функу так: сначала найдите кол-во чекбоксов, потом столько же раз в цикле найдите один чекбокс и кликните по нему. В Selenide это делается вроде автоматом


(Oleksandr Khotemskyi) #3

Привет, я как то ответил большим постом по этой проблеме - Общий алгоритм решения "element is not attached to page document"


(Максим Деменко) #4

спасибо за совет, так и переделал

	public void selectAllCheckboxes() {
		int monitorsAmount = createOrEditMonitorForm.findElements(By.xpath(".//table//tbody/tr")).size();
		for (int i = 1; i <= monitorsAmount; i++)
			createOrEditMonitorForm.find(By.xpath(".//tbody//tr[" + i + "]//input")).click();		
	}

(Maxim Andryushchenkov) #5

Ну вам бы еще вынести локаторы в yaml и дать названия им осознанные, было бы вообще отлично


(Alexandr D ) #6

А если использовать отложенную инициализацию, то такой проблемы вообще не будет.
И не придётся костылить какие-то циклы.


(Maxim Andryushchenkov) #7

Не согласен что это костыль. Это скорее “понимание происходящих процессов на странице”. Ну вот взяли вы например и захотели все чекбоксы нажать и что теперь в тупую нажимать все подряд и если этот метод не сработал то любой другой алгоритм это костыль? Нет. Мы понимаем что есть перерендер, что id уже другой. Взяли и переписали метод и все. А отложенная инициализация для пачки чекбоксов как у вас бы выглядела?


(Alexandr D ) #8

Делать отложенную инициализацию для пачки чек-боксов не надо, её надо делать для всех элементов.
Сейчас 99% страниц нормальных приложений почти после любого действия что-то меняют на лету, будете писать циклы на каждый такой случай? И как читать такой код, где стопицот циклов?

Любой цикл вносит кучу лишних строк в код. А чем больше строк - тем дольше и труднее этот код читается.

Из-за использования цикла нужно теперь думать, как избавиться от дублирования локаторов. Один костыль порождает другой.

А так, могло бы быть лаконично написано что-то типа (не Java, но там тоже всё это по аналогии делается же)

[FindBy(Id = "fullRejectComment", ParentElement = CdOrderModalBlockName)]
        [ElementTitle("Чек-боксы blahblah")]
        private AList<ACheckBox> _cbList;

где-то вверху страницы, и потом легко можно было бы пройтись по всем чек-боксам в одну строку:

_cbList.ForEach(cb => cb.Select());

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

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


(Maxim Andryushchenkov) #9

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

Ну да, можно и на машине ездить без знания как устроен двигатель


(Максим Деменко) #10

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


(Alexandr D ) #11

Сегодня в одном месте, завтра в двух, послезавтра в трёх. Можно сразу написать так, чтобы не пришлось в будущем переписывать.

Да и как-то удобнее, когда локаторы хранятся в одном месте, а не размазаны по всему коду в PO.

Субъективно, конечно.

У меня есть PO с 600-700 строками кода, и там всё очень удобно разложено по регионам. Не нужное всегда свёрнуто.


(Максим Деменко) #12

Я создал отдельную переменную для чекбоксов
@FindBy(xpath = “//*[@id=‘monitor-form’]//table//input”)
private ListWebElementFacade submonitorsCheckboxes;
Но метод все равно падает, с тем же эксепшином.
Что вы имеете ввиду под “отложенной инициализацией”?


(Alexandr D ) #13

Для этого нужно использовать хотя бы штатную PageFactory (для начала - потом придёт понимание что нужно писать своё)
Просто так эта магия не будет работать.

Подробнее тут https://github.com/SeleniumHQ/selenium/wiki/PageFactory
Ну или здесь http://toolsqa.com/selenium-webdriver/page-object-pattern-model-page-factory/


(Taras) #14

а в PageFactory не возникает StaleReference ?))


(Alexandr D ) #15

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


(Taras) #16

согласен … вообше дофига кто хейтит @FindBy - но если переписать initElements со своей реализацией декоратора - то вполне хорошее решение


(Alexandr D ) #17

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

Из полезных вариантов можно выделить:

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

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

Лично я использую первый вариант, второй редко пока где пригождался.

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

Ну и так далее, много полезного можно придумать :slight_smile: