clj-commons / etaoin Goto Github PK
View Code? Open in Web Editor NEWPure Clojure Webdriver protocol implementation
Home Page: https://cljdoc.org/d/etaoin
License: Eclipse Public License 1.0
Pure Clojure Webdriver protocol implementation
Home Page: https://cljdoc.org/d/etaoin
License: Eclipse Public License 1.0
The API docs that are hosted at http://grishaev.me are out of date. Notably they don't have entries for any of the wait functions like wait-visible
, etc.
Currently the query
function only returns the first matching element from the browser. It seems important to also include a way to return all of the matching elments, similar to findElements
in Selenium:
(https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/WebElement.html#findElements-org.openqa.selenium.By-)
Use io/file to deal with trailing slash
provide a module with common settings possibly overridden with system vars
Chrome capabilities spec: https://sites.google.com/a/chromium.org/chromedriver/capabilities
List of Chrome arguments: http://peter.sh/experiments/chromium-command-line-switches
What I've tried for far:
(def cap
{:chromeOptions
{:binary "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
:args ["homepage=http://google.com" "--incognito"]}})
;; or just simply
(def cap {:chromeOptions {:args ["--homepage=gmail.com"]}})
and then
(def driver (chrome {:desired-capabilities cap}))
it does not have any effect on default URL.
One interesting fact, it seems that chromedriver accepts only single arguments, for example:
(def cap {:chromeOptions {:args ["--incognito"]}})
really opens chrome in incognito mode.
The task is to investigate why key-value pairs separated with =
are ignored.
that might help
(defn prepend [seq x]
(cons x seq))
(defn make-url
"Makes an Webdriver URL from a host and port."
[host port]
(format "http://%s:%s" host port))
(defn set-path
[driver path]
(update driver :args prepend path))
(defn set-args
[driver args]
(update driver :args concat args))
(defmulti set-port
"Updates driver's map with the given port added to the args.
Note: it takes not an atom but pure map to be used with swap!.
Our further goal is to reduce atom usage everywhere it is possible."
{:arglists '([driver port])}
(fn [driver & _] (:type driver)))
(defmethods set-port
[:firefox :safari]
[driver port]
(set-args driver ["--port" port]))
(defmethods set-port
[:chrome :headless]
[driver port]
(set-args driver [(str "--port=" port)]))
(defmethod set-port
:phantom
[driver port]
(set-args driver ["--webdriver" port]))
Когда-то у меня была мысль сделать поддержку разных версий драйверов в библиотеке, но я не осилил. Может, получится сейчас? Но сначала нужно подумать, нужно ли нам это вообще.
Идея в следующем: есть наша библотека и драйвер X. Со временем драйвер обновляется, что что-то перестает работать. Мы исправляем код под новый драйвер и выпускаем релиз. Тому, кто пожаловался, говорим -- скачайте новый брайвер и обновите библиотеку.
Так нормально, но было бы круто, если ли бы библиотка помнила прошлые реализации. Например, до версии драйвера 1.3 было так, а позже стало эдак. Тогда библиотеке не обящательно нужны свежие драйверы.
Для этого нужно, чтобы при старте мы определяли версию драйвера, например, 1.25. Это можно сделать, вызвав драйвер с ключом --version
. Далее мы ханим версию словаре.
There are too many logs at the moment. Provide an option to regulate it somehow.
I was surprised by the behavior of the way etaoin merges default and user-passed capabilities.
I tried running
(e/headless {:desired-capabilities
{:chromeOptions
{:args ["--proxy-server=localhost:12345"]}}}
and to my surprise a non-headless chrome window opened up.
Looking at the Etaoin source I found the answer. In the default options table, etaoin has these options for :headless
(def defaults
{...
:headless {:port 9515
:path "chromedriver"
:capabilities {:chromeOptions {:args ["--headless"]}}}})
But then in connect-driver
user supplied options are merged with default options:
(deep-merge cap-default cap)
and the implementation of deep-merge
just takes the last value it sees for a nested key:
(defn deep-merge
[& vals]
(if (every? map? vals)
(apply merge-with deep-merge vals)
(last vals)))
Does it make sense to change the implementation of deep-merge to concatenate sequential values? Like:
(deep-merge {:chromeOptions {:args ["arg1"]}} {:chromeOptions {:args ["arg2"]}}) => {:chromeOptions {:args ["arg1" "arg2"]}}
I can make a PR if you think this is a good idea.
An empty Firefox browser window opens and then the test fails immediately.
Firefox version: 54.0.1
OS version: macOS Sierra 10.12.5
Testing etaoin.api-test
DEBUG etaoin.client: firefox 127.0.0.1:4444 POST session {:desiredCapabilities {}}
DEBUG etaoin.client: firefox 127.0.0.1:4444 POST session//url {:url "file:/Users/Jrock/Projects/etaoin/resources/html/test.html"}
DEBUG etaoin.client: firefox 127.0.0.1:4444 DELETE session/
Exception in thread "main" clojure.lang.ExceptionInfo: throw+: {:response {:value {:error "unknown command", :message "DELETE /session/ did not match a known command", :stacktrace "stack backtrace:\n 0: 0x103fe3444 - backtrace::backtrace::trace::h9f8fad52a1a8acf1\n 1: 0x103fe37fe - backtrace::capture::Backtrace::new::hbc1a12654c8fdba8\n 2: 0x103f0df4d - webdriver::error::WebDriverError::new::h10bc8992bd1e5ce5\n 3: 0x103eb690d - _$LT$webdriver..httpapi..WebDriverHttpApi$LT$U$GT$$GT$::decode_request::h59625e2271927f5c\n 4: 0x103ef545e - _$LT$webdriver..server..HttpHandler$LT$U$GT$$u20$as$u20$hyper..server..Handler$GT$::handle::h75bcf4a70da4d3d8\n 5: 0x103e35b11 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::keep_alive_loop::h6cffe26f2f997d81\n 6: 0x103e36665 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::handle_connection::ha65f143767ef2701\n 7: 0x103ec24c1 - hyper::server::handle::_$u7b$$u7b$closure$u7d$$u7d$::h021c4837b0330036\n 8: 0x103ec2a5e - hyper::server::listener::spawn_with::_$u7b$$u7b$closure$u7d$$u7d$::h922fff03b485de70\n 9: 0x103f030a7 - _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::hb095877d82356444\n 10: 0x103e47c86 - std::panicking::try::do_call::h7a395bca958e623c\n 11: 0x10447554a - __rust_maybe_catch_panic\n 12: 0x103e475bc - std::panicking::try::h55d8916b3f7a3515\n 13: 0x103e44e52 - std::panic::catch_unwind::hf1717d8b89ed66e7\n 14: 0x103e46ffc - std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::hd9c5bf9811bb3b73\n 15: 0x103ea1a4c - _$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h0c4aa35835be38e8\n 16: 0x104471714 - std::sys::imp::thread::Thread::new::thread_start::hae18fa578e305215\n 17: 0x7fffb9b5693a - _pthread_body\n 18: 0x7fffb9b56886 - _pthread_start"}}, :path "session/", :payload nil, :method :delete, :type :etaoin/http-error, :port 4444, :host "127.0.0.1", :status 404, :driver {:args ["geckodriver" "--port" 4444], :desired-capabilities nil, :process #object[java.lang.UNIXProcess 0x5b2ade46 "java.lang.UNIXProcess@5b2ade46"], :locator "xpath", :type :firefox, :env {}, :port 4444, :host "127.0.0.1", :url "http://127.0.0.1:4444", :session nil}} {:response {:value {:error "unknown command", :message "DELETE /session/ did not match a known command", :stacktrace "stack backtrace:\n 0: 0x103fe3444 - backtrace::backtrace::trace::h9f8fad52a1a8acf1\n 1: 0x103fe37fe - backtrace::capture::Backtrace::new::hbc1a12654c8fdba8\n 2: 0x103f0df4d - webdriver::error::WebDriverError::new::h10bc8992bd1e5ce5\n 3: 0x103eb690d - _$LT$webdriver..httpapi..WebDriverHttpApi$LT$U$GT$$GT$::decode_request::h59625e2271927f5c\n 4: 0x103ef545e - _$LT$webdriver..server..HttpHandler$LT$U$GT$$u20$as$u20$hyper..server..Handler$GT$::handle::h75bcf4a70da4d3d8\n 5: 0x103e35b11 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::keep_alive_loop::h6cffe26f2f997d81\n 6: 0x103e36665 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::handle_connection::ha65f143767ef2701\n 7: 0x103ec24c1 - hyper::server::handle::_$u7b$$u7b$closure$u7d$$u7d$::h021c4837b0330036\n 8: 0x103ec2a5e - hyper::server::listener::spawn_with::_$u7b$$u7b$closure$u7d$$u7d$::h922fff03b485de70\n 9: 0x103f030a7 - _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::hb095877d82356444\n 10: 0x103e47c86 - std::panicking::try::do_call::h7a395bca958e623c\n 11: 0x10447554a - __rust_maybe_catch_panic\n 12: 0x103e475bc - std::panicking::try::h55d8916b3f7a3515\n 13: 0x103e44e52 - std::panic::catch_unwind::hf1717d8b89ed66e7\n 14: 0x103e46ffc - std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::hd9c5bf9811bb3b73\n 15: 0x103ea1a4c - _$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h0c4aa35835be38e8\n 16: 0x104471714 - std::sys::imp::thread::Thread::new::thread_start::hae18fa578e305215\n 17: 0x7fffb9b5693a - _pthread_body\n 18: 0x7fffb9b56886 - _pthread_start"}}, :path "session/", :payload nil, :method :delete, :type :etaoin/http-error, :port 4444, :host "127.0.0.1", :status 404, :driver {:args ["geckodriver" "--port" 4444], :desired-capabilities nil, :process #object[java.lang.UNIXProcess 0x5b2ade46 "java.lang.UNIXProcess@5b2ade46"], :locator "xpath", :type :firefox, :env {}, :port 4444, :host "127.0.0.1", :url "http://127.0.0.1:4444", :session nil}}, compiling:(/private/var/folders/yp/46_n05bs7k18jnxtkvjfzpk40000gn/T/form-init1325635718044467402.clj:1:125)
at clojure.lang.Compiler.load(Compiler.java:7391)
at clojure.lang.Compiler.loadFile(Compiler.java:7317)
at clojure.main$load_script.invokeStatic(main.clj:275)
at clojure.main$init_opt.invokeStatic(main.clj:277)
at clojure.main$init_opt.invoke(main.clj:277)
at clojure.main$initialize.invokeStatic(main.clj:308)
at clojure.main$null_opt.invokeStatic(main.clj:342)
at clojure.main$null_opt.invoke(main.clj:339)
at clojure.main$main.invokeStatic(main.clj:421)
at clojure.main$main.doInvoke(main.clj:384)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
Caused by: clojure.lang.ExceptionInfo: throw+: {:response {:value {:error "unknown command", :message "DELETE /session/ did not match a known command", :stacktrace "stack backtrace:\n 0: 0x103fe3444 - backtrace::backtrace::trace::h9f8fad52a1a8acf1\n 1: 0x103fe37fe - backtrace::capture::Backtrace::new::hbc1a12654c8fdba8\n 2: 0x103f0df4d - webdriver::error::WebDriverError::new::h10bc8992bd1e5ce5\n 3: 0x103eb690d - _$LT$webdriver..httpapi..WebDriverHttpApi$LT$U$GT$$GT$::decode_request::h59625e2271927f5c\n 4: 0x103ef545e - _$LT$webdriver..server..HttpHandler$LT$U$GT$$u20$as$u20$hyper..server..Handler$GT$::handle::h75bcf4a70da4d3d8\n 5: 0x103e35b11 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::keep_alive_loop::h6cffe26f2f997d81\n 6: 0x103e36665 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::handle_connection::ha65f143767ef2701\n 7: 0x103ec24c1 - hyper::server::handle::_$u7b$$u7b$closure$u7d$$u7d$::h021c4837b0330036\n 8: 0x103ec2a5e - hyper::server::listener::spawn_with::_$u7b$$u7b$closure$u7d$$u7d$::h922fff03b485de70\n 9: 0x103f030a7 - _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::hb095877d82356444\n 10: 0x103e47c86 - std::panicking::try::do_call::h7a395bca958e623c\n 11: 0x10447554a - __rust_maybe_catch_panic\n 12: 0x103e475bc - std::panicking::try::h55d8916b3f7a3515\n 13: 0x103e44e52 - std::panic::catch_unwind::hf1717d8b89ed66e7\n 14: 0x103e46ffc - std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::hd9c5bf9811bb3b73\n 15: 0x103ea1a4c - _$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h0c4aa35835be38e8\n 16: 0x104471714 - std::sys::imp::thread::Thread::new::thread_start::hae18fa578e305215\n 17: 0x7fffb9b5693a - _pthread_body\n 18: 0x7fffb9b56886 - _pthread_start"}}, :path "session/", :payload nil, :method :delete, :type :etaoin/http-error, :port 4444, :host "127.0.0.1", :status 404, :driver {:args ["geckodriver" "--port" 4444], :desired-capabilities nil, :process #object[java.lang.UNIXProcess 0x5b2ade46 "java.lang.UNIXProcess@5b2ade46"], :locator "xpath", :type :firefox, :env {}, :port 4444, :host "127.0.0.1", :url "http://127.0.0.1:4444", :session nil}} {:response {:value {:error "unknown command", :message "DELETE /session/ did not match a known command", :stacktrace "stack backtrace:\n 0: 0x103fe3444 - backtrace::backtrace::trace::h9f8fad52a1a8acf1\n 1: 0x103fe37fe - backtrace::capture::Backtrace::new::hbc1a12654c8fdba8\n 2: 0x103f0df4d - webdriver::error::WebDriverError::new::h10bc8992bd1e5ce5\n 3: 0x103eb690d - _$LT$webdriver..httpapi..WebDriverHttpApi$LT$U$GT$$GT$::decode_request::h59625e2271927f5c\n 4: 0x103ef545e - _$LT$webdriver..server..HttpHandler$LT$U$GT$$u20$as$u20$hyper..server..Handler$GT$::handle::h75bcf4a70da4d3d8\n 5: 0x103e35b11 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::keep_alive_loop::h6cffe26f2f997d81\n 6: 0x103e36665 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::handle_connection::ha65f143767ef2701\n 7: 0x103ec24c1 - hyper::server::handle::_$u7b$$u7b$closure$u7d$$u7d$::h021c4837b0330036\n 8: 0x103ec2a5e - hyper::server::listener::spawn_with::_$u7b$$u7b$closure$u7d$$u7d$::h922fff03b485de70\n 9: 0x103f030a7 - _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::hb095877d82356444\n 10: 0x103e47c86 - std::panicking::try::do_call::h7a395bca958e623c\n 11: 0x10447554a - __rust_maybe_catch_panic\n 12: 0x103e475bc - std::panicking::try::h55d8916b3f7a3515\n 13: 0x103e44e52 - std::panic::catch_unwind::hf1717d8b89ed66e7\n 14: 0x103e46ffc - std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::hd9c5bf9811bb3b73\n 15: 0x103ea1a4c - _$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h0c4aa35835be38e8\n 16: 0x104471714 - std::sys::imp::thread::Thread::new::thread_start::hae18fa578e305215\n 17: 0x7fffb9b5693a - _pthread_body\n 18: 0x7fffb9b56886 - _pthread_start"}}, :path "session/", :payload nil, :method :delete, :type :etaoin/http-error, :port 4444, :host "127.0.0.1", :status 404, :driver {:args ["geckodriver" "--port" 4444], :desired-capabilities nil, :process #object[java.lang.UNIXProcess 0x5b2ade46 "java.lang.UNIXProcess@5b2ade46"], :locator "xpath", :type :firefox, :env {}, :port 4444, :host "127.0.0.1", :url "http://127.0.0.1:4444", :session nil}}
at slingshot.support$stack_trace.invoke(support.clj:201)
at etaoin.client$call.invokeStatic(client.clj:97)
at etaoin.client$call.invoke(client.clj:64)
at etaoin.api$delete_session.invokeStatic(api.clj:150)
at etaoin.api$delete_session.invoke(api.clj:148)
at etaoin.api$disconnect_driver.invokeStatic(api.clj:1954)
at etaoin.api$disconnect_driver.invoke(api.clj:1948)
at etaoin.api$quit.invokeStatic(api.clj:1988)
at etaoin.api$quit.invoke(api.clj:1984)
at etaoin.api_test$fixture_browsers$fn__7159.invoke(api_test.clj:26)
at etaoin.api_test$fixture_browsers.invokeStatic(api_test.clj:26)
at etaoin.api_test$fixture_browsers.invoke(api_test.clj:23)
at clojure.test$compose_fixtures$fn__7977$fn__7978.invoke(test.clj:693)
at clojure.test$default_fixture.invokeStatic(test.clj:686)
at clojure.test$default_fixture.invoke(test.clj:682)
at clojure.test$compose_fixtures$fn__7977.invoke(test.clj:693)
at clojure.test$test_vars$fn__8005.invoke(test.clj:734)
at clojure.test$default_fixture.invokeStatic(test.clj:686)
at clojure.test$default_fixture.invoke(test.clj:682)
at clojure.test$test_vars.invokeStatic(test.clj:730)
at clojure.test$test_all_vars.invokeStatic(test.clj:736)
at clojure.test$test_ns.invokeStatic(test.clj:757)
at clojure.test$test_ns.invoke(test.clj:742)
at clojure.core$map$fn__4785.invoke(core.clj:2646)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.Cons.next(Cons.java:39)
at clojure.lang.RT.boundedLength(RT.java:1749)
at clojure.lang.RestFn.applyTo(RestFn.java:130)
at clojure.core$apply.invokeStatic(core.clj:648)
at clojure.test$run_tests.invokeStatic(test.clj:767)
at clojure.test$run_tests.doInvoke(test.clj:767)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:646)
at clojure.core$apply.invoke(core.clj:641)
at user$eval3200$fn__3263$fn__3294.invoke(form-init1325635718044467402.clj:1)
at user$eval3200$fn__3263$fn__3264.invoke(form-init1325635718044467402.clj:1)
at user$eval3200$fn__3263.invoke(form-init1325635718044467402.clj:1)
at user$eval3200.invokeStatic(form-init1325635718044467402.clj:1)
at user$eval3200.invoke(form-init1325635718044467402.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6927)
at clojure.lang.Compiler.eval(Compiler.java:6917)
at clojure.lang.Compiler.load(Compiler.java:7379)
... 14 more
Tests failed.
Сейчас в run-driver мы процессим некоторые опции, которые долнжны быть в connect-driver. В частности это все, что касается capabilities. Другими словами, в run-driver должно остаться только то, что относится к самому драйверу. В connect-driver передать то, что касается создания новой сессии. В том числе исправить докстринги к обеим функциям.
headless should be not a separated driver but just a predicate since more and more drivers support headless mode.
If the port used for communication with the webdriver is already in use by another process, starting a driver blocks forever.
Use [etaoin "0.1.5"]
, which uses 5555 as the default port for chromedriver
Start a server process listening on port 5555. One way to do this is to start Clojure with the Java command-line option:
"-Dclojure.server.repl={:port 5555 :accept clojure.core.server/repl}"
Alternatively, Netcat can run a server like this:
nc -kl 127.0.0.1 5555
At the Clojure REPL, run:
(require '[etaoin.api :as e])
(def c (e/chrome))
The REPL blocks and does not return.
Running Netcat, I can see the HTTP request to start the webdriver session:
$ nc -kl 127.0.0.1 5555
POST /session HTTP/1.1
Connection: close
content-type: application/json
accept: application/json
accept-encoding: gzip, deflate
Content-Length: 26
Host: 127.0.0.1:5555
User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_92)
{"desiredCapabilities":{}}
If I kill the Netcat process, org.apache.http.NoHttpResponseException
is thrown in the REPL. Otherwise, the REPL just hangs forever.
Adding a timeout on the HTTP response would at least provide some indication of a problem, although it would not necessarily help with diagnosis.
For greater robustness, it would be ideal to find an unused port.
Create a wiki; move code samples from readme there. Create troubleshooting
section.
Environment: MacOS 10.13.1, geckodriver 0.19.1.
Reproduced with Firefox 56.0.2 (stable), Firefox 57.0 (Beta), Firefox Nightly 58.0a1 (2017-11-12).
I can't reproduce this bug with :chrome
and :phantom
.
Steps to reproduce:
:firefox
driver.(e/fill d {:id :username} "foobar")
Expected: no errors.
Actual:
ERROR in (my-test) (support.clj:201)
Uncaught exception, not in assertion.
expected: nil
actual: clojure.lang.ExceptionInfo: throw+: {:response {:value {:error "invalid argument", :message "Missing 'text' parameter", :stacktrace "stack backtrace:\n 0: 0x101c90804 - backtrace::backtrace::trace::hf4fb9cb16c5cd3d5\n 1: 0x101c90bdf - backtrace::capture::Backtrace::new::h109fc438e1b250fa\n 2: 0x1019c824c - webdriver::error::WebDriverError::new::h55f28d494fb52985\n 3: 0x1019bb1a2 - _$LT$webdriver..command..SendKeysParameters$u20$as$u20$webdriver..command..Parameters$GT$::from_json::hcec9d3aa53ea79d9\n 4: 0x1018dd477 - _$LT$webdriver..command..WebDriverMessage$LT$U$GT$$GT$::from_http::hf39003ef8bb4fef9\n 5: 0x1018e0179 - _$LT$webdriver..httpapi..WebDriverHttpApi$LT$U$GT$$GT$::decode_request::h22eca87d078a6dc4\n 6: 0x10192ac07 - _$LT$webdriver..server..HttpHandler$LT$U$GT$$u20$as$u20$hyper..server..Handler$GT$::handle::h2b535e3cc76d3f78\n 7: 0x101844078 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::keep_alive_loop::h45f7f92b91973f2b\n 8: 0x101844f21 - _$LT$hyper..server..Worker$LT$H$GT$$GT$::handle_connection::h5ee65c07c0fed43f\n 9: 0x1018fa1d7 - hyper::server::handle::_$u7b$$u7b$closure$u7d$$u7d$::h3fe31a4cf8d7cf04\n 10: 0x1018fa75d - hyper::server::listener::spawn_with::_$u7b$$u7b$closure$u7d$$u7d$::hf33d4e4ab2936cf9\n 11: 0x1018479f7 - std::sys_common::backtrace::__rust_begin_short_backtrace::h024931f7d16d4d84\n 12: 0x101858cbd - std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::_$u7b$$u7b$closure$u7d$$u7d$::h4f89971b92f1c7a8\n 13: 0x101808ca7 - _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::h0f3b12cd61063ffe\n 14: 0x10185994f - std::panicking::try::do_call::he35b3e04abf19639\n 15: 0x101f9cc8c - __rust_maybe_catch_panic\n 16: 0x1018592fc - std::panicking::try::h6891a1b8bc2b2efc\n 17: 0x1018566d2 - std::panic::catch_unwind::hf8158f85675cb440\n 18: 0x1018586cc - std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::h7ca9b1c3c82280dd\n 19: 0x1018c5da1 - _$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h5a6b04902633fc56\n 20: 0x101f98e7b - std::sys::imp::thread::Thread::new::thread_start::h65773f24735ca5ff\n 21: 0x7fff5d50e6c0 - _pthread_body\n 22: 0x7fff5d50e56c - _pthread_start"}}, :path "session/bb454ccc-15ec-c646-89dd-c2958435f8ce/element/7533efe0-0cd1-6e49-a3f2-5cbbeaabf963/value", :payload {:value ["f" "o" "o" "b" "a" "r"]}, :method :post, :type :etaoin/http-error, :port 29983, :host "127.0.0.1", :status 400, :driver {:args ("geckodriver" "--port" 29983), :capabilities {:loggingPrefs {:browser "INFO"}}, :process #object[java.lang.UNIXProcess 0x77c66ac2 "java.lang.UNIXProcess@77c66ac2"], :locator "xpath", :type :firefox, :env nil, :port 29983, :host "127.0.0.1", :url "http://127.0.0.1:29983", :session "bb454ccc-15ec-c646-89dd-c2958435f8ce"}}
at slingshot.support$stack_trace.invoke (support.clj:201)
etaoin.client$call.invokeStatic (client.clj:97)
etaoin.client$call.invoke (client.clj:64)
etaoin.api$fill_el.invokeStatic (api.clj:1822)
etaoin.api$fill_el.doInvoke (api.clj:1819)
clojure.lang.RestFn.invoke (RestFn.java:445)
clojure.lang.AFn.applyToHelper (AFn.java:160)
clojure.lang.RestFn.applyTo (RestFn.java:132)
clojure.core$apply.invokeStatic (core.clj:663)
clojure.core$apply.invoke (core.clj:652)
etaoin.api$fill.invokeStatic (api.clj:1835)
etaoin.api$fill.doInvoke (api.clj:1826)
clojure.lang.RestFn.invoke (RestFn.java:445)
e2e.auth$fn__12204$fn__12205.invoke (auth.clj:25)
...
How can I piggieback onto a driver instance and get a Cljs REPL connected to it?
Hi,
I would like to use etaoin 0.1.6
with a selenium in docker:
docker run -p 4444:4444 selenium/standalone-firefox:3.4.0-chromium
I am trying to connect the following way:
(require '[etaoin.api :exclude [wait] :refer :all])
(def driver (connect-driver (create-driver :firefox {:port 4444})))
Here an exception is thrown since the create-session
function calls to http://localhost:4444/sessions
(page does not exist). I think the correct url would be http://localhost:4444/wd/hub/session
.
In Clojure you usually use '!' at the end of impure function names that change the state of an object. In this case, many of the functions change the state of a driver. Eg. (go! driver url) instead of (go driver url)
Your project is very similar to something I've been working on. May switch to this after further research. But I thought I'd mention the naming thing regarding driver mutating functions.
You are probably much more experienced in Clojure than me so correct me if I'm wrong.
write a macro with-wait
that will insert wait
commands in the middle of the body. It's useful for debugging.
especially for wait-...
calls
Функции для запуска драйвера принимают словарь переменных :env
для процесса драйвера. Проблема в том, что при явной передаче переменных не учитываются текущие переменные. Поэтому надо не просто передавать их в ProcessBuilder, а сначала взять текущие через System/getenv и дополнить результат теми, что передали в :env, и только потом передать в билдер.
Можно взять способ как в книжке: прочитать переменные, перевести их в кложурную мапу, потом сделать merge. Кложурные мапы имплементят интерфейс Map, поэтому их можно передать как есть. Но ключи обязательно должны быть строками (и значения тоже). Кейворды в ключах перевести через name, а значения через str.
Убрать todo возле env в коде. Добавить в readme.
add fill-multi func to fill many inputs at once.
What's the officially recommended way of doing this? I'd like to select a particular value from a HTML select element. As first try I could do this with first clicking the select and the clicking the option, however, my controls are not working the same way when it happens with human interaction (seemingly some javascript events are not fired properly?!)
It started falling after changes in visible?
function.
When creating a new driver instance, a map of options should support :profile
field that points either on profile directory (Chrome) or a profile name (Firefox).
Technically, it should be done with passing additional arguments for browser process.
Chrome:
https://sites.google.com/a/chromium.org/chromedriver/capabilities
user-data-dir=/path/to/your/custom/profile
Example:
(def driver (chrome {:capabilities {:chromeOptions {:args ["user-data-dir=/path/to/your/custom/profile"]}}}))
Firefox:
https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile
firefox -CreateProfile JoelUser
;-P
flag:(def driver (ff {:capabilities {:moz:firefoxOptions {:args ["-P" "JoelUser"]}}}))
There should be a multimethid in drv
module that fills those flags properly depending on a browser type.
try to improve xpath clauses to support text() = ...
, contains
and some related features.
`keys` is supposed to be a string, which is then converted to a vector ?
[12:01]
That is what caused the bug with an older version of cheshire, but a newer version of cheshire can encode a vector of java.lang.Chars
igrishaev [12:02 PM]
@borkdude I know it looks strange, but still, webdriver requires an array of 1-symbol strings.
[12:03]
I see now. Probably, there might be `(mapv str some-sting)`
borkdude [12:03 PM]
hmm, but it’s first encoded to json right? aaah ok
igrishaev [12:03 PM]
instead of just vec
borkdude [12:03 PM]
I see what happens now: `(cheshire.core/generate-string (vec "foo")) "[\"f\",\"o\",\"o\"]"`
[12:04]
didn’t know webdriver required this, but now I understand. sorry for the noise
igrishaev [12:04 PM]
webriver takes something like `{"text": ["h", "e", "l", "l", "o"]}`
[12:04]
no worries, that’s a good point to prevent an error (edited)
[12:04]
I’ll create an issue for that (edited)
Add a wrapper that dumps a screenshot and HTML file in case of an uncaught exception, smth like:
(with-postmortem {:html "foo/bar.html" :screenshot "foo/image.png"}
(click driver {:id :non-existing}))
The result is:
foo/bar.html
and foo/image.png
are created;Hi @igrishaev
I was trying out this library ( thanks for this pure clojure solution btw 👍 ) and to evaluate it, I wanted to translate this tutorial which relies on selenium
(ns ui-test.file-upload
(:use etaoin.api)
(:require [clojure.test :as test]
[etaoin.keys :as k]))
(def driver (chrome)) ;; here, a Firefox window should appear
;; let's perform a quick Wiki session
(go driver "http://demo.guru99.com/selenium/upload/")
(click driver {:id "terms" })
(click driver {:id "uploadfile_0" })
(fill {:id "uploadfile_0" } "")
(fill driver {:id "uploadfile_0"} )
I'm new to browser automation and clojure, could you help me out a bit please :)
(js-execute d "return arguments[0].scrollIntoView()" {:ELEMENT :d054b533-3e07-6248-b030-b8c237853d8e :element-6066-11e4-a52e-4f735466cecf :d054b533-3e07-6248-b030-b8c237853d8e})
Время ожидания default-timeout сейчас 20, предлагаю понизить до 7 секунд, потому что в случае ошибки приходится долго ждать. Проверить, есть ли в readme упоминия числа 20, и если да, то поменять.
Attempting to follow along with the instructions provided in the README, I ran into an error initializing a driver instance:
sample.core> (use 'etaoin.api)
nil
sample.core> (require '[etaoin.keys :as k])
nil
sample.core> (def driver (firefox))
java.io.IOException: error=2, No such file or directory
java.io.IOException: Cannot run program "geckodriver": error=2, No such file or directory
clojure.lang.Compiler$CompilerException: java.io.IOException: Cannot run program "geckodriver": error=2, No such file or directory, compiling:(form-init2904624762632379775.clj:47:21)
I expect that I'm walking a well-traveled road over here. Please point out what I'm doing wrong if its something obvious.
Recently, maximize started to rise an exception in the latest chrome.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.