packages feed

webdriver-angular 0.1.1 → 0.1.2

raw patch · 8 files changed

+363/−261 lines, 8 filesdep +hspec-webdriverdep −HUnitdep −lifted-basedep ~transformers

Dependencies added: hspec-webdriver

Dependencies removed: HUnit, lifted-base

Dependency ranges changed: transformers

Files

js/angular-clientsidescripts.js view
@@ -76,23 +76,24 @@ };  /**- * Find an array of elements matching a row within an ng-repeat. Always returns- * an array of only one element.+ * Find an array of elements matching a row within an ng-repeat.+ * Always returns an array of only one element for plain old ng-repeat.+ * Returns an array of all the elements in one segment for ng-repeat-start.  *  * arguments[0] {Element} The scope of the search.  * arguments[1] {string} The text of the repeater, e.g. 'cat in cats'.  * arguments[2] {number} The row index.  *- * @return {Array.<Element>} An array of a single element, the row of the- *     repeater.+ * @return {Array.<Element>} The row of the repeater, or an array of elements+ *     in the first row in the case of ng-repeat-start.  */  clientSideScripts.findRepeaterRows = function() {   var using = arguments[0] || document;   var repeater = arguments[1];   var index = arguments[2]; -  var rows = [];   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];+  var rows = [];   for (var p = 0; p < prefixes.length; ++p) {     var attr = prefixes[p] + 'repeat';     var repeatElems = using.querySelectorAll('[' + attr + ']');@@ -103,7 +104,29 @@       }     }   }-  return [rows[index]];+  // multiRows is an array of arrays, where each inner array contains+  // one row of elements.+  var multiRows = [];+  for (var p = 0; p < prefixes.length; ++p) {+    var attr = prefixes[p] + 'repeat-start';+    var repeatElems = using.querySelectorAll('[' + attr + ']');+    attr = attr.replace(/\\/g, '');+    for (var i = 0; i < repeatElems.length; ++i) {+      if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {+        var elem = repeatElems[i];+        var row = [];+        while (elem.nodeType != 8 ||+            elem.nodeValue.indexOf(repeater) == -1) {+          if (elem.nodeType == 1) {+            row.push(elem);+          }+          elem = elem.nextSibling;+        }+        multiRows.push(row);+      }+    }+  }+  return [rows[index]].concat(multiRows[index]);  };   /**@@ -130,6 +153,22 @@       }     }   }+  for (var p = 0; p < prefixes.length; ++p) {+    var attr = prefixes[p] + 'repeat-start';+    var repeatElems = using.querySelectorAll('[' + attr + ']');+    attr = attr.replace(/\\/g, '');+    for (var i = 0; i < repeatElems.length; ++i) {+      if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {+        var elem = repeatElems[i];+        var row = [];+        while (elem.nodeType != 8 ||+            elem.nodeValue.indexOf(repeater) == -1) {+          rows.push(elem);+          elem = elem.nextSibling;+        }+      }+    }+  }   return rows;  }; @@ -162,14 +201,51 @@       }     }   }+  // multiRows is an array of arrays, where each inner array contains+  // one row of elements.+  var multiRows = [];+  for (var p = 0; p < prefixes.length; ++p) {+    var attr = prefixes[p] + 'repeat-start';+    var repeatElems = using.querySelectorAll('[' + attr + ']');+    attr = attr.replace(/\\/g, '');+    for (var i = 0; i < repeatElems.length; ++i) {+      if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {+        var elem = repeatElems[i];+        var row = [];+        while (elem.nodeType != 8 ||+            (elem.nodeValue && elem.nodeValue.indexOf(repeater) == -1)) {+          if (elem.nodeType == 1) {+            row.push(elem);+          }+          elem = elem.nextSibling;+        }+        multiRows.push(row);+      }+    }+  }   var row = rows[index];+  var multiRow = multiRows[index];   var bindings = [];-  if (row.className.indexOf('ng-binding') != -1) {-    bindings.push(row);+  if (row) {+    if (row.className.indexOf('ng-binding') != -1) {+      bindings.push(row);+    }+    var childBindings = row.getElementsByClassName('ng-binding');+    for (var i = 0; i < childBindings.length; ++i) {+      bindings.push(childBindings[i]);+    }   }-  var childBindings = row.getElementsByClassName('ng-binding');-  for (var i = 0; i < childBindings.length; ++i) {-    bindings.push(childBindings[i]);+  if (multiRow) {+    for (var i = 0; i < multiRow.length; ++i) {+      rowElem = multiRow[i];+      if (rowElem.className.indexOf('ng-binding') != -1) {+        bindings.push(rowElem);+      }+      var childBindings = rowElem.getElementsByClassName('ng-binding');+      for (var j = 0; j < childBindings.length; ++j) {+        bindings.push(childBindings[j]);+      }+    }   }   for (var i = 0; i < bindings.length; ++i) {     var dataBinding = angular.element(bindings[i]).data('$binding');@@ -210,8 +286,30 @@       }     }   }+  // multiRows is an array of arrays, where each inner array contains+  // one row of elements.+  var multiRows = [];+  for (var p = 0; p < prefixes.length; ++p) {+    var attr = prefixes[p] + 'repeat-start';+    var repeatElems = using.querySelectorAll('[' + attr + ']');+    attr = attr.replace(/\\/g, '');+    for (var i = 0; i < repeatElems.length; ++i) {+      if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {+        var elem = repeatElems[i];+        var row = [];+        while (elem.nodeType != 8 ||+            (elem.nodeValue && elem.nodeValue.indexOf(repeater) == -1)) {+          if (elem.nodeType == 1) {+            row.push(elem);+          }+          elem = elem.nextSibling;+        }+        multiRows.push(row);+      }+    }+  }+  var bindings = [];   for (var i = 0; i < rows.length; ++i) {-    var bindings = [];     if (rows[i].className.indexOf('ng-binding') != -1) {       bindings.push(rows[i]);     }@@ -219,21 +317,34 @@     for (var k = 0; k < childBindings.length; ++k) {       bindings.push(childBindings[k]);     }-    for (var j = 0; j < bindings.length; ++j) {-      var dataBinding = angular.element(bindings[j]).data('$binding');-      if(dataBinding) {-        var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding;-        if (bindingName.indexOf(binding) != -1) {-          matches.push(bindings[j]);-        }+  }+  for (var i = 0; i < multiRows.length; ++i) {+    for (var j = 0; j < multiRows[i].length; ++j) {+      var elem = multiRows[i][j];+      if (elem.className.indexOf('ng-binding') != -1) {+        bindings.push(elem);       }+      var childBindings = elem.getElementsByClassName('ng-binding');+      for (var k = 0; k < childBindings.length; ++k) {+        bindings.push(childBindings[k]);+      }     }   }+  for (var j = 0; j < bindings.length; ++j) {+    var dataBinding = angular.element(bindings[j]).data('$binding');+    if (dataBinding) {+      var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding;+      if (bindingName.indexOf(binding) != -1) {+        matches.push(bindings[j]);+      }+    }+  }   return matches; };  /**  * Find an input elements by model name.+ * DEPRECATED - use findByModel  *  * arguments[0] {Element} The scope of the search.  * arguments[1] {string} The model name.@@ -249,6 +360,27 @@     var inputs = using.querySelectorAll(selector);     if (inputs.length) {       return inputs;+    }+  }+};++/**+ * Find a elements by model name.+ *+ * arguments[0] {Element} The scope of the search.+ * arguments[1] {string} The model name.+ *+ * @return {Array.<Element>} The matching elements.+ */+clientSideScripts.findByModel = function() {+  var using = arguments[0] || document;+  var model = arguments[1];+  var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];+  for (var p = 0; p < prefixes.length; ++p) {+    var selector = '[' + prefixes[p] + 'model="' + model + '"]';+    var elements = using.querySelectorAll(selector);+    if (elements.length) {+      return elements;     }   } };
src/Test/WebDriver/Commands/Angular.hs view
@@ -25,9 +25,11 @@     , getLocationAbsUrl     ) where +import Control.Applicative ((<$>)) import Control.Monad.IO.Class (liftIO, MonadIO) import Control.Exception (throwIO, Exception)-import Control.Applicative ((<$>))+import Data.Maybe (catMaybes)+import Data.Monoid ((<>)) import Data.Typeable (Typeable) import Test.WebDriver.Classes import Test.WebDriver.Commands@@ -57,21 +59,32 @@     case (x, A.fromJSON x) of         (A.Null, _) -> return []         (A.Array _, A.Success [A.Null]) -> return []-        _ -> fromJSON' x+        _ -> catMaybes <$> fromJSON' x -- parse as [Maybe Element] and drop the nothings because+                                       -- looking up ByRow returns Nulls in the list. +{- asyncCS :: (WebDriver wd, A.FromJSON a) => T.Text -> [JSArg] -> wd (Maybe a) asyncCS script arg = asyncJS arg body     where         body = maybe (error $ "Unable to find " ++ T.unpack script) id $ M.lookup script cs+-}  -- | Wait until Angular has finished rendering before continuing.  @False@ indicates the timeout -- was hit (see 'setScriptTimeout') and we stopped waiting and @True@ means that angular has -- finished rendering.-waitForAngular :: WebDriver wd => T.Text -- ^ CSS selector to element which has ng-app-                               -> wd Bool+waitForAngular :: (MonadIO wd, WebDriver wd) +               => T.Text -- ^ CSS selector to element which has ng-app+               -> wd Bool waitForAngular sel = do-    a <- asyncCS "waitForAngular" [JSArg sel] :: WebDriver wd => wd (Maybe ())-    return $ maybe False (const True) a+    --a <- asyncCS "waitForAngular" [JSArg sel] :: WebDriver wd => wd (Maybe +    +    let body = maybe (error $ "Unable to find waitForAngular") id $ M.lookup "waitForAngular" cs+        body' = "var oldDone = arguments[1]; arguments[1] = function(e) { oldDone(e || true); };" <> body+    a <- asyncJS [JSArg sel] body'+    case a of+        Nothing -> return False+        Just (A.Bool True) -> return True+        Just _ -> liftIO $ throwIO $ NgException $ "Error waiting for angular: " ++ show a  -- | Exceptions of this type will be thrown when an element is unable to be located. data NgException = NgException String@@ -86,12 +99,10 @@  data NgSelector =      ByBinding T.Text -- ^ Argument is the binding, e.g. {{dog.name}}-  | ByModel T.Text   -- ^ Argument is the model name.  This is just a combination of 'ByInput', 'ByTextarea', and 'BySelect'-  | ByInput T.Text   -- ^ Input element with matching model name, e.g. @\<input ng-model=\"name\" ...\>@.-  | ByTextarea T.Text -- ^ Textarea element with matching model name, e.g. @\<textarea ng-model=\"name\" ... \>@-  | BySelect T.Text   -- ^ Select element with matching model name, e.g. @\<select ng-model=\"name\" ... \>@-  | BySelectedOption T.Text -- ^ Selected options with a select element matching the modelname.  That is,-                          --   The @\<option:checked\>@ elements within a @\<select ng-model=\"name\" ... \>@.+  | ByModel T.Text   -- ^ Argument is the model name.  Searches for elements with the @ng-model=\"name\"@ attribute.+  | BySelectedOption T.Text -- ^ Argument is a model name. Searches for selected options within a select element+                            --   matching the modelname.  That is, the @\<option:checked\>@ elements within a +                            --   @\<select ng-model=\"name\" ... \>@.   deriving (Show,Eq)  data NgRepeater =@@ -122,14 +133,8 @@  findNg' :: WebDriver wd => JSArg -> NgSelector -> wd [Element] findNg' e (ByBinding name) = execElems "findBindings" [e, JSArg name]-findNg' e (ByInput name) = execElems "findInputs" [e, JSArg name]-findNg' e (ByTextarea name) = execElems "findTextareas" [e, JSArg name]-findNg' e (BySelect name) = execElems "findSelects" [e, JSArg name]+findNg' e (ByModel name) = execElems "findByModel" [e, JSArg name] findNg' e (BySelectedOption name) = execElems "findSelectedOptions" [e, JSArg name]-findNg' e (ByModel name) = concat <$> sequence [ findNg' e $ ByInput name-                                               , findNg' e $ ByTextarea name-                                               , findNg' e $ BySelect name-                                               ]  -- | Find an element from the document which matches the Angular repeater.  If zero or more than one -- element are returned, an exception of type 'NgException' is thrown.
+ test/ManagerSpecs.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-}+module ManagerSpecs where++import Control.Concurrent (threadDelay)+import Test.Hspec.WebDriver+import Test.WebDriver.Commands.Angular++mSpecs :: Spec+mSpecs = describe "session manager tests" $ do+    parallel $ it "runs in parallel" $ using [Firefox, Chrome] $ do+        openPage "http://localhost:3456/index.html"+        waitForAngular "body" `shouldReturn` True++        i <- findNg $ ByModel "xxx"+        i `shouldBeTag` "input"+        sendKeys "John" i++        liftIO $ threadDelay $ 25 * 10^(6::Int)++        sendKeys " Mark" i+        xHead <- findNg $ ByBinding "{{xxx}}"+        xHead `shouldBeTag` "h1"+        xHead `shouldHaveText` "X John Mark"++    parallel $ do+        describe "Firefox 1" $+            it "waits for a while" $ using Firefox $ do+                openPage "http://localhost:3456/index.html"+                waitForAngular "body" `shouldReturn` True+                +                i <- findNg $ ByModel "xxx"+                sendKeys "Firefox 1" i+                +                liftIO $ threadDelay $ 2 * 10^(6::Int)++        describe "Firefox 2" $+            it "waits for a while" $ using Firefox $ do+                openPage "http://localhost:3456/index.html"+                waitForAngular "body" `shouldReturn` True+                +                i <- findNg $ ByModel "xxx"+                sendKeys "Firefox 2" i+                +                liftIO $ threadDelay $ 2 * 10^(6::Int)++        describe "Firefox 3" $+            it "waits for a while" $ using Firefox $ do+                openPage "http://localhost:3456/index.html"+                waitForAngular "body" `shouldReturn` True+                +                i <- findNg $ ByModel "xxx"+                sendKeys "Firefox 3" i+                +                liftIO $ threadDelay $ 2 * 10^(6::Int)+
+ test/NgSpecs.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-}+module NgSpecs where++import Test.Hspec.WebDriver+import Test.WebDriver.Commands.Angular++ngSpecs :: Spec+ngSpecs = describe "Angular webdriver commands" $ do+    it "finds elements by binding" $ using Firefox $ do+        openPage "http://localhost:3456/index.html"+        waitForAngular "body" `shouldReturn` True++        binding <- findNg $ ByBinding "{{a}}"+        binding `shouldBeTag` "h1"+        binding `shouldHaveText` "Hello A!"++    it "does not find missing model elements" $ using Firefox $ do+        findNgs (ByModel "qqq") `shouldReturn` []+        findNg (ByModel "qqq") `shouldThrow` NgException "Selector ByModel \"qqq\" returned []"++    it "finds input elements" $ using Firefox $ do+        i <- findNg $ ByModel "xxx"+        i `shouldBeTag` "input"+        sendKeys "John" i++        xHead <- findNg $ ByBinding "{{xxx}}"+        xHead `shouldBeTag` "h1"+        xHead `shouldHaveText` "X John"++    it "finds textarea elements" $ using Firefox $ do+        t <- findNg $ ByModel "yyy"+        t `shouldBeTag` "textarea"+        sendKeys "Mark" t++        yHead <- findNg $ ByBinding "{{yyy}}"+        yHead `shouldBeTag` "h2"+        yHead `shouldHaveText` "Y Mark"++    it "finds select elements" $ using Firefox $ do+        s <- findNg $ ByModel "zzz"+        s `shouldBeTag` "select"+        opt <- findElemFrom s $ ByCSS "option[value=\"Bar\"]"+        click opt++        zHead <- findNg $ ByBinding "{{zzz}}"+        zHead `shouldBeTag` "h3"+        zHead `shouldHaveText` "Z Bar"++    it "finds selected option inside select opt" $ using Firefox $ do+        opt <- findNg $ BySelectedOption "zzz"+        opt `shouldBeTag` "option"+        opt `shouldHaveText` "Bar"++    it "finds model elements of different types" $ using Firefox $ do+        [i, t, s] <- findNgs $ ByModel "name"+        i `shouldBeTag` "input"+        t `shouldBeTag` "textarea"+        s `shouldBeTag` "select"++    it "finds all repeater rows" $ using Firefox $ do+        [r1, r2, r3] <- findRepeaters $ ByRows "dog in dogs"+        mapM_ (`shouldBeTag`"li") [r1, r2, r3]+        r1 `shouldHaveText` "Spot mutt"+        r2 `shouldHaveText` "Spike poodle"+        r3 `shouldHaveText` "Jupiter bulldog"++        findRepeaters (ByRows "cat in cats") `shouldReturn` []+        findRepeater (ByRows "cat in cats") `shouldThrow` NgException "Selector ByRows \"cat in cats\" returned []"++    it "finds a single repeater row" $ using Firefox $ do+        r2 <- findRepeater $ ByRow "dog in dogs" 1+        r2 `shouldBeTag` "li"+        r2 `shouldHaveText` "Spike poodle"++        findRepeaters (ByRow "cat in cats" 1) `shouldReturn` []+        findRepeater (ByRow "cat in cats" 1) `shouldThrow` NgException "Selector ByRow \"cat in cats\" 1 returned []"++    it "finds a repeater column" $ using Firefox $ do+        [c1, c2, c3] <- findRepeaters $ ByColumn "dog in dogs" "{{dog.name}}"+        mapM_ (`shouldBeTag`"span") [c1, c2, c3]++        c1 `shouldHaveText` "Spot"+        c2 `shouldHaveText` "Spike"+        c3 `shouldHaveText` "Jupiter"++        findRepeaters (ByColumn "cat in cats" "{{cat.name}}") `shouldReturn` []+        findRepeater (ByColumn "cat in cats" "{{cat.name}}") `shouldThrow`+            NgException "Selector ByColumn \"cat in cats\" \"{{cat.name}}\" returned []"++    it "finds a repeater by row and column" $ using Firefox $ do+        c2 <- findRepeater $ ByRowAndCol "dog in dogs" 1 "{{dog.breed}}"+        c2 `shouldBeTag` "span"+        c2 `shouldHaveText` "poodle"++        findRepeaters (ByRowAndCol "cat in cats" 12 "{{cat.name}}") `shouldReturn` []+        findRepeater (ByRowAndCol "cat in cats" 22 "{{cat.name}}") `shouldThrow`+            NgException "Selector ByRowAndCol \"cat in cats\" 22 \"{{cat.name}}\" returned []"++    it "evaluates an angular expression" $ using Firefox $ do+        e <- findNg $ ByBinding "{{a}}"+        ngEvaluate e "cost | number:2" `shouldReturn` ("12.60" :: String)++    it "loads the location url" $ using Firefox $ do+        getLocationAbsUrl "body" `shouldReturn` "http://localhost:3456/index.html"++    it "loads {{cost}} from document" $ using Firefox $ do+        [s1, s2] <- findNgs $ ByBinding "{{cost}}"+        s1 `shouldBeTag` "span"+        s2 `shouldBeTag` "span"+        s1 `shouldHaveAttr` ("id", "span-one")+        s2 `shouldHaveAttr` ("id", "span-two")++    it "loads {{cost}} only from one element" $ using Firefox $ do+        d <- findElem $ ById "one"+        d `shouldBeTag` "div"+        s1 <- findNgFrom d $ ByBinding "{{cost}}"+        s1 `shouldBeTag` "span"+        s1 `shouldHaveAttr` ("id", "span-one")
− test/Specs.hs
@@ -1,204 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Specs where--import Test.WebDriver.Commands-import Test.WebDriver.Commands.Angular--import Data.Monoid-import Control.Monad.IO.Class (liftIO)-import Control.Exception.Lifted (try, Exception)-import Test.Hspec.Core (Example(..), Result(..))-import Test.HUnit (assertEqual, assertFailure)-import Test.Hspec hiding (shouldReturn, shouldBe, shouldSatisfy, shouldThrow)-import qualified Test.Hspec as H-import qualified Test.WebDriver as W-import qualified Test.WebDriver.Classes as W-import qualified Data.Text as T--data MySession = Firefox-            -- | Chrome--sessionCaps :: MySession -> W.Capabilities-sessionCaps Firefox = W.defaultCaps--matchingCaps :: MySession -> W.Capabilities -> Bool-matchingCaps Firefox (W.Capabilities { W.browser = W.Firefox _ _ _ }) = True-matchingCaps _ _ = False--data WithSession = WithSession MySession (W.WD ())-instance Example WithSession where-    evaluateExample (WithSession stype w) _ action = action (W.runWD session w') >> return Success-        where-            session = W.defaultSession-            w' = do ss <- sessions-                    case filter (\(_, caps) -> matchingCaps stype caps) ss of-                        ((s, _):_) -> W.putSession $ session { W.wdSessId = Just s }-                        _ -> do s <- createSession $ sessionCaps stype-                                W.putSession s-                    w-                        -shouldBe :: (Show a, Eq a) => a -> a -> W.WD ()-x `shouldBe` y = liftIO $ x `H.shouldBe` y--shouldBeTag :: Element -> T.Text -> W.WD ()-e `shouldBeTag` name = do-    t <- tagName e-    liftIO $ assertEqual ("tag of " ++ show e) name t--shouldHaveText :: Element -> T.Text -> W.WD ()-e `shouldHaveText` txt = do-    t <- getText e-    liftIO $ assertEqual ("text of " ++ show e) txt t--shouldHaveAttr :: Element -> (T.Text, T.Text) -> W.WD ()-e `shouldHaveAttr` (a, txt) = do-    t <- attr e a-    liftIO $ assertEqual ("attribute " ++ T.unpack a ++ " of " ++ show e) (Just txt) t--shouldReturn :: (Show a, Eq a) => W.WD a -> a -> W.WD ()-action `shouldReturn` expected = action >>= (\a -> liftIO $ a `H.shouldBe` expected)--shouldThrow :: (Show e, Eq e, Exception e) => W.WD a -> e -> W.WD ()-shouldThrow w expected = do-    r <- try w-    case r of-        Left err -> err `shouldBe` expected-        Right _ -> liftIO $ assertFailure $ "did not get expected exception " ++ show expected--assertName :: T.Text -> W.WD ()-assertName n = do-    e <- findNg $ ByBinding "{{name}}"-    e `shouldBeTag` "h2"-    e `shouldHaveText` ("The name is " <> n)--specs :: Spec-specs = describe "Angular webdriver commands" $ do-    it "finds elements by binding" $ WithSession Firefox $ do-        openPage "http://localhost:3456/index.html"-        _ <- waitForAngular "body" --`shouldReturn` True--        binding <- findNg $ ByBinding "{{a}}"-        binding `shouldBeTag` "h1"-        binding `shouldHaveText` "Hello A!"--    it "finds input elements" $ WithSession Firefox $ do-        i <- findNg $ ByInput "name"-        i `shouldBeTag` "input"-        sendKeys "John" i-        assertName "John"--        i2 <- findNg $ ByInput "xxx"-        i2 `shouldBeTag` "input"--        findNgs (ByInput "yyy") `shouldReturn` []-        findNg (ByInput "yyy") `shouldThrow` NgException "Selector ByInput \"yyy\" returned []"--    it "finds textarea elements" $ WithSession Firefox $ do-        openPage "http://localhost:3456/index.html"-        _ <- waitForAngular "body" --`shouldReturn` True--        t <- findNg $ ByTextarea "name"-        t `shouldBeTag` "textarea"-        sendKeys "Mark" t-        assertName "Mark"--        t2 <- findNg $ ByTextarea "yyy"-        t2 `shouldBeTag` "textarea"-        sendKeys "Bob" t2-        assertName "Mark"--        findNgs (ByTextarea "xxx") `shouldReturn` []-        findNg (ByTextarea "xxx") `shouldThrow` NgException "Selector ByTextarea \"xxx\" returned []"--    it "finds select elements" $ WithSession Firefox $ do-        s <- findNg $ BySelect "name"-        s `shouldBeTag` "select"-        opt <- findElemFrom s $ ByCSS "option[value=\"Goodbye\"]"-        click opt-        assertName "Goodbye"--        opt' <- findNg $ BySelectedOption "name"-        opt' `shouldBeTag` "option"-        opt' `shouldHaveText` "Goodbye"--        findNgs (BySelect "xxx") `shouldReturn` []--    it "finds model elements of different types" $ WithSession Firefox $ do-        [i, t, s] <- findNgs $ ByModel "name"-        i `shouldBeTag` "input"-        t `shouldBeTag` "textarea"-        s `shouldBeTag` "select"-        i `shouldHaveAttr` ("value", "Goodbye")-        t `shouldHaveAttr` ("value", "Goodbye")--    it "finds a single module element" $ WithSession Firefox $ do-        i <- findNg $ ByModel "xxx"-        i `shouldBeTag` "input"-        i `shouldHaveText` ""--        t <- findNg $ ByModel "yyy"-        t `shouldBeTag` "textarea"--        s <- findNg $ ByModel "zzz"-        s `shouldBeTag` "select"--    it "finds all repeater rows" $ WithSession Firefox $ do-        [r1, r2, r3] <- findRepeaters $ ByRows "dog in dogs"-        mapM_ (`shouldBeTag`"li") [r1, r2, r3]-        r1 `shouldHaveText` "Spot mutt"-        r2 `shouldHaveText` "Spike poodle"-        r3 `shouldHaveText` "Jupiter bulldog"--        findRepeaters (ByRows "cat in cats") `shouldReturn` []-        findRepeater (ByRows "cat in cats") `shouldThrow` NgException "Selector ByRows \"cat in cats\" returned []"--    it "finds a single repeater row" $ WithSession Firefox $ do-        r2 <- findRepeater $ ByRow "dog in dogs" 1-        r2 `shouldBeTag` "li"-        r2 `shouldHaveText` "Spike poodle"--        findRepeaters (ByRow "cat in cats" 1) `shouldReturn` []-        findRepeater (ByRow "cat in cats" 1) `shouldThrow` NgException "Selector ByRow \"cat in cats\" 1 returned []"--    it "finds a repeater column" $ WithSession Firefox $ do-        [c1, c2, c3] <- findRepeaters $ ByColumn "dog in dogs" "{{dog.name}}"-        mapM_ (`shouldBeTag`"span") [c1, c2, c3]--        c1 `shouldHaveText` "Spot"-        c2 `shouldHaveText` "Spike"-        c3 `shouldHaveText` "Jupiter"--        findRepeaters (ByColumn "cat in cats" "{{cat.name}}") `shouldReturn` []-        findRepeater (ByColumn "cat in cats" "{{cat.name}}") `shouldThrow`-            NgException "Selector ByColumn \"cat in cats\" \"{{cat.name}}\" returned []"--    it "finds a repeater by row and column" $ WithSession Firefox $ do-        c2 <- findRepeater $ ByRowAndCol "dog in dogs" 1 "{{dog.breed}}"-        c2 `shouldBeTag` "span"-        c2 `shouldHaveText` "poodle"--        -- These currently cause Javascript errors-        --findRepeaters (ByRowAndCol "cat in cats" 12 "{{cat.name}}") `shouldReturn` []-        --findRepeater (ByRowAndCol "cat in cats" 22 "{{cat.name}}") `shouldThrow`-        --    NgException "Selector ByRowAndCol \"cat in cats\" 22 \"{{cat.name}}\" returned []"--    it "evaluates an angular expression" $ WithSession Firefox $ do-        e <- findNg $ ByBinding "{{a}}"-        ngEvaluate e "cost | number:2" `shouldReturn` ("12.60" :: T.Text)--    it "loads the location url" $ WithSession Firefox $ do-        getLocationAbsUrl "body" `shouldReturn` "http://localhost:3456/index.html"--    it "loads {{cost}} from document" $ WithSession Firefox $ do-        [s1, s2] <- findNgs $ ByBinding "{{cost}}"-        s1 `shouldBeTag` "span"-        s2 `shouldBeTag` "span"-        s1 `shouldHaveAttr` ("id", "span-one")-        s2 `shouldHaveAttr` ("id", "span-two")--    it "loads {{cost}} only from one element" $ WithSession Firefox $ do-        d <- findElem $ ById "one"-        d `shouldBeTag` "div"-        s1 <- findNgFrom d $ ByBinding "{{cost}}"-        s1 `shouldBeTag` "span"-        s1 `shouldHaveAttr` ("id", "span-one")
test/main.hs view
@@ -6,21 +6,16 @@ import Network.Wai.Application.Static import Network.Wai.Handler.Warp (run) import Test.Hspec (hspec)+import Test.Hspec.WebDriver (createSessionManager) -import Specs+import NgSpecs+import ManagerSpecs  startServer :: IO ThreadId startServer = forkIO $     run 3456 $ staticApp $ defaultFileServerSettings "test/www" -{-stop :: ThreadId -> IO ()-stop t = do-    runWD defaultSession $ do-        ss <- sessions-        mapM_ (\(x,_) -> putSession defaultSession {wdSessId = Just x} >> closeSession) ss--    killThread t-}- main :: IO ()-main = bracket startServer killThread $ \_ ->-    hspec specs+main = bracket startServer killThread $ \_ -> do+    createSessionManager 2+    hspec $ ngSpecs >> mSpecs
test/www/index.html view
@@ -7,6 +7,9 @@     <body ng-app="test">         <div ng-controller="TestCtrl">             <h1>Hello {{a}}!</h1>+	    <h1>X {{xxx}}</h1>+	    <h2>Y {{yyy}}</h2>+	    <h3>Z {{zzz}}</h3>             <h2>The name is {{name}}</h2>             <input ng-model="name"></input>             <input ng-model="xxx"></input>
webdriver-angular.cabal view
@@ -1,5 +1,5 @@ name:              webdriver-angular-version:           0.1.1+version:           0.1.2 cabal-version:     >= 1.8 build-type:        Simple synopsis:          Webdriver actions to assist with testing a webpage which uses Angular.Js@@ -8,7 +8,7 @@ maintainer:        John Lenz <lenz@math.uic.edu> license:           MIT license-file:      LICENSE-homepage:          https://bitbucket.org/wuzzeb/hs-webdriver-angular+homepage:          https://bitbucket.org/wuzzeb/webdriver-utils stability:         Experimental description:       For end to end testing of web applications from Haskell, the                     <https://hackage.haskell.org/package/webdriver webdriver> package is a great tool but@@ -27,7 +27,7 @@  source-repository head     type: mercurial-    location: https://bitbucket.org/wuzzeb/hs-webdriver-angular+    location: https://bitbucket.org/wuzzeb/webdriver-utils  library     hs-source-dirs:  src@@ -56,11 +56,9 @@      build-depends: base >= 4 && < 5                  , hspec >= 1.8-                 , HUnit >= 1.2 && < 1.3-                 , lifted-base >= 0.2-                 , text >= 0.11-                 , transformers >= 0.3-                 , warp >= 2.0+                 , hspec-webdriver+                 , transformers                  , wai-app-static >= 2.0+                 , warp >= 2.0                  , webdriver                  , webdriver-angular