Introduction: Taxi

semperos edited this page Sep 27, 2012 · 5 revisions

For API Documentation with examples, see Taxi API Documentation

Clj-webdriver provides several layers of abstraction over Selenium-WebDriver. The Taxi API represents the highest-level, most concise way for interacting with the browser and elements on the page. Its primary features include:

  • Simple mechanisms for opening/closing browsers
  • Default CSS query capability for finding elements on the page (configurable)
  • Functions for managing HTML elements, multiple open windows, cookies, test timeouts, and more
  • Seamless interop with lower levels of clj-webdriver's API

As a quick example, here is how to log into Github using the Taxi API:

(use '

(set-driver! {:browser :firefox})

(to "")
(click "a[href*='login']")

(input-text "#login_field" "your_username")
(-> "#password"
  (input-text "your_password")

Whereas the goal of clj-webdriver's Core API is flexibility in how one queries the page and an explicit, functional style, the goal of the Taxi API is to provide a set of pluggable defaults that makes things more concise while writing tests.

Configuration & Details

There are only two pieces of state handled "in the background": a Driver instance and a finder function. These are controlled with set-driver! and set-finder! respectively.

The Driver instance is specified just as in the core namespace; you pass in a :browser, as well as an optional :cache-spec if you want to use Caching Support. You can always access the Driver record in the var*driver*, though you should use set-driver! when possible.

A finder function takes a single query argument and returns all the elements that match that query. Alternatively, if an Element record itself is passed in as the query argument, it is returned unchanged (this allows for nice function composition). The namespace provides two finder functions by default, css-finder and xpath-finder, with CSS querying as the default (per recommendations from the Selenium-WebDriver community). You can always access the finder function directly in the var*finder-fn*, though you should use set-finder! when possible.

Here are the implementations of the built-in css-finder and xpath-finder, to make things clearer:

(defn css-finder
  "Given a CSS query `q`, return a lazy seq of the elements found by calling `find-elements` with `by-css`. If `q` is an `Element`, it is returned unchanged."
  (if (element? q)
    (core/find-elements *driver* {:css q})))

(defn xpath-finder
  "Given a XPath query `q`, return a lazy seq of the elements found by calling `find-elements` with `by-xpath`. If `q` is an `Element`, it is returned unchanged."
  (if (element? q)
    (core/find-elements *driver* {:xpath q})))

Note that these functions leverage the internal *driver* instance, and pass that to core/find-elements using a default finding method (in these cases, :css and :xpath options). In conjunction with this, all functions that logically only take one Element (e.g., click, input-text, etc.) simply take the first element returned by these finder functions. This means that you can model your own finder functions on the examples above, if you need custom functionality.

These facts combined allow us to write (click "a#my_button") instead of the much longer (click driver (find-element {:css "a#my_button"}). More important than the length of the second function, however, is that it allows for several different ways of accomplishing the same query. Here are just a few:

(find-element {:css "a#my_button"})
(find-element {:xpath "//a[@id='my_button']"})
(find-element {:tag :a, :id "my_button"})

While this flexibility is important to the overall power of the clj-webdriver API, often you just need a single, unified way to query the page and author your tests. The Taxi API encourages just such a standardization, and provides added concision as a result.

At this point, all user-facing functionality provided at lower levels are exposed in the Taxi API. Unless you need more direct access to Driver or Element records (or you just have an aversion to pseudo-non-functional paradigms), you should use the Taxi API for your own development.

Multiple Browsers

If you want to use multiple browsers with the Taxi API, instead of registering a default one with the set-driver! function, you may simply create a new Driver instance manually and use it with the Taxi API. For example, the following calls to to are equivalent:

(def driver (new-driver {:browser :chrome}))
(set-driver! driver)

(to "")
(to driver "")

This is true throughout the Taxi API. Any function that implicitly uses the driver registered with set-driver! can also optionally take a driver explicitly. In this case, you could start as many browsers as you want, never call set-driver!, and just pass them in when needed. For example:

(def driver-a (new-driver {:browser :chrome}))
(def driver-b (new-driver {:browser :firefox}))

(to driver-a "")
(to driver-b "")
;; etc.