Как использовать Selenium с Powershell для работы с браузером


22 февраля 2022


Используем Powershell с Selenium для эмуляции работы браузера

Selenium - это набор библиотек, которые могут автоматизировать действия осуществляемые в браузере. В основном эти библиотеки используют в тестировании сайтов. В этой статье будет рассмотрен пример работы Selenium с Powershell.

 

Когда и где нужен Selenium

Если вы когда-то использовали инструменты типа Invoke-WebRequest (curl или wget) для получения содержимого веб-страниц, то замечали, что результат может быть не идентичен тому, что вы видите в браузере. Причин у этого достаточно много, но основные следующие:

  • Команды, модули и библиотеки не выполняют код JavaScript (за очень редким исключением). Этот код читается как обычный текст. В браузерах же, обычно, встроен интерпретатор Javascript (такая же 'консоль' как и Powershell). Через этот интерпретатор и выполняется код. Сам же JS код может получать какие-то данные от другого сервера;
  • Браузеры, обычно, отправляют 'User-Agent', которые содержат информацию о вашем устройстве. На основе этого сервер может вернуть разные данные. Этот параметр нужно дополнительно указывать в командах, когда браузеры делают это автоматически;
  • Эмуляция движения мышки, что может быть важно в отдельных случаях. 

Что бы получать содержимое сайта в том виде, в котором видите его вы через браузер, и был придуман Selenium. Мы используем библиотеку Selenium (доступную на многих языка программирования), которая сформирует запросы к драйверу браузера, а тот выполнит код на реальном браузере. Один из примеров того, как это может работать:

Принцип работы Selenium 4

Selenium - это один из самых популярных способов тестирования сайтов. Из-за частого изменения в сайтах, нововведений в браузерах и устройств, тестирования приходится автоматизировать. Такой вид тестирования часто привязывают к процессу DevOps CI.

Для системного администратора этот инструмент может пригодиться, если вы используете веб-версии клиентов. По сути вы можете создать скрипт, который будет настраивать, например, Exchange через ECP (сам никогда подобного не делал).

Кроме всего, Selenium, это частый инструмент, который можно встретить в вакансиях DevOps (мне это сложно понять). Учитывая, что принцип работы селениум одинаков на всех языках, вы потом сможете легко переключиться на другой.

Примеры, которые будут приведены ниже, так же можно будет привязать к Zabbix тестируя работу конкретных частей сайта регулярно. Пример того, как это можно сделать, был показан в статье 'Используем Powershell для отправки и получения данных с Zabbix'.

 

Предварительные требования

