Чистая реализация протокола WebDriver. Используйте эту библиотеку, чтобы автоматизировать браузер, проверить свое поведение на границе, моделировать действия человека или все, что вы хотите.
Это названо в честь Этаина Шрдлу - машины для печати, которая стала живой после того, как на ней была создана заметка загадков.
С 0.4.0
экземпляр драйвера является картой, но не атом, как раньше. Это было сложное решение для принятия решения, но мы избавили от атома, чтобы следовать Clojure Way в нашем коде. Вообще говоря, вы никогда не обозначаете водителя и не храните в нем что -то. Все внутренние функции, которые использовались для изменения экземпляра, теперь просто возвращают новую версию карты. Если у вас есть swap!
Или что -то похожее в вашем коде для драйвера, пожалуйста, рефактируйте свой код перед обновлением.
С 0.4.0
библиотека поддерживает действия WebDriver. Действия - это команды, отправленные водителю в партии. См. Подробный связанный раздел в TOC.
С 0.4.0
ETAOIN может воспроизводить файлы скриптов, созданные в интерактивном селене IDE. См. Связанный раздел ниже.
Ct t
как обычно.Вы можете отправить свою компанию в этот список.
Есть два шага для установки:
etaoin
в свой код Clojure Добавьте следующее в :dependencies
в вашем файле project.clj
:
[etaoin "0.4.6"]
Работает с Clojure 1.9 и выше.
На этой странице представлены инструкции о том, как установить драйверы, необходимые для автоматизации браузера.
Установите браузеры Chrome и Firefox, загружающие их с официальных сайтов. На всех платформах не будет проблем.
Установите конкретные драйверы, которые вам нужны:
Google Chrome Driver:
brew cask install chromedriver
для пользователей Mac2.28
версия. 2.27
и ниже имеют ошибку, связанную с максимизацией окна (см. [[Устранение неполадок]]).Геккодривер, водитель Firefox:
brew install geckodriver
для пользователей MacPhantom.js Browser:
brew install phantomjs
для пользователей MacДрайвер Safari (только для Mac):
Теперь проверьте свою установку, запустив любую из этих команд. Для каждой команды должен запуститься бесконечный процесс с локальным HTTP -сервером.
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
Вы можете запустить тесты для запуска этой библиотеки:
lein test
Вы увидите, как Windows Browser открываются и закрываются в серии. Тесты используют локальный HTML -файл со специальной макеткой для проверки большинства случаев.
См. Ниже для раздела по устранению неполадок, если у вас есть проблемы
Хорошие новости, которые вы можете автоматизировать свой браузер непосредственно из реплики:
( use 'etaoin.api)
( require '[etaoin.keys :as k])
( def driver ( firefox )) ; ; here, a Firefox window should appear
; ; let's perform a quick Wiki session
( go driver " https://en.wikipedia.org/ " )
( wait-visible driver [{ :id :simpleSearch } { :tag :input :name :search }])
; ; search for something
( fill driver { :tag :input :name :search } " Clojure programming language " )
( fill driver { :tag :input :name :search } k/enter)
( wait-visible driver { :class :mw-search-results })
; ; select an `option` in select-box by visible text
; ; <select id="country">
; ; <option value="rf">Russia</option>
; ; <option value="usa">United States</option>
; ; <option value="uk">United Kingdom</option>
; ; <option value="fr">France</option>
; ;</select>
( select driver :country " France " )
( get-element-value driver :country )
; ;=> "fr"
; ; I'm sure the first link is what I was looking for
( click driver [{ :class :mw-search-results } { :class :mw-search-result-heading } { :tag :a }])
( wait-visible driver { :id :firstHeading })
; ; let's ensure
( get-url driver) ; ; "https://en.wikipedia.org/wiki/Clojure"
( get-title driver) ; ; "Clojure - Wikipedia"
( has-text? driver " Clojure " ) ; ; true
; ; navigate on history
( back driver)
( forward driver)
( refresh driver)
( get-title driver) ; ; "Clojure - Wikipedia"
; ; stops Firefox and HTTP server
( quit driver)
Видите ли, любая функция требует экземпляра драйвера в качестве первого аргумента. Таким образом, вы можете упростить его с помощью Macros doto
:
( def driver ( firefox ))
( doto driver
( go " https://en.wikipedia.org/ " )
( wait-visible [{ :id :simpleSearch } { :tag :input :name :search }])
; ; ...
( fill { :tag :input :name :search } k/enter)
( wait-visible { :class :mw-search-results })
( click :some-button )
; ; ...
( wait-visible { :id :firstHeading })
; ; ...
( quit ))
В этом случае ваш код выглядит как DSL, разработанный только для таких целей.
Вы можете использовать fill-multi
, чтобы сократить код как:
( fill driver :login " login " )
( fill driver :password " pass " )
( fill driver :textarea " some text " )
в
( fill-multi driver { :login " login "
:password " pass "
:textarea " some text " })
Если какое -либо исключение происходит во время сеанса браузера, внешний процесс может висеть навсегда, пока вы не убьете его вручную. Чтобы предотвратить это, используйте with-<browser>
макросы следующим образом:
( with-firefox {} ff ; ; additional options first, then bind name
( doto ff
( go " https://google.com " )
...))
Что бы ни случилось во время сеанса, процесс будет остановлен в любом случае.
Большинство функций, таких как click
, fill
и т. Д. Требуются термин запроса, чтобы обнаружить элемент на странице. Например:
( click driver { :tag :button })
( fill driver { :id " searchInput " } " Clojure " )
Библиотека поддерживает следующие типы и значения запросов.
:active
обозначает текущий активный элемент. Например, при открытии страницы Google он фокусирует курсор на основном вводе поиска. Таким образом, нет необходимости нажимать вручную. Пример:
( fill driver :active " Let's search for something " keys/enter)
Любое другое ключевое слово, которое указывает идентификатор элемента. Для страницы Google это :lst-ib
или "lst-ib"
(также поддерживаются строки). Реестр имеет значение. Пример:
( fill driver :lst-ib " What is the Matrix? " keys/enter)
строка с выражением XPath. Будьте осторожны, когда пишут их вручную. Проверьте раздел Troubleshooting
ниже. Пример:
( fill driver " .//input[@id='lst-ib'][@name='q'] " " XPath in action! " keys/enter)
Карта с или :xpath
или :css
-клавиш с строковой выражением соответствующего синтаксиса. Пример:
( fill driver { :xpath " .//input[@id='lst-ib'] " } " XPath selector " keys/enter)
( fill driver { :css " input#lst-ib[name='q'] " } " CSS selector " keys/enter)
См. Руководство по выборочному образу CSS для получения дополнительной информации.
Запрос может быть любой другой картой, которая представляет собой выражение XPath в качестве данных. Правила:
:tag
представляет имя тега. Это становится *
, когда не проходит.:index
ключ расширяется в пункт запекания [x]
. Полезно, когда вам нужно выбрать третью строку из таблицы, например.:fn/
пространство имен и расширяется во что -то конкретное.Примеры:
Найдите первую метку div
( query driver { :tag :div })
; ; expands into .//div
Найдите n-й- div
( query driver { :tag :div :index 1 })
; ; expands into .//div[1]
Найдите тег a
с атрибутом класса равны active
( query driver { :tag :a :class " active " })
; ; ".//a[@class="active"]"
Найдите форму по его атрибутам:
( query driver { :tag :form :method :GET :class :message })
; ; expands into .//form[@method="GET"][@class="message"]
Найдите кнопку по тексту (точное совпадение):
( query driver { :tag :button :fn/text " Press Me " })
; ; .//button[text()="Press Me"]
Найдите n -й элемент ( p
, a
, что угодно) с текстом «Скачать»:
( query driver { :fn/has-text " download " :index 3 })
; ; .//*[contains(text(), "download")][3]
Найдите элемент, у которого есть следующий класс:
( query driver { :tag :div :fn/has-class " overlay " })
; ; .//div[contains(@class, "overlay")]
Найдите элемент, который имеет следующий домен в Href:
( query driver { :tag :a :fn/link " google.com " })
; ; .//a[contains(@href, "google.com")]
Найдите элемент, у которого есть следующие классы сразу:
( query driver { :fn/has-classes [ :active :sticky :marked ]})
; ; .//*[contains(@class, "active")][contains(@class, "sticky")][contains(@class, "marked")]
Найдите виджеты ввода включенного/отключенного ввода:
; ; first input
( query driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
( query driver { :tag :input :fn/enabled true })
; ; .//input[@enabled=true()]
; ; all inputs
( query-all driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
Запрос может быть вектор, который состоит из любых выражений, упомянутых выше. В таком запросе каждый следующий термин ищет из предыдущего рекурсивно.
Простой пример:
( click driver [{ :tag :html } { :tag :body } { :tag :a }])
Вы также можете объединить выражения как XPATH, так и CSS (обратите внимание на ведущую точку в выражении XPath:
( click driver [{ :tag :html } { :css " div.class " } " .//a[@class='download'] " ])
Иногда вам может потребоваться взаимодействовать с NTH -элементом запроса, например, когда вы хотите нажать на вторую ссылку в этом примере:
< ul >
< li class =" search-result " >
< a href =" a " > a </ a >
</ li >
< li class =" search-result " >
< a href =" b " > b </ a >
</ li >
< li class =" search-result " >
< a href =" c " > c </ a >
</ li >
</ ul >
В этом случае вы можете либо использовать :index
, которая поддерживается для выражений XPath, как это:
( click driver [{ :tag :li :class :search-result :index 2 } { :tag :a }])
Или вы можете использовать трюк nth-ребенка с выражением CSS, как это:
( click driver { :css " li.search-result:nth-child(2) a " })
Наконец, также возможно получить NTH- элемент непосредственно, используя query-all
:
( click-el driver ( nth ( query-all driver { :css " li.search-result a " }) 2 ))
Обратите внимание на использование click-el
здесь, так как query-all
возвращает элемент, а не селектор, который можно передать, чтобы click
напрямую.
query-tree
берет селекторов и действует как дерево. Каждый следующий селектор запросит элементы из предыдущих. Селектор кулака опирается на находки, а остальные используют Find-Elements-From
( query-tree driver { :tag :div } { :tag :a })
означает
{:tag :div} -> [div1 div2 div3]
div1 -> [a1 a2 a3]
div2 -> [a4 a5 a6]
div3 -> [a7 a8 a9]
Таким образом, результатом будет [A1 ... A9]
Чтобы взаимодействовать с элементами, найденными через запрос, вы должны передать результат запроса либо click-el
, либо fill-el
:
( click-el driver ( first ( query-all driver { :tag :a })))
Таким образом, вы можете собирать элементы в вектор и произвольно взаимодействовать с ними в любое время:
( def elements ( query-all driver { :tag :input :type :text })
( fill-el driver ( first elements) " This is a test " )
( fill-el driver ( rand-nth elements) " I like tests! " )
В целях эмуляции человеческого ввода вы можете использовать функцию fill-human
. Следующие параметры включены по умолчанию:
{ :mistake-prob 0.1 ; ; a real number from 0.1 to 0.9, the higher the number, the more typos will be made
:pause-max 0.2 } ; ; max typing delay in seconds
И вы можете переопределить их:
( fill-human driver q text { :mistake-prob 0.5
:pause-max 1 })
; ; or just use default opts by omitting them
( fill-human driver q text)
Для множественного ввода с эмуляцией человека используйте fill-human-multi
( fill-human-multi driver { :login " login "
:pass " password "
:textarea " some text " }
{ :mistake-prob 0.5
:pause-max 1 })
Функция click
запускает левую мышь нажмите на элемент, найденный термином запроса:
( click driver { :tag :button })
Функция click
использует только первый элемент, найденный запросом, который иногда приводит к нажатию на неправильные элементы. Чтобы убедиться, что есть один и только один элемент, используйте функцию click-single
. Он действует так же, но повышает исключение при запросе страницы возвращает несколько элементов:
( click-single driver { :tag :button :name " search " })
Двойной щелчок используется редко в Интернете, но с функцией double-click
(Chrome, Phantom.js):
( double-click driver { :tag :dbl-click-btn })
Существует также куча «слепых» функций щелчка. Они запускают мышь нажали на текущую позицию мыши:
( left-click driver)
( middle-click driver)
( right-click driver)
Другая группа функций делает то же самое, но переместите указатель мыши на указанный элемент, прежде чем нажать на них:
( left-click-on driver { :tag :img })
( middle-click-on driver { :tag :img })
( right-click-on driver { :tag :img })
Средний щелчок мыши полезен при открытии ссылки на новой вкладке фона. Иногда щелчок правой кнопкой мыши используется для имитации контекстного меню в веб -приложениях.
Библиотека поддерживает действия WebDriver. В целом, действия - это команды, описывающие устройства виртуального ввода.
{ :actions [{ :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]}
{ :type " pointer "
:id " UUID or some name "
:parameters { :pointerType " mouse " }
:actions [{ :type " pointerMove " :origin " pointer " :x 396 :y 323 }
; ; double click
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }]}]}
Вы можете создать карту вручную и отправить ее на метод perform-actions
:
( def keyboard-input { :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]})
( perform-actions driver keyboard-input)
или используйте обертки. Сначала вам нужно создать устройства виртуального ввода, например:
( def keyboard ( make-key-input ))
а затем заполните его необходимыми действиями:
( -> keyboard
( add-key-down keys/shift-left)
( add-key-down " a " )
( add-key-up " a " )
( add-key-up keys/shift-left))
Расширенный пример:
( let [driver ( chrome )
_ ( go driver " https://google.com " )
search-box ( query driver { :name :q })
mouse ( -> ( make-mouse-input )
( add-pointer-click-el search-box))
keyboard ( -> ( make-key-input )
add-pause
( with-key-down keys/shift-left
( add-key-press " e " ))
( add-key-press " t " )
( add-key-press " a " )
( add-key-press " o " )
( add-key-press " i " )
( add-key-press " n " )
( add-key-press keys/enter))]
( perform-actions driver keyboard mouse)
( quit driver))
Чтобы очистить состояние виртуальных устройств ввода, выпустите все нажатые клавиши и т. Д., Используйте метод release-actions
:
( release-actions driver)
Нажатие на кнопку ввода файла открывает диалог, специфичный для ОС, который вам не разрешено взаимодействовать с использованием протокола Webdriver. Используйте функцию upload-file
, чтобы прикрепить локальный файл к виджету ввода файла. Функция принимает селектор, который указывает на ввод файла и либо полный путь в качестве строки, либо нативный экземпляр java.io.File
. Файл должен существовать, или вы получите исключение в противном случае. Пример использования:
( def driver ( chrome ))
; ; open a web page that serves uploaded files
( go driver " http://nervgh.github.io/pages/angular-file-upload/examples/simple/ " )
; ; bound selector to variable; you may also specify an id, class, etc
( def input { :tag :input :type :file })
; ; upload an image with the first one file input
( def my-file " /Users/ivan/Downloads/sample.png " )
( upload-file driver input my-file)
; ; or pass a native Java object:
( require '[clojure.java.io :as io])
( def my-file ( io/file " /Users/ivan/Downloads/sample.png " ))
( upload-file driver input my-file)
Вызов функции screenshot
сбрасывает текущую страницу в изображение PNG на вашем диске:
( screenshot driver " page.png " ) ; ; relative path
( screenshot driver " /Users/ivan/page.png " ) ; ; absolute path
Также поддерживается нативный объект файла Java:
; ; when imported as `[clojure.java.io :as io]`
( screenshot driver ( io/file " test.png " ))
; ; native object
( screenshot driver ( java.io.File. " test-native.png " ))
С Firefox и Chrome вы можете захватить не всю страницу, а ни одну элемент, скажем, Div, входной виджет или что -то еще. На данный момент это не работает с другими браузерами. Пример:
( screenshot-element driver { :tag :div :class :smart-widget } " smart_widget.png " )
С макросом with-screenshots
вы можете сделать снимок экрана после каждой формы
( with-screenshots driver " ../screenshots "
( fill driver :simple-input " 1 " )
( fill driver :simple-input " 2 " )
( fill driver :simple-input " 3 " ))
Что эквивалентно записи:
( fill driver :simple-input " 1 " )
( screenshot driver " ../screenshots/chrome-...123.png " )
( fill driver :simple-input " 2 " )
( screenshot driver " ../screenshots/chrome-...124.png " )
( fill driver :simple-input " 3 " )
( screenshot driver " ../screenshots/chrome-...125.png " )
Недавно Google Chrome и позже Firefox начали поддержать функцию без головного режима. Когда на экране не появляется ни одно из окон пользовательского интерфейса, только вывод STDOUT попадает в консоль. Эта функция позволяет запускать интеграционные тесты на серверах, которые не имеют графического вывода.
Убедитесь, что ваш браузер поддерживает режим без головы, проверяя, принимает ли он аргумент командной строки --headles
при запуске его с терминала. Phantom.js Driver без головы по своей природе (он никогда не был разработан для рендеринга пользовательского интерфейса).
При запуске драйвера пропустите :headless
логический флаг, чтобы переключиться в режим без головы. Обратите внимание, что поддерживается только последняя версия Chrome и Firefox. Для других водителей флаг будет проигнорирован.
( def driver ( chrome { :headless true })) ; ; runs headless Chrome
или
( def driver ( firefox { :headless true })) ; ; runs headless Firefox
Чтобы проверить любой драйвер, запускался в режиме без головы, используете headless?
ПРЕДУПРЕЖДЕНИЕ:
( headless? driver) ; ; true
Обратите внимание, что это всегда будет верно для экземпляров Phantom.js.
Есть несколько ярлыков для запуска Chrome или Firefox в режиме без головы по умолчанию:
( def driver ( chrome-headless ))
; ; or
( def driver ( firefox-headless {...})) ; ; with extra settings
; ; or
( with-chrome-headless nil driver
( go driver " http://example.com " ))
( with-firefox-headless {...} driver ; ; extra settings
( go driver " http://example.com " ))
Существуют также, when-headless
и when-not-headless
макроз, которые позволяют выполнять кучу команд только в том случае, если браузер находится в режиме без головы или не соответственно:
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
Чтобы подключиться к драйверу, уже работающему на локальном или удаленном хосте, необходимо указать :host
, который может быть либо именем хоста (localhost, kont.remote.host.net), либо IP -адрес (127.0.0.1, 183.102.156.31 ) и :port
. Если порт не указан, установлен порт по умолчанию.
Пример:
; ; Chrome
( def driver ( chrome { :host " 127.0.0.1 " :port 9515 })) ; ; for connection to driver on localhost on port 9515
; ; Firefox
( def driver ( firefox { :host " 192.168.1.11 " })) ; ; the default port for firefox is 4444
Чтобы работать с водителем в Docker, вы можете сделать готовые изображения:
Пример для хрома:
docker run --name chromedriver -p 9515:4444 -d -e CHROMEDRIVER_WHITELISTED_IPS='' robcherry/docker-chromedriver:latest
для Firefox:
docker run --name geckodriver -p 4444:4444 -d instrumentisto/geckodriver
Чтобы подключиться к драйвере, вам просто нужно указать :host
как localhost
или 127.0.0.1
и :port
, на котором он работает. Если порт не указан, установлен порт по умолчанию.
( def driver ( chrome-headless { :host " localhost " :port 9515 :args [ " --no-sandbox " ]}))
( def driver ( firefox-headless { :host " localhost " })) ; ; will try to connect to port 4444
Чтобы установить настройки прокси Использовать переменные среды HTTP_PROXY
/ HTTPS_PROXY
или пройти карту следующего типа:
{ :proxy { :http " some.proxy.com:8080 "
:ftp " some.proxy.com:8080 "
:ssl " some.proxy.com:8080 "
:socks { :host " myproxy:1080 " :version 5 }
:bypass [ " http://this.url " " http://that.url " ]
:pac-url " localhost:8888 " }}
; ; example
( chrome { :proxy { :http " some.proxy.com:8080 "
:ssl " some.proxy.com:8080 " }})
ПРИМЕЧАНИЕ: A: PAC-URL для прокси-файла автоконфигурации. Используется с Safari, поскольку другие опции прокси не работают в этом браузере.
Чтобы точно настроить прокси, вы можете использовать оригинальный объект и передать его возможностям:
{ :capabilities { :proxy { :proxyType " manual "
:proxyAutoconfigUrl " some.proxy.com:8080 "
:ftpProxy " some.proxy.com:8080 "
:httpProxy " some.proxy.com:8080 "
:noProxy [ " http://this.url " " http://that.url " ]
:sslProxy " some.proxy.com:8080 "
:socksProxy " some.proxy.com:1080 "
:socksVersion 5 }}}
( chrome { :capabilities { :proxy {...}}})
С недавними обновлениями библиотека приносит отличную функцию. Теперь вы можете проследить события, которые поступают с панели Devtools. Это означает, что все, что вы видите в консоли разработчика, теперь доступно через API. Это работает только с Google Chrome сейчас.
Чтобы запустить драйвер с указанными специальными настройками разработки, просто передайте пустую карту в поле :dev
при запуске драйвера:
( def c ( chrome { :dev {}}))
Значение не должно быть nil
. Когда это пустая карта, специальная функция принимает значения по умолчанию. Вот полная версия настроек DEV со всеми указанными значениями.
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
Под капюшоном он заполняет специальный словарь perfLoggingPrefs
внутри объекта chromeOptions
.
Теперь, когда ваш браузер накапливает эти события, вы можете прочитать их, используя специальное пространство названий dev
.
( go c " http://google.com " )
; ; wait until the page gets loaded
; ; load the namespace
( require '[etaoin.dev :as dev])
Давайте получим список всех HTTP -запросов, произошедших во время загрузки страницы.
( def reqs ( dev/get-requests c))
; ; reqs is a vector of maps
( count reqs)
; ; 19
; ; what were their types?
( set ( map :type reqs))
; ; #{:script :other :document :image :xhr}
; ; we've got Js requests, images, AJAX and other stuff
; ; check the last one request, it's an image named tia.png
( -> reqs last clojure.pprint/pprint)
{ :state 4 ,
:id " 1000052292.8 " ,
:type :image ,
:xhr? false ,
:url " https://www.gstatic.com/inputtools/images/tia.png " ,
:with-data? nil,
:request
{ :method :get ,
:headers
{ :Referer " https://www.google.com/ " ,
:User-Agent
" Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 " }},
:response
{ :status 200 ,
:headers {}, ; ; truncated
:mime " image/png " ,
:remote-ip " 173.194.73.94 " },
:done? true }
Поскольку мы в основном заинтересованы в запросах AJAX, существует функция get-ajax
, которая выполняет то же самое, но фильтрует запросы XHR:
( -> c dev/get-ajax last clojure.pprint/pprint)
{ :state 4 ,
:id " 1000051989.41 " ,
:type :xhr ,
:xhr? true ,
:url
" https://www.google.com/complete/search?q=clojure%20spec&cp=12&client=psy-ab&xssi=t&gs_ri=gws-wiz&hl=ru&authuser=0&psi=4iUbXdapJsbmrgTVt7H4BA.1562060259137&ei=4iUbXdapJsbmrgTVt7H4BA " ,
:with-data? nil,
:request
{ :method :get ,
:headers
{ :Referer " https://www.google.com/ " ,
:User-Agent
" Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 " }},
:response
{ :status 200 ,
:headers {}, ; ; truncated
:mime " application/json " ,
:remote-ip " 74.125.131.99 " },
:done? true }
Типичный шаблон использования get-ajax
является следующим. Вы хотели бы проверить, был ли определенный запрос на сервер. Поэтому вы нажимаете кнопку, подождите некоторое время, а затем читаете запросы, сделанные вашим браузером.
Налив список запросов, вы ищете тот, который вам нужен (например, по его URL), а затем проверяете его состояние. У :state
Field есть та же семантика, что и XMLHttpRequest.readyState
. Это целое число от 1 до 4 с тем же поведением.
Чтобы проверить, был ли запрос завершен, выполнен или не удастся, используйте эти предикаты:
( def req ( last reqs))
( dev/request-done? req)
; ; true
( dev/request-failed? req)
; ; false
( dev/request-success? req)
; ; true
Обратите внимание на этот request-done?
Это не значит, что запрос преуспел. Это означает, что его трубопровод достиг последнего шага.
Предупреждение: Когда вы читаете журналы Dev, вы потребляете их из внутреннего буфера, который промывается. Второй вызов get-requests
или get-ajax
вернет пустой список.
Возможно, вы хотите собрать эти журналы самостоятельно. Функция dev/get-performance-logs
возвращает список журналов, чтобы вы накапливали их в атом или что-то в этом роде:
( def logs ( atom []))
; ; repeat that form from time to time
( do ( swap! logs concat ( dev/get-performance-logs c))
true )
( count @logs)
; ; 76
Существуют logs->requests
и logs->ajax
, которые преобразуют журналы в запросы. В отличие от get-requests
и get-ajax
, они являются чистыми функциями и ничего не промывают.
( dev/logs->requests @logs)
При работе с журналами и запросами обратите внимание на его подсчет и размер. У карт есть много ключей, и количество предметов в коллекциях может быть огромным. Печать целого куча событий может заморозить ваш редактор. Рассмотрите возможность использования функции clojure.pprint/pprint
поскольку она опирается на максимальный уровень и пределы длины.
Иногда может быть трудно выяснить, что пошло не так во время последней сессии тестов пользовательского интерфейса. Специальный макрос with-postmortem
сохраняет некоторые полезные данные на диске до того, как исключение было запустино. Эти данные представляют собой скриншот, HTML -код и журналы консоли JS. Примечание: не все браузеры поддерживают получение журналов JS.
Пример:
( def driver ( chrome ))
( with-postmortem driver { :dir " /Users/ivan/artifacts " }
( click driver :non-existing-element ))
Исключение поднимется, но в /Users/ivan/artifacts
будет три файла, названные шаблоном <browser>-<host>-<port>-<datetime>.<ext>
:
firefox-127.0.0.1-4444-2017-03-26-02-45-07.png
: фактический скриншот страницы браузера;firefox-127.0.0.1-4444-2017-03-26-02-45-07.html
: HTML-контент нынешнего браузера;firefox-127.0.0.1-4444-2017-03-26-02-45-07.json
: файл JSON с журналами консолей; Это вектор объектов.Обработчик берет карту параметров со следующими ключами. Все они могут отсутствовать.
{ ; ; default directory where to store artifacts
; ; might not exist, will be created otherwise. pwd is used when not passed
:dir " /home/ivan/UI-tests "
; ; a directory where to store screenshots; :dir is used when not passed
:dir-img " /home/ivan/UI-tests/screenshots "
; ; the same but for HTML sources
:dir-src " /home/ivan/UI-tests/HTML "
; ; the same but for console logs
:dir-log " /home/ivan/UI-tests/console "
; ; a string template to format a date; See SimpleDateFormat Java class
:date-format " yyyy-MM-dd-HH-mm-ss " }
Функция (get-logs driver)
возвращает журналы браузера как вектор карт. Каждая карта имеет следующую структуру:
{ :level :warning ,
:message " 1,2,3,4 anonymous (:1) " ,
:timestamp 1511449388366 ,
:source nil,
:datetime #inst " 2017-11-23T15:03:08.366-00:00 " }
В настоящее время журналы доступны только в Chrome и Phantom.js. Обратите внимание, что текст сообщения и тип источника сильно зависят от браузера. Chrome вытирает журналы, как только они будут прочитаны. Phantom.js держит их, но только до тех пор, пока вы не измените страницу.
При запуске экземпляра драйвера карта дополнительных параметров может быть передана, чтобы настроить поведение браузера:
( def driver ( chrome { :path " /path/to/driver/binary " }))
Ниже, вот карта параметров поддержка библиотеки. Все они могут быть пропущены или имеют значения NIL. Некоторые из них, если не прошли, взяты с карты defaults
.
{ ; ; Host and port for webdriver's process. Both are taken from defaults
; ; when are not passed. If you pass a port that has been already taken,
; ; the library will try to take a random one instead.
:host " 127.0.0.1 "
:port 9999
; ; Path to webdriver's binary file. Taken from defaults when not passed.
:path-driver " /Users/ivan/Downloads/geckodriver "
; ; Path to the driver's binary file. When not passed, the driver discovers it
; ; by its own.
:path-browser " /Users/ivan/Downloads/firefox/firefox "
; ; Extra command line arguments sent to the browser's process. See your browser's
; ; supported flags.
:args [ " --incognito " " --app " " http://example.com " ]
; ; Extra command line arguments sent to the webdriver's process.
:args-driver [ " -b " " /path/to/firefox/binary " ]
; ; Sets browser's minimal logging level. Only messages with level above
; ; that one will be collected. Useful for fetching Javascript logs. Possible
; ; values are: nil (aliases :off, :none), :debug, :info, :warn (alias :warning),
; ; :err (aliases :error, :severe, :crit, :critical), :all. When not passed,
; ; :all is set.
:log-level :err ; ; to show only errors but not debug
; ; Sets driver's log level.
; ; The value is a string. Possible values are:
; ; chrome: [ALL, DEBUG, INFO, WARNING, SEVERE, OFF]
; ; phantomjs: [ERROR, WARN, INFO, DEBUG] (default INFO)
; ; firefox [fatal, error, warn, info, config, debug, trace]
:driver-log-level
; ; Paths to the driver's log files as strings.
; ; When not set, the output goes to /dev/null (or NUL on Windows)
:log-stdout
:log-stderr
; ; Path to a custorm browser profile. See the section below.
:profile " /Users/ivan/Library/Application Support/Firefox/Profiles/iy4iitbg.Test "
; ; Env variables sent to the driver's process.
:env { :MOZ_CRASHREPORTER_URL " http://test.com " }
; ; Initial window size.
:size [ 1024 680 ]
; ; Default URL to open. Works only in FF for now.
:url " http://example.com "
; ; Override the default User-Agent. Useful for headless mode.
:user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) "
; ; Where to download files.
:download-dir " /Users/ivan/Desktop "
; ; Driver-specific options. Make sure you have read the docs before setting them.
:capabilities { :chromeOptions { :args [ " --headless " ]}}}
Когда вы перемещаетесь на определенную страницу, драйвер ждет, пока вся страница не будет полностью загружена. Что в большинстве случаев в большинстве случаев не отражает то, как люди взаимодействуют с Интернетом.
Измените это поведение по умолчанию с помощью опции :load-strategy
. Для этого есть три возможных значения :: :none
,: :eager
и :normal
, что является дефолтом, когда не проходит.
Когда вы проходите :none
, драйвер немедленно отвечает, поэтому вы можете выполнить следующие инструкции. Например:
( def c ( chrome ))
( go c " http://some.slow.site.com " )
; ; you'll hang on this line until the page loads
( do-something )
Теперь при передаче опции стратегии загрузки:
( def c ( chrome { :load-strategy :none }))
( go c " http://some.slow.site.com " )
; ; no pause, acts immediately
( do-something )
Для :eager
Option он работает только с Firefox в момент добавления этой функции в библиотеку.
Существует возможность ввести серию ключей одновременно. Это полезно для имитации удержания системного ключа, такого как управление, сдвиг или что -то еще при печати.
Пространство имен etaoin.keys
несет кучу константов ключей, а также набор функций, связанных с вводом.
( require '[etaoin.keys :as keys])
Быстрый пример ввода обычных символов, сдерживающих смену:
( def c ( chrome ))
( go c " http://google.com " )
( fill-active c ( keys/with-shift " caps is great " ))
Основной вход заполняется «Кэпки великолепно». Теперь вы хотите удалить последнее слово. В Chrome это делается путем нажатия обратного пространства, удерживающего alt. Давайте сделаем это:
( fill-active c ( keys/with-alt keys/backspace))
Теперь у вас есть только «Кэпки» - это вход.
Рассмотрим более сложный пример, который повторяет поведение реальных пользователей. Вы хотели бы удалить все из ввода. Во -первых, вы перемещаете камеру в самом начале. Затем переместите его до конца, удерживая сдвиг, чтобы все было выбрано. Наконец, вы нажимаете Delete, чтобы очистить выбранную текст.
Комбинация:
( fill-active c keys/home ( keys/with-shift keys/end) keys/delete)
Есть также with-ctrl
и with-command
функциями, которые действуют одинаково.
Обратите внимание, эти функции не применяются к ярлыкам глобального браузера. Например, ни «Команда + R», ни «Команда + T» не перезагружают страницу или откройте новую вкладку.
Все функции keys/with-*
являются просто обертками на функции keys/chord
, которые могут использоваться для сложных случаев.
Чтобы указать свой собственный каталог, где загружать файлы, пройти :download-dir
в карту опций при запуске драйвера:
( def driver ( chrome { :download-dir " /Users/ivan/Desktop " }))
Теперь, как только вы нажимаете на ссылку, в эту папку следует вставить файл. В настоящее время поддерживаются только Chrome и Firefox.
Firefox требует указания типов MIME из этих файлов, которые следует загружать, не показывая системный диалог. По умолчанию, когда параметр :download-dir
передается, библиотека добавляет наиболее распространенные типы MIME: архивы, медиа-файлы, офисные документы и т. Д. Если вам нужно добавить свой собственный, переопределите это предпочтение вручную:
( def driver ( firefox { :download-dir " /Users/ivan/Desktop "
:prefs { :browser.helperApps.neverAsk.saveToDisk
" some-mime/type-1;other-mime/type-2 " }}))
Чтобы проверить, был ли файл загружен во время тестов пользовательского интерфейса, см. Раздел тестирования ниже.
Установите пользовательский заголовок пользователя-агента с помощью опции :user-agent
при создании драйвера, например:
( def f ( firefox { :user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) " }))
Чтобы получить текущее значение заголовка во время выполнения, используйте функцию:
( get-user-agent f)
; ; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Установка этого заголовка очень важна для браузеров без головы, так как большинство сайтов проверяют, включает ли пользовательский агент «без головы» строку. Это может привести к 403 ответам или какому -то странному поведению сайта.
При запуске Chrome или Firefox вы можете указать специальный профиль, созданный для испытательных целей. Профиль-это папка, которая сохраняет настройки браузера, историю, закладки и другие данные пользователя.
Представьте, что вы хотите запустить свои интеграционные тесты против пользователя, который отключил выполнение JavaScript или рендеринг изображений. Подготовить специальный профиль для этой задачи было бы хорошим выбором.
chrome://version/
страница. Скопируйте путь к файлу, который находится под заголовкой Profile Path
.-P
, -p
или -ProfileManager
, как описывает официальная страница.about:support
. Рядом с заголовком Profile Folder
нажмите кнопку « Show in Finder
. Новое окно папки должно появиться. Скопируйте его путь оттуда. После того, как у вас есть путь профиля, запустите драйвер со специальным ключом :profile
следующим образом:
; ; Chrome
( def chrome-profile
" /Users/ivan/Library/Application Support/Google/Chrome/Profile 2/Default " )
( def chrm ( chrome { :profile chrome-profile}))
; ; Firefox
( def ff-profile
" /Users/ivan/Library/Application Support/Firefox/Profiles/iy4iitbg.Test " )
( def ff ( firefox { :profile ff-profile}))
Библиотека отправляет набор функций для прокрутки страницы.
Самый важный, scroll-query
перепрыгивает первый элемент, найденный с термином запроса:
( def driver ( chrome ))
; ; the form button placed somewhere below
( scroll-query driver :button-submit )
; ; the main article
( scroll-query driver { :tag :h1 })
Чтобы перейти к абсолютному положению, просто используйте scroll
следующим образом:
( scroll driver 100 600 )
; ; or pass a map with x and y keys
( scroll driver { :x 100 :y 600 })
Чтобы относительно прокручивать, используйте scroll-by
с значениями смещения:
; ; keeps the same horizontal position, goes up for 100 pixels
( scroll-by driver 0 -100 ) ; ; map parameter is also supported
Есть два ярлыка, чтобы прыгнуть в верхнюю или нижнюю часть страницы:
( scroll-bottom driver) ; ; you'll see the footer...
( scroll-top driver) ; ; ...and the header again
Следующие функции прокрутите страницу во всех направлениях:
( scroll-down driver 200 ) ; ; scrolls down by 200 pixels
( scroll-down driver) ; ; scrolls down by the default (100) number of pixels
( scroll-up driver 200 ) ; ; the same, but scrolls up...
( scroll-up driver)
( scroll-left driver 200 ) ; ; ...left
( scroll-left driver)
( scroll-right driver 200 ) ; ; ... and right
( scroll-right driver)
Одним из заметок, во всех случаях действия прокрутки обслуживаются с JavaScript. Убедитесь, что ваш браузер включен.
Работая со страницей, вы не можете взаимодействовать с теми элементами, которые помещаются в кадр или iframe. Приведенные ниже функции переключают текущий контекст на конкретном кадре:
( switch-frame driver :frameId ) ; ; now you are inside an iframe with id="frameId"
( click driver :someButton ) ; ; click on a button inside that iframe
( switch-frame-top driver) ; ; switches on the top of the page again
Кадры могут быть вложены одно в другое. Функции принимают это во внимание. Скажем, у вас есть макет HTML, как это:
< iframe src =" ... " >
< iframe src =" ... " >
< button id =" the-goal " >
</ frame >
</ frame >
Таким образом, вы можете достичь кнопки со следующим кодом:
( switch-frame-first driver) ; ; switches to the first top-level iframe
( switch-frame-first driver) ; ; the same for an iframe inside the previous one
( click driver :the-goal )
( switch-frame-parent driver) ; ; you are in the first iframe now
( switch-frame-parent driver) ; ; you are at the top
Чтобы уменьшить количество строк кода, существует особый with-frame
. Это временные переключатели кадры при выполнении корпуса, возвращающего свое последнее выражение, и переключаясь на предыдущий кадр впоследствии.
( with-frame driver { :id :first-frame }
( with-frame driver { :id :nested-frame }
( click driver { :id :nested-button })
42 ))
Приведенный выше код возвращает 42
, оставаясь в том же кадре, что и до оценки макросов.
Чтобы оценить код JavaScript в браузере, запустите:
( js-execute driver " alert(1) " )
Вы можете передать любые дополнительные параметры в вызов, и катает их внутри сценария с arguments
похожим на массив, объект:
( js-execute driver " alert(arguments[2].foo) " 1 false { :foo " hello! " })
В результате, hello!
Строка появится внутри диалога.
Чтобы вернуть любые данные в Clojure, просто добавьте return
в свой сценарий:
( js-execute driver " return {foo: arguments[2].foo, bar: [1, 2, 3]} "
1 false { :foo " hello! " })
; ; {:bar [1 2 3], :foo "hello!"}
Если ваш скрипт выполняет запросы AJAX или работает в setTimeout
или в любых других асинхронных вещах, вы не можете просто return
результат. Вместо этого следует вызвать специальный обратный вызов против данных, которые вы хотели бы достичь. Webdriver рассматривает этот обратный вызов в качестве последнего аргумента для вашего сценария и может быть связан с объектом, похожим на arguments
.
Пример:
( js-async
driver
" var args = arguments; // preserve the global args
var callback = args[args.length-1];
setTimeout(function() {
callback(args[0].foo.bar.baz);
},
1000); "
{ :foo { :bar { :baz 42 }}})
Возвращает 42
в код Clojure.
Чтобы оценить асинхронный сценарий, вам нужно либо настроить особый тайм -аут для этого:
( set-script-timeout driver 5 ) ; ; in seconds
или оберните код в макросы, которые делают это временно:
( with-script-timeout driver 30
( js-async driver " some long script " ))
Основное различие между программой и человеком заключается в том, что первое работает очень быстро. Это значит так быстро, что иногда браузер не может представить новый HTML во времени. Таким образом, после каждого действия вам лучше поместить wait-<something>
функция, которая просто опробовает браузер, пока предикат не оценивается в True. Или просто (wait <seconds>)
, если вы не заботитесь об оптимизации.
Макро with-wait
может быть полезным, когда вам нужно придумать каждое действие (wait n)
. Например, следующая форма
( with-chrome {} driver
( with-wait 3
( go driver " http://site.com " )
( click driver { :id " search_button " })))
превращается в что -то вроде этого:
( with-chrome {} driver
( wait 3 )
( go driver " http://site.com " )
( wait 3 )
( click driver { :id " search_button " }))
и, таким образом, возвращает результат последней формы исходного тела.
Существует еще один макрос (doto-wait n driver & body)
, который действует как стандартный doto
, но придает каждую форму (wait n)
. Например:
( with-chrome {} driver
( doto-wait 1 driver
( go " http://site.com " )
( click :this-link )
( click :that-button )
...etc))
Последняя форма будет что -то вроде этого:
( with-chrome {} driver
( doto driver
( wait 1 )
( go " http://site.com " )
( wait 1 )
( click :this-link )
( wait 1 )
( click :that-button )
...etc))
В дополнение к with-wait
и do-wait
есть ряд функций ожидания: wait-visible
, wait-has-alert
, wait-predicate
и т. Д. (См. Полный список в разделе Corressing). Они принимают временные значения по умолчанию/интервалы, которые можно пересмотреть с помощью макросов with-wait-timeout
и with-wait-interval
, соответственно.
Пример из теста EtAoin:
( deftest test-wait-has-text
( testing " wait for text simple "
( with-wait-timeout 15 ; ; time in seconds
( doto *driver*
( refresh )
( wait-visible { :id :document-end })
( click { :id :wait-button })
( wait-has-text :wait-span " -secret- " ))
( is true " text found " ))))
Подождите текст:
wait-has-text
ожидает, пока в нем нет текста, в том числе внутренний HTML).
( wait-has-text driver :wait-span " -secret- " )
wait-has-text-everywhere
, как wait-has-text
но ищет текст по всей странице
( wait-has-text-everywhere driver " -secret- " )
Чтобы ваш тест не зависел друг от друга, вам нужно обернуть их в приспособление, которое создаст новый экземпляр драйвера и правильно его выключил в конце.
Хорошее решение может состоять в том, чтобы иметь глобальную переменную (несвязанную по умолчанию), которая будет указывать на целевой драйвер во время тестов.
( ns project.test.integration
" A module for integration tests "
( :require [clojure.test :refer :all ]
[etaoin.api :refer :all ]))
( def ^:dynamic *driver*)
( defn fixture-driver
" Executes a test running a driver. Bounds a driver
with the global *driver* variable. "
[f]
( with-chrome {} driver
( binding [*driver* driver]
( f ))))
( use-fixtures
:each ; ; start and stop driver for each test
fixture-driver)
; ; now declare your tests
( deftest ^:integration
test-some-case
( doto *driver*
( go url-project)
( click :some-button )
( refresh )
...
))
Если по какой -то причине вы хотите использовать один экземпляр, вы можете использовать такие светильники, как это:
( ns project.test.integration
" A module for integration tests "
( :require [clojure.test :refer :all ]
[etaoin.api :refer :all ]))
( def ^:dynamic *driver*)
( defn fixture-browser [f]
( with-chrome-headless { :args [ " --no-sandbox " ]} driver
( disconnect-driver driver)
( binding [*driver* driver]
( f ))
( connect-driver driver)))
; ; creating a session every time that automatically erases resources
( defn fixture-clear-browser [f]
( connect-driver *driver*)
( go *driver* " http://google.com " )
( f )
( disconnect-driver *driver*))
; ; this is run `once` before running the tests
( use-fixtures
:once
fixture-browser)
; ; this is run `every` time before each test
( use-fixtures
:each
fixture-clear-browser)
...some tests
Для более быстрого тестирования вы можете использовать этот пример:
.....
( defn fixture-browser [f]
( with-chrome-headless { :args [ " --no-sandbox " ]} driver
( binding [*driver* driver]
( f ))))
; ; note that resources, such as cookies, are deleted manually,
; ; so this does not guarantee that the tests are clean
( defn fixture-clear-browser [f]
( delete-cookies *driver*)
( go *driver* " http://google.com " )
( f ))
......
В приведенном выше примере мы рассмотрели случай, когда вы запускаете тесты против одного типа драйвера. Тем не менее, вы можете проверить свой сайт на нескольких драйверах, скажем, Chrome и Firefox. В этом случае ваш приспособление может стать немного сложнее:
( def driver-type [ :firefox :chrome ])
( defn fixture-drivers [f]
( doseq [type driver-types]
( with-driver type {} driver
( binding [*driver* driver]
( testing ( format " Testing in %s browser " ( name type))
( f ))))))
Теперь каждый тест будет проходить дважды в браузерах Firefox и Chrome. Обратите внимание, что тестовый вызов готовят с помощью макроса testing
, который вкладывает имя драйвера в отчет. После того, как вы получите ошибку, вы легко найдете, какой драйвер не удалось точно, тесты точно.
Чтобы сохранить некоторые артефакты в случае исключения, оберните тело вашего теста with-postmortem
Handler следующим образом:
( deftest test-user-login
( with-postmortem *driver* { :dir " /path/to/folder " }
( doto *driver*
( go " http://127.0.0.1:8080 " )
( click-visible :login )
; ; any other actions...
)))
Теперь, если в этом тесте произойдет какое -либо исключение, артефакты будут сохранены.
Чтобы не копировать и вставить карту параметров, объявьте ее в верхней части модуля. Если вы используете Circle CI, было бы здорово сохранить данные в специальном каталоге артефактов, который может быть загружен в виде zip -файла после завершения сборки:
( def pm-dir
( or ( System/getenv " CIRCLE_ARTIFACTS " ) ; ; you are on CI
" /some/local/path " )) ; ; local machine
( def pm-opt
{ :dir pm-dir})
Теперь передайте эту карту повсюду в обработчик PM:
; ; test declaration
( with-postmortem *driver* pm-opt
; ; test body goes here
)
Как только возникает ошибка, вы найдете изображение PNG, которое представляет страницу вашего браузера в момент исключения и дамп HTML.
Поскольку тесты пользовательского интерфейса могут занять много времени для прохождения, это определенно хорошая практика, чтобы самостоятельно пройти тесты на сервер и пользовательского интерфейса.
Во -первых, добавьте ^:integration
тега ко всем тестам, которые запускаются в браузере, которые следует за следующим:
( deftest ^:integration
test-password-reset-pipeline
( doto *driver*
( go url-password-reset)
( click :reset-btn )
...
Затем откройте файл project.clj
и добавьте селекторы тестирования:
:test-selectors { :default ( complement :integration )
:integration :integration }
Теперь, как только вы запустите lein test
вы запустите все тесты, кроме браузеров. Чтобы запустить интеграционные тесты, запустите lein test :integration
.
Основное различие между программой и человеком заключается в том, что первое работает очень быстро. Это значит так быстро, что иногда браузер не может представить новый HTML во времени. Таким образом, после каждого действия вам необходимо поставить wait-<something>
функция, которая просто опробовает браузер, проверяя предикат. O Просто (wait <seconds>)
, если вы не заботитесь об оптимизации.
Иногда файл начинает автоматически загружать, когда вы нажимаете на ссылку или просто посещали какую -то страницу. В тестах вам необходимо убедиться, что файл действительно был успешно загружен. Общим сценарием будет:
Пример:
; ; Local helper that checks whether it is really an Excel file.
( defn xlsx? [file]
( -> file
.getAbsolutePath
( str/ends-with? " .xlsx " )))
; ; Top-level declarations
( def DL-DIR " /Users/ivan/Desktop " )
( def driver ( chrome { :download-dir DL-DIR}))
; ; Later, in tests...
( click-visible driver :download-that-application )
( wait driver 7 ) ; ; wait for a file has been downloaded
; ; Now, scan the directory and try to find a file:
( let [files ( file-seq ( io/file DL-DIR))
found ( some xlsx? files)]
( is found ( format " No *.xlsx file found in %s directory. " DL-DIR)))
EtAoin может воспроизводить файлы, созданные Selenium IDE. Это официальная утилита, чтобы создавать сценарии интерактивно. IDE поступает как расширение вашего браузера. После установки он записывает ваши действия в файл JSON с расширением .side
. Вы можете сохранить этот файл и запустить его с помощью EtAoin.
Давайте представим, что вы установили IDE и записали некоторые действия в качестве официальной документации. Теперь, когда у вас есть файл test.side
, сделайте это:
( require '[etaoin.ide.flow :as flow])
( def driver ( chrome ))
( def ide-file ( io/resource " ide/test.side " ))
( def opt
{ ; ; The base URL redefines the one from the file.
; ; For example, the file was written on the local machine
; ; (http://localhost:8080), and we want to perform the scenario
; ; on staging (https://preprod-001.company.com)
:base-url " https://preprod-001.company.com "
; ; keywords :test-.. and :suite-.. (id, ids, name, names)
; ; are used to select specific tests. When not passed,
; ; all tests get run. For example:
:test-id " xxxx-xxxx... " ; ; a single test by its UUID
:test-name " some-test " ; ; a single test by its name
:test-ids [ " xxxx-xxxx... " , ...] ; ; multiple tests by their ids
:test-names [ " some-test1 " , ...] ; ; multiple tests by their names
; ; the same for suites:
:suite-id ...
:suite-name ...
:suite-ids [...]
:suite-names [...]})
( flow/run-ide-script driver ide-file opt)
Все, что связано с IDE, хранится в пакете etaoin.ide
.
Вы также можете запустить сценарий из командной строки. Вот пример lein run
:
lein run -m etaoin.ide.main -d firefox -p ' {:port 8888 :args ["--no-sandbox"]} ' -r ide/test.side
А также из Uberjar. В этом случае etaoin должен быть в первичных зависимостях, а не в :dev
или :test
.
java -cp .../poject.jar -m etaoin.ide.main -d firefox -p ' {:port 8888} ' -f ide/test.side
Мы поддерживаем следующие аргументы (проверяем их, используя команду lein run -m etaoin.ide.main -h
):
-d, --driver-name name :chrome The name of driver. The default is `:chrome`
-p, --params params {} Parameters for the driver represented as an
EDN string, e.g '{:port 8080}'
-f, --file path Path to an IDE file on disk
-r, --resource path Path to an IDE resource
--test-ids ids Comma-separeted test ID(s)
--suite-ids ids Comma-separeted suite ID(s)
--test-names names Comma-separeted test name(s)
--suite-names names Comma-separeted suite name(s)
--base-url url Base URL for tests
-h, --help
Обратите внимание на --params
. Это должна быть строка EDN, представляющая карту Clojure. Это та же самая карта, которую вы передаете в драйвер, когда инициируете ее.
Обратите внимание, что поддержка IDE все еще экспериментальна. Если вы сталкиваетесь с неожиданным поведением, не стесняйтесь открывать проблему. На данный момент мы поддерживаем Chrome и Firefox для файлов IDE.
Пример:
etaoin.api> ( def driver ( chrome ))
#'etaoin.api/driver
etaoin.api> ( maximize driver)
ExceptionInfo throw+: { :response {
:sessionId " 2672b934de785aabb730fd19330cf40c " ,
:status 13 ,
:value { :message " unknown error: cannot get automation extension n from unknown error: page could not be found: chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/_generated_background_page.html n
(Session info: chrome=57.0.2987.133) n (Driver info: chromedriver=2.27.440174
(e97a722caafc2d3a8b807ee115bfb307f7d2cfd9),platform=Mac OS X 10.11.6 x86_64) " }},
...
Решение: просто обновите свой chromedriver
до последней версии. Протестировано с 2,29, работает нормально. Люди говорят, что это тоже с 2,28.
Помните, что менеджер пакетов brew
имеет устаревшую версию 2.27. Вам, вероятно, придется скачать двоичные файлы с официального сайта.
См. Связанный вопрос в проекте Selenium.
При передаче вектороподобного запроса, скажем [{:tag :p} "//*[text()='foo')]]"}]
будьте осторожны с рукописными выражениями XPath. В векторе каждый его выражение ищет из предыдущего в цикле. Здесь есть скрытая ошибка: без ведущей точки, пункт "//..."
означает найти элемент из корня всей страницы. С точкой это означает найти из текущего узла, который является одним из предыдущего запроса, и так далее.
Вот почему, легко выбрать что -то совершенно другое, что вы хотели бы. Правильным выражением будет: [{:tag :p} ".//*[text()='foo')]]"}]
.
Пример:
etaoin.api> ( click driver :some-id )
ExceptionInfo throw+: { :response {
:sessionId " d112ce8ddb49accdae78a769d5809eae " ,
:status 11 ,
:value { :message " element not visible n (Session info: chrome=57.0.2987.133) n
(Driver info: chromedriver=2.29.461585
(0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b),platform=Mac OS X 10.11.6 x86_64) " }},
...
Решение: вы пытаетесь щелкнуть элемент, который не виден, или его раздувание так же мало, как невозможно, чтобы человек нажимал на него. Вы должны пройти еще один селектор.
Проблема: Когда вы сосредотачиваетесь на другом окне, сеанс Webdriver, который запускается под провалом Google Chrome.
Решение: Google Chrome может приостановить вкладку, когда она была неактивной в течение некоторого времени. Когда страница приостановлена, на ней не может быть сделана операция. Нет кликов, выполнения JS и т. Д. Постарайтесь, чтобы окно хрома активным во время тестового сеанса.
Проблема: Когда вы пытаетесь запустить драйвер, вы получите ошибку:
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
Синтаксическая ошибка (ExceptionInfo) Компиляция At (Repl: 1: 13). Throw+: {: response {: value {: error "неизвестная ошибка",: сообщение "Неверный аргумент: не удается убить выходящий процесс" ....
Возможная причина: «Запуск Firefox как root в сеансе обычного пользователя не поддерживается»
Решение: чтобы проверить, запустите драйвер с помощью пути к файлам журналов и уровнем журнала «трассировки» и изучите их вывод.
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
Подобная проблема: Mozilla/Geckodriver#1655
Проблема: Когда вы пытаетесь запустить Chromedriver, вы получите ошибку:
clojure.lang.exceptionInfo: throw+: {: response {: sessionId "....." ,: status 13 ,: value {: сообщение "Неизвестная ошибка: Chrom Файл не существует) ...
Возможная причина:
Распространенной причиной для сбоя Chrome во время запуска является запущенный Chrome в качестве пользователя root (администратора) на Linux. Несмотря на то, что при создании сеанса WebDriver можно обойти эту проблему, передавая флаг-нет сандбокс, такая конфигурация не поддерживается и сильно обескуражена. You need to configure your environment to run Chrome as a regular user instead.
Solution: Run driver with an argument --no-sandbox
. Осторожность! This is a bypass OS security model.
( def driver ( chrome { :args [ " --no-sandbox " ]}))
A similar problem is described here
The etaoin.api2
namespace brings some bits of alternative macros and functions. They provide better syntax and live in a separate namespace to prevent the old API from breaking.
At the moment, the api2
module provides a set of with-...
macros with a let
-like binding form:
( ns ...
( :require
[etaoin.api :as api]
[etaoin.api2 :as api2]))
( api2/with-chrome [driver {}]
( api/go driver " http://ya.ru " ))
The options map can be skipped so you have only a binding symbol:
( api2/with-firefox [ff]
( api/go ff " http://ya.ru " ))
The project is open for your improvements and ideas. If any of unit tests fall on your machine please submit an issue giving your OS version, browser and console output.
Copyright © 2017—2020 Ivan Grishaev.
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.