webdriver-w3c-0.0.2: src/Web/Api/WebDriver/Monad.hs
{- |
Module : Web.Api.WebDriver.Monad
Description : A WebDriver session monad.
Copyright : 2018, Automattic, Inc.
License : GPL-3
Maintainer : Nathan Bloomfield (nbloomf@gmail.com)
Stability : experimental
Portability : POSIX
A monad transformer for building WebDriver sessions.
-}
{-#
LANGUAGE
GADTs,
Rank2Types,
KindSignatures,
RecordWildCards,
OverloadedStrings
#-}
module Web.Api.WebDriver.Monad (
WebDriverT
, execWebDriverT
, debugWebDriverT
, checkWebDriverT
, WebDriverTT()
, execWebDriverTT
, debugWebDriverTT
, checkWebDriverTT
, liftWebDriverTT
, evalWDAct
, Http.evalIO
, evalWDActMockIO
, Http.evalMockIO
-- * Config
, WebDriverConfig(..)
, defaultWebDriverConfig
, defaultWebDriverState
, defaultWebDriverEnvironment
, defaultWDEnv
, defaultWebDriverLogOptions
-- * API
, fromState
, modifyState
, fromEnv
, comment
, wait
, logDebug
, logNotice
, throwError
, throwJsonError
, throwHttpException
, throwIOException
, expect
, expectIs
, assert
, catchError
, catchJsonError
, catchHttpException
, catchIOException
, catchAnyError
, parseJson
, lookupKeyJson
, constructFromJson
, httpGet
, httpSilentGet
, httpPost
, httpSilentPost
, httpDelete
, httpSilentDelete
, hPutStrLn
, hPutStrLnBlocking
, getStrLn
, promptForString
, promptForSecret
, readFilePath
, writeFilePath
, fileExists
, breakpointsOn
, breakpointsOff
, breakpoint
, breakpointWith
-- * Types
, Http.E(..)
, Http.JsonError(..)
, WDError(..)
, Http.R(..)
, Http.LogOptions(..)
, WDEnv(..)
, ResponseFormat(..)
, ApiVersion(..)
, Outcome(..)
, Http.Url
, Http.HttpResponse(..)
, WDLog(..)
, Http.P(..)
, WDAct(..)
, Http.S(..)
, WDState(..)
, BreakpointSetting(..)
-- * Logs
, getAssertions
, Http.logEntries
, Http.printHttpLogs
, Http.basicLogEntryPrinter
) where
import Prelude hiding (readFile, writeFile, putStrLn)
import Control.Concurrent.MVar
( MVar )
import Control.Exception
( IOException, try )
import Control.Lens
( (^.), (^?) )
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
( encodePretty )
import Data.Aeson.Lens
( key, _Value, _String )
import qualified Data.ByteString.Char8 as SC
( unpack )
import Data.ByteString.Lazy
( ByteString, readFile, writeFile )
import qualified Data.ByteString.Lazy.Char8 as LC
( unpack, pack )
import Data.Functor.Identity
( Identity(..) )
import Data.IORef
( IORef, newIORef, readIORef, writeIORef )
import Data.List
( intercalate )
import qualified Data.Map.Strict as M
( Map, fromList )
import Data.Text
( pack, unpack, Text )
import qualified Network.HTTP.Client as N
( HttpException(..), HttpExceptionContent(..) )
import Network.Wreq
( Status, statusMessage, statusCode, responseStatus, defaults )
import System.Directory
( doesFileExist )
import System.IO
( Handle, hGetLine, hSetEcho, hGetEcho, hFlush, stdout, stdin )
import System.IO.Error
( eofErrorType, doesNotExistErrorType, mkIOError )
import Test.QuickCheck
( Property )
import qualified Control.Monad.Script.Http as Http
import qualified Data.MockIO as Mock
import qualified Data.MockIO.FileSystem as FS
import Web.Api.WebDriver.Types
import Web.Api.WebDriver.Assert
-- | 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
(Monad eff, Monad (t eff), MonadTrans t)
=> Functor (WebDriverTT t eff) where
fmap f = WDT . fmap f . unWDT
instance
(Monad eff, Monad (t eff), MonadTrans t)
=> Applicative (WebDriverTT t eff) where
pure = return
(<*>) = ap
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 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
{ _initialState :: Http.S WDState
, _environment :: Http.R WDError WDLog WDEnv
, _evaluator :: forall a. Http.P WDAct a -> eff a
}
-- | Default `IO` effects
defaultWebDriverConfig :: WebDriverConfig IO
defaultWebDriverConfig = WDConfig
{ _initialState = defaultWebDriverState
, _environment = defaultWebDriverEnvironment
, _evaluator = Http.evalIO evalWDAct
}
defaultWebDriverState :: Http.S WDState
defaultWebDriverState = Http.S
{ Http._httpOptions = defaults
, Http._httpSession = Nothing
, Http._userState = WDState
{ _sessionId = Nothing
, _breakpoints = BreakpointsOff
}
}
defaultWebDriverEnvironment :: Http.R WDError WDLog WDEnv
defaultWebDriverEnvironment = Http.R
{ Http._logHandle = stdout
, Http._logLock = Nothing
, Http._logEntryPrinter = Http.basicLogEntryPrinter
, Http._uid = ""
, Http._logOptions = defaultWebDriverLogOptions
, Http._httpErrorInject = promoteHttpResponseError
, Http._env = defaultWDEnv
}
-- | Uses default geckodriver settings
defaultWDEnv :: WDEnv
defaultWDEnv = WDEnv
{ _remoteHostname = "localhost"
, _remotePort = 4444
, _remotePath = ""
, _dataPath = ""
, _responseFormat = SpecFormat
, _apiVersion = CR_2018_03_04
, _stdin = stdin
, _stdout = stdout
}
-- | Noisy, JSON, in color, without headers.
defaultWebDriverLogOptions :: Http.LogOptions WDError WDLog
defaultWebDriverLogOptions = Http.trivialLogOptions
{ Http._logColor = True
, Http._logJson = True
, Http._logHeaders = False
, Http._logSilent = False
, Http._printUserError = printWDError
, Http._printUserLog = printWDLog
}
-- | Execute a `WebDriverTT` session.
execWebDriverTT
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverConfig eff
-> 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 `WebDriverTT` session, returning an assertion summary with the result.
debugWebDriverTT
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverConfig eff
-> 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.
checkWebDriverTT
:: (Monad eff, Monad (t eff), MonadTrans t, Show q)
=> WebDriverConfig eff
-> (t eff (Either (Http.E WDError) a, Http.S WDState, Http.W WDError WDLog) -> IO q) -- ^ Condense to `IO`
-> (q -> Bool) -- ^ Result check
-> WebDriverTT t eff a
-> Property
checkWebDriverTT config cond check =
Http.checkHttpTT
(_initialState config)
(_environment config)
(_evaluator config)
cond check . unWDT
-- | `WebDriverTT` over `IdentityT`.
type WebDriverT eff a = WebDriverTT IdentityT eff a
-- | Execute a `WebDriverT` session.
execWebDriverT
:: (Monad eff)
=> WebDriverConfig eff
-> WebDriverT eff a
-> eff (Either (Http.E WDError) a, Http.S WDState, Http.W WDError WDLog)
execWebDriverT config = runIdentityT . execWebDriverTT config
-- | Execute a `WebDriverT` session, returning an assertion summary with the result.
debugWebDriverT
:: (Monad eff)
=> WebDriverConfig eff
-> WebDriverT eff a
-> eff (Either String a, AssertionSummary)
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
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
-> WebDriverT eff t
-> Property
checkWebDriverT config cond = checkWebDriverTT config (cond . runIdentityT)
-- | Get a computed value from the state
fromState
:: (Monad eff, Monad (t eff), MonadTrans t)
=> (Http.S WDState -> a) -> WebDriverTT t eff a
fromState = WDT . Http.gets
-- | Mutate the state
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
:: (Monad eff, Monad (t eff), MonadTrans t)
=> (Http.R WDError WDLog WDEnv -> a) -> WebDriverTT t eff a
fromEnv = WDT . Http.reader
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
:: (Monad eff, Monad (t eff), MonadTrans t)
=> String -> WebDriverTT t eff ()
comment = WDT . Http.comment
-- | 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
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WDError -> WebDriverTT t eff a
throwError = WDT . Http.throwError
throwJsonError
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Http.JsonError -> WebDriverTT t eff a
throwJsonError = WDT . Http.throwJsonError
throwHttpException
:: (Monad eff, Monad (t eff), MonadTrans t)
=> N.HttpException -> WebDriverTT t eff a
throwHttpException = WDT . Http.throwHttpException
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
:: (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
:: (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
:: (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
:: (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
:: (Monad eff, Monad (t eff), MonadTrans t)
=> ByteString -> WebDriverTT t eff Value
parseJson = WDT . Http.parseJson
-- | May throw a `JsonError`.
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
:: (Monad eff, Monad (t eff), MonadTrans t, FromJSON a)
=> Value -> WebDriverTT t eff a
constructFromJson = WDT . Http.constructFromJson
-- | Capures `HttpException`s.
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
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Http.Url -> WebDriverTT t eff Http.HttpResponse
httpSilentGet = WDT . Http.httpSilentGet
-- | Capures `HttpException`s.
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
:: (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
:: (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
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Http.Url -> WebDriverTT t eff Http.HttpResponse
httpSilentDelete = WDT . Http.httpSilentDelete
-- | Capures `IOException`s.
hPutStrLn
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Handle -> String -> WebDriverTT t eff ()
hPutStrLn h = WDT . Http.hPutStrLn h
-- | Capures `IOException`s.
hPutStrLnBlocking
:: (Monad eff, Monad (t eff), MonadTrans t)
=> MVar () -> Handle -> String -> WebDriverTT t eff ()
hPutStrLnBlocking lock h = WDT . Http.hPutStrLnBlocking lock h
promptWDAct
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WDAct a -> WebDriverTT t eff a
promptWDAct = WDT . Http.prompt . Http.P
instance
(Monad eff, Monad (t eff), MonadTrans t)
=> Assert (WebDriverTT t eff) where
assert = logNotice . LogAssertion
-- | Filter the assertions from a WebDriver log.
getAssertions :: [WDLog] -> [Assertion]
getAssertions xs = get xs
where
get [] = []
get (w:ws) = case w of
LogAssertion a -> a : get ws
_ -> get ws
-- | Errors specific to WebDriver sessions.
data WDError
= NoSession
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors>
| ResponseError ResponseErrorCode String String (Maybe Value) Status
| UnableToConnect
| RemoteEndTimeout
| UnhandledHttpException N.HttpException
| ImageDecodeError String
| UnexpectedValue String
| UnexpectedResult Outcome String
| BreakpointHaltError
deriving Show
-- | Read-only environment variables specific to WebDriver.
data WDEnv = WDEnv
{ -- | Hostname of the remote WebDriver server
_remoteHostname :: String
-- | Port of the remote WebDriver server
, _remotePort :: Int
-- | Extra path for the remote WebDriver server
, _remotePath :: String
-- | Path where secret data is stored
, _dataPath :: FilePath
-- | Flag for the format of HTTP responses from the remote end. Needed because not all remote ends are spec-compliant.
, _responseFormat :: ResponseFormat
-- | Version of the WebDriver specification.
, _apiVersion :: ApiVersion
, _stdin :: Handle
, _stdout :: Handle
}
-- | Version of the WebDriver specification.
data ApiVersion
= CR_2018_03_04 -- ^ Candidate Recommendation, March 4, 2018
deriving (Eq, Show)
-- | Format flag for HTTP responses from the remote end. Chromedriver, for instance, is not spec-compliant. :)
data ResponseFormat
= SpecFormat -- ^ Responses as described in the spec.
| 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.
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.
printWDLog :: Bool -> WDLog -> String
printWDLog _ w = show w
-- | Type representing an abstract outcome. Do with it what you will.
data Outcome = IsSuccess | IsFailure
deriving (Eq, Show)
-- | Representation of the actions we can perform on a `Session` (in the @wreq@ sense).
data SessionVerb
= Close | Open
deriving (Eq, Show)
-- | WebDriver specific effects
data WDAct a where
ReadFilePath :: FilePath -> WDAct (Either IOException ByteString)
WriteFilePath :: FilePath -> ByteString -> WDAct (Either IOException ())
FileExists :: FilePath -> WDAct (Either IOException Bool)
HGetLine :: Handle -> WDAct (Either IOException String)
HGetLineNoEcho :: Handle -> WDAct (Either IOException String)
-- | For validating responses. Throws an `UnexpectedValue` error if the two arguments are not equal according to their `Eq` instance.
expect
:: (Monad eff, Monad (t eff), MonadTrans t, Eq a, Show a)
=> a
-> 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
N.HttpExceptionRequest _ (N.StatusCodeException s r) -> do
err <- r ^? key "value" . key "error" . _Value
code <- case fromJSON err of
Success m -> return m
_ -> Nothing
msg <- fmap unpack (r ^? key "value" . key "message" . _String)
str <- fmap unpack (r ^? key "value" . key "stacktrace" . _String)
let obj = r ^? key "value" . key "data" . _Value
status <- s ^? responseStatus
return $ ResponseError code msg str obj status
N.HttpExceptionRequest _ (N.ConnectionFailure _) -> Just UnableToConnect
N.HttpExceptionRequest _ N.ConnectionTimeout -> Just RemoteEndTimeout
_ -> Just $ UnhandledHttpException e
-- | For pretty printing.
printWDError :: Bool -> WDError -> String
printWDError json e = case e of
NoSession -> "No session in progress"
ResponseError code msg trace obj status ->
let
code = status ^. statusCode
smsg = status ^. statusMessage
in
(("Response: " ++ show code ++ " " ++ SC.unpack smsg ++ "\n") ++) $
LC.unpack $ encodePretty $ object
[ "error" .= toJSON code
, "message" .= toJSON msg
, "stacktrace" .= toJSON trace
, "data" .= (toJSON <$> obj)
]
UnableToConnect -> "Unable to connect to WebDriver server"
RemoteEndTimeout -> "Remote End Timeout"
UnhandledHttpException e -> "Unhandled HTTP Exception: " ++ show e
ImageDecodeError msg -> "Image decode: " ++ msg
UnexpectedValue msg -> "Unexpected value: " ++ msg
UnexpectedResult r msg -> case r of
IsSuccess -> "Unexpected success: " ++ msg
IsFailure -> "Unexpected failure: " ++ msg
BreakpointHaltError -> "Breakpoint Halt"
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)
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 eff, Monad (t eff), MonadTrans t)
=> String -- ^ Prompt text
-> WebDriverTT t eff String
promptForSecret prompt = do
outH <- fromEnv (_stdout . Http._env)
inH <- fromEnv (_stdin . Http._env)
hPutStrLn outH prompt
result <- promptWDAct $ HGetLineNoEcho inH
case result of
Right string -> return string
Left e -> throwIOException e
-- | Captures `IOException`s
readFilePath
:: (Monad eff, Monad (t eff), MonadTrans t)
=> FilePath
-> WebDriverTT t eff ByteString
readFilePath path = do
result <- promptWDAct $ ReadFilePath path
case result of
Right bytes -> return bytes
Left e -> throwIOException e
-- | Captures `IOException`s
writeFilePath
:: (Monad eff, Monad (t eff), MonadTrans t)
=> FilePath
-> ByteString
-> WebDriverTT t eff ()
writeFilePath path bytes = do
result <- promptWDAct $ WriteFilePath path bytes
case result of
Right () -> return ()
Left e -> throwIOException e
-- | Captures `IOException`s
fileExists
:: (Monad eff, Monad (t eff), MonadTrans t)
=> FilePath
-> WebDriverTT t eff Bool
fileExists path = do
result <- promptWDAct $ FileExists path
case result of
Right p -> return p
Left e -> throwIOException e
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
ReadFilePath path -> try $ readFile path
WriteFilePath path bytes -> try $ writeFile path bytes
FileExists path -> try $ doesFileExist path
HGetLine handle -> try $ do
hGetLine handle
HGetLineNoEcho handle -> try $ do
echo <- hGetEcho handle
hSetEcho handle False
secret <- hGetLine handle
hSetEcho handle echo
return secret
-- | Standard `Mock.MockIO` evaluator for `WDAct`.
evalWDActMockIO :: WDAct a -> Mock.MockIO u a
evalWDActMockIO act = case act of
ReadFilePath path -> do
Mock.incrementTimer 1
world <- Mock.getMockWorld
let result = FS.getLines (Left path) $ Mock._files world
case result of
Nothing -> do
return $ Left $ mkIOError doesNotExistErrorType "" Nothing (Just path)
Just lns -> return $ Right $ LC.pack $ unlines lns
WriteFilePath path bytes -> do
Mock.incrementTimer 1
fmap Right $ Mock.modifyMockWorld $ \w -> w
{ Mock._files = FS.writeLines (Left path) [LC.unpack bytes] $ Mock._files w }
FileExists path -> do
Mock.incrementTimer 1
world <- Mock.getMockWorld
return $ Right $ FS.fileExists (Left path) $ Mock._files world
HGetLine handle -> do
Mock.incrementTimer 1
world <- Mock.getMockWorld
let dne = mkIOError doesNotExistErrorType "" (Just handle) Nothing
let eof = mkIOError eofErrorType "" (Just handle) Nothing
let result = FS.readLine dne eof (Right handle) $ Mock._files world
case result of
Left err -> return $ Left err
Right (str, fs) -> do
Mock.modifyMockWorld $ \w -> w { Mock._files = fs }
return $ Right str
HGetLineNoEcho handle -> do
Mock.incrementTimer 1
world <- Mock.getMockWorld
let dne = mkIOError doesNotExistErrorType "" (Just handle) Nothing
let eof = mkIOError eofErrorType "" (Just handle) Nothing
let result = FS.readLine dne eof (Right handle) $ Mock._files world
case result of
Left err -> return $ Left err
Right (str, fs) -> do
Mock.modifyMockWorld $ \w -> w { Mock._files = fs }
return $ Right str