WebDriver协议的纯Clojure实现。使用该库自动化浏览器,测试您的前端行为,模拟人类的动作或您想要的任何内容。
它以Etaoin Shrdlu的名字命名,这是一台在其上产生了一个神秘音符后活着的打字机。
由于0.4.0
,驱动程序实例是地图,但不像以前那样的原子。这是一个很难决定的解决方案,但是我们已经摆脱了原子来遵循我们的代码中的Clojure方式。一般而言,您永远不会删除驾驶员或在其中存储东西。现在,用于修改实例的所有内部功能现在只需返回新版本的地图即可。如果您有swap!
或驱动程序代码中类似的内容,请在更新之前对代码进行重构。
由于0.4.0
,库支持WebDriver操作。操作是批处理发送给驱动程序的命令。请参阅TOC中的详细相关部分。
由于0.4.0
,Etaoin可以播放在Interactive Selenium IDE中创建的脚本文件。请参阅下面的相关部分。
Ct t
进行单元测试。欢迎您将您的公司提交该列表。
安装有两个步骤:
etaoin
安装到您的Clojure代码中将以下内容添加到:dependencies
project.clj
文件中的依赖项向量:
[etaoin "0.4.6"]
与Clojure 1.9及以上合作。
此页面提供了有关如何安装驱动程序的说明,您需要自动化浏览器。
安装Chrome和Firefox浏览器,从官方网站下载它们。在所有平台上都不会有问题。
安装您需要的特定驱动程序:
Google Chrome驱动程序:
brew cask install chromedriver
2.28
版。 2.27
及以下具有与最大化窗口有关的错误(请参阅[[[故障排除]])。Geckodriver,Firefox的驱动程序:
brew install geckodriver
phantom.js浏览器:
brew install phantomjs
Safari驱动程序(仅适用于Mac):
现在,检查您的安装启动这些命令中的任何一个。对于每个命令,应启动使用本地HTTP服务器的无尽进程。
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
您可以为此库启动进行测试:
lein test
您会看到浏览器窗口串联打开和关闭。测试使用具有特殊布局的本地HTML文件来验证大多数情况。
如果您有问题,请参见下面的故障排除部分
您可能会直接从repl中自动化浏览器的好消息:
( 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)
您会看到,任何功能都需要驱动程序实例作为第一个参数。因此,您可以使用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)
任何指示元素ID的其他关键字。对于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-th div
标签
( query driver { :tag :div :index 1 })
; ; expands into .//div[1]
找到具有类属性等于active
标签a
( 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
,whting):
( 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'] " ])
有时,您可能需要与查询的第n个元素进行交互,例如,在要单击此示例中的第二个链接时:
< 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 >
在这种情况下,您可以使用以下XPath表达式支持的:index
指令:
( click driver [{ :tag :li :class :search-result :index 2 } { :tag :a }])
或者,您可以将nth-Child Trick与CSS表达式使用:
( click driver { :css " li.search-result:nth-child(2) a " })
最后,也可以通过使用query-all
直接获得nth元素:
( 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)很少在Web中使用双击,但可以使用双击。
( 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 })
在新的背景选项卡中打开链接时,中间鼠标单击很有用。右键单击有时用于模仿Web应用程序中的上下文菜单。
图书馆支持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)
单击文件输入按钮打开一个特定于OS的对话框,您不允许使用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启动了支持名为Headless Mode的功能。当无头时,屏幕上没有任何UI窗口出现,只有Stdout输出进入控制台。此功能使您可以在没有图形输出设备的服务器上运行集成测试。
通过检查是否接受--headles
从终端运行它时,请确保浏览器是否接受无头模式。 Phantom.js驱动程序的性质无头(从未用于渲染UI)。
启动驱动程序时,通过: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
Macroses允许执行一堆命令:
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
要连接到已经在本地或远程主机上运行的驱动程序,您必须指定可能是主机名(Localhost,some.remote.host.net)或IP地址(127.0.0.1,183.102.156.31)的:host
)和: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中的驾驶员一起工作,您可以拍摄现成的图像:
Chrome的示例:
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 " }})
注意:答: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
。当它是一个空的地图时,特殊功能会默认。这是开发设置的完整版本,并指定了所有可能的值。
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
在引擎盖下,它填充了chromeOptions
对象中的特殊perfLoggingPrefs
字典。
现在,您的浏览器会累积这些事件,您可以使用特殊的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?
并不意味着该请求已经成功。这仅意味着其管道已经达到最后一步。
警告:当您阅读开发日志时,您会从潮红的内部缓冲区中消耗它们。第二个呼叫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
函数,因为它依赖于最大级别和长度限制。
有时,在上次UI测试会议期间发现出了问题可能很难。在触发异常之前,特殊的宏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中可用。请注意,消息文本和源类型高度取决于浏览器。镀铬一旦阅读了日志。 phantom.js保留它们,但仅在更改页面之前。
运行驱动程序实例时,可能会传递其他参数的地图以调整浏览器的行为:
( def driver ( chrome { :path " /path/to/driver/binary " }))
下面,这是库支持的参数地图。所有这些都可能被跳过或具有零值。其中一些(如果未通过)取自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
选项,它仅在将功能添加到库中时才与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中,这是通过按下BackSpace Holding 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 " }}))
要检查在UI测试期间是否下载文件,请参见下面的测试部分。
例如,在创建驱动程序时,设置一个自定义的用户代理标头,则使用: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
键运行firefox。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将此回调作为您的脚本的最后一个参数传递,并且可以使用类似于Array的对象来达到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>)
,如果您不关心优化。
当您需要使用(wait n)
进行每个操作时with-wait
宏可能会有所帮助。例如,以下形式
( 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
等(请参阅“相关”部分中的完整列表)。他们接受默认超时/间隔值,可以分别使用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
处理程序中,如下所示:
( 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,将数据保存到特殊的文物目录中将是很棒的,该目录一旦构建完成后,该数据可能会以邮政为文件下载:
( 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
)
发生错误后,您将找到一个在异常和HTML转储时代表您的浏览器页面的PNG图像。
由于UI测试可能需要花费大量时间来通过,因此彼此独立通过服务器和UI测试绝对是一个好习惯。
首先,添加^: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可以播放硒IDE产生的文件。这是一种官方实用程序,可以交互创建方案。 IDE是您浏览器的扩展。安装后,它将您的操作记录到带有.side
扩展程序的JSON文件中。您可以保存该文件并使用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
一家。这必须是代表Clojure地图的EDN字符串。那是您在启动驱动程序时传递到驱动程序的地图。
请注意,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
Package Manager拥有过时的2.27版。您可能必须从官方网站下载二进制文件。
请参阅Selenium项目中的相关问题。
传递类似矢量的查询时,说[{:tag :p} "//*[text()='foo')]]"}]
。在矢量中,其每个表达式都会从上一个循环中搜索。这里有一个隐藏的错误:没有领先点, "//..."
子句意味着从整个页面的根部找到一个元素。使用一个点,这意味着从当前节点中找到是从上一个查询等节点等。
这就是为什么,很容易选择完全不同的东西。适当的表达方式是: [{: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) " }},
...
解决方案:您正在尝试单击一个不可见的元素,或者它的布置却少了,因为人类不可能单击它。您应该通过另一个选择器。
问题:当您专注于其他窗口时,在Google Chrome下运行的WebDriver会话失败。
解决方案: Google Chrome在一段时间内无效时可能会暂停选项卡。当该页面被暂停时,无法对其进行操作。无点击,JS执行等。因此,请尝试在测试会话期间保持Chrome窗口活动。
问题:当您尝试启动驱动程序时,您会遇到错误:
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
语法错误(exceptionInfo)在(repl:1:13)上编译。 throw+:{:wendesp {:value {:错误“未知错误”,:message“无效参数:不能杀死退出的过程” ...
可能的原因:“不支持常规用户会话中的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+:{:wendesp {:sessionid“ .....文件不存在)...
可能的原因:
在启动期间,Chrome崩溃的一个常见原因是在Linux上运行Chrome作为root用户(管理员)。虽然可以通过传递-NO-Sandbox标志在创建WebDriver会话时解决此问题,但是这种配置是不支持的,并且高度灰心。您需要配置环境以将Chrome作为常规用户运行。
解决方案:带有参数的驱动程序--no-sandbox
。警告!这是一个旁路操作系统安全模型。
( def driver ( chrome { :args [ " --no-sandbox " ]}))
这里描述了类似的问题
etaoin.api2
命名空间带来了一些替代宏和功能。 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.