Selenium RC: Дружим с XPath

При работе с Selenium доступ к объектам осуществляется через локаторы - строки, идентифицирующие объект, над которым проводится то или иное действие. Наиболее удобными и наиболее быстрыми являются локаторы, определенные по ID объекта ( у каждого объекта на HTML странице может быть определен атрибут ID, причем он должен быть уникальным ). Ну уж если не определен ID, то как минимум для элементов форм есть атрибуты Name, через которые тоже достаточно удобно и просто работать. Но в общем случае, приходится работать с большим многообразием объектов, причем и действия приходится делать самые разнообразные. Например: 

  • на странице есть несколько полей с одинаковым атрибутом Name, но у разных форм и нужно работать с конкретным полем конкретной формы.
  • на странице множество объектов сходной структуры и их надо обработать одинаково (например, очистить все текстовые поля)
  • нужно обработать одинаковым образом все объекты, которые характеризуются определенным текстов некоторых дочерних объектов (например, мы знаем заголовки таблицы, а нужно сделать клик на ссылке, которая находится на том же уровне)

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

  1. Во-первых, данный способ задания местоположения объекта оперирует с фактическими HTML-тегами, что дает возможность формировать локаторы исходя из непосредственно HTML-кода страницы, который можно просмотреть.
  2. Во-вторых, есть возможность задать некоторую иерархию объектов, при этом пропустить варьируемые элементы (удобно, когда надо вычислить элемент внутри таблицы, не привязываясь к конкретным ячейкам).
  3. В-третьих, элемент можно задать используя как теги, так и определенные значения атрибутов, причем можно проверить на частичное соответствие (удобно, когда элемент уникально идентифицируется обработчиком некоторого события). 
  4. В-четвертых, в Selenium есть отдельный метод, который позволит нам узнать количество элементов, удовлетворяющих заданному XPath, а такде возможность использовать индексы, что позволяет выделить и перебирать целую коллекцию элементов.

А теперь перейдем к практической составляющей. Допустим, у нас есть набор различных графиков в виде bar-chart или pie-chart, причем при клике на каждый элемент происходит переход на некоторую страницу. Реализация в HTML подобного имеет вид:

{syntaxhighlighter brush: xml;fontsize: 100; first-line: 1; }<map> <area href="ref1"> <area href="ref2"> <area href="ref3"> ... <area href="ref1"> <img src="some_img.gif"> </map>{/syntaxhighlighter}

вот таких map-блоков произвольное количество. В каждом из этих блоков произвольное количество элементов area. Но везде есть ссылки и на эти area-объекты мы можем сделать клик. Итак, как можно организовать обработку всех активных областей всех диаграмм. Вначале, мы узнаем, сколько же всего этих диаграмм присутствует. Предположим, что у нас уже есть проинициализированный объект Selenium-a и мы уже на нужной странице. Соответственно, реализация имеет вид (далее все примеры приводятся с использованием синтаксиса Java, но по аналогии переносится на остальные языки, на которых реализован Selenium-клиент):

{syntaxhighlighter brush: java;fontsize: 100; first-line: 1; }int chartsCount = selenium.getXPathCount( "//map" ).intValue();{/syntaxhighlighter}

Теперь в цикле для каждой диаграммы мы вычислим количество активных областей, после чего поочередно сделаем клики на них ( в целях примера просто вызовем метод click для каждой активной области ). Реализация имеет вид:

{syntaxhighlighter brush: java;fontsize: 100; first-line: 1; }int chartsCount = selenium.getXPathCount( "//map" ).intValue(); for( int i = 1 ; i <= chartsCount ; i++ ){ int areasCount = selenium.getXPathCount( "//map[" + i + "]/area" ); for( int j = 1 ; j <= areasCount ; j++ ){ selenium.click( "//map[" + i + "]/area[" + j + "]" ); } }{/syntaxhighlighter}

Обратите внимание, что здесь мы уже оперировали с индексами. Например, XPath вида "//map[" + i + "]/area[" + j + "]" обращается к элементу map, который на данной странице следует под порядковым номером (индексом) i и идет обращение к j-му элементу area данного map.

Другой пример. Допустим, у нас есть набор ссылок, у которых в обработчике onclick находится один и тот же метод, но с разными параметрами. Подобное часто встречается в таблицах, когда для каждой строки есть определенный набор функциональных элементов, которые работают именно с этой строкой. Допустим, это всё элементы img с атрибутом src="some_icon.gif" и обработчик onclick вызывает функцию someAction, но с разными параметрами в зависимости от строки. Также, допустим, у каждого такого изображения есть значение атрибута alt, в котором указывается с каким элементом происходит действие. То есть, фактически HTML имеет вид:

{syntaxhighlighter brush: xml;fontsize: 100; first-line: 1; }<table> <tr> <td> <img src="some_icon.gif" onclick="someAction( 105 )" alt="Item 1"> </td> </tr> <tr> <td> <img src="some_icon.gif" onclick="someAction( 78 )" alt="Item 2"> </td> </tr> ................................................. <tr> <td> <img src="some_icon.gif" onclick="someAction( 923 )" alt="Item N"> </td> </tr> </table>{/syntaxhighlighter}

И допустим, нам надо получить список значений атрибута alt для всех этих изображений. Реализуется это по следующему алгоритму:

  • Получаем количество всех необходимых изображений
  • Перебираем каждое изображение и извлекаем его атрибут alt

Программная реализация имеет вид:

{syntaxhighlighter brush: java;fontsize: 100; first-line: 1; }int count = selenium.getXPathCount( "//img[@src='some_icon.gif' and contains( @onclick , 'someAction' ) ]" );

String [] titles = new String()[ count ];

for( int i = 1 ; i <= count ; i++ ){
titles[ i - 1 ] = selenium.getAttribute(“//img[@src=‘some_icon.gif’ and contains( @onclick , ‘someAction’ ) ][” + i + “]@alt” );
}{/syntaxhighlighter}

Обратите внимание, что в записи //img[@src=‘some_icon.gif’ and contains( @onclick , ‘someAction’ ) ][" + i + "] присутствует 2 выражения в квадратных скобках. Если в квадратных скобках находится число, то это выражение воспринимается как индекс. В противном случае, это выражение-фильтр по атрибутам.

В данных примерах была продемонстрирована возможность групповой обработки объектов схожей структуры, использую XPath в качестве локатора. Вышеприведенные примеры являются упрощенным вариантом тех задач, которые приходилось решать на практике, то есть эти примеры взяты не с потолка. Тем не менее, у подобной гибкости есть один недостаток - для больших страниц работа с XPath занимает достаточно ощутимое время, а также в разных браузерах XPath по времени работает по-разному. Поэтому, по возможности, с целью повышения возможности тестировать приложение, лучше все-таки задавать объектам ID и обращаться по ID, но тем не менее в ряде случаев можно выработать достаточно гибкое решение и XPath может в этом очень сильно помочь.