XPath универсальный C# шаг работы с элементами

CSS

Client
Регистрация
22.05.2010
Сообщения
1 327
Реакции
665
Баллы
113
Данный код позволяет очень удобно работать с xpath из C# шага, задача кода - замена стандартных "кубиков" на более продвинутый метод поиска элементов - xpath, при этом в удобной обёртке.

Технология xpath очень гибкая, в частности одно из популярных применений (которые сложно сделать без неё стандартными средствами ZP) - поиск элементов методом "найти элемент, а в нём другой элемент, а в нём третий..." то есть вложенный поиск когда невозможно найти уникальный признак по которому можно сразу идентифицировать элемент.

Также можно например найти соседний элемент (который имеет уникальный признак), и от него "оттолкнуться" чтобы найти нужный нам элемент (который не имеет уникального признак поиска). В общем простор для поиска элементов просто широчайший.

Как работает:
- Вставляется функция в блок "общий код", это и есть обработчик
- Из C# шага вызывается код "клиента" который выполняет что вам надо (кликнуть, взять что-то, установить значение, в общем классический get|set|rise)

В чём основное удобство, вот так выглядит код в C# шаге:
C#:
Развернуть Свернуть Копировать
string xpath_exp = "//select[@id='lang-chooser']/option[@selected='selected'][contains(.,'United States')]";
string action_ev = "rise|focus";
string set_action ="";

return CommonCode.FindElementAndExecuteAction(instance, xpath_exp, action_ev, set_action);

Входные параметры:
xpath_exp - выражение xpath для нахождения элемента
action_ev - что делаем с элементом, возможные варианты:

аргумент 1:
- get - взять значение
- set - установить значение
- rise - выполнить JS event

аргумент 2: то что делаем с элементом, например комплексные варианты с примерами:
get|width - взять ширину найденого элемента
set|value - установить значение в элемент, например в текстовое поле нужный вам текст
rise|click - клик по элементу

В общем здесь всё то же самое что и в конструкторе действий.

set_action - используется лишь в случаях когда делается set (например set|value или там set|style), то есть установка значения, в этом случае пишется указанный текст, то есть то что будет прописано в значении.

Также реализованы следующие фишки:
rise|scroll - промотает до нужного элемента
set|selecteditems - выбор выпадающего меню, при этом поддерживает Regex:ваш_текст

Прочая информация:
- Код работает начиная с версии ZP 5.8.0.0
- Используется тип эмуляции заданный в проекте (в настройках, либо заданный вами в шаге)
- Сейчас пока что нет возможности распознавать капчу таким образом (не сделан get|captcha)
- Если элемент не будет найден, то этот шаг выйдет с ошибкой
- Для составления xpath выражений удобно использовать расширение браузера FireFox под названием Firepath (ставится как дополнение к дополнению Firebug)
- Код написал darkdiver по моей просьбе, за что ему низкий поклон и большая благодарность
- Уроки по xpath можно найти здесь http://zvon.org/xxl/XPathTutorial/Output_rus/example1.html

Пример использования кода во вложениях.
 

Вложения

Последнее редактирование:
Спасибо. так, глядишь до XPath скоро доберемся в стандартных экшенах
 
какие события еще поддерживаются и можно ли передавать координаты события если они должны быть?
cуществует ли FindElementAndExecuteAction для более одного элемента?
Как быть с регулярным выражением?
Какие исключения вызывает этот метод если обьект не найден?
Какой практический смысл в этом методе если есть куча других?
 
