@@ -34,6 +34,7 @@ import Data.Text (Text)
34import Language.Javascript.JSaddle (syncPoint, liftJSM)
34import Language.Javascript.JSaddle (syncPoint, liftJSM)
35import Language.Javascript.JSaddle.Warp
35import Language.Javascript.JSaddle.Warp
36import Network.HTTP.Types (status200)
36import Network.HTTP.Types (status200)
37import Network.Wai
38import Network.Wai
38import Network.WebSockets
39import Network.WebSockets
39import Reflex.Dom.Builder.Immediate (GhcjsDomSpace)
40import Reflex.Dom.Builder.Immediate (GhcjsDomSpace)
@@ -45,7 +46,7 @@ import System.IO.Temp
45import System.Process
46import System.Process
46import System.Timeout
47import System.Timeout
47import Test.HUnit (assertEqual, assertFailure)
48import Test.HUnit (assertEqual, assertFailure)
48−import Test.Hspec (hspec, it, describe, beforeAll, shouldBe)
49+import Test.Hspec (Spec, parallel, hspec, it, describe, shouldBe)
49import Test.WebDriver (WD)
50import Test.WebDriver (WD)
51import qualified Data.ByteString.Lazy as LBS
52import qualified Data.ByteString.Lazy as LBS
@@ -60,8 +61,8 @@ import qualified Test.WebDriver as WD
60testTimeLimit :: Int
61testTimeLimit :: Int
61testTimeLimit = 5 * 1000 * 1000
62testTimeLimit = 5 * 1000 * 1000
63−chromeConfig :: WD.WDConfig
64+chromeConfig :: Text -> WD.WDConfig
64−chromeConfig = WD.useBrowser (WD.chrome { WD.chromeBinary = Just "/run/current-system/sw/bin/chromium", WD.chromeOptions = ["--headless"]}) WD.defaultConfig
65+chromeConfig fp = WD.useBrowser (WD.chrome { WD.chromeBinary = Just $ T.unpack fp, WD.chromeOptions = ["--headless"] }) WD.defaultConfig
66keyMap :: DMap DKey Identity
67keyMap :: DMap DKey Identity
67keyMap = DMap.fromList
68keyMap = DMap.fromList
@@ -105,8 +106,11 @@ instance EqTag DKey Identity where
105 eqTagged Key_Bool Key_Bool = (==)
106 eqTagged Key_Bool Key_Bool = (==)
107main :: IO ()
108main :: IO ()
108−main = hspec $ beforeAll startSeleniumServer $ do
109+main = withSeleniumServer $ \selenium ->
110+ hspec (parallel (tests selenium)) `finally` _selenium_stopServer selenium
112+tests :: Selenium -> Spec
110 describe "text" $ do
114 describe "text" $ do
111 it "works" $ do
115 it "works" $ do
112 testWidgetStatic (checkBodyText "hello world") $ do
116 testWidgetStatic (checkBodyText "hello world") $ do
@@ -1003,15 +1007,29 @@ main = hspec $ beforeAll startSeleniumServer $ do
1003 (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap $ leftmost [postBuildPatch <$ pb, replace]
1007 (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap $ leftmost [postBuildPatch <$ pb, replace]
1004 liftIO $ dmap `shouldBe` keyMap
1008 liftIO $ dmap `shouldBe` keyMap
1010+data Selenium = Selenium
1011+ { _selenium_portNumber :: PortNumber
1012+ , _selenium_stopServer :: IO ()
1006−startSeleniumServer :: IO ()
1015+startSeleniumServer :: PortNumber -> IO (IO ())
1007−startSeleniumServer = do
1016+startSeleniumServer port = do
1008− (_,_,_,ph) <- createProcess $ (proc "selenium-server" [])
1017+ (_,_,_,ph) <- createProcess $ (proc "selenium-server" ["-port", show port])
1009 { std_in = NoStream
1018 { std_in = NoStream
1010 , std_out = NoStream
1019 , std_out = NoStream
1011 , std_err = NoStream
1020 , std_err = NoStream
1013− _ <- forkIO $ print =<< waitForProcess ph
1022+ return $ terminateProcess ph
1024+withSeleniumServer :: (Selenium -> IO ()) -> IO ()
1025+withSeleniumServer f = do
1026+ let port = 4444 -- TODO port <- getFreePort
1027+ stopServer <- startSeleniumServer port
1014 threadDelay $ 1000 * 1000 * 2 -- TODO poll or wait on a a signal to block on
1028 threadDelay $ 1000 * 1000 * 2 -- TODO poll or wait on a a signal to block on
1030+ { _selenium_portNumber = port
1031+ , _selenium_stopServer = stopServer
1016triggerEventWithChan :: (Reflex t, TriggerEvent t m, Prerender t m) => Chan a -> m (Event t a)
1034triggerEventWithChan :: (Reflex t, TriggerEvent t m, Prerender t m) => Chan a -> m (Event t a)
1017triggerEventWithChan chan = do
1035triggerEventWithChan chan = do
@@ -1088,13 +1106,14 @@ testWidget' beforeJS afterSwitchover bodyWidget = maybe (error "test timed out")
1088 mainHydrationWidgetWithSwitchoverAction (putMVar waitUntilSwitchover ()) (pure ()) $ bodyWidget
1106 mainHydrationWidgetWithSwitchoverAction (putMVar waitUntilSwitchover ()) (pure ()) $ bodyWidget
1089 syncPoint
1107 syncPoint
1090 application <- jsaddleOr defaultConnectionOptions entryPoint $ \_ sendResponse -> sendResponse $ responseLBS status200 [] $ "<!doctype html>\n" <> LBS.fromStrict html
1108 application <- jsaddleOr defaultConnectionOptions entryPoint $ \_ sendResponse -> sendResponse $ responseLBS status200 [] $ "<!doctype html>\n" <> LBS.fromStrict html
1091− --port <- randomRIO (3000, 50000)
1109+ port <- getFreePort
1092− let port = 3911 -- TODO
1093− let settings = Warp.setPort port Warp.defaultSettings
1110+ let settings = Warp.setPort (fromIntegral $ toInteger port) Warp.defaultSettings
1094 -- hSilence to get rid of ConnectionClosed logs
1111 -- hSilence to get rid of ConnectionClosed logs
1095 jsaddleWarp = forkIO $ hSilence [stderr] $ Warp.runSettings settings application
1112 jsaddleWarp = forkIO $ hSilence [stderr] $ Warp.runSettings settings application
1096 bracket jsaddleWarp killThread $ \_ -> do
1113 bracket jsaddleWarp killThread $ \_ -> do
1114+ browserPath <- T.strip . T.pack <$> readProcess "which" [ "chromium" ] ""
1115+ when (T.null browserPath) $ fail "No browser was found"
1097− WD.runSession chromeConfig . WD.finallyClose $ do
1116+ WD.runSession (chromeConfig browserPath) . WD.finallyClose $ do
1098 WD.openPage $ "http://localhost:" <> show port
1117 WD.openPage $ "http://localhost:" <> show port
1099 a <- beforeJS
1118 a <- beforeJS
1100 liftIO $ putMVar waitBeforeJS ()
1119 liftIO $ putMVar waitBeforeJS ()
@@ -1102,6 +1121,17 @@ testWidget' beforeJS afterSwitchover bodyWidget = maybe (error "test timed out")
1102 liftIO $ threadDelay 100000 -- wait a bit
1121 liftIO $ threadDelay 100000 -- wait a bit
1103 afterSwitchover a
1122 afterSwitchover a
1124+-- TODO: Should this be part of more widely used module?
1125+getFreePort :: MonadIO m => m PortNumber
1126+getFreePort = liftIO $ withSocketsDo $ do
1127+ addr:_ <- getAddrInfo (Just defaultHints) (Just "127.0.0.1") (Just "0")
1128+ bracket (open addr) close socketPort
1131+ sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
1132+ bind sock (addrAddress addr)
1105testWidgetDebug :: (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> IO ()
1135testWidgetDebug :: (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> IO ()
1106testWidgetDebug bodyWidget = do
1136testWidgetDebug bodyWidget = do
1107 let staticApp = do
1137 let staticApp = do