clj-commons / etaoin

Pure Clojure Webdriver protocol implementation
https://cljdoc.org/d/etaoin
Eclipse Public License 1.0
915 stars 95 forks source link

How do we query for immediate children of an element? #660

Closed Outrovurt closed 1 month ago

Outrovurt commented 1 month ago

Version 0.4.6

Platform Operating System: Arch Linux Clojure version: 1.11.1

Browser vendor: chrome

Symptom Ok, this is part bug, part feature request: the children function should return immediate child elements only, however it actually returns any matching descendant. It's true, the docs aren't that specific, but in CSS queries we make the distinction between children (immediate) and descendants. I feel we should do the same with etaoin. That said, changing the functionality of children is a breaking change, so it would probably make more sense to introduce descendants, and alias to children, and perhaps immediate-children or direct-children. But a separate function to the existing children is definitely a good idea.

Reproduction

(children driver ul-element {:tag :li})

This will return all <li> elements, not just immediate children of ul-element.

Actual behavior See above.

Expected behavior See above.

Diagnosis See above.

Action This is open to discussion, please let me know your thoughts on the matter. If something else exists which does this already, also let me know. Thanks.

lread commented 1 month ago

Hi thanks for the issue @Outrovurt, the child and children naming was not the best, and these fns therefore have been recently deprecated, see https://github.com/clj-commons/etaoin/issues/559#issuecomment-2248599808. New names are: query-from and query-all-from.

This change will be in the next release of Etaoin.

Does that help?

Outrovurt commented 1 month ago

Hi Lee,

dgr's comment that you linked to is spot on in his analysis. So can you confirm what changes which impact this issue will be in the next release and how it will work? Specifically if I want to write a query to return just the immediate children, how would I do that? From what you have written, it sounds like you are just renaming child -> query-from and children -> query-all-from, is that correct? Or are you additionally going to create new child and children functions which do what they are supposed to?

lread commented 1 month ago

Hi @Outrovurt, we have no plans to implement new child and children; the existing fns will remain to avoid a breaking change but will be deprecated.

I'll fire up a REPL and see if I can answer your question about immediate children.

Let's set things up:

(require '[etaoin.api :as e])

(def driver (e/firefox))

And then navigate somewhere interesting:

(e/go driver "https://clojure.org")

I did a page inspection in Firefox and saw a div with the class clj-learn-more. Let's see if we can query its immediate children.

We'll use the :outerHTML property to see if we were successful.

Let's start with XPath, ./* is immediate children of:

(for [el (e/query-all driver {:fn/has-class "clj-learn-more"} "./*")]
  (e/get-element-property-el driver el :outerHTML))
;; => ("<h3 class=\"clj-learn-more-heading\">Learn More</h3>"
;;     "<a href=\"about/rationale\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Rationale</h4>\n              <p class=\"clj-learn-more-detail\">A brief overview of Clojure and the features it includes</p></a>"
;;     "<a href=\"guides/getting_started\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Getting Started</h4>\n              <p class=\"clj-learn-more-detail\">Resources for getting Clojure up and running</p></a>"
;;     "<a href=\"reference/documentation\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Reference</h4>\n              <p class=\"clj-learn-more-detail\">Grand tour of all that Clojure has to offer</p></a>"
;;     "<a href=\"guides/guides\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Guides</h4>\n              <p class=\"clj-learn-more-detail\">Walkthroughs to help you learn along the way</p></a>"
;;     "<a href=\"community/resources\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Community</h4>\n              <p class=\"clj-learn-more-detail\">We have a vibrant, flourishing  community. Join us!</p></a>"
;;     "<a href=\"https://www.youtube.com/user/ClojureTV/videos\"><img src=\"/images/clojuretv.png\" alt=\"Clojure TV logo\"></a>")

But maybe you are more familiar with CSS. I had to look this one up, but the equivalent is :scope > *:

(for [el (e/query-all driver {:fn/has-class "clj-learn-more"} {:css ":scope > *"})]
  (e/get-element-property-el driver el :outerHTML))
;; => ("<h3 class=\"clj-learn-more-heading\">Learn More</h3>"
;;     "<a href=\"about/rationale\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Rationale</h4>\n              <p class=\"clj-learn-more-detail\">A brief overview of Clojure and the features it includes</p></a>"
;;     "<a href=\"guides/getting_started\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Getting Started</h4>\n              <p class=\"clj-learn-more-detail\">Resources for getting Clojure up and running</p></a>"
;;     "<a href=\"reference/documentation\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Reference</h4>\n              <p class=\"clj-learn-more-detail\">Grand tour of all that Clojure has to offer</p></a>"
;;     "<a href=\"guides/guides\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Guides</h4>\n              <p class=\"clj-learn-more-detail\">Walkthroughs to help you learn along the way</p></a>"
;;     "<a href=\"community/resources\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Community</h4>\n              <p class=\"clj-learn-more-detail\">We have a vibrant, flourishing  community. Join us!</p></a>"
;;     "<a href=\"https://www.youtube.com/user/ClojureTV/videos\"><img src=\"/images/clojuretv.png\" alt=\"Clojure TV logo\"></a>")

Or if we want to express our query entirely in CSS (this is probably clearer):

(for [el (e/query-all driver {:css ".clj-learn-more > *"})]
  (e/get-element-property-el driver el :outerHTML))
;; => ("<h3 class=\"clj-learn-more-heading\">Learn More</h3>"
;;     "<a href=\"about/rationale\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Rationale</h4>\n              <p class=\"clj-learn-more-detail\">A brief overview of Clojure and the features it includes</p></a>"
;;     "<a href=\"guides/getting_started\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Getting Started</h4>\n              <p class=\"clj-learn-more-detail\">Resources for getting Clojure up and running</p></a>"
;;     "<a href=\"reference/documentation\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Reference</h4>\n              <p class=\"clj-learn-more-detail\">Grand tour of all that Clojure has to offer</p></a>"
;;     "<a href=\"guides/guides\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Guides</h4>\n              <p class=\"clj-learn-more-detail\">Walkthroughs to help you learn along the way</p></a>"
;;     "<a href=\"community/resources\" class=\"w-inline-block clj-learn-more-item\">\n              <h4 class=\"clj-learn-more-item-heading\">Community</h4>\n              <p class=\"clj-learn-more-detail\">We have a vibrant, flourishing  community. Join us!</p></a>"
;;     "<a href=\"https://www.youtube.com/user/ClojureTV/videos\"><img src=\"/images/clojuretv.png\" alt=\"Clojure TV logo\"></a>")

So, Etaoin queries already support restricting elements to immediate children, so we don't need any specialized fns. Does that help?

Outrovurt commented 1 month ago

Aha, so just use the XPath or CSS queries, no special functions. Got it, thanks a lot Lee.

lread commented 1 month ago

My pleasure @Outrovurt, I'm sure your question will help someone else down the line.