packages feed

webdriver-w3c 0.0.1 → 0.0.2

raw patch · 21 files changed

+976/−553 lines, 21 filesdep +transformersdep ~JuicyPixelsdep ~QuickCheckdep ~SHA

Dependencies added: transformers

Dependency ranges changed: JuicyPixels, QuickCheck, SHA, aeson, aeson-pretty, base64-bytestring, bytestring, containers, directory, exceptions, http-client, http-types, lens, lens-aeson, network-uri, parsec, random, scientific, script-monad, stm, tasty, tasty-expected-failure, tasty-hunit, tasty-quickcheck, text, time, unordered-containers, uri-encode, vector, wreq

Files

+ CHANGELOG.md view
@@ -0,0 +1,29 @@+Changelog for webdriver-w3c+===========================++0.0.2+-----++This version introduces significant changes to the API, prompted by changes in the `script-monad` dependency. The main change is that `WebDriver` and `WebDriverT` have been replaced by `WebDriverT` and `WebDriverTT` and are a more sensible monad transformer and monad transformer transformer, respectively. The main effect of this is that (1) `WebDriver*` types take an extra parameter for the effect monad, and (2) functions for working with `WebDriver*` now have additional `Monad` and `MonadTrans` constraints. The library will now only compile with GHC >=8.6 due to a transitive dependency on `QuantifiedConstraints`.++* Added+  * Browser preferences field on `FirefoxOptions` and `ChromeOptions`+  * `readDataFile`, `writeDataFile`, `readJsonFile`, and `writeJsonFile` data helpers+  * `breakpoint` and `breakpointWith` for helping with debugging; controlled by `breakpointsOn`, and `breakpointsOff`+  * `expectIs`+* Changed+  * Switched order of arguments for `elementSendKeys`, `getElementAttribute`, `getElementProperty`, and `getElementCssValue`. The element reference now comes last to make it easier to chain these with `>>=`.+  * `logDebug` and `logNotice`+  * Tested on geckodriver 0.23.0.+* Fixed+  * Bug in behavior of `cleanupOnError` was causing it to miss some errors, which left the remote end session open++++0.0.1+-----++* Added+    * `WebDriver` monad for remotely controlling user agents. Also comes in monad transformer flavor with `WebDriverT` +    * Bindings for all [WebDriver endpoints](https://w3c.github.io/webdriver/webdriver-spec.html) as of 2018-04-20+    * Integration with the [Tasty](https://hackage.haskell.org/package/tasty) test framework
− ChangeLog.md
@@ -1,12 +0,0 @@-# Changelog for webdriver-w3c--## Unreleased changes--(none)--## 0.0.1--* New-    * `WebDriver` monad for remotely controlling user agents. Also comes in monad transformer flavor with `WebDriverT` -    * Bindings for all [WebDriver endpoints](https://w3c.github.io/webdriver/webdriver-spec.html) as of 2018-04-20-    * Integration with the [Tasty](https://hackage.haskell.org/package/tasty) test framework
README.md view
@@ -37,7 +37,7 @@  * _A cursory glance:_ This brief [tutorial](https://github.com/nbloomf/webdriver-w3c/blob/master/doc/Tutorial.md) shows how to go from nothing to one very simple test. * _To start a simple project:_ If you want to write a test suite, there's a separate tutorial on using the [tasty integration](https://github.com/nbloomf/webdriver-w3c/blob/master/doc/TastyDemo.md).-* _To dig into the API:_ The API docs will eventually be on Hackage.+* _To dig into the API:_ The API docs are on [Hackage](https://hackage.haskell.org/package/webdriver-w3c). * _To mess with the library code:_ There's a very small amount of [developer documentation](https://github.com/nbloomf/webdriver-w3c/blob/master/dev/doc.md); I'm also happy to answer questions.  
app/Main.lhs view
@@ -12,8 +12,10 @@ > import Test.Tasty.WebDriver >  > import Test.Tasty+> import Control.Monad.Trans.Class > import qualified System.Environment as SE > import Control.Monad+> import System.IO >  > main :: IO () > main = return ()@@ -61,7 +63,7 @@  Ok! You've got your WebDriver proxy (geckodriver) running in one terminal window, and ghci running in another. Let's start with a simple example to illustrate what we can do, then explain how it works. Read this code block, even if the syntax is meaningless. -> do_a_barrel_roll :: WebDriver IO ()+> do_a_barrel_roll :: WebDriverT IO () > do_a_barrel_roll = do >   fullscreenWindow >   navigateTo "https://www.google.com"@@ -82,7 +84,7 @@  > example1 :: IO () > example1 = do->   execWebDriver defaultWebDriverConfig+>   execWebDriverT defaultWebDriverConfig >     (runIsolated defaultFirefoxCapabilities do_a_barrel_roll) >   return () @@ -183,14 +185,14 @@  In addition to the usual browser action commands, you can sprinkle your `WebDriver` sessions with *assertions*. Here's an example. -> what_page_is_this :: (Monad eff) => WebDriver eff ()+> what_page_is_this :: (Monad eff) => WebDriverT eff () > what_page_is_this = do >   navigateTo "https://www.google.com" >   title <- getTitle >   assertEqual title "Welcome to Lycos!" "Making sure we're at the lycos homepage" >   return () -Note the signature: `(Monad eff) => WebDriver eff ()` instead of `WebDriver IO ()`. What's happening here is that `WebDriver` is parameterized by the monad that effects (like writing to files and making HTTP requests) take place in. These effects are "run" by an explicit evaluator that, for the default configuration, happens to use `IO`, but both the effect monad and the evaluator are configurable. By swapping out `IO` for another type we can, for example, run our tests against a mock Internet, and swapping out the evaluator we might have a "dry run" evaluator that doesn't actually do anything, but logs what it would have done. It's good practice to make our `WebDriver` code maximally flexible by using an effect parameter like `eff` instead of the concrete `IO` unless there's a good reason not to.+Note the signature: `(Monad eff) => WebDriverT eff ()` instead of `WebDriverT IO ()`. What's happening here is that `WebDriverT` is a transformer over a monad `eff` within which a restricted set of effects (like writing to files and making HTTP requests) take place. These effects are "run" by an explicit evaluator that, for the default configuration, happens to use `IO`, but both the effect monad and the evaluator are configurable. By swapping out `IO` for another type we can, for example, run our tests against a mock Internet, and swapping out the evaluator we might have a "dry run" evaluator that doesn't actually do anything, but logs what it would have done. It's good practice to make our `WebDriver` code maximally flexible by using an effect parameter like `eff` instead of the concrete `IO` unless there's a good reason not to.  Anyway, back to the example. What do you think this code does? Let's try it: type @@ -202,7 +204,7 @@  > example2 :: IO () > example2 = do->   (_, result) <- debugWebDriver defaultWebDriverConfig+>   (_, result) <- debugWebDriverT defaultWebDriverConfig >     (runIsolated defaultFirefoxCapabilities what_page_is_this) >   printSummary result >   return ()@@ -214,7 +216,7 @@ 3. `debugWebDriver` works much like `execWebDriver`, except that it collects the results of any assertion statements and summarizes them (this is `result`). 4. `printSummary` takes the assertion results and prints them out all pretty like. -So what kinds of assertions can be made? The best place to learn about these is in the generated Haddock documentation.+Documentation on assertions is on [Hackage](https://hackage.haskell.org/package/webdriver-w3c-0.0.1/docs/Web-Api-WebDriver-Assert.html).   @@ -225,7 +227,7 @@  Suppose we've got two WebDriver tests. These are pretty dweeby just for illustration's sake. -> back_button :: (Monad eff) => WebDriver eff ()+> back_button :: (Monad eff) => WebDriverT eff () > back_button = do >   navigateTo "https://www.google.com" >   navigateTo "https://wordpress.com"@@ -234,7 +236,7 @@ >   assertEqual title "Google" "Behavior of 'back' button from WordPress homepage" >   return () > -> refresh_page :: (Monad eff) => WebDriver eff ()+> refresh_page :: (Monad eff) => WebDriverT eff () > refresh_page = do >   navigateTo "https://www.mozilla.org" >   pageRefresh@@ -262,12 +264,13 @@ >   SE.setEnv "TASTY_NUM_THREADS" "1" >   defaultWebDriverMain >     $ localOption (SilentLog)+>     $ localOption (PrivateMode True) >     $ test_suite  Here's what happened: -1. `test_suite` is a Tasty tree of individual `WebDriver` test cases.-2. `defaultWebDriverMain` is a Tasty function that runs test trees. In this case we've also used `localOption` to tweak how the tests run -- suppressing the usual session log output.+1. `test_suite` is a Tasty tree of individual `WebDriverT` test cases.+2. `defaultWebDriverMain` is a Tasty function that runs test trees. In this case we've also used `localOption` to tweak how the tests run -- in this case suppressing the usual session log output and running the browser in private mode.  Tasty gave us lots of nice things for free, like pretty printing test results and timings. @@ -284,7 +287,7 @@      1 out of 2 tests failed (11.53s) -Other test case constructors and test options are available. For now the best place to see what's possible is the haddock documentation for `Test.Tasty.WebDriver`.+Other test case constructors and test options are available; see [Hackage](https://hackage.haskell.org/package/webdriver-w3c-0.0.1/docs/Test-Tasty-WebDriver.html) for the details.  The test suite for `webdriver-w3c` itself uses the Tasty integration. There is also a function, `checkWebDriver`, that can be used to build tests with QuickCheck, if you don't find that idea abominable. :) @@ -293,13 +296,13 @@ We need more power! ------------------- -The vanilla `WebDriver` is designed to help you control a browser with _batteries included_, but it has limitations. It can't possibly anticipate all the different ways you might want to control your tests, and it can't do arbitrary `IO`. But we have a powerful and very general escape hatch: `WebDriver` is a special case of the `WebDriverT` monad transformer. +The vanilla `WebDriverT` is designed to help you control a browser with _batteries included_, but it has limitations. It can't possibly anticipate all the different ways you might want to control your tests, and it can't do arbitrary `IO`. But we have a powerful and very general escape hatch: the `WebDriverT` monad transformer is a special case of the `WebDriverTT` monad transformer _transformer_.  The actual definition of `WebDriver` is -    type WebDriver eff a = WebDriverT (IdentityT eff) a+    type WebDriverT eff a = WebDriverTT IdentityT eff a -where `IdentityT` is the _inner monad_ in transformer terms -- actually it's an inner monad transformer, on the effect monad `eff`. By swapping out `IdentityT` for another transformer we can add features specific to our application.+where `IdentityT` is the _inner monad transformer_. By swapping out `IdentityT` for another transformer we can add more features specific to our application.  Here's a typical example. Say you're testing a site with two deployment tiers -- "test" and "production". For the most part the same test suite should run against both tiers, but there are minor differences. Say the base URLs are slightly different; maybe production lives at `example.com` while test lives at `test.example.com`. Also while developing a new feature some parts of the test suite should only run on the test tier, maybe controlled by a feature flag. @@ -323,8 +326,8 @@ > instance (Monad eff) => Functor (ReaderT r eff) where >   fmap f x = x >>= (return . f) > -> liftReaderT :: (Monad eff) => eff a -> ReaderT r eff a-> liftReaderT x = ReaderT $ \_ -> x+> instance MonadTrans (ReaderT r) where+>   lift x = ReaderT $ \_ -> x >  > reader :: (Monad eff) => (r -> a) -> ReaderT r eff a > reader f = ReaderT $ \r -> return $ f r@@ -344,20 +347,20 @@ >   , featureFlag = False >   } -And we can augment `WebDriverT` with our reader transformer.+And we can augment `WebDriverTT` with our reader transformer. -> type MyWebDriver eff a = WebDriverT (ReaderT MyEnv eff) a+> type MyWebDriverT eff a = WebDriverTT (ReaderT MyEnv) eff a -Now we can build values in `MyWebDriver` using the same API as before, using the extra features of the inner monad with `liftWebDriverT`.+Now we can build values in `MyWebDriver` using the same API as before, using the extra features of the inner monad with `liftWebDriverTT`. -> custom_environment :: (Monad eff) => MyWebDriver eff ()+> custom_environment :: (Monad eff) => MyWebDriverT eff () > custom_environment = do->   theTier <- liftWebDriverT $ reader tier+>   theTier <- liftWebDriverTT $ reader tier >   case theTier of >     Test -> navigateTo "http://google.com" >     Production -> navigateTo "http://yahoo.com" -To actually run sessions using our custom monad stack we need to make a few adjustments. First, we use `execWebDriverT` instead of `execWebDriver`. This function takes one extra argument corresponding to `lift` for the inner transformer.+To actually run sessions using our custom monad stack we need to make a few adjustments. First, we use `execWebDriverTT` instead of `execWebDriverT`.  Second, we need to supply a function that "runs" the inner transformer (in this case `ReaderT eff a`) to `IO`. @@ -369,7 +372,7 @@ > example4 :: Tier -> IO () > example4 t = do >   execReaderT (env t) $->     execWebDriverT defaultWebDriverConfig liftReaderT+>     execWebDriverTT defaultWebDriverConfig >       (runIsolated defaultFirefoxCapabilities custom_environment) >   return () @@ -378,17 +381,44 @@     example4 Test     example4 Production -We can similarly use a custom inner monad to check assertions and with the tasty integration; there are analogous `debugWebDriverT` and `testCaseT` functions.+We can similarly use a custom inner monad to check assertions and with the tasty integration; there are analogous `debugWebDriverTT` and `testCaseTT` functions.  `ReaderT` is just one option for the inner monad transformer. We could put mutable state, delimited continuations, or even another HTTP API monad in there. Use your imagination!   -Where to Learn More--------------------+Debugging+--------- -The canonical source is the generated haddock documentation. Until this is uploaded to hackage, the only way to get the docs is to run+Running browser sessions is one thing, but writing and debugging them is another. `webdriver-w3c` has some tools for dealing with this as well. Besides the log, which gives a thorough account of what happened, we can include breakpoints in our code. When breakpoints are activated, they stop the session and give us a chance to poke around the browser before moving on. -    stack haddock+Here's a simple example. -in this directory and point your browser to the local URL that command emits under "Updating Haddock index for local packages and dependencies in..."+> stop_and_smell_the_ajax :: (Monad eff) => WebDriverT eff ()+> stop_and_smell_the_ajax = do+>   breakpointsOn+> +>   navigateTo "https://google.com"+> +>   breakpoint "Just checking"+> +>   navigateTo "https://mozilla.org"+> +>   breakpoint "are we there yet"++We can run this with `example5`:++> example5 :: IO ()+> example5 = do+>   execWebDriverT defaultWebDriverConfig+>     (runIsolated defaultFirefoxCapabilities stop_and_smell_the_ajax)+>   return ()++The basic `breakpoint` command gives the option to continue, throw an error, dump the current state and environment to stdout, and turn breakpoints off. A fancier version, `breakpointWith`, takes an additional argument letting us trigger a custom action.++++Where to Learn More+-------------------++For now the canonical documentation is the haddock annotations on [Hackage](https://hackage.haskell.org/package/webdriver-w3c).
app/ParallelStressTest.hs view
@@ -13,7 +13,7 @@ import System.Environment (lookupEnv) import Text.Read (readMaybe) -_test :: (Monad eff) => WebDriver eff ()+_test :: (Monad eff) => WebDriverT eff () _test = navigateTo "https://google.com"  
app/TastyDemo.lhs view
@@ -25,13 +25,13 @@ Define your tests ----------------- -First things first: to make a WebDriver test suite, we need some WebDriver tests. These are just values of type `WebDriver IO ()`. (Or more generally, `Effectful m => WebDriver m ()`, but that's not important for now.) Here are a few dweeby examples. It's not necessary for the tests to start with `_test` or use snake_case; I'm doing it here out of habit.+First things first: to make a WebDriver test suite, we need some WebDriver tests. These are just values of type `WebDriverT IO ()`. (Or more generally, `(Monad eff, Monad (t eff), MonadTrans t) => WebDriverTT t eff ()`, but that's not important for now.) Here are a few dweeby examples. It's not necessary for the tests to start with `_test` or use snake_case; I'm doing it here out of habit. -> _test_one :: (Monad eff) => WebDriver eff ()+> _test_one :: (Monad eff) => WebDriverT eff () > _test_one = do >   navigateTo "https://google.com" > -> _test_two :: (Monad eff) => WebDriver eff ()+> _test_two :: (Monad eff) => WebDriverT eff () > _test_two = do >   navigateTo "https://yahoo.com" >   assertSuccess "time travel achieved"@@ -46,11 +46,7 @@      defaultWebDriverMain :: TestTree -> IO () -This function wraps tasty's `defaultMain`, which handles command line option parsing, and adds some magic of its own. `TestTree` is tasty's type for an executable test suite. There are three functions for building these out of WebDriver sessions.--1. `testCase`, which defines a simple named test consisting of a single WebDriver session-2. `testGroup`, which defines a named list of `TestTree`s-3. `testCaseWithSetup`, which is like `testCase` but takes additional setup and teardown instructions to be run before and after the test; `testCaseWithSetup` is less frequently useful, but it's there just in case.+This function wraps tasty's `defaultMain`, which handles command line option parsing, and adds some magic of its own. `TestTree` is tasty's type for an executable test suite. There are several functions for building these out of WebDriver sessions; they live in `Test.Tasty.WebDriver` and have names starting with `testCase`.  Here's an example `main`. @@ -110,7 +106,7 @@  `webdriver-w3c` can also run your tests in parallel. To take advantage of this, you'll need to compile your executable with `-threaded -rtsopts -with-rtsopts=-N` and start it with the `--num-threads N` option. You'll also need to start more than one remote end of each type. Note that if you want to run N tests in parallel, then you'll need N instances of _each_ remote end (geckodriver and chromedriver) running in the background. This is because the tests are _processed_ sequentially, even if they run in parallel. For instance, if you have 100 firefox tests followed by 100 chrome tests, but run them with one geckodriver and one chromedriver, the tests will run sequentially. -There are a bunch of other command line options for tweaking the behavior of your webdriver tests; use `wd-tasty-demo --help` to see a list. Most of these are pretty specialized. Other options are pretty common. In addition to `--wd-remote-ends` and `--wd-remote-ends-config`, there's `--wd-driver`, for specifying which driver to use, and `--wd-response-format`, which is required when using chromedriver because chromedriver is not fully spec compliant.+There are a bunch of other command line options for tweaking the behavior of your webdriver tests; use `wd-tasty-demo --help` to see a list. Most of these are pretty specialized. Other options are pretty common. In addition to `--wd-remote-ends` and `--wd-remote-ends-config`, there's `--wd-driver`, for specifying which driver to use, and `--wd-response-format`, which is required when using chromedriver because chromedriver is not fully spec compliant as of this writing.   Example sessions
src/Test/Tasty/WebDriver.hs view
@@ -1,6 +1,6 @@ {- | Module      : Test.Tasty.WebDriver-Description : WebDriver integration with the Tasty test framework.+Description : WebDriverT integration with the Tasty test framework. Copyright   : 2018, Automattic, Inc. License     : GPL-3 Maintainer  : Nathan Bloomfield (nbloomf@gmail.com)@@ -51,6 +51,7 @@   , Headless(..)   , LogColors(..)   , GeckodriverLog(..)+  , PrivateMode(..)    , module Test.Tasty.WebDriver.Config   ) where@@ -59,10 +60,16 @@  import Control.Monad.IO.Class   ( MonadIO, liftIO )+import Control.Monad.Trans.Class+  ( MonadTrans(..) )+import Control.Monad.Trans.Identity+  ( IdentityT(..) ) import Data.Typeable   ( Typeable, Proxy(Proxy) ) import Data.List   ( unlines, lookup )+import qualified Data.HashMap.Strict as HM+  ( fromList ) import System.IO   ( Handle, stdout, stderr, stdin, openFile, IOMode(..), hClose ) import Control.Concurrent@@ -80,7 +87,7 @@ import Data.IORef   ( IORef, newIORef, atomicModifyIORef' ) import Data.Maybe-  ( fromMaybe )+  ( fromMaybe, catMaybes ) import Data.Time.Clock.System   ( getSystemTime ) import Network.HTTP.Client@@ -159,19 +166,23 @@ _OPT_REMOTE_ENDS_CONFIG :: String _OPT_REMOTE_ENDS_CONFIG = "wd-remote-ends-config" +_OPT_PRIVATE_MODE :: String+_OPT_PRIVATE_MODE = "wd-private-mode"  -data WebDriverTest m eff = WebDriverTest++data WebDriverTest t eff = WebDriverTest   { wdTestName :: String-  , wdTestSession :: WebDriverT (m eff) ()+  , wdTestSession :: WebDriverTT t eff ()   , wdEval :: forall a. P WDAct a -> eff a-  , wdLift :: forall a. eff a -> m eff a-  , wdToIO :: forall a. m eff a -> IO a+  , wdToIO :: forall a. t eff a -> IO a   }   -instance (Monad eff, Monad (m eff), Typeable eff, Typeable m) => TT.IsTest (WebDriverTest m eff) where+instance+  (Monad eff, Monad (t eff), MonadTrans t, Typeable eff, Typeable t)+    => TT.IsTest (WebDriverTest t eff) where   testOptions = return     [ TO.Option (Proxy :: Proxy Driver)     , TO.Option (Proxy :: Proxy Headless)@@ -190,6 +201,7 @@     , TO.Option (Proxy :: Proxy NumRetries)     , TO.Option (Proxy :: Proxy LogColors)     , TO.Option (Proxy :: Proxy GeckodriverLog)+    , TO.Option (Proxy :: Proxy PrivateMode)     ]    run opts WebDriverTest{..} _ = do@@ -210,6 +222,7 @@       LogPrinterLock (Just logLock) = TO.lookupOption opts       LogColors logColors = TO.lookupOption opts       GeckodriverLog geckoLogLevel = TO.lookupOption opts+      PrivateMode privateMode = TO.lookupOption opts      let       title = comment wdTestName@@ -224,20 +237,26 @@         Geckodriver -> emptyCapabilities           { _browserName = Just Firefox           , _firefoxOptions = Just defaultFirefoxOptions-              { _firefoxBinary = browserPath-              , _firefoxArgs = if headless then Just ["-headless"] else Nothing-              , _firefoxLog = Just FirefoxLog-                  { _firefoxLogLevel = Just geckoLogLevel-                  }+            { _firefoxBinary = browserPath+            , _firefoxArgs = Just $ catMaybes+              [ if headless then Just "-headless" else Nothing+              , if privateMode then Just "-private" else Nothing+              ]+            , _firefoxLog = Just FirefoxLog+              { _firefoxLogLevel = Just geckoLogLevel               }+            }           }          Chromedriver -> emptyCapabilities           { _browserName = Just Chrome           , _chromeOptions = Just $ defaultChromeOptions-              { _chromeBinary = browserPath-              , _chromeArgs = if headless then Just ["--headless"] else Nothing-              }+            { _chromeBinary = browserPath+            , _chromeArgs = Just $ catMaybes+              [ if headless then Just "--headless" else Nothing+              , if privateMode then Just "--incognito" else Nothing+              ]+            }           }      dataPath <- case datas of@@ -271,6 +290,7 @@               , _httpSession = Nothing               , _userState = WDState                 { _sessionId = Nothing+                , _breakpoints = BreakpointsOff                 }               }             , _environment = defaultWebDriverEnvironment@@ -296,7 +316,7 @@               }             } -        (result, summary) <- wdToIO $ debugWebDriverT config wdLift $+        (result, summary) <- wdToIO $ debugWebDriverTT config $             title >> attemptLabel attemptNumber >> runIsolated caps wdTestSession          atomically $ releaseRemoteEnd remotesRef driver remote@@ -323,7 +343,7 @@ -- | `WebDriver` test case with the default `IO` effect evaluator. testCase   :: TT.TestName-  -> WebDriver IO () -- ^ The test+  -> WebDriverT IO () -- ^ The test   -> TT.TestTree testCase name test =   testCaseWithSetup name (return ()) return (const test)@@ -335,7 +355,7 @@   => TT.TestName   -> (forall a. P WDAct a -> eff a) -- ^ Evaluator   -> (forall a. eff a -> IO a) -- ^ Conversion to `IO`-  -> WebDriver eff ()+  -> WebDriverT eff ()   -> TT.TestTree testCaseM name eval toIO test =   testCaseWithSetupM name eval toIO (return ()) return (const test)@@ -343,35 +363,33 @@  -- | `WebDriverT` test case with the default `IO` effect evaluator. testCaseT-  :: (Monad (m IO), Typeable m)+  :: (Monad (t IO), MonadTrans t, Typeable t)   => TT.TestName-  -> (forall a. IO a -> m IO a) -- ^ Lift effects to the inner monad-  -> (forall a. m IO a -> IO a) -- ^ Conversion to `IO`-  -> WebDriverT (m IO) () -- ^ The test+  -> (forall a. t IO a -> IO a) -- ^ Conversion to `IO`+  -> WebDriverTT t IO () -- ^ The test   -> TT.TestTree-testCaseT name lift toIO test =-  testCaseWithSetupT name lift toIO (return ()) return (const test)+testCaseT name toIO test =+  testCaseWithSetupT name toIO (return ()) return (const test)   -- | `WebDriverT` test case with a custom effect evaluator. testCaseTM-  :: (Monad eff, Monad (m eff), Typeable eff, Typeable m)+  :: (Monad eff, Monad (t eff), MonadTrans t, Typeable eff, Typeable t)   => TT.TestName   -> (forall a. P WDAct a -> eff a) -- ^ Evaluator-  -> (forall a. eff a -> m eff a) -- ^ Lift effects to the inner monad-  -> (forall a. m eff a -> IO a) -- ^ Conversion to `IO`.-  -> WebDriverT (m eff) () -- ^ The test+  -> (forall a. t eff a -> IO a) -- ^ Conversion to `IO`.+  -> WebDriverTT t eff () -- ^ The test   -> TT.TestTree-testCaseTM name eval lift toIO test =-  testCaseWithSetupTM name eval lift toIO (return ()) return (const test)+testCaseTM name eval toIO test =+  testCaseWithSetupTM name eval toIO (return ()) return (const test)   -- | `WebDriver` test case with additional setup and teardown phases using the default `IO` effect evaluator. Setup runs before the test (for e.g. logging in) and teardown runs after the test (for e.g. deleting temp files). testCaseWithSetup   :: TT.TestName-  -> WebDriver IO u -- ^ Setup-  -> (v -> WebDriver IO ()) -- ^ Teardown-  -> (u -> WebDriver IO v) -- ^ The test+  -> WebDriverT IO u -- ^ Setup+  -> (v -> WebDriverT IO ()) -- ^ Teardown+  -> (u -> WebDriverT IO v) -- ^ The test   -> TT.TestTree testCaseWithSetup name =   testCaseWithSetupM name (evalIO evalWDAct) id@@ -383,23 +401,22 @@   => TT.TestName   -> (forall u. P WDAct u -> eff u) -- ^ Evaluator   -> (forall u. eff u -> IO u) -- ^ Conversion to `IO`-  -> WebDriver eff u -- ^ Setup-  -> (v -> WebDriver eff ()) -- ^ Teardown-  -> (u -> WebDriver eff v) -- ^ The test+  -> WebDriverT eff u -- ^ Setup+  -> (v -> WebDriverT eff ()) -- ^ Teardown+  -> (u -> WebDriverT eff v) -- ^ The test   -> TT.TestTree testCaseWithSetupM name eval toIO =-  testCaseWithSetupTM name eval IdentityT (toIO . runIdentityT)+  testCaseWithSetupTM name eval (toIO . runIdentityT)   -- | `WebDriverT` test case with additional setup and teardown phases using the default `IO` effect evaluator. Setup runs before the test (for e.g. logging in) and teardown runs after the test (for e.g. deleting temp files). testCaseWithSetupT-  :: (Monad (m IO), Typeable m)+  :: (Monad (t IO), MonadTrans t, Typeable t)   => TT.TestName-  -> (forall a. IO a -> m IO a) -- ^ Lift effects to the inner monad-  -> (forall a. m IO a -> IO a) -- ^ Conversion to `IO`-  -> WebDriverT (m IO) u -- ^ Setup-  -> (v -> WebDriverT (m IO) ()) -- ^ Teardown-  -> (u -> WebDriverT (m IO) v) -- ^ Test+  -> (forall a. t IO a -> IO a) -- ^ Conversion to `IO`+  -> WebDriverTT t IO u -- ^ Setup+  -> (v -> WebDriverTT t IO ()) -- ^ Teardown+  -> (u -> WebDriverTT t IO v) -- ^ Test   -> TT.TestTree testCaseWithSetupT name =   testCaseWithSetupTM name (evalIO evalWDAct)@@ -407,21 +424,19 @@  -- | `WebDriverT` test case with additional setup and teardown phases and a custom effect evaluator. Setup runs before the test (for logging in, say) and teardown runs after the test (for deleting temp files, say).  testCaseWithSetupTM-  :: (Monad eff, Monad (m eff), Typeable eff, Typeable m)+  :: (Monad eff, Monad (t eff), MonadTrans t, Typeable eff, Typeable t)   => TT.TestName   -> (forall a. P WDAct a -> eff a) -- ^ Evaluator-  -> (forall a. eff a -> m eff a) -- ^ Lift effects to the inner monad-  -> (forall a. m eff a -> IO a) -- ^ Conversion to `IO`.-  -> WebDriverT (m eff) u -- ^ Setup-  -> (v -> WebDriverT (m eff) ()) -- ^ Teardown-  -> (u -> WebDriverT (m eff) v) -- ^ Test+  -> (forall a. t eff a -> IO a) -- ^ Conversion to `IO`.+  -> WebDriverTT t eff u -- ^ Setup+  -> (v -> WebDriverTT t eff ()) -- ^ Teardown+  -> (u -> WebDriverTT t eff v) -- ^ Test   -> TT.TestTree-testCaseWithSetupTM name eval lift toIO setup teardown test =+testCaseWithSetupTM name eval toIO setup teardown test =   TT.singleTest name WebDriverTest     { wdTestName = name     , wdTestSession = setup >>= test >>= teardown     , wdEval = eval-    , wdLift = lift     , wdToIO = toIO     } @@ -469,6 +484,19 @@   +-- | Run in private mode.+newtype PrivateMode+  = PrivateMode { thePrivateMode :: Bool }+  deriving Typeable++instance TO.IsOption PrivateMode where+  defaultValue = PrivateMode True+  parseValue = fmap PrivateMode . TO.safeReadBool+  optionName = return _OPT_PRIVATE_MODE+  optionHelp = return "run in private mode: (false), true"+++ -- | Path where secrets are stored. newtype DataPath   = DataPath { theDataPath :: Maybe FilePath }@@ -543,7 +571,7 @@     "cr-2018-03-04" -> Just $ WebDriverApiVersion CR_2018_03_04     _ -> Nothing   optionName = return _OPT_API_VERSION-  optionHelp = return "WebDriver API version: (cr-2018-03-04)"+  optionHelp = return "WebDriverT API version: (cr-2018-03-04)"   @@ -745,7 +773,7 @@   --- | Run a tree of webdriver tests. Thin wrapper around tasty's @defaultMain@ that attempts to determine the deployment tier and interprets remote end config command line options.+-- | Run a tree of WebDriverT tests. Thin wrapper around tasty's @defaultMain@ that attempts to determine the deployment tier and interprets remote end config command line options. defaultWebDriverMain :: TT.TestTree -> IO () defaultWebDriverMain tree = do   logLock <- newMVar ()
src/Test/Tasty/WebDriver/Config.hs view
@@ -52,14 +52,17 @@   { freeRemoteEnds :: MS.Map DriverName [RemoteEnd]   } deriving (Eq, Show) +instance Semigroup RemoteEndPool where+  x <> y = RemoteEndPool+    { freeRemoteEnds = MS.unionWith (++) (freeRemoteEnds x) (freeRemoteEnds y)+    }+ instance Monoid RemoteEndPool where   mempty = RemoteEndPool     { freeRemoteEnds = MS.fromList []     } -  mappend x y = RemoteEndPool-    { freeRemoteEnds = MS.unionWith (++) (freeRemoteEnds x) (freeRemoteEnds y)-    }+  mappend = (<>)  -- | Push a remote end to the pool stack for a given driver. addRemoteEndForDriver :: DriverName -> RemoteEnd -> RemoteEndPool -> RemoteEndPool
src/Web/Api/WebDriver.hs view
@@ -10,6 +10,7 @@  module Web.Api.WebDriver (     module Web.Api.WebDriver.Assert+  , module Web.Api.WebDriver.Classes   , module Web.Api.WebDriver.Endpoints   , module Web.Api.WebDriver.Helpers   , module Web.Api.WebDriver.Monad@@ -19,6 +20,7 @@   ) where  import Web.Api.WebDriver.Assert+import Web.Api.WebDriver.Classes import Web.Api.WebDriver.Endpoints import Web.Api.WebDriver.Helpers import Web.Api.WebDriver.Monad
src/Web/Api/WebDriver/Assert.hs view
@@ -281,15 +281,18 @@   , successes :: [Assertion]   } deriving (Eq, Show) -instance Monoid AssertionSummary where-  mempty = AssertionSummary 0 0 [] []--  mappend x y = AssertionSummary+instance Semigroup AssertionSummary where+  x <> y = AssertionSummary     { numSuccesses = numSuccesses x + numSuccesses y     , numFailures = numFailures x + numFailures y     , failures = failures x ++ failures y     , successes = successes x ++ successes y     }++instance Monoid AssertionSummary where+  mempty = AssertionSummary 0 0 [] []++  mappend = (<>)  -- | Summarize a single assertion. summary :: Assertion -> AssertionSummary
src/Web/Api/WebDriver/Endpoints.hs view
@@ -159,6 +159,8 @@   , _WEB_FRAME_ID   ) where +import Control.Monad.Trans.Class+  ( MonadTrans(..) ) import Data.Aeson   ( Value(..), encode, object, (.=), toJSON ) import Data.Text@@ -192,7 +194,9 @@   -- | Url of the remote WebDriver server.-theRemoteUrl :: (Monad m) => WebDriverT m String+theRemoteUrl+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff String theRemoteUrl = do   host <- fromEnv (_remoteHostname . _env)   port <- fromEnv (_remotePort . _env)@@ -200,7 +204,7 @@   return $ concat [ "http://", host, ":", show port, path]  -- | Url of the remote WebDriver server, with session ID.-theRemoteUrlWithSession :: (Monad m) => WebDriverT m String+theRemoteUrlWithSession :: (Monad eff, Monad (t eff), MonadTrans t) => WebDriverTT t eff String theRemoteUrlWithSession = do   st <- fromState (_sessionId . _userState)   case st of@@ -218,18 +222,21 @@  -- | If a WebDriver session ends without issuing a delete session command, then the server keeps its session state alive. `cleanupOnError` catches errors and ensures that a `deleteSession` request is sent. cleanupOnError-  :: (Monad m)-  => WebDriverT m a -- ^ `WebDriver` session that may throw errors-  -> WebDriverT m a-cleanupOnError x = catchError x $ \e ->-  deleteSession >> throwError e+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff a -- ^ `WebDriver` session that may throw errors+  -> WebDriverTT t eff a+cleanupOnError x = catchAnyError x+  (\e -> deleteSession >> throwError e)+  (\e -> deleteSession >> throwHttpException e)+  (\e -> deleteSession >> throwIOException e)+  (\e -> deleteSession >> throwJsonError e)  -- | Run a WebDriver computation in an isolated browser session. Ensures that the session is closed on the remote end. runIsolated-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Capabilities-  -> WebDriverT m a-  -> WebDriverT m ()+  -> WebDriverTT t eff a+  -> WebDriverTT t eff () runIsolated caps theSession = cleanupOnError $ do   session_id <- newSession caps   modifyState $ setSessionId (Just session_id)@@ -243,18 +250,18 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#new-session>. For an extensible version allowing arbitrary changes to the JSON value representing the `Capabilities` parameter, see `newSession'`. newSession-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Capabilities-  -> WebDriverT m SessionId+  -> WebDriverTT t eff SessionId newSession = newSession' id   -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#new-session>. This generalizes `newSession'` by taking an additional function @Value -> Value@ that is applied to the `Capabilities` parameter after it is converted to JSON, but before it is passed to the HTTP call. newSession'-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => (Value -> Value)   -> Capabilities-  -> WebDriverT m SessionId+  -> WebDriverTT t eff SessionId newSession' f caps = do   baseUrl <- theRemoteUrl   format <- fromEnv (_responseFormat . _env)@@ -277,7 +284,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#delete-session>. deleteSession-  :: (Monad m) => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () deleteSession = do   (baseUrl, format) <- theRequestContext   httpDelete baseUrl@@ -290,8 +298,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#status>. sessionStatus-  :: (Monad m)-  => WebDriverT m (Bool, String)+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff (Bool, String) sessionStatus = do   baseUrl <- theRemoteUrl   format <- fromEnv (_responseFormat . _env)@@ -316,8 +324,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts>. getTimeouts-  :: (Monad m)-  => WebDriverT m TimeoutConfig+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff TimeoutConfig getTimeouts = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/timeouts")@@ -329,9 +337,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#set-timeouts>. setTimeouts-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => TimeoutConfig-  -> WebDriverT m ()+  -> WebDriverTT t eff () setTimeouts timeouts = do   baseUrl <- theRemoteUrlWithSession   let !payload = encode timeouts@@ -341,9 +349,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#navigate-to>. To access this enpoint without logging the request or the result, use `navigateToStealth`. navigateTo-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Url-  -> WebDriverT m ()+  -> WebDriverTT t eff () navigateTo url = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "url" .= url ]@@ -357,9 +365,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#navigate-to>. This function does not log the request or response; if you do want the interaction logged, use `navigateTo`. navigateToStealth-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Url-  -> WebDriverT m ()+  -> WebDriverTT t eff () navigateToStealth url = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "url" .= url ]@@ -373,8 +381,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-current-url>. getCurrentUrl-  :: (Monad m)-  => WebDriverT m Url+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff Url getCurrentUrl = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/url")@@ -387,8 +395,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#back>. goBack-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () goBack = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object []@@ -402,8 +410,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#forward>. goForward-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () goForward = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object []@@ -417,8 +425,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#refresh>. pageRefresh-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () pageRefresh = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object []@@ -432,8 +440,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-title>. getTitle-  :: (Monad m)-  => WebDriverT m String+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff String getTitle = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/title")@@ -446,8 +454,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-window-handle>. getWindowHandle-  :: (Monad m)-  => WebDriverT m ContextId+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff ContextId getWindowHandle = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/window")@@ -460,8 +468,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#close-window>. closeWindow-  :: (Monad m)-  => WebDriverT m [ContextId]+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff [ContextId] closeWindow = do   baseUrl <- theRemoteUrlWithSession   httpDelete (baseUrl ++ "/window")@@ -475,9 +483,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#switch-to-window>. switchToWindow-  :: (Monad m, HasContextId t)-  => t-  -> WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t, HasContextId a)+  => a+  -> WebDriverTT t eff () switchToWindow t = do   let contextId = contextIdOf t   baseUrl <- theRemoteUrlWithSession@@ -488,8 +496,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-window-handles>. getWindowHandles-  :: (Monad m)-  => WebDriverT m [ContextId]+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff [ContextId] getWindowHandles = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/window/handles")@@ -503,9 +511,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#switch-to-frame>. switchToFrame-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => FrameReference-  -> WebDriverT m ()+  -> WebDriverTT t eff () switchToFrame ref = do   (baseUrl, format) <- theRequestContext   let@@ -521,16 +529,14 @@     >>= (return . _responseBody)     >>= parseJson     >>= lookupKeyJson "value"-    >>= case format of-          SpecFormat -> expect (object [])-          ChromeFormat -> expect Null+    >>= expectEmptyObject format   return ()   -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#switch-to-parent-frame>. switchToParentFrame-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () switchToParentFrame = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object []@@ -544,8 +550,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect>. getWindowRect-  :: (Monad m)-  => WebDriverT m Rect+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff Rect getWindowRect = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/window/rect")@@ -557,9 +563,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect>. setWindowRect-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Rect-  -> WebDriverT m Rect+  -> WebDriverTT t eff Rect setWindowRect rect = do   baseUrl <- theRemoteUrlWithSession   let !payload = encode rect@@ -572,8 +578,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#maximize-window>. maximizeWindow-  :: (Monad m)-  => WebDriverT m Rect+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff Rect maximizeWindow = do   baseUrl <- theRemoteUrlWithSession   let !payload = encode $ object []@@ -586,8 +592,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#minimize-window>. minimizeWindow-  :: (Monad m)-  => WebDriverT m Rect+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff Rect minimizeWindow = do   baseUrl <- theRemoteUrlWithSession   let !payload = encode $ object []@@ -600,8 +606,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#fullscreen-window>. fullscreenWindow-  :: (Monad m)-  => WebDriverT m Rect+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff Rect fullscreenWindow = do   baseUrl <- theRemoteUrlWithSession   let !payload = encode $ object []@@ -614,10 +620,10 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-element>. findElement-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => LocationStrategy   -> Selector-  -> WebDriverT m ElementRef+  -> WebDriverTT t eff ElementRef findElement strategy selector = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "value" .= selector, "using" .= toJSON strategy ]@@ -634,10 +640,10 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-elements>. findElements-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => LocationStrategy   -> Selector-  -> WebDriverT m [ElementRef]+  -> WebDriverTT t eff [ElementRef] findElements strategy selector = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "value" .= selector, "using" .= toJSON strategy ]@@ -655,11 +661,11 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-element-from-element>. findElementFromElement-  :: (Monad m, HasElementRef t)+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)   => LocationStrategy   -> Selector-  -> t-  -> WebDriverT m ElementRef+  -> a+  -> WebDriverTT t eff ElementRef findElementFromElement strategy selector root = do   (baseUrl, format) <- theRequestContext   let root_id = elementRefOf root@@ -677,11 +683,11 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-elements-from-element>. findElementsFromElement-  :: (Monad m, HasElementRef t)+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)   => LocationStrategy   -> Selector-  -> t-  -> WebDriverT m [ElementRef]+  -> a+  -> WebDriverTT t eff [ElementRef] findElementsFromElement strategy selector root = do   (baseUrl, format) <- theRequestContext   let root_id = elementRefOf root@@ -700,8 +706,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-active-element>. getActiveElement-  :: (Monad m)-  => WebDriverT m ElementRef+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff ElementRef getActiveElement = do   (baseUrl, format) <- theRequestContext   httpGet (baseUrl ++ "/element/active")@@ -717,9 +723,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#is-element-selected>. isElementSelected-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m Bool+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff Bool isElementSelected element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession@@ -732,11 +738,11 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-attribute>. getElementAttribute-  :: (Monad m, HasElementRef t)-  => t-  -> AttributeName-  -> WebDriverT m (Either Bool String)-getElementAttribute element name = do+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => AttributeName+  -> a+  -> WebDriverTT t eff (Either Bool String)+getElementAttribute name element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession   x <- httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/attribute/" ++ E.encode name)@@ -752,11 +758,11 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-property>. getElementProperty-  :: (Monad m, HasElementRef t)-  => t-  -> PropertyName-  -> WebDriverT m Value-getElementProperty element name = do+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => PropertyName+  -> a+  -> WebDriverTT t eff Value+getElementProperty name element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/property/" ++ E.encode name)@@ -767,11 +773,11 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-css-value>. getElementCssValue-  :: (Monad m, HasElementRef t)-  => t-  -> CssPropertyName-  -> WebDriverT m String-getElementCssValue element name = do+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => CssPropertyName+  -> a+  -> WebDriverTT t eff String+getElementCssValue name element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/css/" ++ name)@@ -783,9 +789,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-text>. getElementText-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m String+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff String getElementText element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession@@ -798,9 +804,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-tag-name>. getElementTagName-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m String+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff String getElementTagName element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession@@ -813,9 +819,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-rect>. getElementRect-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m Rect+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff Rect getElementRect element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession@@ -828,9 +834,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#is-element-enabled>. isElementEnabled-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m Bool+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff Bool isElementEnabled element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession@@ -843,9 +849,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#element-click>. elementClick-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff () elementClick element = do   (baseUrl, format) <- theRequestContext   let elementRef = show $ elementRefOf element@@ -860,9 +866,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#element-clear>. elementClear-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff () elementClear element = do   (baseUrl, format) <- theRequestContext   let elementRef = show $ elementRefOf element@@ -877,26 +883,26 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#element-send-keys>. elementSendKeys-  :: (Monad m, HasElementRef t)-  => t-  -> String-  -> WebDriverT m ()-elementSendKeys element text = do+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => String+  -> a+  -> WebDriverTT t eff ()+elementSendKeys text element = do   let elementRef = show $ elementRefOf element-  baseUrl <- theRemoteUrlWithSession+  (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "text" .= text ]   httpPost (baseUrl ++ "/element/" ++ elementRef ++ "/value") payload     >>= (return . _responseBody)     >>= parseJson     >>= lookupKeyJson "value"-    >>= expect (object [])+    >>= expectEmptyObject format   return ()   -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-page-source>. getPageSource-  :: (Monad m)-  => WebDriverT m String+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff String getPageSource = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/source")@@ -909,8 +915,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-page-source>. Does not dump the page source into the logs. :) getPageSourceStealth-  :: (Monad m)-  => WebDriverT m String+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff String getPageSourceStealth = do   baseUrl <- theRemoteUrlWithSession   httpSilentGet (baseUrl ++ "/source")@@ -923,10 +929,10 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#execute-script>. executeScript-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Script   -> [Value]-  -> WebDriverT m Value+  -> WebDriverTT t eff Value executeScript script args = do   baseUrl <- theRemoteUrlWithSession   let !payload = encode $ object [ "script" .= script, "args" .= toJSON args ]@@ -938,10 +944,10 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#execute-async-script>. executeAsyncScript-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Script   -> [Value]-  -> WebDriverT m Value+  -> WebDriverTT t eff Value executeAsyncScript script args = do   baseUrl <- theRemoteUrlWithSession   let !payload = encode $ object [ "script" .= script, "args" .= toJSON args ]@@ -953,8 +959,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-all-cookies>. getAllCookies-  :: (Monad m)-  => WebDriverT m [Cookie]+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff [Cookie] getAllCookies = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/cookie")@@ -967,9 +973,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-named-cookie>. getNamedCookie-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => CookieName-  -> WebDriverT m Cookie+  -> WebDriverTT t eff Cookie getNamedCookie name = do   baseUrl <- theRemoteUrlWithSession   httpGet (baseUrl ++ "/cookie/" ++ E.encode name)@@ -981,9 +987,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie>. addCookie-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Cookie-  -> WebDriverT m ()+  -> WebDriverTT t eff () addCookie cookie = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "cookie" .= cookie ]@@ -997,9 +1003,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#delete-cookie>. deleteCookie-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => CookieName-  -> WebDriverT m ()+  -> WebDriverTT t eff () deleteCookie name = do   (baseUrl, format) <- theRequestContext   httpDelete (baseUrl ++ "/cookie/" ++ E.encode name)@@ -1012,8 +1018,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#delete-all-cookies>. deleteAllCookies-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () deleteAllCookies = do   (baseUrl, format) <- theRequestContext   httpDelete (baseUrl ++ "/cookie")@@ -1026,41 +1032,41 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#perform-actions>. For a variant on this endpoint that does not log the request and response, see `performActionsStealth`. performActions-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => [Action]-  -> WebDriverT m ()+  -> WebDriverTT t eff () performActions = _performActions False   -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#perform-actions>. This function is identical to `performActions` except that it does not log the request or response. Handy if the action includes secret info. performActionsStealth-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => [Action]-  -> WebDriverT m ()+  -> WebDriverTT t eff () performActionsStealth = _performActions True   _performActions-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => Bool   -> [Action]-  -> WebDriverT m ()+  -> WebDriverTT t eff () _performActions stealth action = do-  baseUrl <- theRemoteUrlWithSession+  (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "actions" .= toJSON action ]   let httpMethod = if stealth then httpSilentPost else httpPost   httpMethod (baseUrl ++ "/actions") payload     >>= (return . _responseBody)     >>= parseJson     >>= lookupKeyJson "value"-    >>= expect (object [])+    >>= expectEmptyObject format   return ()   -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#release-actions>. releaseActions-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () releaseActions = do   baseUrl <- theRemoteUrlWithSession   httpDelete (baseUrl ++ "/actions")@@ -1069,8 +1075,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#dismiss-alert>. dismissAlert-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () dismissAlert = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object []@@ -1084,8 +1090,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#accept-alert>. acceptAlert-  :: (Monad m)-  => WebDriverT m ()+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff () acceptAlert = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object []@@ -1099,8 +1105,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-alert-text>. getAlertText-  :: (Monad m)-  => WebDriverT m (Maybe String)+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff (Maybe String) getAlertText = do   baseUrl <- theRemoteUrlWithSession   msg <- httpGet (baseUrl ++ "/alert/text")@@ -1115,9 +1121,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text>. sendAlertText-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => String-  -> WebDriverT m ()+  -> WebDriverTT t eff () sendAlertText msg = do   (baseUrl, format) <- theRequestContext   let !payload = encode $ object [ "text" .= msg ]@@ -1131,8 +1137,8 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#take-screenshot>. takeScreenshot-  :: (Monad m)-  => WebDriverT m SB.ByteString+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff SB.ByteString takeScreenshot = do   baseUrl <- theRemoteUrlWithSession   result <- httpGet (baseUrl ++ "/screenshot")@@ -1148,9 +1154,9 @@  -- | See <https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot>. takeElementScreenshot-  :: (Monad m, HasElementRef t)-  => t-  -> WebDriverT m SB.ByteString+  :: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)+  => a+  -> WebDriverTT t eff SB.ByteString takeElementScreenshot element = do   let elementRef = show $ elementRefOf element   baseUrl <- theRemoteUrlWithSession@@ -1167,18 +1173,18 @@  -- | Detect empty responses by response format. Necessary because chromedriver is not strictly spec compliant. expectEmptyObject-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => ResponseFormat   -> Value-  -> WebDriverT m Value+  -> WebDriverTT t eff Value expectEmptyObject format value = case format of-  SpecFormat -> expect (object []) value+  SpecFormat -> expectIs (\x -> elem x [Null, object []]) "empty object or null" value   ChromeFormat -> expect Null value   theRequestContext-  :: (Monad m)-  => WebDriverT m (String, ResponseFormat)+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff (String, ResponseFormat) theRequestContext = do   baseUrl <- theRemoteUrlWithSession   format <- fromEnv (_responseFormat . _env)
src/Web/Api/WebDriver/Helpers.hs view
@@ -9,8 +9,14 @@ -}  module Web.Api.WebDriver.Helpers (+  -- * Data+    writeDataFile+  , readDataFile+  , writeJsonFile+  , readJsonFile+   -- * Secrets-    stashCookies+  , stashCookies   , loadCookies    -- * Actions@@ -18,8 +24,12 @@   , typeString   ) where +import Control.Monad.Trans.Class+  ( MonadTrans(..) ) import qualified Data.Aeson as Aeson-  ( encode )+  ( encode, ToJSON(..), Value )+import Data.ByteString.Lazy+  ( ByteString ) import qualified Data.ByteString.Lazy.Char8 as BS   ( pack ) import qualified Data.Digest.Pure.SHA as SHA@@ -36,9 +46,9 @@  -- | Save all cookies for the current domain to a file. stashCookies-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => String -- ^ Passed through SHA1, and the digest is used as the filename.-  -> WebDriverT m ()+  -> WebDriverTT t eff () stashCookies string =   let file = SHA.showDigest $ SHA.sha1 $ BS.pack string in   getAllCookies >>= writeCookieFile file@@ -46,9 +56,9 @@  -- | Load cookies from a file saved with `stashCookies`. Returns `False` if the cookie file is missing or cannot be read. loadCookies-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => String -- ^ Passed through SHA1, and the digest is used as the filename.-  -> WebDriverT m Bool+  -> WebDriverTT t eff Bool loadCookies string = do   let file = SHA.showDigest $ SHA.sha1 $ BS.pack string   contents <- readCookieFile file@@ -61,10 +71,10 @@  -- | Write cookies to a file under the secrets path.  writeCookieFile-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => FilePath -- ^ File path; relative to @$DATA_PATH\/secrets\/cookies\/@   -> [Cookie]-  -> WebDriverT m ()+  -> WebDriverTT t eff () writeCookieFile file cookies = do   path <- fromEnv (_dataPath . _env)   let fullpath = path ++ "/secrets/cookies/" ++ file@@ -73,9 +83,9 @@  -- | Read cookies from a file stored with `writeCookieFile`. Returns `Nothing` if the file does not exist. readCookieFile-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => FilePath -- ^ File path; relative to @$DATA_PATH\/secrets\/cookies\/@-  -> WebDriverT m (Maybe [Cookie])+  -> WebDriverTT t eff (Maybe [Cookie]) readCookieFile file = do   path <- fromEnv (_dataPath . _env)   let fullpath = path ++ "/secrets/cookies/" ++ file@@ -87,6 +97,49 @@       >>= mapM constructFromJson       >>= (return . Just)     else return Nothing++++-- | Write a `ByteString` to the data directory+writeDataFile+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => FilePath -- ^ File path, relative to @$DATA_PATH@, with leading slash+  -> ByteString+  -> WebDriverTT t eff ()+writeDataFile file contents = do+  path <- fromEnv (_dataPath . _env)+  writeFilePath (path ++ file) contents++-- | Read a `ByteString` from the data directory+readDataFile+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => FilePath -- ^ File path, relative to @$DATA_PATH@, with leading slash+  -> WebDriverTT t eff ByteString+readDataFile file = do+  path <- fromEnv (_dataPath . _env)+  readFilePath $ path ++ file++++-- | Write JSON to the data directory+writeJsonFile+  :: (Monad eff, Monad (t eff), MonadTrans t, Aeson.ToJSON a)+  => FilePath -- ^ File path, relative to @$DATA_PATH@, with leading slash+  -> a+  -> WebDriverTT t eff ()+writeJsonFile file a = do+  path <- fromEnv (_dataPath . _env)+  writeFilePath (path ++ file) (Aeson.encode $ Aeson.toJSON a)++-- | Read a JSON `Value` from the data directory+readJsonFile+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => FilePath -- ^ File path, relative to @$DATA_PATH@, with leading slash+  -> WebDriverTT t eff Aeson.Value+readJsonFile file = do+  path <- fromEnv (_dataPath . _env)+  readFilePath (path ++ file) >>= parseJson+   -- | `KeyDownAction` with the given `Char`.
src/Web/Api/WebDriver/Monad.hs view
@@ -7,23 +7,30 @@ Stability   : experimental Portability : POSIX -A monad and monad transformer for +A monad transformer for building WebDriver sessions. -} -{-# LANGUAGE GADTs, Rank2Types, OverloadedStrings #-}-module Web.Api.WebDriver.Monad (-    WebDriver-  , execWebDriver-  , debugWebDriver-  , checkWebDriver+{-#+  LANGUAGE+    GADTs,+    Rank2Types,+    KindSignatures,+    RecordWildCards,+    OverloadedStrings+#-} -  , WebDriverT()+module Web.Api.WebDriver.Monad (+    WebDriverT   , execWebDriverT   , debugWebDriverT   , checkWebDriverT-  , liftWebDriverT-  , IdentityT(..) +  , WebDriverTT()+  , execWebDriverTT+  , debugWebDriverTT+  , checkWebDriverTT+  , liftWebDriverTT+   , evalWDAct   , Http.evalIO   , evalWDActMockIO@@ -43,16 +50,20 @@   , fromEnv   , comment   , wait+  , logDebug+  , logNotice   , throwError   , throwJsonError   , throwHttpException   , throwIOException   , expect+  , expectIs   , assert   , catchError   , catchJsonError   , catchHttpException   , catchIOException+  , catchAnyError   , parseJson   , lookupKeyJson   , constructFromJson@@ -64,9 +75,16 @@   , httpSilentDelete   , hPutStrLn   , hPutStrLnBlocking+  , getStrLn+  , promptForString+  , promptForSecret   , readFilePath   , writeFilePath   , fileExists+  , breakpointsOn+  , breakpointsOff+  , breakpoint+  , breakpointWith    -- * Types   , Http.E(..)@@ -85,15 +103,18 @@   , WDAct(..)   , Http.S(..)   , WDState(..)+  , BreakpointSetting(..)    -- * Logs   , getAssertions   , Http.logEntries+  , Http.printHttpLogs+  , Http.basicLogEntryPrinter ) where   -import Prelude hiding (readFile, writeFile)+import Prelude hiding (readFile, writeFile, putStrLn)  import Control.Concurrent.MVar   ( MVar )@@ -103,6 +124,10 @@   ( (^.), (^?) ) import Control.Monad   ( ap )+import Control.Monad.Trans.Class+  ( MonadTrans(..) )+import Control.Monad.Trans.Identity+  ( IdentityT(..) ) import Data.Aeson   ( Value(), Result(Success), toJSON, (.=), FromJSON, fromJSON, object ) import Data.Aeson.Encode.Pretty@@ -119,6 +144,8 @@   ( Identity(..) ) import Data.IORef   ( IORef, newIORef, readIORef, writeIORef )+import Data.List+  ( intercalate ) import qualified Data.Map.Strict as M   ( Map, fromList ) import Data.Text@@ -147,24 +174,36 @@   --- | Wrapper type around `Http.HttpT`; a stack of error, reader, writer, state, and prompt monads.-newtype WebDriverT m a = WDT-  { unWDT :: Http.HttpT WDError WDEnv WDLog WDState WDAct m a }+-- | Wrapper type around `Http.HttpTT`; a stack of error, reader, writer, state, and prompt monad transformers.+newtype WebDriverTT+  (t :: (* -> *) -> * -> *)+  (eff :: * -> *)+  (a :: *)+  = WDT+    { unWDT :: Http.HttpTT WDError WDEnv WDLog WDState WDAct t eff a } -instance Functor (WebDriverT m) where+instance+  (Monad eff, Monad (t eff), MonadTrans t)+    => Functor (WebDriverTT t eff) where   fmap f = WDT . fmap f . unWDT -instance Applicative (WebDriverT m) where+instance+  (Monad eff, Monad (t eff), MonadTrans t)+    => Applicative (WebDriverTT t eff) where   pure = return   (<*>) = ap -instance Monad (WebDriverT m) where+instance+  (Monad eff, Monad (t eff), MonadTrans t)+    => Monad (WebDriverTT t eff) where   return = WDT . return   (WDT x) >>= f = WDT (x >>= (unWDT . f)) --- | Lift a value from the inner monad-liftWebDriverT :: (Monad m) => m a -> WebDriverT m a-liftWebDriverT = WDT . Http.liftHttpT+-- | Lift a value from the inner transformed monad+liftWebDriverTT+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => t eff a -> WebDriverTT t eff a+liftWebDriverTT = WDT . Http.liftHttpTT  -- | Type representing configuration settings for a WebDriver session data WebDriverConfig eff = WDConfig@@ -187,6 +226,7 @@   , Http._httpSession = Nothing   , Http._userState = WDState     { _sessionId = Nothing+    , _breakpoints = BreakpointsOff     }   } @@ -194,6 +234,7 @@ defaultWebDriverEnvironment = Http.R   { Http._logHandle = stdout   , Http._logLock = Nothing+  , Http._logEntryPrinter = Http.basicLogEntryPrinter   , Http._uid = ""   , Http._logOptions = defaultWebDriverLogOptions   , Http._httpErrorInject = promoteHttpResponseError@@ -226,210 +267,273 @@   --- | Execute a `WebDriverT` session.-execWebDriverT-  :: (Monad eff, Monad (m eff))+-- | Execute a `WebDriverTT` session.+execWebDriverTT+  :: (Monad eff, Monad (t eff), MonadTrans t)   => WebDriverConfig eff-  -> (forall u. eff u -> m eff u) -- ^ Lift effects to the inner monad-  -> WebDriverT (m eff) a-  -> m eff (Either (Http.E WDError) a, Http.S WDState, Http.W WDError WDLog)-execWebDriverT config lift = Http.execHttpTM-  (_initialState config) (_environment config) (_evaluator config) lift . unWDT+  -> WebDriverTT t eff a+  -> t eff (Either (Http.E WDError) a, Http.S WDState, Http.W WDError WDLog)+execWebDriverTT config = Http.execHttpTT+  (_initialState config) (_environment config) (_evaluator config) . unWDT --- | Execute a `WebDriverT` session, returning an assertion summary with the result.-debugWebDriverT-  :: (Monad eff, Monad (m eff))+-- | Execute a `WebDriverTT` session, returning an assertion summary with the result.+debugWebDriverTT+  :: (Monad eff, Monad (t eff), MonadTrans t)   => WebDriverConfig eff-  -> (forall u. eff u -> m eff u) -- ^ Lift effects to the inner monad-  -> WebDriverT (m eff) a-  -> m eff (Either String a, AssertionSummary)-debugWebDriverT config lift session = do-  (result, _, w) <- execWebDriverT config lift session+  -> WebDriverTT t eff a+  -> t eff (Either String a, AssertionSummary)+debugWebDriverTT config session = do+  (result, _, w) <- execWebDriverTT config session   let output = case result of         Right a -> Right a         Left e -> Left $ Http.printError (printWDError True) e   return (output, summarize $ getAssertions $ Http.logEntries w)  -- | For testing with QuickCheck.-checkWebDriverT-  :: (Monad eff, Monad (m eff))+checkWebDriverTT+  :: (Monad eff, Monad (t eff), MonadTrans t, Show q)   => WebDriverConfig eff-  -> (forall u. eff u -> m eff u) -- ^ Lift effects to the inner monad-  -> (m eff (Either (Http.E WDError) t, Http.S WDState, Http.W WDError WDLog) -> IO q) -- ^ Condense to `IO`+  -> (t eff (Either (Http.E WDError) a, Http.S WDState, Http.W WDError WDLog) -> IO q) -- ^ Condense to `IO`   -> (q -> Bool) -- ^ Result check-  -> WebDriverT (m eff) t+  -> WebDriverTT t eff a   -> Property-checkWebDriverT config lift cond check =-  Http.checkHttpTM+checkWebDriverTT config cond check =+  Http.checkHttpTT     (_initialState config)     (_environment config)     (_evaluator config)-    lift cond check . unWDT+    cond check . unWDT     --- | `WebDriverT` over `IdentityT`.-type WebDriver eff a = WebDriverT (IdentityT eff) a---- | The identity monad transformer.-newtype IdentityT m a = IdentityT { runIdentityT :: m a }--instance (Monad m) => Monad (IdentityT m) where-  return = IdentityT . return-  x >>= f = IdentityT $ runIdentityT x >>= (runIdentityT . f)--instance (Functor m) => Functor (IdentityT m) where-  fmap f = IdentityT . fmap f . runIdentityT--instance (Monad m) => Applicative (IdentityT m) where-  pure = return-  (<*>) = ap+-- | `WebDriverTT` over `IdentityT`.+type WebDriverT eff a = WebDriverTT IdentityT eff a   --- | Execute a `WebDriver` session.-execWebDriver+-- | Execute a `WebDriverT` session.+execWebDriverT   :: (Monad eff)   => WebDriverConfig eff-  -> WebDriver eff a+  -> WebDriverT eff a   -> eff (Either (Http.E WDError) a, Http.S WDState, Http.W WDError WDLog)-execWebDriver config = runIdentityT . execWebDriverT config IdentityT+execWebDriverT config = runIdentityT . execWebDriverTT config --- | Execute a `WebDriver` session, returning an assertion summary with the result.-debugWebDriver+-- | Execute a `WebDriverT` session, returning an assertion summary with the result.+debugWebDriverT   :: (Monad eff)   => WebDriverConfig eff-  -> WebDriver eff a+  -> WebDriverT eff a   -> eff (Either String a, AssertionSummary)-debugWebDriver config session = do-  (result, _, w) <- execWebDriver config session+debugWebDriverT config session = do+  (result, _, w) <- execWebDriverT config session   let output = case result of         Right a -> Right a         Left e -> Left $ Http.printError (printWDError True) e   return (output, summarize $ getAssertions $ Http.logEntries w)  -- | For testing with QuickCheck-checkWebDriver-  :: (Monad eff)+checkWebDriverT+  :: (Monad eff, Show q)   => WebDriverConfig eff   -> (eff (Either (Http.E WDError) t, Http.S WDState, Http.W WDError WDLog) -> IO q) -- ^ Condense to `IO`   -> (q -> Bool) -- ^ Result check-  -> WebDriver eff t+  -> WebDriverT eff t   -> Property-checkWebDriver config cond = checkWebDriverT config IdentityT (cond . runIdentityT)+checkWebDriverT config cond = checkWebDriverTT config (cond . runIdentityT)    -- | Get a computed value from the state-fromState :: (Http.S WDState -> a) -> WebDriverT m a+fromState+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => (Http.S WDState -> a) -> WebDriverTT t eff a fromState = WDT . Http.gets  -- | Mutate the state-modifyState :: (Http.S WDState -> Http.S WDState) -> WebDriverT m ()+modifyState+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => (Http.S WDState -> Http.S WDState) -> WebDriverTT t eff () modifyState = WDT . Http.modify  -- | Get a computed value from the environment-fromEnv :: (Http.R WDError WDLog WDEnv -> a) -> WebDriverT m a+fromEnv+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => (Http.R WDError WDLog WDEnv -> a) -> WebDriverTT t eff a fromEnv = WDT . Http.reader -logEntry :: WDLog -> WebDriverT m ()-logEntry = WDT . Http.logEntry+logDebug+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WDLog -> WebDriverTT t eff ()+logDebug = WDT . Http.logDebug +logNotice+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WDLog -> WebDriverTT t eff ()+logNotice = WDT . Http.logNotice+ -- | Write a comment to the log.-comment :: String -> WebDriverT m ()+comment+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => String -> WebDriverTT t eff () comment = WDT . Http.comment --- | In milliseconds-wait :: Int -> WebDriverT m ()+-- | Suspend the current session. Handy when waiting for pages to load.+wait+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Int -- ^ Wait time in milliseconds+  -> WebDriverTT t eff () wait = WDT . Http.wait -throwError :: WDError -> WebDriverT m a+throwError+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WDError -> WebDriverTT t eff a throwError = WDT . Http.throwError -throwJsonError :: Http.JsonError -> WebDriverT m a+throwJsonError+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Http.JsonError -> WebDriverTT t eff a throwJsonError = WDT . Http.throwJsonError -throwHttpException :: N.HttpException -> WebDriverT m a+throwHttpException+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => N.HttpException -> WebDriverTT t eff a throwHttpException = WDT . Http.throwHttpException -throwIOException :: IOException -> WebDriverT m a+throwIOException+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => IOException -> WebDriverTT t eff a throwIOException = WDT . Http.throwIOException +-- | Explicitly handle any of the error types thrown in `WebDriverTT`+catchAnyError+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff a+  -> (WDError -> WebDriverTT t eff a)+  -> (N.HttpException -> WebDriverTT t eff a)+  -> (IOException -> WebDriverTT t eff a)+  -> (Http.JsonError -> WebDriverTT t eff a)+  -> WebDriverTT t eff a+catchAnyError x hE hH hI hJ = WDT $ Http.catchAnyError (unWDT x)+  (unWDT . hE) (unWDT . hH) (unWDT . hI) (unWDT . hJ)+ -- | Rethrows other error types-catchError :: WebDriverT m a -> (WDError -> WebDriverT m a) -> WebDriverT m a+catchError+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff a+  -> (WDError -> WebDriverTT t eff a)+  -> WebDriverTT t eff a catchError x h = WDT $ Http.catchError (unWDT x) (unWDT . h)  -- | Rethrows other error types-catchJsonError :: WebDriverT m a -> (Http.JsonError -> WebDriverT m a) -> WebDriverT m a+catchJsonError+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff a+  -> (Http.JsonError -> WebDriverTT t eff a)+  -> WebDriverTT t eff a catchJsonError x h = WDT $ Http.catchJsonError (unWDT x) (unWDT . h)  -- | Rethrows other error types-catchHttpException :: WebDriverT m a -> (N.HttpException -> WebDriverT m a) -> WebDriverT m a+catchHttpException+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff a+  -> (N.HttpException -> WebDriverTT t eff a)+  -> WebDriverTT t eff a catchHttpException x h = WDT $ Http.catchHttpException (unWDT x) (unWDT . h)  -- | Rethrows other error types-catchIOException :: WebDriverT m a -> (IOException -> WebDriverT m a) -> WebDriverT m a+catchIOException+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff a+  -> (IOException -> WebDriverTT t eff a)+  -> WebDriverTT t eff a catchIOException x h = WDT $ Http.catchIOException (unWDT x) (unWDT . h)  -- | May throw a `JsonError`.-parseJson :: ByteString -> WebDriverT m Value+parseJson+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => ByteString -> WebDriverTT t eff Value parseJson = WDT . Http.parseJson  -- | May throw a `JsonError`.-lookupKeyJson :: Text -> Value -> WebDriverT m Value+lookupKeyJson+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Text -> Value -> WebDriverTT t eff Value lookupKeyJson key = WDT . Http.lookupKeyJson key  -- | May throw a `JsonError`.-constructFromJson :: (FromJSON a) => Value -> WebDriverT m a+constructFromJson+  :: (Monad eff, Monad (t eff), MonadTrans t, FromJSON a)+  => Value -> WebDriverTT t eff a constructFromJson = WDT . Http.constructFromJson  -- | Capures `HttpException`s.-httpGet :: Http.Url -> WebDriverT m Http.HttpResponse+httpGet+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Http.Url -> WebDriverTT t eff Http.HttpResponse httpGet = WDT . Http.httpGet  -- | Does not write request or response info to the log, except to note that a request occurred. Capures `HttpException`s.-httpSilentGet :: Http.Url -> WebDriverT m Http.HttpResponse+httpSilentGet+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Http.Url -> WebDriverTT t eff Http.HttpResponse httpSilentGet = WDT . Http.httpSilentGet  -- | Capures `HttpException`s.-httpPost :: Http.Url -> ByteString -> WebDriverT m Http.HttpResponse+httpPost+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Http.Url -> ByteString -> WebDriverTT t eff Http.HttpResponse httpPost url = WDT . Http.httpPost url  -- | Does not write request or response info to the log, except to note that a request occurred. Capures `HttpException`s.-httpSilentPost :: Http.Url -> ByteString -> WebDriverT m Http.HttpResponse+httpSilentPost+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Http.Url -> ByteString -> WebDriverTT t eff Http.HttpResponse httpSilentPost url = WDT . Http.httpSilentPost url  -- | Capures `HttpException`s.-httpDelete :: Http.Url -> WebDriverT m Http.HttpResponse+httpDelete+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Http.Url -> WebDriverTT t eff Http.HttpResponse httpDelete = WDT . Http.httpDelete  -- | Does not write request or response info to the log, except to note that a request occurred. Capures `HttpException`s.-httpSilentDelete :: Http.Url -> WebDriverT m Http.HttpResponse+httpSilentDelete+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Http.Url -> WebDriverTT t eff Http.HttpResponse httpSilentDelete = WDT . Http.httpSilentDelete  -- | Capures `IOException`s.-hPutStrLn :: Handle -> String -> WebDriverT m ()+hPutStrLn+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => Handle -> String -> WebDriverTT t eff () hPutStrLn h = WDT . Http.hPutStrLn h  -- | Capures `IOException`s.-hPutStrLnBlocking :: MVar () -> Handle -> String -> WebDriverT m ()+hPutStrLnBlocking+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => MVar () -> Handle -> String -> WebDriverTT t eff () hPutStrLnBlocking lock h = WDT . Http.hPutStrLnBlocking lock h -promptWDAct :: WDAct a -> WebDriverT m a+promptWDAct+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WDAct a -> WebDriverTT t eff a promptWDAct = WDT . Http.prompt . Http.P   -instance Assert (WebDriverT m) where-  assert = logEntry . LogAssertion+instance+  (Monad eff, Monad (t eff), MonadTrans t)+    => Assert (WebDriverTT t eff) where+  assert = logNotice . LogAssertion     --- | Filter the assertions from a `WebDriver` log.+-- | Filter the assertions from a WebDriver log. getAssertions :: [WDLog] -> [Assertion] getAssertions xs = get xs   where@@ -453,6 +557,7 @@   | ImageDecodeError String   | UnexpectedValue String   | UnexpectedResult Outcome String+  | BreakpointHaltError   deriving Show  -- | Read-only environment variables specific to WebDriver.@@ -490,16 +595,41 @@   | ChromeFormat -- ^ Responses as emitted by chromedriver.   deriving (Eq, Show) +data BreakpointSetting+  = BreakpointsOn+  | BreakpointsOff+  deriving (Eq, Show)+ -- | Includes a @Maybe String@ representing the current session ID, if one has been opened.-newtype WDState = WDState+data WDState = WDState   { _sessionId :: Maybe String+  , _breakpoints :: BreakpointSetting   } deriving Show +breakpointsOn+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff ()+breakpointsOn = modifyState $ \st -> st+  { Http._userState = (Http._userState st)+    { _breakpoints = BreakpointsOn+    }+  }++breakpointsOff+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff ()+breakpointsOff = modifyState $ \st -> st+  { Http._userState = (Http._userState st)+    { _breakpoints = BreakpointsOff+    }+  }+ -- | WebDriver specific log entries. data WDLog   = LogAssertion Assertion   | LogSession SessionVerb   | LogUnexpectedResult Outcome String+  | LogBreakpoint String   deriving Show  -- | Pretty printer for log entries.@@ -528,15 +658,27 @@  -- | For validating responses. Throws an `UnexpectedValue` error if the two arguments are not equal according to their `Eq` instance. expect-  :: (Monad m, Eq a, Show a)+  :: (Monad eff, Monad (t eff), MonadTrans t, Eq a, Show a)   => a   -> a-  -> WebDriverT m a+  -> WebDriverTT t eff a expect x y = if x == y   then return y   else throwError $ UnexpectedValue $     "expected " ++ show x ++ " but got " ++ show y +-- | For validating responses. Throws an `UnexpectedValue` error if the `a` argument does not satisfy the predicate.+expectIs+  :: (Monad eff, Monad (t eff), MonadTrans t, Show a)+  => (a -> Bool)+  -> String -- ^ Human readable error label+  -> a+  -> WebDriverTT t eff a+expectIs p label x = if p x+  then return x+  else throwError $ UnexpectedValue $+    "expected " ++ label ++ " but got " ++ show x+ -- | Promote semantic HTTP exceptions to typed errors. promoteHttpResponseError :: N.HttpException -> Maybe WDError promoteHttpResponseError e = case e of@@ -581,26 +723,38 @@   UnexpectedResult r msg -> case r of     IsSuccess -> "Unexpected success: " ++ msg     IsFailure -> "Unexpected failure: " ++ msg+  BreakpointHaltError -> "Breakpoint Halt" --- | Prompt for input on `stdin`.-promptForString-  :: (Monad m)-  => String -- ^ Prompt text-  -> WebDriverT m String-promptForString prompt = do+putStrLn+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => String -> WebDriverTT t eff ()+putStrLn str = do   outH <- fromEnv (_stdout . Http._env)+  hPutStrLn outH str++getStrLn+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => WebDriverTT t eff String+getStrLn = do   inH <- fromEnv (_stdin . Http._env)-  hPutStrLn outH prompt   result <- promptWDAct $ HGetLine inH   case result of     Right string -> return string     Left e -> throwIOException e +-- | Prompt for input on `stdin`.+promptForString+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => String -- ^ Prompt text+  -> WebDriverTT t eff String+promptForString prompt =+  putStrLn prompt >> getStrLn+ -- | Prompt for input on `stdin`, but do not echo the typed characters back to the terminal -- handy for getting suuper secret info. promptForSecret-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => String -- ^ Prompt text-  -> WebDriverT m String+  -> WebDriverTT t eff String promptForSecret prompt = do   outH <- fromEnv (_stdout . Http._env)   inH <- fromEnv (_stdin . Http._env)@@ -612,9 +766,9 @@  -- | Captures `IOException`s readFilePath-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => FilePath-  -> WebDriverT m ByteString+  -> WebDriverTT t eff ByteString readFilePath path = do   result <- promptWDAct $ ReadFilePath path   case result of@@ -623,10 +777,10 @@  -- | Captures `IOException`s writeFilePath-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => FilePath   -> ByteString-  -> WebDriverT m ()+  -> WebDriverTT t eff () writeFilePath path bytes = do   result <- promptWDAct $ WriteFilePath path bytes   case result of@@ -635,9 +789,9 @@  -- | Captures `IOException`s fileExists-  :: (Monad m)+  :: (Monad eff, Monad (t eff), MonadTrans t)   => FilePath-  -> WebDriverT m Bool+  -> WebDriverTT t eff Bool fileExists path = do   result <- promptWDAct $ FileExists path   case result of@@ -646,6 +800,94 @@   +data BreakpointAction+  = BreakpointContinue+  | BreakpointHalt+  | BreakpointDump -- ^ Show the current state and environment+  | BreakpointSilence -- ^ Turn breakpoints off and continue+  | BreakpointAct -- ^ Client-supplied action+  deriving (Eq, Show)++parseBreakpointAction :: String -> Maybe BreakpointAction+parseBreakpointAction str = case str of+  "c" -> Just BreakpointContinue+  "h" -> Just BreakpointHalt+  "d" -> Just BreakpointDump+  "s" -> Just BreakpointSilence+  "a" -> Just BreakpointAct+  _ -> Nothing++breakpointMessage+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => String -> Maybe String -> WebDriverTT t eff ()+breakpointMessage msg custom = do+  putStrLn "=== BREAKPOINT ==="+  putStrLn msg+  putStrLn "c : continue"+  putStrLn "h : halt"+  putStrLn "d : dump webdriver state"+  putStrLn "s : turn breakpoints off and continue"+  case custom of+    Just act -> putStrLn $ "a : " ++ act+    Nothing -> return ()+  putStrLn "=================="++breakpointWith+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => String+  -> Maybe (String, WebDriverTT t eff ())+  -> WebDriverTT t eff ()+breakpointWith msg act = do+  bp <- fromState (_breakpoints . Http._userState)+  case bp of+    BreakpointsOff -> return ()+    BreakpointsOn -> do+      logNotice $ LogBreakpoint msg+      let+        (actionDescription, action) = case act of+          Nothing -> (Nothing, return ())+          Just (title, action) -> (Just title, action)+      breakpointMessage msg actionDescription+      command <- getStrLn+      case parseBreakpointAction command of+        Just BreakpointContinue -> return ()+        Just BreakpointHalt -> throwError BreakpointHaltError+        Just BreakpointDump -> do+          putStrLn "=== DUMP ========="+          fromState dumpState >>= putStrLn+          fromEnv dumpEnv >>= putStrLn+          putStrLn "=================="+          breakpointWith msg act+        Just BreakpointSilence -> breakpointsOff+        Just BreakpointAct -> action+        Nothing -> do+          putStrLn $ "Unrecognized breakpoint option '" ++ command ++ "'"+          breakpointWith msg act++breakpoint+  :: (Monad eff, Monad (t eff), MonadTrans t)+  => String+  -> WebDriverTT t eff ()+breakpoint msg = breakpointWith msg Nothing++dumpState :: Http.S WDState -> String+dumpState Http.S{..} = intercalate "\n"+  [ "Session ID: " ++ show (_sessionId _userState)+  , show (_breakpoints _userState)+  ]++dumpEnv :: Http.R WDError WDLog WDEnv -> String+dumpEnv Http.R{..} = intercalate "\n"+  [ "Remote Host: " ++ (_remoteHostname _env)+  , "Remote Port: " ++ show (_remotePort _env)+  , "Remote Path: " ++ (_remotePath _env)+  , "Data Path: " ++ (_dataPath _env)+  , "Response Format: " ++ show (_responseFormat _env)+  , "API Version: " ++ show (_apiVersion _env)+  ]+++ -- | Standard `IO` evaluator for `WDAct`. evalWDAct :: WDAct a -> IO a evalWDAct act = case act of@@ -654,11 +896,9 @@   FileExists path -> try $ doesFileExist path    HGetLine handle -> try $ do-    hFlush handle     hGetLine handle    HGetLineNoEcho handle -> try $ do-    hFlush handle     echo <- hGetEcho handle     hSetEcho handle False     secret <- hGetLine handle
src/Web/Api/WebDriver/Types.hs view
@@ -82,6 +82,8 @@   ( Scientific, scientific ) import Data.String   ( IsString(..) )+import Data.HashMap.Strict+  ( HashMap, toList, fromList ) import Data.Aeson.Types   ( ToJSON(..), FromJSON(..), Value(..), KeyValue   , Pair, (.:?), (.:), (.=), object, typeMismatch )@@ -453,30 +455,35 @@ data ChromeOptions = ChromeOptions   { _chromeBinary :: Maybe FilePath -- ^ @binary@   , _chromeArgs :: Maybe [String] -- ^ @args@+  , _chromePrefs :: Maybe (HashMap Text Value) -- ^ @prefs@   } deriving (Eq, Show)  instance FromJSON ChromeOptions where   parseJSON (Object v) = ChromeOptions     <$> v .:? "binary"     <*> v .:? "args"+    <*> v .:? "prefs"   parseJSON invalid = typeMismatch "ChromeOptions" invalid  instance ToJSON ChromeOptions where   toJSON ChromeOptions{..} = object_     [ "binary" .=? (toJSON <$> _chromeBinary)     , "args" .=? (toJSON <$> _chromeArgs)+    , "prefs" .=? (toJSON <$> _chromePrefs)     ]  instance Arbitrary ChromeOptions where   arbitrary = ChromeOptions     <$> arbitrary     <*> arbitrary+    <*> arbHashMap  -- | All members set to `Nothing`. defaultChromeOptions :: ChromeOptions defaultChromeOptions = ChromeOptions   { _chromeBinary = Nothing   , _chromeArgs = Nothing+  , _chromePrefs = Nothing   }  @@ -486,6 +493,7 @@   { _firefoxBinary :: Maybe FilePath -- ^ @binary@   , _firefoxArgs :: Maybe [String] -- ^ @args@   , _firefoxLog :: Maybe FirefoxLog+  , _firefoxPrefs :: Maybe (HashMap Text Value) -- ^ @prefs@   } deriving (Eq, Show)  instance FromJSON FirefoxOptions where@@ -493,6 +501,7 @@     <$> v .:? "binary"     <*> v .:? "args"     <*> v .:? "log"+    <*> v .:? "prefs"   parseJSON invalid = typeMismatch "FirefoxOptions" invalid  instance ToJSON FirefoxOptions where@@ -500,6 +509,7 @@     [ "binary" .=? (toJSON <$> _firefoxBinary)     , "args" .=? (toJSON <$> _firefoxArgs)     , "log" .=? (toJSON <$> _firefoxLog)+    , "prefs" .=? (toJSON <$> _firefoxPrefs)     ]  instance Arbitrary FirefoxOptions where@@ -507,13 +517,44 @@     <$> arbitrary     <*> arbitrary     <*> arbitrary+    <*> arbHashMap +arbHashMap :: Gen (Maybe (HashMap Text Value))+arbHashMap = do+  p <- arbitrary+  if p+    then return Nothing+    else do+      m <- fromList <$> listOf (mPair arbKey arbPrefVal)+      return $ Just m++arbKey :: Gen Text+arbKey = pack <$> ('k':) <$> arbitrary++arbText :: Gen Text+arbText = pack <$> arbitrary++arbPrefVal :: Gen Value+arbPrefVal = do+  k <- arbitrary :: Gen Int+  case k`mod`3 of+    0 -> Bool <$> arbitrary+    1 -> String <$> arbText+    _ -> Number <$> arbScientific++mPair :: (Monad m) => m a -> m b -> m (a,b)+mPair ga gb = do+  a <- ga+  b <- gb+  return (a,b)+ -- | All members set to `Nothing`. defaultFirefoxOptions :: FirefoxOptions defaultFirefoxOptions = FirefoxOptions   { _firefoxBinary = Nothing   , _firefoxArgs = Nothing   , _firefoxLog = Nothing+  , _firefoxPrefs = Nothing   }  
test/Main.hs view
@@ -31,7 +31,7 @@   args <- getArgs   withArgs (["--wd-remote-ends","geckodriver https://localhost:4444 https://localhost:4445 chromedriver https://localhost:9515 https://localhost:9516"] ++ args) $     defaultWebDriverMain $-      (localOption $ NumRetries 3) $+      (localOption $ NumRetries 1) $         testGroup "All Tests"           [ Test.Tasty.WebDriver.Config.Test.tests           , Web.Api.WebDriver.Assert.Test.tests lock
test/Web/Api/WebDriver/Assert/Test.hs view
@@ -86,43 +86,43 @@   -> TT.TestTree assertionTestCases config cond = TT.testGroup "Assertions"   [ QC.testProperty "assertSuccess" $-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize [success "Success!" "yay!"]) $       do         assertSuccess "yay!"    , QC.testProperty "assertFailure" $-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize [failure "Failure :(" "oh no"]) $       do         assertFailure "oh no"    , QC.testProperty "assertTrue (success)" $ \msg ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize [success "True is True" msg]) $       do         assertTrue True msg    , QC.testProperty "assertTrue (failure)" $ \msg ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize [failure "False is True" msg]) $       do         assertTrue False msg    , QC.testProperty "assertFalse (success)" $ \msg ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize [success "False is False" msg]) $       do         assertFalse False msg    , QC.testProperty "assertFalse (failure)" $ \msg ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize [failure "True is False" msg]) $       do         assertFalse True msg    , QC.testProperty "assertEqual (Int, success)" $ \k ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show k ++ " is equal to " ++ show k)@@ -133,7 +133,7 @@         assertEqual (k :: Int) k (fromString $ show k)    , QC.testProperty "assertEqual (Int, failure)" $ \k ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show (k+1) ++ " is equal to " ++ show k)@@ -144,7 +144,7 @@         assertEqual (k+1 :: Int) k (fromString $ show k)    , QC.testProperty "assertEqual (String, success)" $ \str ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show str ++ " is equal to " ++ show str)@@ -155,7 +155,7 @@         assertEqual (str :: String) str (fromString str)    , QC.testProperty "assertEqual (String, failure)" $ \str ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show (str++"?") ++ " is equal to " ++ show str)@@ -166,7 +166,7 @@         assertEqual (str++"?" :: String) str (fromString str)    , QC.testProperty "assertNotEqual (Int, success)" $ \k ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show (k+1) ++ " is not equal to " ++ show k)@@ -177,7 +177,7 @@         assertNotEqual (k+1 :: Int) k (fromString $ show k)    , QC.testProperty "assertNotEqual (Int, failure)" $ \k ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show k ++ " is not equal to " ++ show k)@@ -188,7 +188,7 @@         assertNotEqual (k :: Int) k (fromString $ show k)    , QC.testProperty "assertNotEqual (String, success)" $ \str ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show (str++"?") ++ " is not equal to " ++ show str)@@ -199,7 +199,7 @@         assertNotEqual (str++"?" :: String) str (fromString str)    , QC.testProperty "assertNotEqual (String, failure)" $ \str ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show str ++ " is not equal to " ++ show str)@@ -210,7 +210,7 @@         assertNotEqual (str :: String) str (fromString str)      , QC.testProperty "assertIsSubstring (success)" $ \str1 str2 ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show str1 ++ " is a substring of " ++ show (str2++str1++str2))@@ -222,7 +222,7 @@      , QC.testProperty "assertIsSubstring (failure)" $ \c str1 str2 ->     let str3 = filter (/= c) str2 in-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show (c:str1) ++ " is a substring of " ++ show str3)@@ -234,7 +234,7 @@      , QC.testProperty "assertIsNotSubstring (success)" $ \c str1 str2 ->     let str3 = filter (/= c) str2 in-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show (c:str1) ++ " is not a substring of " ++ show str3)@@ -245,7 +245,7 @@         assertIsNotSubstring (c:str1 :: String) (str3) (fromString str1)      , QC.testProperty "assertIsNotSubstring (failure)" $ \str1 str2 ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show str1 ++ " is not a substring of " ++ show (str2++str1++str2))@@ -256,7 +256,7 @@         assertIsNotSubstring (str1 :: String) (str2++str1++str2) (fromString str1)      , QC.testProperty "assertIsNamedSubstring (success)" $ \name str1 str2 ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show str1 ++ " is a substring of " ++ name)@@ -268,7 +268,7 @@      , QC.testProperty "assertIsNamedSubstring (failure)" $ \name c str1 str2 ->     let str3 = filter (/= c) str2 in-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show (c:str1) ++ " is a substring of " ++ name)@@ -280,7 +280,7 @@      , QC.testProperty "assertIsNotNamedSubstring (success)" $ \name c str1 str2 ->     let str3 = filter (/= c) str2 in-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [success           (fromString $ show (c:str1) ++ " is not a substring of " ++ name)@@ -291,7 +291,7 @@         assertIsNotNamedSubstring (c:str1 :: String) (str3,name) (fromString str1)      , QC.testProperty "assertIsNotNamedSubstring (failure)" $ \name str1 str2 ->-    checkWebDriver config cond+    checkWebDriverT config cond       (== summarize         [failure           (fromString $ show str1 ++ " is not a substring of " ++ name)
test/Web/Api/WebDriver/Monad/Test.hs view
@@ -42,14 +42,14 @@   -testCaseMockIO :: String -> WebDriver (MockIO WebDriverServerState) () -> TestTree+testCaseMockIO :: String -> WebDriverT (MockIO WebDriverServerState) () -> TestTree testCaseMockIO name = testCaseM name   (evalMockIO evalWDActMockIO)   (\x -> return $ fst $ runMockIO x defaultWebDriverServer)  endpointTests   :: (Monad eff)-  => (String -> WebDriver eff () -> TestTree)+  => (String -> WebDriverT eff () -> TestTree)   -> FilePath   -> [TestTree] endpointTests buildTestCase path =
test/Web/Api/WebDriver/Monad/Test/Session/InvalidElementState.hs view
@@ -17,7 +17,7 @@ invalidElementState   :: (Monad eff)   => WDError-  -> WebDriver eff ()+  -> WebDriverT eff() invalidElementState e = case e of   ResponseError InvalidElementState _ _ _ _ -> assertSuccess "yay!"   err -> assertFailure $@@ -26,7 +26,7 @@  invalidElementStateExit   :: (Monad eff)-  => (String -> WebDriver eff () -> T.TestTree)+  => (String -> WebDriverT eff() -> T.TestTree)   -> FilePath   -> T.TestTree invalidElementStateExit buildTestCase dir =@@ -39,10 +39,10 @@   _test_elementClear_invalid_element_state-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff() _test_elementClear_invalid_element_state page =   let-    session :: (Monad eff) => WebDriver eff ()+    session :: (Monad eff) => WebDriverT eff()     session = do       navigateTo page       !element <- findElement CssSelector "body"
test/Web/Api/WebDriver/Monad/Test/Session/Success.hs view
@@ -15,13 +15,13 @@ unexpectedError   :: (Monad eff)   => WDError-  -> WebDriver eff ()+  -> WebDriverT eff () unexpectedError e = assertFailure $ AssertionComment $ "Unexpected error:\n" ++ show e   successfulExit   :: (Monad eff)-  => (String -> WebDriver eff () -> T.TestTree)+  => (String -> WebDriverT eff () -> T.TestTree)   -> FilePath   -> T.TestTree successfulExit buildTestCase dir =@@ -72,6 +72,7 @@     , buildTestCase "getPageSourceStealth" (_test_getPageSourceStealth_success path)     , buildTestCase "getAllCookies" (_test_getAllCookies_success path)     ,   ifDriverIs Chromedriver TE.ignoreTest+      $ T.localOption (PrivateMode False)       $ buildTestCase "getNamedCookie" (_test_getNamedCookie_success path)     ,   ifDriverIs Chromedriver TE.ignoreTest       $ buildTestCase "deleteCookie" (_test_deleteCookie_success path)@@ -99,7 +100,7 @@   _test_sessionStatus_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_sessionStatus_success page =   let     session = do@@ -113,7 +114,7 @@   _test_getTimeouts_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_getTimeouts_success =   let     session = do@@ -126,7 +127,7 @@   _test_setTimeouts_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_setTimeouts_success =   let     session = do@@ -139,7 +140,7 @@   _test_navigateTo_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_navigateTo_success page =   let     session = do@@ -152,7 +153,7 @@   _test_navigateToStealth_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_navigateToStealth_success page =   let     session = do@@ -165,7 +166,7 @@   _test_getCurrentUrl_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_getCurrentUrl_success =   let     session = do@@ -178,7 +179,7 @@   _test_goBack_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_goBack_success =   let     session = do@@ -191,7 +192,7 @@   _test_goForward_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_goForward_success =   let     session = do@@ -204,7 +205,7 @@   _test_pageRefresh_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_pageRefresh_success =   let     session = do@@ -217,7 +218,7 @@   _test_getTitle_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_getTitle_success =   let     session = do@@ -230,7 +231,7 @@   _test_getWindowHandle_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_getWindowHandle_success =   let     session = do@@ -247,7 +248,7 @@   _test_switchToWindow_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_switchToWindow_success =   let     session = do@@ -264,7 +265,7 @@   _test_getWindowHandles_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getWindowHandles_success page =   let     session = do@@ -283,7 +284,7 @@   _test_switchToFrame_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_switchToFrame_success page =   let     session = do@@ -297,7 +298,7 @@   _test_switchToParentFrame_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_switchToParentFrame_success page =   let     session = do@@ -311,7 +312,7 @@   _test_getWindowRect_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_getWindowRect_success =   let     session = do@@ -324,7 +325,7 @@   _test_setWindowRect_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_setWindowRect_success =   let     session = do@@ -342,7 +343,7 @@   _test_maximizeWindow_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_maximizeWindow_success =   let     session = do@@ -355,7 +356,7 @@   _test_minimizeWindow_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_minimizeWindow_success =   let     session = do@@ -368,7 +369,7 @@   _test_fullscreenWindow_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_fullscreenWindow_success =   let     session = do@@ -381,7 +382,7 @@   _test_findElement_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_findElement_success page =   let     session = do@@ -399,7 +400,7 @@   _test_findElements_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_findElements_success page =   let     session = do@@ -432,7 +433,7 @@   _test_findElementFromElement_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_findElementFromElement_success page =   let     session = do@@ -451,7 +452,7 @@   _test_findElementsFromElement_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_findElementsFromElement_success page =   let     session = do@@ -485,7 +486,7 @@   _test_getActiveElement_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_getActiveElement_success =   let     session = do@@ -498,7 +499,7 @@   _test_isElementSelected_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_isElementSelected_success page =   let     session = do@@ -513,13 +514,13 @@   _test_getElementAttribute_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getElementAttribute_success page =   let     session = do       navigateTo page       !element <- getActiveElement-      !attr <- getElementAttribute element "href"+      !attr <- getElementAttribute "href" element       assertSuccess "yay"       return () @@ -532,13 +533,13 @@   _test_getElementCssValue_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getElementCssValue_success page =   let     session = do       navigateTo page       !element <- findElement CssSelector "p#super-cool"-      !text <- getElementCssValue element "text-decoration"+      !text <- getElementCssValue "text-decoration" element       case text of         "none" -> assertSuccess "yay"         _ -> assertFailure $ AssertionComment $ "expected 'none', got '" ++ text ++ "'"@@ -549,7 +550,7 @@   _test_getElementText_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getElementText_success page =   let     session = do@@ -564,7 +565,7 @@   _test_getElementTagName_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getElementTagName_success page =   let     session = do@@ -581,7 +582,7 @@   _test_getElementRect_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getElementRect_success page =   let     session = do@@ -596,7 +597,7 @@   _test_isElementEnabled_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_isElementEnabled_success page =   let     session = do@@ -611,7 +612,7 @@   _test_elementClick_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_elementClick_success page =   let     session = do@@ -626,7 +627,7 @@   _test_elementClear_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_elementClear_success page =   let     session = do@@ -641,13 +642,13 @@   _test_elementSendKeys_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_elementSendKeys_success page =   let     session = do       navigateTo page       !element <- findElement CssSelector "input[name='sometext']"-      () <- elementSendKeys element "foo"+      () <- elementSendKeys "foo" element       assertSuccess "yay"       return () @@ -656,7 +657,7 @@   _test_getPageSource_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getPageSource_success page =   let     session = do@@ -670,7 +671,7 @@   _test_getPageSourceStealth_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getPageSourceStealth_success page =   let     session = do@@ -692,7 +693,7 @@   _test_getAllCookies_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getAllCookies_success page =   let     session = do@@ -708,7 +709,7 @@   _test_getNamedCookie_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getNamedCookie_success page =   let     session = do@@ -729,7 +730,7 @@   _test_deleteCookie_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_deleteCookie_success page =   let     session = do@@ -744,7 +745,7 @@   _test_deleteAllCookies_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_deleteAllCookies_success =   let     session = do@@ -757,7 +758,7 @@   _test_performActions_keyboard_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_performActions_keyboard_success =   let     session = do@@ -806,7 +807,7 @@   _test_performActionsStealth_keyboard_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_performActionsStealth_keyboard_success =   let     session = do@@ -819,7 +820,7 @@   _test_releaseActions_success-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_releaseActions_success =   let     session = do@@ -832,7 +833,7 @@   _test_dismissAlert_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_dismissAlert_success page =   let     session = do@@ -853,7 +854,7 @@   _test_acceptAlert_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_acceptAlert_success page =   let     session = do@@ -874,7 +875,7 @@   _test_getAlertText_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_getAlertText_success page =   let     session = do@@ -904,7 +905,7 @@   _test_sendAlertText_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_sendAlertText_success page =   let     session = do@@ -919,7 +920,7 @@   _test_takeScreenshot_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_takeScreenshot_success page =   let     session = do@@ -933,7 +934,7 @@   _test_takeElementScreenshot_success-  :: (Monad eff) => FilePath -> WebDriver eff ()+  :: (Monad eff) => FilePath -> WebDriverT eff () _test_takeElementScreenshot_success page =   let     session = do
test/Web/Api/WebDriver/Monad/Test/Session/UnknownError.hs view
@@ -16,14 +16,14 @@ unknownError   :: (Monad eff)   => WDError-  -> WebDriver eff ()+  -> WebDriverT eff () unknownError e = case e of   ResponseError UnknownError _ _ _ _ -> assertSuccess "yay!"   _ -> assertFailure "Expecting 'unknown error'"   unknownErrorExit-  :: (Monad eff) => (String -> WebDriver eff () -> T.TestTree)+  :: (Monad eff) => (String -> WebDriverT eff () -> T.TestTree)   -> FilePath   -> T.TestTree unknownErrorExit buildTestCase path = T.testGroup "Unknown Error"@@ -34,7 +34,7 @@   _test_navigateTo_unknown_error-  :: (Monad eff) => WebDriver eff ()+  :: (Monad eff) => WebDriverT eff () _test_navigateTo_unknown_error =   let     session = do
webdriver-w3c.cabal view
@@ -1,5 +1,5 @@ name:           webdriver-w3c-version:        0.0.1+version:        0.0.2 description:    Please see the README on Github at <https://github.com/nbloomf/webdriver-w3c#readme> homepage:       https://github.com/nbloomf/webdriver-w3c#readme bug-reports:    https://github.com/nbloomf/webdriver-w3c/issues@@ -14,7 +14,7 @@ synopsis:       Bindings to the WebDriver API  extra-source-files:-  ChangeLog.md+  CHANGELOG.md   README.md  source-repository head@@ -32,33 +32,34 @@   build-depends:       base >=4.7 && <5 -    , aeson >=1.2.4.0 && <1.3-    , aeson-pretty >=0.8.5 && <0.9-    , base64-bytestring >=1.0.0.1 && <1.1-    , bytestring >=0.10.8.2 && <0.11-    , containers >=0.5.10.2 && <0.6-    , directory >=1.3.0.2 && <1.4-    , exceptions >=0.8.3 && <0.9-    , http-client >=0.5.10 && <0.6-    , http-types >=0.12.1 && <0.13-    , JuicyPixels >=3.2.9.4 && <3.3-    , lens >=4.16 && <4.17-    , lens-aeson >=1.0.2 && <1.1-    , network-uri >= 2.6 && < 2.7-    , QuickCheck >=2.10.1 && <2.11-    , random >=1.1 && <1.2-    , scientific >=0.3.5.2 && <0.4-    , script-monad >=0.0.1 && <0.0.2-    , SHA >=1.6.4.2 && <1.7-    , stm >=2.4.5.0 && <2.5-    , tasty >=1.0.1.1 && <1.1-    , tasty-expected-failure >=0.11.1.1 && <0.12-    , text >=1.2.3.0 && <1.3-    , time >=1.8.0.2 && <1.9-    , unordered-containers >=0.2.9.0 && <0.3-    , uri-encode >=1.5.0.5 && <1.6-    , vector >=0.12.0.1 && <0.13-    , wreq >=0.5.2 && <0.6+    , aeson >=1.2.4.0+    , aeson-pretty >=0.8.5+    , base64-bytestring >=1.0.0.1+    , bytestring >=0.10.8.2+    , containers >=0.5.10.2+    , directory >=1.3.0.2+    , exceptions >=0.8.3+    , http-client >=0.5.10+    , http-types >=0.12.1+    , JuicyPixels >=3.2.9.4+    , lens >=4.16+    , lens-aeson >=1.0.2+    , network-uri >= 2.6+    , QuickCheck >=2.10.1+    , random >=1.1+    , scientific >=0.3.5.2+    , script-monad >=0.0.1+    , SHA >=1.6.4.2+    , stm >=2.4.5.0+    , tasty >=1.0.1.1+    , tasty-expected-failure >=0.11.1.1+    , text >=1.2.3.0+    , time >=1.8.0.2+    , transformers >=0.5.5.0+    , unordered-containers >=0.2.9.0+    , uri-encode >=1.5.0.5+    , vector >=0.12.0.1+    , wreq >=0.5.2    exposed-modules:     Test.Tasty.WebDriver@@ -85,7 +86,8 @@       webdriver-w3c     , base >=4.7 && <5 -    , tasty >=1.0.1.1 && <1.1+    , tasty >=1.0.1.1+    , transformers >=0.5.5.0   @@ -98,8 +100,8 @@       webdriver-w3c     , base >=4.7 && <5 -    , tasty >=1.0.1.1 && <1.1-    , tasty-expected-failure >=0.11.1.1 && <0.12+    , tasty >=1.0.1.1+    , tasty-expected-failure >=0.11.1.1   @@ -112,7 +114,7 @@       webdriver-w3c     , base >=4.7 && <5 -    , tasty >=1.0.1.1 && <1.1+    , tasty >=1.0.1.1   @@ -127,31 +129,32 @@       webdriver-w3c     , base >=4.7 && <5 -    , aeson >=1.2.4.0 && <1.3-    , base64-bytestring >=1.0.0.1 && <1.1-    , bytestring >=0.10.8.2 && <0.11-    , containers >=0.5.10.2 && <0.6-    , directory >=1.3.0.2 && <1.4-    , exceptions >=0.8.3 && <0.9-    , http-client >=0.5.10 && <0.6-    , http-types >=0.12.1 && <0.13-    , JuicyPixels >=3.2.9.4 && <3.3-    , lens >=4.16 && <4.17-    , lens-aeson >=1.0.2 && <1.1-    , parsec >=3.1.13.0 && <4-    , QuickCheck >=2.10.1 && <2.11-    , random >=1.1 && <1.2-    , unordered-containers >=0.2.9.0 && <0.3-    , scientific >=0.3.5.2 && <0.4-    , script-monad >=0.0.1 && <0.0.2-    , tasty >=1.0.1.1 && <1.1-    , tasty-expected-failure >=0.11.1.1 && <0.12-    , tasty-hunit >=0.10.0.1 && <0.11-    , tasty-quickcheck >=0.9.2 && <1.0-    , time >=1.8.0.2 && <1.9-    , text >=1.2.3.0 && <1.3-    , vector >=0.12.0.1 && <1.0-    , wreq >=0.5.2 && <0.6+    , aeson >=1.2.4.0+    , base64-bytestring >=1.0.0.1+    , bytestring >=0.10.8.2+    , containers >=0.5.10.2+    , directory >=1.3.0.2+    , exceptions >=0.8.3+    , http-client >=0.5.10+    , http-types >=0.12.1+    , JuicyPixels >=3.2.9.4+    , lens >=4.16+    , lens-aeson >=1.0.2+    , parsec >=3.1.13.0+    , QuickCheck >=2.10.1+    , random >=1.1+    , unordered-containers >=0.2.9.0+    , scientific >=0.3.5.2+    , script-monad >=0.0.1+    , tasty >=1.0.1.1+    , tasty-expected-failure >=0.11.1.1+    , tasty-hunit >=0.10.0.1+    , tasty-quickcheck >=0.9.2+    , time >=1.8.0.2+    , text >=1.2.3.0+    , transformers >=0.5.5.0+    , vector >=0.12.0.1+    , wreq >=0.5.2    other-modules:     Test.Tasty.WebDriver.Config.Test