Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit acfcbdf

Browse files
committed
docs(e2e-testing): deprecate ng-scenario and update E2E testing doc to discuss protractor
1 parent 879b0bc commit acfcbdf

File tree

1 file changed

+49
-285
lines changed

1 file changed

+49
-285
lines changed

docs/content/guide/e2e-testing.ngdoc

+49-285
Original file line numberDiff line numberDiff line change
@@ -3,319 +3,83 @@
33
@name E2E Testing
44
@description
55

6+
# E2E Testing
7+
68
<div class="alert alert-danger">
7-
**Note:** Angular Scenario Runner is depricated. If you're starting a new Angular project,
8-
consider using [Protractor](https://github.com/angular/protractor).
9+
**Note:** In the past, end to end testing could be done with a deprecated tool called
10+
[Angular Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
11+
is now in maintenance mode.
912
</div>
1013

11-
# E2E Testing with the Angular Scenario Runner
12-
1314
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
14-
verify the correctness of new features, catch bugs and notice regressions.
15+
verify the correctness of new features, catch bugs and notice regressions. End to end tests
16+
are the first line of defense for catching bugs, but sometimes issues come up with integration
17+
between components which can't be captured in a unit test. End to end tests are made to find
18+
these problems.
19+
20+
We have built [Protractor](https://github.com/angular/protractor), an end
21+
to end test runner which simulates user interactions that will help you verify the health of your
22+
Angular application.
1523

16-
To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
17-
that will help you verify the health of your Angular application.
24+
## Using Protractor
1825

19-
## Overview
26+
Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests that are also
27+
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
28+
to control browsers and simulate user actions.
2029

21-
You write scenario tests in JavaScript. These tests describe how your application should behave
22-
given a certain interaction in a specific state.
30+
For more information on Protractor, view [getting started](https://github.com/angular/protractor/blob/master/docs/getting-started.md)
31+
or the [api docs](https://github.com/angular/protractor/blob/master/docs/api.md).
2332

24-
A scenario is comprised of one or more `it` blocks that describe the requirements of your
25-
application. `it` blocks are made of **commands** and **expectations**. Commands tell the Runner
26-
to do something with the application such as navigate to a page or click on a button. Expectations
27-
tell the Runner to assert something about the application's state, such as the value of a field or
28-
the current URL.
33+
Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax.
34+
As in unit testing, a test file is comprised of one or
35+
more `it` blocks that describe the requirements of your application. `it` blocks are made of
36+
**commands** and **expectations**. Commands tell Protractor to do something with the application
37+
such as navigate to a page or click on a button. Expectations tell Protractor to assert something
38+
about the application's state, such as the value of a field or the current URL.
2939

3040
If any expectation within an `it` block fails, the runner marks the `it` as "failed" and continues
3141
on to the next block.
3242

33-
Scenarios may also have `beforeEach` and `afterEach` blocks, which will be run before or after
43+
Test files may also have `beforeEach` and `afterEach` blocks, which will be run before or after
3444
each `it` block regardless of whether the block passes or fails.
3545

3646
<img src="img/guide/scenario_runner.png">
3747

38-
In addition to the above elements, scenarios may also contain helper functions to avoid duplicating
48+
In addition to the above elements, tests may also contain helper functions to avoid duplicating
3949
code in the `it` blocks.
4050

41-
Here is an example of a simple scenario:
51+
Here is an example of a simple test:
4252
```js
43-
describe('Buzz Client', function() {
44-
it('should filter results', function() {
45-
input('user').enter('jacksparrow');
46-
element(':button').click();
47-
expect(repeater('ul li').count()).toEqual(10);
48-
input('filterText').enter('Bees');
49-
expect(repeater('ul li').count()).toEqual(1);
50-
});
51-
});
52-
```
53-
54-
Note that
55-
[`input('user')`](https://github.com/angular/angular.js/blob/master/docs/content/guide/dev_guide.e2e-testing.ngdoc#L119)
56-
finds the `<input>` element with `ng-model="user"` not `name="user"`.
57-
58-
This scenario describes the requirements of a Buzz Client, specifically, that it should be able to
59-
filter the stream of the user. It starts by entering a value in the input field with ng-model="user", clicking
60-
the only button on the page, and then it verifies that there are 10 items listed. It then enters
61-
'Bees' in the input field with ng-model='filterText' and verifies that the list is reduced to a single item.
62-
63-
The API section below lists the available commands and expectations for the Runner.
64-
65-
## API
66-
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/dsl.js
67-
68-
### `pause()`
69-
Pauses the execution of the tests until you call `resume()` in the console (or click the resume
70-
link in the Runner UI).
71-
72-
### `sleep(seconds)`
73-
Pauses the execution of the tests for the specified number of `seconds`.
74-
75-
### `browser().navigateTo(url)`
76-
Loads the `url` into the test frame.
77-
78-
### `browser().navigateTo(url, fn)`
79-
Loads the URL returned by `fn` into the testing frame. The given `url` is only used for the test
80-
output. Use this when the destination URL is dynamic (that is, the destination is unknown when you
81-
write the test).
82-
83-
### `browser().reload()`
84-
Refreshes the currently loaded page in the test frame.
85-
86-
### `browser().window().href()`
87-
Returns the window.location.href of the currently loaded page in the test frame.
88-
89-
### `browser().window().path()`
90-
Returns the window.location.pathname of the currently loaded page in the test frame.
91-
92-
### `browser().window().search()`
93-
Returns the window.location.search of the currently loaded page in the test frame.
94-
95-
### `browser().window().hash()`
96-
Returns the window.location.hash (without `#`) of the currently loaded page in the test frame.
97-
98-
### `browser().location().url()`
99-
Returns the {@link ng.$location $location.url()} of the currently loaded page in
100-
the test frame.
101-
102-
### `browser().location().path()`
103-
Returns the {@link ng.$location $location.path()} of the currently loaded page in
104-
the test frame.
105-
106-
### `browser().location().search()`
107-
Returns the {@link ng.$location $location.search()} of the currently loaded page
108-
in the test frame.
109-
110-
### `browser().location().hash()`
111-
Returns the {@link ng.$location $location.hash()} of the currently loaded page in
112-
the test frame.
113-
114-
### `expect(future).{matcher}`
115-
Asserts the value of the given `future` satisfies the `matcher`. All API statements return a
116-
`future` object, which get a `value` assigned after they are executed. Matchers are defined using
117-
`angular.scenario.matcher`, and they use the value of futures to run the expectation. For example:
118-
`expect(browser().location().href()).toEqual('http://www.google.com')`. Available matchers
119-
are presented further down this document.
120-
121-
### `expect(future).not().{matcher}`
122-
Asserts the value of the given `future` satisfies the negation of the `matcher`.
123-
124-
### `using(selector, label)`
125-
Scopes the next DSL element selection.
126-
127-
### `binding(name)`
128-
Returns the value of the first binding matching the given `name`.
129-
130-
### `input(name).enter(value)`
131-
Enters the given `value` in the text field with the corresponding ng-model `name`.
132-
133-
### `input(name).check()`
134-
Checks/unchecks the checkbox with the corresponding ng-model `name`.
135-
136-
### `input(name).select(value)`
137-
Selects the given `value` in the radio button with the corresponding ng-model `name`.
138-
139-
### `input(name).val()`
140-
Returns the current value of an input field with the corresponding ng-model `name`.
141-
142-
### `repeater(selector, label).count()`
143-
Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is
144-
used for test output.
145-
146-
### `repeater(selector, label).row(index)`
147-
Returns an array with the bindings in the row at the given `index` in the repeater matching the
148-
given jQuery `selector`. The `label` is used for test output.
149-
150-
### `repeater(selector, label).column(binding)`
151-
Returns an array with the values in the column with the given `binding` in the repeater matching
152-
the given jQuery `selector`. The `label` is used for test output.
153-
154-
### `select(name).option(value)`
155-
Picks the option with the given `value` on the select with the given ng-model `name`.
156-
157-
### `select(name).options(value1, value2...)`
158-
Picks the options with the given `values` on the multi select with the given ng-model `name`.
159-
160-
### `element(selector, label).count()`
161-
Returns the number of elements that match the given jQuery `selector`. The `label` is used for test
162-
output.
53+
describe('TODO list', function() {
54+
it('should filter results', function() {
16355

164-
### `element(selector, label).click()`
165-
Clicks on the element matching the given jQuery `selector`. The `label` is used for test output.
56+
// Find the element with ng-model="user" and type "jacksparrow" into it
57+
element(by.model('user')).sendKeys('jacksparrow');
16658

167-
### `element(selector, label).query(fn)`
168-
Executes the function `fn(selectedElements, done)`, where selectedElements are the elements that
169-
match the given jQuery `selector` and `done` is a function that is called at the end of the `fn`
170-
function. The `label` is used for test output.
59+
// Find the first (and only) button on the page and click it
60+
element(by.css(':button')).click();
17161

172-
### `element(selector, label).{method}()`
173-
Returns the result of calling `method` on the element matching the given jQuery `selector`, where
174-
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
175-
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
176-
`scrollTop`, `offset`. The `label` is used for test output.
62+
// Verify that there are 10 tasks
63+
expect(element.all(by.repeater('task in tasks')).count()).toEqual(10);
17764

178-
### `element(selector, label).{method}(value)`
179-
Executes the `method` passing in `value` on the element matching the given jQuery `selector`, where
180-
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
181-
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
182-
`scrollTop`, `offset`. The `label` is used for test output.
65+
// Enter 'groceries' into the element with ng-model="filterText"
66+
element(by.model('filterText')).sendKeys('groceries');
18367

184-
### `element(selector, label).{method}(key)`
185-
Returns the result of calling `method` passing in `key` on the element matching the given jQuery
186-
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
187-
`label` is used for test output.
188-
189-
### `element(selector, label).{method}(key, value)`
190-
Executes the `method` passing in `key` and `value` on the element matching the given jQuery
191-
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
192-
`label` is used for test output.
193-
194-
## Matchers
195-
196-
Matchers are used in combination with the `expect(...)` function as described above and can
197-
be negated with `not()`. For instance: `expect(element('h1').text()).not().toEqual('Error')`.
198-
199-
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/matchers.js
200-
201-
```js
202-
// value and Object comparison following the rules of angular.equals().
203-
expect(value).toEqual(value)
204-
205-
// a simpler value comparison using ===
206-
expect(value).toBe(value)
207-
208-
// checks that the value is defined by checking its type.
209-
expect(value).toBeDefined()
210-
211-
// the following two matchers are using JavaScript's standard truthiness rules
212-
expect(value).toBeTruthy()
213-
expect(value).toBeFalsy()
214-
215-
// verify that the value matches the given regular expression. The regular
216-
// expression may be passed in form of a string or a regular expression
217-
// object.
218-
expect(value).toMatch(expectedRegExp)
219-
220-
// a check for null using ===
221-
expect(value).toBeNull()
222-
223-
// Array.indexOf(...) is used internally to check whether the element is
224-
// contained within the array.
225-
expect(value).toContain(expected)
226-
227-
// number comparison using < and >
228-
expect(value).toBeLessThan(expected)
229-
expect(value).toBeGreaterThan(expected)
230-
```
231-
232-
## Example
233-
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples.
234-
235-
### Conditional actions with element(...).query(fn)
236-
237-
E2E testing with angular scenario is highly asynchronous and hides a lot of complexity by
238-
queueing actions and expectations that can handle futures. From time to time, you might need
239-
conditional assertions or element selection. Even though you should generally try to avoid this
240-
(as it is can be sign for unstable tests), you can add conditional behavior with
241-
`element(...).query(fn)`. The following code listing shows how this function can be used to delete
242-
added entries (where an entry is some domain object) using the application's web interface.
243-
244-
Imagine the application to be structured into two views:
245-
246-
1. *Overview view* which lists all the added entries in a table and
247-
2. a *detail view* which shows the entries' details and contains a delete button. When clicking the
248-
delete button, the user is redirected back to the *overview page*.
249-
250-
```js
251-
beforeEach(function () {
252-
var deleteEntry = function () {
253-
browser().navigateTo('/entries');
254-
255-
// we need to select the <tbody> element as it might be the case that there
256-
// are no entries (and therefore no rows). When the selector does not
257-
// result in a match, the test would be marked as a failure.
258-
element('table tbody').query(function (tbody, done) {
259-
// ngScenario gives us a jQuery lite wrapped element. We call the
260-
// `children()` function to retrieve the table body's rows
261-
var children = tbody.children();
262-
263-
if (children.length > 0) {
264-
// if there is at least one entry in the table, click on the link to
265-
// the entry's detail view
266-
element('table tbody a').click();
267-
// and, after a route change, click the delete button
268-
element('.btn-danger').click();
269-
}
270-
271-
// if there is more than one entry shown in the table, queue another
272-
// delete action.
273-
if (children.length > 1) {
274-
deleteEntry();
275-
}
276-
277-
// remember to call `done()` so that ngScenario can continue
278-
// test execution.
279-
done();
280-
});
281-
282-
};
283-
284-
// start deleting entries
285-
deleteEntry();
68+
// Verify that now there is only one item in the task list
69+
expect(element.all(by.repeater('task in tasks')).count()).toEqual(1);
70+
});
28671
});
28772
```
28873

289-
In order to understand what is happening, we should emphasize that ngScenario calls are not
290-
immediately executed, but queued (in ngScenario terms, we would be talking about adding
291-
future actions). If we had only one entry in our table, then the following future actions
292-
would be queued:
293-
294-
```js
295-
// delete entry 1
296-
browser().navigateTo('/entries');
297-
element('table tbody').query(function (tbody, done) { ... });
298-
element('table tbody a');
299-
element('.btn-danger').click();
300-
```
301-
302-
For two entries, ngScenario would have to work on the following queue:
74+
This test describes the requirements of a ToDo list, specifically, that it should be able to
75+
filter the list of items.
30376

304-
```js
305-
// delete entry 1
306-
browser().navigateTo('/entries');
307-
element('table tbody').query(function (tbody, done) { ... });
308-
element('table tbody a');
309-
element('.btn-danger').click();
310-
311-
// delete entry 2
312-
// indented to represent "recursion depth"
313-
browser().navigateTo('/entries');
314-
element('table tbody').query(function (tbody, done) { ... });
315-
element('table tbody a');
316-
element('.btn-danger').click();
317-
```
77+
## Example
78+
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples, or look
79+
at the embedded examples in the Angular documentation (For example, [$http](http://docs.angularjs.org/api/ng/service/$http)
80+
has an end to end test in the example under the `protractor.js` tag).
31881

31982
## Caveats
32083

321-
`ngScenario` does not work with apps that manually bootstrap using `angular.bootstrap`. You must use the `ng-app` directive.
84+
Protractor does not work out-of-the-box with apps that manually bootstrap manually using
85+
`angular.bootstrap`. You must use the `ng-app` directive.

0 commit comments

Comments
 (0)