diff --git a/docs/content/api/index.ngdoc b/docs/content/api/index.ngdoc
index 2ec86346425a..f48a2b385753 100644
--- a/docs/content/api/index.ngdoc
+++ b/docs/content/api/index.ngdoc
@@ -24,9 +24,8 @@
## Angular Testing API
* {@link angular.mock Testing Mocks API} - Mock objects for testing
-* {@link
-https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en_US
-Angular Scenario Runner} - Automated scenario testing documentation
+* {@link guide/dev_guide.e2e-testing Angular Scenario Runner} - Automated scenario testing
+documentation
## Angular Utility Functions
diff --git a/docs/content/guide/dev_guide.e2e-testing.ngdoc b/docs/content/guide/dev_guide.e2e-testing.ngdoc
new file mode 100644
index 000000000000..d725e07a5d93
--- /dev/null
+++ b/docs/content/guide/dev_guide.e2e-testing.ngdoc
@@ -0,0 +1,178 @@
+@workInProgress
+@ngdoc overview
+@name Developer Guide: E2E Testing
+@description
+
+As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
+verify the correctness of new features, catch bugs and notice regressions.
+
+To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
+that will help you verify the health of your Angular application.
+
+# Overview
+You will write scenario tests in JavaScript, which describe how your application should behave,
+given a certain interaction in a specific state. A scenario is comprised of one or more it blocks
+(you can think of these as the requirements of your application), which in turn are made of
+**commands** and **expectations**. Commands tell the Runner to do something with the application
+(such as navigate to a page or click on a button), and expectations tell the Runner to assert
+something about the state (such as the value of a field or the current URL). If any expectation
+fails, the runner marks the `it` as "failed" and continues on to the next one. Scenarios may also
+have **beforeEach** and **afterEach** blocks, which will be run before (or after) each `it` block,
+regardless of whether they pass or fail.
+
+
+
+In addition to the above elements, scenarios may also contain helper functions to avoid duplicating
+code in the `it` blocks.
+
+Here is an example of a simple scenario:
+
+describe('Buzz Client', function() {
+it('should filter results', function() {
+ input('user').enter('jacksparrow');
+ element(':button').click();
+ expect(repeater('ul li').count()).toEqual(10);
+ input('filterText').enter('Bees');
+ expect(repeater('ul li').count()).toEqual(1);
+});
+});
+
+This scenario describes the requirements of a Buzz Client, specifically, that it should be able to
+filter the stream of the user. It starts by entering a value in the 'user' input field, clicking
+the only button on the page, and then it verifies that there are 10 items listed. It then enters
+'Bees' in the 'filterText' input field and verifies that the list is reduced to a single item.
+
+The API section below lists the available commands and expectations for the Runner.
+
+# API
+Source: {@link https://github.com/angular/angular.js/blob/master/src/scenario/dsl.js}
+
+## pause()
+Pauses the execution of the tests until you call `resume()` in the console (or click the resume
+link in the Runner UI).
+
+## sleep(seconds)
+Pauses the execution of the tests for the specified number of `seconds`.
+
+## browser().navigateTo(url)
+Loads the `url` into the test frame.
+
+## browser().navigateTo(url, fn)
+Loads the URL returned by `fn` into the testing frame. The given `url` is only used for the test
+output. Use this when the destination URL is dynamic (that is, the destination is unknown when you
+write the test).
+
+## browser().reload()
+Refreshes the currently loaded page in the test frame.
+
+## browser().window().href()
+Returns the window.location.href of the currently loaded page in the test frame.
+
+## browser().window().path()
+Returns the window.location.pathname of the currently loaded page in the test frame.
+
+## browser().window().search()
+Returns the window.location.search of the currently loaded page in the test frame.
+
+## browser().window().hash()
+Returns the window.location.hash (without `#`) of the currently loaded page in the test frame.
+
+## browser().location().url()
+Returns the {@link api/angular.service.$location $location.url()} of the currently loaded page in
+the test frame.
+
+## browser().location().path()
+Returns the {@link api/angular.service.$location $location.path()} of the currently loaded page in
+the test frame.
+
+## browser().location().search()
+Returns the {@link api/angular.service.$location $location.search()} of the currently loaded page
+in the test frame.
+
+## browser().location().hash()
+Returns the {@link api/angular.service.$location $location.hash()} of the currently loaded page in
+the test frame.
+
+## expect(future).{matcher}
+Asserts the value of the given `future` satisfies the `matcher`. All API statements return a
+`future` object, which get a `value` assigned after they are executed. Matchers are defined using
+`angular.scenario.matcher`, and they use the value of futures to run the expectation. For example:
+`expect(browser().location().href()).toEqual('http://www.google.com')`
+
+## expect(future).not().{matcher}
+Asserts the value of the given `future` satisfies the negation of the `matcher`.
+
+## using(selector, label)
+Scopes the next DSL element selection.
+
+## binding(name)
+Returns the value of the first binding matching the given `name`.
+
+## input(name).enter(value)
+Enters the given `value` in the text field with the given `name`.
+
+## input(name).check()
+Checks/unchecks the checkbox with the given `name`.
+
+## input(name).select(value)
+Selects the given `value` in the radio button with the given `name`.
+
+## input(name).val()
+Returns the current value of an input field with the given `name`.
+
+## repeater(selector, label).count()
+Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is
+used for test ouput.
+
+## repeater(selector, label).row(index)
+Returns an array with the bindings in the row at the given `index` in the repeater matching the
+given jQuery `selector`. The `label` is used for test output.
+
+## repeater(selector, label).column(binding)
+Returns an array with the values in the column with the given `binding` in the repeater matching
+the given jQuery `selector`. The `label` is used for test output.
+
+## select(name).option(value)
+Picks the option with the given `value` on the select with the given `name`.
+
+## select(name).option(value1, value2...)
+Picks the options with the given `values` on the multi select with the given `name`.
+
+## element(selector, label).count()
+Returns the number of elements that match the given jQuery `selector`. The `label` is used for test
+output.
+
+## element(selector, label).click()
+Clicks on the element matching the given jQuery `selector`. The `label` is used for test output.
+
+## element(selector, label).query(fn)
+Executes the function `fn(selectedElements, done)`, where selectedElements are the elements that
+match the given jQuery `selector` and `done` is a function that is called at the end of the `fn`
+function. The `label` is used for test output.
+
+## element(selector, label).{method}()
+Returns the result of calling `method` on the element matching the given jQuery `selector`, where
+`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
+`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
+`scrollTop`, `offset`. The `label` is used for test output.
+
+## element(selector, label).{method}(value)
+Executes the `method` passing in `value` on the element matching the given jQuery `selector`, where
+`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
+`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
+`scrollTop`, `offset`. The `label` is used for test output.
+
+## element(selector, label).{method}(key)
+Returns the result of calling `method` passing in `key` on the element matching the given jQuery
+`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
+`label` is used for test output.
+
+## element(selector, label).{method}(key, value)
+Executes the `method` passing in `key` and `value` on the element matching the given jQuery
+`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
+`label` is used for test output.
+
+JavaScript is a dynamically typed language which comes with great power of expression, but it also
+come with almost no-help from the compiler. For this reason we feel very strongly that any code
+written in JavaScript needs to come with a strong set of tests. We have built many features into
+angular which makes testing your angular applications easy. So there is no excuse for not do it.
diff --git a/docs/content/tutorial/step_03.ngdoc b/docs/content/tutorial/step_03.ngdoc
index 89a1b0cb7917..9be7380a69a2 100644
--- a/docs/content/tutorial/step_03.ngdoc
+++ b/docs/content/tutorial/step_03.ngdoc
@@ -99,9 +99,8 @@ describe('PhoneCat App', function() {
Even though the syntax of this test looks very much like our controller unit test written with
-Jasmine, the end-to-end test uses APIs of {@link
-https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
-Angular's end-to-end test runner}.
+Jasmine, the end-to-end test uses APIs of {@link guide/dev_guide.e2e-testing Angular's end-to-end
+test runner}.
To run the end-to-end test, open one of the following in a new browser tab:
diff --git a/docs/content/tutorial/step_08.ngdoc b/docs/content/tutorial/step_08.ngdoc
index ef55885fbb6c..7d9c82d7d4fe 100644
--- a/docs/content/tutorial/step_08.ngdoc
+++ b/docs/content/tutorial/step_08.ngdoc
@@ -173,10 +173,8 @@ angular's server}.
# Experiments
-* Using the {@link
-https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
-end-to-end test runner API}, write a test that verifies that we display 4 thumbnail images on the
-Nexus S details page.
+* Using the {@link guide/dev_guide.e2e-testing Angular's end-to-end test runner API}, write a test
+that verifies that we display 4 thumbnail images on the Nexus S details page.
# Summary
diff --git a/docs/img/guide/scenario_runner.png b/docs/img/guide/scenario_runner.png
new file mode 100644
index 000000000000..a39037a0aa38
Binary files /dev/null and b/docs/img/guide/scenario_runner.png differ
diff --git a/src/markups.js b/src/markups.js
index 7532916932fe..b7761857b8bc 100644
--- a/src/markups.js
+++ b/src/markups.js
@@ -187,7 +187,7 @@ angularTextMarkup('option', function(text, textNode, parentElement){
expect(element('#link-3').attr('href')).toBe("/123");
element('#link-3').click();
- expect(browser().location().path()).toEqual('/123');
+ expect(browser().window().path()).toEqual('/123');
});
it('should execute ng:click but not reload when href empty string and name specified', function() {
@@ -207,7 +207,7 @@ angularTextMarkup('option', function(text, textNode, parentElement){
expect(element('#link-6').attr('href')).toBe("/6");
element('#link-6').click();
- expect(browser().location().path()).toEqual('/6');
+ expect(browser().window().path()).toEqual('/6');
});
diff --git a/src/scenario/dsl.js b/src/scenario/dsl.js
index e0af0c8ce4e5..db81dd3550d0 100644
--- a/src/scenario/dsl.js
+++ b/src/scenario/dsl.js
@@ -34,11 +34,14 @@ angular.scenario.dsl('sleep', function() {
* browser().navigateTo(url) Loads the url into the frame
* browser().navigateTo(url, fn) where fn(url) is called and returns the URL to navigate to
* browser().reload() refresh the page (reload the same URL)
- * browser().location().href() the full URL of the page
- * browser().location().hash() the full hash in the url
- * browser().location().path() the full path in the url
- * browser().location().hashSearch() the hashSearch Object from angular
- * browser().location().hashPath() the hashPath string from angular
+ * browser().window.href() window.location.href
+ * browser().window.path() window.location.pathname
+ * browser().window.search() window.location.search
+ * browser().window.hash() window.location.hash without # prefix
+ * browser().location().url() see angular.service.$location#url
+ * browser().location().path() see angular.service.$location#path
+ * browser().location().search() see angular.service.$location#search
+ * browser().location().hash() see angular.service.$location#hash
*/
angular.scenario.dsl('browser', function() {
var chain = {};
@@ -65,42 +68,60 @@ angular.scenario.dsl('browser', function() {
});
};
- chain.location = function() {
+ chain.window = function() {
var api = {};
api.href = function() {
- return this.addFutureAction('browser url', function($window, $document, done) {
+ return this.addFutureAction('window.location.href', function($window, $document, done) {
done(null, $window.location.href);
});
};
+ api.path = function() {
+ return this.addFutureAction('window.location.path', function($window, $document, done) {
+ done(null, $window.location.pathname);
+ });
+ };
+
+ api.search = function() {
+ return this.addFutureAction('window.location.search', function($window, $document, done) {
+ done(null, $window.location.search);
+ });
+ };
+
api.hash = function() {
- return this.addFutureAction('browser url hash', function($window, $document, done) {
+ return this.addFutureAction('window.location.hash', function($window, $document, done) {
done(null, $window.location.hash.replace('#', ''));
});
};
- api.path = function() {
- return this.addFutureAction('browser url path', function($window, $document, done) {
- done(null, $window.location.pathname);
+ return api;
+ };
+
+ chain.location = function() {
+ var api = {};
+
+ api.url = function() {
+ return this.addFutureAction('$location.url()', function($window, $document, done) {
+ done(null, $window.angular.scope().$service('$location').url());
});
};
- api.search = function() {
- return this.addFutureAction('browser url search', function($window, $document, done) {
- done(null, $window.angular.scope().$service('$location').search);
+ api.path = function() {
+ return this.addFutureAction('$location.path()', function($window, $document, done) {
+ done(null, $window.angular.scope().$service('$location').path());
});
};
- api.hashSearch = function() {
- return this.addFutureAction('browser url hash search', function($window, $document, done) {
- done(null, $window.angular.scope().$service('$location').hashSearch);
+ api.search = function() {
+ return this.addFutureAction('$location.search()', function($window, $document, done) {
+ done(null, $window.angular.scope().$service('$location').search());
});
};
- api.hashPath = function() {
- return this.addFutureAction('browser url hash path', function($window, $document, done) {
- done(null, $window.angular.scope().$service('$location').hashPath);
+ api.hash = function() {
+ return this.addFutureAction('$location.hash()', function($window, $document, done) {
+ done(null, $window.angular.scope().$service('$location').hash());
});
};
diff --git a/test/scenario/dslSpec.js b/test/scenario/dslSpec.js
index a6e8090147db..32d7ebb637d5 100644
--- a/test/scenario/dslSpec.js
+++ b/test/scenario/dslSpec.js
@@ -123,7 +123,7 @@ describe("angular.scenario.dsl", function() {
});
});
- describe('Location', function() {
+ describe('window', function() {
beforeEach(function() {
$window.location = {
href: 'http://myurl/some/path?foo=10#/bar?x=2',
@@ -131,51 +131,66 @@ describe("angular.scenario.dsl", function() {
search: '?foo=10',
hash: '#bar?x=2'
};
+ });
+
+ it('should return full URL for href', function() {
+ $root.dsl.browser().window().href();
+ expect($root.futureResult).toEqual($window.location.href);
+ });
+
+ it('should return the pathname', function() {
+ $root.dsl.browser().window().path();
+ expect($root.futureResult).toEqual($window.location.pathname);
+ });
+
+ it('should return the search part', function() {
+ $root.dsl.browser().window().search();
+ expect($root.futureResult).toEqual($window.location.search);
+ });
+
+ it('should return the hash without the #', function() {
+ $root.dsl.browser().window().hash();
+ expect($root.futureResult).toEqual('bar?x=2');
+ });
+ });
+
+ describe('location', function() {
+ beforeEach(function() {
$window.angular.scope = function() {
return {
$service: function(serviceId) {
if (serviceId == '$location') {
return {
- hashSearch: {x: 2},
- hashPath: '/bar',
- search: {foo: 10}
+ url: function() {return '/path?search=a#hhh';},
+ path: function() {return '/path';},
+ search: function() {return {search: 'a'};},
+ hash: function() {return 'hhh';}
};
- } else {
- throw new Error('unknown service id ' + serviceId);
}
+ throw new Error('unknown service id ' + serviceId);
}
};
};
});
- it('should return full URL for href', function() {
- $root.dsl.browser().location().href();
- expect($root.futureResult).toEqual($window.location.href);
+ it('should return full url', function() {
+ $root.dsl.browser().location().url();
+ expect($root.futureResult).toEqual('/path?search=a#hhh');
});
it('should return the pathname', function() {
$root.dsl.browser().location().path();
- expect($root.futureResult).toEqual($window.location.pathname);
- });
-
- it('should return the hash without the #', function() {
- $root.dsl.browser().location().hash();
- expect($root.futureResult).toEqual('bar?x=2');
+ expect($root.futureResult).toEqual('/path');
});
it('should return the query string as an object', function() {
$root.dsl.browser().location().search();
- expect($root.futureResult).toEqual({foo: 10});
+ expect($root.futureResult).toEqual({search: 'a'});
});
- it('should return the hashSearch as an object', function() {
- $root.dsl.browser().location().hashSearch();
- expect($root.futureResult).toEqual({x: 2});
- });
-
- it('should return the hashPath', function() {
- $root.dsl.browser().location().hashPath();
- expect($root.futureResult).toEqual('/bar');
+ it('should return the hash without the #', function() {
+ $root.dsl.browser().location().hash();
+ expect($root.futureResult).toEqual('hhh');
});
});
});