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 +151/−19
- src/Test/WebDriver/Commands/Angular.hs +24/−19
- test/ManagerSpecs.hs +55/−0
- test/NgSpecs.hs +118/−0
- test/Specs.hs +0/−204
- test/main.hs +6/−11
- test/www/index.html +3/−0
- webdriver-angular.cabal +6/−8
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