packages feed

hspec-webdriver 1.1.0 → 1.2.0

raw patch · 3 files changed

+149/−99 lines, 3 filesdep +aesondep ~HUnitdep ~hspecdep ~hspec-corePVP ok

version bump matches the API change (PVP)

Dependencies added: aeson

Dependency ranges changed: HUnit, hspec, hspec-core, webdriver

API changes (from Hackage documentation)

- Test.Hspec.WebDriver: Android :: BrowserDefaults
- Test.Hspec.WebDriver: Chrome :: BrowserDefaults
- Test.Hspec.WebDriver: Firefox :: BrowserDefaults
- Test.Hspec.WebDriver: IE :: BrowserDefaults
- Test.Hspec.WebDriver: IPad :: BrowserDefaults
- Test.Hspec.WebDriver: IPhone :: BrowserDefaults
- Test.Hspec.WebDriver: Opera :: BrowserDefaults
- Test.Hspec.WebDriver: class Show c => TestCapabilities c
- Test.Hspec.WebDriver: class Using a where type family UsingList a
- Test.Hspec.WebDriver: data BrowserDefaults
- Test.Hspec.WebDriver: instance GHC.Classes.Eq Test.Hspec.WebDriver.BrowserDefaults
- Test.Hspec.WebDriver: instance GHC.Enum.Bounded Test.Hspec.WebDriver.BrowserDefaults
- Test.Hspec.WebDriver: instance GHC.Enum.Enum Test.Hspec.WebDriver.BrowserDefaults
- Test.Hspec.WebDriver: instance GHC.Show.Show Test.Hspec.WebDriver.BrowserDefaults
- Test.Hspec.WebDriver: instance Test.Hspec.WebDriver.TestCapabilities Test.Hspec.WebDriver.BrowserDefaults
- Test.Hspec.WebDriver: instance Test.Hspec.WebDriver.Using Test.Hspec.WebDriver.BrowserDefaults
- Test.Hspec.WebDriver: instance Test.Hspec.WebDriver.Using [Test.Hspec.WebDriver.BrowserDefaults]
- Test.Hspec.WebDriver: newCaps :: TestCapabilities c => c -> IO Capabilities
+ Test.Hspec.WebDriver: WdOptions :: Bool -> WdOptions
+ Test.Hspec.WebDriver: [skipRemainingTestsAfterFailure] :: WdOptions -> Bool
+ Test.Hspec.WebDriver: androidCaps :: Capabilities
+ Test.Hspec.WebDriver: chromeCaps :: Capabilities
+ Test.Hspec.WebDriver: data Capabilities :: *
+ Test.Hspec.WebDriver: data WdOptions
+ Test.Hspec.WebDriver: firefoxCaps :: Capabilities
+ Test.Hspec.WebDriver: ieCaps :: Capabilities
+ Test.Hspec.WebDriver: instance Data.Default.Class.Default Test.Hspec.WebDriver.WdOptions
+ Test.Hspec.WebDriver: ipadCaps :: Capabilities
+ Test.Hspec.WebDriver: iphoneCaps :: Capabilities
+ Test.Hspec.WebDriver: operaCaps :: Capabilities
+ Test.Hspec.WebDriver: runWDOptions :: WdOptions -> WD () -> WdExample ()
+ Test.Hspec.WebDriver: runWDWithOptions :: multi -> WdOptions -> WD () -> WdExample multi
- Test.Hspec.WebDriver: WdExample :: multi -> (WD ()) -> WdExample multi
+ Test.Hspec.WebDriver: WdExample :: multi -> WdOptions -> (WD ()) -> WdExample multi
- Test.Hspec.WebDriver: it :: (?loc :: CallStack, Example a) => String -> a -> SpecWith (Arg a)
+ Test.Hspec.WebDriver: it :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)
- Test.Hspec.WebDriver: session :: TestCapabilities cap => String -> ([cap], SpecWith (WdTestSession multi)) -> Spec
+ Test.Hspec.WebDriver: session :: String -> ([Capabilities], SpecWith (WdTestSession multi)) -> Spec
- Test.Hspec.WebDriver: sessionWith :: TestCapabilities cap => WDConfig -> String -> ([cap], SpecWith (WdTestSession multi)) -> Spec
+ Test.Hspec.WebDriver: sessionWith :: WDConfig -> String -> ([(Capabilities, String)], SpecWith (WdTestSession multi)) -> Spec
- Test.Hspec.WebDriver: using :: Using a => a -> SpecWith (WdTestSession multi) -> (UsingList a, SpecWith (WdTestSession multi))
+ Test.Hspec.WebDriver: using :: [caps] -> SpecWith (WdTestSession multi) -> ([caps], SpecWith (WdTestSession multi))

