Иногда возникает необходимость написания разнообразных костыльных парсеров или, по-новому, роботов RPA. Возникает такая необходимость, например, когда у нужного сервиса банально нет API. В этом случае есть два варианта написания робота (не рассматриваем другие языки программирования):
1. Использование эндпоинта(ов) приложения. В этом случае есть большой минус - мы используем недокументированный интерфейс, соответственно нам придется через инструменты разработчика браузера просматривать исходящие запросы и выводить закономерности для использования их в своем коде. Так-же интерфейс может быть очень сложный, и может потребоваться много времени для того что бы разобраться даже в элементарных действиях, не говоря уже о том что может использоваться ws протокол, который в 1с пока не поддерживается (на момент выпуска публикации).
2. Использование интерфейсов браузера, например COM интерфейс internet explorer, сервер инструментов разработчика того же хрома и т.д.
Предлагаю вариант решения для второго способа. Написание данного решения вдохновлено статьей Управление Selenium Web Driver из 1С (или парсинг из 1С по-взрослому).
Предварительные действия:
1. Установка webdriver, selenium grid.
Установку можно посмотреть в статье Управление Selenium Web Driver из 1С (или парсинг из 1С по-взрослому).
Если вам не нужен selenium grid, можно обойтись без установки Java, достаточно будет просто запустить веб драйвер на нужном порте.
2. Создаем в конфигурации (расширении) обработку.
Она нужна для использования в качестве инстанса браузера, для удобства использования и читабельности кода.
Обработка будет с двумя реквизитами: "ИдентификаторСессии" - Строка(128) и "НеЗакрыватьПриОшибке" - Булево. Код модуля объекта обработки:
// Документация по используемому интерфейсу - https://w3c.github.io/webdriver/
Перем Соединение, Команды;
Перем Клавиши Экспорт;
#Область ПрограммныйИнтерфейс
#Область Сессия
// Удаляет сессию, закрывает агент пользователя.
//
// Возвращаемое значение:
// Булево -
//
Функция Закрыть() Экспорт
Если ПустаяСтрока(ИдентификаторСессии) Тогда
Возврат Ложь;
КонецЕсли;
ВыполнитьКоманду(Команды.УДАЛИТЬ_СЕССИЮ);
ИдентификаторСессии = "";
Возврат Истина
КонецФункции
// Создает сеанс (сессию) веб драйвера и конечный REST узел
// на сервере для этой сессии.
//
// Параметры:
// ПортСервера - Число - Порт на котором запущен сервер (webdriver или slenium grid).
// Параметры - Соответствие - Параметры открытия, если не передано будут использованы
// параметры по умолчанию См. ПараметрыОткрытияБраузера().
//
// Возвращаемое значение:
// Булево -
//
Функция Открыть(ПортСервера = 4444, Параметры = Неопределено) Экспорт
Если НЕ ПустаяСтрока(ИдентификаторСессии) Тогда
ВызватьИсключение НСтр("ru = 'Экземпляр браузера уже открыт!'");
КонецЕсли;
Если Параметры = Неопределено Тогда
Параметры = ПараметрыОткрытияБраузера();
КонецЕсли;
Соединение = Новый HTTPСоединение("127.0.0.1", ПортСервера);
Ответ = ВыполнитьКоманду(Команды.ЗАПУСТИТЬ_СЕССИЮ, Параметры);
ИдентификаторСессии = Ответ["value"]["sessionId"];
Возврат Истина;
КонецФункции
#КонецОбласти
#Область Навигация
// Навигация. Переходит вперед по истории.
//
// Возвращаемое значение:
// Булево -
//
Функция Вперед() Экспорт
Возврат ВыполнитьКоманду(Команды.ВПЕРЕД);
КонецФункции
// Навигация. Переходит назад по истории.
//
// Возвращаемое значение:
// Булево -
//
Функция Назад() Экспорт
Возврат ВыполнитьКоманду(Команды.НАЗАД);
КонецФункции
// Обновляет текущую страницу.
//
// Возвращаемое значение:
// Булево -
//
Функция Обновить() Экспорт
Возврат ВыполнитьКоманду(Команды.ОБНОВИТЬ);
КонецФункции
// Заставляет агент пользователя изменить текущее расположение.
//
// Параметры:
// Адрес - Строка - новый URL.
//
// Возвращаемое значение:
// Булево -
//
Функция ПерейтиПоURL(Адрес) Экспорт
Параметры = Новый Соответствие;
Параметры["url"] = Адрес;
ВыполнитьКоманду(Команды.ПЕРЕЙТИ_ПО_URL, Параметры);
Возврат Истина;
КонецФункции
// Возвращает текущее расположение.
//
// Возвращаемое значение:
// Строка - Текущее расположение.
//
Функция ТекущийURL() Экспорт
Возврат ВыполнитьКоманду(Команды.ТЕКУЩИЙ_URL)["value"];
КонецФункции
// Возвращает заголовок текущей страницы.
//
// Возвращаемое значение:
// Строка - заголовок документа
//
Функция ЗаголовокДокумента() Экспорт
Возврат ВыполнитьКоманду(Команды.ЗАГОЛОВОК_ДОКУМЕНТА)["value"];
КонецФункции
#КонецОбласти
#Область КонтекстАгента
// Возвращает исходный HTML текущей страницы.
//
// Возвращаемое значение:
// Строка - код html.
//
Функция КодСтраницы() Экспорт
Возврат ВыполнитьКоманду(Команды.КОД_СТРАНИЦЫ)["value"];
КонецФункции
// Исполняет JS скрипт в текущем контексте агента пользователя.
// При необходимости передачи элементов в качестве аргументов скрипта,
// они должны быть десериализованы в соответствие с ключом "element-6066-11e4-a52e-4f735466cecf" и
// значением - идентификатором элемента (под идентификатором имеется ввиду - НЕ значение атрибута "id"!
// а идентификатор для элемента назначемый агентом клиента).
// Как пример - соответствие получаемое методом "НайтиЭлемент".
// Пример использования:
//
// Драйвер = Обработки.ДрайверChrome.Создать();
// Драйвер.Открыть();
// Драйвер.ПерейтиПоURL("http://localhost/");
//
// Аргументы = Новый Массив;
// Аргументы.Добавить(5);
//
// ТекстСкрипта = "alert(""Переданное число "" + arguments[0])";
//
// Драйвер.ВыполнитьСкрипт(ТекстСкрипта, Аргументы);
//
// Параметры:
// ТекстСкрипта - Строка - Скрипт для выполнения.
// Аргументы - Массив - Массив аргументов.
//
// Возвращаемое значение:
// Произвольное -
//
Функция ВыполнитьСкрипт(ТекстСкрипта, Аргументы = Неопределено) Экспорт
Параметры = Новый Соответствие;
Параметры["script"] = ТекстСкрипта;
Если Аргументы <> Неопределено Тогда
Параметры["args"] = Аргументы;
КонецЕсли;
Возврат ВыполнитьКоманду(Команды.ВЫПОЛНИТЬ_СКРИПТ, Параметры)["value"];
КонецФункции
#КонецОбласти
#Область Куки
// Возвращает массив описания куки.
//
// Возвращаемое значение:
// Массив - Массив куки.
//
Функция ПолучитьКуки() Экспорт
Возврат ВыполнитьКоманду(Команды.ПОЛУЧИТЬ_КУКИ)["value"];
КонецФункции
// Возвращает описание куки по имени.
//
// Параметры:
// Имя - Строка - Имя куки.
//
// Возвращаемое значение:
// Булево -
//
Функция ПолучитьКукиПоИмени(Имя) Экспорт
Параметры = Новый Соответствие;
Параметры["name"] = Имя;
Возврат ВыполнитьКоманду(Команды.ПОЛУЧИТЬ_КУКИ_ПО_ИМЕНИ, Параметры)["value"];
КонецФункции
// Добавляет текущие cookie.
// Важно что бы устанавливаемые куки были получены с того же домена,
// т.к. при попытке установить куки полученные с другого сайта может быть
// вызвано исключение.
//
// Параметры:
// Куки - Соответствие, Структура - Элемент массива cookie, полученного методом ПолучитьКуки().
// При ручном добавлении куки, коллекция может содержать ключи:
// *name - Строка - Имя cookie. (Обязательно)
// *value - Строка - Значение cookie. (Обязательно)
// *path - Строка - Путь к cookie. По умолчанию используется "/", если ключ отсутствует. (Не обязательно)
// *domain - Строка - Домен, которому виден файл cookie. По умолчанию используется URL-домен
// активного документа текущего контекста просмотра, если ключ отсутствует. (Не обязательно)
// *secure - Булево - Является ли файл cookie безопасным. По умолчанию имеет значение Ложь, если ключ отсутствует. (Не обязательно)
// *httpOnly - Булево - Используется данный cookie только по HTTP. По умолчанию имеет значение Ложь. (Не обязательно)
// *expiry - Число - Указывает когда срок действия файла cookie истекает, указывается в секундах с эпохи Unix. (Не обязательно)
// *sameSite - Строка - Применяется ли файл cookie к политике SameSite. По умолчанию установлено значение "None", если ключ отсутствует.
// Может принимать значения "Lax", "Strict" или "None". (Не обязательно)
//
// Возвращаемое значение:
// Булево -
//
Функция УстановитьКуки(Знач Куки) Экспорт
Параметры = Новый Соответствие;
Параметры["cookie"] = Куки;
ВыполнитьКоманду(Команды.ДОБАВИТЬ_КУКИ, Параметры);
Возврат Истина;
КонецФункции
// Удаляет все текущие куки.
//
// Возвращаемое значение:
// Булево -
//
Функция УдалитьКуки() Экспорт
ВыполнитьКоманду(Команды.УДАЛИТЬ_КУКИ);
Возврат Истина;
КонецФункции
// Удаляет текущие куки по имени.
//
// Возвращаемое значение:
// Булево -
//
Функция УдалитьКукиПоИмени(Имя) Экспорт
Параметры = Новый Соответствие;
Параметры["name"] = Имя;
ВыполнитьКоманду(Команды.УДАЛИТЬ_КУКИ_ПО_ИМЕНИ, Параметры);
Возврат Истина;
КонецФункции
#КонецОбласти
#Область Элементы
// Находит элемент в дереве DOM, если элемент не найден
// вызывает исключение.
//
// Параметры:
// СпособПоиска - Строка - Способ поиска элемента, принимает значения:
// "css selector" - Поиск по селектору CSS, Например "div.d-flex".
// "link text" - Поиск по тексту тега "a".
// "partial link text" - Поиск по части текста тега "a".
// "tag name" - Поиск по имени тега.
// "xpath" - Поиск по XPath.
// Значение - Строка - Селектор, например ".my_class" при поиске по css селектору, или xpath при поиске по xpath и т.д.
//
// Возвращаемое значение:
// Соответсвие -
//
Функция НайтиЭлемент(СпособПоиска, Значение) Экспорт
Параметры = Новый Соответствие;
Параметры["using"] = СпособПоиска;
Параметры["value"] = Значение;
Результат = ВыполнитьКоманду(Команды.НАЙТИ_ЭЛЕМЕНТ, Параметры)["value"];
Если ТипЗнч(Результат) = Тип("Массив") Тогда
Возврат Результат[0];
Иначе
Возврат Результат;
КонецЕсли;
КонецФункции
// Находит элементы в дереве DOM, если по селектору не найдено
// ни одного элемента, возвращает пустой массив.
//
// Параметры:
// СпособПоиска - Строка - Способ поиска элемента, принимает значения:
// "css selector" - Поиск по селектору CSS, Например "div.d-flex".
// "link text" - Поиск по тексту тега "a".
// "partial link text" - Поиск по части текста тега "a".
// "tag name" - Поиск по имени тега.
// "xpath" - Поиск по XPath.
// Значение - Строка - Селектор, например ".my_class" при поиске по css селектору, или xpath при поиске по xpath и т.д.
//
// Возвращаемое значение:
// Массив - массив структур с описанием элемента, см. ОписаниеЭлемента().
//
Функция НайтиЭлементы(СпособПоиска, Значение) Экспорт
Параметры = Новый Соответствие;
Параметры["using"] = СпособПоиска;
Параметры["value"] = Значение;
Возврат ВыполнитьКоманду(Команды.НАЙТИ_ЭЛЕМЕНТЫ, Параметры)["value"];
КонецФункции
// Находит элемент в дереве DOM от заданного элемента,
// если элемент не найден вызывает исключение.
//
// Параметры:
// ЭлементРодитель - Структура - см. ОписаниеЭлемента().
// СпособПоиска - Строка - Способ поиска элемента, принимает значения:
// "css selector" - Поиск по селектору CSS, Например "div.d-flex".
// "link text" - Поиск по тексту тега "a".
// "partial link text" - Поиск по части текста тега "a".
// "tag name" - Поиск по имени тега.
// "xpath" - Поиск по XPath.
// Значение - Строка - Селектор, например ".my_class" при поиске по css селектору, или xpath при поиске по xpath и т.д.
//
// Возвращаемое значение:
// Соответствие -
//
Функция НайтиЭлементОтЭлемента(Элемент, СпособПоиска, Значение) Экспорт
Параметры = Новый Соответствие;
Параметры["using"] = СпособПоиска;
Параметры["value"] = Значение;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Результат = ВыполнитьКоманду(Команды.НАЙТИ_ЭЛЕМЕНТ_ОТ_ЭЛЕМЕНТА, Параметры)["value"];
Если ТипЗнч(Результат) = Тип("Массив") Тогда
Возврат Результат[0];
Иначе
Возврат Результат;
КонецЕсли;
КонецФункции
// Находит элементы в дереве DOM от заданного элемента,
// если по селектору не найдено ни одного элемента, возвращает пустой массив.
//
// Параметры:
// ЭлементРодитель - Структура - см. ОписаниеЭлемента().
// СпособПоиска - Строка - Способ поиска элемента, принимает значения:
// "css selector" - Поиск по селектору CSS, Например "div.d-flex".
// "link text" - Поиск по тексту тега "a".
// "partial link text" - Поиск по части текста тега "a".
// "tag name" - Поиск по имени тега.
// "xpath" - Поиск по XPath.
// Значение - Строка - Селектор, например ".my_class" при поиске по css селектору, или xpath при поиске по xpath и т.д.
//
// Возвращаемое значение:
// Массв - массив структур с описанием элемента, см. ОписаниеЭлемента().
//
Функция НайтиЭлементыОтЭлемента(Элемент, СпособПоиска, Значение) Экспорт
Параметры = Новый Соответствие;
Параметры["using"] = СпособПоиска;
Параметры["value"] = Значение;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Возврат ВыполнитьКоманду(Команды.НАЙТИ_ЭЛЕМЕНТЫ_ОТ_ЭЛЕМЕНТА, Параметры)["value"];
КонецФункции
// Возвращает текущий активный элемент.
//
// Возвращаемое значение:
// Соответствие -
//
Функция ПолучитьАктивныйЭлемент() Экспорт
Возврат ВыполнитьКоманду(Команды.ПОЛУЧИТЬ_АКТИВНЫЙ_ЭЛЕМЕНТ)["value"];
КонецФункции
// Получает значение атрибута элемента.
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
// Атрибут - Строка - Имя атрибута
//
// Возвращаемое значение:
// Строка, неопределено - значение атрибута.
//
Функция ЗначениеАтрибутаЭлемета(Элемент, Атрибут) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Параметры["name"] = Атрибут;
Возврат ВыполнитьКоманду(Команды.ЗНАЧЕНИЕ_АТРИБУТА_ЭЛЕМЕНТА, Параметры)["value"];
КонецФункции
// Получает значение свойства элемента.
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
// Атрибут - Строка - Имя атрибута
//
// Возвращаемое значение:
// Строка, неопределено - значение атрибута.
//
Функция ЗначениеСвойстваЭлемета(Элемент, Свойство) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Параметры["name"] = Свойство;
Возврат ВыполнитьКоманду(Команды.ЗНАЧЕНИЕ_СВОЙСТВА_ЭЛЕМЕНТА, Параметры)["value"];
КонецФункции
// Получает значение свойства стиля элемента.
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
// Атрибут - Строка - Имя атрибута
//
// Возвращаемое значение:
// Строка, неопределено - значение атрибута.
//
Функция ЗначениеСвойстваСтиляЭлемета(Элемент, Свойство) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Параметры["property name"] = Свойство;
Возврат ВыполнитьКоманду(Команды.ЗНАЧЕНИЕ_СВОЙСТВА_СТИЛЯ_ЭЛЕМЕНТА, Параметры)["value"];
КонецФункции
// Получает текст элемента.
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент;
//
// Возвращаемое значение:
// Строка - текст
//
Функция ТекстЭлемента(Элемент) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Возврат ВыполнитьКоманду(Команды.ТЕКСТ_ЭЛЕМЕНТА, Параметры)["value"];
КонецФункции
// Возвращает имя тега элмента, например "DIV".
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
//
// Возвращаемое значение:
// Строка -
//
Функция ИмяТегаЭлемента(Элемент) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Возврат ВыполнитьКоманду(Команды.ИМЯ_ТЕГА_ЭЛЕМЕНТА, Параметры)["value"];
КонецФункции
// Возвращает позицию элемента, десериализованное в соответствие с ключами:
// "x" - Положение оси X верхнего левого угла веб-элемента относительно текущего элемента документа контекста просмотра в пикселях CSS.
// "y" - Положение по оси Y верхнего левого угла веб-элемента относительно текущего элемента документа контекста просмотра в пикселях CSS.
// "height" - Высота ограничивающего прямоугольника веб-элемента в пикселях CSS.
// "width" - Ширина ограничивающего прямоугольника веб-элемента в пикселях CSS.
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
//
// Возвращаемое значение:
// Соответствие -
//
Функция ПозицияЭлемента(Элемент) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Возврат ВыполнитьКоманду(Команды.ПОЗИЦИЯ_ЭЛЕМЕНТА, Параметры)["value"];
КонецФункции
#КонецОбласти
#Область ВзаимодействиеСЭлементами
// Эмулирует единичное нажатие ЛКМ по переданному элементу.
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
//
// Возвращаемое значение:
// Булево -
//
Функция Нажать(Элемент) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
ВыполнитьКоманду(Команды.НАЖАТЬ_НА_ЭЛЕМЕНТ, Параметры);
Возврат Истина;
КонецФункции
// Отправляет нажатия клавиш, можно отправлять как простые
// символы, так и специальные клавиши, например:
//
// Клавиши = СерверСелениум.ПолучитьКлавиши();
//
// Драйвер = Обработки.ДрайверChrome.Создать();
// Драйвер.Открыть();
// Драйвер.ПерейтиПоURL("http://localhost/");
//
// Элемент = Драйвер.НайтиЭлемент(Поиск.ПоXPATH, "//input[@placeholder=""Поиск...""]");
// Драйвер.ОтправитьКлавиши(Элемент, "Селениум" + Клавиши.ENTER);
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
// Значение - Строка - Строка, символы которой будут введены в поле.
//
// Возвращаемое значение:
// Булево -
//
Функция ОтправитьКлавиши(Элемент, Значение) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Параметры["text"] = Значение;
ВыполнитьКоманду(Команды.ОТПРАВИТЬ_КЛАВИШИ, Параметры);
Возврат Истина;
КонецФункции
// Очищает переданное поле ввода
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент.
//
// Возвращаемое значение:
// Булево -
//
Функция ОчиститьПоле(Элемент) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
ВыполнитьКоманду(Команды.ОЧИСТИТЬ_ЭЛЕМЕНТ, Параметры);
Возврат Истина;
КонецФункции
#КонецОбласти
#Область Скриншоты
// Создает скриншот текущей области.
//
// Возвращаемое значение:
// ДвоичныеДанные - Скриншот в формате png
//
Функция СделатьСкриншот() Экспорт
Возврат Base64Значение(ВыполнитьКоманду(Команды.СДЕЛАТЬ_СКРИНШОТ));
КонецФункции
// Создает скриншот переданного элемента.
//
// Возвращаемое значение:
// ДвоичныеДанные - Скриншот в формате png
//
Функция СделатьСкриншотЭлемента(Элемент) Экспорт
Параметры = Новый Соответствие;
Параметры["element id"] = ИдентификаторЭлемента(Элемент);
Возврат Base64Значение(ВыполнитьКоманду(Команды.СДЕЛАТЬ_СКРИНШОТ, Параметры));
КонецФункции
#КонецОбласти
// Ищет элементы по классу (значение атрибута "class")
//
// Параметры:
// Класс - Строка - Имя класса.
//
// Возвращаемое значение:
// Массив - Массив структур см. ОписаниеЭлемента()
//
Функция НайтиЭлементыПоКлассу(Класс) Экспорт
Параметры = Новый Соответствие;
Параметры["using"] = "css selector";
Параметры["value"] = "." + Класс;
Возврат ВыполнитьКоманду(Команды.НАЙТИ_ЭЛЕМЕНТЫ, Параметры)["value"];
КонецФункции
// Ищет элемент по идентификатору (значение атрибута "id")
//
// Параметры:
// Идентификатор - Строка -
//
// Возвращаемое значение:
// Соответствие - Десериализованный в соответствие, сериализованный веб элемент
//
Функция НайтиЭлементПоИдентификатору(Идентификатор) Экспорт
Параметры = Новый Соответствие;
Параметры["using"] = "css selector";
Параметры["value"] = "#" + Идентификатор;
Возврат ВыполнитьКоманду(Команды.НАЙТИ_ЭЛЕМЕНТ, Параметры)["value"];
КонецФункции
// Получает элемента-родителя переданного элемента
//
// Параметры:
// Элемент - Соответствие - Десериализованный в соответствие, сериализованный веб элемент;
//
// Возвращаемое значение:
// Соответствие - Десериализованный в соответствие, сериализованный веб элемент;
//
Функция ПолучитьРодительскийЭлемент(Элемент) Экспорт
Параметры = Новый Соответствие;
Параметры["script"] = "return arguments[0].parentNode";
Параметры["args"] = ОбщегоНазначенияКлиентСервер.ЗначениеВМассиве(Элемент);
Возврат ВыполнитьКоманду(Команды.ВЫПОЛНИТЬ_СКРИПТ, Параметры)["value"];
КонецФункции
// Ищет элемент по тексту.
//
// Параметры:
// Текст - Строка - Текст поиска
// Тег - Строка - Имя тега элемента, если не указано то будет выбран любой тег
// ТочноеСовпадение - Булево -
//
// Возвращаемое значение:
// Соответствие - Десериализованный в соответствие, сериализованный веб элемент;
//
Функция НайтиЭлементПоТексту(Текст, Тег = "*", ТочноеСовпадение = Ложь) Экспорт
Если ТочноеСовпадение Тогда
ТекстПоиска = СтрЗаменить(СтрЗаменить("//<ИмяТега>[text() = ""<ТекстПоиска>""]", "<ИмяТега>", Тег), "<ТекстПоиска>", Текст);
Иначе
ТекстПоиска = СтрЗаменить(СтрЗаменить("//<ИмяТега>[contains(text(), ""<ТекстПоиска>"")]", "<ИмяТега>", Тег), "<ТекстПоиска>", Текст);
КонецЕсли;
Параметры = Новый Соответствие;
Параметры["using"] = "xpath";
Параметры["value"] = ТекстПоиска;
Возврат ВыполнитьКоманду(Команды.НАЙТИ_ЭЛЕМЕНТ, Параметры)["value"];
КонецФункции
#КонецОбласти
#Область СлужебныйПрограммныйИнтерфейс
// Отправляет запрос на сервер драйвера.
//
// Параметры:
// Команда - Структура -
// Параметры - Соответствие -
//
// Возвращаемое значение:
// Соответствие -
//
Функция ВыполнитьКоманду(Команда, Знач Параметры = Неопределено) Экспорт
Если Параметры = Неопределено Тогда
Параметры = Новый Соответствие;
КонецЕсли;
Если Параметры["session id"] = Неопределено И НЕ ПустаяСтрока(ИдентификаторСессии) Тогда
Параметры["session id"] = ИдентификаторСессии;
КонецЕсли;
АдресРесурса = СтроковыеФункцииКлиентСервер.ВставитьПараметрыВСтроку(Команда.Шаблон, Параметры);
Параметры.Удалить("session id");
Запрос = Новый HTTPЗапрос(АдресРесурса);
УстановитьЗаголовкиЗапроса(Запрос);
Если НужноУстановитьТелоЗапроса(Команда, Параметры) Тогда
// Проблема с кастомными юникод символами, при сериализации
// экранируются слеши.
Запрос.УстановитьТелоИзСтроки(СтрЗаменить(СериализоватьJSON(Параметры), "\\", "\"));
КонецЕсли;
Ответ = Соединение.ВызватьHTTPМетод(Команда.Метод, Запрос);
Если Ответ.КодСостояния <> 200 Тогда
ЗаписатьОшибкуЖурналРегистрации(Команда.Метод, Запрос, Ответ);
Если НЕ НеЗакрыватьПриОшибке Тогда
Закрыть();
КонецЕсли;
ВызватьИсключение НСтр("ru = 'Ошибка вызова сервера WebDriver.
|Подробности в журнале регистрации.'");
КонецЕсли;
Возврат ДесериализоватьJSON(Ответ.ПолучитьТелоКакСтроку());
КонецФункции
// Возвращает параметры открытия браузера.
//
// ТаймаутСкриптов - Число, Неопределено - Таймаут ожидания выполнения скрипта, если
// передано Неопределено (null), выполнение скриптов не будет прерываться.
// ТаймаутПерехода - Число - Таймаут ожидания загрузки страницы.
// ТаймаутОпределенияМестоположенияЭлемента - Число - Таймаут поиска элемента.
// ИнструкцияНавигации - Строка - Указывает когда загрузка страницы будет считаться завершенной, принимает значения:
// "normal" - Ожидает полную загрузку и выполнение всех скриптов (состояние документа "complete").
// "eager" - Ожидает построение DOM (состояние документа "interactive").
// "none" - Возвращает управление сразу после перехода, фактически не ожидает загрузку.
//
// Возвращаемое значение:
// Соответствие -
//
Функция ПараметрыОткрытияБраузера(ТаймаутСкриптов = Неопределено, ТаймаутПерехода = 300, ТаймаутОпределенияМестоположенияЭлемента = 0, ИнструкцияНавигации = "normal") Экспорт
// https://www.w3.org/TR/webdriver/#new-session
Параметры = Новый Соответствие;
Параметры["capabilities"] = Новый Соответствие;
// Описание сессии
Параметры["capabilities"]["alwaysMatch"] = Новый Соответствие;
Параметры["capabilities"]["alwaysMatch"]["browserName"] = "chrome"; // Создаем экземпляр драйвера Chrome
Параметры["capabilities"]["alwaysMatch"]["pageLoadStrategy"] = ИнструкцияНавигации; // Указываем что нужно ожидать полную загрузку со всеми ассетами
// Описание таймаутов
Параметры["capabilities"]["timeouts"] = Новый Соответствие;
Параметры["capabilities"]["timeouts"]["script"] = XMLСтрока(ТаймаутСкриптов); // Таймаут выполнения скрипта
Параметры["capabilities"]["timeouts"]["pageLoad"] = XMLСтрока(ТаймаутПерехода); // Таймаут загрузки страницы
Параметры["capabilities"]["timeouts"]["implicit"] = XMLСтрока(ТаймаутОпределенияМестоположенияЭлемента); // Таймаут ожидания обнаружения элемента
Возврат Параметры;
КонецФункции
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
Процедура ДобавитьКоманду(ИдентификаторКоманды, Метод, Шаблон)
Команды.Вставить(ИдентификаторКоманды, Новый Структура("Шаблон, Метод", Шаблон, Метод));
КонецПроцедуры
Процедура ЗаписатьОшибкуЖурналРегистрации(Метод, Запрос, Ответ)
Шаблон = "[Метод] [Ресурс] HTTP/1.1
|[ЗаголовкиЗапроса]
|
|[ТелоЗапроса]
|
|HTTP/1.1 [КодСостояния]
|[ЗаголовкиОтвета]
|
|[ТелоОтвета]";
Параметры = Новый Структура("Метод, Ресурс, ЗаголовкиЗапроса, ТелоЗапроса, КодСостояния, ЗаголовкиОтвета, ТелоОтвета",
Метод, Запрос.АдресРесурса, ПредставлениеHTTPЗаголовков(Запрос.Заголовки), Запрос.ПолучитьТелоКакСтроку(),
Ответ.КодСостояния, ПредставлениеHTTPЗаголовков(Ответ.Заголовки), Ответ.ПолучитьТелоКакСтроку());
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Ошибка вызова сервера WebDriver.'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные(),
,
СтроковыеФункцииКлиентСервер.ВставитьПараметрыВСтроку(Шаблон, Параметры));
КонецПроцедуры
Процедура УстановитьЗаголовкиЗапроса(Запрос)
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
КонецПроцедуры
Функция ПредставлениеHTTPЗаголовков(Заголовки)
Массив = Новый Массив;
Для Каждого КлючЗначение Из Заголовки Цикл
Массив.Добавить(СтрШаблон("%1: %2", КлючЗначение.Ключ, КлючЗначение.Значение));
КонецЦикла;
Возврат СтрСоединить(Массив, Символы.ПС);
КонецФункции
Функция СериализоватьJSON(Значение)
ПараметрыЗаписиJSON = Новый ПараметрыЗаписиJSON(, Символы.Таб);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку(ПараметрыЗаписиJSON);
НастройкиСериализацииJSON = Новый НастройкиСериализацииJSON;
НастройкиСериализацииJSON.ВариантЗаписиДаты = ВариантЗаписиДатыJSON.ЛокальнаяДата;
ЗаписатьJSON(
ЗаписьJSON,
Значение,
НастройкиСериализацииJSON);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Функция ДесериализоватьJSON(СтрокаJSON, ВСоответствие = Истина)
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
Возврат ПрочитатьJSON(ЧтениеJSON, ВСоответствие);
КонецФункции
Функция ИдентификаторЭлемента(Элемент)
Для Каждого КлючЗначение Из Элемент Цикл
Возврат КлючЗначение.Значение;
КонецЦикла;
КонецФункции
Функция НужноУстановитьТелоЗапроса(Команда, Параметры)
Возврат ВРег(Команда.Метод) = "POST";
КонецФункции
#КонецОбласти
// Актуальный список команд.
Команды = Новый Структура;
// Сессии.
ДобавитьКоманду("ЗАПУСТИТЬ_СЕССИЮ", "POST", "/session");
ДобавитьКоманду("УДАЛИТЬ_СЕССИЮ", "DELETE", "/session/[session id]");
// Навигация.
ДобавитьКоманду("ВПЕРЕД", "POST", "/session/[session id]/forward");
ДобавитьКоманду("НАЗАД", "POST", "/session/[session id]/back");
ДобавитьКоманду("ОБНОВИТЬ", "POST", "/session/[session id]/refresh");
ДобавитьКоманду("ПЕРЕЙТИ_ПО_URL", "POST", "/session/[session id]/url");
ДобавитьКоманду("ТЕКУЩИЙ_URL", "GET", "/session/[session id]/url");
ДобавитьКоманду("ЗАГОЛОВОК_ДОКУМЕНТА", "GET", "/session/[session id]/title");
// Работа с документом и текущим контекстом.
ДобавитьКоманду("КОД_СТРАНИЦЫ", "GET", "/session/[session id]/source");
ДобавитьКоманду("ВЫПОЛНИТЬ_СКРИПТ", "POST", "/session/[session id]/execute/sync");
// Куки.
ДобавитьКоманду("ПОЛУЧИТЬ_КУКИ", "GET", "/session/[session id]/cookie");
ДобавитьКоманду("ПОЛУЧИТЬ_КУКИ_ПО_ИМЕНИ", "GET", "/session/[session id]/cookie/[name]");
ДобавитьКоманду("ДОБАВИТЬ_КУКИ", "POST", "/session/[session id]/cookie");
ДобавитьКоманду("УДАЛИТЬ_КУКИ", "DELETE", "/session/[session id]/cookie");
ДобавитьКоманду("УДАЛИТЬ_КУКИ_ПО_ИМЕНИ", "DELETE", "/session/[session id]/cookie/[name]");
// Элементы.
ДобавитьКоманду("НАЙТИ_ЭЛЕМЕНТ", "POST", "/session/[session id]/element");
ДобавитьКоманду("НАЙТИ_ЭЛЕМЕНТЫ", "POST", "/session/[session id]/elements");
ДобавитьКоманду("НАЙТИ_ЭЛЕМЕНТ_ОТ_ЭЛЕМЕНТА", "POST", "/session/[session id]/element/[element id]/element");
ДобавитьКоманду("НАЙТИ_ЭЛЕМЕНТЫ_ОТ_ЭЛЕМЕНТА", "POST", "/session/[session id]/element/[element id]/elements");
ДобавитьКоманду("ПОЛУЧИТЬ_АКТИВНЫЙ_ЭЛЕМЕНТ", "GET", "/session/[session id]/element/active");
ДобавитьКоманду("ЗНАЧЕНИЕ_АТРИБУТА_ЭЛЕМЕНТА", "GET", "/session/[session id]/element/[element id]/attribute/[name]");
ДобавитьКоманду("ЗНАЧЕНИЕ_СВОЙСТВА_ЭЛЕМЕНТА", "GET", "/session/[session id]/element/[element id]/property/[name]");
ДобавитьКоманду("ЗНАЧЕНИЕ_СВОЙСТВА_СТИЛЯ_ЭЛЕМЕНТА", "GET", "/session/[session id]/element/[element id]/css/[property name]");
ДобавитьКоманду("ТЕКСТ_ЭЛЕМЕНТА", "GET", "/session/[session id]/element/[element id]/text");
ДобавитьКоманду("ИМЯ_ТЕГА_ЭЛЕМЕНТА", "GET", "/session/[session id]/element/[element id]/name");
ДобавитьКоманду("ПОЗИЦИЯ_ЭЛЕМЕНТА", "GET", "/session/[session id]/element/[element id]/rect");
// Взаимодействие с элементом
ДобавитьКоманду("НАЖАТЬ_НА_ЭЛЕМЕНТ", "POST", "/session/[session id]/element/[element id]/click");
ДобавитьКоманду("ОТПРАВИТЬ_КЛАВИШИ", "POST", "/session/[session id]/element/[element id]/value");
ДобавитьКоманду("ОЧИСТИТЬ_ЭЛЕМЕНТ", "POST", "/session/[session id]/element/[element id]/clear");
// Скриншоты.
ДобавитьКоманду("СДЕЛАТЬ_СКРИНШОТ", "GET", "/session/[session id]/screenshot");
ДобавитьКоманду("СДЕЛАТЬ_СКРИНШОТ_ЭЛЕМЕНТА", "GET", "/session/[session id]/element/[element id]/screenshot");
// Клавиши доступные для отправки в поле ввода, помимо простых юникод символов
// https://www.selenium.dev/selenium/docs/api/rb/Selenium/WebDriver/Keys.html
Клавиши = Новый Структура;
Клавиши.Вставить("CANCEL", "\ue001");
Клавиши.Вставить("BREAK", "\ue001");
Клавиши.Вставить("HELP", "\ue002");
Клавиши.Вставить("BACKSPACE", "\ue003");
Клавиши.Вставить("BACK_SPACE", Клавиши.BACKSPACE);
Клавиши.Вставить("TAB", "\ue004");
Клавиши.Вставить("CLEAR", "\ue005");
Клавиши.Вставить("RETURN", "\ue006");
Клавиши.Вставить("ENTER", "\ue007");
Клавиши.Вставить("SHIFT", "\ue008");
Клавиши.Вставить("LEFT_SHIFT", Клавиши.SHIFT);
Клавиши.Вставить("CONTROL", "\ue009");
Клавиши.Вставить("LEFT_CONTROL",Клавиши.CONTROL);
Клавиши.Вставить("ALT", "\ue00a");
Клавиши.Вставить("LEFT_ALT", Клавиши.ALT);
Клавиши.Вставить("PAUSE", "\ue00b");
Клавиши.Вставить("ESCAPE", "\ue00c");
Клавиши.Вставить("SPACE", "\ue00d");
Клавиши.Вставить("PAGE_UP", "\ue00e");
Клавиши.Вставить("PAGE_DOWN", "\ue00f");
Клавиши.Вставить("END", "\ue010");
Клавиши.Вставить("HOME", "\ue011");
Клавиши.Вставить("LEFT", "\ue012");
Клавиши.Вставить("ARROW_LEFT", Клавиши.LEFT);
Клавиши.Вставить("UP", "\ue013");
Клавиши.Вставить("ARROW_UP", Клавиши.UP);
Клавиши.Вставить("RIGHT", "\ue014");
Клавиши.Вставить("ARROW_RIGHT", Клавиши.RIGHT);
Клавиши.Вставить("DOWN", "\ue015");
Клавиши.Вставить("ARROW_DOWN", Клавиши.DOWN);
Клавиши.Вставить("INSERT", "\ue016");
Клавиши.Вставить("DELETE", "\ue017");
Клавиши.Вставить("SEMICOLON", "\ue018");
Клавиши.Вставить("EQUALS", "\ue019");
Клавиши.Вставить("NUMPAD0", "\ue01a");
Клавиши.Вставить("NUMPAD1", "\ue01b");
Клавиши.Вставить("NUMPAD2", "\ue01c");
Клавиши.Вставить("NUMPAD3", "\ue01d");
Клавиши.Вставить("NUMPAD4", "\ue01e");
Клавиши.Вставить("NUMPAD5", "\ue01f");
Клавиши.Вставить("NUMPAD6", "\ue020");
Клавиши.Вставить("NUMPAD7", "\ue021");
Клавиши.Вставить("NUMPAD8", "\ue022");
Клавиши.Вставить("NUMPAD9", "\ue023");
Клавиши.Вставить("MULTIPLY", "\ue024");
Клавиши.Вставить("ADD", "\ue025");
Клавиши.Вставить("SEPARATOR", "\ue026");
Клавиши.Вставить("SUBTRACT", "\ue027");
Клавиши.Вставить("DECIMAL", "\ue028");
Клавиши.Вставить("DIVIDE", "\ue029");
Клавиши.Вставить("F1", "\ue031");
Клавиши.Вставить("F2", "\ue032");
Клавиши.Вставить("F3", "\ue033");
Клавиши.Вставить("F4", "\ue034");
Клавиши.Вставить("F5", "\ue035");
Клавиши.Вставить("F6", "\ue036");
Клавиши.Вставить("F7", "\ue037");
Клавиши.Вставить("F8", "\ue038");
Клавиши.Вставить("F9", "\ue039");
Клавиши.Вставить("F10", "\ue03a");
Клавиши.Вставить("F11", "\ue03b");
Клавиши.Вставить("F12", "\ue03c");
Клавиши.Вставить("META", "\ue03d");
Клавиши.Вставить("COMMAND", "\ue03d");
Клавиши.Вставить("ZENKAKU_HANKAKU", "\ue040");
Далее во всех примерах будет использоваться созданный объект данной обработки, название переменной - "Браузер".
Примеры использования:
Создание сессии (открытие окна браузера).
Браузер = Обработки.ДрайверChrome.Создать();
Браузер.Открыть();
Создание сессии с указанием нестандартного порта (например при простом открытии веб драйвера без selenium grid) и указанием папки для загрузки файлов (для хрома, для остальных нужно гуглить preferences).
Браузер = Обработки.ДрайверChrome.Создать();
Параметры = Браузер.ПараметрыОткрытияБраузера();
Параметры["capabilities"]["alwaysMatch"]["goog:chromeOptions"] = Новый Соответствие;
Параметры["capabilities"]["alwaysMatch"]["goog:chromeOptions"]["prefs"] = Новый Соответствие;
// Обратите внимание на экранирование слешей, это важно из-за костыля при отправке
// json на сервер.
Параметры["capabilities"]["alwaysMatch"]["goog:chromeOptions"]["prefs"]["download.default_directory"] = "C:fakepath\\downloads";
Браузер.Открыть(9501, Параметры);
Удаление сессии (закрытие окна браузера).
Браузер.Закрыть();
Переход по URL. Обратите внимание, что управление в 1с не вернется пока не будет завершена загрузка страницы. Что браузер будет считать загрузкой страницы - мы можем установить сами см. параметры функции ПараметрыОткрытияБраузера.
Браузер.ПерейтиПоURL("//infostart.ru/"); // Нужно писать полный адрес, инфостарт съедает протокол
Получение текущего URL.
Расположение = Браузер.ТекущийURL();
Получение заголовка страницы.
НаименованиеДокумента = Браузер.ЗаголовокДокумента();
Переход вперед по истории.
Браузер.Вперед();
Переход назад по истории.
Браузер.Назад();
Обновление текущей страницы.
Браузер.Обновить()
Получение HTML кода страницы (без элементов теневого дерева).
СтрокаHTML = Браузер.КодСтраницы();
Выполнение произвольного скрипта.
Браузер.ВыполнитьСкрипт("alert(Привет)");
Выполнение скрипта с передачей элемента в качестве аргумента.
ЭлементКнопка = Браузер.НайтиЭлемент("css selector", "button.btn-default");
Аргументы = Новый Массив;
Аргументы.Добавить(ЭлементКнопка);
Браузер.ВыполнитьСкрипт("arguments[0].disabled = false", Аргументы);
Получение всех текущих cookie.
МассивКуки = Браузер.ПолучитьКуки();
Получение cookie по наименованию.
Куки = Браузер.ПолучитьКукиПоИмени("_gid");
Установка cookie.
Браузер.УстановитьКуки(Куки);
Удаление всех текущих cookie.
Браузер.УдалитьКуки();
Удаление cookie по наименованию.
Браузер.УдалитьКукиПоИмени("_gid");
Поиск элемента (ов). Помимо селектора css, при поиске можно использовать текст ссылки, часть текста ссылки, имя тега и XPATH (см. описание функций поиска элементов).
// Поиск по селектору css (. - класс, # - идентификатор и т.д.)
Элемент = Браузер.НайтиЭлемент("css selector", "#header");
МассивЭлементов = Браузер.НайтиЭлементы("css selector", ".d-flex");
Поиск элемента(ов) от указанного элемента.
// Поиск по селектору css (. - класс, # - идентификатор и т.д.)
Элемент = Браузер.НайтиЭлемент("css selector", "#header");
МассивЭлементов = Браузер.НайтиЭлементыОтЭлемента(Элемент, "css selector", ".d-flex");
Получение активного элемента. (свойство документа activeElement)
Элемент = Браузер.ПолучитьАктивныйЭлемент();
Получение значения атрибута элемента.
СсылкаНаКартинку = Браузер.ЗначениеАтрибутаЭлемета(ЭлементКартинка, "src");
Получение значения свойства элемента.
МожноПеретащить = Браузер.ЗначениеСвойстваЭлемета(Элемент, "draggable");
Получение значения css свойства элемента.
СтрокаЦветЭлемента = Браузер.ЗначениеСвойстваСтиляЭлемета(Элемент, "background-color");
Получение текста элемента.
Текст = Браузер.ТекстЭлемента(Элемент);
Получение имени тега элемента.
ИмяТега = Браузер.ТекстЭлемента(Элемент);
ЭтоSpan = ВРег(ИмяТега) = "SPAN";
Получение позиции элемента.
ПозицияЭлемента = Браузер.ПозицияЭлемента(Элемент);
ШиринаОграничивающегоПрямоугольника = ПозицияЭлемента.width;
Эмуляция нажатия на элемент. В случае если нажатие инициировало переход на другую страницу, управление не вернется в 1с пока не завершится загрузка страницы.
Браузер.Нажать(ЭлементКнопка);
Очистка поля.
Браузер.ОчиститьПоле(ЭлементПолеВвода);
Отправка нажатий клавиш. (эмуляция нажатия клавиш пользователем)
Браузер.ОтправитьКлавиши(ЭлементЛогин, "login");
Отправка нажатий спец. клавиш. (функциональные клавиши, клавиши управления и т.д.)
Браузер.ОтправитьКлавиши(ЭлементПароль, Пароль + Браузер.Клавиши.ENTER);
Создание скриншота текущий области страницы.
ДвоичныеДанныеСкриншотPNG = Браузер.СделатьСкриншот();
Создание скриншота элемента.
ДвоичныеДанныеСкриншотPNG = Браузер.СделатьСкриншотЭлемента(ЭлементКнопка);
Это основные примеры использования, которых хватит для 99% задач. Если нужны более продвинутые функции, например переопределение позиции геолокации или установка сразу всех cookie для всех сайтов (например при открытии браузера), то нужно смотреть Chrome DevTools Protocol, они дают практически безграничные возможности. При запуске сессии в веб драйвере эти инструменты доступны по тому же эндпоинту что и сессия. Пример установки cookie для всех доменов сразу через DevTools Protocol:
Команда = Новый Структура("Метод, Шаблон");
Команда.Метод = "POST";
Команда.Шаблон = "/session/[session id]/chromium/send_command";
Параметры = Новый Соответствие;
Параметры["cmd"] = "Network.setCookies";
Параметры["params"] = Новый Структура("cookies", МассивКуки);
Браузер.ВыполнитьКоманду(Команда, Параметры);
Для самых ленивых, кому лень создавать самому обработку из п.2, добавляю в публикацию готовую.
Тестировалось на демке БСП 3.1.7.343.