|
| 1 | +@workInProgress |
| 2 | +@ngdoc overview |
| 3 | +@name Developer Guide: E2E Testing |
| 4 | +@description |
| 5 | + |
| 6 | +As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to |
| 7 | +verify the correctness of new features, catch bugs and notice regressions. |
| 8 | + |
| 9 | +To solve this problem, we have built an Angular Scenario Runner which simulates user interactions |
| 10 | +that will help you verify the health of your Angular application. |
| 11 | + |
| 12 | +# Overview |
| 13 | +You will write scenario tests in JavaScript, which describe how your application should behave, |
| 14 | +given a certain interaction in a specific state. A scenario is comprised of one or more it blocks |
| 15 | +(you can think of these as the requirements of your application), which in turn are made of |
| 16 | +**commands** and **expectations**. Commands tell the Runner to do something with the application |
| 17 | +(such as navigate to a page or click on a button), and expectations tell the Runner to assert |
| 18 | +something about the state (such as the value of a field or the current URL). If any expectation |
| 19 | +fails, the runner marks the `it` as "failed" and continues on to the next one. Scenarios may also |
| 20 | +have **beforeEach** and **afterEach** blocks, which will be run before (or after) each `it` block, |
| 21 | +regardless of whether they pass or fail. |
| 22 | + |
| 23 | +<img src="img/guide/scenario_runner.png"> |
| 24 | + |
| 25 | +In addition to the above elements, scenarios may also contain helper functions to avoid duplicating |
| 26 | +code in the `it` blocks. |
| 27 | + |
| 28 | +Here is an example of a simple scenario: |
| 29 | +<pre> |
| 30 | +describe('Buzz Client', function() { |
| 31 | +it('should filter results', function() { |
| 32 | + input('user').enter('jacksparrow'); |
| 33 | + element(':button').click(); |
| 34 | + expect(repeater('ul li').count()).toEqual(10); |
| 35 | + input('filterText').enter('Bees'); |
| 36 | + expect(repeater('ul li').count()).toEqual(1); |
| 37 | +}); |
| 38 | +}); |
| 39 | +</pre> |
| 40 | +This scenario describes the requirements of a Buzz Client, specifically, that it should be able to |
| 41 | +filter the stream of the user. It starts by entering a value in the 'user' input field, clicking |
| 42 | +the only button on the page, and then it verifies that there are 10 items listed. It then enters |
| 43 | +'Bees' in the 'filterText' input field and verifies that the list is reduced to a single item. |
| 44 | + |
| 45 | +The API section below lists the available commands and expectations for the Runner. |
| 46 | + |
| 47 | +# API |
| 48 | +Source: {@link https://github.com/angular/angular.js/blob/master/src/scenario/dsl.js} |
| 49 | + |
| 50 | +## pause() |
| 51 | +Pauses the execution of the tests until you call `resume()` in the console (or click the resume |
| 52 | +link in the Runner UI). |
| 53 | + |
| 54 | +## sleep(seconds) |
| 55 | +Pauses the execution of the tests for the specified number of `seconds`. |
| 56 | + |
| 57 | +## browser().navigateTo(url) |
| 58 | +Loads the `url` into the test frame. |
| 59 | + |
| 60 | +## browser().navigateTo(url, fn) |
| 61 | +Loads the URL returned by `fn` into the testing frame. The given `url` is only used for the test |
| 62 | +output. Use this when the destination URL is dynamic (that is, the destination is unknown when you |
| 63 | +write the test). |
| 64 | + |
| 65 | +## browser().reload() |
| 66 | +Refreshes the currently loaded page in the test frame. |
| 67 | + |
| 68 | +## browser().window().href() |
| 69 | +Returns the window.location.href of the currently loaded page in the test frame. |
| 70 | + |
| 71 | +## browser().window().path() |
| 72 | +Returns the window.location.pathname of the currently loaded page in the test frame. |
| 73 | + |
| 74 | +## browser().window().search() |
| 75 | +Returns the window.location.search of the currently loaded page in the test frame. |
| 76 | + |
| 77 | +## browser().window().hash() |
| 78 | +Returns the window.location.hash (without `#`) of the currently loaded page in the test frame. |
| 79 | + |
| 80 | +## browser().location().url() |
| 81 | +Returns the {@link api/angular.service.$location $location.url()} of the currently loaded page in |
| 82 | +the test frame. |
| 83 | + |
| 84 | +## browser().location().path() |
| 85 | +Returns the {@link api/angular.service.$location $location.path()} of the currently loaded page in |
| 86 | +the test frame. |
| 87 | + |
| 88 | +## browser().location().search() |
| 89 | +Returns the {@link api/angular.service.$location $location.search()} of the currently loaded page |
| 90 | +in the test frame. |
| 91 | + |
| 92 | +## browser().location().hash() |
| 93 | +Returns the {@link api/angular.service.$location $location.hash()} of the currently loaded page in |
| 94 | +the test frame. |
| 95 | + |
| 96 | +## expect(future).{matcher} |
| 97 | +Asserts the value of the given `future` satisfies the `matcher`. All API statements return a |
| 98 | +`future` object, which get a `value` assigned after they are executed. Matchers are defined using |
| 99 | +`angular.scenario.matcher`, and they use the value of futures to run the expectation. For example: |
| 100 | +`expect(browser().location().href()).toEqual('http://www.google.com')` |
| 101 | + |
| 102 | +## expect(future).not().{matcher} |
| 103 | +Asserts the value of the given `future` satisfies the negation of the `matcher`. |
| 104 | + |
| 105 | +## using(selector, label) |
| 106 | +Scopes the next DSL element selection. |
| 107 | + |
| 108 | +## binding(name) |
| 109 | +Returns the value of the first binding matching the given `name`. |
| 110 | + |
| 111 | +## input(name).enter(value) |
| 112 | +Enters the given `value` in the text field with the given `name`. |
| 113 | + |
| 114 | +## input(name).check() |
| 115 | +Checks/unchecks the checkbox with the given `name`. |
| 116 | + |
| 117 | +## input(name).select(value) |
| 118 | +Selects the given `value` in the radio button with the given `name`. |
| 119 | + |
| 120 | +## input(name).val() |
| 121 | +Returns the current value of an input field with the given `name`. |
| 122 | + |
| 123 | +## repeater(selector, label).count() |
| 124 | +Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is |
| 125 | +used for test ouput. |
| 126 | + |
| 127 | +## repeater(selector, label).row(index) |
| 128 | +Returns an array with the bindings in the row at the given `index` in the repeater matching the |
| 129 | +given jQuery `selector`. The `label` is used for test output. |
| 130 | + |
| 131 | +## repeater(selector, label).column(binding) |
| 132 | +Returns an array with the values in the column with the given `binding` in the repeater matching |
| 133 | +the given jQuery `selector`. The `label` is used for test output. |
| 134 | + |
| 135 | +## select(name).option(value) |
| 136 | +Picks the option with the given `value` on the select with the given `name`. |
| 137 | + |
| 138 | +## select(name).option(value1, value2...) |
| 139 | +Picks the options with the given `values` on the multi select with the given `name`. |
| 140 | + |
| 141 | +## element(selector, label).count() |
| 142 | +Returns the number of elements that match the given jQuery `selector`. The `label` is used for test |
| 143 | +output. |
| 144 | + |
| 145 | +## element(selector, label).click() |
| 146 | +Clicks on the element matching the given jQuery `selector`. The `label` is used for test output. |
| 147 | + |
| 148 | +## element(selector, label).query(fn) |
| 149 | +Executes the function `fn(selectedElements, done)`, where selectedElements are the elements that |
| 150 | +match the given jQuery `selector` and `done` is a function that is called at the end of the `fn` |
| 151 | +function. The `label` is used for test output. |
| 152 | + |
| 153 | +## element(selector, label).{method}() |
| 154 | +Returns the result of calling `method` on the element matching the given jQuery `selector`, where |
| 155 | +`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`, |
| 156 | +`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`, |
| 157 | +`scrollTop`, `offset`. The `label` is used for test output. |
| 158 | + |
| 159 | +## element(selector, label).{method}(value) |
| 160 | +Executes the `method` passing in `value` on the element matching the given jQuery `selector`, where |
| 161 | +`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`, |
| 162 | +`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`, |
| 163 | +`scrollTop`, `offset`. The `label` is used for test output. |
| 164 | + |
| 165 | +## element(selector, label).{method}(key) |
| 166 | +Returns the result of calling `method` passing in `key` on the element matching the given jQuery |
| 167 | +`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The |
| 168 | +`label` is used for test output. |
| 169 | + |
| 170 | +## element(selector, label).{method}(key, value) |
| 171 | +Executes the `method` passing in `key` and `value` on the element matching the given jQuery |
| 172 | +`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The |
| 173 | +`label` is used for test output. |
| 174 | + |
| 175 | +JavaScript is a dynamically typed language which comes with great power of expression, but it also |
| 176 | +come with almost no-help from the compiler. For this reason we feel very strongly that any code |
| 177 | +written in JavaScript needs to come with a strong set of tests. We have built many features into |
| 178 | +angular which makes testing your angular applications easy. So there is no excuse for not do it. |
0 commit comments