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 +31/−0
- Test/Hspec/WebDriver.hs +112/−94
- hspec-webdriver.cabal +6/−5
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