В первую очередь нужно скачать драйвер Selenium. Его можно получить через официальный сайт (нужна версия для C#) или с репозитория Nuget (в примерах используется версия 4.1). В своем случае мной был разархивирован архив с полученный с 'Nuget', где я взял файл 'WebDriver.dll'. Это единственный файл, который нужен. Он может зависеть от установленной у вас версии .NET.

Расположение файла WebDriver.dll Selenium

Драйвер браузера так же скачивается отдельно. В примерах ниже я буду использовать ChromeDriver, но вы можете выбрать любой другой. Драйвер Chrome можно скачать с официального сайта Google. Важно, что бы версия драйвера соответствовала версии вашего браузера.

Оба файла я поместил в одну папку:

Расположение файла WebDriver.dll и chromedriver вместе

Драйвер браузера - должен быть доступен через переменную 'Path'. Учитывая, что мы используем Powershell, я могу это выполнить следующим образом:

# Директория с драйвером браузера
$working_path = 'D:\selenium'

# Добавляем директорию в окружение, если ее нет
if (($env:Path -split ';') -notcontains $working_path) {
    $env:Path += ";$working_path"
}

Добавление директории с chromedriver в виртуальное окружение

В примере выше - мы добавляем директорию с драйвером только на время сессии Powershell. Т.е. вам нужно будет вызывать этот код в каждом скрипте при работе с Selenium. Вы это можете исправить используя обычные оснастки Windows.

После того как мы поместили файлы в нужную папку и создали переменную, мы должны импортировать Selenium в самом скрипте. Это можно сделать одним из следующих способов:

Add-Type -Path "$working_path\WebDriver.dll"
# или
Import-Module "$working_path\WebDriver.dll"
# или
[System.Reflection.Assembly]::LoadFrom("$working_path\WebDriver.dll")

Если при импортировании модуля у вас появится ошибка - скорее всего этот модуль не подходит к вашей версии .NET (на скриншоте были видны версии для разных версий).

В своих примерах я использую Powershell 5.1. Аналогичное должно работать и на старших версиях.

Модуль для Powershell

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

При использовании Selenium очень важно соответствие браузера и драйвера. В модуле, например, используется 86 версия Chrome, а актуальная версия, на данный момент, 99. Вы можете поддерживать модуль самостоятельно, исправляя такие моменты. Я его упомянул для случая, если вы столкнетесь с проблемами или будете искать какую-то реализацию функционала. В этом случае будет куда посмотреть.

В статье рассматривается применение библиотеки C# через Powershell.

 

Открытие страницы

После импорта модуля мы должны создать объект 'Selenium'. Если упрощать, то объект - это переменная у которой появляются разные команды (методы) и значения (свойства).

Создать объект и открыть страницу можно следующим образом:

# Создаем объект
$selenium = New-Object OpenQA.Selenium.Chrome.ChromeDriver
# Выполняем метод открытия страницы
$selenium.Navigate().GoToURL('https://fixmypc.ru/')

Открытие страницы с Selenium через Powershell

В примере показан Chrome, но вы можете использовать по аналогии и другие:

Выбор браузера для работы с Selenium через Powershell

Нажатия и поиск элементов

Одной из важных частей работы с селениум является поиск по странице. Этот поиск связан с поиском HTML тегов, CSS классов и идентификаторов. Для этого есть 2-а метода:

# Поиск первого/одного тега/класса и т.д.
$selenium.FindElement(...)
# Поиск нескольких тегов/классов и т.д.
$selenium.FindElements(...)

В предыдущих версиях Selenium (до версии 4), для поиска тега '<p>' использовался следующий подход (упрощенный пример):

# не верный подход
$selenium.FindElements('p')

На данный момент он не работает. Сейчас, что бы найти нужный элемент, создается еще один объект за счет класса 'By'. Это выглядит примерно следующим образом:

# верный подход
$selenium.FindElements([OpenQA.Selenium.By]::CssSelector('p'))

Поиск элементов в браузере с Selenium через Powershell

CSS selector и XPath

'CSSselector', с примера выше, это не просто название, а подвид языков для поиска элементов. Основных видов таких языков два: XPath и CSS selector. Самое заметное отличие между ними - это синтаксис. Например так мы найден элементы на странице имеющие класс 'someclass' (например <div class="someclass">):

# XPath
$selenium.FindElements([OpenQA.Selenium.By]::XPath("//[@class='someclass']"))
# CSS selector
$selenium.FindElements([OpenQA.Selenium.By]::CssSelector('.someclass'))

Преимущества, в основном, на стороне 'CSS selector' (скорость, документация, синтаксис), но и у XPath есть особенности (поиск элемента в обе вперед/назад). В простых сценариях эта разница может не играть роли вовсе.

Готовый XPath и CSS selector можно увидеть если открыть 'Dev Tools' (f12) в Google Chrome и нажать на нужный элемент правой клавишей:

Получение XPath и CSS selector через инструменты разработчика в браузере

Разница синтаксиса при поиске одного и того же элемента:

  • CSS selector: body > div > main > div > div.col-md-8 > div:nth-child(2) > div.card-body > a
  • XPath: /html/body/div/main/div/div[1]/div[1]/div[1]/a

Пример поиска элемента в обоих языках:

# XPath
$selenium.FindElement([OpenQA.Selenium.By]::XPath('/html/body/div/main/div/div[1]/div[1]/div[1]/a'))
# CSS selector
$selenium.FindElement([OpenQA.Selenium.By]::CssSelector('body > div > main > div > div.col-md-8 > div:nth-child(2) > div.card-body > a'))

Кроме уже описанных способов поиска существуют и другие. Названия говорят сами за себя:

[OpenQA.Selenium.By]::ClassName('')
[OpenQA.Selenium.By]::Id('')
[OpenQA.Selenium.By]::LinkText('')
[OpenQA.Selenium.By]::Name('')
[OpenQA.Selenium.By]::PartialLinkText('')
[OpenQA.Selenium.By]::TagName('')

Нажатия

Что бы выполнить нажатие (однократное) нужно использовать метод 'Click()' у найденного объекта:

$selenium.FindElement([OpenQA.Selenium.By]::CssSelector('body > div > main > div > div.col-md-8 > div:nth-child(2) > div.card-body > a')).Click()

Нажатие на кнопку при работе с Selenium через Powershell

Заполнение форм и отправка полей

Еще одна задача, которая может вам понадобиться, отправка форм и заполнение полей. На странице SLA калькулятора есть два поля с идентификаторами 'minutes' и 'hours'. С помощью следующей части скрипта мы найдем их и заполним тестовыми данными '4' и '44':

$hours = $selenium.FindElement([OpenQA.Selenium.By]::CssSelector('#hours'))
# активация поля
$hours.Click()
# заполнение данными
$hours.SendKeys('4')

$minutes_inp = $selenium.FindElement([OpenQA.Selenium.By]::CssSelector('#minutes'))
# активация поля
$minutes_inp.Click()
# заполнение данными
$minutes_inp.SendKeys('44')

Заполнение полей формы при работе с Selenium через Powershell

При этом нужно учитывать, что не всегда ввод данных ('SendKeys') требует предварительного нажатия ('Click'). Это может зависеть от кода Javascript, который используется на странице. 

Если поле, которое вы пытаетесь заполнить,  уже имело данные, то вы можете их очистить используя метод 'Clear()'.

Мы рассматривали только заполнение текстовых полей, но еще существуют: радио кнопки, селекторы и т.д. Какие-то заполняются методом 'Click()'.

Для селекторов (dropdown меню) используется отдельный класс. Этот класс находится в библиотеке 'WebDriver.Support.dll'. Саму библиотеку нужно тоже скачать (ссылка на NuGet) и разместить рядом с 'WebDriver.dll'. Пример ниже не относится к основной части статьи, но дает понимание, где это используется:

# Импорт библиотеки
Add-Type -Path "$working_path\WebDriver.Support.dll"

$octets = $selenium.FindElement([OpenQA.Selenium.By]::CssSelector('#parts'))
# Помечаем найденный элемент как содержащий выбор
$obj = New-Object OpenQA.Selenium.Support.UI.SelectElement($octets)
# делаем выбор на основе текста
$obj.SelectByText('1')

Выбор значение через dropdown при работе с Selenium через Powershell

В остальных случаях можно посмотреть описание объекта:

$hours | Get-Member

Дополнительные методы и свойства объекта Selenium в Powershell

Еще один метод, 'Submit()', позволяет отправить заполненную форму. Перед такой отправкой, так же как и в предыдущем примере, мы должны найти его через 'FindElement'. Нужная '<form>' имеет ID 'sla-calculator':

$form = $selenium.FindElement([OpenQA.Selenium.By]::CssSelector('#sla-calculator'))
$form.Submit()

Отправка формы при работе с Selenium через Powershell

Так же стоит учитывать, что не все кнопки отправки данных, как с примера выше, относятся к формам. Т.е. 'Submit()' может не сработать и понадобится использовать 'Click()' по кнопке 'Отправить'.

Передвижение мышки

Еще одним важным моментом, при работе, с селениум является передвижение мышки. Например, есть элементы связанные с JS и они сработают только при наведении курсора мышки. Кроме этого есть защиты, которые определяют пользователя как робота по поведению мыши.

Сами движения определяются через метод 'Actions'. Создать такой объект можно так:

$action = New-Object OpenQA.Selenium.Interactions.Actions($selenium)

Что бы перевести курсор на нужный объект можно выполнить следующие шаги:

$el = $selenium.FindElement([OpenQA.Selenium.By]::CssSelector('#windows'))
$action.MoveToElement($el).Build().Perform()

Передвижение мышки при работе с Selenium через Powershell

Поведений, которые мы можем определить, достаточно много. Например 'DragAndDrop' определят нажатие и удержание кнопки мышки до определенного момента:

$action | Get-Member

Дополнительные методы и свойства передвижения мышки (action) при работе с Selenium в Powershell

 

Ожидание ответа сервера

Одной из частых причин ошибок является время отображение элементов. Данные на сайт могут попадать с нескольких серверов, что может повышать время ответа. Браузер может не отслеживать такое поведение и выводить ошибку. Если вам нужно дождаться вывода этих данных, то вы можете использовать несколько приемов.

Первый - это обычное ожидание через функционал Powershell. Например, так вы установите время ожидания в 5 секунд:

sleep(5)

Такой тип ожиданий считается плохой практикой в целом, но может работать достаточно часто.

Еще один способ, который называется неявным ожиданием (Implicit), устанавливается для Selenium в целом:

$selenium.Manage().Timeouts().ImplicitWait = New-TimeSpan -Seconds 5

Существует так же и явное ожидание (Explicit), которое устанавливается для конкретного элемента.

Пример работы можно увидеть в конце статьи.

 

Вывод текста, статусов и заголовков

Для тестирования сайтов часто используется сравнение текста на сайте с заранее предопределенным. Например, что кнопка на сайте имеет текст "Отправить". Для этого есть много отдельных методов.

Для примера так мы можем получить заголовок страницы:

$selenium.Title

Текстовое значение какого-то элемента:

$selenium.FindElement([OpenQA.Selenium.By]::CssSelector('#year')).Text

Так мы получим ссылку (значение атрибута href):

$selenium.FindElement([OpenQA.Selenium.By]::CssSelector('body > div > main > div > div.col-lg-8 > ol > li:nth-child(2) > a')).GetAttribute('href')

Получение текста, ссылок и заголовков при работе с Selenium через Powershell

Это не все значения, которые вы можете вывести. Остальные свойства можно увидеть следующим способом:

$selenium | Get-Member
$selenium.FindElement([OpenQA.Selenium.By]::CssSelector('#year')) | Get-Member

 

Закрытие вкладки и браузера

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

$selenium.Close()
$selenium.Quit()

При закрытии браузера - выполняется так же очистка кэша.

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

 

Настройка браузера

Очень легко прийти к какой-то непонятной ошибке, если использовать разные настройки браузеров. Мой опыт был связан с тем, что я не учитывал размер обычного браузера, на котором просматривал сайт. Из-за того, что я использовал настройки по умолчанию, селениум открывал браузер на половину экрана, а обычный я открывал на весь экран. Из-за этого менялись CSS классы на которые я опирался.

Для установки таких настроек используется класс (для Chrome) 'OpenQA.Selenium.Chrome.ChromeOptions'. Пример настроек, которые разрешат открывать сайты с "проблемными сертификатами" и в полное окно:

$selenium_options = New-Object OpenQA.Selenium.Chrome.ChromeOptions
$selenium_options.AddArgument('start-maximized')
$selenium_options.AcceptInsecureCertificates = $True

$selenium = New-Object OpenQA.Selenium.Chrome.ChromeDriver($ChromeOptions)

Настройки браузера при работе с Selenium через Powershell

Другие настройки можно посмотреть так:

$selenium_options = New-Object OpenQA.Selenium.Chrome.ChromeOptions
$selenium_options | Get-Member

Дополнительные методы и свойства для настройки браузера Selenium через Powershell

Если вы не нашли нужный аргумент, то вы можете поискать его на сайте драйвера. В случае Chrome это два сайта (первый, второй). Например, можно создать профиль, который импортироваться при запуске скриптов.

Аргументы для настройки браузера Selenium

 

 

Пример использования

Еще один пример, который похож на рассматриваемый в течении всей статьи, но имеющий отличия. Тут рассматривается игра по преобразованию битов. В этой игре есть поле с вариантами выбора и не относящиеся к '<form>'. Так же, если не установить задержку 'ImplicityWait', то будет ошибка.

# Директория с драйвером браузера
$working_path = 'D:\selenium'

# Добавляем директорию в окружение, если ее нет
if (($env:Path -split ';') -notcontains $working_path) {
    $env:Path += ";$working_path"
}

# Основная библиотека Selenium
Add-Type -Path "$working_path\WebDriver.dll"
# Загружаем библиотеку для выбора 'select'
Add-Type -Path "$working_path\WebDriver.Support.dll"

# Создаем объект драйвера
$driver = New-Object OpenQA.Selenium.Chrome.ChromeDriver

# Выполняем метод открытия страницы
$driver.Navigate().GoToURL('https://fixmypc.ru/services/igra-po-preobrazovaniiu-bitov-v-ipv4-adres/')

# Получаем объект с выбором октетов
$octets = $driver.FindElement([OpenQA.Selenium.By]::CssSelector('#parts'))
# Помечаем октеты как элемент с выбором
$obj = New-Object OpenQA.Selenium.Support.UI.SelectElement($octets)
# Делаем выбор на основе текста
$obj.SelectByText('1')

# получаем двоичное представление
# и устанавливаем задержку
$driver.Manage().Timeouts().ImplicitWait = New-TimeSpan -Seconds 5
$addr = $driver.FindElement([OpenQA.Selenium.By]::CssSelector('#ip-address-bits')).getAttribute("value")

# вычисляем ответ
$answer = [Convert]::ToInt32($addr, 2)

# помещаем вычисленный ответ в поле
$driver.FindElement([OpenQA.Selenium.By]::CssSelector('#ip-address')).SendKeys($answer)
# отправляем данные
$driver.FindElement([OpenQA.Selenium.By]::CssSelector('#ip-bits-game > div:nth-child(4) > div > button')).Click()

# закрываем вкладку и браузер
$driver.Close()
$driver.Quit()


...

Теги: #powershell #selenium


Каналы
Telegram FixMyPc Telegram Лента FixMyPC RSS Rss
Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, приобретённого опыта и лучшие практики в системном администрировании и программировании.