Последнее редактирование:
какие события еще поддерживаются
Любые как и в конструкторе действий, set|get - value, innertext, outerhtml и.т.д., rise - focus, click, blur, onchange - и.т.д.
можно ли передавать координаты события если они должны быть?
Имеется ввиду например работа с drag & drop? Если да, то сейчас не поддерживается
cуществует ли FindElementAndExecuteAction для более одного элемента?
В C# не разбираюсь, на первый взгляд это просто название функции самописной, которая вызывается из общего кода, соответственно туда можно что-то своё добавить. Прояснить может darkdiver.
Как быть с регулярным выражением?
В данной реализации они не предусмотрены, поэтому придётся писать замену на xpath. Честно говоря с тех пор как начал использовать xpath - не возникало ни разу необходимости возвращаться на регулярки, они просто стали не нужны абсолютно в этом классе задач. Может быть Mozilla выкатит когда-нибудь версию xpath 2.0 в которой поддерживаются регулярки.
Какие исключения вызывает этот метод если обьект не найден?
Под объёктом имеется ввиду элемент? Если да, то в зависимости от причины почему не найден сработают исключения:
- вкладка не найдена
- элемент не найден
Какой практический смысл в этом методе если есть куча других?
Если кратко то стояла задача использовать мощь xpath и простоту кубиков (стандартных шагов поиска элементов), соответственно смысл именно в этом - просто и удобно использовать гибкий инструмент в (почти) привычном виде.
 
  • Спасибо
Реакции: budora
and where is the library for CommonCode?
 
and where is the library for CommonCode?
In attach template, then function:
exec_action.png
 
When I try your template it tells me 'no reference to CommonCode'. Maybe because I'm using 5.7.1 version?
 
  • Спасибо
Реакции: CSS
  • Спасибо
Реакции: bigcajones
Хотел бы немного дополнить как можно собирать все элементы со страницы т.к. в оригинальном примере вы можете получить только 1-й элемент на странице, к примеру вам необходимо собрать все ссылки со страницы используя XPath, а не только первую, так вот для начала вам необходимо добавить в общий код следующую функцию:
C#:
Развернуть Свернуть Копировать
public static HtmlElement[] FindElementsByXpath(Instance instance, string xpath)
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
           if (tab.IsBusy) tab.WaitDownloading();
      
           // find html element by tag
           var hes = tab.FindElementsByXPath(xpath);
           if (hes.IsVoid) throw new Exception("элементы не найдены");
            return hes.Elements;
        }
Теперь используя вот такую конструкцию в коде, вы можете подсчитать общее кол-во элементов на странице по критериям поиска:
C#:
Развернуть Свернуть Копировать
var linksCollection = CommonCode.FindElementsByXpath(instance, xpath_exp);
return linksCollection.Length;
А вот теперь и пример как можно собрать все элементы со страницы и записать их в список, в моем случае это были ссылки:
C#:
Развернуть Свернуть Копировать
var links = project.Lists["Links"];
var xpath_exp = ".//*[@id='123']/a";
var action_ev = "get|href";
var set_action ="";
var linksCollectionLength = CommonCode.FindElementsByXpath(instance, xpath_exp);
for (int i = 0; i < linksCollectionLength.Length; i++)
{
var linksCollection = CommonCode.FindElementAndExecuteAction(instance, xpath_exp, action_ev, set_action);
links.Add(linksCollection);
}
Хочу выразить благодарность darkdiver - т.к. именно он помог мне разобраться как это все осуществить! Советую использовать данную возможность поиска т.к. она реально облегчает этот процесс, а в некоторых случаях без неё просто не реально добраться до нужного элемента!
 
Последнее редактирование модератором:
olymp, благодарю за код!

Ранее разговаривал с Дайвером, говорит слишком высокий overhead при таком подходе, поэтому лично я отказался на тот момент. Метод хорош сам по себе, только его бы упростить путём выноса в общий код этих циклов, оставив в C# шаге "заказчика" только интерфейс запроса (как сейчас сделано: запрос в шаге, а обработчик в общем коде). Придумать только нужно некий абстрактный интерфейс вроде "save|список_куда_сохраняем" и объединить его с существующим в единое целое.

Думаю кому-то понадобится это дело (как вам понадобилось по циклу перебирать), возможно даже и я это буду - тот доработает и заделится решением это красиво и удобно реализующим. Так и сдвинется дело вперёд.
 
С тех пор как познакомился с xpath понял что регулярками хуже тянуть то, что необходимо, но и они не лишены необходимости в использовании, поэтому плюсую за xpath в самой зенке вообще вещь просто офигенная.
 
