From 0de187a538135d4e21fdad03e151d8c8137c2822 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Thu, 11 Jul 2024 20:08:10 -0500 Subject: [PATCH 1/5] doc: user-guide: add list of :fn/* (#605) * doc: user-guide: add list of :fn/* Added a complete list of :fn/* functions that are available in the map syntax. This includes everything in impl/xpath.clj, including :fn/has-string. * docs: user-guide: update :fn/* text to use one line per sentence * Move CHANGELOG entry for #602 to docs section --- CHANGELOG.adoc | 1 + doc/01-user-guide.adoc | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 270545e1..5fe6b7f3 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -16,6 +16,7 @@ A release with an intentional breaking changes is marked with: * docs ** https://github.com/clj-commons/etaoin/issues/534[#534]: better describe `etaoin.api/select` and its alternatives ** https://github.com/clj-commons/etaoin/issues/536[#536]: user guide examples are now all os agnostic and CI tested via test-doc-blocks on all supported OSes +* https://github.com/clj-commons/etaoin/issues/602[#602]: Document all :fn/* query pseudo-functions in a definitive list == v1.0.40 diff --git a/doc/01-user-guide.adoc b/doc/01-user-guide.adoc index d9d3479d..4dc35970 100644 --- a/doc/01-user-guide.adoc +++ b/doc/01-user-guide.adoc @@ -569,7 +569,30 @@ Useful when you need to select a third row from a table, for example. * Any non-special key represents an attribute and its value. * `:fn/` is a prefix followed by a supported query function. -Examples: +There are several query functions of the form `:fn/*`. +Each query function takes a parameter which is the value associated with the query function keyword in the map. + +* `:fn/text`: Takes a string parameter. Matches if the element has the exact text specified. +* `:fn/has-text`: Takes a string parameter. + Matches if the element includes the specified text. +* `:fn/has-string`: Takes a string parameter. + Matches if the element string contains the specified string. + The difference between `:fn/has-text` and `:fn/has-string` is the difference between the XPath `text()` and `string()` functions (`text()` is the text within a given element and `string()` is the text of all descendant elements concatenated together in document order). + Generally, if you're targeting an element at the top of the hierarchy, you probably want `:fn/has-string`, and if you're targeting a single element at the bottom of the hierarchy, you probably want to use `:fn/has-text`. +* `:fn/has-class`: Takes a string parameter. + Matches if the element's `class` attribute includes the string. Unlike using a `:class` key in the map, `:fn/has-class` can match single classes, whereas `:class` is an exact match of the whole class string. +* `:fn/has-classes`: Takes a vector of strings parameter. + Matches if the element's `class` attribute includes _all_ of the specified class strings. +* `:fn/link`: Takes a string parameter. + Matches if the element's `href` attribute contains the specified string. +* `:fn/enabled`: Takes a boolean (`true` or `false`) parameter. + If the parameter is `true`, matches if the element is enabled. + If the parameter is `false`, matches if the element is disabled. +* `:fn/disabled`: Takes a boolean (`true` or `false`) parameter. + If the parameter is `true`, matches if the element is disabled. + If the parameter is `true`, matches if the element is enabled. + +Here are some examples of the map syntax: * find the first `div` tag + From 22b225dd0cba21d46a59f331fd081440a2dccfe6 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Tue, 16 Jul 2024 09:23:26 -0500 Subject: [PATCH 2/5] Add support for shadow DOM #604 (#606) Implement and document shadow DOM functions (#604) --- CHANGELOG.adoc | 1 + doc/01-user-guide.adoc | 119 ++++++++++++++++++++++++++++ doc/user-guide-sample.html | 9 +++ env/test/resources/static/test.html | 9 +++ src/etaoin/api.clj | 118 +++++++++++++++++++++++++++ test/etaoin/api_test.clj | 39 +++++++++ 6 files changed, 295 insertions(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 5fe6b7f3..5985b21f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -10,6 +10,7 @@ A release with an intentional breaking changes is marked with: * https://github.com/clj-commons/etaoin/pull/552[#552]: Add support for wide characters to input fill functions (https://github.com/tupini07[@tupini07]) * https://github.com/clj-commons/etaoin/issues/566[#566]: Recognize `:driver-log-level` for Edge +* https://github.com/clj-commons/etaoin/issues/604[#604]: Add support for shadow DOM * bump all deps to current versions * tests ** https://github.com/clj-commons/etaoin/issues/572[#572]: stop using chrome `--no-sandbox` option, it has become problematic on Windows (and we did not need it anyway) diff --git a/doc/01-user-guide.adoc b/doc/01-user-guide.adoc index 4dc35970..539f356c 100644 --- a/doc/01-user-guide.adoc +++ b/doc/01-user-guide.adoc @@ -845,6 +845,125 @@ The following query will find a vector of `div` tags, then return a set of all ` ;; => ("a1" "a2" "a3" "a4" "a5" "a6" "a7" "a8" "a9") ---- +[#shadow-dom] +=== Querying the Shadow DOM + +The shadow DOM provides a way to attach another DOM tree to a specified element in the normal DOM and have the internals of that tree hidden from JavaScript and CSS on the same page. +When the browser renders the DOM, the elements from the shadow DOM appear at the location where the tree is rooted in the normal DOM. +This provides a level of encapsulation, allowing "components" in the shadow DOM to be styled differently than the rest of the page and preventing conflicts between the normal page CSS and the component CSS. +The shadow DOM is also hidden from normal Web Driver queries (`query`) and thus requires a separate set of API calls to query it. For more details about the shadow DOM, see this article at https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#shadow_dom_and_custom_elements[Mozilla Developer Network (MDN)]. + +There are a few terms that are important to understand when dealing with the Shadow DOM. +The "shadow root host" is the element in the standard DOM to which a shadow root is attached as a property. +The "shadow root" is the top of the shadow DOM tree rooted at the shadow root host. + +The following examples use this HTML fragment in the User Guide sample HTML that has a bit of shadow DOM in it. + +[source,html] +---- +I'm not in the shadow DOM +
+ +
+---- + +Everthing in the `template` element is part of the shadow DOM. +The `div` with the `id` of `shadow-root-host` is, as the ID suggests, the shadow root host element. + +Given this HTML, you can run a standard `query` to find the shadow root host and then use `get-element-property-el` to return to the `"shadowRoot"` property. +Note that the element IDs returned in the following examples will be unique to the specific Etaoin driver and driver session and you will not see the same IDs. + +[source,clojure] +---- +(e/query driver {:id "shadow-root-host"}) +;; an element ID similar to (but not the same as) +;; "78344155-7a53-46fb-a46e-e864210e501d" + +(e/get-element-property-el driver (e/query driver {:id "shadow-root-host"}) "shadowRoot") +;; something similar to +;; {:shadow-6066-11e4-a52e-4f735466cecf "ac5ab914-7f93-427f-a0bf-f7e91098fd37"} + +(e/get-element-property driver {:id "shadow-root-host"} "shadowRoot") +;; something similar to +;; {:shadow-6066-11e4-a52e-4f735466cecf "ac5ab914-7f93-427f-a0bf-f7e91098fd37"} +---- + +If you go this route, you're going to have to pick apart the return +values. +The element-id of the shadow root is the string value of the first map key. + +You can get the shadow root element ID more directly using Etaoin's `get-element-shadow-root` API. +The query parameter looks for a matching element in the standard DOM and returns its shadow root property. + +[source,clojure] +---- +(e/get-element-shadow-root driver {:id "shadow-root-host"}) +;; something similar to +;; "ac5ab914-7f93-427f-a0bf-f7e91098fd37" +---- + +If you already have the shadow root host element, you can return its corresponding shadow root element ID using `get-element-shadow-root-el`. + +[source,clojure] +---- +(def host (e/query driver {:id "shadow-root-host"})) +(e/get-element-shadow-root-el driver host) +;; something similar to +;; "ac5ab914-7f93-427f-a0bf-f7e91098fd37" +---- + +You can test whether an element is a shadow root host using `has-shadow-root?` and `has-shadow-root-el?`. + +[source,clojure] +---- +(e/has-shadow-root? driver {:id "shadow-root-host"}) +;; => true +(e/has-shadow-root-el? driver host) +;; => true +(e/has-shadow-root? driver {:id "not-in-shadow"}) +;; => false +---- + +Now that you know how to retrieve the shadow root, you can query elements in the shadow DOM using `query-shadow-root`, `query-all-shadow-root`, `query-shadow-root-el`, and `query-all-shadow-root-el`. + +For `query-shadow-root` and `query-all-shadow-root`, the `q` parameter specifies a query of the _normal_ DOM to find the shadow root host. +If the host is identified, the `shadow-q` parameter is a query that is executed within the shadow DOM rooted at the shadow root host. + +The `query-shadow-root-el` and `query-all-shadow-root-el` allow you to specify the shadow root host element directly, rather than querying for it. + +[source,clojure] +---- +(def in-shadow (e/query-shadow-root driver {:id "shadow-root-host"} {:css "#in-shadow"})) +(e/get-element-text-el driver in-shadow) +;; => "I'm in the shadow DOM" + +(->> (e/query-all-shadow-root driver {:id "shadow-root-host"} {:css "span"}) + (map #(e/get-element-text-el driver %))) +;; => ("I'm in the shadow DOM" "I'm also in the shadow DOM") + +(def shadow-root (e/get-element-shadow-root-el driver host)) +(e/get-element-text-el driver (e/query-shadow-root-el driver shadow-root {:css "#in-shadow"})) +;; => "I'm in the shadow DOM" + +(->> (e/query-all-shadow-root-el driver shadow-root {:css "span"}) + (map #(e/get-element-text-el driver %))) +;; > ("I'm in the shadow DOM" "I'm also in the shadow DOM") +---- + +[#shadow-root-browser-limitations] +[NOTE] +==== +In the previous shadow root queries, you should note that we used CSS selectors for the `shadow-q` argument in each case. +This was done because current browsers do not support XPath, which is what the Etaoin map syntax is typically translated into under the hood. +While it is expected that browsers will support XPath queries of the shadow DOM in the future, it is unclear when this support might appear. +For now, use CSS. + +For more information, see the https://wpt.fyi/results/webdriver/tests/classic/find_element_from_shadow_root/find.py?label=experimental&label=master&aligned[Web Platforms Test Dashobard]. +==== + === Interacting with Queried Elements To interact with elements found via a `query` or `query-all` function call you have to pass the query result to either `click-el` or `fill-el` (note the `-el` suffix): diff --git a/doc/user-guide-sample.html b/doc/user-guide-sample.html index 42c88019..b5f030b5 100644 --- a/doc/user-guide-sample.html +++ b/doc/user-guide-sample.html @@ -140,6 +140,15 @@

Query Tree Example

+

Shadow DOM Example

+ I'm not in the shadow DOM +
+ +
+

Frames

In main page paragraph

diff --git a/env/test/resources/static/test.html b/env/test/resources/static/test.html index 1e4473e5..9071cef8 100644 --- a/env/test/resources/static/test.html +++ b/env/test/resources/static/test.html @@ -238,6 +238,15 @@

Find text with quote

+

Shadow DOM

+ I'm not in the shadow DOM +
+ +
+

Document end

diff --git a/src/etaoin/api.clj b/src/etaoin/api.clj index dc9d79cc..102bd4a2 100644 --- a/src/etaoin/api.clj +++ b/src/etaoin/api.clj @@ -25,6 +25,8 @@ **Querying/Selecting DOM Elements** - [[query]] [[query-all]] [[query-tree]] + - [[query-shadow-root]] [[query-shadow-root-el]] [[query-all-shadow-root]] [[query-all-shadow-root-el]] + - [[has-shadow-root?]] [[has-shadow-root-el?]] - [[exists?]] [[absent?]] - [[displayed?]] [[displayed-el?]] [[enabled?]] [[enabled-el?]] [[disabled?]] [[invisible?]] [[visible?]] - [[child]] [[children]] @@ -33,6 +35,7 @@ - [[get-element-property]] [[get-element-property-el]] [[get-element-properties]] - [[has-class?]] [[has-class-el?]] [[has-no-class?]] - [[get-element-css]] [[get-element-css-el]] [[get-element-csss]] + - [[get-element-shadow-root]] [[get-element-shadow-root-el]] - [[get-element-text]] [[get-element-text-el]] [[has-text?]] - [[get-element-inner-html]] [[get-element-inner-html-el]] - [[get-element-value]] [[get-element-value-el]] @@ -1611,6 +1614,110 @@ [driver q] (get-element-property driver q :value)) +(defn get-element-shadow-root-el + "Returns the shadow root for the specified element or `nil` if the + element does not have a shadow root." + [driver el] + (-> (get-element-property-el driver el "shadowRoot") + first + second)) + +(defn get-element-shadow-root + "Returns the shadow root for the first element matching the query, or + `nil` if the element does not have a shadow root. + + See [[query]] for more details on `q`." + [driver q] + (get-element-shadow-root-el driver (query driver q))) + +;;; +;;; Shadow root queries +;;; + +(defmulti ^:private find-element-from-shadow-root* dispatch-driver) + +(defmethods find-element-from-shadow-root* + [:firefox :safari] + [driver shadow-root-el locator term] + {:pre [(some? shadow-root-el)]} + (-> (execute {:driver driver + :method :post + :path [:session (:session driver) :shadow shadow-root-el :element] + :data {:using locator :value term}}) + :value + first + second)) + +(defmethod find-element-from-shadow-root* :default + [driver shadow-root-el locator term] + {:pre [(some? shadow-root-el)]} + (-> (execute {:driver driver + :method :post + :path [:session (:session driver) :shadow shadow-root-el :element] + :data {:using locator :value term}}) + :value + :ELEMENT)) + +(defmulti ^:private find-elements-from-shadow-root* dispatch-driver) + +(defmethod find-elements-from-shadow-root* :default + [driver shadow-root-el locator term] + {:pre [(some? shadow-root-el)]} + (->> (execute {:driver driver + :method :post + :path [:session (:session driver) :shadow shadow-root-el :elements] + :data {:using locator :value term}}) + :value + (mapv (comp second first)))) + +(defn query-shadow-root-el + "Queries the shadow DOM rooted at `shadow-root-el`, looking for the + first element specified by `shadow-q`. + + The `shadow-q` parameter is similar to the `q` parameter of + the [[query]] function, but some drivers may limit it to specific + formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information." + [driver shadow-root-el shadow-q] + (let [[loc term] (query/expand driver shadow-q)] + (find-element-from-shadow-root* driver shadow-root-el loc term))) + +(defn query-all-shadow-root-el + "Queries the shadow DOM rooted at `shadow-root-el`, looking for all + elements specified by `shadow-q`. + + The `shadow-q` parameter is similar to the `q` parameter of + the [[query]] function, but some drivers may limit it to specific + formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information." + [driver shadow-root-el shadow-q] + (let [[loc term] (query/expand driver shadow-q)] + (find-elements-from-shadow-root* driver shadow-root-el loc term))) + +(defn query-shadow-root + "First, conducts a standard search (as if by [[query]]) for an element + with a shadow root. Then, from that shadow root element, conducts a + search of the shadow DOM for the first element matching `shadow-q`. + + For details on `q`, see [[query]]. + + The `shadow-q` parameter is similar to the `q` parameter of + the [[query]] function, but some drivers may limit it to specific + formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information." + [driver q shadow-q] + (query-shadow-root-el driver (get-element-shadow-root driver q) shadow-q)) + +(defn query-all-shadow-root + "First, conducts a standard search (as if by [[query]]) for an element + with a shadow root. Then, from that shadow root element, conducts a + search of the shadow DOM for all elements matching `shadow-q`. + + For details on `q`, see [[query]]. + + The `shadow-q` parameter is similar to the `q` parameter of + the [[query]] function, but some drivers may limit it to specific + formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information." + [driver q shadow-q] + (query-all-shadow-root-el driver (get-element-shadow-root driver q) shadow-q)) + ;; ;; cookies ;; @@ -2431,6 +2538,17 @@ :arglists '([driver])} has-no-alert? (complement has-alert?)) +(defn has-shadow-root-el? + "Returns `true` if the specified element has a shadow root or `false` otherwise." + [driver el] + (boolean (get-element-shadow-root-el driver el))) + +(defn has-shadow-root? + "Returns `true` if the first element matching the query has a shadow + root or `false` otherwise." + [driver q] + (boolean (get-element-shadow-root driver q))) + ;; ;; wait functions ;; diff --git a/test/etaoin/api_test.clj b/test/etaoin/api_test.clj index abade557..802cd937 100644 --- a/test/etaoin/api_test.clj +++ b/test/etaoin/api_test.clj @@ -799,6 +799,45 @@ (e/wait 1) (is (str/ends-with? (e/get-url *driver*) "?login=1&password=2&message=3")))))) +(deftest test-shadow-dom + (testing "basic functional sanity" + ;; Validate that the test DOM is as we would expect + (is (e/has-text? *driver* {:id "not-in-shadow"} "I'm not in the shadow DOM")) + (is (not (e/has-text? *driver* {:id "in-shadow"} "I'm in the shadow DOM")))) + (testing "getting the shadow root for an element" + (is (some? (e/get-element-shadow-root *driver* {:id "shadow-root-host"}))) + (is (some? (e/get-element-shadow-root-el *driver* + (e/query *driver* {:id "shadow-root-host"}))))) + (testing "whether an element has a shadow root" + (is (e/has-shadow-root? *driver* {:id "shadow-root-host"})) + (is (e/has-shadow-root-el? *driver* (e/query *driver* {:id "shadow-root-host"})))) + (let [shadow-root (e/get-element-shadow-root *driver* {:id "shadow-root-host"})] + (testing "querying the shadow root element for a single element" + (is (= "I'm in the shadow DOM" + (->> (e/query-shadow-root-el *driver* + shadow-root + {:css "#in-shadow"}) + (e/get-element-text-el *driver*)))) + (is (= "I'm also in the shadow DOM" + (->> (e/query-shadow-root-el *driver* + shadow-root + {:css "#also-in-shadow"}) + (e/get-element-text-el *driver*))))) + (testing "querying the shadow root element for multiple elements" + (is (= ["I'm in the shadow DOM" "I'm also in the shadow DOM"] + (->> (e/query-all-shadow-root-el *driver* + shadow-root + {:css "span"}) + (mapv #(e/get-element-text-el *driver* %))))))) + (testing "querying the shadow root element" + (is (= "I'm in the shadow DOM" + (->> (e/query-shadow-root *driver* {:id "shadow-root-host"} {:css "#in-shadow"}) + (e/get-element-text-el *driver*))))) + (testing "querying the shadow root element for multiple elements" + (is (= ["I'm in the shadow DOM" "I'm also in the shadow DOM"] + (->> (e/query-all-shadow-root *driver* {:id "shadow-root-host"} {:css "span"}) + (mapv #(e/get-element-text-el *driver* %))))))) + (comment ;; start test server (def test-server (p/process {:out :inherit :err :inherit} "bb test-server --port" 9993)) From 11e1dfedcd8ba6dac7d419014820ec1f312c46ec Mon Sep 17 00:00:00 2001 From: Lee Read Date: Tue, 16 Jul 2024 10:33:22 -0400 Subject: [PATCH 3/5] doc: thank Dave Roberts for contribution [skip ci] (#607) Thanks @dgr! --- CHANGELOG.adoc | 4 +++- README.adoc | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 5985b21f..b4ebd85f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -11,13 +11,15 @@ A release with an intentional breaking changes is marked with: (https://github.com/tupini07[@tupini07]) * https://github.com/clj-commons/etaoin/issues/566[#566]: Recognize `:driver-log-level` for Edge * https://github.com/clj-commons/etaoin/issues/604[#604]: Add support for shadow DOM +(https://github.com/dgr[@dgr]) * bump all deps to current versions * tests ** https://github.com/clj-commons/etaoin/issues/572[#572]: stop using chrome `--no-sandbox` option, it has become problematic on Windows (and we did not need it anyway) * docs ** https://github.com/clj-commons/etaoin/issues/534[#534]: better describe `etaoin.api/select` and its alternatives ** https://github.com/clj-commons/etaoin/issues/536[#536]: user guide examples are now all os agnostic and CI tested via test-doc-blocks on all supported OSes -* https://github.com/clj-commons/etaoin/issues/602[#602]: Document all :fn/* query pseudo-functions in a definitive list +** https://github.com/clj-commons/etaoin/issues/602[#602]: Document all `:fn/*` query pseudo-functions in a definitive list +(https://github.com/dgr[@dgr]) == v1.0.40 diff --git a/README.adoc b/README.adoc index 47ce353d..11b7ff70 100644 --- a/README.adoc +++ b/README.adoc @@ -102,6 +102,7 @@ Can be `alpha`, `beta`, `rc1`, etc. * https://github.com/verma[Uday Verma] * https://github.com/mjmeintjes[Matt Meintjes] * https://github.com/tupini07[Andrea Tupini] +* https://github.com/dgr[Dave Roberts] === Current Maintainers From 386918aeb023eb3f090535e4f4840350646e661b Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Wed, 17 Jul 2024 14:28:26 -0500 Subject: [PATCH 4/5] Add :fn/index as alias for :index in map syntax (#603) (#608) Closes #603 --- CHANGELOG.adoc | 2 ++ doc/01-user-guide.adoc | 14 +++++++------- env/test/resources/static/test.html | 10 ++++++++++ src/etaoin/impl/xpath.clj | 8 ++++---- test/etaoin/api_test.clj | 7 +++++++ 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b4ebd85f..5a98b6af 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,8 @@ A release with an intentional breaking changes is marked with: * https://github.com/clj-commons/etaoin/issues/566[#566]: Recognize `:driver-log-level` for Edge * https://github.com/clj-commons/etaoin/issues/604[#604]: Add support for shadow DOM (https://github.com/dgr[@dgr]) +* https://github.com/clj-commons/etaoin/issues/603[#603]: Add :fn/index as alias for :index in map syntax +(https://github.com/dgr[@dgr]) * bump all deps to current versions * tests ** https://github.com/clj-commons/etaoin/issues/572[#572]: stop using chrome `--no-sandbox` option, it has become problematic on Windows (and we did not need it anyway) diff --git a/doc/01-user-guide.adoc b/doc/01-user-guide.adoc index 539f356c..f448f4d5 100644 --- a/doc/01-user-guide.adoc +++ b/doc/01-user-guide.adoc @@ -564,14 +564,14 @@ The rules are: * A `:tag` key represents a tag's name. Defaults to `*`. -* An `:index` key expands into the trailing XPath `[x]` clause. -Useful when you need to select a third row from a table, for example. * Any non-special key represents an attribute and its value. * `:fn/` is a prefix followed by a supported query function. There are several query functions of the form `:fn/*`. Each query function takes a parameter which is the value associated with the query function keyword in the map. +* `:fn/index`: Takes an positive integer parameter. + This expands into a trailing XPath `[x]` clause and is useful when you need to select a specific row in a table, for example. * `:fn/text`: Takes a string parameter. Matches if the element has the exact text specified. * `:fn/has-text`: Takes a string parameter. Matches if the element includes the specified text. @@ -608,7 +608,7 @@ Here are some examples of the map syntax: + [source,clojure] ---- -(= (e/query driver {:tag :div :index 1}) +(= (e/query driver {:tag :div :fn/index 1}) ;; equivalent via xpath: (e/query driver ".//div[1]")) ;; => true @@ -645,7 +645,7 @@ Here are some examples of the map syntax: + [source,clojure] ---- -(e/get-element-text driver {:fn/has-text "blarg" :index 3}) +(e/get-element-text driver {:fn/has-text "blarg" :fn/index 3}) ;; => "blarg in a p" ;; equivalent in xpath: @@ -765,11 +765,11 @@ Maybe you want to click on the second link within: ---- -You can use the `:index` like so: +You can use the `:fn/index` like so: [source,clojure] ---- -(e/click driver [{:tag :li :class :search-result :index 2} {:tag :a}]) +(e/click driver [{:tag :li :class :search-result :fn/index 2} {:tag :a}]) ;; check click tracker from our sample page: (e/get-element-text driver :clicked) ;; => "b" @@ -1252,7 +1252,7 @@ The most important one, `scroll-query` jumps the the first element found with th ---- (e/go driver sample-page) ;; scroll to the 5th h2 heading -(e/scroll-query driver {:tag :h2} {:index 5}) +(e/scroll-query driver {:tag :h2} {:fn/index 5}) ;; and back up to first h1 (e/scroll-query driver {:tag :h1}) diff --git a/env/test/resources/static/test.html b/env/test/resources/static/test.html index 9071cef8..837d1a8e 100644 --- a/env/test/resources/static/test.html +++ b/env/test/resources/static/test.html @@ -208,6 +208,16 @@

Find multiple elements nested

+

Find elements with index

+ +
    +
  1. One
  2. +
  3. Two
  4. +
  5. Three
  6. +
  7. Four
  8. +
  9. Five
  10. +
+

Operate on multiple elements

diff --git a/src/etaoin/impl/xpath.clj b/src/etaoin/impl/xpath.clj index 9a7ea987..72b1a71b 100644 --- a/src/etaoin/impl/xpath.clj +++ b/src/etaoin/impl/xpath.clj @@ -81,7 +81,7 @@ [[_ bool]] (node-boolean "@enabled" bool)) -(defmethod clause :index +(defmethod clause :fn/index [[_ idx]] (if idx (node-index idx) @@ -99,7 +99,7 @@ [q] (let [[tag q] (pop-map q :tag) tag (or tag :*) - idx-key :index - [index q] (pop-map q idx-key) - nodes (concat (into [] q) {idx-key index})] + [fn-index q] (pop-map q :fn/index) + [index q] (pop-map q :index) + nodes (concat (into [] q) {:fn/index (or fn-index index)})] (node-join (concat [".//" (to-str tag)] (map clause nodes))))) diff --git a/test/etaoin/api_test.clj b/test/etaoin/api_test.clj index 802cd937..1847f93a 100644 --- a/test/etaoin/api_test.clj +++ b/test/etaoin/api_test.clj @@ -698,6 +698,13 @@ (is (= (count elements) 2)) (is (= texts ["1" "2"]))))) +(deftest test-fn-index + (testing ":fn/index" + (let [items (for [index (range 1 6)] + (->> (e/query *driver* {:class :indexed :fn/index index}) + (e/get-element-text-el *driver*)))] + (is (= items ["One" "Two" "Three" "Four" "Five"]))))) + (deftest test-multiple-elements (testing "tag names" (let [q {:xpath ".//div[@id='operate-multiple-elements']//*"} From c199428297be806a9c89be0e628806d41bea58ac Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Wed, 24 Jul 2024 16:19:10 -0500 Subject: [PATCH 5/5] Ignore emacs backup files (#609) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c0522c64..2e3704b2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ pom.xml.asc .hgignore .hg/ .DS_Store +*~ gh-pages node_modules @@ -34,3 +35,4 @@ build.xml /fiddle/ /.cljdoc-preview /.vscode +