packages feed

webdriver-wrapper-0.2.0.0: src/Test/WebDriverWrapper/Selenium.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}

module Test.WebDriverWrapper.Selenium (startSelenium, getSeleniumIfNeeded) where

import Test.WebDriverWrapper.Constants (defaultSeleniumJarUrl, seleniumPath, downloadPath, geckoDriverPath, seleniumLogPath, chromeDriverPath)
import Test.WebDriverWrapper.Helpers (download)
import System.Directory (createDirectoryIfMissing, doesFileExist)
import Control.Monad (unless)
import GHC.IO.Handle ( Handle, hGetLine, hClose )
import System.Process (ProcessHandle, createProcess)
import Data.String.Interpolate (i)
import System.IO (openFile)
import GHC.IO.IOMode (IOMode(..))
import Control.Retry (retryOnError, RetryStatus (..))
import System.IO.Error (isEOFError)
import UnliftIO.Retry ( constantDelay, limitRetries )
import Data.Foldable.Extra (orM)
import Data.List (isInfixOf)
import System.Process.Run (proc)
import Test.WebDriver.Capabilities (Browser)
import Test.WebDriver (Browser(..))

-- | Starts Selenium and waits for its ok message ( "Selenium Server is up and running" ) to show up at the log file.
-- Takes in a `Browser` (since it needs to use the correct webdriver).
-- Returns the handles for the Selenium process.
startSelenium :: Browser -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
startSelenium browser = do
    geckoPath  <- geckoDriverPath
    chromePath <- chromeDriverPath
    selPath <- seleniumPath
    logFile <- seleniumLogPath
    writeFile logFile "" -- Create file if it doesn't exist, clears it if it does. Needs to happen for "logFileHasReadyMessage" to work correctly. 
    let
        webdriverArgs = case browser of 
            Firefox {} -> [i|-Dwebdriver.gecko.driver=#{geckoPath}|]
            Chrome {}  -> [i|-Dwebdriver.chrome.driver=#{chromePath}|]
            _          -> error "unsuported browser"

        selArgs = webdriverArgs : ["-jar", selPath ,"-log", logFile]
        processParams = proc "java" selArgs
    processHandles <- createProcess processParams
    waitForSeleniumStart
    return processHandles

waitForSeleniumStart :: IO()
waitForSeleniumStart = do
    logFile <- seleniumLogPath
    fileHandle <-  openFile logFile ReadMode
    succeeded <- retryOnError  retryPolicy'
        (\_ e -> return $ isEOFError e)
        (\retryStatus -> if rsIterNumber retryStatus < 599 then logFileHasReadyMessage fileHandle else return False)
    hClose fileHandle
    unless succeeded $ error $ "Couldn't start Selenium successfully. Check " <> logFile <> " for more information."
        where
            -- waits a minute, retrying every 100ms
            retryPolicy' = constantDelay 100000 <> limitRetries 600

logFileHasReadyMessage :: Handle -> IO Bool
logFileHasReadyMessage fileHandle = orM remainingLines
    where
        remainingLines = repeat hasReadyMessage
        hasReadyMessage = (readyMessage `isInfixOf`) <$> hGetLine fileHandle

        readyMessage = "Selenium Server is up and running"

-- | Checks if @selenium@ is in the `downloadPath`. If not, download it. 
getSeleniumIfNeeded :: IO ()
getSeleniumIfNeeded = do
    selPath <- seleniumPath
    hasSelenium  <- doesFileExist selPath
    unless hasSelenium getSelenium

getSelenium :: IO ()
getSelenium = do
    downloadPath' <- downloadPath
    selPath       <- seleniumPath
    createDirectoryIfMissing True downloadPath'
    download defaultSeleniumJarUrl selPath