WebDriver 프로토콜의 순수한 Clojure 구현. 해당 라이브러리를 사용하여 브라우저를 자동화하고, 프론트 엔드 동작을 테스트하고, 인간의 행동 또는 원하는 것을 시뮬레이션하십시오.
그것은 Etaoin Shrdlu의 이름을 따서 명명되었습니다.
0.4.0
이므로 드라이버 인스턴스는 맵이지만 예전처럼 원자는 아닙니다 . 결정하기 어려운 솔루션이지만 코드에서 Clojure 방식을 따라야 할 Atom을 제거했습니다. 일반적으로 말하면, 당신은 결코 운전자를 데려하거나 그 안에 무언가를 보관하지 않습니다. 인스턴스를 수정하는 데 사용 된 모든 내부 기능은 이제 새 버전의 맵을 반환합니다. swap!
또는 드라이버 코드에서 유사한 내용은 업데이트하기 전에 코드를 리팩터링하십시오.
0.4.0
이후 라이브러리는 WebDriver 동작을 지원합니다. 행동은 배치로 드라이버에게 전송 된 명령입니다. TOC의 자세한 관련 섹션을 참조하십시오.
0.4.0
이므로 Etaoin은 대화식 Selenium IDE에서 생성 된 스크립트 파일을 재생할 수 있습니다. 아래 관련 섹션을 참조하십시오.
Ct t
누르는 EMACS에서 직접 장치 테스트를 실행하십시오.회사를 해당 목록에 제출할 수 있습니다.
설치해야 할 두 단계가 있습니다.
etaoin
Clojure 코드에 설치하십시오 다음을 project.clj
파일의 :dependencies
벡터에 추가하십시오.
[etaoin "0.4.6"]
Clojure 1.9 이상에서 작동합니다.
이 페이지는 브라우저를 자동화하는 데 필요한 드라이버를 설치하는 방법에 대한 지침을 제공합니다.
공식 사이트에서 다운로드하는 Chrome 및 Firefox 브라우저를 설치하십시오. 모든 플랫폼에 문제가 없습니다.
필요한 특정 드라이버를 설치하십시오.
Chrome 드라이버 :
brew cask install chromedriver
2.28
버전이 설치되어 있는지 확인하십시오. 2.27
이하는 창 최대화와 관련된 버그가 있습니다 ([[문제 해결]] 참조).Firefox의 드라이버 인 Geckodriver :
brew install geckodriver
Phantom.js 브라우저 :
brew install phantomjs
사파리 드라이버 (Mac 전용) :
이제이 명령을 시작하여 설치를 확인하십시오. 각 명령에 대해 로컬 HTTP 서버를 사용한 끝없는 프로세스가 시작되어야합니다.
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
이 라이브러리 시작에 대한 테스트를 실행할 수 있습니다.
lein test
브라우저 Windows가 열리고 직렬로 닫히는 것을 볼 수 있습니다. 테스트는 특수 레이아웃이있는 로컬 HTML 파일을 사용하여 대부분의 사례를 확인합니다.
문제가있는 경우 문제 해결 섹션에 대해서는 아래를 참조하십시오.
좋은 소식은 대체에서 직접 브라우저를 자동화 할 수 있습니다.
( use 'etaoin.api)
( require '[etaoin.keys :as k])
( def driver ( firefox )) ; ; here, a Firefox window should appear
; ; let's perform a quick Wiki session
( go driver " https://en.wikipedia.org/ " )
( wait-visible driver [{ :id :simpleSearch } { :tag :input :name :search }])
; ; search for something
( fill driver { :tag :input :name :search } " Clojure programming language " )
( fill driver { :tag :input :name :search } k/enter)
( wait-visible driver { :class :mw-search-results })
; ; select an `option` in select-box by visible text
; ; <select id="country">
; ; <option value="rf">Russia</option>
; ; <option value="usa">United States</option>
; ; <option value="uk">United Kingdom</option>
; ; <option value="fr">France</option>
; ;</select>
( select driver :country " France " )
( get-element-value driver :country )
; ;=> "fr"
; ; I'm sure the first link is what I was looking for
( click driver [{ :class :mw-search-results } { :class :mw-search-result-heading } { :tag :a }])
( wait-visible driver { :id :firstHeading })
; ; let's ensure
( get-url driver) ; ; "https://en.wikipedia.org/wiki/Clojure"
( get-title driver) ; ; "Clojure - Wikipedia"
( has-text? driver " Clojure " ) ; ; true
; ; navigate on history
( back driver)
( forward driver)
( refresh driver)
( get-title driver) ; ; "Clojure - Wikipedia"
; ; stops Firefox and HTTP server
( quit driver)
모든 기능에는 드라이버 인스턴스가 첫 번째 인수로 필요합니다. 따라서 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
키가있는 맵. 해당 구문의 문자열 표현식이있는 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"]
"다운로드"텍스트를 사용하여 nth 요소 ( p
, a
, 무엇이든)를 찾으십시오.
( query driver { :fn/has-text " download " :index 3 })
; ; .//*[contains(text(), "download")][3]
다음 수업이있는 요소를 찾으십시오.
( query driver { :tag :div :fn/has-class " overlay " })
; ; .//div[contains(@class, "overlay")]
HREF에서 다음 도메인이있는 요소를 찾으십시오.
( query driver { :tag :a :fn/link " google.com " })
; ; .//a[contains(@href, "google.com")]
다음 클래스가 한 번에있는 요소를 찾으십시오.
( query driver { :fn/has-classes [ :active :sticky :marked ]})
; ; .//*[contains(@class, "active")][contains(@class, "sticky")][contains(@class, "marked")]
활성화/비활성화 된 입력 위젯을 찾으십시오.
; ; first input
( query driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
( query driver { :tag :input :fn/enabled true })
; ; .//input[@enabled=true()]
; ; all inputs
( query-all driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
쿼리는 위에서 언급 한 모든 표현식으로 구성된 벡터 일 수 있습니다. 그러한 쿼리에서, 다음 용어는 각각 이전의 용어에서 재귀 적으로 검색됩니다.
간단한 예 :
( click driver [{ :tag :html } { :tag :body } { :tag :a }])
XPath와 CSS 표현식을 모두 결합 할 수 있습니다 (XPath 표현식의 주요 점에서주의를 기울이십시오.
( click driver [{ :tag :html } { :css " div.class " } " .//a[@class='download'] " ])
예를 들어이 예에서 두 번째 링크를 클릭하려는 경우 쿼리의 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 }])
또는 다음과 같은 CSS 표현식과 함께 Nth-Child 트릭을 사용할 수 있습니다.
( 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 ))
query-all
직접 click
할 수있는 선택기가 아닌 요소를 반환하므로 여기에서 click-el
을 사용하십시오.
query-tree
선택기를 가져와 나무처럼 행동합니다. 다음 선택기는 이전의 요소의 요소를 쿼리합니다. 주먹 선택기는 찾기 요소에 의존하고 나머지는 Find-elements를 사용합니다.
( query-tree driver { :tag :div } { :tag :a })
수단
{:tag :div} -> [div1 div2 div3]
div1 -> [a1 a2 a3]
div2 -> [a4 a5 a6]
div3 -> [a7 a8 a9]
결과는 [A1 ... A9]입니다.
쿼리를 통해 발견 된 요소와 상호 작용하려면 쿼리 결과를 click-el
또는 fill-el
로 전달해야합니다.
( click-el driver ( first ( query-all driver { :tag :a })))
따라서 요소를 벡터로 수집하고 언제든지 임의로 상호 작용할 수 있습니다.
( def elements ( query-all driver { :tag :input :type :text })
( fill-el driver ( first elements) " This is a test " )
( fill-el driver ( rand-nth elements) " I like tests! " )
인간 입력을 모방하기 위해 fill-human
기능을 사용할 수 있습니다. 다음 옵션은 기본적으로 활성화됩니다.
{ :mistake-prob 0.1 ; ; a real number from 0.1 to 0.9, the higher the number, the more typos will be made
:pause-max 0.2 } ; ; max typing delay in seconds
그리고 당신은 그것들을 재정의 할 수 있습니다.
( fill-human driver q text { :mistake-prob 0.5
:pause-max 1 })
; ; or just use default opts by omitting them
( fill-human driver q text)
인간 에뮬레이션에 대한 여러 입력의 경우 fill-human-multi
사용하십시오
( fill-human-multi driver { :login " login "
:pass " password "
:textarea " some text " }
{ :mistake-prob 0.5
:pause-max 1 })
click
함수는 왼쪽 마우스를 트리거합니다. 쿼리 용어로 찾은 요소를 클릭합니다.
( click driver { :tag :button })
click
함수는 쿼리에서 찾은 첫 번째 요소 만 사용하여 때로는 잘못된 항목을 클릭합니다. 하나의 요소 만 발견되도록 click-single
기능을 사용하십시오. 동일하게 작동하지만 페이지 쿼리가 여러 요소를 반환 할 때 예외가 발생합니다.
( click-single driver { :tag :button :name " search " })
이중 클릭은 웹에서 거의 사용되지 않지만 double-click
함수 (Chrome, Phantom.js)에서는 가능합니다.
( double-click driver { :tag :dbl-click-btn })
"블라인드"클릭 함수도 있습니다. 그들은 현재 마우스 위치에서 마우스 클릭을 트리거합니다.
( left-click driver)
( middle-click driver)
( right-click driver)
또 다른 기능은 동일하게 수행하지만 마우스 포인터를 지정된 요소로 이동시키기 전에 다음과 같습니다.
( left-click-on driver { :tag :img })
( middle-click-on driver { :tag :img })
( right-click-on driver { :tag :img })
중간 마우스 클릭은 새 배경 탭에서 링크를 열 때 유용합니다. 오른쪽 클릭은 때때로 웹 응용 프로그램에서 컨텍스트 메뉴를 모방하는 데 사용됩니다.
라이브러리는 WebDriver 동작을 지원합니다. 일반적으로 작업은 가상 입력 장치를 설명하는 명령입니다.
{ :actions [{ :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]}
{ :type " pointer "
:id " UUID or some name "
:parameters { :pointerType " mouse " }
:actions [{ :type " pointerMove " :origin " pointer " :x 396 :y 323 }
; ; double click
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }]}]}
맵을 수동으로 생성하여 perform-actions
방법으로 보낼 수 있습니다.
( def keyboard-input { :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]})
( perform-actions driver keyboard-input)
또는 포장지를 사용하십시오. 먼저 가상 입력 장치를 작성해야합니다.
( def keyboard ( make-key-input ))
그런 다음 필요한 조치로 채우십시오.
( -> keyboard
( add-key-down keys/shift-left)
( add-key-down " a " )
( add-key-up " a " )
( add-key-up keys/shift-left))
확장 예 :
( let [driver ( chrome )
_ ( go driver " https://google.com " )
search-box ( query driver { :name :q })
mouse ( -> ( make-mouse-input )
( add-pointer-click-el search-box))
keyboard ( -> ( make-key-input )
add-pause
( with-key-down keys/shift-left
( add-key-press " e " ))
( add-key-press " t " )
( add-key-press " a " )
( add-key-press " o " )
( add-key-press " i " )
( add-key-press " n " )
( add-key-press keys/enter))]
( perform-actions driver keyboard mouse)
( quit driver))
가상 입력 장치의 상태를 지우려면 모든 프레스 키 등을 릴리스하고 release-actions
방법을 사용하십시오.
( release-actions driver)
파일 입력 버튼을 클릭하면 WebDriver 프로토콜 사용과 상호 작용할 수없는 OS 특이 적 대화 상자가 열립니다. 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 인스턴스에 대해 항상 true를 반환합니다.
기본적으로 헤드리스 모드에서 크롬 또는 파이어 폭스를 실행할 수있는 몇 가지 바로 가기가 있습니다.
( def driver ( chrome-headless ))
; ; or
( def driver ( firefox-headless {...})) ; ; with extra settings
; ; or
( with-chrome-headless nil driver
( go driver " http://example.com " ))
( with-firefox-headless {...} driver ; ; extra settings
( go driver " http://example.com " ))
브라우저가 각각 헤드리스 모드에 있거나 각각없는 경우에만 많은 명령을 수행 할 수있는 when-headless
when-not-headless
매크로스도 있습니다.
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
로컬 또는 원격 호스트에서 이미 실행되는 드라이버에 연결하려면 호스트 이름 (localhost, some.remote.host.net) 또는 IP 주소 (127.0.0.1, 183.102.156.31 일 수있는 :host
) 및 The :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 " }})
참고 : A : 프록시 AutoConfiguration 파일의 PAC-URL. Safari와 함께 다른 프록시 옵션이 해당 브라우저에서 작동하지 않습니다.
프록시를 미세 조정하려면 원래 객체를 사용하여 기능으로 전달할 수 있습니다.
{ :capabilities { :proxy { :proxyType " manual "
:proxyAutoconfigUrl " some.proxy.com:8080 "
:ftpProxy " some.proxy.com:8080 "
:httpProxy " some.proxy.com:8080 "
:noProxy [ " http://this.url " " http://that.url " ]
:sslProxy " some.proxy.com:8080 "
:socksProxy " some.proxy.com:1080 "
:socksVersion 5 }}}
( chrome { :capabilities { :proxy {...}}})
최근 업데이트를 통해 라이브러리는 훌륭한 기능을 제공합니다. 이제 DevTools 패널에서 나오는 이벤트를 추적 할 수 있습니다. 이는 개발자 콘솔에서 볼 수있는 모든 것이 API를 통해 사용할 수 있음을 의미합니다. 지금은 Google Chrome에서만 작동합니다.
특수 개발 설정이 지정된 드라이버를 시작하려면 비어있는 맵을 다음과 같이 전달하십시오 :dev
필드 드라이버를 실행할 때 :
( def c ( chrome { :dev {}}))
값은 nil
되어서는 안됩니다. 빈 맵이면 특수 함수가 기본값을받습니다. 다음은 가능한 모든 값이 지정된 Dev 설정의 정식 버전입니다.
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
후드 아래에서, 그것은 chromeOptions
객체 내부에 특수 perfLoggingPrefs
사전을 채 웁니다.
브라우저에서 이러한 이벤트가 축적되었으므로 특수 dev
네임 스페이스를 사용하여 이벤트를 읽을 수 있습니다.
( go c " http://google.com " )
; ; wait until the page gets loaded
; ; load the namespace
( require '[etaoin.dev :as dev])
페이지가로드되는 동안 모든 HTTP 요청이 발생한 모든 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
has와 같은 의미를 가지고 있습니다. 동일한 동작을 가진 1에서 4까지의 정수입니다.
요청이 완료되었거나 완료되었거나 실패했는지 확인하려면 다음을 사용하십시오.
( def req ( last reqs))
( dev/request-done? req)
; ; true
( dev/request-failed? req)
; ; false
( dev/request-success? req)
; ; true
그 request-done?
요청이 성공했다는 의미는 아닙니다. 파이프 라인이 최종 단계에 도달했음을 의미합니다.
경고 : DEV 로그를 읽으면 플러시되는 내부 버퍼에서 소비합니다. get-requests
또는 get-ajax
대한 두 번째 호출은 빈 목록을 반환합니다.
아마도 당신은이 통나무를 자신의로 수집하고 싶을 것입니다. 함수 dev/get-performance-logs
로그 목록을 반환하여 원자 또는 그 밖의 모든 것을 축적합니다.
( def logs ( atom []))
; ; repeat that form from time to time
( do ( swap! logs concat ( dev/get-performance-logs c))
true )
( count @logs)
; ; 76
로그를 요청으로 변환하는 logs->requests
및 logs->ajax
함수가 있습니다. get-requests
및 get-ajax
와는 달리, 그들은 순수한 기능이며 아무것도 플러시하지 않습니다.
( dev/logs->requests @logs)
로그 및 요청으로 작업 할 때는 수와 크기에주의하십시오. 맵에는 많은 키가 있으며 컬렉션의 항목의 양은 엄청날 수 있습니다. 많은 이벤트를 인쇄하면 편집자가 정지 될 수 있습니다. clojure.pprint/pprint
함수는 최대 레벨 및 길이 한계에 의존하므로 사용하는 것을 고려하십시오.
때로는 마지막 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>
: 템플릿으로 이름이 지정된 3 개의 파일이 있습니다.
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
and :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 Alt를 누르는 것을 누르면 수행됩니다. 그렇게합시다 :
( fill-active c ( keys/with-alt keys/backspace))
이제 입력에 "캡은"만 있습니다.
실제 사용자의 행동을 반복하는 더 복잡한 예를 고려하십시오. 입력에서 모든 것을 삭제하고 싶습니다. 먼저, 당신은 처음에 캐럿을 움직입니다. 그런 다음 모든 것이 선택되도록 최종 상태로 이동하십시오. 마지막으로 삭제를 눌러 선택한 텍스트를 지우십시오.
콤보는 다음과 같습니다.
( 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는 시스템 대화 상자를 표시하지 않고 다운로드 해야하는 파일의 마임 유형을 지정해야합니다. 기본적으로 :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!"}
스크립트가 setTimeout
또는 기타 비동기 항목에서 AJAX 요청을 수행하거나 작동하는 경우 결과를 return
할 수 없습니다. 대신, 달성하려는 데이터에 대해 특별 콜백을 호출해야합니다. WebDriver는이 콜백을 스크립트의 마지막 인수로 전달하며 arguments
배열과 같은 객체로 도달 할 수 있습니다.
예:
( js-async
driver
" var args = arguments; // preserve the global args
var callback = args[args.length-1];
setTimeout(function() {
callback(args[0].foo.bar.baz);
},
1000); "
{ :foo { :bar { :baz 42 }}})
42
Clojure 코드로 반환합니다.
비동기 스크립트를 평가하려면 특별한 시간 초과를 설정해야합니다.
( set-script-timeout driver 5 ) ; ; in seconds
또는 코드를 일시적으로하는 매크로로 랩핑하십시오.
( with-script-timeout driver 30
( js-async driver " some long script " ))
프로그램과 인간의 주요 차이점은 첫 번째 프로그램이 매우 빠르게 작동한다는 것입니다. 그것은 너무 빨리 의미하기 때문에 브라우저가 새로운 HTML을 제 시간에 렌더링 할 수 없습니다. 따라서 각 조치 후에는 술어가 True로 평가 될 때까지 브라우저를 폴링 wait-<something>
기능을 대기하는 것이 좋습니다. 또는 최적화에 관심이 없다면 (wait <seconds>)
with-wait
매크로는 각 동작을 (wait n)
로 전제해야 할 때 도움이 될 수 있습니다. 예를 들어, 다음 형식입니다
( with-chrome {} driver
( with-wait 3
( go driver " http://site.com " )
( click driver { :id " search_button " })))
다음과 같은 것으로 바뀝니다.
( with-chrome {} driver
( wait 3 )
( go driver " http://site.com " )
( wait 3 )
( click driver { :id " search_button " }))
따라서 원래 몸의 마지막 형태의 결과를 반환합니다.
표준 doto
처럼 작용하지만 각 양식을 (wait n)
로 전제하는 또 다른 매크로 (doto-wait n driver & body)
가 있습니다. 예를 들어:
( 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
Macros를 사용하여 각각 재정의 할 수있는 기본 시간 초과/간격 값을 허용합니다.
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를 사용하는 경우 빌드가 완료되면 ZIP 파일로 다운로드 할 수있는 특수 아티팩트 디렉토리에 데이터를 저장하는 것이 좋습니다.
( def pm-dir
( or ( System/getenv " CIRCLE_ARTIFACTS " ) ; ; you are on CI
" /some/local/path " )) ; ; local machine
( def pm-opt
{ :dir pm-dir})
이제 해당지도를 어디에서나 PM 처리기로 전달하십시오.
; ; test declaration
( with-postmortem *driver* pm-opt
; ; test body goes here
)
오류가 발생하면 예외 및 HTML 덤프의 순간에 브라우저 페이지를 나타내는 PNG 이미지가 있습니다.
UI 테스트는 통과하는 데 많은 시간이 걸릴 수 있으므로 서버와 UI 테스트를 서로 독립적으로 통과시키는 것이 좋습니다.
먼저, ^: add ^: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>
something uption을 넣어야합니다. o 최적화에 관심이 없다면 (wait <seconds>)
.
때로는 링크를 클릭하거나 일부 페이지를 방문한 후 파일이 자동으로 다운로드되기 시작합니다. 테스트에서는 파일이 실제로 성공적으로 다운로드되었는지 확인해야합니다. 일반적인 시나리오는 다음과 같습니다.
예:
; ; Local helper that checks whether it is really an Excel file.
( defn xlsx? [file]
( -> file
.getAbsolutePath
( str/ends-with? " .xlsx " )))
; ; Top-level declarations
( def DL-DIR " /Users/ivan/Desktop " )
( def driver ( chrome { :download-dir DL-DIR}))
; ; Later, in tests...
( click-visible driver :download-that-application )
( wait driver 7 ) ; ; wait for a file has been downloaded
; ; Now, scan the directory and try to find a file:
( let [files ( file-seq ( io/file DL-DIR))
found ( some xlsx? files)]
( is found ( format " No *.xlsx file found in %s directory. " DL-DIR)))
Etaoin은 Selenium IDE가 생성 한 파일을 재생할 수 있습니다. 시나리오를 대화식으로 만드는 것은 공식적인 유틸리티입니다. IDE는 브라우저의 확장으로 제공됩니다. 일단 설치되면. .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 지원은 여전히 실험적이라는 점에 유의하십시오. 예상치 못한 행동에 직면하면 문제를 열어주십시오. 현재 우리는 IDE 파일의 경우 Chrome 및 Firefox 만 지원합니다.
예:
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이 있습니다. 공식 사이트에서 바이너리를 다운로드해야 할 것입니다.
셀레늄 프로젝트의 관련 문제를 참조하십시오.
벡터와 같은 쿼리를 전달할 때 [{:tag :p} "//*[text()='foo')]]"}]
손으로 작성된 Xpath 표현식에주의하십시오. 벡터에서는 모든 표현식이 루프에서 이전의 표현식 검색을 검색합니다. 여기에는 숨겨진 실수가 있습니다. 선행 점이 없으면 "//..."
조항은 전체 페이지의 루트에서 요소를 찾는 것을 의미합니다. 도트를 사용하면 이전 쿼리의 현재 노드 등 현재 노드를 찾는 것을 의미합니다.
그렇기 때문에 원하는 것과 완전히 다른 것을 선택하는 것은 쉽습니다. 적절한 표현은 [{:tag :p} ".//*[text()='foo')]]"}]
과 같습니다.
예:
etaoin.api> ( click driver :some-id )
ExceptionInfo throw+: { :response {
:sessionId " d112ce8ddb49accdae78a769d5809eae " ,
:status 11 ,
:value { :message " element not visible n (Session info: chrome=57.0.2987.133) n
(Driver info: chromedriver=2.29.461585
(0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b),platform=Mac OS X 10.11.6 x86_64) " }},
...
솔루션 : 보이지 않는 요소를 클릭하려고하거나 인간이 클릭하는 것이 불가능한 요소만으로도 요소를 클릭하려고합니다. 다른 선택기를 통과해야합니다.
문제 : 다른 창에 집중하면 Chrome에서 실행되는 WebDriver 세션이 실패합니다.
솔루션 : Google 크롬은 한동안 비활성화되었을 때 탭을 중단 할 수 있습니다. 페이지가 일시 중단되면 작업을 수행 할 수 없습니다. 클릭, JS 실행 등이 없으므로 테스트 세션 중에 Chrome Window를 활성화하십시오.
문제 : 드라이버를 시작하려고하면 오류가 발생합니다.
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
구문 오류 (ExceptionInfo) 컴파일 (Repl : 1 : 13). Throw+: {: response {: value {: 오류 "알 수없는 오류", : "무효 인수 : 종료 된 프로세스를 죽일 수 없습니다"....
가능한 원인 : "일반 사용자 세션에서 루트로 Firefox를 실행하는 것은 지원되지 않습니다".
솔루션 : 확인하려면 로그 파일의 경로와 "추적"로그 레벨로 드라이버를 실행하고 출력을 탐색하십시오.
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
비슷한 문제 : Mozilla/Geckodriver#1655
문제 : Chromedriver를 시작하려고하면 오류가 발생합니다.
clojure.lang.exceptioninfo : trash+: {: response {: response {: sessionid ", : : statess 13, : value {: value {: 메시지"알 수없는 오류 : Chrome이 시작되지 않았다 : 비정상적으로 종료되지 않았다. 파일이 존재하지 않습니다) ...
가능한 원인 :
스타트 업 중에 크롬이 충돌하는 일반적인 원인은 Linux에서 Rome 사용자 (관리자)로 Chrome을 실행하는 것입니다. WebDriver 세션을 만들 때-No-Sandbox 플래그를 통과 하여이 문제를 해결할 수는 있지만 이러한 구성은 지원되지 않고 낙담합니다. 대신 Chrome을 일반 사용자로 실행하도록 환경을 구성해야합니다.
Solution: Run driver with an argument --no-sandbox
. 주의! This is a bypass OS security model.
( def driver ( chrome { :args [ " --no-sandbox " ]}))
A similar problem is described here
The etaoin.api2
namespace brings some bits of alternative macros and functions. They provide better syntax and live in a separate namespace to prevent the old API from breaking.
At the moment, the api2
module provides a set of with-...
macros with a let
-like binding form:
( ns ...
( :require
[etaoin.api :as api]
[etaoin.api2 :as api2]))
( api2/with-chrome [driver {}]
( api/go driver " http://ya.ru " ))
The options map can be skipped so you have only a binding symbol:
( api2/with-firefox [ff]
( api/go ff " http://ya.ru " ))
The project is open for your improvements and ideas. If any of unit tests fall on your machine please submit an issue giving your OS version, browser and console output.
Copyright © 2017—2020 Ivan Grishaev.
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.