PageRenderTime 28ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/webdriver/util.clj

http://github.com/semperos/clj-webdriver
Clojure | 317 lines | 276 code | 29 blank | 12 comment | 61 complexity | 31c30224dfe029bf0b3ec1db90fbbd11 MD5 | raw file
  1. (ns webdriver.util
  2. (:require [clojure.string :as str]
  3. [clojure.java.io :as io]
  4. [clojure.walk :as walk])
  5. (:import [java.io PushbackReader Writer]
  6. [org.openqa.selenium Capabilities HasCapabilities
  7. WebDriver WebElement NoSuchElementException]))
  8. (declare build-query)
  9. (defn build-css-attrs
  10. "Given a map of attribute-value pairs, build the latter portion of a CSS query that follows the tag."
  11. [attr-val]
  12. (clojure.string/join (for [[attr value] attr-val]
  13. (cond
  14. (= :text attr) (throw (IllegalArgumentException. "CSS queries do not support checking against the text of an element."))
  15. (= :index attr) (str ":nth-child(" (inc value) ")") ;; CSS is 1-based
  16. :else (str "[" (name attr) "='" value "']")))))
  17. (defn build-xpath-attrs
  18. "Given a map of attribute-value pairs, build the bracketed portion of an XPath query that follows the tag"
  19. [attr-val]
  20. (clojure.string/join (for [[attr value] attr-val]
  21. (cond
  22. (= :text attr) (str "[text()=\"" value "\"]")
  23. (= :index attr) (str "[" (inc value) "]") ; in clj-webdriver,
  24. :else (str "[@" ; all indices 0-based
  25. (name attr)
  26. "="
  27. "'" (name value) "']")))))
  28. (defn build-css-with-hierarchy
  29. "Given a vector of queries in hierarchical order, create a CSS query.
  30. For example: `[{:tag :div, :id \"content\"}, {:tag :a, :class \"external\"}]` would
  31. produce the CSS query \"div[id='content'] a[class='external']\""
  32. [v-of-attr-vals]
  33. (str/join
  34. " "
  35. (for [attr-val v-of-attr-vals]
  36. (cond
  37. (or (contains? attr-val :css)
  38. (contains? attr-val :xpath)) (throw (IllegalArgumentException. "Hierarhical queries do not support the use of :css or :xpath entries."))
  39. (some #{(:tag attr-val)} [:radio
  40. :checkbox
  41. :textfield
  42. :password
  43. :filefield]) (throw (IllegalArgumentException. "Hierarchical queries do not support the use of \"meta\" tags such as :button*, :radio, :checkbox, :textfield, :password or :filefield. "))
  44. :else (:css (build-query attr-val :css))))))
  45. (defn build-xpath-with-hierarchy
  46. "Given a vector of queries in hierarchical order, create XPath.
  47. For example: `[{:tag :div, :id \"content\"}, {:tag :a, :class \"external\"}]` would
  48. produce the XPath \"//div[@id='content']//a[@class='external']"
  49. [v-of-attr-vals]
  50. (clojure.string/join (for [attr-val v-of-attr-vals]
  51. (cond
  52. (or (contains? attr-val :css)
  53. (contains? attr-val :xpath)) (throw (IllegalArgumentException. "Hierarhical queries do not support the use of :css or :xpath entries."))
  54. (some #{(:tag attr-val)} [:radio
  55. :checkbox
  56. :textfield
  57. :password
  58. :filefield]) (throw (IllegalArgumentException. "Hierarchical queries do not support the use of \"meta\" tags such as :button*, :radio, :checkbox, :textfield, :password or :filefield. "))
  59. :else (:xpath (build-query attr-val))))))
  60. (declare remove-regex-entries)
  61. (defn build-query
  62. "Given a map of attribute-value pairs, generate XPath or CSS based on `output`. Optionally include a `prefix` to specify whether this should be a `:global` \"top-level\" query or a `:local`, child query."
  63. ([attr-val] (build-query attr-val :xpath :global))
  64. ([attr-val output] (build-query attr-val output :global))
  65. ([attr-val output prefix]
  66. (if-not (map? attr-val) ;; dispatch here for hierarhical queries
  67. (if (= output :xpath)
  68. (build-xpath-with-hierarchy attr-val)
  69. (build-css-with-hierarchy attr-val))
  70. (let [attr-val (remove-regex-entries attr-val)]
  71. (cond
  72. (contains? attr-val :xpath) {:xpath (:xpath attr-val)}
  73. (contains? attr-val :css) {:css (:css attr-val)}
  74. (= (:tag attr-val) :radio) (build-query (assoc attr-val :tag :input :type "radio"))
  75. (= (:tag attr-val) :checkbox) (build-query (assoc attr-val :tag :input :type "checkbox"))
  76. (= (:tag attr-val) :textfield) (build-query (assoc attr-val :tag :input :type "text"))
  77. (= (:tag attr-val) :password) (build-query (assoc attr-val :tag :input :type "password"))
  78. (= (:tag attr-val) :filefield) (build-query (assoc attr-val :tag :input :type "filefield"))
  79. :else (let [tag (if (nil? (:tag attr-val))
  80. :*
  81. (:tag attr-val))
  82. attr-val (dissoc attr-val :tag)
  83. prefix-legend {:local "."
  84. :global ""}]
  85. (if (= output :xpath)
  86. (let [query-str (str (prefix-legend prefix) "//"
  87. (name tag)
  88. (when (seq attr-val)
  89. (build-xpath-attrs attr-val)))]
  90. {:xpath query-str})
  91. ;; else, CSS
  92. (let [query-str (str (name tag)
  93. (when (seq attr-val)
  94. (build-css-attrs attr-val)))]
  95. {:css query-str}))))))))
  96. (defn contains-regex?
  97. "Checks if the values of a map contain a regex"
  98. [m]
  99. (boolean (some (fn [entry]
  100. (let [[k v] entry]
  101. (= java.util.regex.Pattern (class v)))) m)))
  102. (defn all-regex?
  103. "Checks if all values of a map are regexes"
  104. [m]
  105. (and (seq m)
  106. (not-any? (fn [entry]
  107. (let [[k v] entry]
  108. (not= java.util.regex.Pattern (class v)))) m)))
  109. (defn filter-regex-entries
  110. "Given a map `m`, return a map containing only entries whose values are regular expressions."
  111. [m]
  112. (into {} (filter
  113. #(let [[k v] %] (= java.util.regex.Pattern (class v)))
  114. m)))
  115. (defn remove-regex-entries
  116. "Given a map `m`, return a map containing only entries whose values are NOT regular expressions."
  117. [m]
  118. (into {} (remove
  119. #(let [[k v] %] (= java.util.regex.Pattern (class v)))
  120. m)))
  121. (defn first-n-chars
  122. "Get first n characters of `s`, then add ellipsis"
  123. ([s] (first-n-chars s 60))
  124. ([s n]
  125. (if (zero? n)
  126. "..."
  127. (str (re-find (re-pattern (str "(?s).{1," n "}")) s)
  128. (when (> (count s) n)
  129. "...")))))
  130. (defn elim-breaks
  131. "Eliminate line breaks; used for REPL printing"
  132. [s]
  133. (str/replace s #"(\r|\n|\r\n)" " "))
  134. (defmacro when-attr
  135. "Special `when` macro for checking if an attribute isn't available or is an empty string"
  136. [obj & body]
  137. `(when (not (or (nil? ~obj) (empty? ~obj)))
  138. ~@body))
  139. ;; from Clojure's core.clj
  140. (defmacro assert-args
  141. [& pairs]
  142. `(do (when-not ~(first pairs)
  143. (throw (IllegalArgumentException.
  144. (str (first ~'&form) " requires " ~(second pairs) " in " ~'*ns* ":" (:line (meta ~'&form))))))
  145. ~(let [more (nnext pairs)]
  146. (when more
  147. (list* `assert-args more)))))
  148. ;; from Clojure's core.clj
  149. (defn pr-on
  150. {:private true
  151. :static true}
  152. [x w]
  153. (if *print-dup*
  154. (print-dup x w)
  155. (print-method x w))
  156. nil)
  157. ;; from Clojure core_print.clj
  158. (defn- print-sequential [^String begin, print-one, ^String sep, ^String end, sequence, ^Writer w]
  159. (binding [*print-level* (and (not *print-dup*) *print-level* (dec *print-level*))]
  160. (if (and *print-level* (neg? *print-level*))
  161. (.write w "#")
  162. (do
  163. (.write w begin)
  164. (when-let [xs (seq sequence)]
  165. (if (and (not *print-dup*) *print-length*)
  166. (loop [[x & xs] xs
  167. print-length *print-length*]
  168. (if (zero? print-length)
  169. (.write w "...")
  170. (do
  171. (print-one x w)
  172. (when xs
  173. (.write w sep)
  174. (recur xs (dec print-length))))))
  175. (loop [[x & xs] xs]
  176. (print-one x w)
  177. (when xs
  178. (.write w sep)
  179. (recur xs)))))
  180. (.write w end)))))
  181. ;; from Clojure core_print.clj
  182. (defn- print-map [m print-one w]
  183. (print-sequential
  184. "{"
  185. (fn [e ^Writer w]
  186. (do (print-one (key e) w) (.append w \space) (print-one (val e) w)))
  187. ", "
  188. "}"
  189. (seq m) w))
  190. ;; from Clojure core_print.clj
  191. (defn- print-meta [o, ^Writer w]
  192. (when-let [m (meta o)]
  193. (when (and (pos? (count m))
  194. (or *print-dup*
  195. (and *print-meta* *print-readably*)))
  196. (.write w "^")
  197. (if (and (= (count m) 1) (:tag m))
  198. (pr-on (:tag m) w)
  199. (pr-on m w))
  200. (.write w " "))))
  201. (defmethod print-method WebDriver
  202. [^WebDriver q w]
  203. (let [^Capabilities caps (.getCapabilities ^HasCapabilities q)]
  204. (print-simple
  205. (str "#<" "Title: " (.getTitle q) ", "
  206. "URL: " (first-n-chars (.getCurrentUrl q)) ", "
  207. "Browser: " (.getBrowserName caps) ", "
  208. "Version: " (.getVersion caps) ", "
  209. "JS Enabled: " (.isJavascriptEnabled caps) ", "
  210. "Native Events Enabled: " (boolean (re-find #"nativeEvents=true" (str caps))) ", "
  211. "Object: " q ">") w)))
  212. (defmethod print-method WebElement
  213. [^WebElement q w]
  214. (let [tag-name (.getTagName q)
  215. text (.getText q)
  216. id (.getAttribute q "id")
  217. class-name (.getAttribute q "class")
  218. name-name (.getAttribute q "name")
  219. value (.getAttribute q "value")
  220. href (.getAttribute q "href")
  221. src (.getAttribute q "src")
  222. obj q]
  223. (print-simple
  224. (str "#<"
  225. (when-attr tag-name
  226. (str "Tag: " "<" tag-name ">" ", "))
  227. (when-attr text
  228. (str "Text: " (-> text elim-breaks first-n-chars) ", "))
  229. (when-attr id
  230. (str "Id: " id ", "))
  231. (when-attr class-name
  232. (str "Class: " class-name ", "))
  233. (when-attr name-name
  234. (str "Name: " name-name ", "))
  235. (when-attr value
  236. (str "Value: " (-> value elim-breaks first-n-chars) ", "))
  237. (when-attr href
  238. (str "Href: " href ", "))
  239. (when-attr src
  240. (str "Source: " src ", "))
  241. "Object: " q ">") w)))
  242. (defn dashes-to-camel-case
  243. "A simple conversion of `-x` to `X` for the given string."
  244. [s]
  245. (reduce (fn [^String state ^String item]
  246. (.replaceAll state item
  247. (str/upper-case (str (second item)))))
  248. s
  249. (distinct (re-seq #"-[^-]" s))))
  250. (defn camel-case-to-dashes
  251. "Convert Pascal-case to dashes. This takes into account edge cases like `fooJSBar` and `fooBarB`, where dashed versions will be `foo-jS-bar` and `foo-barB` respectively."
  252. [s]
  253. (reduce (fn [^String state ^String item]
  254. ;; e.g.: state = trustAllSSLCertificates
  255. ;; item can be either "tA" or "lSSLC"
  256. (if (= (count item) 2)
  257. (.replaceFirst state item
  258. (str (first item)
  259. "-"
  260. (str/lower-case (second item))))
  261. (.replaceFirst state item
  262. (str (first item)
  263. "-"
  264. (str/lower-case (second item))
  265. (subs item 2 (dec (count item)))
  266. "-"
  267. (str/lower-case (last item))))))
  268. s
  269. (re-seq #"[a-z]?[A-Z]+(?:(?!$))" s)
  270. ;; (re-seq #"[a-z]?[A-Z]+" s)
  271. ;; (re-seq #"[a-z][A-Z](?![A-Z]|$)" s)
  272. ))
  273. (defn clojure-keys
  274. "Recursively transforms all map keys from strings to keywords, converting Pascal-case to dash-separated."
  275. [m]
  276. (let [f (fn [[k v]] (if (string? k) [(keyword (camel-case-to-dashes k)) v] [k v]))]
  277. ;; only apply to maps
  278. (walk/postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
  279. (defn java-keys
  280. "Recursively transforms all map keys from keywords into strings, converting dash-separated to Pascal-case."
  281. [m]
  282. (let [f (fn [[k v]] (if (keyword? k) [(dashes-to-camel-case (name k)) v] [k v]))]
  283. ;; only apply to maps
  284. (walk/postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
  285. (defn throw-nse
  286. ([] (throw-nse ""))
  287. ([msg]
  288. (throw (NoSuchElementException. (str msg "\n" "When an element cannot be found in clj-webdriver, nil is returned. You've just tried to perform an action on an element that returned as nil for the search query you used. Please verify the query used to locate this element; it is not on the current page.")))))