diff --git a/js/angular-clientsidescripts.js b/js/angular-clientsidescripts.js
--- a/js/angular-clientsidescripts.js
+++ b/js/angular-clientsidescripts.js
@@ -26,9 +26,15 @@
  * executeScript should be put here. These scripts are transmitted over
  * the wire using their toString representation, and cannot reference
  * external variables. They can, however use the array passed in to
- * arguments. Instead of params, all functions on clientSideScripts
- * should list the arguments array they expect.
+ * arguments.
+ *
+ * Some implementations seem to have issues with // comments, so use star-style
+ * inside scripts.
  */
+
+ // jshint browser: true
+ // jshint shadow: true
+ /* global angular */
 var clientSideScripts = exports;
 
 /**
@@ -36,12 +42,12 @@
  * no outstanding $http calls before continuing.
  *
  * Asynchronous.
- * arguments[0] {string} The selector housing an ng-app
- * arguments[1] {function} callback
+ *
+ * @param {string} selector The selector housing an ng-app
+ * @param {function} callback callback
  */
-clientSideScripts.waitForAngular = function() {
-  var el = document.querySelector(arguments[0]);
-  var callback = arguments[1];
+clientSideScripts.waitForAngular = function(selector, callback) {
+  var el = document.querySelector(selector);
   try {
     angular.element(el).injector().get('$browser').
         notifyWhenNoOutstandingRequests(callback);
@@ -53,14 +59,13 @@
 /**
  * Find a list of elements in the page by their angular binding.
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {string} The binding, e.g. {{cat.name}}.
+ * @param {string} binding The binding, e.g. {{cat.name}}.
+ * @param {Element} using The scope of the search.
  *
- * @return {Array.<WebElement>} The elements containing the binding.
+ * @return {Array.<Element>} The elements containing the binding.
  */
-clientSideScripts.findBindings = function() {
-  var using = arguments[0] || document;
-  var binding = arguments[1];
+clientSideScripts.findBindings = function(binding, using) {
+  using = using || document;
   var bindings = using.getElementsByClassName('ng-binding');
   var matches = [];
   for (var i = 0; i < bindings.length; ++i) {
@@ -72,7 +77,7 @@
       }
     }
   }
-  return matches; // Return the whole array for webdriver.findElements.
+  return matches; /* Return the whole array for webdriver.findElements. */
 };
 
 /**
@@ -80,17 +85,15 @@
  * 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.
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
+ * @param {number} index The row index.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @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];
+ clientSideScripts.findRepeaterRows = function(repeater, index, using) {
+  using = using || document;
 
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
   var rows = [];
@@ -104,8 +107,8 @@
       }
     }
   }
-  // multiRows is an array of arrays, where each inner array contains
-  // one row of elements.
+  /* 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';
@@ -132,14 +135,13 @@
  /**
  * Find all rows of an ng-repeat.
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {string} The text of the repeater, e.g. 'cat in cats'.
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} All rows of the repeater.
  */
- clientSideScripts.findAllRepeaterRows = function() {
-  var using = arguments[0] || document;
-  var repeater = arguments[1];
+ clientSideScripts.findAllRepeaterRows = function(repeater, using) {
+  using = using || document;
 
   var rows = [];
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
@@ -160,7 +162,6 @@
     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);
@@ -175,19 +176,16 @@
 /**
  * Find an element within an ng-repeat by its row and column.
  *
- * 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.
- * arguments[3] {string} The column binding, e.g. '{{cat.name}}'.
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
+ * @param {number} index The row index.
+ * @param {string} binding The column binding, e.g. '{{cat.name}}'.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} The element in an array.
  */
-clientSideScripts.findRepeaterElement = function() {
+clientSideScripts.findRepeaterElement = function(repeater, index, binding, using) {
   var matches = [];
-  var using = arguments[0] || document;
-  var repeater = arguments[1];
-  var index = arguments[2];
-  var binding = arguments[3];
+  using = using || document;
 
   var rows = [];
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
@@ -201,8 +199,8 @@
       }
     }
   }
-  // multiRows is an array of arrays, where each inner array contains
-  // one row of elements.
+  /* 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';
@@ -237,7 +235,7 @@
   }
   if (multiRow) {
     for (var i = 0; i < multiRow.length; ++i) {
-      rowElem = multiRow[i];
+      var rowElem = multiRow[i];
       if (rowElem.className.indexOf('ng-binding') != -1) {
         bindings.push(rowElem);
       }
@@ -262,17 +260,15 @@
 /**
  * Find the elements in a column of an ng-repeat.
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {string} The text of the repeater, e.g. 'cat in cats'.
- * arguments[2] {string} The column binding, e.g. '{{cat.name}}'.
+ * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
+ * @param {string} binding The column binding, e.g. '{{cat.name}}'.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} The elements in the column.
  */
-clientSideScripts.findRepeaterColumn = function() {
+clientSideScripts.findRepeaterColumn = function(repeater, binding, using) {
   var matches = [];
-  var using = arguments[0] || document;
-  var repeater = arguments[1];
-  var binding = arguments[2];
+  using = using || document;
 
   var rows = [];
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
@@ -286,8 +282,8 @@
       }
     }
   }
-  // multiRows is an array of arrays, where each inner array contains
-  // one row of elements.
+  /* 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';
@@ -346,14 +342,13 @@
  * Find an input elements by model name.
  * DEPRECATED - use findByModel
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {string} The model name.
+ * @param {string} model The model name.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} The matching input elements.
  */
-clientSideScripts.findInputs = function() {
-  var using = arguments[0] || document;
-  var model = arguments[1];
+clientSideScripts.findInputs = function(model, using) {
+  using = using || document;
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
   for (var p = 0; p < prefixes.length; ++p) {
     var selector = 'input[' + prefixes[p] + 'model="' + model + '"]';
@@ -365,16 +360,15 @@
 };
 
 /**
- * Find a elements by model name.
+ * Find elements by model name.
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {string} The model name.
+ * @param {string} model The model name.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} The matching elements.
  */
-clientSideScripts.findByModel = function() {
-  var using = arguments[0] || document;
-  var model = arguments[1];
+clientSideScripts.findByModel = function(model, using) {
+  using = using || document;
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
   for (var p = 0; p < prefixes.length; ++p) {
     var selector = '[' + prefixes[p] + 'model="' + model + '"]';
@@ -386,16 +380,72 @@
 };
 
 /**
+ * Find buttons by textual content.
+ *
+ * @param {string} searchText The exact text to match.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
+ *
+ * @return {Array.<Element>} The matching elements.
+ */
+clientSideScripts.findByButtonText = function(searchText, using) {
+  using = using || document;
+  var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
+  var matches = [];
+  for (var i = 0; i < elements.length; ++i) {
+    var element = elements[i];
+    var elementText;
+    if (element.tagName.toLowerCase() == 'button') {
+      elementText = element.innerText || element.textContent;
+    } else {
+      elementText = element.value;
+    }
+    if (elementText === searchText) {
+      matches.push(element);
+    }
+  }
+
+  return matches;
+};
+
+/**
+ * Find buttons by textual content.
+ *
+ * @param {string} searchText The exact text to match.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
+ *
+ * @return {Array.<Element>} The matching elements.
+ */
+clientSideScripts.findByPartialButtonText = function(searchText, using) {
+  using = using || document;
+  var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]');
+  var matches = [];
+  for (var i = 0; i < elements.length; ++i) {
+    var element = elements[i];
+    var elementText;
+    if (element.tagName.toLowerCase() == 'button') {
+      elementText = element.innerText || element.textContent;
+    } else {
+      elementText = element.value;
+    }
+    if (elementText.indexOf(searchText) > -1) {
+      matches.push(element);
+    }
+  }
+
+  return matches;
+};
+
+
+/**
  * Find multiple select elements by model name.
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {string} The model name.
+ * @param {string} model The model name.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} The matching select elements.
  */
-clientSideScripts.findSelects = function() {
-  var using = arguments[0] || document;
-  var model = arguments[1];
+clientSideScripts.findSelects = function(model, using) {
+  using = using || document;
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
   for (var p = 0; p < prefixes.length; ++p) {
     var selector = 'select[' + prefixes[p] + 'model="' + model + '"]';
@@ -409,14 +459,13 @@
 /**
  * Find selected option elements by model name.
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {string} The model name.
+ * @param {string} model The model name.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} The matching select elements.
 */
-clientSideScripts.findSelectedOptions = function() {
-  var using = arguments[0] || document;
-  var model = arguments[1];
+clientSideScripts.findSelectedOptions = function(model, using) {
+  using = using || document;
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
   for (var p = 0; p < prefixes.length; ++p) {
     var selector = 'select[' + prefixes[p] + 'model="' + model + '"] option:checked';
@@ -430,14 +479,13 @@
 /**
  * Find textarea elements by model name.
  *
- * arguments[0] {Element} The scope of the search.
- * arguments[1] {String} The model name.
+ * @param {String} model The model name.
+ * @param {Element} using The scope of the search.  Defaults to 'document'.
  *
  * @return {Array.<Element>} An array of matching textarea elements.
 */
-clientSideScripts.findTextareas = function() {
-  var using = arguments[0] || document;
-  var model = arguments[1];
+clientSideScripts.findTextareas = function(model, using) {
+  using = using || document;
 
   var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
   for (var p = 0; p < prefixes.length; ++p) {
@@ -450,16 +498,43 @@
 };
 
 /**
+ * Find elements by css selector and textual content.
+ *
+ * @param {string} cssSelector The css selector to match.
+ * @param {string} searchText The exact text to match.
+ * @param {Element} using The scope of the search. Defaults to 'document'.
+ *
+ * @return {Array.<Element>} An array of matching elements.
+ */
+clientSideScripts.findByCssContainingText = function(cssSelector, searchText, using) {
+  var using = using || document;
+  var elements = using.querySelectorAll(cssSelector);
+  var matches = [];
+  for (var i = 0; i < elements.length; ++i) {
+    var element = elements[i];
+    var elementText = element.innerText || element.textContent;
+    if (elementText.indexOf(searchText) > -1) {
+      matches.push(element);
+    }
+  }
+  return matches;
+};
+
+/**
  * Tests whether the angular global variable is present on a page. Retries
  * in case the page is just loading slowly.
  *
  * Asynchronous.
- * arguments[0] {number} Number of times to retry.
- * arguments[1] {function} callback
+ *
+ * @param {number} attempts Number of times to retry.
+ * @param {function} asyncCallback callback
  */
-clientSideScripts.testForAngular = function() {
-  var attempts = arguments[0];
-  var callback = arguments[arguments.length - 1];
+clientSideScripts.testForAngular = function(attempts, asyncCallback) {
+  var callback = function(args) {
+    setTimeout(function() {
+      asyncCallback(args);
+    }, 0);
+  };
   var check = function(n) {
     try {
       if (window.angular && window.angular.resumeBootstrap) {
@@ -471,7 +546,7 @@
           callback([false, 'retries looking for angular exceeded']);
         }
       } else {
-        window.setTimeout(function() {check(n - 1)}, 1000);
+        window.setTimeout(function() {check(n - 1);}, 1000);
       }
     } catch (e) {
       callback([false, e]);
@@ -483,14 +558,12 @@
 /**
  * Evalute an Angular expression in the context of a given element.
  *
- * arguments[0] {Element} The element in whose scope to evaluate.
- * arguments[1] {string} The expression to evaluate.
+ * @param {Element} element The element in whose scope to evaluate.
+ * @param {string} expression The expression to evaluate.
  *
  * @return {?Object} The result of the evaluation.
  */
-clientSideScripts.evaluate = function() {
-  var element = arguments[0];
-  var expression = arguments[1];
+clientSideScripts.evaluate = function(element, expression) {
 
   return angular.element(element).scope().$eval(expression);
 };
@@ -498,9 +571,28 @@
 /**
  * Return the current url using $location.absUrl().
  *
- * arguments[0] {string} The selector housing an ng-app
+ * @param {string} selector The selector housing an ng-app
  */
-clientSideScripts.getLocationAbsUrl = function() {
-  var el = document.querySelector(arguments[0]);
+clientSideScripts.getLocationAbsUrl = function(selector) {
+  var el = document.querySelector(selector);
   return angular.element(el).injector().get('$location').absUrl();
+};
+
+/**
+ * Browse to another page using in-page navigation.
+ *
+ * @param {string} selector The selector housing an ng-app
+ * @param {string} url In page URL using the same syntax as $location.url(),
+ *     /path?search=a&b=c#hash
+ */
+clientSideScripts.setLocation = function(selector, url) {
+  var el = document.querySelector(selector);
+  var $injector = angular.element(el).injector();
+  var $location = $injector.get('$location');
+  var $rootScope = $injector.get('$rootScope');
+
+  if (url !== $location.url()) {
+    $location.url(url);
+    $rootScope.$digest();
+  }
 };
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
@@ -23,6 +23,7 @@
     -- * Misc
     , ngEvaluate
     , getLocationAbsUrl
+    , setNgLocation
     ) where
 
 import Control.Applicative ((<$>))
@@ -125,9 +126,9 @@
 findNgsFrom e = findNg' $ JSArg e
 
 findNg' :: WebDriver wd => JSArg -> NgSelector -> wd [Element]
-findNg' e (ByBinding name) = execElems "findBindings" [e, JSArg name]
-findNg' e (ByModel name) = execElems "findByModel" [e, JSArg name]
-findNg' e (BySelectedOption name) = execElems "findSelectedOptions" [e, JSArg name]
+findNg' e (ByBinding name) = execElems "findBindings" [JSArg name, e]
+findNg' e (ByModel name) = execElems "findByModel" [JSArg name, e]
+findNg' e (BySelectedOption name) = execElems "findSelectedOptions" [JSArg name, e]
 
 -- | 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.
@@ -148,10 +149,10 @@
 findRepeatersFrom e = findRepeater' $ JSArg e
 
 findRepeater' :: WebDriver wd => JSArg -> NgRepeater -> wd [Element]
-findRepeater' e (ByRows rep) = execElems "findAllRepeaterRows" [e, JSArg rep]
-findRepeater' e (ByRow rep idx) = execElems "findRepeaterRows" [e, JSArg rep, JSArg idx]
-findRepeater' e (ByColumn rep idx) = execElems "findRepeaterColumn" [e, JSArg rep, JSArg idx]
-findRepeater' e (ByRowAndCol rep row col) = execElems "findRepeaterElement" [e, JSArg rep, JSArg row, JSArg col]
+findRepeater' e (ByRows rep) = execElems "findAllRepeaterRows" [JSArg rep, e]
+findRepeater' e (ByRow rep idx) = execElems "findRepeaterRows" [JSArg rep, JSArg idx, e]
+findRepeater' e (ByColumn rep idx) = execElems "findRepeaterColumn" [JSArg rep, JSArg idx, e]
+findRepeater' e (ByRowAndCol rep row col) = execElems "findRepeaterElement" [JSArg rep, JSArg row, JSArg col, e]
 
 -- | Evaluate an angular expression, using the scope attached to the given element.
 ngEvaluate :: (WebDriver wd, A.FromJSON a) 
@@ -164,3 +165,15 @@
 getLocationAbsUrl :: WebDriver wd => T.Text -- ^ CSS selector to element which has ng-app
                                   -> wd T.Text
 getLocationAbsUrl sel = execCS "getLocationAbsUrl" [JSArg sel]
+
+-- | Browse to another page using in-page navigation (via @$location.url@).
+setNgLocation :: (MonadIO wd, WebDriver wd)
+              => T.Text -- ^ CSS selector to the element which has ng-app
+              -> T.Text -- ^ URL
+              -> wd ()
+setNgLocation sel url = do
+    x <- execCS "setLocation" [JSArg sel, JSArg url]
+    case x of
+        A.Null -> return ()
+        _ -> liftIO $ throwIO $ NgException $ "Error setting location: " ++ show x
+
diff --git a/src/Test/WebDriver/Commands/Internal.hs b/src/Test/WebDriver/Commands/Internal.hs
--- a/src/Test/WebDriver/Commands/Internal.hs
+++ b/src/Test/WebDriver/Commands/Internal.hs
@@ -5,6 +5,7 @@
 
 import Control.Applicative
 import Data.Maybe (catMaybes)
+import Data.Monoid ((<>))
 import Language.JavaScript.Parser (JSNode(..), Node(..))
 import qualified Data.HashMap.Lazy as M
 import qualified Data.Text as T
@@ -16,6 +17,13 @@
 parseClientTop (NN (JSSourceElementsTop es)) = catMaybes $ map parseClientDef es
 parseClientTop _ = []
 
+-- | Build a function body that can be passed to async.  The body must use
+-- arguments array but the code from clientSideScripts.js uses function arguments.
+buildFunction :: Node -> T.Text
+buildFunction func = "return (" <> T.pack func' <> ").apply(null, arguments);"
+    where
+        func' = dropWhile (=='\n') $ JS.renderToString $ NN func
+
 -- | Parse a call clientSideScripts.somefunction = function() {...}, returning the function name
 -- and the body.
 parseClientDef :: JSNode -> Maybe (T.Text, T.Text)
@@ -25,9 +33,9 @@
                             _ -- literal .
                             (NT (JSIdentifier name) _ _))
                         , NN (JSOperator (NT (JSLiteral "=") _ _))
-                        , NN (JSFunctionExpression _ _ _ _ _ (NN (JSBlock _ body _)))
+                        , NN (func@(JSFunctionExpression _ _ _ _ _ _))
                         ]))
-    = Just (T.pack name, T.pack $ dropWhile (=='\n') $ JS.renderToString $ NN $ JSSourceElementsTop body)
+    = Just (T.pack name, buildFunction func)
                         -- Use renderToString instead of renderJS so we don't need to depend on builder
 parseClientDef _ = Nothing
 
diff --git a/test/NgSpec.hs b/test/NgSpec.hs
--- a/test/NgSpec.hs
+++ b/test/NgSpec.hs
@@ -117,3 +117,7 @@
         s1 <- findNgFrom d $ ByBinding "{{cost}}"
         s1 `shouldBeTag` "span"
         s1 `shouldHaveAttr` ("id", "span-one")
+
+    it "sets the location" $ runWD $ do
+        setNgLocation "body" "foo"
+        getLocationAbsUrl "body" `shouldReturn` "http://localhost:3456/index.html#/foo"
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.3
+version:           0.1.4
 cabal-version:     >= 1.8
 build-type:        Simple
 synopsis:          Webdriver actions to assist with testing a webpage which uses Angular.Js
