XPath для получения элементов между двумя указанными - покритикуйте, пожалуйста

Есть страница, на ней много div-ов, они все на одном уровне вложенности, sibling-и. Мне нужно выбрать группу элементов, которые находятся между двумя обязательно находящимися на странице div-ами (я эти div-ы нахожу по их текстовому содержимому). Группа элементов между двумя граничными div-ами может содержать произвольное количество div-ов, от 4 до 12 у меня.
Я написал такой XPath запрос, и он работает. В упрощённом виде выглядит так:
$x("//div[text() = 'MAYAK_START']/following-sibling::div[position() <= ( count(//div[text() = 'MAYAK_END']/preceding-sibling::*) - count(//div[text() = 'MAYAK_START']/preceding-sibling::*) ) ]")
здесь div-ы с текстом MAYAK_START и MAYAK_END это 2 граничных элемента (“маячки”). Я считаю количество элементов перед концом группы, отнимаю от него количество элементов перед началом группы и получаю количество элементов в группе. Потом отсчитываю от начала группы это количество элементов, и выбираю их.

Подскажите пожалуйста, насколько это приемлемый подход? Есть ли какие-то более подходящие альтернативы?
Можно было бы выбрать все элементы от начального и до конца, перебирать их в коде и остановиться тогда, когда попадётся элемент с тектсом хвостового элемента, но мне такой вариант кажется ещё более громоздким чем XPath предложенный выше.

Локаторы должны быть как можно более простыми. Городить 2х этажный XPath - самому себе усложнять жизнь при саппорте. Уже не говоря о том, что хардкодить текст в локаторах - еще большее зло.

На каком языке пишете? В Java 8 подобный финт можно провернуть очень просто.

2 лайка

пишу на python, но это не важно.

Если логика выборки элементов сложная, то эта сложность всё равно где-то будет - или в 2х этажном XPath, или в коде, который будет обрабатывать резульаты простого (простых) XPath. “Провернуть финт” - как я понимаю, это перенести сложность в код, но средствами языка так это красиво обернуть, чтобы я “ничего не заметил” и получил изящную языковую конструкцию?

За python ничего не могу сказать, но в Java это будет выглядеть следующим образом:

	public List<WebElement> filterElements(List<WebElement> elements, String firstToken, String lastToken) {
		return EntryStream.of(elements)
				.filterValues(e -> getText(e).equals(firstToken) || getText(e).equals(lastToken))
				.keys()
				.intervalMap((i, j) -> i >= 0 && j >= 0, (i, j) -> elements.subList(i, j + 1))
				.toFlatList(Function.identity());
	}

А дальше, закидывайте сюда абсолютно любой список: хоть div, хоть tr / td… Все, что угодно, из чего можно вычитать текст.

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

	public List<WebElement> filterElements(List<WebElement> elements, Predicate<WebElement> filter) {
		return EntryStream.of(elements)
				.filterValues(filter::test)
				.keys()
				.intervalMap((i, j) -> i >= 0 && j >= 0, (i, j) -> elements.subList(i, j + 1))
				.toFlatList(Function.identity());
	}

Ну и вызываем то, что душе угодно:

filterElements(findElements(locator), (WebElement e) -> getText(e).equals(fToken) || getText(e).equals(lToken));
filterElements(findElements(locator), (WebElement e) -> getAttribute(e, "value").equals(token));
filterElements(findElements(locator), WebElement::isSelected);

Сложный код? Нет. Много места занимает? Нет. Универсальный? Да. Изящный? Да.

Если такое же можно написать на python, вы получите гораздо больший профит, нежели в случае с предложенным XPath.

2 лайка

если 7 Java все то же самое(конечно не так красиво будет виглядеть) можно через Guava Collections2 замутить

ИМХО чересчур сложно. Если внезапно что-то поменяется в разметке страницы - адаптировать такой XPath будет безумно сложно и долго. Я бы скорее делал бы findAll с локатором, цепляющим что-то общее у всех этих div-ов. Если этого общего нет (хотя я уверен, что есть - это явно какая-то функциональная группа) - я бы попросил программера, ответственного за данную функциональность, это общее создать; самый простой способ - это добавить id типа functionalGroup%i% ко всем этим динамическим div-ам.

Недавно тоже столкнулся со схожей задачей.
Решил её вот так:

//div[text() = ‘MAYAK_END’]/preceding-sibling::*[preceding-sibling::div[text()=‘MAYAK_START’]]

Надеюсь, будет полезно :wink:

1 лайк

ай маладец (с)

//*[preceding::div[.=“MAYAK_START”]][following::div[.=“MAYAK_END”]]

1 лайк