|
24 | 24 |
|
25 | 25 | **Querying/Selecting DOM Elements**
|
26 | 26 | - [[query]] [[query-all]] [[query-tree]]
|
27 |
| - - [[query-shadow-root]] [[query-shadow-root-el]] [[query-all-shadow-root]] [[query-all-shadow-root-el]] |
| 27 | + - [[query-from-shadow-root]] [[query-from-shadow-root-el]] [[query-all-from-shadow-root]] [[query-all-from-shadow-root-el]] |
28 | 28 | - [[has-shadow-root?]] [[has-shadow-root-el?]]
|
29 | 29 | - [[exists?]] [[absent?]]
|
30 | 30 | - [[displayed?]] [[displayed-el?]] [[enabled?]] [[enabled-el?]] [[disabled?]] [[invisible?]] [[visible?]]
|
|
196 | 196 | ;; assume same idea as chrome (TBD)
|
197 | 197 | :capabilities {:ms:edgeOptions {:w3c true}}}})
|
198 | 198 |
|
| 199 | +;; Web Driver identifiers used as object type tags |
| 200 | +;; See: https://www.w3.org/TR/webdriver2/#elements |
| 201 | +(def ^:private shadow-root-identifier :shadow-6066-11e4-a52e-4f735466cecf) |
| 202 | +;; See: https://www.w3.org/TR/webdriver2/#shadow-root |
| 203 | +(def ^:private web-element-identifier :element-6066-11e4-a52e-4f735466cecf) |
| 204 | + |
199 | 205 | ;;
|
200 | 206 | ;; utils
|
201 | 207 | ;;
|
|
211 | 217 | (when (get-method feature (:type driver))
|
212 | 218 | true))
|
213 | 219 |
|
| 220 | +(defn- unwrap-webdriver-object |
| 221 | + "Unwraps an object tagged with `identifier` from a Web Driver JSON object, |
| 222 | + `web-driver-obj`. If `web-driver-obj` is not tagged with |
| 223 | + `identifier` (i.e., the specified identifer is not present), throw |
| 224 | + an exception." |
| 225 | + [web-driver-obj identifier] |
| 226 | + (let [obj (get web-driver-obj identifier ::not-found)] |
| 227 | + (if (= obj ::not-found) |
| 228 | + (throw (ex-info (str "Could not find object tagged with " identifier |
| 229 | + " in " (str web-driver-obj)) |
| 230 | + {:web-driver-obj web-driver-obj |
| 231 | + :identifier identifier})) |
| 232 | + obj))) |
| 233 | + |
214 | 234 | ;;
|
215 | 235 | ;; api
|
216 | 236 | ;;
|
|
545 | 565 | :value
|
546 | 566 | (mapv (comp second first))))
|
547 | 567 |
|
| 568 | +(defn- follow-path-from-element* |
| 569 | + "Starting at `el`, search for the first query in `path`, then from the |
| 570 | + resulting element, search for the next, and so on. If `path` is |
| 571 | + empty, returns `el`. A member of the `path` is limited to: |
| 572 | +
|
| 573 | + * a keyword (converted to an element ID) |
| 574 | + * a string (converted to an XPath expression) |
| 575 | + * a map using {:xpath ...} (converted to XPath) |
| 576 | + * a map using {:css ...} (converted to CSS) |
| 577 | + * a map following the Etaoin map syntax (converted to the driver default, typically XPath) |
| 578 | +
|
| 579 | + Things that are not supported as `path` elements: |
| 580 | + * `query`'s `:active` keyword |
| 581 | + * other sequences" |
| 582 | + [driver el path] |
| 583 | + (reduce (fn [el q] |
| 584 | + (let [[loc term] (query/expand driver q)] |
| 585 | + (find-element-from* driver el loc term))) |
| 586 | + el |
| 587 | + path)) |
| 588 | + |
548 | 589 | ;;
|
549 | 590 | ;; Querying elements (high-level API)
|
550 | 591 | ;;
|
|
1348 | 1389 | [driver q]
|
1349 | 1390 | (get-element-value-el driver (query driver q)))
|
1350 | 1391 |
|
| 1392 | +(defmulti ^:private get-element-shadow-root* |
| 1393 | + "Returns the shadow root element associated with the specified shadow |
| 1394 | + root host element, `el`, or `nil` if the specified element is not a |
| 1395 | + shadow root host." |
| 1396 | + dispatch-driver) |
| 1397 | + |
| 1398 | +(defmethod get-element-shadow-root* |
| 1399 | + :default |
| 1400 | + [driver el] |
| 1401 | + ;; Note that we're using get-element-property-el here, rather than |
| 1402 | + ;; executing a Web Driver Get Element Shadow Root API call. This is |
| 1403 | + ;; because the error handling for this API call is inconsistent |
| 1404 | + ;; across drivers whereas getting the property is consistent and |
| 1405 | + ;; probably not as brittle as drivers are updated. |
| 1406 | + ;; |
| 1407 | + ;; Specifically, if the element does not have a shadow root, then |
| 1408 | + ;; when executing a Get Element Shadow Root API call... (as of August 2024) |
| 1409 | + ;; * Firefox: throws 404 |
| 1410 | + ;; * Safari: returns {:value nil} |
| 1411 | + ;; * Chrome: throws HTTP status 200, Web Driver status 65 |
| 1412 | + ;; * Edge: throws HTTP 200, Web Driver status 65 |
| 1413 | + ;; |
| 1414 | + ;; My guess is that Chrome and Edge are probably behaving correctly |
| 1415 | + ;; and Firefox and Safari are not. |
| 1416 | + ;; |
| 1417 | + ;; Perhaps update this at a later date when drivers better conform |
| 1418 | + ;; to the standard. |
| 1419 | + (when-let [root (get-element-property-el driver el "shadowRoot")] |
| 1420 | + (unwrap-webdriver-object root shadow-root-identifier))) |
| 1421 | + |
| 1422 | +(defmethod get-element-shadow-root* |
| 1423 | + :safari |
| 1424 | + [driver el] |
| 1425 | + ;; Safari gives us the shadow root in a non-standard wrapper |
| 1426 | + (when-let [root (get-element-property-el driver el "shadowRoot")] |
| 1427 | + (-> root first second))) |
1351 | 1428 |
|
1352 |
| -;; TODO: Dev Doc: Why not: /session/{session id}/element/{element id}/shadow from w3c spec? |
1353 | 1429 | (defn get-element-shadow-root-el
|
1354 | 1430 | "Returns the shadow root for the specified element or `nil` if the
|
1355 | 1431 | element does not have a shadow root."
|
1356 | 1432 | [driver el]
|
1357 |
| - (-> (get-element-property-el driver el "shadowRoot") |
1358 |
| - first |
1359 |
| - second)) |
| 1433 | + (get-element-shadow-root* driver el)) |
1360 | 1434 |
|
1361 | 1435 | (defn get-element-shadow-root
|
1362 | 1436 | "Returns the shadow root for the first element matching the query, or
|
|
1378 | 1452 | :path [:session (:session driver) :shadow shadow-root-el :element]
|
1379 | 1453 | :data {:using locator :value term}})
|
1380 | 1454 | :value
|
1381 |
| - first |
1382 |
| - second)) |
| 1455 | + (unwrap-webdriver-object web-element-identifier))) |
1383 | 1456 |
|
1384 | 1457 | (defn- find-elements-from-shadow-root*
|
1385 | 1458 | [driver shadow-root-el locator term]
|
|
1389 | 1462 | :path [:session (:session driver) :shadow shadow-root-el :elements]
|
1390 | 1463 | :data {:using locator :value term}})
|
1391 | 1464 | :value
|
1392 |
| - (mapv (comp second first)))) |
| 1465 | + (mapv #(unwrap-webdriver-object % web-element-identifier)))) |
1393 | 1466 |
|
1394 |
| -(defn query-shadow-root-el |
| 1467 | +(defn query-from-shadow-root-el |
1395 | 1468 | "Queries the shadow DOM rooted at `shadow-root-el`, looking for the
|
1396 | 1469 | first element specified by `shadow-q`.
|
1397 | 1470 |
|
1398 | 1471 | The `shadow-q` parameter is similar to the `q` parameter of
|
1399 | 1472 | the [[query]] function, but some drivers may limit it to specific
|
1400 | 1473 | formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information.
|
1401 | 1474 |
|
| 1475 | + Note that `shadow-q` does not support `query`'s `:active` keyword. |
| 1476 | +
|
1402 | 1477 | https://www.w3.org/TR/webdriver2/#dfn-find-element-from-shadow-root"
|
1403 | 1478 | [driver shadow-root-el shadow-q]
|
1404 |
| - (let [[loc term] (query/expand driver shadow-q)] |
1405 |
| - (find-element-from-shadow-root* driver shadow-root-el loc term))) |
| 1479 | + (if (sequential? shadow-q) |
| 1480 | + (let [q1-el (query-from-shadow-root-el driver shadow-root-el (first shadow-q))] |
| 1481 | + (follow-path-from-element* driver q1-el (next shadow-q))) |
| 1482 | + (let [[loc term] (query/expand driver shadow-q)] |
| 1483 | + (find-element-from-shadow-root* driver shadow-root-el loc term)))) |
1406 | 1484 |
|
1407 |
| -(defn query-all-shadow-root-el |
| 1485 | +(defn query-all-from-shadow-root-el |
1408 | 1486 | "Queries the shadow DOM rooted at `shadow-root-el`, looking for all
|
1409 | 1487 | elements specified by `shadow-q`.
|
1410 | 1488 |
|
1411 | 1489 | The `shadow-q` parameter is similar to the `q` parameter of
|
1412 | 1490 | the [[query]] function, but some drivers may limit it to specific
|
1413 | 1491 | formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information.
|
1414 | 1492 |
|
| 1493 | + Note that `shadow-q` does not support `query`'s `:active` keyword. |
| 1494 | +
|
1415 | 1495 | https://www.w3.org/TR/webdriver2/#dfn-find-elements-from-shadow-root"
|
1416 | 1496 | [driver shadow-root-el shadow-q]
|
1417 |
| - (let [[loc term] (query/expand driver shadow-q)] |
1418 |
| - (find-elements-from-shadow-root* driver shadow-root-el loc term))) |
1419 |
| - |
1420 |
| -(defn query-shadow-root |
| 1497 | + (if (sequential? shadow-q) |
| 1498 | + (let [last-q (last shadow-q) |
| 1499 | + but-last-q (butlast shadow-q)] |
| 1500 | + (if-let [first-q (first but-last-q)] |
| 1501 | + (let [first-el (query-from-shadow-root-el driver shadow-root-el first-q) |
| 1502 | + but-last-el (follow-path-from-element* driver first-el (next but-last-q)) |
| 1503 | + [loc term] (query/expand driver last-q)] |
| 1504 | + (find-elements-from* driver but-last-el loc term)) |
| 1505 | + (query-all-from-shadow-root-el driver shadow-root-el last-q))) |
| 1506 | + (let [[loc term] (query/expand driver shadow-q)] |
| 1507 | + (find-elements-from-shadow-root* driver shadow-root-el loc term)))) |
| 1508 | + |
| 1509 | +(defn query-from-shadow-root |
1421 | 1510 | "First, conducts a standard search (as if by [[query]]) for an element
|
1422 | 1511 | with a shadow root. Then, from that shadow root element, conducts a
|
1423 | 1512 | search of the shadow DOM for the first element matching `shadow-q`.
|
|
1426 | 1515 |
|
1427 | 1516 | The `shadow-q` parameter is similar to the `q` parameter of
|
1428 | 1517 | the [[query]] function, but some drivers may limit it to specific
|
1429 |
| - formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information." |
| 1518 | + formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information. |
| 1519 | + Note that `shadow-q` does not support `query`'s `:active` keyword." |
1430 | 1520 | [driver q shadow-q]
|
1431 |
| - (query-shadow-root-el driver (get-element-shadow-root driver q) shadow-q)) |
| 1521 | + (query-from-shadow-root-el driver (get-element-shadow-root driver q) shadow-q)) |
1432 | 1522 |
|
1433 |
| -(defn query-all-shadow-root |
| 1523 | +(defn query-all-from-shadow-root |
1434 | 1524 | "First, conducts a standard search (as if by [[query]]) for an element
|
1435 | 1525 | with a shadow root. Then, from that shadow root element, conducts a
|
1436 | 1526 | search of the shadow DOM for all elements matching `shadow-q`.
|
|
1439 | 1529 |
|
1440 | 1530 | The `shadow-q` parameter is similar to the `q` parameter of
|
1441 | 1531 | the [[query]] function, but some drivers may limit it to specific
|
1442 |
| - formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information." |
| 1532 | + formats (e.g., CSS). See [this note](/doc/01-user-guide.adoc#shadow-root-browser-limitations) for more information. |
| 1533 | + Note that `shadow-q` does not support `query`'s `:active` keyword." |
1443 | 1534 | [driver q shadow-q]
|
1444 |
| - (query-all-shadow-root-el driver (get-element-shadow-root driver q) shadow-q)) |
| 1535 | + (query-all-from-shadow-root-el driver (get-element-shadow-root driver q) shadow-q)) |
1445 | 1536 |
|
1446 | 1537 | ;;
|
1447 | 1538 | ;; cookies
|
|
1543 | 1634 | (defn el->ref
|
1544 | 1635 | "Return map representing an element reference for WebDriver.
|
1545 | 1636 |
|
1546 |
| - The magic `:element-` constant in source is taken from the [WebDriver Spec](https://www.w3.org/TR/webdriver/#elements). |
| 1637 | + The magic `:element-` constant in source is taken from the [WebDriver Spec](https://www.w3.org/TR/webdriver2/#elements). |
1547 | 1638 |
|
1548 | 1639 | Passing the element reference map to `js-execute` automatically expands it
|
1549 | 1640 | into a DOM node. For example:
|
|
1556 | 1647 | (js-execute driver \"arguments[0].scrollIntoView()\", (el->ref el))
|
1557 | 1648 | ```"
|
1558 | 1649 | [el]
|
1559 |
| - {:ELEMENT el |
1560 |
| - :element-6066-11e4-a52e-4f735466cecf el}) |
| 1650 | + {:ELEMENT el |
| 1651 | + web-element-identifier el}) |
1561 | 1652 |
|
1562 | 1653 | (defn js-execute
|
1563 | 1654 | "Return result of `driver` executing Javascript `script` with `args` synchronously in the browser.
|
|
0 commit comments