Я и мои коллеги по ZP очень ждем xpath в стандартном функционале (в кубиках, конструкторе действий)
 
Да важный момент для тек кто не так сильно знаком с СИ
добавте директивы юзинг и во вкладке (общий код )
надеюсь директивы вы умеете добавлять.
после // Insert your code here
вставте этот код

Код:
Развернуть Свернуть Копировать
        public static HtmlElement FindByXpath(Instance instance, string xpath)
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
           if (tab.IsBusy) tab.WaitDownloading();
      
           // find html element by tag
           HtmlElement he = tab.FindElementByXPath(xpath, 0);
           if (he.IsVoid || he.IsNull) throw new Exception("элемент не найден");
            return he;
        }
    
        private static void WaitDownloading(Tab tab)
        {
            if (tab.IsBusy) tab.WaitDownloading();
        }
    
        public static string FindElementAndExecuteAction(Instance instance, string xpath, string action, string attrValue="")
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
        
            var he = CommonCode.FindByXpath(instance, xpath);
            if (!action.Contains("|")) throw new ArgumentException ("неверный формат action");
            var tmp = action.ToLower().Split(new [] {'|'}, 2);
            var mainAction = tmp[0];
            var subAction = tmp[1];
        
            switch (mainAction)
            {
                case "get":
                    switch(subAction)
                    {
                        case "value":
                            string attributeValue = he.GetValue();
                            if (string.IsNullOrEmpty(attributeValue))
                                throw new Exception("Значение пустое");
                            return attributeValue;
                        case "selecteditems":
                            string selectedItems = he.GetSelectedItems();
                            if (string.IsNullOrEmpty(selectedItems))
                                throw new Exception("Значение selectedItems пустое");
                            return selectedItems;
                        default:
                            string attributeText = he.GetAttribute(subAction);
                            if (string.IsNullOrEmpty(attributeText))
                                throw new Exception(string.Format("Атрибут {0} пустой", subAction));
                            return attributeText;
                    }
                case "rise":
                    switch(subAction)
                    {
                        case "scroll":
                            he.ScrollIntoView();
                            break;
                        default:
                            he.RiseEvent(subAction, instance.EmulationLevel);
                            break;
                    }
                    WaitDownloading(tab);
                    return "ok";
                case "set":
                    switch(subAction)
                    {
                        case "value":
                            he.SetValue(attrValue, instance.EmulationLevel, he.TagName=="select");
                            break;
                        case "selecteditems":
                            const string selecteditems_regexp = "Regex:";
                            if (attrValue.StartsWith(selecteditems_regexp))
                            {
                                attrValue = attrValue.Substring(selecteditems_regexp.Length, attrValue.Length - selecteditems_regexp.Length);
                                var regex = new Regex(attrValue);
                                var items = he.GetAttribute("items").Split(new [] { ";" }, StringSplitOptions.RemoveEmptyEntries);
                                var matchedItems = string.Join(";", items.Where(item => regex.Match(item).Success));
                                he.SetAttribute("selecteditems", matchedItems); 
                            }
                            else
                            {
                                he.SetSelectedItems(attrValue);
                            }
                            break;
                        default:
                            he.SetAttribute(subAction, attrValue);
                            break;
                    }
                    WaitDownloading(tab);
                    return "ok";
                default:
                    throw new Exception ("неизвестное действие");
            }
            return "ok";
        }
 
Подскажите, а как сделать клик по ссылке на сайте со скроллом вниз\вверх? Ссылки находятся в списке, потом ее нужно найти на странице и если есть кликнуть.
И можно ли реализовать как-то скролл на паузе?
 
Последнее редактирование:
Ребят, помогите пожалуйста. Я новичок в C# но очень хочется иметь возможность использовать xpath для сбора элементов в список. Вроде делал все как сказал olymp Но у меня ошибки при компиляции кода проекта. Что то я не так сделал(

Проект приложил.
 

Вложения

