webdriver-w3c-0.0.2: src/Web/Api/WebDriver/Endpoints.hs
{- |
Module : Web.Api.WebDriver.Endpoints
Description : Type-safe bindings for the WebDriver HTTP API.
Copyright : 2018, Automattic, Inc.
License : GPL-3
Maintainer : Nathan Bloomfield (nbloomf@gmail.com)
Stability : experimental
Portability : POSIX
These bindings try to stick as closely to the spec as possible. We use the following conventions.
* The @Stealth@ suffix on a function indicates that it does not log request or response data (but it does log that a request/response occurred).
* A prime (@'@) on a POST function name indicates that it takes an additional function parameter that mutates the payload after it is converted to JSON, but before sending the request. This is a cheap way to future-proof the bindings and accommodate nonstandard request parameters.
The most recent version of the spec is available at <https://w3c.github.io/webdriver/webdriver-spec.html>.
-}
{-# LANGUAGE OverloadedStrings, BangPatterns #-}
module Web.Api.WebDriver.Endpoints (
runIsolated
-- * Sessions
-- ** New Session
, newSession
, newSession'
-- ** Delete Session
, deleteSession
-- ** Status
, sessionStatus
-- ** Get Timeouts
, getTimeouts
-- ** Set Timeouts
, setTimeouts
-- * Navigation
-- ** Navigate To
, navigateTo
, navigateToStealth
-- ** Get Current URL
, getCurrentUrl
-- ** Go Back
, goBack
-- ** Go Forward
, goForward
-- ** Page Refresh
, pageRefresh
-- ** Get Title
, getTitle
-- * Command Contexts
-- ** Get Window Handle
, getWindowHandle
-- ** Close Window
, closeWindow
-- ** Switch To Window
, switchToWindow
-- ** Get Window Handles
, getWindowHandles
-- ** Switch To Frame
, switchToFrame
-- ** Switch To Parent Frame
, switchToParentFrame
-- ** Get Window Rect
, getWindowRect
-- ** Set Window Rect
, setWindowRect
-- ** Maximize Window
, maximizeWindow
-- ** Minimize Window
, minimizeWindow
-- ** Fullscreen Window
, fullscreenWindow
-- * Element Retrieval
-- ** Find Element
, findElement
-- ** Find Elements
, findElements
-- ** Find Element From Element
, findElementFromElement
-- ** Find Elements From Element
, findElementsFromElement
-- ** Get Active Element
, getActiveElement
-- * Element State
-- ** Is Element Selected
, isElementSelected
-- ** Get Element Attribute
, getElementAttribute
-- ** Get Element Property
, getElementProperty
-- ** Get Element CSS Value
, getElementCssValue
-- ** Get Element Text
, getElementText
-- ** Get Element Tag Name
, getElementTagName
-- ** Get Element Rect
, getElementRect
-- ** Is Element Enabled
, isElementEnabled
-- * Element Interaction
-- ** Element Click
, elementClick
-- ** Element Clear
, elementClear
-- ** Element Send Keys
, elementSendKeys
-- * Document Handling
-- ** Get Page Source
, getPageSource
, getPageSourceStealth
-- ** Execute Script
, executeScript
-- ** Execute Async Script
, executeAsyncScript
-- * Cookies
-- ** Get All Cookies
, getAllCookies
-- ** Get Named Cookie
, getNamedCookie
-- ** Add Cookie
, addCookie
-- ** Delete Cookie
, deleteCookie
-- ** Delete All Cookies
, deleteAllCookies
-- * Actions
-- ** Perform Actions
, performActions
, performActionsStealth
-- ** Release Actions
, releaseActions
-- * User Prompts
-- ** Dismiss Alert
, dismissAlert
-- ** Accept Alert
, acceptAlert
-- ** Get Alert Text
, getAlertText
-- ** Send Alert Text
, sendAlertText
-- * Screen Capture
-- ** Take Screenshot
, takeScreenshot
-- ** Take Element Screenshot
, takeElementScreenshot
-- Spec Constants
, _WEB_ELEMENT_ID
, _WEB_WINDOW_ID
, _WEB_FRAME_ID
) where
import Control.Monad.Trans.Class
( MonadTrans(..) )
import Data.Aeson
( Value(..), encode, object, (.=), toJSON )
import Data.Text
( Text, unpack, pack )
import Data.Text.Encoding
( encodeUtf8 )
import qualified Data.ByteString as SB
import qualified Data.ByteString.Base64 as B64
import qualified Network.URI.Encode as E
import Web.Api.WebDriver.Types
import Web.Api.WebDriver.Classes
import Web.Api.WebDriver.Monad
-- | Spec-defined "web element identifier" string constant. See <https://w3c.github.io/webdriver/webdriver-spec.html#elements>.
_WEB_ELEMENT_ID :: Text
_WEB_ELEMENT_ID = "element-6066-11e4-a52e-4f735466cecf"
-- | Spec-defined "web window identifier" string constant. See <https://w3c.github.io/webdriver/webdriver-spec.html#command-contexts>.
_WEB_WINDOW_ID :: Text
_WEB_WINDOW_ID = "window-fcc6-11e5-b4f8-330a88ab9d7f"
-- | Spec-defined "web frame identifier" string constant. See <https://w3c.github.io/webdriver/webdriver-spec.html#command-contexts>.
_WEB_FRAME_ID :: Text
_WEB_FRAME_ID = "frame-075b-4da1-b6ba-e579c2d3230a"
-- | Url of the remote WebDriver server.
theRemoteUrl
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff String
theRemoteUrl = do
host <- fromEnv (_remoteHostname . _env)
port <- fromEnv (_remotePort . _env)
path <- fromEnv (_remotePath . _env)
return $ concat [ "http://", host, ":", show port, path]
-- | Url of the remote WebDriver server, with session ID.
theRemoteUrlWithSession :: (Monad eff, Monad (t eff), MonadTrans t) => WebDriverTT t eff String
theRemoteUrlWithSession = do
st <- fromState (_sessionId . _userState)
case st of
Nothing -> throwError NoSession
Just session_id -> do
baseUrl <- theRemoteUrl
return $ concat [ baseUrl, "/session/", session_id ]
-- | Set the session id of a `WDState`.
setSessionId
:: Maybe String
-> S WDState
-> S WDState
setSessionId x st = st { _userState = (_userState st) { _sessionId = x } }
-- | 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 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 eff, Monad (t eff), MonadTrans t)
=> Capabilities
-> WebDriverTT t eff a
-> WebDriverTT t eff ()
runIsolated caps theSession = cleanupOnError $ do
session_id <- newSession caps
modifyState $ setSessionId (Just session_id)
theSession
deleteSession
modifyState $ setSessionId Nothing
-- | 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 eff, Monad (t eff), MonadTrans t)
=> Capabilities
-> 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 eff, Monad (t eff), MonadTrans t)
=> (Value -> Value)
-> Capabilities
-> WebDriverTT t eff SessionId
newSession' f caps = do
baseUrl <- theRemoteUrl
format <- fromEnv (_responseFormat . _env)
let
!payload = encode $ f $ object
[ "capabilities" .= object
[ "alwaysMatch" .= toJSON caps ]
, "desiredCapabilities" .= toJSON caps
]
httpPost (baseUrl ++ "/session") payload
>>= (return . _responseBody)
>>= parseJson
>>= case format of
SpecFormat -> lookupKeyJson "value"
ChromeFormat -> return
>>= lookupKeyJson "sessionId"
>>= constructFromJson
>>= (return . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#delete-session>.
deleteSession
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
deleteSession = do
(baseUrl, format) <- theRequestContext
httpDelete baseUrl
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#status>.
sessionStatus
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff (Bool, String)
sessionStatus = do
baseUrl <- theRemoteUrl
format <- fromEnv (_responseFormat . _env)
r <- httpGet (baseUrl ++ "/status")
>>= (return . _responseBody)
>>= parseJson
ready <- case format of
SpecFormat ->
lookupKeyJson "value" r
>>= lookupKeyJson "ready"
>>= constructFromJson
ChromeFormat -> return True
msg <- case format of
SpecFormat ->
lookupKeyJson "value" r
>>= lookupKeyJson "message"
>>= constructFromJson
>>= (return . unpack)
ChromeFormat -> return "chromedriver is not spec compliant :)"
return (ready, msg)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts>.
getTimeouts
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff TimeoutConfig
getTimeouts = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/timeouts")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#set-timeouts>.
setTimeouts
:: (Monad eff, Monad (t eff), MonadTrans t)
=> TimeoutConfig
-> WebDriverTT t eff ()
setTimeouts timeouts = do
baseUrl <- theRemoteUrlWithSession
let !payload = encode timeouts
httpPost (baseUrl ++ "/timeouts") payload
return ()
-- | 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 eff, Monad (t eff), MonadTrans t)
=> Url
-> WebDriverTT t eff ()
navigateTo url = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object [ "url" .= url ]
httpPost (baseUrl ++ "/url") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | 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 eff, Monad (t eff), MonadTrans t)
=> Url
-> WebDriverTT t eff ()
navigateToStealth url = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object [ "url" .= url ]
httpSilentPost (baseUrl ++ "/url") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-current-url>.
getCurrentUrl
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff Url
getCurrentUrl = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/url")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (return . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#back>.
goBack
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
goBack = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object []
httpPost (baseUrl ++ "/back") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#forward>.
goForward
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
goForward = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object []
httpPost (baseUrl ++ "/forward") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#refresh>.
pageRefresh
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
pageRefresh = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object []
httpPost (baseUrl ++ "/refresh") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-title>.
getTitle
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff String
getTitle = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/title")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (return . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-window-handle>.
getWindowHandle
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ContextId
getWindowHandle = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/window")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (return . ContextId . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#close-window>.
closeWindow
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff [ContextId]
closeWindow = do
baseUrl <- theRemoteUrlWithSession
httpDelete (baseUrl ++ "/window")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (sequence . map constructFromJson)
>>= (return . map (ContextId . unpack))
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#switch-to-window>.
switchToWindow
:: (Monad eff, Monad (t eff), MonadTrans t, HasContextId a)
=> a
-> WebDriverTT t eff ()
switchToWindow t = do
let contextId = contextIdOf t
baseUrl <- theRemoteUrlWithSession
let !payload = encode $ object [ "handle" .= show contextId ]
httpPost (baseUrl ++ "/window") payload
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-window-handles>.
getWindowHandles
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff [ContextId]
getWindowHandles = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/window/handles")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (sequence . map constructFromJson)
>>= (return . map (ContextId . unpack))
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#switch-to-frame>.
switchToFrame
:: (Monad eff, Monad (t eff), MonadTrans t)
=> FrameReference
-> WebDriverTT t eff ()
switchToFrame ref = do
(baseUrl, format) <- theRequestContext
let
!frame = case ref of
TopLevelFrame -> Null
FrameNumber k -> Number $ fromIntegral k
FrameContainingElement element_id -> String $ pack $ show element_id
!payload = encode $ object
[ "id" .= toJSON frame ]
httpPost (baseUrl ++ "/frame") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#switch-to-parent-frame>.
switchToParentFrame
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
switchToParentFrame = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object []
httpPost (baseUrl ++ "/frame/parent") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect>.
getWindowRect
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff Rect
getWindowRect = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/window/rect")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect>.
setWindowRect
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Rect
-> WebDriverTT t eff Rect
setWindowRect rect = do
baseUrl <- theRemoteUrlWithSession
let !payload = encode rect
httpPost (baseUrl ++ "/window/rect") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#maximize-window>.
maximizeWindow
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff Rect
maximizeWindow = do
baseUrl <- theRemoteUrlWithSession
let !payload = encode $ object []
httpPost (baseUrl ++ "/window/maximize") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#minimize-window>.
minimizeWindow
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff Rect
minimizeWindow = do
baseUrl <- theRemoteUrlWithSession
let !payload = encode $ object []
httpPost (baseUrl ++ "/window/minimize") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#fullscreen-window>.
fullscreenWindow
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff Rect
fullscreenWindow = do
baseUrl <- theRemoteUrlWithSession
let !payload = encode $ object []
httpPost (baseUrl ++ "/window/fullscreen") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-element>.
findElement
:: (Monad eff, Monad (t eff), MonadTrans t)
=> LocationStrategy
-> Selector
-> WebDriverTT t eff ElementRef
findElement strategy selector = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object [ "value" .= selector, "using" .= toJSON strategy ]
httpPost (baseUrl ++ "/element") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= case format of
SpecFormat -> lookupKeyJson _WEB_ELEMENT_ID
ChromeFormat -> lookupKeyJson "ELEMENT"
>>= constructFromJson
>>= (return . ElementRef . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-elements>.
findElements
:: (Monad eff, Monad (t eff), MonadTrans t)
=> LocationStrategy
-> Selector
-> WebDriverTT t eff [ElementRef]
findElements strategy selector = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object [ "value" .= selector, "using" .= toJSON strategy ]
httpPost (baseUrl ++ "/elements") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= case format of
SpecFormat -> mapM (lookupKeyJson _WEB_ELEMENT_ID)
ChromeFormat -> mapM (lookupKeyJson "ELEMENT")
>>= mapM constructFromJson
>>= (return . map (ElementRef . unpack))
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-element-from-element>.
findElementFromElement
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> LocationStrategy
-> Selector
-> a
-> WebDriverTT t eff ElementRef
findElementFromElement strategy selector root = do
(baseUrl, format) <- theRequestContext
let root_id = elementRefOf root
let !payload = encode $ object [ "value" .= selector, "using" .= toJSON strategy ]
httpPost (baseUrl ++ "/element/" ++ show root_id ++ "/element") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= case format of
SpecFormat -> lookupKeyJson _WEB_ELEMENT_ID
ChromeFormat -> lookupKeyJson "ELEMENT"
>>= constructFromJson
>>= (return . ElementRef . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#find-elements-from-element>.
findElementsFromElement
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> LocationStrategy
-> Selector
-> a
-> WebDriverTT t eff [ElementRef]
findElementsFromElement strategy selector root = do
(baseUrl, format) <- theRequestContext
let root_id = elementRefOf root
let !payload = encode $ object [ "value" .= selector, "using" .= toJSON strategy ]
httpPost (baseUrl ++ "/element/" ++ show root_id ++ "/elements") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= case format of
SpecFormat -> mapM (lookupKeyJson _WEB_ELEMENT_ID)
ChromeFormat -> mapM (lookupKeyJson "ELEMENT")
>>= mapM constructFromJson
>>= (return . map (ElementRef . unpack))
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-active-element>.
getActiveElement
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ElementRef
getActiveElement = do
(baseUrl, format) <- theRequestContext
httpGet (baseUrl ++ "/element/active")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= case format of
SpecFormat -> lookupKeyJson _WEB_ELEMENT_ID
ChromeFormat -> lookupKeyJson "ELEMENT"
>>= constructFromJson
>>= (return . ElementRef . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#is-element-selected>.
isElementSelected
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> a
-> WebDriverTT t eff Bool
isElementSelected element = do
let elementRef = show $ elementRefOf element
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/selected")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-attribute>.
getElementAttribute
:: (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)
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
case x of
Null -> return (Left False)
String "true" -> return (Left True)
String attr -> return (Right $ unpack attr)
_ -> throwJsonError $ JsonError "Invalid element attribute response"
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-property>.
getElementProperty
:: (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)
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-css-value>.
getElementCssValue
:: (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)
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-text>.
getElementText
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> a
-> WebDriverTT t eff String
getElementText element = do
let elementRef = show $ elementRefOf element
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/text")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-tag-name>.
getElementTagName
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> a
-> WebDriverTT t eff String
getElementTagName element = do
let elementRef = show $ elementRefOf element
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/name")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-element-rect>.
getElementRect
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> a
-> WebDriverTT t eff Rect
getElementRect element = do
let elementRef = show $ elementRefOf element
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/rect")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#is-element-enabled>.
isElementEnabled
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> a
-> WebDriverTT t eff Bool
isElementEnabled element = do
let elementRef = show $ elementRefOf element
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/enabled")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#element-click>.
elementClick
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> a
-> WebDriverTT t eff ()
elementClick element = do
(baseUrl, format) <- theRequestContext
let elementRef = show $ elementRefOf element
let !payload = encode $ object []
httpPost (baseUrl ++ "/element/" ++ elementRef ++ "/click") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#element-clear>.
elementClear
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> a
-> WebDriverTT t eff ()
elementClear element = do
(baseUrl, format) <- theRequestContext
let elementRef = show $ elementRefOf element
let !payload = encode $ object []
httpPost (baseUrl ++ "/element/" ++ elementRef ++ "/clear") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#element-send-keys>.
elementSendKeys
:: (Monad eff, Monad (t eff), MonadTrans t, HasElementRef a)
=> String
-> a
-> WebDriverTT t eff ()
elementSendKeys text element = do
let elementRef = show $ elementRefOf element
(baseUrl, format) <- theRequestContext
let !payload = encode $ object [ "text" .= text ]
httpPost (baseUrl ++ "/element/" ++ elementRef ++ "/value") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-page-source>.
getPageSource
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff String
getPageSource = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/source")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (return . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-page-source>. Does not dump the page source into the logs. :)
getPageSourceStealth
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff String
getPageSourceStealth = do
baseUrl <- theRemoteUrlWithSession
httpSilentGet (baseUrl ++ "/source")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (return . unpack)
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#execute-script>.
executeScript
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Script
-> [Value]
-> WebDriverTT t eff Value
executeScript script args = do
baseUrl <- theRemoteUrlWithSession
let !payload = encode $ object [ "script" .= script, "args" .= toJSON args ]
httpPost (baseUrl ++ "/execute/sync") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#execute-async-script>.
executeAsyncScript
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Script
-> [Value]
-> WebDriverTT t eff Value
executeAsyncScript script args = do
baseUrl <- theRemoteUrlWithSession
let !payload = encode $ object [ "script" .= script, "args" .= toJSON args ]
httpPost (baseUrl ++ "/execute/async") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-all-cookies>.
getAllCookies
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff [Cookie]
getAllCookies = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/cookie")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= mapM constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-named-cookie>.
getNamedCookie
:: (Monad eff, Monad (t eff), MonadTrans t)
=> CookieName
-> WebDriverTT t eff Cookie
getNamedCookie name = do
baseUrl <- theRemoteUrlWithSession
httpGet (baseUrl ++ "/cookie/" ++ E.encode name)
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie>.
addCookie
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Cookie
-> WebDriverTT t eff ()
addCookie cookie = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object [ "cookie" .= cookie ]
httpSilentPost (baseUrl ++ "/cookie") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#delete-cookie>.
deleteCookie
:: (Monad eff, Monad (t eff), MonadTrans t)
=> CookieName
-> WebDriverTT t eff ()
deleteCookie name = do
(baseUrl, format) <- theRequestContext
httpDelete (baseUrl ++ "/cookie/" ++ E.encode name)
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#delete-all-cookies>.
deleteAllCookies
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
deleteAllCookies = do
(baseUrl, format) <- theRequestContext
httpDelete (baseUrl ++ "/cookie")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | 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 eff, Monad (t eff), MonadTrans t)
=> [Action]
-> 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 eff, Monad (t eff), MonadTrans t)
=> [Action]
-> WebDriverTT t eff ()
performActionsStealth = _performActions True
_performActions
:: (Monad eff, Monad (t eff), MonadTrans t)
=> Bool
-> [Action]
-> WebDriverTT t eff ()
_performActions stealth action = do
(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"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#release-actions>.
releaseActions
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
releaseActions = do
baseUrl <- theRemoteUrlWithSession
httpDelete (baseUrl ++ "/actions")
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#dismiss-alert>.
dismissAlert
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
dismissAlert = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object []
httpPost (baseUrl ++ "/alert/dismiss") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#accept-alert>.
acceptAlert
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff ()
acceptAlert = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object []
httpPost (baseUrl ++ "/alert/accept") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#get-alert-text>.
getAlertText
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff (Maybe String)
getAlertText = do
baseUrl <- theRemoteUrlWithSession
msg <- httpGet (baseUrl ++ "/alert/text")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
case msg of
Null -> return Nothing
String text -> return $ Just (unpack text)
_ -> throwJsonError $ JsonError "Invalid alert text response"
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text>.
sendAlertText
:: (Monad eff, Monad (t eff), MonadTrans t)
=> String
-> WebDriverTT t eff ()
sendAlertText msg = do
(baseUrl, format) <- theRequestContext
let !payload = encode $ object [ "text" .= msg ]
httpPost (baseUrl ++ "/alert/text") payload
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= expectEmptyObject format
return ()
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#take-screenshot>.
takeScreenshot
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff SB.ByteString
takeScreenshot = do
baseUrl <- theRemoteUrlWithSession
result <- httpGet (baseUrl ++ "/screenshot")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (return . B64.decode . encodeUtf8)
case result of
Right img -> return img
Left str -> throwError $ ImageDecodeError str
-- | See <https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot>.
takeElementScreenshot
:: (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
result <- httpGet (baseUrl ++ "/element/" ++ elementRef ++ "/screenshot")
>>= (return . _responseBody)
>>= parseJson
>>= lookupKeyJson "value"
>>= constructFromJson
>>= (return . B64.decode . encodeUtf8)
case result of
Right img -> return img
Left str -> throwError $ ImageDecodeError str
-- | Detect empty responses by response format. Necessary because chromedriver is not strictly spec compliant.
expectEmptyObject
:: (Monad eff, Monad (t eff), MonadTrans t)
=> ResponseFormat
-> Value
-> WebDriverTT t eff Value
expectEmptyObject format value = case format of
SpecFormat -> expectIs (\x -> elem x [Null, object []]) "empty object or null" value
ChromeFormat -> expect Null value
theRequestContext
:: (Monad eff, Monad (t eff), MonadTrans t)
=> WebDriverTT t eff (String, ResponseFormat)
theRequestContext = do
baseUrl <- theRemoteUrlWithSession
format <- fromEnv (_responseFormat . _env)
return (baseUrl, format)