UNPKG

12.1 kBMarkdownView Raw
1CukeFarm
2===
3
4An opinionated template for writing Cucumber tests with Protractor.
5
6CukeFarm provides a set of [Cucumber] Steps that can be used to build feature files that are backed by automation using the [Protractor] framework. It also provides a set of helper functions that can be used when writing your own Step Definitions. Check out the [docs] directory for a full list of the Steps and helper functions. The docs are automatically generated using [docha].
7
8[![Build Status](https://travis-ci.org/ReadyTalk/cukefarm.svg?branch=master)](https://travis-ci.org/ReadyTalk/cukefarm)
9
10# Getting Started
11
12To begin, install Protractor. Follow the instructions in the 'Prerequisites' and 'Setup' sections of the [Protractor Tutorial].
13
14Next, install Cucumber using the following command:
15
16 npm install cucumber --save-dev
17
18# Installation
19
20Install CukeFarm by executing the following command from the root of your project:
21
22 npm install cukefarm --save-dev
23
24# Set Up
25
26## Config Object
27
28CukeFarm provides a generic Protractor config file. However, you must provide some additional options that are specific to your project.
29
30### Necessary Options
31
32* Create file called `protractor.conf.js`
33* Use the `require` function to import CukeFarm
34* On the CukeFarm config object, create the following properties:
35 * `specs = <path_to_your_feature_files>`
36 * `capabilities.browserName = <protractor_browser_name>`
37* On the CukeFarm config object, push the path to your project specific World file (See 'World Object' below) onto the `cucumberOpts.require` property
38* Set the CukeFarm config object as the config property on the module exports object
39
40Below is a sample `protractor.conf.js` file that provides the minimum options necessary to run your tests:
41
42 # protractor.conf.js
43
44 var config = require('cukefarm').config;
45
46 config.specs = '../features/**/*.feature';
47 config.capabilities.browserName = 'chrome';
48 config.cucumberOpts.require.push('../support/World.js');
49
50 exports.config = config;
51
52### Adding Step Definitions
53
54CukeFarm provides a set of general Step Definitions, but you will likely need to add more that are specific to your project. Simply push the path of your Step Definition files onto the CukeFarm config's `cucumberOpts.require` property:
55
56 # protractor.conf.js
57
58 files = ['./support/World.js', './step_definitions/**/*.js'];
59 for (i = 0; i < files.length; i++) {
60 config.cucumberOpts.require.push(files[i]);
61 }
62
63### Additional Options
64
65There are a number of different options that Protractor looks for when parsing the test config. You can add these additional options in the same way you added the properties above.
66
67For a full list of what options can be passed to Protractor, see their [Reference Configuration File]
68
69## World Object
70
71The CukeFarm World object give you access to a number of helper functions that will aid you in writing your Step Definitions. However, you must provide a Page Object Map specific to your project.
72
73### What is a Page Object Map?
74
75A Page Object Map will map the Page Objects that you create to human language Strings that can be used when writing Gherkin.
76
77### What does a Page Object Map look like?
78
79Here is a sample Page Object Map:
80
81 # PageObjectMap.js
82
83 module.exports = {
84 "Page One" : require('./pages/PageOne'),
85 "Page Two" : require('./pages/PageTwo'),
86 "Page Three" : require('./pages/PageThree')
87 };
88
89Note: The above sample works, but it requires you to update the map every time you add or remove a Page Object. If you instead define your Page Objects using our Best Practices, you can dynamically create the Page Object Map.
90
91### Adding a Page Object Map to the World
92
93* Create a file called `World.js`
94* Use the `require` function to import CukeFarm
95* Use the `require` function to import your Page Object Map
96* Use the `require` function to import the `defineSupportCode` function from Cucumber
97* Set the pageObjectMap property of the CukeFarm World _prototype_ to your Page Object Map
98 * You must set this on the prototype because Cucumber actually instantiates the World itself using a Constructor function
99* Call the `setWorldConstructor` function inside of `defineSupportCode` and pass it the `World` constructor
100
101Below is a sample `World.js` file:
102
103 var World = require('cukefarm').World;
104 var {defineSupportCode} = require('cucumber');
105
106 World.prototype.pageObjectMap = require('./PageObjectMap');
107
108 defineSupportCode(function({setWorldConstructor}) {
109 setWorldConstructor(World);
110 });
111
112### Why use a Page Object Map?
113
114One of the guiding principles of CukeFarm is that steps should be reusable across multiple page objects wherever possible. This allows you to DRY up your code and prevent an explosion of Step Definitions. To enable this design, CukeFarm forces you to store what page you are on. Generally this is done by calling either the `Given I am on the "<something>" page` or the `Then I should be on the "<something>" page` step, which takes the captured Gherkin and instantiates the page it is mapped to. Later when you try to access a WebElement, the step may use the stored page object to access it, eliminating the need for separate steps per page object. For instance, a sample scenario might look like this:
115
116 # Search.feature
117
118 Scenario: Clicking the "Foo" button on the Search Page will fill in the "Bar" field on the Results Page
119 Given I am on the "Search" page
120 When I type "Foo" in the "Search" field
121 And I click the "Search" button
122 Then I should be on the "Results" page
123 And the "Showing Results For Field" should contain the text "Foo"
124
125# Writing Cucumber Scenarios with CukeFarm
126
127CukeFarm does force you to adhere to certain practices and conventions when writing Cucumber scenarios.
128
129## Page Objects
130
131CukeFarm expects you to organize the representation of your system into objects similar to the [WebDriver Page Object]. CukeFarm has the following expectations of your Page Objects:
132
133### WebElement Property Naming Conventions
134
135Adhering to the following conventions will allow you to use the `stringToVariableName` function on the Transform object to convert captured Gherkin into Page Object keys:
136
137* Every WebElement you intend to interact with on a page should be a property of that Page Object
138* Your key should be formatted [nameOfElement][TypeOfElement]. For instance `fooButton`
139* Your key should be camel case with the first letter being lower case.
140
141### `waitForLoaded` Function
142
143Each Page Object is expected to have a `waitForLoaded` function that returns a promise. The promise should only resolve if the page successfully loads. A typical `waitForLoaded` function will look something like this:
144
145 this.waitForLoaded = function() {
146 return browser.wait((function(_this) {
147 return function() {
148 return _this.barField.isPresent();
149 };
150 })(this), 1000);
151 };
152
153### `get` Function
154
155To provide easy access for the 'Given I am on the "<something>" page' step to reach your page, your page object should contain a `get` function that somehow navigates to your page. A typical `get` function will look something like this:
156
157 this.get = function() {
158 return browser.get('search');
159 };
160
161### Export the class
162
163Be sure to export the Page Object _class_ as opposed to an instance of it.
164
165### Best Practices
166
167* Rather than simply exporting the class, export an object that has two properties: the class and a Gherkin name for your Page Object.
168 * Why: This allows you to dynamically generate a Page Object Map by grabbing all Page Object files using a library like [node-globules] and accessing the Gherkin name from the Page Object export. See below for an example.
169
170### Example Page Objects
171
172Below is the example Scenario from above along with the Page Objects and Page Object Map necessary to support it:
173
174 # Search.feature
175
176 Scenario: Clicking the "Foo" button on the Search Page will fill in the "Bar" field on the Results Page
177 Given I am on the "Search" page
178 When I type "Foo" in the "Search" field
179 And I click the "Search" button
180 Then I should be on the "Results" page
181 And the "Showing Results For Field" should contain the text "Foo"
182
183
184 # PageObjectMap.js
185
186 var file, files, globule, i, len, page, path;
187
188 globule = require('globule');
189 path = require('path');
190
191 files = globule.find('e2e/pages/**/*.js');
192
193 for (i = 0; i < files.length; i++) {
194 page = require(path.resolve(files[i]));
195 module.exports[page.name] = page["class"];
196 }
197
198
199 # SearchPage.js
200
201 var SearchPage = function SearchPage() {
202
203 this.searchField = $('input.search-field');
204 this.searchButton = $('button.search-button');
205
206 this.get = function() {
207 return browser.get('search');
208 };
209
210 this.waitForLoaded = function() {
211 return browser.wait((function(_this) {
212 return function() {
213 return _this.searchButton.isPresent();
214 };
215 })(this), 30000);
216 };
217 }
218
219 module.exports = {
220 "class": SearchPage,
221 name: 'Search'
222 };
223
224
225 # ResultsPage.js
226
227 var ResultsPage = function ResultsPage() {
228
229 this.showingResultsForField = $('span.results-for');
230
231 this.get = function() {
232 return browser.get('results');
233 };
234
235 this.waitForLoaded = function() {
236 return browser.wait((function(_this) {
237 return function() {
238 return _this.showingResultsForField.isPresent();
239 };
240 })(this), 30000);
241 };
242 }
243
244 module.exports = {
245 "class": ResultsPage,
246 name: 'Results'
247 };
248
249
250 # Search.html
251
252 <html>
253 <body>
254 <input class="search-field" />
255 <button class="search-button">Search</button>
256 </body>
257 </html>
258
259
260 # Results.html
261
262 <html>
263 <body>
264 <span class="results-for">Showing results for Foo</span>
265 </body>
266 </html>
267
268# Running Scenarios
269
270To run your scenarios, simply execute the following command:
271
272 protractor path/to/your/protractor.conf.js
273
274# Helper Functions
275
276CukeFarm provides helper functions on the following objects that are defined on the World.
277
278## `transform` Object
279
280The `transform` object contains functions to transform strings that were captured by Step Names into other data types to be used in the Step Definition.
281
282## Custom Transforms
283
284All functions provided by the `transform` object are also provided as custom transforms so that they can be direcly applied to capture groups when using Cucumber Expressions.
285
286## `elementHelper` Object
287
288The `elementHelper` object contains functions to interact with Protractor elements.
289
290# Contributing to CukeFarm
291
292Pull requests are always welcome. Please make sure to adhere to the following guidelines:
293
294## Unit Test your code
295
296In particular, be sure to unit test your Step Definitions. This should be done in two ways:
2971. Test that the name (regex) matches what you expect it to match.
2982. Test that code within the Step Definition functions as you expect.
299
300Note: The unit tests are the contract for the Step Definition names. Any changes to Step Definition names that do not break any unit tests are considered to be backward compatible and may occur at any time in a minor version or patch. IT IS YOUR RESPONSIBILITY TO SAFEGUARD YOUR FEATURE FILES.
301
302# Running CukeFarm Unit Tests
303
304* Install [Firefox]
305* Run `npm install` to download dependencies.
306* Run `npm --prefix ./spec/test_app/ install ./spec/test_app/` to download dependencies for the test app.
307* Run `npm test` to run the unit tests.
308
309[Cucumber]:https://www.npmjs.com/package/cucumber
310[Protractor]:http://angular.github.io/protractor
311[docs]:docs
312[docha]:https://github.com/tehsenaus/docha
313[Protractor Tutorial]:https://angular.github.io/protractor/#/tutorial
314[Reference Configuration File]:https://github.com/angular/protractor/blob/master/docs/referenceConf.js
315[WebDriver Page Object]:https://code.google.com/p/selenium/wiki/PageObjects
316[node-globules]:https://github.com/cowboy/node-globule
317[Firefox]:https://www.mozilla.org/en-US/