Ребят, кто тоже плохо понимает что куда вставлять. Держите последний демо проект по xpath из него просто копируйте OwnCodeUsings http://screencast.com/t/EySbqxpRdg3J

Проект приложил.
 

Вложения

Последнее редактирование:
В список не собирает. Берет только первое значение но добавляет его в список столько раз, сколько встречается элементов в списке. Имя всегда одинаковое. Потестил на меню на тестовой странице.
Хотел бы немного дополнить как можно собирать все элементы со страницы т.к. в оригинальном примере вы можете получить только 1-й элемент на странице, к примеру вам необходимо собрать все ссылки со страницы используя XPath, а не только первую, так вот для начала вам необходимо добавить в общий код следующую функцию:
C#:
Развернуть Свернуть Копировать
public static HtmlElement[] FindElementsByXpath(Instance instance, string xpath)
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
           if (tab.IsBusy) tab.WaitDownloading();
      
           // find html element by tag
           var hes = tab.FindElementsByXPath(xpath);
           if (hes.IsVoid) throw new Exception("элементы не найдены");
            return hes.Elements;
        }
Теперь используя вот такую конструкцию в коде, вы можете подсчитать общее кол-во элементов на странице по критериям поиска:
C#:
Развернуть Свернуть Копировать
var linksCollection = CommonCode.FindElementsByXpath(instance, xpath_exp);
return linksCollection.Length;
А вот теперь и пример как можно собрать все элементы со страницы и записать их в список, в моем случае это были ссылки:
C#:
Развернуть Свернуть Копировать
var links = project.Lists["Links"];
var xpath_exp = ".//*[@id='123']/a";
var action_ev = "get|href";
var set_action ="";
var linksCollectionLength = CommonCode.FindElementsByXpath(instance, xpath_exp);
for (int i = 0; i < linksCollectionLength.Length; i++)
{
var linksCollection = CommonCode.FindElementAndExecuteAction(instance, xpath_exp, action_ev, set_action);
links.Add(linksCollection);
}
Хочу выразить благодарность darkdiver - т.к. именно он помог мне разобраться как это все осуществить! Советую использовать данную возможность поиска т.к. она реально облегчает этот процесс, а в некоторых случаях без неё просто не реально добраться до нужного элемента!


В список не собирает. Берет только первое значение но добавляет его в список столько раз, сколько встречается элементов в списке. Имя всегда одинаковое. Потестил на меню на тестовой странице. Возможно что то не то делаю.
 

Вложения

@mig-z, воспользуйтесь этим снипетом:
C#:
Развернуть Свернуть Копировать
Tab tab = instance.MainTab;
if (tab.IsBusy) tab.WaitDownloading();
// get document
Document doc = tab.MainDocument;

// find element by attribute
HtmlElementCollection heCol = doc.FindElementsByXPath(@"//a");

//вытаскиваем атрибут href из каждого элемента
var data = heCol.AttributesToString("href").Split(new string[] {Environment.NewLine},0).ToList();

//закидываем всё в список
project.Lists["Список 1"].AddRange(data);
project.SendInfoToLog("добавлено ["+data.Count+"] элементов");
Позже объединю это дело с кодом из первого поста.

Снипет ищет все совпадения по XPath (все теги <a>), вытаскивает у каждого совпадения свой аттрибут ("href"), и сохраняет их все в список ("Список 1").
 
как я понимаю XPath работает только с табом то есть с реальной страницей , нельзя его использовать в гет запросах(
 
Ты можешь полученный html из get установить в активную вкладку, и дальше работать как обычно
Код:
Развернуть Свернуть Копировать
var x = project.Variables["res"].Value; // переменная x с html кодом после гет запроса
instance.ActiveTab.MainDocument.Body.SetAttribute("innerHtml", x);

Или можно сторонние библиотеки подключить, вот наверно хорошая штука =)
http://habrahabr.ru/post/112325/
 
@mig-z, воспользуйтесь этим снипетом:
C#:
Развернуть Свернуть Копировать
Tab tab = instance.MainTab;
if (tab.IsBusy) tab.WaitDownloading();
// get document
Document doc = tab.MainDocument;

