Skip to content

Commit

Permalink
Working on better dependency injection
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasjm committed Jun 5, 2024
1 parent e03d945 commit ac5fc32
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 259 deletions.
2 changes: 1 addition & 1 deletion sandwich-contexts/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ library:
- Test.Sandwich.Contexts.Nix
- Test.Sandwich.Contexts.PostgreSQL
- Test.Sandwich.Contexts.Waits

- Test.Sandwich.Contexts.Types.Network
- Test.Sandwich.Contexts.Types.S3
- Test.Sandwich.Contexts.Util.Ports
dependencies:
- aeson
- conduit
Expand Down
2 changes: 1 addition & 1 deletion sandwich-contexts/sandwich-contexts.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ library
Test.Sandwich.Contexts.Waits
Test.Sandwich.Contexts.Types.Network
Test.Sandwich.Contexts.Types.S3
Test.Sandwich.Contexts.Util.Ports
other-modules:
Test.Sandwich.Contexts.FakeSmtpServer.Derivation
Test.Sandwich.Contexts.ReverseProxy.TCP
Test.Sandwich.Contexts.Util.Aeson
Test.Sandwich.Contexts.Util.Nix
Test.Sandwich.Contexts.Util.Ports
Test.Sandwich.Contexts.Util.SocketUtil
Test.Sandwich.Contexts.Util.UUID
Paths_sandwich_contexts
Expand Down
3 changes: 3 additions & 0 deletions sandwich-webdriver/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ dependencies:
- http-client
- http-client-tls
- http-conduit
- lens
- lens-regex-pcre
- microlens
- microlens-aeson
- monad-control
Expand All @@ -36,6 +38,7 @@ dependencies:
- retry
- safe
- sandwich >= 0.1.0.3
- sandwich-contexts
- string-interpolate
- text
- time
Expand Down
11 changes: 10 additions & 1 deletion sandwich-webdriver/sandwich-webdriver.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ library
Test.Sandwich.WebDriver.Internal.Binaries.DetectFirefox
Test.Sandwich.WebDriver.Internal.Binaries.DetectPlatform
Test.Sandwich.WebDriver.Internal.Capabilities
Test.Sandwich.WebDriver.Internal.Ports
Test.Sandwich.WebDriver.Internal.Capabilities.Extra
Test.Sandwich.WebDriver.Internal.Screenshots
Test.Sandwich.WebDriver.Internal.StartWebDriver
Test.Sandwich.WebDriver.Internal.Types
Expand Down Expand Up @@ -74,6 +74,8 @@ library
, http-client
, http-client-tls
, http-conduit
, lens
, lens-regex-pcre
, microlens
, microlens-aeson
, monad-control
Expand All @@ -85,6 +87,7 @@ library
, retry
, safe
, sandwich >=0.1.0.3
, sandwich-contexts
, string-interpolate
, text
, time
Expand Down Expand Up @@ -152,6 +155,8 @@ executable sandwich-webdriver-exe
, http-client
, http-client-tls
, http-conduit
, lens
, lens-regex-pcre
, microlens
, microlens-aeson
, monad-control
Expand All @@ -163,6 +168,7 @@ executable sandwich-webdriver-exe
, retry
, safe
, sandwich >=0.1.0.3
, sandwich-contexts
, sandwich-webdriver
, string-interpolate
, text
Expand Down Expand Up @@ -230,6 +236,8 @@ test-suite sandwich-webdriver-test
, http-client
, http-client-tls
, http-conduit
, lens
, lens-regex-pcre
, microlens
, microlens-aeson
, monad-control
Expand All @@ -241,6 +249,7 @@ test-suite sandwich-webdriver-test
, retry
, safe
, sandwich >=0.1.0.3
, sandwich-contexts
, sandwich-webdriver
, string-interpolate
, text
Expand Down
19 changes: 15 additions & 4 deletions sandwich-webdriver/src/Test/Sandwich/WebDriver.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
module Test.Sandwich.WebDriver (
-- * Introducing a WebDriver server
introduceWebDriver
, introduceWebDriverViaNix
, introduceWebDriverOptions
, addCommandLineOptionsToWdOptions

Expand Down Expand Up @@ -43,6 +44,8 @@ import qualified Data.Map as M
import Data.Maybe
import Data.String.Interpolate
import Test.Sandwich
import Test.Sandwich.Contexts.Files
import Test.Sandwich.Contexts.Nix
import Test.Sandwich.Internal
import Test.Sandwich.WebDriver.Class
import Test.Sandwich.WebDriver.Config
Expand All @@ -60,18 +63,26 @@ import UnliftIO.MVar
introduceWebDriver :: (
BaseMonadContext m context
) => WdOptions -> SpecFree (LabelValue "webdriver" WebDriver :> context) m () -> SpecFree context m ()
introduceWebDriver wdOptions = introduce "Introduce WebDriver session" webdriver (allocateWebDriver wdOptions) cleanupWebDriver
introduceWebDriver wdOptions = undefined -- introduce "Introduce WebDriver session" webdriver (allocateWebDriver wdOptions) cleanupWebDriver

introduceWebDriverViaNix :: (
BaseMonadContext m context, HasNixContext context, HasFile context "java"
) => WdOptions -> SpecFree (LabelValue "webdriver" WebDriver :> context) m () -> SpecFree context m ()
introduceWebDriverViaNix wdOptions =
introduce "Introduce WebDriver session" webdriver (allocateWebDriver wdOptions) cleanupWebDriver

-- | Same as introduceWebDriver, but merges command line options into the 'WdOptions'.
introduceWebDriverOptions :: forall a context m. (BaseMonadContext m context, HasCommandLineOptions context a)
introduceWebDriverOptions :: forall a context m. (
BaseMonadContext m context, HasCommandLineOptions context a, HasFile context "java"
)
=> WdOptions -> SpecFree (LabelValue "webdriver" WebDriver :> context) m () -> SpecFree context m ()
introduceWebDriverOptions wdOptions = introduce "Introduce WebDriver session" webdriver alloc cleanupWebDriver
where alloc = do
clo <- getCommandLineOptions
allocateWebDriver (addCommandLineOptionsToWdOptions @a clo wdOptions)

-- | Allocate a WebDriver using the given options.
allocateWebDriver :: (HasBaseContext context, BaseMonad m) => WdOptions -> ExampleT context m WebDriver
allocateWebDriver :: (BaseMonad m, HasBaseContext context, HasFile context "java") => WdOptions -> ExampleT context m WebDriver
allocateWebDriver wdOptions = do
debug "Beginning allocateWebDriver"
dir <- fromMaybe "/tmp" <$> getCurrentFolder
Expand All @@ -80,7 +91,7 @@ allocateWebDriver wdOptions = do
-- | Allocate a WebDriver using the given options and putting logs under the given path.
allocateWebDriver' :: FilePath -> WdOptions -> IO WebDriver
allocateWebDriver' runRoot wdOptions = do
runNoLoggingT $ startWebDriver wdOptions runRoot
runNoLoggingT $ flip runReaderT (undefined :: LabelValue "file-java" (EnvironmentFile "java")) $ startWebDriver wdOptions runRoot

-- | Clean up the given WebDriver.
cleanupWebDriver :: (BaseMonad m) => WebDriver -> ExampleT context m ()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# LANGUAGE OverloadedLists #-}
-- |

module Test.Sandwich.WebDriver.Internal.Capabilities (
-- * Chrome
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE OverloadedLists #-}

module Test.Sandwich.WebDriver.Internal.Capabilities.Extra (
configureHeadlessCapabilities
, configureDownloadCapabilities
) where

import Control.Monad.Catch (MonadMask)
import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
import Control.Monad.Logger
import Control.Monad.Trans.Control (MonadBaseControl)
import qualified Data.Aeson as A
import Data.Function
import qualified Data.List as L
import Data.Maybe
import Data.String.Interpolate
import qualified Data.Text as T
import qualified Data.Vector as V
import GHC.Stack
import Lens.Micro
import Lens.Micro.Aeson
import Test.Sandwich
import Test.Sandwich.WebDriver.Internal.Binaries.DetectChrome (detectChromeVersion)
import Test.Sandwich.WebDriver.Internal.Types
import qualified Test.WebDriver as W
import qualified Test.WebDriver.Firefox.Profile as FF
import UnliftIO.Exception


#if MIN_VERSION_aeson(2,0,0)
import qualified Data.Aeson.Key as A
import qualified Data.Aeson.KeyMap as HM
fromText :: T.Text -> A.Key
fromText = A.fromText
#else
import qualified Data.HashMap.Strict as HM
fromText :: T.Text -> T.Text
fromText = id
#endif


type Constraints m = (HasCallStack, MonadLogger m, MonadUnliftIO m, MonadBaseControl IO m, MonadMask m)

-- | Add headless configuration to the Chrome browser
configureHeadlessCapabilities :: Constraints m => WdOptions -> RunMode -> W.Capabilities -> m W.Capabilities
configureHeadlessCapabilities wdOptions (RunHeadless (HeadlessConfig {..})) caps@(W.Capabilities {W.browser=browser@(W.Chrome {..})}) = do
headlessArg <- liftIO (detectChromeVersion (chromeBinaryPath wdOptions)) >>= \case
Left err -> do
warn [i|Couldn't determine chrome version when configuring headless capabilities (err: #{err}); passing --headless|]
return "--headless"
Right (ChromeVersion (major, _, _, _))
-- See https://www.selenium.dev/blog/2023/headless-is-going-away/
| major >= 110 -> return "--headless=new"
| otherwise -> return "--headless"

let browser' = browser { W.chromeOptions = headlessArg:resolution:chromeOptions }

return (caps { W.browser = browser' })

where
resolution = [i|--window-size=#{w},#{h}|]
(w, h) = fromMaybe (1920, 1080) headlessResolution

-- | Add headless configuration to the Firefox capabilities
configureHeadlessCapabilities _ (RunHeadless (HeadlessConfig {})) caps@(W.Capabilities {W.browser=(W.Firefox {}), W.additionalCaps=ac}) = return (caps { W.additionalCaps = additionalCaps })
where
additionalCaps = case L.findIndex (\x -> fst x == "moz:firefoxOptions") ac of
Nothing -> ("moz:firefoxOptions", A.object [("args", A.Array ["-headless"])]) : ac
Just i' -> let ffOptions' = snd (ac !! i')
& ensureKeyExists "args" (A.Array [])
& ((key "args" . _Array) %~ addHeadlessArg) in
L.nubBy (\x y -> fst x == fst y) (("moz:firefoxOptions", ffOptions') : ac)

ensureKeyExists :: T.Text -> A.Value -> A.Value -> A.Value
ensureKeyExists key' _ val@(A.Object (HM.lookup (fromText key') -> Just _)) = val
ensureKeyExists key' defaultVal (A.Object m@(HM.lookup (fromText key') -> Nothing)) = A.Object (HM.insert (fromText key') defaultVal m)
ensureKeyExists _ _ _ = error "Expected Object in ensureKeyExists"

addHeadlessArg :: V.Vector A.Value -> V.Vector A.Value
addHeadlessArg xs | (A.String "-headless") `V.elem` xs = xs
addHeadlessArg xs = (A.String "-headless") `V.cons` xs

configureHeadlessCapabilities _ (RunHeadless {}) browser = error [i|Headless mode not yet supported for browser '#{browser}'|]
configureHeadlessCapabilities _ _ browser = return browser


-- | Configure download capabilities to set the download directory and disable prompts
-- (since you can't test download prompts using Selenium)
configureDownloadCapabilities :: (
MonadIO m, MonadBaseControl IO m
) => [Char] -> W.Capabilities -> m W.Capabilities
configureDownloadCapabilities downloadDir caps@(W.Capabilities {W.browser=browser@(W.Firefox {..})}) = do
case ffProfile of
Nothing -> return ()
Just _ -> liftIO $ throwIO $ userError [i|Can't support Firefox profile yet.|]

profile <- FF.defaultProfile
& FF.addPref "browser.download.folderList" (2 :: Int)
& FF.addPref "browser.download.manager.showWhenStarting" False
& FF.addPref "browser.download.dir" downloadDir
& FF.addPref "browser.helperApps.neverAsk.saveToDisk" ("*" :: String)
& FF.prepareProfile

return (caps { W.browser = browser { W.ffProfile = Just profile } })
configureDownloadCapabilities downloadDir caps@(W.Capabilities {W.browser=browser@(W.Chrome {..})}) = return $ caps { W.browser=browser' }
where
browser' = browser { W.chromeExperimentalOptions = options }

basePrefs :: A.Object
basePrefs = case HM.lookup "prefs" chromeExperimentalOptions of
Just (A.Object hm) -> hm
Just x -> error [i|Expected chrome prefs to be object, got '#{x}'.|]
Nothing -> mempty

prefs :: A.Object
prefs = basePrefs
& foldl (.) id [HM.insert k v | (k, v) <- downloadPrefs]

options = HM.insert "prefs" (A.Object prefs) chromeExperimentalOptions

downloadPrefs = [("profile.default_content_setting_values.automatic_downloads", A.Number 1)
, ("profile.content_settings.exceptions.automatic_downloads.*.setting", A.Number 1)
, ("download.prompt_for_download", A.Bool False)
, ("download.directory_upgrade", A.Bool True)
, ("download.default_directory", A.String (T.pack downloadDir))]
configureDownloadCapabilities _ browser = return browser
78 changes: 0 additions & 78 deletions sandwich-webdriver/src/Test/Sandwich/WebDriver/Internal/Ports.hs

This file was deleted.

Loading

0 comments on commit ac5fc32

Please sign in to comment.