Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A haskell "withSubprocess" construct around a do block

I'm working through the Haskell WebDriver selenium package for testing, here.

I have this example:

import Test.WebDriver

firefoxConfig :: WDConfig
firefoxConfig = defaultConfig

main :: IO ()
main = runSession firefoxConfig $ do                      
  openPage "http://google.com"                            
  searchInput <- findElem ( ByCSS "input[type='text']" )  
  sendKeys "Hello, World!" searchInput                    
  submit searchInput                                      
  closeSession                                            

The getting started section makes clear that the selenium client requires a selenium server to communicate with

java -jar selenium-server-standalone-*.jar

Without it running, you get this:

ghci    λ> main
*** Exception: FailedConnectionException2 "127.0.0.1" 4444 False connect: does not exist (Connection refused)

I'd like to wrap my whole test script in a function that initializes the selenium-server, records its pid, and kill(pid) after I run the session. That is, for the duration of my existing main, I'd like to call the java selenium-server into existence, but I'd like it to stop existing as soon as the call finishes.

In python I'd do this by something like defining a __enter__() and an __exit__() with other tear-down stuff, subprocess.Popen, record the id, kill it, then calling

with Browser() as b:
  do_stuff

I get the sense the runSession entity is the kind of thing I would need to replicate to wrap startup and teardown like this in the sense that it takes the firefoxConfig $ do block as an argument and I want to do that, too.

However, I can't quite understand the types from interrogating runSession, how to make this kind of thing:

ghci    λ> :t runSession
runSession
  :: Test.WebDriver.Config.WebDriverConfig conf =>
     conf -> WD a -> IO a

I think I'd be looking for some kind of withMonad I could apply to this which is applied to the do. I think the syntax would be some kind of...

import Test.WebDriver
import System.Process

firefoxConfig :: WDConfig
firefoxConfig = defaultConfig

withBrowser :: Monad a -> Monad a -- maybe this type?
withBrowser = do
  r <- createProcess (proc "java -jar selenium-server-standalone-*.jar" [])
  -- other magic here?

main :: IO ()
main = withBrowser $ runSession firefoxConfig $ do                      
  openPage "http://google.com"                            
  searchInput <- findElem ( ByCSS "input[type='text']" )  
  sendKeys "Hello, World!" searchInput                    
  submit searchInput                                      
  closeSession                                            

How would I achieve this? Is monad right at all? Is there a more Haskell idiom or strategy for this?

like image 681
Mittenchops Avatar asked Dec 08 '16 05:12

Mittenchops


1 Answers

You basically just want bracket from https://hackage.haskell.org/package/base-4.9.0.0/docs/Control-Exception.html#v:bracket .

It allows you to specify setup and teardown IO actions to to run around a third action. It automatically handles feeding the output of the setup action into both the main and teardown actions, do your setup action should just give the PID as it's result, so the teardown action will be told what PID to kill.

Something like:

withBrowser browserAction
  = bracket startSelenium killSelenium (const browserAction)

(Where I've assumed you don't want the main action to have to take an argument for the pid, so I used const to ignore it)

like image 69
Ben Avatar answered Oct 09 '22 18:10

Ben