// find element by attribute
HtmlElementCollection heCol = doc.FindElementsByXPath(@"//a");

//вытаскиваем атрибут href из каждого элемента
var data = heCol.AttributesToString("href").Split(new string[] {Environment.NewLine},0).ToList();

//закидываем всё в список
project.Lists["Список 1"].AddRange(data);
project.SendInfoToLog("добавлено ["+data.Count+"] элементов");
Позже объединю это дело с кодом из первого поста.

Снипет ищет все совпадения по XPath (все теги <a>), вытаскивает у каждого совпадения свой аттрибут ("href"), и сохраняет их все в список ("Список 1").


Спасибо!

Доработал ваш пример, для тех кто хочет вытаскивать значения innertext. На примере тестовой страницы уроков зенно.
Код:
Развернуть Свернуть Копировать
Tab tab = instance.MainTab;
if (tab.IsBusy) tab.WaitDownloading();
// get document
Document doc = tab.MainDocument;
// find element by attribute
HtmlElementCollection heCol = doc.FindElementsByXPath(@"//div[@id='nav']/ul/li/a");
//вытаскиваем атрибут innertext из каждого элемента
var data = heCol.AttributesToString("innertext").Split(new string[] {Environment.NewLine},0).ToList();

//закидываем всё в список
project.Lists["Список 1"].AddRange(data);
project.SendInfoToLog("добавлено ["+data.Count+"] элементов");
 
  • Спасибо
Реакции: zennoX
Имеем на странице 100 однотипных DIV с . Внутри каждого ещё несколько DIV с разной степенью вложенности. В одном DIV нужная мне ссылка, в другом DIV нужный мне текст, третий DIV может быть, а может и нет и внутри него картинки, которые также могут быть, а может и нет. Все DIV можно идентифицировать по статичным outerhtml.
Мне нужно составить таблицу с 4 столбцами: порядковый номер DIV (от 1 до 100), ссылка, текст, список урлов картинок.
Поможет ли мне в этом xpath или есть способ попроще?
Если поможет, то как его правильно организовать?
 
Почему-то get|innerhtml возвращает постоянно пустоту. Хотя там есть контент.
Такое ощущение что GetAttribute не умеет всех атрибутов возвращать.
С src для img тоже самое - пустой возвращает.
 
Последнее редактирование:
Почему-то get|innerhtml возвращает постоянно пустоту. Хотя там есть контент.
Такое ощущение что GetAttribute не умеет всех атрибутов возвращать.
С src для img тоже самое - пустой возвращает.
умеет. где-то ошибаетесь с идентификацией элемента
 
Было бы очень круто, если бы в след. версиях Зенно появилась фича по упрощению поиска и конструированию структуры Xpath. По типу как по правому клику "Исследовать элемент", а ниже "Получить путь к элементу в xpath".
Ну и набор заготовок строк для разных целей, а их там полсотни.
 
Было бы очень круто, если бы в след. версиях Зенно появилась фича по упрощению поиска и конструированию структуры Xpath. По типу как по правому клику "Исследовать элемент", а ниже "Получить путь к элементу в xpath".
Ну и набор заготовок строк для разных целей, а их там полсотни.
планируем
 
Наверняка простой вопросец, но бьюсь уже час:
Небходимо в xpath_exp использовать переменную из проекта, например вот здесь:

string xpath_exp = "//*[@id='chat_99350594']/div[2]/div[2]/div/div/textarea";

вместо "chat_99350594" нужно использовать переменную "test_var".

в редактор кода она вставляется(по правому клику) как project.Variables["test_var"].Value, но код не работает.
Эти кавычки меня доведут
 
C#:
Развернуть Свернуть Копировать
string xpath_exp = "//*[@id='"+project.Variables["test_var"].Value+"_99350594']/div[2]/div[2]/div/div/textarea";
 
  • Спасибо
Реакции: ttimbaland1983 и IgorSush
Спасибо!:-)
 
Последнее редактирование:

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)