packages feed

sandwich-webdriver-0.3.0.1: src/Test/Sandwich/WebDriver/Internal/Binaries/Firefox.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}

module Test.Sandwich.WebDriver.Internal.Binaries.Firefox (
  obtainFirefox
  , obtainGeckoDriver

  , FirefoxToUse(..)
  , GeckoDriverToUse(..)
  , GeckoDriverVersion(..)
  ) where

import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Monad.Trans.Except
import Data.String.Interpolate
import qualified Data.Text as T
import Test.Sandwich
import Test.Sandwich.Contexts.Files
import Test.Sandwich.WebDriver.Internal.Binaries.Common
import Test.Sandwich.WebDriver.Internal.Binaries.DetectPlatform
import Test.Sandwich.WebDriver.Internal.Binaries.Firefox.Detect
import Test.Sandwich.WebDriver.Internal.Binaries.Firefox.Types
import UnliftIO.Directory


-- | Manually obtain a firefox binary, according to the 'FirefoxToUse' policy,
obtainFirefox :: (
  MonadReader context m, HasBaseContext context
  , MonadUnliftIO m, MonadLogger m
  ) => FirefoxToUse -> m (Either T.Text FilePath)
obtainFirefox UseFirefoxFromPath = do
  findExecutable "firefox" >>= \case
    Just p -> do
      debug [i|Found firefox: #{p}|]
      return $ Right p
    Nothing -> expectationFailure [i|Couldn't find "firefox" on the PATH|]
obtainFirefox (UseFirefoxAt p) = doesFileExist p >>= \case
  False -> return $ Left [i|Path '#{p}' didn't exist|]
  True -> do
    debug [i|Found firefox: #{p}|]
    return $ Right p
obtainFirefox (UseFirefoxFromNixpkgs nixContext) = do
  debug [i|Building Firefox with Nix|]
  -- ret <- case os of
  --   "darwin" ->
  --     -- The only Firefox version that currently works on Darwin as of 5/5/2025 is firefox-bin
  --     buildNixPackage' nixContext "firefox-bin" >>= (liftIO . defaultFindFile "firefox")
  --   _ ->
  --     getBinaryViaNixPackage' @"firefox" nixContext "firefox"
  ret <- getBinaryViaNixPackage' @"firefox" nixContext "firefox"
  debug [i|Built Firefox: #{ret}|]
  return $ Right ret

-- | Manually obtain a @geckodriver@ binary, according to the 'GeckoDriverToUse' policy,
-- storing it under the provided 'FilePath' if necessary and returning the exact path.
obtainGeckoDriver :: (
  MonadReader context m, HasBaseContext context
  , MonadUnliftIO m, MonadLogger m
  )
  -- | How to obtain @geckodriver@
  => GeckoDriverToUse
  -> m (Either T.Text FilePath)
obtainGeckoDriver (DownloadGeckoDriverFrom toolsDir url) = do
  let path = [i|#{toolsDir}/#{geckoDriverExecutable}|]
  doesFileExist path >>= \case
    True -> do
      debug [i|GeckoDriver already existed at #{path}|]
    False -> do
      debug [i|Downloading GeckoDriver from #{url}...|]
      curlDownloadToPath url path
      debug [i|Downloaded GeckoDriver to #{path}|]
  return $ Right path
obtainGeckoDriver (DownloadGeckoDriverVersion toolsDir geckoDriverVersion) = runExceptT $ do
  let path = getGeckoDriverPath toolsDir geckoDriverVersion
  doesFileExist path >>= \case
    True -> do
      debug [i|GeckoDriver already existed at #{path}|]
      return path
    False -> do
      let downloadUrl = getGeckoDriverDownloadUrl geckoDriverVersion detectPlatform
      debug [i|Downloading GeckoDriver from #{downloadUrl}|]
      ExceptT $ downloadAndUntarballToPath downloadUrl path
      debug [i|Downloaded GeckoDriver to #{path}|]
      return path
obtainGeckoDriver (DownloadGeckoDriverAutodetect toolsDir) = runExceptT $ do
  version <- ExceptT $ liftIO $ getGeckoDriverVersion Nothing
  debug [i|Trying to obtain GeckoDriver version #{version} (autodetected)|]
  ExceptT $ obtainGeckoDriver (DownloadGeckoDriverVersion toolsDir version)
obtainGeckoDriver (UseGeckoDriverAt path) = liftIO (doesFileExist path) >>= \case
  False -> return $ Left [i|Path '#{path}' didn't exist|]
  True -> do
    debug [i|Found GeckoDriver at #{path}|]
    return $ Right path
obtainGeckoDriver (UseGeckoDriverFromNixpkgs nixContext) = Right <$> getBinaryViaNixPackage' @"geckodriver" nixContext "geckodriver"


getGeckoDriverPath :: FilePath -> GeckoDriverVersion -> FilePath
getGeckoDriverPath toolsDir (GeckoDriverVersion (x, y, z)) = [i|#{toolsDir}/geckodrivers/#{x}.#{y}.#{z}/#{geckoDriverExecutable}|]

geckoDriverExecutable :: T.Text
geckoDriverExecutable = case detectPlatform of
  Windows -> "geckodriver.exe"
  _ -> "geckodriver"