Files

Changelog.md view
@@ -1,3 +1,34 @@+## 1.2.0++*   The way capabilities are handled was simplified.  In the old API (1.1.0 and below) there was a typeclass TestCapabilities+    and you had to create an enumeration (or use the default provided by this library) and then there was a Using typeclass that+    allowed lists or a single capability.  All this complexity and abuse of typeclasses turned out to be unnecessary.  After +    using hspec-webdriver in my own projects for several years, the best way to handle capabilities is in a utility module to have+    a list `allBrowsers :: [Capabilities]` and then each session in the spec just passes `allBrowsers` to `using` (the XKCD exaple+    in the documentation now uses this approach).  The `allBrowsers` value in the utility module can then be edited and changed to+    specify which browsers/caps to use for the examples.++    The actual API changes are as follows:++    * TestCapabilities was removed.+    * BrowserDefaults was removed and instead there are symbols `firefoxCaps`, `chromeCaps`, etc. which you can+      use instead of BrowserDefaults.  Or you can ignore these and create your own capaiblities directly using+      utility functions from the `webdriver` package.+    * The Using typeclass was removed but the `using` helper function still exists as a standalone function.+      You must now pass list of capabilities to `using` but otherwise the usage is the same.  As mentioned above,+      I suggest the argument to `using` is a list defined in a utility module similar to the XKCD example.+      (In my large test code, no call to using needed to be changed).+    * `session` and `sessionWith` changed to take a list of Capabilities, but when used with `using` the output of `using`+      is still the input to `session`.  Also, `sessionWith` takes a descriptive string to be used to better describe+      capabilities.++* In previous versions, as soon as an example had an error the entire session was aborted.  This is still the default,+  but there are now functions `runWDOptions` and `runWDWithOptions` which take a setting which can cause the session to continue+  even if the example has an error.  There is an API change in the definition of the `WdExample` data constructor to take the+  new options, but as long as you were using `runWD` and `runWDWith` and not using `WdExample` directly, there is no change+  needed to your code.  `runWD` and `runWDWith` still have the same behavior as before; as soon as an error occurs, the entire+  session is aborted.+ ## 1.1.0  * Support for webdriver 0.8.  The API in hspec-webdriver itself did not change, but we re-export WebDriver.Commands so I bumped the
Test/Hspec/WebDriver.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings, FlexibleInstances, DeriveDataTypeable, TypeFamilies, CPP #-}+{-# LANGUAGE OverloadedStrings, FlexibleInstances, DeriveDataTypeable, TypeFamilies, CPP, NamedFieldPuns #-} -- | Write hspec tests that are webdriver tests, automatically managing the webdriver sessions. -- -- This module re-exports functions from "Test.Hspec" and "Test.WebDriver.Commands" and it is@@ -10,11 +10,17 @@ -- > -- >import Test.Hspec.WebDriver -- >+-- >allBrowsers :: [Capabilities]+-- >allBrowsers = [firefoxCaps, chromeCaps, ieCaps]+-- >+-- >browsersExceptIE :: [Capabilities]+-- >browsersExceptIE = [firefoxCaps, chromeCaps]+-- > -- >main :: IO () -- >main = hspec $ -- >    describe "XKCD Tests" $ do -- >--- >        session "for 327" $ using Firefox $ do+-- >        session "for 327" $ using allBrowsers $ do -- >            it "opens the page" $ runWD $ -- >                openPage "http://www.xkcd.com/327/" -- >            it "checks hover text" $ runWD $ do@@ -22,7 +28,7 @@ -- >                e `shouldBeTag` "img" -- >                e `shouldHaveAttr` ("title", "Her daughter is named Help I'm trapped in a driver's license factory.") -- >--- >        parallel $ session "for 303" $ using [Firefox, Chrome] $ do+-- >        parallel $ session "for 303" $ using browsersExceptIE $ do -- >            it "opens the page" $ runWD $ -- >                openPage "http://www.xkcd.com/303/" -- >            it "checks the title" $ runWD $ do@@ -34,10 +40,12 @@ -- @\/wd\/hub@ (this is the default). module Test.Hspec.WebDriver(   -- * Webdriver Example-    BrowserDefaults(..)-  , WdExample(..)+    WdExample(..)+  , WdOptions (..)   , runWD+  , runWDOptions   , runWDWith+  , runWDWithOptions   , pending   , pendingWith   , example@@ -46,9 +54,18 @@   , session   , sessionWith   , inspectSession-  , Using(..)+  , using   , WdTestSession +  -- * Default Capabilities+  , firefoxCaps+  , chromeCaps+  , ieCaps+  , operaCaps+  , iphoneCaps+  , ipadCaps+  , androidCaps+   -- * Expectations   , shouldBe   , shouldBeTag@@ -57,9 +74,6 @@   , shouldReturn   , shouldThrow -  -- * Custom Capabilities-  , TestCapabilities(..)-   -- * Re-exports from "Test.Hspec"   , hspec   , Spec@@ -73,6 +87,7 @@    -- * Re-exports from "Test.WebDriver"   , WD+  , Capabilities   , module Test.WebDriver.Commands ) where @@ -83,62 +98,27 @@ import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.State (state, evalState, execState) import Data.Default (Default(..))-import Data.Typeable (Typeable, cast) import Data.IORef (newIORef, writeIORef, readIORef)-import Test.HUnit (assertEqual, assertFailure) import qualified Data.Text as T+import Data.Typeable (Typeable, cast)+import Test.HUnit (assertEqual, assertFailure)+import qualified Data.Aeson as A  #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>), Applicative) import Data.Traversable (traverse) #endif +import qualified Test.Hspec as H import Test.Hspec hiding (shouldReturn, shouldBe, shouldSatisfy, shouldThrow, pending, pendingWith, example) import Test.Hspec.Core.Spec (Result(..), Item(..), Example(..), SpecTree, Tree(..), fromSpecList, runSpecM)-import qualified Test.Hspec as H -import Test.WebDriver (WD)-import Test.WebDriver.Commands+import Test.WebDriver (WD, Capabilities) import qualified Test.WebDriver as W-import qualified Test.WebDriver.Session as W+import Test.WebDriver.Commands import qualified Test.WebDriver.Config as W---- | Webdriver expectations consist of a set of browser 'W.Capabilities' to use and the actual test as--- a 'WD' monad.  The browser capabilities are specified by an enumeration which is an instance of--- 'TestCapabilities'.  The @BrowserDefaults@ enumeration provides items that represent the default set of--- capabilities for each browser (see 'W.defaultCaps').------ To obtain more control over the capabilities (e.g. to test multiple versions of IE or to test--- Firefrox without javascript), you should @import Test.Hspec.WebDriver hiding (BrowserDefaults)@--- and then create your own enumeration which is an instance of 'TestCapabilities' and 'Using'.-data BrowserDefaults = Firefox | Chrome | IE | Opera | IPhone | IPad | Android-    deriving (Eq, Show, Enum, Bounded)---- | Provides information about the browser capabilities used for testing.  If you want more control--- over capabilities, you should hide 'BrowserDefaults' and then make an enumeration of all the--- webdriver capabilities you will be testing with.  For example,------ >data TestCaps = Firefox--- >              | FirefoxWithoutJavascript--- >              | Chrome--- >              | IE8--- >              | IE9--- >   deriving (Show, Eq, Bounded, Enum)------ @TestCaps@ must then be made an instance of @TestCapabilities@.  Also, instances of @Using@--- should be created.-class Show c => TestCapabilities c where-    -- | The capabilities to pass to 'createSession'.-    newCaps :: c -> IO W.Capabilities--instance TestCapabilities BrowserDefaults where-    newCaps Firefox = return $ W.defaultCaps { W.browser = W.firefox }-    newCaps Chrome = return $ W.defaultCaps { W.browser = W.chrome }-    newCaps IE = return $ W.defaultCaps { W.browser = W.ie }-    newCaps Opera = return $ W.defaultCaps { W.browser = W.opera }-    newCaps IPhone = return $ W.defaultCaps { W.browser = W.iPhone }-    newCaps IPad = return $ W.defaultCaps { W.browser = W.iPad }-    newCaps Android = return $ W.defaultCaps { W.browser = W.android }+import qualified Test.WebDriver.Capabilities as W+import qualified Test.WebDriver.Session as W  -- | The state passed between examples inside the mvars. data SessionState multi = SessionState {@@ -161,8 +141,8 @@ -- | A webdriver example. -- -- The webdriver action of type @'WD' ()@ should interact with the webpage using commands from--- "Test.WebDriver.Commands" (which is re-exported from this module) and then use the <#g:2--- expectations> in this module.  It is possible to split up the spec of a single page into multiple+-- "Test.WebDriver.Commands" (which is re-exported from this module) and then use the+-- <#g:4 expectations> in this module.  It is possible to split up the spec of a single page into multiple -- examples where later examples start with the web browser state from the end of the previous -- example.  This is helpful to keep each individual example small and allows the entire spec to be -- described at the beginning with pending examples.@@ -181,13 +161,26 @@ -- session the example should be executed against.  A new session is created every time a new value -- of type @multi@ is seen.  Note that the type system enforces that every example within the -- session has the same type @multi@.-data WdExample multi = WdExample multi (WD ()) | WdPending (Maybe String)+data WdExample multi = WdExample multi WdOptions (WD ()) | WdPending (Maybe String) +-- | Optional options that can be passed to 'runWDOptions'.+data WdOptions = WdOptions {+  -- | As soon as an example fails, skip all remaining tests in the session.  Defaults to True.+  skipRemainingTestsAfterFailure :: Bool+  }++instance Default WdOptions where+  def = WdOptions { skipRemainingTestsAfterFailure = True }+ -- | A shorthand for constructing a 'WdExample' from a webdriver action when you are only testing a -- single browser session at once.  See the XKCD example at the top of the page. runWD :: WD () -> WdExample ()-runWD = WdExample ()+runWD = WdExample () def +-- | A version of runWD that accepts some custom options+runWDOptions :: WdOptions -> WD () -> WdExample ()+runWDOptions = WdExample ()+ -- | Create a webdriver example, specifying which of the multiple sessions the example should be -- executed against.  I suggest you create an enumeration for multi, for example: --@@ -198,7 +191,7 @@ -- >runUser = runWDWith -- > -- >spec :: Spec--- >spec = session "tests some page" $ using Firefox $ do+-- >spec = session "tests some page" $ using [firefoxCaps] $ do -- >    it "does something with Gandolf" $ runUser Gandolf $ do -- >        openPage ... -- >    it "does something with Bilbo" $ runUser Bilbo $ do@@ -211,10 +204,14 @@ -- two sessions.  Note that a session for Legolas will only be created the first time he shows up in -- a call to @runUser@, which might be never.  To share information between the sessions (e.g. some -- data that Gandolf creates that Bilbo should expect), the best way I have found is to use IORefs--- created with 'runIO', and then use implicit parameters to pass the IORefs between examples.+-- created with 'runIO' (wrapped in a utility module). runWDWith :: multi -> WD () -> WdExample multi-runWDWith = WdExample+runWDWith multi = WdExample multi def +-- | A version of runWDWith that accepts some custom options+runWDWithOptions :: multi -> WdOptions -> WD () -> WdExample multi+runWDWithOptions = WdExample+ -- | A pending example. pending :: WdExample multi pending = WdPending Nothing@@ -228,7 +225,7 @@ -- session the expectation is executed against, so a default value is used.  In the case of single -- sessions, the type is @WdExample ()@. example :: Default multi => Expectation -> WdExample multi-example = WdExample def . liftIO+example = WdExample def def . liftIO  -- | Combine the examples nested inside this call into a webdriver session or multiple sessions. -- For each of the capabilities in the list, the examples are executed one at a time in depth-first@@ -248,44 +245,67 @@ -- -- This function uses the default webdriver host (127.0.0.1), port (4444), and basepath -- (@\/wd\/hub@).-session :: TestCapabilities cap => String -> ([cap], SpecWith (WdTestSession multi)) -> Spec-session = sessionWith W.defaultConfig+session :: String -> ([Capabilities], SpecWith (WdTestSession multi)) -> Spec+session msg (caps, spec) = sessionWith W.defaultConfig msg (caps', spec)+  where+    caps' = map f caps+    f c = case A.toJSON (W.browser c) of+      A.String b -> (c, T.unpack b)+      _ -> (c, show c) -- this should not be the case, every browser toJSON is a string  -- | A variation of 'session' which allows you to specify the webdriver configuration.  Note that -- the capabilities in the 'W.WDConfig' will be ignored, instead the capabilities will come from the--- list of 'TestCapabilities'.-sessionWith :: TestCapabilities cap => W.WDConfig -> String -> ([cap], SpecWith (WdTestSession multi)) -> Spec+-- list of 'Capabilities' passed to 'sessionWith'.+--+-- In addition, each capability is paired with a descriptive string which is passed to hspec to+-- describe the example.  By default, 'session' uses the browser name as the description.  'sessionWith'+-- supports a more detailed description so that in the hspec output you can distinguish between+-- capabilities that share the same browser but differ in the details, for example capabilities with and+-- without javascript.+sessionWith :: W.WDConfig -> String -> ([(Capabilities, String)], SpecWith (WdTestSession multi)) -> Spec sessionWith cfg msg (caps, spec) = spec'     where+        procT c = procTestSession cfg (W.getCaps c) spec         spec' = case caps of                     [] -> it msg $ H.pendingWith "No capabilities specified"-                    [c] -> describe (msg ++ " using " ++ show c) $ procTestSession cfg c spec-                    _ -> describe msg $ mapM_ (\c -> describe ("using " ++ show c) $ procTestSession cfg c spec) caps+                    [(c,cDscr)] -> describe (msg ++ " using " ++ cDscr) $ procT c+                    _ -> describe msg $ mapM_ (\(c,cDscr) -> describe ("using " ++ cDscr) $ procT c) caps --- | A typeclass of things which can be converted to a list of capabilities.  It has two uses.--- First, it allows you to create a datatype of grouped capabilities in addition to your actual--- capabilities.  These psudo-caps can be passed to @using@ to convert them to a list of your actual--- capabilities.  Secondly, it allows the word @using@ to be used with 'session' so that the session+-- | A synonym for constructing pairs that allows the word @using@ to be used with 'session' so that the session -- description reads like a sentance. ----- >session "for the home page" $ using Firefox $ do+-- >allBrowsers :: [Capabilities]+-- >allBrowsers = [firefoxCaps, chromeCaps, ieCaps]+-- >+-- >browsersExceptIE :: [Capabilities]+-- >browsersExceptIE = [firefoxCaps, chromeCaps]+-- >+-- >mobileBrowsers :: [Capabilities]+-- >mobileBrowsers = [iphoneCaps, ipadCaps, androidCaps]+-- >+-- >myspec :: Spec+-- >myspec = do+-- >  session "for the home page" $ using allBrowsers $ do -- >    it "loads the page" $ runWD $ do -- >        ... -- >    it "scrolls the carosel" $ runWD $ do -- >        ...--- >session "for the users page" $ using [Firefox, Chrome] $ do+-- >  session "for the users page" $ using browsersExceptIE $ do -- >    ...-class Using a where-    type UsingList a-    using :: a -> SpecWith (WdTestSession multi) -> (UsingList a, SpecWith (WdTestSession multi))--instance Using BrowserDefaults where-    type UsingList BrowserDefaults = [BrowserDefaults]-    using d s = ([d], s)+using :: [caps] -> SpecWith (WdTestSession multi) -> ([caps], SpecWith (WdTestSession multi))+using = (,) -instance Using [BrowserDefaults] where-    type UsingList [BrowserDefaults] = [BrowserDefaults]-    using d s = (d, s)+-- | Default capabilities which can be used in the list passed to 'using'.  I suggest creating a+-- top-level definition such as @allBrowsers@ and @browsersWithoutIE@ such as in the XKCD example at+-- the top of the page, so that you do not specify the browsers in the individual spec.+firefoxCaps, chromeCaps, ieCaps, operaCaps, iphoneCaps, ipadCaps, androidCaps :: Capabilities+firefoxCaps = W.defaultCaps { W.browser = W.firefox }+chromeCaps = W.defaultCaps { W.browser = W.chrome }+ieCaps = W.defaultCaps { W.browser = W.ie }+operaCaps = W.defaultCaps { W.browser = W.opera }+iphoneCaps = W.defaultCaps { W.browser = W.iPhone }+ipadCaps = W.defaultCaps { W.browser = W.iPad }+androidCaps = W.defaultCaps { W.browser = W.android }  data AbortSession = AbortSession     deriving (Show, Typeable)@@ -366,22 +386,20 @@  -- | Convert a spec tree of test items to a spec tree of generic items by creating a single session for -- the entire tree.-procTestSession :: TestCapabilities cap-                => W.WDConfig -> cap -> SpecWith (WdTestSession multi) -> Spec-procTestSession cfg c s = do-    (mvars, cap, trees) <- runIO $ do-        cap <- newCaps c+procTestSession :: W.WDConfig -> Capabilities -> SpecWith (WdTestSession multi) -> Spec+procTestSession cfg cap s = do+    (mvars, trees) <- runIO $ do         trees <- runSpecM s         let cnt = countItems trees         mvars <- replicateM cnt newEmptyMVar-        return (mvars, cap, trees)+        return (mvars, trees)      fromSpecList $ mapWithCounter (procSpecItem cfg {W.wdCapabilities = cap} mvars) trees  instance Eq multi => Example (WdExample multi) where     type Arg (WdExample multi) = WdTestSession multi     evaluateExample (WdPending msg) _ _ _ = return $ Pending msg-    evaluateExample (WdExample multi wd) _ act _ = do+    evaluateExample (WdExample multi (WdOptions {skipRemainingTestsAfterFailure}) wd) _ act _ = do         prevHadError <- newIORef False         aborted <- newIORef False @@ -389,11 +407,11 @@              tstate <- wdTestOpen testsession -            msess <- case (lookup multi $ stSessionMap tstate, stPrevHadError tstate, stPrevAborted tstate) of-                (_, True, _) -> return Nothing-                (_, _, True) -> return Nothing-                (Just s, False, False) -> return $ Just s-                (Nothing, False, False) ->+            msess <- case (lookup multi $ stSessionMap tstate,+                           (stPrevHadError tstate || stPrevAborted tstate) && skipRemainingTestsAfterFailure) of+                (_, True) -> return Nothing+                (Just s, False) -> return $ Just s+                (Nothing, False) ->                     Just <$> stCreateSession tstate                         `onException` wdTestClose testsession tstate { stPrevHadError = True } 
hspec-webdriver.cabal view
@@ -1,5 +1,5 @@ name:              hspec-webdriver-version:           1.1.0+version:           1.2.0 cabal-version:     >= 1.8 build-type:        Simple synopsis:          Write end2end web application tests using webdriver and hspec@@ -29,14 +29,15 @@      build-depends: base                 >= 4          && < 5 -                 , HUnit                >= 1.2 && < 1.4+                 , HUnit                >= 1.2+                 , aeson                >= 0.8                  , data-default         >= 0.5                  , hashable             >= 1.2-                 , hspec-core           >= 2.0 && < 2.3-                 , hspec                >= 2.0 && < 2.3+                 , hspec-core           >= 2.0+                 , hspec                >= 2.0                  , lifted-base          >= 0.2                  , stm                  >= 2.4                  , text                 >= 0.11                  , transformers         >= 0.3                  , unordered-containers >= 0.2-                 , webdriver            >= 0.6.1 && < 0.9+                 , webdriver            >= 0.6.1