diff --git a/js/angular-clientsidescripts.js b/js/angular-clientsidescripts.js
--- a/js/angular-clientsidescripts.js
+++ b/js/angular-clientsidescripts.js
@@ -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;
     }
   }
 };
diff --git a/src/Test/WebDriver/Commands/Angular.hs b/src/Test/WebDriver/Commands/Angular.hs
--- a/src/Test/WebDriver/Commands/Angular.hs
+++ b/src/Test/WebDriver/Commands/Angular.hs
@@ -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.
diff --git a/test/ManagerSpecs.hs b/test/ManagerSpecs.hs
new file mode 100644
--- /dev/null
+++ b/test/ManagerSpecs.hs
@@ -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)
+
diff --git a/test/NgSpecs.hs b/test/NgSpecs.hs
new file mode 100644
--- /dev/null
+++ b/test/NgSpecs.hs
@@ -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")
diff --git a/test/Specs.hs b/test/Specs.hs
deleted file mode 100644
--- a/test/Specs.hs
+++ /dev/null
@@ -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")
diff --git a/test/main.hs b/test/main.hs
--- a/test/main.hs
+++ b/test/main.hs
@@ -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
diff --git a/test/www/index.html b/test/www/index.html
--- a/test/www/index.html
+++ b/test/www/index.html
@@ -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>
diff --git a/webdriver-angular.cabal b/webdriver-angular.cabal
--- a/webdriver-angular.cabal
+++ b/webdriver-angular.cabal
@@ -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
