Converting a monadic value to an IO in Polysemy

210 views Asked by At

I am trying to build an automated feature testing suite using webdriver and polysemy in Haskell. I've gotten as far as defining proper effects and interpreting them into a webdriver WD monad, but now I'm stuck.

I have a value of type Member BrowserMaster r => Sem r () where BrowserMaster is my custom capability.

And this is the interpreter:

runBrowserMaster :: Members [Embed WD.WD, Embed IO] r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
  ClickElement bmSelector ->
    let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
     in embed action
    {- ... -}

Now I'm wondering how to convert the Embed WD.WD effect into Embed IO, so that I end up with just one.

I tried to craft an interpreter:

runWebDriver :: Member (Embed IO) r => Sem (Embed WD.WD ': r) a -> Sem r a
runWebDriver = interpret $
  \a -> embed $ runSession chromeConfig . finallyClose $ do
      setImplicitWait 60000
      setWindowSize (1024, 768)
      unEmbed a

(Here runSession chromeConfig . finallyClose is a WD a -> IO a)

It does work, but it seems to fire up a new browser session for each of the commands instead of starting it just once, doing everything within and closing.

I have an intuition that it has to do something with resource acquisition and release, but I just cannot get my head around this to be able to put it all together.

1

There are 1 answers

1
Sir4ur0n On BEST ANSWER

Keep in mind that each interpreter will be executed each time an action of the BrowserMaster effect is executed. So every time it runs the runWebDriver interpreter, which explains why it creates, runs and close the session.

I think what you want to do is instead to create/delete the session once, and execute your whole code in this session. Also, since WD is already a wrapper around IO, I think it's unnecessary to embed both effects.

I am not familiar with your code nor the webdriver library, but I assume this would be something along the lines of:

main :: IO ()
main = runSession chromeConfig . finallyClose $ do
  setImplicitWait 60000
  setWindowSize (1024, 768)
  runM . runBrowserMaster $ myBusinessCode

runBrowserMaster :: Member (Embed WD.WD) r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
  ClickElement bmSelector ->
    let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
     in embed action
    {- ... -}

Note: If you need to run some IO code in the interpreter, use liftIO to make it an WD action instead, e.g. liftIO $ putStrLn "Hello world".

PS: I recommend renaming the runBrowserMaster interpreter to something like browserMasterToWD as it better represents what it does: interpret the BrowserMaster effect in terms of an WD action.