Implementação pura de clojure do protocolo Webdriver. Use essa biblioteca para automatizar um navegador, testar o comportamento do seu front -end, simular ações humanas ou o que quiser.
Ele recebeu o nome de Etaoin Shrdlu - uma máquina de digitação que ficou viva depois que uma nota de mistérios foi produzida nela.
Desde 0.4.0
, a instância do driver é um mapa, mas não um átomo como costumava ser. Foi uma solução difícil de decidir, mas nos livramos do Atom para seguir o Clojure Way em nosso código. De um modo geral, você nunca descreve um motorista ou armazena algo dentro dele. Todas as funções internas usadas para modificar a instância agora retornam uma nova versão de um mapa. Se você tem swap!
Ou algo semelhante em seu código para o driver, refator seu código antes de atualizar.
Desde 0.4.0
, a biblioteca suporta ações do WebDriver. Ações são comandos enviados ao motorista em lote. Consulte a seção relacionada detalhada no TOC.
Desde 0.4.0
, o etaoin pode reproduzir arquivos de script criados no IDE interativo de selênio. Veja a seção relacionada abaixo.
Ct t
como de costume.Você pode enviar sua empresa para essa lista.
Existem duas etapas para instalação:
etaoin
no seu código de clojure Adicione o seguinte :dependencies
em seu projeto project.clj
:
[etaoin "0.4.6"]
Trabalha com Clojure 1.9 e acima.
Esta página fornece instruções sobre como instalar drivers necessários para automatizar seu navegador.
Instale os navegadores Chrome e Firefox, baixando -os nos sites oficiais. Não haverá um problema em todas as plataformas.
Instale drivers específicos que você precisa:
Driver do Google Chrome:
brew cask install chromedriver
para usuários de Mac2.28
versão instalada. 2.27
e abaixo tem um bug relacionado à maximização de uma janela (consulte [[Solução de problemas]]).Geckodriver, um motorista para o Firefox:
brew install geckodriver
para usuários de MacNavegador Phantom.js:
brew install phantomjs
para usuários de MacDriver Safari (somente para Mac):
Agora, verifique sua instalação iniciando qualquer um desses comandos. Para cada comando, um processo sem fim com um servidor HTTP local deve iniciar.
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
Você pode executar testes para este lançamento da biblioteca:
lein test
Você verá as janelas do navegador abertas e fechadas em série. Os testes usam um arquivo HTML local com um layout especial para validar a maioria dos casos.
Veja abaixo a seção Solução de problemas se tiver problemas
A boa notícia que você pode automatizar seu navegador diretamente do 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)
Veja bem, qualquer função requer uma instância do driver como o primeiro argumento. Portanto, você pode simplificá -lo usando 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 ))
Nesse caso, seu código parece um DSL projetado apenas para fins.
Você pode usar fill-multi
para encurtar o código como:
( fill driver :login " login " )
( fill driver :password " pass " )
( fill driver :textarea " some text " )
em
( fill-multi driver { :login " login "
:password " pass "
:textarea " some text " })
Se ocorrer alguma exceção durante uma sessão do navegador, o processo externo poderá ficar para sempre até que você o mate manualmente. Para evitá-lo, use macros with-<browser>
da seguinte forma:
( with-firefox {} ff ; ; additional options first, then bind name
( doto ff
( go " https://google.com " )
...))
Aconteça o que acontecer durante uma sessão, o processo será interrompido de qualquer maneira.
A maioria das funções como click
, fill
, etc requer um termo de consulta para descobrir um elemento em uma página. Por exemplo:
( click driver { :tag :button })
( fill driver { :id " searchInput " } " Clojure " )
A biblioteca suporta os seguintes tipos e valores de consulta.
:active
significa o elemento ativo atual. Ao abrir a página do Google, por exemplo, ele concentra o cursor na entrada principal de pesquisa. Portanto, não há necessidade de clicar manualmente. Exemplo:
( fill driver :active " Let's search for something " keys/enter)
Qualquer outra palavra -chave que indique o ID de um elemento. Para o Google Page, é :lst-ib
ou "lst-ib"
(strings também são suportadas). O registro é importante. Exemplo:
( fill driver :lst-ib " What is the Matrix? " keys/enter)
uma string com uma expressão XPath. Tenha cuidado ao escrevê -los manualmente. Verifique a seção Troubleshooting
abaixo. Exemplo:
( fill driver " .//input[@id='lst-ib'][@name='q'] " " XPath in action! " keys/enter)
Um mapa com :xpath
ou :css
com uma expressão de string da sintaxe correspondente. Exemplo:
( fill driver { :xpath " .//input[@id='lst-ib'] " } " XPath selector " keys/enter)
( fill driver { :css " input#lst-ib[name='q'] " } " CSS selector " keys/enter)
Consulte o Manual do Seletor CSS para obter mais informações.
Uma consulta pode ser qualquer outro mapa que represente uma expressão XPath como dados. As regras são:
:tag
representa o nome de uma tag. Torna -se *
quando não passou.:index
se expande na cláusula [x]
à direita. Útil quando você precisa selecionar uma terceira linha de uma tabela, por exemplo.:fn/
namespace e se expande para algo específico.Exemplos:
Encontre a primeira tag div
( query driver { :tag :div })
; ; expands into .//div
Encontre a tag n -ª div
( query driver { :tag :div :index 1 })
; ; expands into .//div[1]
Encontre a tag a
com o atributo de classe é igual ao active
( query driver { :tag :a :class " active " })
; ; ".//a[@class="active"]"
Encontre um formulário por seus atributos:
( query driver { :tag :form :method :GET :class :message })
; ; expands into .//form[@method="GET"][@class="message"]
Encontre um botão por seu texto (correspondência exata):
( query driver { :tag :button :fn/text " Press Me " })
; ; .//button[text()="Press Me"]
Encontre um enésimo elemento ( p
, a
, o que for) com o texto "Download":
( query driver { :fn/has-text " download " :index 3 })
; ; .//*[contains(text(), "download")][3]
Encontre um elemento que tenha a seguinte classe:
( query driver { :tag :div :fn/has-class " overlay " })
; ; .//div[contains(@class, "overlay")]
Encontre um elemento que tenha o seguinte domínio em um HREF:
( query driver { :tag :a :fn/link " google.com " })
; ; .//a[contains(@href, "google.com")]
Encontre um elemento que tenha as seguintes aulas de uma só vez:
( query driver { :fn/has-classes [ :active :sticky :marked ]})
; ; .//*[contains(@class, "active")][contains(@class, "sticky")][contains(@class, "marked")]
Encontre os widgets de entrada ativados/desativados:
; ; 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()]
Uma consulta pode ser um vetor que consiste em quaisquer expressões mencionadas acima. Em tal consulta, todo o próximo termo pesquisa de uma de maneira anterior.
Um exemplo simples:
( click driver [{ :tag :html } { :tag :body } { :tag :a }])
Você também pode combinar expressões XPath e CSS (preste atenção em um ponto líder na expressão XPath:
( click driver [{ :tag :html } { :css " div.class " } " .//a[@class='download'] " ])
Às vezes, pode ser necessário interagir com o enésimo elemento de uma consulta, por exemplo, ao querer clicar no segundo link neste exemplo:
< 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 >
Nesse caso, você pode usar a Diretiva :index
que é suportada para expressões XPath como esta:
( click driver [{ :tag :li :class :search-result :index 2 } { :tag :a }])
Ou você pode usar o truque do enésimo filho com a expressão de CSS como esta:
( click driver { :css " li.search-result:nth-child(2) a " })
Finalmente, também é possível obter o enésimo elemento diretamente usando query-all
:
( click-el driver ( nth ( query-all driver { :css " li.search-result a " }) 2 ))
Observe o uso do click-el
aqui, pois query-all
retorna um elemento, não um seletor que pode ser passado para click
diretamente.
query-tree
leva seletores e age como uma árvore. Todo próximo seletor consulta elementos dos anteriores. O seletor de punho conta com elementos de localização e os demais usam os elementos encontrados
( query-tree driver { :tag :div } { :tag :a })
significa
{:tag :div} -> [div1 div2 div3]
div1 -> [a1 a2 a3]
div2 -> [a4 a5 a6]
div3 -> [a7 a8 a9]
Portanto, o resultado será [A1 ... A9]
Para interagir com os elementos encontrados por meio de uma consulta, você deve passar o resultado da consulta para click-el
ou fill-el
:
( click-el driver ( first ( query-all driver { :tag :a })))
Portanto, você pode coletar elementos em um vetor e interagir arbitrariamente com eles a qualquer momento:
( 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! " )
Para fins de emular a entrada humana, você pode usar a função fill-human
. As seguintes opções são ativadas por padrão:
{ :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
E você pode redefini -los:
( 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)
Para entrada múltipla com a emulação humana, use fill-human-multi
( fill-human-multi driver { :login " login "
:pass " password "
:textarea " some text " }
{ :mistake-prob 0.5
:pause-max 1 })
A função click
aciona o mouse esquerdo Clique em um elemento encontrado por um termo de consulta:
( click driver { :tag :button })
A função click
usa apenas o primeiro elemento encontrado pela consulta, que às vezes leva a clicar nos itens errados. Para garantir que exista um e apenas um elemento encontrado, use a função click-single
. Ele age da mesma forma, mas levanta uma exceção ao consultar a página retorna vários elementos:
( click-single driver { :tag :button :name " search " })
Um clique duplo é usado raramente na web, mas é possível com a função double-click
(Chrome, Phantom.js):
( double-click driver { :tag :dbl-click-btn })
Há também um monte de funções de clique "cegas". Eles desencadeiam cliques no mouse na posição atual do mouse:
( left-click driver)
( middle-click driver)
( right-click driver)
Outro grupo de funções faz o mesmo, mas mova o ponteiro do mouse para um elemento especificado antes de clicar neles:
( left-click-on driver { :tag :img })
( middle-click-on driver { :tag :img })
( right-click-on driver { :tag :img })
Um clique do mouse do meio é útil ao abrir um link em uma nova guia de fundo. Às vezes, o clique com o botão direito é usado para imitar um menu de contexto em aplicativos da Web.
A biblioteca suporta ações do WebDriver. Em geral, ações são comandos que descrevem dispositivos de entrada virtual.
{ :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 }]}]}
Você pode criar um mapa manualmente e enviá-lo para o método 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)
ou use invólucros. Primeiro, você precisa criar dispositivos de entrada virtual, por exemplo:
( def keyboard ( make-key-input ))
e então preencha -o com as ações necessárias:
( -> keyboard
( add-key-down keys/shift-left)
( add-key-down " a " )
( add-key-up " a " )
( add-key-up keys/shift-left))
Exemplo estendido:
( 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))
Para limpar o estado dos dispositivos de entrada virtual, libere todas as teclas pressionadas etc., use o método release-actions
:
( release-actions driver)
Clicar em um botão de entrada de arquivo abre uma caixa de diálogo específica do sistema operacional que você não tem permissão para interagir com o uso do protocolo WebDriver. Use a função upload-file
para anexar um arquivo local a um widget de entrada de arquivo. A função leva um seletor que aponta para uma entrada de arquivo e um caminho completo como uma string ou uma instância nativa java.io.File
. O arquivo deve existir ou você terá uma exceção. Exemplo de uso:
( 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)
Chamar uma função screenshot
despeja a página atual em uma imagem PNG em seu disco:
( screenshot driver " page.png " ) ; ; relative path
( screenshot driver " /Users/ivan/page.png " ) ; ; absolute path
Um objeto de arquivo java nativo também é suportado:
; ; when imported as `[clojure.java.io :as io]`
( screenshot driver ( io/file " test.png " ))
; ; native object
( screenshot driver ( java.io.File. " test-native.png " ))
Com o Firefox e o Chrome, você pode capturar não toda a página, mas um único elemento, digamos uma div, um widget de entrada ou qualquer outra coisa. Não funciona com outros navegadores por enquanto. Exemplo:
( screenshot-element driver { :tag :div :class :smart-widget } " smart_widget.png " )
Com macro with-screenshots
, você pode fazer uma captura de tela após cada formulário
( with-screenshots driver " ../screenshots "
( fill driver :simple-input " 1 " )
( fill driver :simple-input " 2 " )
( fill driver :simple-input " 3 " ))
O que é equivalente a um registro:
( 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 " )
Recentemente, o Google Chrome e posteriormente o Firefox começaram a suportar um recurso denominado modo sem cabeça. Ao ficar sem cabeça, nenhuma das janelas da interface do usuário ocorre na tela, apenas a saída STDOUT entra no console. Esse recurso permite executar testes de integração em servidores que não possuem dispositivo de saída gráfica.
Verifique se o seu navegador suporta o modo sem cabeça, verificando se aceitar -o argumento da linha de comando de --headles
ao executá -lo do terminal. O motorista Phantom.js é sem cabeça por sua natureza (nunca foi desenvolvido para renderizar a interface do usuário).
Ao iniciar um driver, passe :headless
para mudar para o modo sem cabeça. Observe que apenas a versão mais recente do Chrome e do Firefox é suportada. Para outros motoristas, a bandeira será ignorada.
( def driver ( chrome { :headless true })) ; ; runs headless Chrome
ou
( def driver ( firefox { :headless true })) ; ; runs headless Firefox
Para verificar qualquer driver foi executado no modo sem cabeça, use headless?
predicado:
( headless? driver) ; ; true
Observe que sempre retornará verdadeiro para instâncias Phantom.js.
Existem vários atalhos para executar o Chrome ou o Firefox no modo sem cabeça por padrão:
( 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 " ))
Há também when-headless
e when-not-headless
que permitem executar vários comandos apenas se um navegador estiver em modo sem cabeça ou não, respectivamente:
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
Para se conectar a um driver que já está em execução em um host local ou remoto, você deve especificar o parâmetro :host
que pode ser um nome de host (localhost, alguns.remote.host.net) ou um endereço IP (127.0.1, 183.102.156.31 ) e a :port
. Se a porta não for especificada, a porta padrão será definida.
Exemplo:
; ; 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
Para trabalhar com o motorista no Docker, você pode tirar imagens prontas:
Exemplo para o Chrome:
docker run --name chromedriver -p 9515:4444 -d -e CHROMEDRIVER_WHITELISTED_IPS='' robcherry/docker-chromedriver:latest
Para Firefox:
docker run --name geckodriver -p 4444:4444 -d instrumentisto/geckodriver
Para conectar -se ao driver, você só precisa especificar o parâmetro :host
como localhost
ou 127.0.0.1
e a :port
na qual está em execução. Se a porta não for especificada, a porta padrão será definida.
( def driver ( chrome-headless { :host " localhost " :port 9515 :args [ " --no-sandbox " ]}))
( def driver ( firefox-headless { :host " localhost " })) ; ; will try to connect to port 4444
Para definir configurações de proxy, use variáveis de ambiente HTTP_PROXY
/ HTTPS_PROXY
ou passe um mapa do seguinte tipo:
{ :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 " }})
Nota: A: PAC-URL para um arquivo de configuração automática proxy. Usado com o Safari como as outras opções de proxy não funcionam nesse navegador.
Para ajustar o proxy, você pode usar o objeto original e passá -lo para os recursos:
{ :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 {...}}})
Com atualizações recentes, a biblioteca traz um ótimo recurso. Agora você pode rastrear eventos que vêm do painel Devtools. Isso significa que tudo o que você vê no console do desenvolvedor agora está disponível na API. Isso funciona apenas com o Google Chrome agora.
Para iniciar um driver com configurações especiais de desenvolvimento especificadas, basta passar um mapa vazio para o campo :dev
ao executar um driver:
( def c ( chrome { :dev {}}))
O valor não deve ser nil
. Quando é um mapa vazio, uma função especial leva os padrões. Aqui está uma versão completa das configurações de dev com todos os valores possíveis especificados.
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
Sob o capô, ele preenche um dicionário especial perfLoggingPrefs
dentro do objeto chromeOptions
.
Agora que seu navegador acumula esses eventos, você pode lê -los usando um espaço de nome dev
especial.
( go c " http://google.com " )
; ; wait until the page gets loaded
; ; load the namespace
( require '[etaoin.dev :as dev])
Vamos ter uma lista de todas as solicitações HTTP que ocorreram durante a página estava carregando.
( 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 }
Como estamos interessados principalmente em solicitações de Ajax, há uma função get-ajax
que faz o mesmo, mas filtra as solicitações 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 }
Um padrão típico de uso get-ajax
é o seguinte. Você gostaria de verificar se uma determinada solicitação foi demitida ao servidor. Então você pressiona um botão, aguarde um tempo e, em seguida, leia as solicitações feitas pelo seu navegador.
Tendo uma lista de solicitações, você procura o que você precisa (por exemplo, por seu URL) e depois verifica seu estado. O campo :state
tem a mesma semântica como o XMLHttpRequest.readyState
. É um número inteiro de 1 a 4 com o mesmo comportamento.
Para verificar se uma solicitação foi concluída, realizada ou falhada, use estes predicados:
( def req ( last reqs))
( dev/request-done? req)
; ; true
( dev/request-failed? req)
; ; false
( dev/request-success? req)
; ; true
Observar esse request-done?
Não significa que a solicitação tenha sido bem -sucedido. Isso significa apenas que seu pipeline atingiu uma etapa final.
AVISO: Quando você lê logs de dev, você os consome de um buffer interno que é liberado. A segunda chamada para get-requests
ou get-ajax
retornará uma lista vazia.
Talvez você queira coletar esses registros por conta própria. Uma função dev/get-performance-logs
retorna uma lista de toras para que você os acumule em um átomo ou o que for:
( def logs ( atom []))
; ; repeat that form from time to time
( do ( swap! logs concat ( dev/get-performance-logs c))
true )
( count @logs)
; ; 76
Existem logs->requests
e logs->ajax
que convertem logs em solicitações. Ao contrário get-requests
e get-ajax
, são funções puras e não vão lavar nada.
( dev/logs->requests @logs)
Ao trabalhar com logs e solicitações, preste atenção na contagem e tamanho. Os mapas têm muitas chaves e a quantidade de itens em coleções pode ser enorme. Imprimir um monte de eventos pode congelar seu editor. Considere usar a função de clojure.pprint/pprint
pois se baseia nos limites máximos de nível e comprimento.
Às vezes, pode ser difícil descobrir o que deu errado durante a última sessão de testes de interface do usuário. Uma macro especial with-postmortem
economiza alguns dados úteis no disco antes que a exceção fosse acionada. Esses dados são uma captura de tela, código HTML e logs do console JS. Nota: Nem todos os navegadores suportam obter logs JS.
Exemplo:
( def driver ( chrome ))
( with-postmortem driver { :dir " /Users/ivan/artifacts " }
( click driver :non-existing-element ))
Uma exceção aumentará, mas em /Users/ivan/artifacts
haverá três arquivos nomeados por um modelo <browser>-<host>-<port>-<datetime>.<ext>
:
firefox-127.0.0.1-4444-2017-03-26-02-45-07.png
: uma captura de tela real da página do navegador;firefox-127.0.0.1-4444-2017-03-26-02-45-07.html
: o conteúdo HTML do navegador atual;firefox-127.0.0.1-4444-2017-03-26-02-45-07.json
: um arquivo json com logs de console; Esses são um vetor de objetos.O manipulador leva um mapa de opções com as seguintes teclas. Todos eles podem estar ausentes.
{ ; ; 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 " }
Função (get-logs driver)
retorna os logs do navegador como um vetor de mapas. Cada mapa tem a seguinte estrutura:
{ :level :warning ,
:message " 1,2,3,4 anonymous (:1) " ,
:timestamp 1511449388366 ,
:source nil,
:datetime #inst " 2017-11-23T15:03:08.366-00:00 " }
Atualmente, os logs estão disponíveis apenas no Chrome e Phantom.js. Observe que o texto da mensagem e o tipo de origem dependem muito do navegador. O Chrome limpa os troncos depois de lidos. Phantom.js os mantém, mas apenas até você alterar a página.
Ao executar uma instância do driver, um mapa de parâmetros adicionais pode ser passado para ajustar o comportamento do navegador:
( def driver ( chrome { :path " /path/to/driver/binary " }))
Abaixo, aqui está um mapa dos parâmetros o suporte da biblioteca. Todos eles podem ser ignorados ou têm valores nulos. Alguns deles, se não foram aprovados, são retirados do mapa 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 " ]}}}
Quando você navega para uma determinada página, o driver aguarda até que toda a página esteja completamente carregada. O que está bem na maioria dos casos, mas ainda não reflete a maneira como os seres humanos interagem com a Internet.
Altere esse comportamento padrão com a opção :load-strategy
. Existem três valores possíveis para isso :: :none
, :eager
e :normal
o que é o padrão quando não foi aprovado.
Quando você passa :none
, o motorista responde imediatamente para que você seja bem -vindo para executar as próximas instruções. Por exemplo:
( def c ( chrome ))
( go c " http://some.slow.site.com " )
; ; you'll hang on this line until the page loads
( do-something )
Agora, ao passar a opção de estratégia de carga:
( def c ( chrome { :load-strategy :none }))
( go c " http://some.slow.site.com " )
; ; no pause, acts immediately
( do-something )
Para a opção :eager
, ele funciona apenas com o Firefox no momento de adicionar o recurso à biblioteca.
Há uma opção para inserir uma série de chaves simultaneamente. Isso é útil para imitar a manutenção de uma chave do sistema como controle, mudança ou qualquer outra coisa ao digitar.
O espaço para nome etaoin.keys
carrega várias constantes -chave, bem como um conjunto de funções relacionadas à entrada.
( require '[etaoin.keys :as keys])
Um exemplo rápido de inserir caracteres comuns segurando o turno:
( def c ( chrome ))
( go c " http://google.com " )
( fill-active c ( keys/with-shift " caps is great " ))
A entrada principal é preenchida com "Caps é ótimo". Agora você gostaria de excluir a última palavra. No Chrome, isso é feito pressionando o backspace segurando alt. Vamos fazer isso:
( fill-active c ( keys/with-alt keys/backspace))
Agora você tem apenas "caps está" na entrada.
Considere um exemplo mais complexo que repete o comportamento dos usuários reais. Você gostaria de excluir tudo da entrada. Primeiro, você move o cuidador no início. Em seguida, mova -o para o final do Shift, para que tudo seja selecionado. Finalmente, você pressiona excluir para limpar o texto selecionado.
A combinação é:
( fill-active c keys/home ( keys/with-shift keys/end) keys/delete)
Também existem funções with-ctrl
e with-command
que agem da mesma forma.
Preste atenção, essas funções não se aplicam aos atalhos do navegador global. Por exemplo, nem "command + r" nem "comando + t" recarregue a página ou abra uma nova guia.
Todas as keys/with-*
são apenas invólucros na função de keys/chord
que podem ser usadas para casos complexos.
Para especificar seu próprio diretório onde baixar arquivos, passe :download-dir
em um mapa de opções ao executar um driver:
( def driver ( chrome { :download-dir " /Users/ivan/Desktop " }))
Agora, depois de clicar em um link, um arquivo deve ser colocado nessa pasta. Atualmente, apenas o Chrome e o Firefox são suportados.
O Firefox exige especificar os tipos de mímica desses arquivos que devem ser baixados sem mostrar uma caixa de diálogo do sistema. Por padrão, quando o parâmetro :download-dir
é passado, a biblioteca adiciona os tipos MIME mais comuns: arquivos, arquivos de mídia, documentos do escritório etc. Se você precisar adicionar seu próprio, substituir essa preferência manualmente:
( def driver ( firefox { :download-dir " /Users/ivan/Desktop "
:prefs { :browser.helperApps.neverAsk.saveToDisk
" some-mime/type-1;other-mime/type-2 " }}))
Para verificar se um arquivo foi baixado durante os testes da interface do usuário, consulte a seção de teste abaixo.
Defina um cabeçalho de agente de usuário personalizado com a opção :user-agent
ao criar um driver, por exemplo:
( def f ( firefox { :user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) " }))
Para obter o valor atual do cabeçalho no tempo de execução, use a função:
( get-user-agent f)
; ; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Definir esse cabeçalho é muito importante para os navegadores sem cabeça, pois a maioria dos sites verifica se o agente do usuário inclui a string "sem cabeça". Isso pode levar a 403 resposta ou a algum comportamento estranho do site.
Ao executar o Chrome ou o Firefox, você pode especificar um perfil especial feito para fins de teste. Um perfil é uma pasta que mantém as configurações do navegador, histórico, marcadores e outros dados específicos do usuário.
Imagine que você gostaria de executar seus testes de integração contra um usuário que desativou a execução do JavaScript ou a renderização da imagem. Preparar um perfil especial para essa tarefa seria uma boa escolha.
chrome://version/
página. Copie o caminho do arquivo que está abaixo da legenda Profile Path
.-P
, -p
ou -ProfileManager
, conforme a página oficial descreve.about:support
. Perto da legenda Profile Folder
, pressione o Show in Finder
. Uma nova janela de pasta deve aparecer. Copie seu caminho a partir daí. Depois de obter um caminho de perfil, inicie um driver com uma chave especial :profile
da seguinte forma:
; ; 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}))
A biblioteca envia um conjunto de funções para rolar a página.
O mais importante, scroll-query
salta o primeiro elemento encontrado com o termo da consulta:
( def driver ( chrome ))
; ; the form button placed somewhere below
( scroll-query driver :button-submit )
; ; the main article
( scroll-query driver { :tag :h1 })
Para pular para a posição absoluta, basta usar scroll
da seguinte forma:
( scroll driver 100 600 )
; ; or pass a map with x and y keys
( scroll driver { :x 100 :y 600 })
Para rolar relativamente, use scroll-by
com valores de deslocamento:
; ; keeps the same horizontal position, goes up for 100 pixels
( scroll-by driver 0 -100 ) ; ; map parameter is also supported
Existem dois atalhos para pular superior ou inferior da página:
( scroll-bottom driver) ; ; you'll see the footer...
( scroll-top driver) ; ; ...and the header again
As funções a seguir rolarem a página em todas as direções:
( 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)
Uma nota, em todos os casos, as ações de rolagem são servidas com JavaScript. Verifique se o seu navegador está ativado.
Enquanto trabalha com a página, você não pode interagir com os itens que são colocados em um quadro ou um iframe. As funções abaixo alternam o contexto atual em um quadro específico:
( 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
Os quadros podem ser aninhados um em outro. As funções levam isso em consideração. Digamos que você tenha um layout HTML como este:
< iframe src =" ... " >
< iframe src =" ... " >
< button id =" the-goal " >
</ frame >
</ frame >
Então você pode alcançar o botão com o seguinte código:
( 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
Para reduzir o número de linhas de código, existe uma macro especial with-frame
. Ele troca temporária quadros enquanto executa o corpo que retorna sua última expressão e muda para o quadro anterior posteriormente.
( with-frame driver { :id :first-frame }
( with-frame driver { :id :nested-frame }
( click driver { :id :nested-button })
42 ))
O código acima retorna 42
permanecendo no mesmo quadro que já foi antes de avaliar as macros.
Para avaliar um código JavaScript em um navegador, execute:
( js-execute driver " alert(1) " )
Você pode passar quaisquer parâmetros adicionais para a chamada e cath-os dentro de um script com o objeto arguments
do tipo Array:
( js-execute driver " alert(arguments[2].foo) " 1 false { :foo " hello! " })
Como resultado, hello!
String aparecerá dentro da caixa de diálogo.
Para retornar qualquer dados ao Clojure, basta adicionar return
ao seu script:
( js-execute driver " return {foo: arguments[2].foo, bar: [1, 2, 3]} "
1 false { :foo " hello! " })
; ; {:bar [1 2 3], :foo "hello!"}
Se o seu script executar solicitações do AJAX ou operar no setTimeout
ou em qualquer outro material assíncrono, você não poderá apenas return
o resultado. Em vez disso, um retorno de chamada especial deve ser chamado contra os dados que você deseja alcançar. O WebDriver passa esse retorno de chamada como o último argumento do seu script e pode ser alcançado com o objeto de Array arguments
.
Exemplo:
( 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 }}})
Retorna 42
ao código Clojure.
Para avaliar um script assíncrono, você precisa configurar um tempo limite especial para isso:
( set-script-timeout driver 5 ) ; ; in seconds
Ou envolva o código em uma macros que o faz temporário:
( with-script-timeout driver 30
( js-async driver " some long script " ))
A principal diferença entre um programa e um ser humano é que o primeiro opera muito rápido. Significa tão rápido que, às vezes, um navegador não pode renderizar um novo HTML no tempo. Então, após cada ação, é melhor você colocar a função wait-<something>
que apenas pesquisou um navegador até que o predicado seja avaliado no verdadeiro. Ou apenas (wait <seconds>)
se você não se importa com otimização.
A macro with-wait
pode ser útil quando você precisar prender cada ação com (wait n)
. Por exemplo, o seguinte formulário
( with-chrome {} driver
( with-wait 3
( go driver " http://site.com " )
( click driver { :id " search_button " })))
se transforma em algo assim:
( with-chrome {} driver
( wait 3 )
( go driver " http://site.com " )
( wait 3 )
( click driver { :id " search_button " }))
e assim retorna o resultado da última forma do corpo original.
Há outra macro (doto-wait n driver & body)
que age como o doto
padrão, mas prenda cada formulário com (wait n)
. Por exemplo:
( with-chrome {} driver
( doto-wait 1 driver
( go " http://site.com " )
( click :this-link )
( click :that-button )
...etc))
A forma final seria algo assim:
( with-chrome {} driver
( doto driver
( wait 1 )
( go " http://site.com " )
( wait 1 )
( click :this-link )
( wait 1 )
( click :that-button )
...etc))
Além de with-wait
e do-wait
existem várias funções de espera: wait-visible
, wait-has-alert
, wait-predicate
, etc (consulte a lista completa na seção Correstonsing). Eles aceitam valores de tempo limite/intervalo padrão que podem ser redefinidos usando as macros with-wait-timeout
e with-wait-interval
, respectivamente.
Exemplo do teste de 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 " ))))
Espere o texto:
wait-has-text
aguarda até que um elemento tenha texto em qualquer lugar dentro dele (incluindo HTML interno).
( wait-has-text driver :wait-span " -secret- " )
wait-has-text-everywhere
como wait-has-text
mas procura texto em toda a página
( wait-has-text-everywhere driver " -secret- " )
Para fazer com que seu teste não dependa um do outro, você precisa envolvê -los em um acessório que criará uma nova instância de um driver e o desligará corretamente no final, se cada teste.
Uma boa solução pode ser ter uma variável global (não ligada por padrão) que apontará para o driver de destino durante os testes.
( 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 )
...
))
Se, por algum motivo, você quiser usar uma única instância, você pode usar acessórios como este:
( 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
Para testes mais rápidos, você pode usar este exemplo:
.....
( 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 ))
......
No exemplo acima, examinamos um caso quando você executa testes em um único tipo de driver. No entanto, convém testar seu site em vários drivers, digamos, Chrome e Firefox. Nesse caso, seu acessório pode se tornar um pouco mais complexo:
( 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 ))))))
Agora, cada teste será executado duas vezes nos navegadores Firefox e Chrome. Observe que a chamada de teste está presa com a macro testing
que coloca o nome do driver no relatório. Depois de receber um erro, você facilitará o que o driver falhou exatamente nos testes.
Para salvar alguns artefatos em caso de exceção, envolva o corpo do seu teste with-postmortem
da seguinte maneira:
( 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...
)))
Agora que, se ocorrer alguma exceção nesse teste, os artefatos serão salvos.
Para não copiar e colar o mapa de opções, declare -o na parte superior do módulo. Se você usar o Circle CI, seria ótimo salvar os dados em um diretório de artefatos especiais que poderiam ser baixados como um arquivo zip depois que a compilação for concluída:
( def pm-dir
( or ( System/getenv " CIRCLE_ARTIFACTS " ) ; ; you are on CI
" /some/local/path " )) ; ; local machine
( def pm-opt
{ :dir pm-dir})
Agora passe esse mapa em todos os lugares para o manipulador de PM:
; ; test declaration
( with-postmortem *driver* pm-opt
; ; test body goes here
)
Depois que ocorre um erro, você encontrará uma imagem PNG que representa sua página do navegador no momento da exceção e do dump html.
Como os testes de interface do usuário podem levar muito tempo para passar, é definitivamente uma boa prática passar os testes de servidor e interface do usuário independentemente um do outro.
Primeiro, add ^:integration
a todos os testes que são executados no navegador, como segue:
( deftest ^:integration
test-password-reset-pipeline
( doto *driver*
( go url-password-reset)
( click :reset-btn )
...
Em seguida, abra seu arquivo project.clj
e adicione seletores de teste:
:test-selectors { :default ( complement :integration )
:integration :integration }
Agora, depois de iniciar lein test
você executará todos os testes, exceto os do navegador. Para executar testes de integração, inicie lein test :integration
.
A principal diferença entre um programa e um humano é que o primeiro opera muito rápido. Significa tão rápido que, às vezes, um navegador não pode renderizar um novo HTML no tempo. Portanto, após cada ação, você precisa colocar a função wait-<something>
que apenas pesquisou um navegador que verifica um predicado. O Just (wait <seconds>)
se você não se importa com otimização.
Às vezes, um arquivo começa a baixar automaticamente depois de clicar em um link ou apenas visitar alguma página. Nos testes, você precisa garantir que um arquivo realmente tenha sido baixado com sucesso. Um cenário comum seria:
Exemplo:
; ; 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 pode reproduzir os arquivos produzidos pelo Selenium IDE. É uma utilidade oficial criar cenários interativamente. O IDE vem como uma extensão do seu navegador. Depois de instalado, ele registra suas ações em um arquivo JSON com a extensão .side
. Você pode salvar esse arquivo e executá -lo com o etaoin.
Vamos imaginar que você instalou o IDE e registrou algumas ações como a documentação oficial prescreve. Agora que você tem um arquivo test.side
, faça isso:
( 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)
Tudo relacionado ao IDE é armazenado no pacote etaoin.ide
.
Você também pode executar um script na linha de comando. Aqui está o exemplo lein run
:
lein run -m etaoin.ide.main -d firefox -p ' {:port 8888 :args ["--no-sandbox"]} ' -r ide/test.side
Bem como de um Uberjar. Nesse caso, o etaoin deve estar nas dependências primárias, não no :dev
ou :test
relacionado.
java -cp .../poject.jar -m etaoin.ide.main -d firefox -p ' {:port 8888} ' -f ide/test.side
Apoiamos os seguintes argumentos (confira -os usando o comando 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
Preste atenção ao --params
um. Esta deve ser uma string edn representando um mapa de clojure. Esse é o mesmo mapa que você passa para um motorista quando o inicia.
Observe que o suporte do IDE ainda é experimental. Se você encontrar um comportamento inesperado, sinta -se à vontade para abrir um problema. No momento, apoiamos apenas o Chrome e o Firefox para arquivos IDE.
Exemplo:
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) " }},
...
Solução: basta atualizar seu chromedriver
para a última versão. Testado com 2.29, funciona bem. As pessoas dizem que isso também woks desde 2.28.
Lembre -se de que brew
Package Manager possui a versão 2.27 desatualizada. Você provavelmente terá que baixar binários do site oficial.
Veja a questão relacionada no Projeto Selenium.
Ao passar uma consulta do tipo vetor, diga [{:tag :p} "//*[text()='foo')]]"}]
tenha cuidado com expressões XPath escrivadas à mão. No vetor, toda sua expressão procura do anterior em um loop. Há um erro oculto aqui: sem um ponto principal, a cláusula "//..."
significa encontrar um elemento da raiz de toda a página. Com um ponto, significa encontrar no nó atual, que é da consulta anterior e assim por diante.
É por isso que é fácil selecionar algo completamente diferente que você gostaria. Uma expressão adequada seria: [{:tag :p} ".//*[text()='foo')]]"}]
.
Exemplo:
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) " }},
...
Solução: você está tentando clicar em um elemento que não é visível ou seus dimentões são tão pequenos quanto é impossível para um humano clicar nele. Você deve passar por outro seletor.
Problema: quando você se concentra em outras janelas, a sessão do Webdriver que é executada no Google Chrome falha.
Solução: o Google Chrome pode suspender uma guia quando estiver inativa por algum tempo. Quando a página é suspensa, nenhuma operação pode ser feita nela. Sem cliques, execução JS, etc. Portanto, tente manter a janela do Chrome ativa durante a sessão de teste.
Problema: Quando você tenta iniciar o driver, recebe um erro:
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
Erro de sintaxe (excepção) compilação em (Repl: 1: 13). tiro+: {: resposta {: value {: erro "erro desconhecido" ,: mensagem "argumento inválido: não pode matar um processo de saída" .... ....
Causa possível: "Executar o Firefox como raiz em uma sessão regular de usuário não é suportado"
Solução: Para verificar, execute o driver com o caminho para os arquivos de log e o nível de log "rastrear" e explorar sua saída.
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
Problema semelhante: Mozilla/Geckodriver#1655
Problema: Quando você tenta iniciar o Chromedriver, recebe um erro:
clojure.lang.ExceptionInfo: arremesso+: {: resposta {: sessionId "....." ,: status 13 ,: valor {: mensagem "Erro desconhecido: o Chrome falhou ao iniciar: EXITADO ABNORMAL arquivo não existe) ...
Causa possível:
Uma causa comum para o Chrome travar durante a inicialização está executando o Chrome como usuário root (administrador) no Linux. Embora seja possível contornar esse problema passando-sinalizador de não-arex ao criar sua sessão do WebDriver, essa configuração não é suportada e altamente desencorajada. You need to configure your environment to run Chrome as a regular user instead.
Solution: Run driver with an argument --no-sandbox
. Cuidado! 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.