Consider Supporting Selenium Grid WebDriver
Using [etaoin "0.4.6"] and seleniarm/standalone-chromium:4.1.2-20220227 docker image,
(let [q {:tag :input :id "email"}]
(etaoin/wait-exists driver q)
[(etaoin/query driver q)
(etaoin/query-all driver q)])
yields
[nil ["427cdc53-73a7-4a55-a98a-a6a05a650805"]]
This default multimethod does not find the value correctly, but the Safari and Firefox multimethods do.
When issuing a raw execute:
(let [q {:tag :input :id "email"}
[loc term] (etaoin.query/expand driver q)]
(-> (etaoin.api/execute
{:driver driver
:method :post
:path [:session (:session driver) :element]
:data {:using loc :value term}})))
the return is:
{:state "success",
:sessionId nil,
:class "org.openqa.selenium.remote.Response",
:value
{:element-6066-11e4-a52e-4f735466cecf
"a3b83ecf-961e-41a9-9fb4-2f8e8213b362"},
:status 0}
That's why the (-> ... first second) works. Any ideas?
Hiya @dpassen, thanks for raising this issue.
I've not tried Selenium Grid docker images yet, but to get started, I'll do a sanity test on macOS hitting regular old Chrome.
(require '[etaoin.api :as e])
(def driver (e/chrome))
;; status gives us some idea of what I'm running here:
(e/get-status driver)
;; => {:build
;; {:version
;; "103.0.5060.53 (a1711811edd74ff1cf2150f36ffa3b0dae40b17f-refs/branch-heads/5060@{#853})"},
;; :message "ChromeDriver ready for new sessions.",
;; :os {:arch "x86_64", :name "Mac OS X", :version "10.15.7"},
;; :ready true}
;; I'll navigate to a random page a test page on the InterWebs (so we can be on the page, :-))
(e/go driver "https://testpages.herokuapp.com/styled/basic-html-form-test.html")
;; And I'll adapt your query so it will find a single input on that page:
(let [q {:tag :input :name "username"}]
(e/wait-exists driver q)
[(e/query driver q)
(e/query-all driver q)])
;; I'll also try your raw execute:
(require '[etaoin.query :as eq])
(let [q {:tag :input :name "username"}
[loc term] (eq/expand driver q)]
(-> (etaoin.api/execute
{:driver driver
:method :post
:path [:session (:session driver) :element]
:data {:using loc :value term}})))
;; => {:sessionId "3c9c305bfe7b6fa595bacada3d3cfedc",
;; :status 0,
;; :value {:ELEMENT "0.7985155027546353-1"}}
So does Selenium Grid translate/normalize W3C WebDriver requests?
The :class "org.openqa.selenium.remote.Response" in your example raw response seems to suggest it is does at least add some data.
I'll learn-as-I-go with docker images.
Here's me launching a current seleniarm/standalone-chrome image:
docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" seleniarm/standalone-chromium:4.2.2-20220620
And from the REPL
(require '[etaoin.api :as e])
;; fake out a driver to check the status
(e/get-status {:webdriver-url "http://localhost:4444"})
;; => {:ready true,
;; :message "Selenium Grid ready.",
;; :nodes
;; [{:id "cb4a51db-715b-49f8-8cb4-9a72859526ab",
;; :uri "http://172.17.0.2:4444",
;; :maxSessions 1,
;; :osInfo {:arch "amd64", :name "Linux", :version "5.10.104-linuxkit"},
;; :heartbeatPeriod 60000,
;; :availability "UP",
;; :version "4.2.2 (revision 683ccb65d6)",
;; :slots
;; [{:id
;; {:hostId "cb4a51db-715b-49f8-8cb4-9a72859526ab",
;; :id "9a395f09-e0e6-402b-9dbc-4d587c871d36"},
;; :lastStarted "1970-01-01T00:00:00Z",
;; :session nil,
;; :stereotype
;; {:browserName "chrome",
;; :browserVersion "102.0",
;; :platformName "LINUX"}}]}]}
;; Ok that's certainly a selenium grid generated response
;; Now I'll try to create a session on the grid.
;; I was getting timeouts until I added in the :capabilities you see
;; (a selenium grid docs example made me think this might help)
(def driver (e/chrome {:webdriver-url "http://localhost:4444"
:capabilities {:browserName "chrome"
:browserVersion "102.0"
:platformName "LINUX"}}))
;; let's see what status returns now
(e/get-status driver)
;; => {:ready false,
;; :message "Selenium Grid not ready.",
;; :nodes
;; [{:id "85df67b0-e738-4c4b-b07a-1c5587ce1934",
;; :uri "http://172.17.0.2:4444",
;; :maxSessions 1,
;; :osInfo {:arch "amd64", :name "Linux", :version "5.10.104-linuxkit"},
;; :heartbeatPeriod 60000,
;; :availability "UP",
;; :version "4.2.2 (revision 683ccb65d6)",
;; :slots
;; [{:id
;; {:hostId "85df67b0-e738-4c4b-b07a-1c5587ce1934",
;; :id "e22d24ce-6e0b-4bd7-99b1-b9853fca0bea"},
;; :lastStarted "2022-06-22T20:08:44.408660Z",
;; :session
;; {:capabilities
;; {:browserName "chrome",
;; :acceptInsecureCerts false,
;; :goog:chromeOptions {:debuggerAddress "localhost:38761"},
;; :se:cdpVersion "102.0.5005.115",
;; :networkConnectionEnabled false,
;; :webauthn:extension:largeBlob true,
;; :unhandledPromptBehavior "dismiss and notify",
;; :pageLoadStrategy "normal",
;; :setWindowRect true,
;; :se:cdp
;; "ws://172.17.0.2:4444/session/693254bf31e96a52e2d77fb5d48396d2/se/cdp",
;; :webauthn:virtualAuthenticators true,
;; :proxy {},
;; :chrome
;; {:chromedriverVersion
;; "102.0.5005.115 (174dbe6e33bc81994fceb71d751be201d0b4803d-refs/branch-heads/5005_109@{#3})",
;; :userDataDir "/tmp/.org.chromium.Chromium.f0C2DS"},
;; :browserVersion "102.0.5005.115",
;; :timeouts {:implicit 0, :pageLoad 300000, :script 30000},
;; :platformName "LINUX",
;; :webauthn:extension:credBlob true,
;; :strictFileInteractability false},
;; :sessionId "693254bf31e96a52e2d77fb5d48396d2",
;; :start "2022-06-22T20:08:44.408660Z",
;; :stereotype
;; {:browserName "chrome", :browserVersion "102.0", :platformName "LINUX"},
;; :uri "http://172.17.0.2:4444"},
;; :stereotype
;; {:browserName "chrome",
;; :browserVersion "102.0",
;; :platformName "LINUX"}}]}]}
;; interesting!
;; Ok let's repeat our experiments
(e/go driver "https://testpages.herokuapp.com/styled/basic-html-form-test.html")
;; When I open http://localhost:7900 in my browser I can see that the go worked
(let [q {:tag :input :name "username"}]
(e/wait-exists driver q)
[(e/query driver q)
(e/query-all driver q)])
;; => [nil ["258562f2-4ef4-4484-b364-baa137ff3906"]]
;; ^ matches your experience
(require '[etaoin.query :as eq])
(let [q {:tag :input :name "username"}
[loc term] (eq/expand driver q)]
(-> (etaoin.api/execute
{:driver driver
:method :post
:path [:session (:session driver) :element]
:data {:using loc :value term}})))
;; => {:state "success",
;; :sessionId nil,
;; :class "org.openqa.selenium.remote.Response",
;; :value
;; {:element-6066-11e4-a52e-4f735466cecf "258562f2-4ef4-4484-b364-baa137ff3906"},
;; :status 0}
;; ^ also matches your experience
So to me, without understanding more, it seems that Selenium Grid is not a simple pass-through to chromedriver. It does stuff.
Etaoin currently assumes a connection to chromedriver (or any other driver) is not abstracted in any way. I'm guessing that to support Selenium Grid, we'd have to add in a feature to understand that Etaoin is talking to a Selenium Grid W3C WebDriver HTTP server.
Which seems reasonable if there is enough interest from the community in doing so.
Thanks for the research! Above and beyond
Thanks for the issue, I might not have learned about Selenium Grid otherwise!
I realised this feature for me, but did not tested all functions of Etaion. Result success, but need some modifies. My fork with this feature is and example
Cool @ekochetkov, thanks for sharing. (Note your example is giving me a 404).
So does Selenium Grid WebDriver abstract specific WebDriver implementations to the W3C spec?
Cool @ekochetkov, thanks for sharing. (Note your example is giving me a 404).
Fix
So does Selenium Grid WebDriver abstract specific WebDriver implementations to the W3C spec?
On the Webdriver page of selenium development documentation exists reference to W3C Recommendation
Sooo... my understanding is as Etaoin was developed it adapted to the various WebDriver implementations which did not always follow the W3C spec correctly.
(Some of the adaptations might no longer be necessary with current WebDriver implementations that might be more conformant. Dunno.)
I assume the Selenium Grid abstraction, since it is written by the Selenium team who drive the WebDriver spec, probably abstracts the WebDriver implementations to conform to the spec.
Of interest perhaps: api to get status of grid