Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bizarre, src was somehow removed from the repo
- Loading branch information
1 parent
ce2f37e
commit de84f53
Showing
4 changed files
with
517 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
(ns sparkledriver.browser | ||
(:require [sparkledriver.element :as elem])) | ||
|
||
(def browser-options | ||
"The possible options for building a browser instance in {:option [default setter-fn]} format." | ||
{;; how long to wait for resources loaded by ajax, in milliseconds (default is quite long) | ||
:ajax-load-timeout [30000 #(.ajaxResourceTimeout %1 %2)] | ||
;; how long to wait for JS to run after page load, in milliseconds | ||
:ajax-wait [200 #(.ajaxWait %1 %2)] | ||
;; use a local browser cache | ||
:cache [true #(.cache %1 %2)] | ||
;; if false, open a window so you can watch it work | ||
:headless [true #(.headless %1 %2)] | ||
;; increased logging | ||
:log-trace [false #(.logTrace %1 %2)] | ||
:log-wire [false #(.logWire %1 %2)] | ||
;; we pretend to be Chrome | ||
:request-headers [com.machinepublishers.jbrowserdriver.RequestHeaders/CHROME #(.requestHeaders %1 %2)] | ||
;; store copies of media and attachments in a temporary folder | ||
:save-attachments [true #(.saveAttachments %1 %2)] | ||
:save-media? [true #(.saveMedia %1 %2)] | ||
;; set browser screen dimentions - 1366x768 by default (we're a laptop) | ||
:screen-size [[1366 768] (fn [builder [w h]] | ||
(.screen builder (org.openqa.selenium.Dimension. w h)))] | ||
;; be accepting of weird SSL certs | ||
:ssl-policy ["compatible" #(.ssl %1 %2)] | ||
;; We're in New York, no matter where we are | ||
:timezone [com.machinepublishers.jbrowserdriver.Timezone/AMERICA_NEWYORK #(.timezone %1 %2)] | ||
;; no, really, we're Chrome | ||
:user-agent [com.machinepublishers.jbrowserdriver.UserAgent/CHROME #(.userAgent %1 %2)] | ||
;; SSL certificate verification, off by default because the internet is broken | ||
:verify-hostname? [false #(.hostnameVerification %1 %2)]}) | ||
|
||
(defn make-browser | ||
"Creates a new headless browser instance parameterized by `options`. Examples: | ||
```clojure | ||
(make-browser :log-wire true) ; log network traffic | ||
(make-browser :ajax-wait 1000) ; wait 1000ms for JS to run after page load | ||
(make-browser :headless false) ; pop up a browser window to watch it work | ||
``` | ||
" | ||
[& options] | ||
(assert (or (= nil options) (even? (count options))) | ||
"The options to make-browser must be an even number of key-value pairs.") | ||
(let [merged-opts (->> (partition 2 options) | ||
(reduce (fn [a [k v]] | ||
(if-let [[default setter-fn] (get browser-options k)] | ||
(assoc a k [v setter-fn]) | ||
(throw (IllegalArgumentException. "Invalid browser option.")))) | ||
browser-options) | ||
vals)] | ||
(com.machinepublishers.jbrowserdriver.JBrowserDriver. | ||
(.build (reduce (fn [builder [v f]] | ||
(f builder v)) | ||
(com.machinepublishers.jbrowserdriver.Settings/builder) | ||
merged-opts))))) | ||
|
||
(defn close-browser! | ||
"Close a browser instance, killing the underlying subprocess and freeing all resources." | ||
[browser] | ||
(.quit browser)) | ||
|
||
(defmacro with-browser | ||
"Evaluate `body` in a try block within which `binding` is bound, finally calling close-browser on binding. This is just a version of with-open that traps the exception that closing a jBrowserDriver instance currently throws." | ||
[binding & body] | ||
;; assert-args is private to clojure.core 😿 | ||
;; (assert-args | ||
;; (vector? binding) "a vector for its binding" | ||
;; (= 1 (count binding)) "a single name is expected" | ||
;; (symbol? (binding 0)) "the name should be a symbol") | ||
`(let ~(subvec binding 0 2) | ||
(try | ||
~@body | ||
(finally (close-browser! ~(binding 0)))))) | ||
|
||
(defn fetch! | ||
"Fetch 'url' using 'browser' and return browser after loading is complete. | ||
```clojure | ||
(fetch! browser \"http://www.clojure.com/\") | ||
;;=> returns browser after navigating to the clojure site | ||
```" | ||
[browser url] | ||
(.get browser (str url)) | ||
browser) | ||
|
||
(defn status-code | ||
"Return the current HTTP status code of `browser`." | ||
[browser] | ||
(.getStatusCode browser)) | ||
|
||
(defn current-url | ||
"Return the currently visited url of `browser`." | ||
[browser] | ||
(.getCurrentUrl browser)) | ||
|
||
(defn page-source | ||
"Return the HTML source of the currently visited page in `browser`." | ||
[browser] | ||
(.getPageSource browser)) | ||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
;; browser windows | ||
|
||
(defn current-window | ||
"Return a handle for the currently active `browser` window." | ||
[browser] | ||
(.getWindowHandle browser)) | ||
|
||
(defn all-windows | ||
"Return a set of handles for all `browser` windows." | ||
[browser] | ||
(.getWindowHandles browser)) | ||
|
||
(defn switch-to-window | ||
"Switch `browser` to `window`, which should be a handle returned by all-windows or current-window." | ||
[browser window] | ||
(.window (.switchTo browser) window)) | ||
|
||
(defn maximize-window | ||
"Maximize `browser`'s currently focused window." | ||
[browser] | ||
(.maximize (.window (.manage browser)))) | ||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
;; alert handling | ||
|
||
(defn switch-to-alert | ||
"Switch `browser`'s focus to the current alert popup. Returns the alert's handle." | ||
[browser] | ||
(.alert (.switchTo browser))) | ||
|
||
(defn accept-alert! | ||
"Accept an alert." | ||
[alert] | ||
(.accept alert)) | ||
|
||
(defn dismiss-alert! | ||
"Dismisses an alert." | ||
[alert] | ||
(.dismiss alert)) | ||
|
||
;; XXX currently locks up and never comes back! Upstream problem in | ||
;; jBrowserDriver? | ||
;; | ||
;; (defn alert-text | ||
;; "Returns the text of an alert." | ||
;; [alert] | ||
;; (.getText alert)) | ||
|
||
;; TODO add typing into alerts... | ||
;;void sendKeys(String keysToSend) | ||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
;; exec arbitrary javascript | ||
|
||
(defn execute-script | ||
"Executes JavaScript `script` in the context of the currently selected frame or window of `browser` with `arguments`." | ||
[browser script & arguments] | ||
(.executeScript browser script (to-array arguments))) | ||
|
||
(defn execute-script-async | ||
"Asynchronously execute JavaScript `script` in the context of the currently selected frame or window of `browser` with `arguments`." | ||
[browser script & arguments] | ||
(.executeAsyncScript browser script (to-array arguments))) | ||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
;; interrogate logs | ||
|
||
(defn available-log-types | ||
"Return a set of strings representing the types of logs available for `browser`. N.B. `browser` must have been initialized with some kind of logging enabled, for isntance :log-trace or :log-wire." | ||
[browser] | ||
(.getAvailableLogTypes (.logs (.manage browser)))) | ||
|
||
(defn logs | ||
"Return `browser`'s logs of whatever `kind`, or all if not specified. See http://machinepublishers.github.io/jBrowserDriver/org/openqa/selenium/logging/LogEntry.html for details of the returned items. Example: | ||
``` | ||
(map #(.getMessage %) (logs browser)) | ||
;;=> events that have occurred since the last time you did this | ||
```" | ||
[browser & kind] | ||
(iterator-seq (.iterator (.get (.logs (.manage browser)) (or kind "all"))))) | ||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
;; helpers | ||
|
||
(defn page-wait | ||
"Blocks until the browser's current page quiesces. N.B. This is the default for many operations, so this function is likely only useful when things are misbehaving." | ||
[browser] | ||
(.pageWait browser)) | ||
|
||
(defn attachments-dir | ||
"Return the path to the directory where the browser is storing attachment files." | ||
[browser] | ||
(.attachmentsDir browser)) | ||
|
||
(defn media-dir | ||
"Return the path to the directory where the browser is storing media files." | ||
[browser] | ||
(.mediaDir browser)) | ||
|
||
(defn cache-dir | ||
"Return the path to the directory where the browser is storing cached files." | ||
[browser] | ||
(.cacheDir browser)) | ||
|
||
(defn page-text | ||
"Return the complete visible textual content of the current page in the focused window of `browser`. Text from hidden elements is not included." | ||
[browser] | ||
(elem/text (elem/find-by-tag browser "html"))) | ||
|
||
(defn title | ||
"Return the title of the current page in the focused window of `browser`." | ||
[browser] | ||
(elem/inner-html (elem/find-by-css browser "head title"))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
(ns sparkledriver.cookies | ||
(:require [sparkledriver.browser :as brwsr])) | ||
|
||
(defn browser-cookies->map | ||
"Convert `browser`'s current cookies into the map format used by clj-http. This returns cookies from all domains, but cookie names that are set on multiple domains will only be returned once." | ||
[browser] | ||
(reduce | ||
#(assoc %1 (keyword (.getName %2)) | ||
{:domain (.getDomain %2) | ||
:path (.getPath %2) | ||
:value (.getValue %2)}) | ||
{} | ||
(.getCookies (.manage browser)))) | ||
|
||
(defn delete-all-cookies! | ||
"Clear all cookies from all domains." | ||
[browser] | ||
(.deleteAllCookies (.manage browser)) | ||
browser) | ||
|
||
(defn delete-cookie! | ||
"Delete the named cookie from the given domain. When omitted `domain` defaults to the `browser`'s current domain." | ||
([browser name] | ||
(delete-cookie! browser name (.getHost (java.net.URL. (brwsr/current-url browser))))) | ||
([browser name domain] | ||
(let [manager (.manage browser) | ||
cookies (.getCookies manager)] | ||
(when-let [cookie (some (fn [c] | ||
(and (= (.getName c) name) | ||
(= (.getDomain c) domain) | ||
c)) | ||
cookies)] | ||
(.deleteCookie manager cookie)) | ||
browser))) | ||
|
||
(defn- build-selenium-cookie | ||
"Build an instance of org.openqa.selenium.Cookie with `name`, `value` and `options`." | ||
[name value options] | ||
(.build | ||
(reduce | ||
(fn [builder [k v]] | ||
(case k | ||
:domain (.domain builder v) | ||
:expires-on (.expiresOn builder v) | ||
:http-only (.isHttpOnly builder v) | ||
:secure (.isSecure builder v) | ||
:path (.path builder v) | ||
builder)) | ||
(org.openqa.selenium.Cookie$Builder. name value) | ||
options))) | ||
|
||
(defn set-cookie! | ||
"Set a cookie with given `name` and `value`. If a cookie with the same name and `domain` is already present, it will be replaced. The following keyword arguments are supported. | ||
- `:domain` (String) The cookie domain, defaults to the current domain of the browser. | ||
- `:expires-on` (java.util.Date) Expiration date. Must be in the future or the cookie won't be stored. | ||
- `:http-only` (Boolean) Cookie's HttpOnly flag, disallow access from JavaScript | ||
- `:secure` (Boolean) Cookie's Secure flag, only serve over HTTPS. | ||
- `:path` (String) URL path of the cookie." | ||
[browser name value & {domain :domain :as options}] | ||
(if domain | ||
(delete-cookie! browser name domain) | ||
(delete-cookie! browser name)) | ||
(.addCookie (.manage browser) (build-selenium-cookie name value options)) | ||
browser) |
Oops, something went wrong.