webdriver 0.6.0.4 → 0.6.1
raw patch · 11 files changed
+136/−109 lines, 11 filesdep ~exceptions
Dependency ranges changed: exceptions
Files
- CHANGELOG.md +9/−0
- README.md +49/−28
- TODO +0/−34
- TODO.md +22/−0
- src/Test/WebDriver/Capabilities.hs +8/−6
- src/Test/WebDriver/Class.hs +21/−19
- src/Test/WebDriver/Commands.hs +9/−8
- src/Test/WebDriver/Commands/Internal.hs +1/−1
- src/Test/WebDriver/Config.hs +11/−7
- src/Test/WebDriver/Monad.hs +3/−3
- webdriver.cabal +3/−3
CHANGELOG.md view
@@ -1,4 +1,13 @@ #Change Log++##0.6.1+* Added the ability to pass HTTP request headers at session creation+* Fixed an issue involving an obsolete JSON representation of Chrome capabilities+* Relax upper bound on exceptions dependency++##0.6.0.4+* Support for monad-control 1.0+ ##0.6.0.3 * Relaxed upper bounds on text and http-client versions
README.md view
@@ -1,8 +1,21 @@-# About hs-webdriver is a Selenium WebDriver client for the Haskell programming language. You can use it to automate browser sessions for testing, system administration, etc. For more information about Selenium itself, see http://seleniumhq.org/ +# Contents+* [Installation](#installation)+ * [Installation from Hackage](#installation-from-hackage)+ * [Installation from This Repository](#installation-from-this-repository)+* [Getting Started](#getting-started)+ * [Using the Selenium Server](#using-the-selenium-server)+ * [Hello, World!](#hello-world)+ * [Demonic invocations: a bit of boilerplate](#demonic-invocations-a-bit-of-boilerplate)+ * [Configuring a WebDriver session](#configuring-a-webdriver-session)+ * [Initializing tests](#initializing-tests)+ * [Actually writing tests!](#actually-writing-tests)+* [Integration with Haskell Testing Frameworks](#integration-with-haskell-testing-frameworks)+* [Documentation](#documentation)+ # Installation hs-webdriver uses the Cabal build system to configure, build, install, and generate documentation on multiple platforms. @@ -42,14 +55,14 @@ #Getting Started-WebDriver is a client-server protocol. Since hs-webdriver only implements a WebDriver client, in order to make use of this library you must have a WebDriver server that you can connect to.+WebDriver is a client-server protocol. Since hs-webdriver only implements a WebDriver client, you must have a WebDriver server to which you can connect in order to make use of this library. -##Using the Selenium stand-alone server-While you can use any WebDriver server out there, probably the simplest server to use with hs-webdriver is the Java stand-alone server. This server is cross-platform and works with every major browser. Head over to http://docs.seleniumhq.org/download/ and download the latest version of Selenium Server. Next, run the Java jar; in a POSIX shell, this should look something like:+##Using the Selenium Server+While you can use any WebDriver server out there, probably the simplest server to use with hs-webdriver is [Selenium Server](http://docs.seleniumhq.org/download/). You'll need an installation of the Java runtime to use this server. Once you've downloaded Selenium Server to your current working directory, you can start the server with this shell command: - java -jar selenium-server-standalone-X.X.X.jar+ java -jar selenium-server-standalone-*.jar -The server should now be up and running at localhost on port 4444.+The server should now be listening at localhost on port 4444. ##Hello, World! With the Selenium server running locally, you're ready to write browser automation scripts in Haskell. Let's start with a simple example.@@ -66,64 +79,72 @@ searchInput <- findElem (ByCSS "input[type='text']") sendKeys "Hello, World!" searchInput ```-hs-webdriver uses a very simple EDSL implemented within a state monad. Interacting with the remote browser is done via a sequence of commands within this monad. The state monad maintains implicit information about the WebDriver session between commands, so that individual commands only need to specify parameters relevant to the action they perform. If you're new to monads, there are plenty of resources available for learning on the web, but for now you can think of the `WD` monad as a very simple imperative language operating on an implicitly defined session object.+hs-webdriver uses a very simple EDSL implemented within a state monad. Interacting with the remote browser is done via a sequence of commands within this monad. The state monad maintains implicit information about the WebDriver session between commands, so that individual commands only need to specify parameters relevant to the action they perform. If you're new to monads, there are plenty of resources available for learning on the web, but for now you can think of the `WD` monad as implementing a very simple imperative language operating on an implicit browser session. Let's take a closer look at each piece of this example. ###Demonic invocations: a bit of boilerplate-+```hs {-# LANGUAGE OverloadedStrings #-}--hs-webdriver uses the `Text` type to represent Unicode character sequences, which is significantly more efficient than the standard Haskell implementation for Unicode strings. This directive tells GHC to overload string literals so that they can be used to represent `Text` values.-+```+hs-webdriver uses the `Text` type to represent Unicode character sequences, which is significantly more efficient than the standard Haskell structure for strings. This directive tells GHC to overload string literals so that they can be used to represent `Text` values (or, more generally, any other instance of [IsString](http://hackage.haskell.org/package/base-4.7.0.2/docs/Data-String.html])).+```hs import Test.WebDriver--This line is fairly straightforward; we need to import the library so that we can use it! Most of the basic API is available through the `Test.WebDriver` module, so this is the only import you should need for most tests. There are other modules that may be of interest for advanced usage; in particular, `Test.WebDriver.Commands.Wait` provides so-called "implicit waits" as defined by other WebDriver libraries.+```+This line is fairly straightforward; we need to import the library so that we can use it! Most of the basic API is available through the `Test.WebDriver` module, so this is the only import you should need for most tests. There are other modules that may be of interest for advanced usage; in particular, `Test.WebDriver.Commands.Wait` provides so-called "implicit waits" as defined by other WebDriver libraries. There are also modules that support custom Firefox and Chrome extensions, but these features are not fully stable. ###Configuring a WebDriver session-+```hs myConfig :: WDConfig myConfig = defaultConfig- +``` To configure a new WebDriver session, we use the `WDConfig` type; this is a record type with various configuration fields. To connect to the Selenium server that we spawned earlier, the `defaultConfig` is sufficient. By default, the browser is set to Firefox, but that can be changed; the following configuration will use Google Chrome instead of Firefox for our test:-+```hs myConfig :: WDConfig myConfig = defaultConfig { wdCapabilities = defaultCaps { browser = chrome } }+```+*Note*: To use Google Chrome, you need to install Google's proprietary [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) in a directory where it can be recognized by Selenium Server (see: https://code.google.com/p/selenium/wiki/ChromeDriver). ###Initializing tests-+```hs main :: IO () main = runSession myConfig $ do-+``` `main` is the standard entry point for a Haskell program, defined as a value of type `IO a`. In order to transform our `WD` action into an `IO` action, we use the `runSession` function, which has the type:-+```hs runSession :: WDConfig -> WD a -> IO a- + ``` So we pass to `runSession` our configuration record along with a WebDriver "script" to perform, and it transforms the script into a side-effectful `IO` action. The `WDConfig` record is used to automatically initialize our session with the remote server. -NOTE: `runSession` does not automatically close the session it creates. This is intentional, as you may want to manually inspect the browser state after your code executes. If you want to have the session automatically close, you can use the `finallyClose` function to provide this behavior.-+*Note*: `runSession` does not automatically close the session it creates. This is intentional, as you may want to manually inspect the browser state after your code executes. If you want to have the session automatically close, you can use the `finallyClose` function to provide this behavior.+```hs main = runSession myConfig . finallyClose $ do-+``` ###Actually writing tests!-+```hs openPage "http://google.com" searchInput <- findElem (ByCSS "input[type='text']") sendKeys "Hello, World!" searchInput-+``` Interaction with the browser is accomplished via WebDriver "commands", which are just function calls within the `WD` monad. Most of these commands are defined in the `Test.WebDriver.Commands` modules, and are fairly self-explanatory. In this example, `openPage` opens a new URL, and `findElem` searches for a DOM element on the current page which matches the given selector (possible selectors include `ById`, `ByName`, `ByClass`, `ByTag`, `ByLinkText`, `ByCSS`, and `ByXPath`). The DOM Element found by the result of the search is bound to the local variable `searchInput`, and `sendKeys` sends a sequence of emulated keystrokes to the given element. -This example contains all of the basic elements of a simple WebDriver test. For complete documentation on each command, check out the documentation for `Test.WebDriver.Commands` (see [#Documentation](Documentation)).+This example contains all of the basic elements of a simple WebDriver test. For complete documentation on each command, check out the documentation for [Test.WebDriver.Commands](https://hackage.haskell.org/package/webdriver-0.6.0.4/docs/Test-WebDriver-Commands.html) +#Integration with Haskell Testing Frameworks +This package does not provide utilities to integrate with popular Haskell testing frameworks. However, other packages exist for this purpose:++* [hspec-webdriver](https://hackage.haskell.org/package/hspec-webdriver)+* [quickcheck-webdriver](https://hackage.haskell.org/package/quickcheck-webdriver)+ #Documentation -Documentation for hs-webdriver is available on Hackage at <http://hackage.haskell.org/package/webdriver>. However, here's how to generate local HTML documentation from this source revision:+Documentation for hs-webdriver is available on Hackage at <http://hackage.haskell.org/package/webdriver>. You can also generate local HTML documentation from this source revision with the following shell command: ```sh runhaskell Setup.hs haddock ``` -Haddock will generate documentation and save it in dist/doc/html/webdriver+Haddock will generate documentation and save it in `dist/doc/html/webdriver`
− TODO
@@ -1,34 +0,0 @@-in upcoming releases---- fix loadProfile so that it doesn't cause an overlap with user addExtension calls---- add support for Opera profiles---- improve documentation- -- document errors.- -- provide examples- -- basic runSession usage- -- intermediate usage- -- exception handling with lifted-base- -- explicit waits usage- -- firefox profile usage- -- REPL usage---for 1.0---- add WDSettings, use WDSession as internal State type. --- allow settings to automatically load drivers. add modules with driver loading-functions--- use http-conduit and attoparsec-conduit to stream JSON data. use request methods from http-types----distant future---- use ReaderT to store WDVersion for use with implicit configurations.---misc. unsorted----consider adding withSession to SessionState so that it can be overloaded easily, or rewriting it so that it's overloaded but not a method itself.
+ TODO.md view
@@ -0,0 +1,22 @@+#known issues+- fix loadProfile so that it doesn't cause an overlap with user addExtension calls++#features+- add support for Opera profiles+- overload URL inputs/outputs to implicitly support structured URL types+- provide exception handling utilities (maybeNotFound, ignoreNotFound, ...)++#documentation+- document errors.+- provide examples+ - basic runSession usage+ - intermediate usage+ - exception handling with lifted-base+ - explicit waits usage+ - firefox profile usage+ - REPL usage+- allow WDConfig to automatically load drivers. add modules with driver loading functions+++#considerations+- consider adding withSession to SessionState so that it can be overloaded easily, or rewriting it so that it's overloaded but not a method itself.
src/Test/WebDriver/Capabilities.hs view
@@ -158,12 +158,14 @@ ,"firefox_binary" .= ffBinary ] Chrome {..}- -> catMaybes [ opt "chrome.chromedriverVersion" chromeDriverVersion- , opt "chrome.binary" chromeBinary- ]- ++ ["chrome.switches" .= chromeOptions- ,"chrome.extensions" .= chromeExtensions- ]+ -> catMaybes [ opt "chrome.chromedriverVersion" chromeDriverVersion ]+ ++ [ "chromeOptions" .= object (catMaybes+ [ opt "binary" chromeBinary+ ] +++ [ "args" .= chromeOptions+ , "extensions" .= chromeExtensions+ ]+ )] IE {..} -> ["ignoreProtectedModeSettings" .= ieIgnoreProtectedModeSettings ,"ignoreZoomSetting" .= ieIgnoreZoomSetting
src/Test/WebDriver/Class.hs view
@@ -4,12 +4,13 @@ ( -- * WebDriver class WebDriver(..), Method, methodDelete, methodGet, methodPost, ) where-import Test.WebDriver.Session +import Test.WebDriver.Session import Data.Aeson import Data.Text (Text) import Network.HTTP.Types.Method (methodDelete, methodGet, methodPost, Method)+import Network.HTTP.Types.Header (RequestHeaders) import Control.Monad.Trans.Maybe import Control.Monad.Trans.Identity@@ -26,49 +27,50 @@ - + -- |A class for monads that can handle wire protocol requests. This is the -- operation underlying all of the high-level commands exported in -- "Test.WebDriver.Commands". For more information on the wire protocol see -- <http://code.google.com/p/selenium/wiki/JsonWireProtocol> class (WDSessionState wd) => WebDriver wd where doCommand :: (ToJSON a, FromJSON b) =>- Method -- ^HTTP request method- -> Text -- ^URL of request- -> a -- ^JSON parameters passed in the body- -- of the request. Note that, as a special case,- -- anything that converts to Data.Aeson.Null will- -- result in an empty request body.- -> wd b -- ^The JSON result of the HTTP request.+ RequestHeaders -- ^Additional headers+ -> Method -- ^HTTP request method+ -> Text -- ^URL of request+ -> a -- ^JSON parameters passed in the body+ -- of the request. Note that, as a special case,+ -- anything that converts to Data.Aeson.Null will+ -- result in an empty request body.+ -> wd b -- ^The JSON result of the HTTP request. instance WebDriver wd => WebDriver (SS.StateT s wd) where- doCommand rm t a = lift (doCommand rm t a)- + doCommand rh rm t a = lift (doCommand rh rm t a)+ instance WebDriver wd => WebDriver (LS.StateT s wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a) instance WebDriver wd => WebDriver (MaybeT wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a) instance WebDriver wd => WebDriver (IdentityT wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a) instance (Monoid w, WebDriver wd) => WebDriver (LW.WriterT w wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a) instance WebDriver wd => WebDriver (ReaderT r wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a) instance (Error e, WebDriver wd) => WebDriver (ErrorT e wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a) instance (Monoid w, WebDriver wd) => WebDriver (SRWS.RWST r w s wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a) instance (Monoid w, WebDriver wd) => WebDriver (LRWS.RWST r w s wd) where- doCommand rm t a = lift (doCommand rm t a)+ doCommand rh rm t a = lift (doCommand rh rm t a)
src/Test/WebDriver/Commands.hs view
@@ -91,6 +91,7 @@ import Network.URI hiding (path) -- suppresses warnings import Codec.Archive.Zip import qualified Data.Text.Lazy.Encoding as TL+import Network.HTTP.Types.Header (RequestHeaders) import Control.Applicative import Control.Monad.State.Strict@@ -112,15 +113,15 @@ ignoreReturn = void -- |Create a new session with the given 'Capabilities'.-createSession :: WebDriver wd => Capabilities -> wd WDSession-createSession caps = do- ignoreReturn . doCommand methodPost "/session" . single "desiredCapabilities" $ caps+createSession :: WebDriver wd => RequestHeaders -> Capabilities -> wd WDSession+createSession headers caps = do+ ignoreReturn . doCommand headers methodPost "/session" . single "desiredCapabilities" $ caps getSession -- |Retrieve a list of active sessions and their 'Capabilities'.-sessions :: WebDriver wd => wd [(SessionId, Capabilities)]-sessions = do- objs <- doCommand methodGet "/sessions" Null+sessions :: WebDriver wd => RequestHeaders -> wd [(SessionId, Capabilities)]+sessions headers = do+ objs <- doCommand headers methodGet "/sessions" Null forM objs $ parsePair "id" "capabilities" "sessions" -- |Get the actual 'Capabilities' of the current session.@@ -739,8 +740,8 @@ -- |Get information from the server as a JSON 'Object'. For more information -- about this object see -- <http://code.google.com/p/selenium/wiki/JsonWireProtocol#/status>-serverStatus :: (WebDriver wd) => wd Value -- todo: make this a record type-serverStatus = doCommand methodGet "/status" Null+serverStatus :: (WebDriver wd) => RequestHeaders -> wd Value -- todo: make this a record type+serverStatus headers = doCommand headers methodGet "/status" Null -- |A record that represents a single log entry. data LogEntry =
src/Test/WebDriver/Commands/Internal.hs view
@@ -73,7 +73,7 @@ where msg = "doSessCommand: No session ID found for relative URL " ++ show path- Just (SessionId sId) -> doCommand method+ Just (SessionId sId) -> doCommand [] method (T.concat ["/session/", urlEncode sId, path]) args -- |A wrapper around 'doSessCommand' to create element URLs.
src/Test/WebDriver/Config.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE OverloadedStrings, RecordWildCards, FlexibleContexts #-} module Test.WebDriver.Config( -- * WebDriver configuration- WDConfig(..), defaultConfig, mkSession + WDConfig(..), defaultConfig, mkSession ) where import Test.WebDriver.Session import Test.WebDriver.Capabilities@@ -9,7 +9,8 @@ import Data.Default (Default, def) import Data.String (fromString) -import Network.HTTP.Client (Manager, newManager, defaultManagerSettings) +import Network.HTTP.Client (Manager, newManager, defaultManagerSettings)+import Network.HTTP.Types (RequestHeaders) import Control.Monad.Base (MonadBase, liftBase) @@ -20,6 +21,8 @@ wdHost :: String -- |Port number of the server (default 4444) , wdPort :: Int+ -- |Additional request headers to send to the server during session creation (default [])+ , wdRequestHeaders :: RequestHeaders -- |Capabilities to use for this session , wdCapabilities :: Capabilities -- |Whether or not we should keep a history of HTTP requests/responses@@ -38,18 +41,19 @@ def = WDConfig { wdHost = "127.0.0.1" , wdPort = 4444+ , wdRequestHeaders = [] , wdCapabilities = def- , wdKeepSessHist = False + , wdKeepSessHist = False , wdBasePath = "/wd/hub" , wdHTTPManager = Nothing }- + {- |A default session config connects to localhost on port 4444, and hasn't been initialized server-side. This value is the same as 'def' but with a less polymorphic type. -} defaultConfig :: WDConfig defaultConfig = def- + -- |Constructs a new 'WDSession' from a given 'WDConfig' mkSession :: MonadBase IO m => WDConfig -> m WDSession mkSession WDConfig{..} = do@@ -66,9 +70,9 @@ histUpdate | wdKeepSessHist = (:) | otherwise = \x _ -> [x]- - ++
src/Test/WebDriver/Monad.hs view
@@ -53,8 +53,8 @@ putSession = WD . put instance WebDriver WD where- doCommand method path args =- mkRequest [] method path args+ doCommand headers method path args =+ mkRequest headers method path args >>= sendHTTPRequest >>= getJSONResult >>= either throwIO return@@ -71,7 +71,7 @@ runSession :: WDConfig -> WD a -> IO a runSession conf wd = do sess <- mkSession conf- runWD sess $ createSession (wdCapabilities conf) >> wd+ runWD sess $ createSession (wdRequestHeaders conf) (wdCapabilities conf) >> wd -- |Locally sets a 'WDSession' for use within the given 'WD' action. -- The state of the outer action is unaffected by this function.
webdriver.cabal view
@@ -1,5 +1,5 @@ Name: webdriver-Version: 0.6.0.4+Version: 0.6.1 Cabal-Version: >= 1.8 License: BSD3 License-File: LICENSE@@ -9,7 +9,7 @@ Category: Web, Browser, Testing Synopsis: a Haskell client for the Selenium WebDriver protocol Build-type: Simple-Extra-source-files: README.md, TODO, CHANGELOG.md, .ghci+Extra-source-files: README.md, TODO.md, CHANGELOG.md, .ghci Description: A Selenium WebDriver client for Haskell. You can use it to automate browser sessions@@ -54,7 +54,7 @@ , time == 1.* , unordered-containers >= 0.1.3 && < 0.4 , vector >= 0.3 && < 0.11- , exceptions >= 0.4 && < 0.7+ , exceptions >= 0.4 && < 0.9 , scientific >= 0.2 && < 0.4 , data-default >= 0.2 && < 1.0 , cond >= 0.3 && < 0.5