Coverage

25%
1166
302
864

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek.js

79%
87
69
18
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// ext. libs
281var async = require('async');
291var EventEmitter2 = require('eventemitter2').EventEmitter2;
30
31// int. libs
321var Driver = require('./dalek/driver');
331var Reporter = require('./dalek/reporter');
341var Timer = require('./dalek/timer');
351var Config = require('./dalek/config');
361var Host = require('./dalek/host');
37
38/**
39 * Default options
40 * @type {Object}
41 */
42
431var defaults = {
44 reporter: ['console'],
45 driver: ['native'],
46 browser: ['phantomjs'],
47 viewport: {width: 1280, height: 1024},
48 logLevel: 3
49};
50
51/**
52 * Setup all the options needed to configure dalek
53 *
54 * @param {options} opts Configuration options
55 * @constructor
56 */
57
581var Dalek = function (opts) {
59 // setup instance
607 this._initialize();
61
62 // register exception handler
637 this._registerExceptionHandler();
64
65 // normalize options
667 this.options = this.normalizeOptions(opts);
67
68 // getting advanced options
697 if (opts && opts.advanced) {
702 this.advancedOptions = opts.advanced;
71 }
72
73 // initiate config
747 this.config = new Config(defaults, this.options, this.advancedOptions);
75
76 // override tests if provided on the commandline
777 if (this.options.tests) {
786 this.config.config.tests = this.options.tests;
79 }
80
81 // prepare and load reporter(s)
827 this._setupReporters();
83
84 // count all passed & failed assertions
857 this.reporterEvents.on('report:assertion', this._onReportAssertion.bind(this));
86
87 // init the timer instance
887 this.timer = new Timer();
89
90 // prepare driver event emitter instance
917 this._setupDriverEmitter();
92
93 // check for file option, throw error if none is given
94 // only bypasses if dalek runs in the remote mode
957 if (!Array.isArray(this.config.get('tests')) && !this.options.remote) {
961 this.reporterEvents.emit('error', 'No test files given!');
971 this.driverEmitter.emit('killAll');
981 process.exit(127);
99 }
100
101 // init the driver instance
1027 this._initDriver();
103
104 // check if dalek runs as a remote browser launcher
1057 if (this.options.remote) {
1060 var host = new Host({reporterEvents: this.reporterEvents, config: this.config});
1070 host.run({
108 port: !isNaN(parseFloat(this.options.remote)) && isFinite(this.options.remote) ? this.options.remote : false
109 });
110 }
111};
112
113/**
114 * Daleks base module
115 * Used to configure all the things
116 * and to start off the tests
117 *
118 * @module DalekJS
119 * @class Dalek
120 */
121
1221Dalek.prototype = {
123
124 /**
125 * Runs the configured testsuites
126 *
127 * @method run
128 * @chainable
129 */
130
131 run: function () {
132 // early return; in case of remote
1330 if (this.options.remote) {
1340 return this;
135 }
136
137 // start the timer to measure the execution time
1380 this.timer.start();
139
140 // emit the runner started event
1410 this.reporterEvents.emit('report:runner:started');
142
143 // execute all given drivers sequentially
1440 var drivers = this.driver.getDrivers();
1450 async.series(drivers, this.testsuitesFinished.bind(this));
1460 return this;
147 },
148
149 /**
150 * Reports the all testsuites executed event
151 *
152 * @method testsuitesFinished
153 * @chainable
154 */
155
156 testsuitesFinished: function () {
1571 this.driverEmitter.emit('tests:complete');
1581 setTimeout(this.reportRunFinished.bind(this), 0);
1591 return this;
160 },
161
162 /**
163 * Reports the all testsuites executed event
164 *
165 * @method reportRunFinished
166 * @chainable
167 */
168
169 reportRunFinished: function () {
1702 this.reporterEvents.emit('report:runner:finished', {
171 elapsedTime: this.timer.stop().getElapsedTimeFormatted(),
172 assertions: this.assertionsFailed + this.assertionsPassed,
173 assertionsFailed: this.assertionsFailed,
174 assertionsPassed: this.assertionsPassed,
175 status: this.runnerStatus
176 });
177
178 //we want to exit process with code 1 to single that test did not pass
1792 if(this.runnerStatus !== true) {
1800 var processExitCaptured = false;
181
1820 process.on('exit', function() {
1830 if(processExitCaptured === false) {
1840 processExitCaptured = true;
1850 process.exit(1);
186 }
187 });
188 }
189
1902 return this;
191 },
192
193 /**
194 * Normalizes options
195 *
196 * @method normalizeOptions
197 * @param {object} options Raw options
198 * @return {object} Normalized options
199 */
200
201 normalizeOptions: function (options) {
2027 Object.keys(options).forEach(function (key) {
20351 if ({reporter: 1, driver: 1}[key]) {
20414 options[key] = options[key].map(function (input) { return input.trim(); });
205 }
206 });
207
2087 return options;
209 },
210
211 /**
212 * Sets up system env properties
213 *
214 * @method _initialize
215 * @chainable
216 * @private
217 */
218
219 _initialize: function () {
220 // prepare error data
2217 this.warnings = [];
2227 this.errors = [];
223
224 // prepare state data for the complete test run
2257 this.runnerStatus = true;
2267 this.assertionsFailed = 0;
2277 this.assertionsPassed = 0;
228
2297 return this;
230 },
231
232 /**
233 * Sets up all the reporters
234 *
235 * @method _setupReporters
236 * @chainable
237 * @private
238 */
239
240 _setupReporters: function () {
2417 this.reporters = [];
2427 this.reporterEvents = new EventEmitter2();
2437 this.reporterEvents.setMaxListeners(Infinity);
2447 this.options.reporter = this.config.verifyReporters(this.config.get('reporter'), Reporter);
2457 this.options.reporter.forEach(this._addReporter, this);
2467 return this;
247 },
248
249 /**
250 * Adds a reporter
251 *
252 * @method _addReporter
253 * @param {string} reporter Name of the reporter to add
254 * @chainable
255 * @private
256 */
257
258 _addReporter: function (reporter) {
2597 this.reporters.push(Reporter.loadReporter(reporter, {events: this.reporterEvents, config: this.config, logLevel: this.config.get('logLevel')}));
2607 return this;
261 },
262
263 /**
264 * Updates the assertion state
265 *
266 * @method _onReportAssertion
267 * @param {object} assertion Informations aout the runned assertions
268 * @chainable
269 * @private
270 */
271
272 _onReportAssertion: function (assertion) {
2732 if (assertion.success) {
2741 this.assertionsPassed++;
275 } else {
2761 this.runnerStatus = false;
2771 this.assertionsFailed++;
278 }
2792 return this;
280 },
281
282 /**
283 * Initizializes the driver instances
284 *
285 * @method _initDriver
286 * @chainable
287 * @private
288 */
289
290 _initDriver: function () {
2917 this.driver = new Driver({
292 config: this.config,
293 driverEmitter: this.driverEmitter,
294 reporterEvents: this.reporterEvents
295 });
296
2977 this.options.driver = this.config.verifyDrivers(this.config.get('driver'), this.driver);
2987 return this;
299 },
300
301 /**
302 * Sets up the event dispatcher for driver events
303 *
304 * @method _setupDriverEmitter
305 * @chainable
306 * @private
307 */
308
309 _setupDriverEmitter: function () {
3107 var driverEmitter = new EventEmitter2();
3117 driverEmitter.setMaxListeners(Infinity);
3127 this.driverEmitter = driverEmitter;
3137 return this;
314 },
315
316 /**
317 * Make sure to shutdown dalek & its spawned
318 * components, webservers gracefully if a
319 * runtime error pops up
320 *
321 * @method _registerExceptionHandler
322 * @private
323 * @chainable
324 */
325
326 _registerExceptionHandler: function () {
3277 process.setMaxListeners(Infinity);
3287 process.on('uncaughtException', this._shutdown.bind(this));
3297 return this;
330 },
331
332 /**
333 * Shutdown on uncaught exception
334 *
335 * @method _shutdown
336 * @param {object} exception Runtime exception
337 * @private
338 */
339
340 _shutdown: function (exception) {
341 // ios emulator hack, needs to go in the future
3420 if (exception.message && exception.message.search('This socket has been ended by the other party') !== -1) {
3430 return false;
344 }
345
3460 this.driverEmitter.emit('killAll');
3470 this.reporterEvents.emit('error', exception);
348 }
349
350};
351
352// export dalek as a module
3531module.exports = Dalek;
354

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/actions.js

17%
276
48
228
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
241'use strict';
25
26// ext. libs
271var Q = require('q');
281var uuid = require('./uuid');
291var cheerio = require('cheerio');
30
31// int. global
321var reporter = null;
33
34/**
35 * Actions are a way to control your browsers, e.g. simulate user interactions
36 * like clicking elements, open urls, filling out input fields, etc.
37 *
38 * @class Actions
39 * @constructor
40 * @part Actions
41 * @api
42 */
43
441var Actions = function () {
450 this.uuids = {};
46};
47
48/**
49 * It can be really cumbersome to repeat selectors all over when performing
50 * multiple actions or assertions on the same element(s).
51 * When you use the query method (or its alias $), you're able to specify a
52 * selector once instead of repeating it all over the place.
53 *
54 * So, instead of writing this:
55 *
56 * ```javascript
57 * test.open('http://doctorwhotv.co.uk/')
58 * .assert.text('#nav').is('Navigation')
59 * .assert.visible('#nav')
60 * .assert.attr('#nav', 'data-nav', 'true')
61 * .click('#nav')
62 * .done();
63 * ```
64 *
65 * you can write this:
66 *
67 * ```javascript
68 * test.open('http://doctorwhotv.co.uk/')
69 * .query('#nav')
70 * .assert.text().is('Navigation')
71 * .assert.visible()
72 * .assert.attr('data-nav', 'true')
73 * .click()
74 * .end()
75 * .done();
76 * ```
77 *
78 * Always make sure to terminate it with the [end](assertions.html#meth-end) method!
79 *
80 * @api
81 * @method query
82 * @param {string} selector Selector of the element to query
83 * @chainable
84 */
85
861Actions.prototype.query = function (selector) {
870 var that = !this.test ? this : this.test;
880 that.lastChain.push('querying');
890 that.selector = selector;
900 that.querying = true;
910 return this.test ? this : that;
92};
93
94/**
95 * Alias of query
96 *
97 * @api
98 * @method $
99 * @param {string} selector Selector of the element to query
100 * @chainable
101 */
102
1031Actions.prototype.$ = Actions.prototype.query;
104
105/**
106 * Triggers a mouse event on the first element found matching the provided selector.
107 * Supported events are mouseup, mousedown, click, mousemove, mouseover and mouseout.
108 * TODO: IMPLEMENT
109 *
110 * @method mouseEvent
111 * @param {string} type
112 * @param {string} selector
113 * @chainable
114 */
115
1161Actions.prototype.mouseEvent = function (type, selector) {
1170 var hash = uuid();
1180 var cb = this._generateCallbackAssertion('mouseEvent', 'mouseEvent', type, selector, hash);
1190 this._addToActionQueue([type, selector, hash], 'mouseEvent', cb);
1200 return this;
121};
122
123/**
124 * Sets HTTP_AUTH_USER and HTTP_AUTH_PW values for HTTP based authentication systems.
125 *
126 * If your site is behind a HTTP basic auth, you're able to set the username and the password
127 *
128 * ```javascript
129 * test.setHttpAuth('OSWIN', 'rycbrar')
130 * .open('http://admin.therift.com');
131 * ```
132 *
133 * Most of the time, you`re not storing your passwords within files that will be checked
134 * in your vcs, for this scenario, you have two options:
135 *
136 * The first option is, to use daleks cli capabilities to generate config variables
137 * from the command line, like this
138 *
139 * ```batch
140 * $ dalek --vars USER=OSWIN,PASS=rycbrar
141 * ```
142 *
143 * ```javascript
144 * test.setHttpAuth(test.config.get('USER'), test.config.get('PASS'))
145 * .open('http://admin.therift.com');
146 * ```
147 *
148 * The second option is, to use env variables to generate config variables
149 * from the command line, like this
150 *
151 * ```batch
152 * $ SET USER=OSWIN
153 * $ SET PASS=rycbrar
154 * $ dalek
155 * ```
156 *
157 * ```javascript
158 * test.setHttpAuth(test.config.get('USER'), test.config.get('PASS'))
159 * .open('http://admin.therift.com');
160 * ```
161 *
162 * If both, dalek variables & env variables are set, the dalek variables win.
163 * For more information about this, I recommend to check out the [configuration docs](/docs/config.html)
164 *
165 * TODO: IMPLEMENT
166 *
167 * @method setHttpAuth
168 * @param {string} username
169 * @param {string} password
170 * @return {Actions}
171 */
172
1731Actions.prototype.setHttpAuth = function (username, password) {
1740 var hash = uuid();
1750 var cb = this._generateCallbackAssertion('setHttpAuth', 'setHttpAuth', username, password, hash);
1760 this._addToActionQueue([username, password, hash], 'setHttpAuth', cb);
1770 return this;
178};
179
180/**
181 * Switches to an iFrame context
182 *
183 * Sometimes you encounter situations, where you need to drive/access an iFrame sitting in your page.
184 * You can access such frames with this method, but be aware of the fact, that the complete test context
185 * than switches to the iframe context, every action and assertion will be executed within the iFrame context.
186 * Btw.: The domain of the IFrame can be whatever you want, this method has no same origin policy restrictions.
187 *
188 * If you wan't to get back to the parents context, you have to use the [toParent](#meth-toParent) method.
189 *
190 * ```html
191 * <div>
192 * <iframe id="login" src="/login.html"/>
193 * </div>
194 * ```
195 *
196 * ```javascript
197 * test.open('http://adomain.withiframe.com')
198 * .assert.title().is('Title of a page that embeds an iframe')
199 * .toFrame('#login')
200 * .assert.title().is('Title of a page that can be embedded as an iframe')
201 * .toParent()
202 * .done();
203 * ```
204 *
205 * > NOTE: Buggy in Firefox
206 *
207 * @api
208 * @method toFrame
209 * @param {string} selector Selector of the frame to switch to
210 * @chainable
211 */
212
2131Actions.prototype.toFrame = function (selector) {
2140 var hash = uuid();
215
2160 if (this.querying === true) {
2170 selector = this.selector;
218 }
219
2200 var cb = this._generateCallbackAssertion('toFrame', 'toFrame', selector, hash);
2210 this._addToActionQueue([selector, hash], 'toFrame', cb);
2220 return this;
223};
224
225/**
226 * Switches back to the parent page context when the test context has been
227 * switched to an iFrame context
228 *
229 * ```html
230 * <div>
231 * <iframe id="login" src="/login.html"/>
232 * </div>
233 * ```
234 *
235 * ```javascript
236 * test.open('http://adomain.withiframe.com')
237 * .assert.title().is('Title of a page that embeds an iframe')
238 * .toFrame('#login')
239 * .assert.title().is('Title of a page that can be embedded as an iframe')
240 * .toParent()
241 * .assert.title().is('Title of a page that embeds an iframe')
242 * .done();
243 * ```
244 *
245 * > NOTE: Buggy in Firefox
246 *
247 * @api
248 * @method toParent
249 * @chainable
250 */
251
2521Actions.prototype.toParent = function () {
2530 var hash = uuid();
2540 var cb = this._generateCallbackAssertion('toFrame', 'toFrame', null, hash);
2550 this._addToActionQueue([null, hash], 'toFrame', cb);
2560 return this;
257};
258
259/**
260 * Switches to a different window context
261 *
262 * Sometimes you encounter situations, where you need to access a different window, like popup windows.
263 * You can access such windows with this method, but be aware of the fact, that the complete test context
264 * than switches to the window context, every action and assertion will be executed within the chosen window context.
265 * Btw.: The domain of the window can be whatever you want, this method has no same origin policy restrictions.
266 *
267 * If you want to get back to the parents context, you have to use the [toParentWindow](#meth-toParentWindow) method.
268 *
269 * ```html
270 * <div>
271 * <a onclick="window.open('http://google.com','goog','width=480, height=300')">Open Google</a>
272 * </div>
273 * ```
274 *
275 * ```javascript
276 * test.open('http://adomain.com')
277 * .assert.title().is('Title of a page that can open a popup window')
278 * .toWindow('goog')
279 * .assert.title().is('Google')
280 * .toParentWindow()
281 * .done();
282 * ```
283 *
284 * > NOTE: Buggy in Firefox
285 *
286 * @api
287 * @method toWindow
288 * @param {string} name Name of the window to switch to
289 * @chainable
290 */
291
2921Actions.prototype.toWindow = function (name) {
2930 var hash = uuid();
2940 var cb = this._generateCallbackAssertion('toWindow', 'toWindow', name, hash);
2950 this._addToActionQueue([name, hash], 'toWindow', cb);
2960 return this;
297};
298
299/**
300 * Switches back to the parent window context when the test context has been
301 * switched to a different window context
302 *
303 * ```html
304 * <div>
305 * <a onclick="window.open('http://google.com','goog','width=480, height=300')">Open Google</a>
306 * </div>
307 * ```
308 *
309 * ```javascript
310 * test.open('http://adomain.com')
311 * .assert.title().is('Title of a page that can open a popup window')
312 * .toWindow('goog')
313 * .assert.title().is('Google')
314 * .toParentWindow()
315 * .assert.title().is('Title of a page that can open a popup window')
316 * .done();
317 * ```
318 *
319 * > NOTE: Buggy in Firefox
320 *
321 * @api
322 * @method toParentWindow
323 * @chainable
324 */
325
3261Actions.prototype.toParentWindow = function () {
3270 var hash = uuid();
3280 var cb = this._generateCallbackAssertion('toWindow', 'toWindow', null, hash);
3290 this._addToActionQueue([null, hash], 'toWindow', cb);
3300 return this;
331};
332
333/**
334 * Wait until a resource that matches the given testFx is loaded to process a next step.
335 *
336 * TODO: IMPLEMENT
337 *
338 * @method waitForResource
339 * @param {string} ressource URL of the ressource that should be waited for
340 * @param {number} timeout Timeout in miliseconds
341 * @chainable
342 */
343
3441Actions.prototype.waitForResource = function (ressource, timeout) {
3450 var hash = uuid();
3460 var cb = this._generateCallbackAssertion('waitForResource', 'waitForResource', ressource, timeout, hash);
3470 this._addToActionQueue([ressource, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitForResource', cb);
3480 return this;
349};
350
351/**
352 * Waits until the passed text is present in the page contents before processing the immediate next step.
353 *
354 * TODO: IMPLEMENT
355 *
356 * @method waitForText
357 * @param {string} text Text to be waited for
358 * @param {number} timeout Timeout in miliseconds
359 * @chainable
360 */
361
3621Actions.prototype.waitForText = function (text, timeout) {
3630 var hash = uuid();
3640 var cb = this._generateCallbackAssertion('waitForText', 'waitForText', text, timeout, hash);
3650 this._addToActionQueue([text, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitForText', cb);
3660 return this;
367};
368
369/**
370 * Waits until an element matching the provided selector expression is visible in the remote DOM to process a next step.
371 *
372 * TODO: IMPLEMENT
373 *
374 * @method waitUntilVisible
375 * @param {string} selector Selector of the element that should be waited to become invisible
376 * @param {number} timeout Timeout in miliseconds
377 * @chainable
378 */
379
3801Actions.prototype.waitUntilVisible = function (selector, timeout) {
3810 var hash = uuid();
382
3830 if (this.querying === true) {
3840 timeout = selector;
3850 selector = this.selector;
386 }
387
3880 var cb = this._generateCallbackAssertion('waitUntilVisible', 'waitUntilVisible', selector, timeout, hash);
3890 this._addToActionQueue([selector, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitUntilVisible', cb);
3900 return this;
391};
392
393/**
394 * Waits until an element matching the provided selector expression is no longer visible in remote DOM to process a next step.
395 *
396 * TODO: IMPLEMENT
397 *
398 * @method waitWhileVisible
399 * @param {string} selector Selector of the element that should be waited to become visible
400 * @param {number} timeout Timeout in miliseconds
401 * @chainable
402 */
403
4041Actions.prototype.waitWhileVisible = function (selector, timeout) {
4050 var hash = uuid();
406
4070 if (this.querying === true) {
4080 timeout = selector;
4090 selector = this.selector;
410 }
411
4120 var cb = this._generateCallbackAssertion('waitWhileVisible', 'waitWhileVisible', selector, timeout, hash);
4130 this._addToActionQueue([selector, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitWhileVisible', cb);
4140 return this;
415};
416
417/**
418 * Take a screenshot of the current page.
419 *
420 * The pathname argument takes some placeholders that will be replaced
421 * Placeholder:
422 *
423 * - `:browser` - The browser name (e.g. 'Chrome', 'Safari', 'Firefox', etc.)
424 * - `:version` - The browser version (e.g. '10_0', '23_11_5', etc.)
425 * - `:os` - The operating system (e.g. `OSX`, `Windows`, `Linux`)
426 * - `:osVersion` - The operating system version (e.g `XP`, `7`, `10_8`, etc.)
427 * - `:viewport` - The current viewport in pixels (e.g. `w1024_h768`)
428 * - `:timestamp` - UNIX like timestapm (e.g. `637657345`)
429 * - `:date` - Current date in format MM_DD_YYYY (e.g. `12_24_2013`)
430 * - `:datetime` - Current datetime in format MM_DD_YYYY_HH_mm_ss (e.g. `12_24_2013_14_55_23`)
431 *
432 * ```javascript
433 * // creates 'my/folder/my_file.png'
434 * test.screenshot('my/folder/my_file');
435 * // creates 'my/page/in/safari/homepage.png'
436 * test.screenshot('my/page/in/:browser/homepage');
437 * // creates 'my/page/in/safari_6_0_1/homepage.png'
438 * test.screenshot('my/page/in/:browser_:version/homepage');
439 * // creates 'my/page/in/safari_6_0_1/on/osx/homepage.png'
440 * test.screenshot('my/page/in/:browser_:version/on/:os/homepage');
441 * // creates 'my/page/in/safari_6_0_1/on/osx_10_8/homepage.png'
442 * test.screenshot('my/page/in/:browser_:version/on/:os_:osVersion/homepage');
443 * // creates 'my/page/at/w1024_h768/homepage.png'
444 * test.screenshot('my/page/at/:viewport/homepage');
445 * // creates 'my/page/at/637657345/homepage.png'
446 * test.screenshot('my/page/in_time/:timestamp/homepage');
447 * // creates 'my/page/at/12_24_2013/homepage.png'
448 * test.screenshot('my/page/in_time/:date/homepage');
449 * // creates 'my/page/at/12_24_2013_14_55_23/homepage.png'
450 * test.screenshot('my/page/in_time/:datetime/homepage');
451 * ```
452 *
453 * @api
454 * @method screenshot
455 * @param {string} pathname Name of the folder and file the screenshot should be saved to
456 * @return chainable
457 */
458
4591Actions.prototype.screenshot = function (pathname) {
4600 var hash = uuid();
4610 var cb = this._generateCallbackAssertion('screenshot', 'screenshot', pathname, hash);
4620 this._addToActionQueue(['', pathname, hash], 'screenshot', cb);
4630 return this;
464};
465
466/**
467 * Pause steps suite execution for a given amount of time, and optionally execute a step on done.
468 *
469 * This makes sense, if you have a ticker for example, tht scrolls like every ten seconds
470 * & you want to assure that the visible content changes every ten seconds
471 *
472 * ```javascript
473 * test.open('http://myticker.org')
474 * .assert.visible('.ticker-element:first-child', 'First ticker element is visible')
475 * .wait(10000)
476 * .assert.visible('.ticker-element:nth-child(2)', 'Snd. ticker element is visible')
477 * .wait(10000)
478 * .assert.visible('.ticker-element:last-child', 'Third ticker element is visible')
479 * .done();
480 * ```
481 * If no timeout argument is given, a default timeout of 5 seconds will be used
482 *
483 * ```javascript
484 * test.open('http://myticker.org')
485 * .assert.visible('.ticker-element:first-child', 'First ticker element is visible')
486 * .wait()
487 * .assert.visible('.ticker-element:nth-child(2)', 'Snd. ticker element is visible')
488 * .wait()
489 * .assert.visible('.ticker-element:last-child', 'Third ticker element is visible')
490 * .done();
491 * ```
492 *
493 * @api
494 * @method wait
495 * @param {number} timeout in milliseconds
496 * @chainable
497 */
498
4991Actions.prototype.wait = function (timeout) {
5000 var hash = uuid();
5010 var cb = this._generateCallbackAssertion('wait', 'wait', timeout, hash);
5020 this._addToActionQueue([(timeout ? parseInt(timeout, 10) : 5000), hash], 'wait', cb);
5030 return this;
504};
505
506/**
507 * Reloads current page location.
508 *
509 * This is basically the same as hitting F5/refresh in your browser
510 *
511 * ```javascript
512 * test.open('http://google.com')
513 * .reload()
514 * .done();
515 * ```
516 *
517 * @api
518 * @method reload
519 * @chainable
520 */
521
5221Actions.prototype.reload = function () {
5230 var hash = uuid();
5240 var cb = this._generateCallbackAssertion('refresh', 'refresh', '', hash);
5250 this._addToActionQueue([hash], 'refresh', cb);
5260 return this;
527};
528
529/**
530 * Moves a step forward in browser's history.
531 *
532 * This is basically the same as hitting the forward button in your browser
533 *
534 * ```javascript
535 * test.open('http://google.com')
536 * .open('https://github.com')
537 * .assert.url.is('https://github.com/', 'We are at GitHub')
538 * .back()
539 * .assert.url.is('http://google.com', 'We are at Google!')
540 * .forward()
541 * .assert.url.is('https://github.com/', 'Back at GitHub! Timetravel FTW')
542 * .done();
543 * ```
544 *
545 * @api
546 * @method forward
547 * @chainable
548 */
549
5501Actions.prototype.forward = function () {
5510 var hash = uuid();
5520 var cb = this._generateCallbackAssertion('forward', 'forward', '', hash);
5530 this._addToActionQueue([hash], 'forward', cb);
5540 return this;
555};
556
557/**
558 * Moves back a step in browser's history.
559 *
560 * This is basically the same as hitting the back button in your browser
561 *
562 * ```javascript
563 * test.open('http://google.com')
564 * .open('https://github.com')
565 * .assert.url.is('https://github.com/', 'We are at GitHub')
566 * .back()
567 * .assert.url.is('http://google.com', 'We are at Google!')
568 * .forward()
569 * .assert.url.is('https://github.com/', 'Back at GitHub! Timetravel FTW');
570 * .done();
571 * ```
572 *
573 * @api
574 * @method back
575 * @chainable
576 */
577
5781Actions.prototype.back = function () {
5790 var hash = uuid();
5800 var cb = this._generateCallbackAssertion('back', 'back', '', hash);
5810 this._addToActionQueue([hash], 'back', cb);
5820 return this;
583};
584
585/**
586 * Performs a click on the element matching the provided selector expression.
587 *
588 * If we take Daleks homepage (the one you're probably visiting right now),
589 * the HTML looks something like this (it does not really, but hey, lets assume this for a second)
590 *
591 * ```html
592 * <nav>
593 * <ul>
594 * <li><a id="homeapge" href="/index.html">DalekJS</a></li>
595 * <li><a id="docs" href="/docs.html">Documentation</a></li>
596 * <li><a id="faq" href="/faq.html">F.A.Q</a></li>
597 * </ul>
598 * </nav>
599 * ```
600 *
601 * ```javascript
602 * test.open('http://dalekjs.com')
603 * .click('#faq')
604 * .assert.title().is('DalekJS - Frequently asked questions', 'What the F.A.Q.')
605 * .done();
606 * ```
607 *
608 * By default, this performs a left click.
609 * In the future it might become the ability to also execute a "right button" click.
610 *
611 * > Note: Does not work correctly in Firefox when used on `<select>` & `<option>` elements
612 *
613 * @api
614 * @method click
615 * @param {string} selector Selector of the element to be clicked
616 * @chainable
617 */
618
6191Actions.prototype.click = function (selector) {
6200 var hash = uuid();
621
6220 if (this.querying === true) {
6230 selector = this.selector;
624 }
625
6260 var cb = this._generateCallbackAssertion('click', 'click', selector, hash);
6270 this._addToActionQueue([selector, hash], 'click', cb);
6280 return this;
629};
630
631/**
632 * Submits a form.
633 *
634 * ```html
635 * <form id="skaaro" action="skaaro.php" method="GET">
636 * <input type="hidden" name="intheshadows" value="itis"/>
637 * <input type="text" name="truth" id="truth" value=""/>
638 * </form>
639 * ```
640 *
641 * ```javascript
642 * test.open('http://home.dalek.com')
643 * .type('#truth', 'out there is')
644 * .submit('#skaaro')
645 * .done();
646 * ```
647 *
648 * > Note: Does not work in Firefox yet
649 *
650 * @api
651 * @method submit
652 * @param {string} selector Selector of the form to be submitted
653 * @chainable
654 */
655
6561Actions.prototype.submit = function (selector) {
6570 var hash = uuid();
658
6590 if (this.querying === true) {
6600 selector = this.selector;
661 }
662
6630 var cb = this._generateCallbackAssertion('submit', 'submit', selector, hash);
6640 this._addToActionQueue([selector, hash], 'submit', cb);
6650 return this;
666};
667
668/**
669 * Performs an HTTP request for opening a given location.
670 * You can forge GET, POST, PUT, DELETE and HEAD requests.
671 *
672 * Basically the same as typing a location into your browsers URL bar and
673 * hitting return.
674 *
675 * ```javascript
676 * test.open('http://dalekjs.com')
677 * .assert.url().is('http://dalekjs.com', 'DalekJS I\'m in you')
678 * .done();
679 * ```
680 *
681 * @api
682 * @method open
683 * @param {string} location URL of the page to open
684 * @chainable
685 */
686
6871Actions.prototype.open = function (location) {
688 //see if we should prepend the location with the configured base url is available and needed
6890 if(location.substr(0, 1) === '/' && this.driver.config.config.baseUrl) {
6900 location = this.driver.config.config.baseUrl + location;
691 }
692
6930 var hash = uuid();
6940 var cb = this._generateCallbackAssertion('open', 'open', location, hash);
6950 this._addToActionQueue([location, hash], 'open', cb);
6960 return this;
697};
698
699/**
700 * Types a text into an input field or text area.
701 * And yes, it really types, character for character, like you would
702 * do when using your keyboard.
703 *
704 *
705 * ```html
706 * <form id="skaaro" action="skaaro.php" method="GET">
707 * <input type="hidden" name="intheshadows" value="itis"/>
708 * <input type="text" name="truth" id="truth" value=""/>
709 * </form>
710 * ```
711 *
712 * ```javascript
713 * test.open('http://home.dalek.com')
714 * .type('#truth', 'out there is')
715 * .assert.val('#truth', 'out there is', 'Text has been set')
716 * .done();
717 * ```
718 *
719 * You can also send special keys using unicode.
720 *
721 * * ```javascript
722 * test.open('http://home.dalek.com')
723 * .type('#truth', 'out \uE008there\uE008 is')
724 * .assert.val('#truth', 'out THERE is', 'Text has been set')
725 * .done();
726 * ```
727 * You can go [here](https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value) to read up on special keys and unicodes for them (note that a code of U+EXXX is actually written in code as \uEXXX).
728 *
729 * > Note: Does not work correctly in Firefox with special keys
730 *
731 * @api
732 * @method type
733 * @param {string} selector Selector of the form field to be filled
734 * @param {string} keystrokes Text to be applied to the element
735 * @chainable
736 */
737
7381Actions.prototype.type = function (selector, keystrokes) {
7390 var hash = uuid();
740
7410 if (this.querying === true) {
7420 keystrokes = selector;
7430 selector = this.selector;
744 }
745
7460 var cb = this._generateCallbackAssertion('type', 'type', selector, keystrokes, hash);
7470 this._addToActionQueue([selector, keystrokes], 'type', cb);
7480 return this;
749};
750
751/**
752 * This acts just like .type() with a key difference.
753 * This action can be used on non-input elements (useful for test site wide keyboard shortcuts and the like).
754 * So assumeing we have a keyboard shortcut that display an alert box, we could test that with something like this:
755 *
756 * ```javascript
757 * test.open('http://home.dalek.com')
758 * .sendKeys('body', '\uE00C')
759 * .assert.dialogText('press the escape key give this alert text')
760 * .done();
761 * ```
762 *
763 *
764 * > Note: Does not work correctly in Firefox with special keys
765 *
766 * @api
767 * @method sendKeys
768 * @param {string} selector Selector of the form field to be filled
769 * @param {string} keystrokes Text to be applied to the element
770 * @chainable
771 */
772
7731Actions.prototype.sendKeys = function (selector, keystrokes) {
7740 var hash = uuid();
775
7760 if (this.querying === true) {
7770 keystrokes = selector;
7780 selector = this.selector;
779 }
780
7810 var cb = this._generateCallbackAssertion('sendKeys', 'sendKeys', selector, keystrokes, hash);
7820 this._addToActionQueue([selector, keystrokes], 'sendKeys', cb);
7830 return this;
784};
785
786/**
787 * Types a text into the text input field of a prompt dialog.
788 * Like you would do when using your keyboard.
789 *
790 * ```html
791 * <div>
792 * <a id="aquestion" onclick="this.innerText = window.prompt('Your favourite companion:')">????</a>
793 * </div>
794 * ```
795 *
796 * ```javascript
797 * test.open('http://adomain.com')
798 * .click('#aquestion')
799 * .answer('Rose')
800 * .assert.text('#aquestion').is('Rose', 'Awesome she was!')
801 * .done();
802 * ```
803 *
804 *
805 * > Note: Does not work in Firefox & PhantomJS
806 *
807 * @api
808 * @method answer
809 * @param {string} keystrokes Text to be applied to the element
810 * @return chainable
811 */
812
8131Actions.prototype.answer = function (keystrokes) {
8140 var hash = uuid();
8150 var cb = this._generateCallbackAssertion('promptText', 'promptText', keystrokes, hash);
8160 this._addToActionQueue([keystrokes, hash], 'promptText', cb);
8170 return this;
818};
819
820/**
821 * Executes a JavaScript function within the browser context
822 *
823 * ```javascript
824 * test.open('http://adomain.com')
825 * .execute(function () {
826 * window.myFramework.addRow('foo');
827 * window.myFramework.addRow('bar');
828 * })
829 * .done();
830 * ```
831 *
832 * You can also apply arguments to the function
833 *
834 * ```javascript
835 * test.open('http://adomain.com')
836 * .execute(function (paramFoo, aBar) {
837 * window.myFramework.addRow(paramFoo);
838 * window.myFramework.addRow(aBar);
839 * }, 'foo', 'bar')
840 * .done();
841 * ```
842 *
843 * > Note: Buggy in Firefox
844 *
845 * @api
846 * @method execute
847 * @param {function} script JavaScript function that should be executed
848 * @return chainable
849 */
850
8511Actions.prototype.execute = function (script) {
8520 var hash = uuid();
8530 var args = [this.contextVars].concat(Array.prototype.slice.call(arguments, 1) || []);
8540 var cb = this._generateCallbackAssertion('execute', 'execute', script, args, hash);
8550 this._addToActionQueue([script, args, hash], 'execute', cb);
8560 return this;
857};
858
859/**
860 * Waits until a function returns true to process any next step.
861 *
862 * You can also set a callback on timeout using the onTimeout argument,
863 * and set the timeout using the timeout one, in milliseconds. The default timeout is set to 5000ms.
864 *
865 * ```javascript
866 * test.open('http://adomain.com')
867 * .waitFor(function () {
868 * return window.myCheck === true;
869 * })
870 * .done();
871 * ```
872 *
873 * You can also apply arguments to the function, as well as a timeout
874 *
875 * ```javascript
876 * test.open('http://adomain.com')
877 * .waitFor(function (aCheck) {
878 * return window.myThing === aCheck;
879 * }, ['arg1', 'arg2'], 10000)
880 * .done();
881 * ```
882 *
883 * > Note: Buggy in Firefox
884 *
885 * @method waitFor
886 * @param {function} fn Async function that resolves an promise when ready
887 * @param {array} args Additional arguments
888 * @param {number} timeout Timeout in miliseconds
889 * @chainable
890 * @api
891 */
892
8931Actions.prototype.waitFor = function (script, args, timeout) {
8940 var hash = uuid();
8950 timeout = timeout || 5000;
8960 args = [this.contextVars].concat(Array.prototype.slice.call(arguments, 1) || []);
8970 var cb = this._generateCallbackAssertion('waitFor', 'waitFor', script, args, timeout, hash);
8980 this._addToActionQueue([script, args, timeout, hash], 'waitFor', cb);
8990 return this;
900};
901
902/**
903 * Accepts an alert/prompt/confirm dialog. This is basically the same actions as when
904 * you are clicking okay or hitting return in one of that dialogs.
905 *
906 * ```html
907 * <div>
908 * <a id="attentione" onclick="window.alert('Alonsy!')">ALERT!ALERT!</a>
909 * </div>
910 * ```
911 *
912 * ```javascript
913 * test.open('http://adomain.com')
914 * // alert appears
915 * .click('#attentione')
916 * // alert is gone
917 * .accept()
918 * .done();
919 * ```
920 *
921 * > Note: Does not work in Firefox & PhantomJS
922 *
923 * @api
924 * @method accept
925 * @return chainable
926 */
927
9281Actions.prototype.accept = function () {
9290 var hash = uuid();
9300 var cb = this._generateCallbackAssertion('acceptAlert', 'acceptAlert', hash);
9310 this._addToActionQueue([hash], 'acceptAlert', cb);
9320 return this;
933};
934
935/**
936 * Dismisses an prompt/confirm dialog. This is basically the same actions as when
937 * you are clicking cancel in one of that dialogs.
938 *
939 * ```html
940 * <div>
941 * <a id="nonono" onclick="(this.innerText = window.confirm('No classic doctors in the 50th?') ? 'Buh!' : ':(') ">What!</a>
942 * </div>
943 * ```
944 *
945 * ```javascript
946 * test.open('http://adomain.com')
947 * // prompt appears
948 * .click('#nonono')
949 * // prompt is gone
950 * .dismiss()
951 * .assert.text('#nonono').is(':(', 'So sad')
952 * .done();
953 * ```
954 *
955 * > Note: Does not work in Firefox & PhantomJS
956 *
957 * @api
958 * @method dismiss
959 * @return chainable
960 */
961
9621Actions.prototype.dismiss = function () {
9630 var hash = uuid();
9640 var cb = this._generateCallbackAssertion('dismissAlert', 'dismissAlert', hash);
9650 this._addToActionQueue([hash], 'dismissAlert', cb);
9660 return this;
967};
968
969/**
970 * Resizes the browser window to a set of given dimensions (in px).
971 * The default configuration of dalek opening pages is a width of 1280px
972 * and a height of 1024px. You can specify your own default in the configuration.
973 *
974 * ```html
975 * <div>
976 * <span id="magicspan">The span in the fireplace</span>
977 * </div>
978 * ```
979 *
980 * ```css
981 * #magicspan {
982 * display: inline;
983 * }
984 *
985 * // @media all and (max-width: 500px) and (min-width: 300px)
986 * #magicspan {
987 * display: none;
988 * }
989 * ```
990 *
991 * ```javascript
992 * test.open('http://adomain.com')
993 * .assert.visible('#magicspan', 'Big screen, visible span')
994 * .resize({width: 400, height: 500})
995 * .assert.notVisible('#magicspan', 'Small screen, no visible span magic!')
996 * .done();
997 * ```
998 *
999 *
1000 * > Note: Does not work in Firefox
1001 *
1002 * @api
1003 * @method resize
1004 * @param {object} dimensions Width and height as properties to apply
1005 * @chainable
1006 */
1007
10081Actions.prototype.resize = function (dimensions) {
10090 var hash = uuid();
10100 var cb = this._generateCallbackAssertion('resize', 'resize', dimensions, hash);
10110 this._addToActionQueue([dimensions, hash], 'resize', cb);
10120 return this;
1013};
1014
1015/**
1016 * Maximizes the browser window.
1017 *
1018 * ```html
1019 * <div>
1020 * <span id="magicspan">The span in the fireplace</span>
1021 * </div>
1022 * ```
1023 *
1024 * ```css
1025 * #magicspan {
1026 * display: inline;
1027 * }
1028 *
1029 * @media all and (max-width: 500px) and (min-width: 300px) {
1030 * #magicspan {
1031 * display: none;
1032 * }
1033 * }
1034 * ```
1035 *
1036 * ```javascript
1037 * test.open('http://adomain.com')
1038 * .resize({width: 400, height: 500})
1039 * .assert.notVisible('#magicspan', 'Small screen, no visible span magic!')
1040 * .maximize()
1041 * .assert.visible('#magicspan', 'Big screen, visible span')
1042 * .done();
1043 * ```
1044 *
1045 * > Note: Does not work in Firefox and PhantomJS
1046 *
1047 * @api
1048 * @method maximize
1049 * @chainable
1050 */
1051
10521Actions.prototype.maximize = function () {
10530 var hash = uuid();
10540 var cb = this._generateCallbackAssertion('maximize', 'maximize', hash);
10550 this._addToActionQueue([hash], 'maximize', cb);
10560 return this;
1057};
1058
1059/**
1060 * Sets a cookie.
1061 * More configuration options will be implemented in the future,
1062 * by now, you can only set a cookie with a specific name and contents.
1063 * This will be a domain wide set cookie.
1064 *
1065 * ```javascript
1066 * test.open('http://adomain.com')
1067 * .setCookie('my_cookie_name', 'my=content')
1068 * .done();
1069 * ```
1070 *
1071 * @api
1072 * @method setCookie
1073 * @chainable
1074 */
1075
10761Actions.prototype.setCookie = function (name, contents) {
10770 var hash = uuid();
10780 var cb = this._generateCallbackAssertion('setCookie', 'setCookie', name, contents, hash);
10790 this._addToActionQueue([name, contents, hash], 'setCookie', cb);
10800 return this;
1081};
1082
1083/**
1084 * Waits until an element matching the provided
1085 * selector expression exists in remote DOM to process any next step.
1086 *
1087 * Lets assume we have a ticker that loads its contents via AJAX,
1088 * and appends new elements, when the call has been successfully answered:
1089 *
1090 * ```javascript
1091 * test.open('http://myticker.org')
1092 * .assert.text('.ticker-element:first-child', 'First!', 'First ticker element is visible')
1093 * // now we load the next ticker element, defsult timeout is 5 seconds
1094 * .waitForElement('.ticker-element:nth-child(2)')
1095 * .assert.text('.ticker-element:nth-child(2)', 'Me snd. one', 'Snd. ticker element is visible')
1096 * // Lets assume that this AJAX call can take longer, so we raise the default timeout to 10 seconds
1097 * .waitForElement('.ticker-element:last-child', 10000)
1098 * .assert.text('.ticker-element:last-child', 'Me, third one!', 'Third ticker element is visible')
1099 * .done();
1100 * ```
1101 *
1102 * @api
1103 * @method waitForElement
1104 * @param {string} selector Selector that matches the element to wait for
1105 * @param {number} timeout Timeout in milliseconds
1106 * @chainable
1107 */
1108
11091Actions.prototype.waitForElement = function (selector, timeout) {
11100 var hash = uuid();
1111
11120 if (this.querying === true) {
11130 timeout = selector;
11140 selector = this.selector;
1115 }
1116
11170 var cb = this._generateCallbackAssertion('waitForElement', 'waitForElement', selector + ' : ' + timeout, hash);
11180 this._addToActionQueue([selector, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitForElement', cb);
11190 return this;
1120};
1121
1122/**
1123 * Fills the fields of a form with given values.
1124 *
1125 * ```html
1126 * <input type="text" value="not really a value" id="ijustwannahaveavalue"/>
1127 * ```
1128 *
1129 * ```javascript
1130 * test.open('http://dalekjs.com')
1131 * .setValue('#ijustwannahaveavalue', 'a value')
1132 * .assert.val('#ijustwannahaveavalue', 'a value', 'Value is changed');
1133 * ```
1134 *
1135 * @api
1136 * @method setValue
1137 * @param {string} selector
1138 * @param {string} value
1139 * @return {Actions}
1140 */
1141
11421Actions.prototype.setValue = function (selector, value) {
11430 var hash = uuid();
1144
11450 if (this.querying === true) {
11460 value = selector;
11470 selector = this.selector;
1148 }
1149
11500 var cb = this._generateCallbackAssertion('setValue', 'setValue', selector + ' : ' + value, hash);
11510 this._addToActionQueue([selector, value, hash], 'setValue', cb);
11520 return this;
1153};
1154
1155// LOG (May should live in its own module)
1156// ---------------------------------------
1157
11581Actions.prototype.logger = {};
1159
1160/**
1161 * Logs a part of the remote dom
1162 *
1163 * ```html
1164 * <body>
1165 * <div id="smth">
1166 * <input type="hidden" value="not really a value" id="ijustwannahaveavalue"/>
1167 * </div>
1168 * </body>
1169 * ```
1170 *
1171 * ```javascript
1172 * test.open('http://dalekjs.com/guineapig')
1173 * .log.dom('#smth')
1174 * .done();
1175 * ```
1176 *
1177 * Will output this:
1178 *
1179 * ```html
1180 * DOM: #smth <input type="hidden" value="not really a value" id="ijustwannahaveavalue"/>
1181 * ```
1182
1183 *
1184 * @api
1185 * @method log.dom
1186 * @param {string} selector CSS selector
1187 * @chainable
1188 */
1189
11901Actions.prototype.logger.dom = function (selector) {
11910 var hash = uuid();
1192
11930 var cb = function logDomCb (data) {
11940 if (data && data.key === 'source' && !this.uuids[data.uuid]) {
11950 this.uuids[data.uuid] = true;
11960 var $ = cheerio.load(data.value);
11970 var result = selector ? $(selector).html() : $.html();
11980 selector = selector ? selector : ' ';
11990 result = !result ? ' Not found' : result;
12000 this.reporter.emit('report:log:user', 'DOM: ' + selector + ' ' + result);
1201 }
1202 }.bind(this);
1203
12040 this._addToActionQueue([hash], 'source', cb);
12050 return this;
1206};
1207
1208/**
1209 * Logs a user defined message
1210 *
1211 * ```javascript
1212 * test.open('http://dalekjs.com/guineapig')
1213 * .execute(function () {
1214 * this.data('aKey', 'aValue');
1215 * })
1216 * .log.message(function () {
1217 * return test.data('aKey'); // outputs MESSAGE: 'aValue'
1218 * })
1219 * .done();
1220 * ```
1221 *
1222 * 'Normal' messages can be logged too:
1223 *
1224 * ```javascript
1225 * test.open('http://dalekjs.com/guineapig')
1226 * .log.message('FooBar') // outputs MESSAGE: FooBar
1227 * .done();
1228 * ```
1229 *
1230 * @api
1231 * @method log.message
1232 * @param {function|string} message
1233 * @chainable
1234 */
1235
12361Actions.prototype.logger.message = function (message) {
12370 var hash = uuid();
1238
12390 var cb = function logMessageCb (data) {
12400 if (data && data.key === 'noop' && !this.uuids[data.hash]) {
12410 this.uuids[data.hash] = true;
12420 var result = (typeof(data.value) === 'function') ? data.value.bind(this)() : data.value;
12430 this.reporter.emit('report:log:user', 'MESSAGE: ' + result);
1244 }
1245 }.bind(this);
1246
12470 this._addToActionQueue([message, hash], 'noop', cb);
12480 return this;
1249};
1250
1251/**
1252 * Generates a callback that will be fired when the action has been completed.
1253 * The callback itself will then validate the answer and will also emit an event
1254 * that the action has been successfully executed.
1255 *
1256 * @method _generateCallbackAssertion
1257 * @param {string} key Unique key of the action
1258 * @param {string} type Type of the action (normalle the actions name)
1259 * @return {function} The generated callback function
1260 * @private
1261 */
1262
12631Actions.prototype._generateCallbackAssertion = function (key, type) {
12640 var cb = function (data) {
12650 if (data && data.key === key && !this.uuids[data.uuid]) {
12660 if (!data || (data.value && data.value === null)) {
12670 data.value = '';
1268 }
1269
12700 if (key === 'execute') {
12710 Object.keys(data.value.dalek).forEach(function (key) {
12720 this.contextVars[key] = data.value.dalek[key];
1273 }.bind(this));
1274
12750 data.value.test.forEach(function (test) {
12760 this.reporter.emit('report:assertion', {
1277 success: test.ok,
1278 expected: true,
1279 value: test.ok,
1280 message: test.message,
1281 type: 'OK'
1282 });
1283
12840 this.incrementExpectations();
1285
12860 if (!test.ok) {
12870 this.incrementFailedAssertions();
1288 }
1289 }.bind(this));
1290
12910 data.value = '';
1292 }
1293
12940 this.uuids[data.uuid] = true;
12950 reporter.emit('report:action', {
1296 value: data.value,
1297 type: type,
1298 uuid: data.uuid
1299 });
1300 }
1301 }.bind(this);
13020 return cb;
1303};
1304
1305/**
1306 * Adds a method to the queue of actions/assertions to execute
1307 *
1308 * @method _addToActionQueue
1309 * @param {object} opts Options of the action to invoke
1310 * @param {string} driverMethod Name of the method to call on the driver
1311 * @param {function} A callback function that will be executed when the action has been executed
1312 * @private
1313 * @chainable
1314 */
1315
13161Actions.prototype._addToActionQueue = function (opts, driverMethod, cb) {
13170 this.actionPromiseQueue.push(function () {
13180 var deferred = Q.defer();
1319 // add a generic identifier as the last argument to any action method call
13200 opts.push(uuid());
1321 // check the method on the driver object && the callback function
13220 if (typeof(this.driver[driverMethod]) === 'function' && typeof(cb) === 'function') {
1323 // call the method on the driver object
13240 this.driver[driverMethod].apply(this.driver, opts);
13250 deferred.resolve();
1326 } else {
13270 deferred.reject();
1328 }
1329
1330 // listen to driver message events & apply the callback argument
13310 this.driver.events.on('driver:message', cb);
13320 return deferred.promise;
1333 }.bind(this));
13340 return this;
1335};
1336
13371Actions.prototype._button = function(button) {
13380 var buttons = {LEFT: 0, MIDDLE: 1, RIGHT: 2};
1339
13400 if (button === undefined) {
13410 button = 0;
13420 } else if (typeof button !== 'number') {
13430 button = buttons[button.toUpperCase()] || 0;
1344 }
1345
13460 return button;
1347};
1348
1349// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/click
13501Actions.prototype.buttonClick = function (button) {
13510 var hash = uuid();
13520 button = this._button(button);
1353
13540 var cb = this._generateCallbackAssertion('buttonClick', 'buttonClick');
13550 this._addToActionQueue([button, hash], 'buttonClick', cb);
1356
13570 return this;
1358};
1359
1360// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/moveto
13611Actions.prototype.moveTo = function (selector, x, y) {
13620 var hash = uuid();
1363
13640 if (this.querying === true) {
13650 selector = this.selector;
1366 }
1367
13680 if (x === undefined) {
13690 x = null;
1370 }
1371
13720 if (y === undefined) {
13730 y = null;
1374 }
1375
1376 // move to coordinate
13770 var cb = this._generateCallbackAssertion('moveto', 'moveto');
13780 this._addToActionQueue([selector, x, y, hash], 'moveto', cb);
1379
13800 return this;
1381};
1382
1383/**
1384 * Close the active window and automatically selects the parent window.
1385 *
1386 * ```javascript
1387 * this.test.toWindow('test');
1388 * this.test.close();
1389 *
1390 * //you can now write your code as if the original parent window was selected because .close()
1391 * //selects that automatically for you so you don't have to call .toParentWindow() everytime
1392 * ```
1393 *
1394 * @api
1395 * @method close
1396 * @chainable
1397 */
13981Actions.prototype.close = function () {
13990 var hash = uuid();
14000 var cb = this._generateCallbackAssertion('close', 'close', hash);
14010 this._addToActionQueue([hash], 'close', cb);
1402
1403 //since the current window is now closed, make sense to automatically select the parent window since you would have to do this anyway
14040 this.toParentWindow();
1405
14060 return this;
1407};
1408
1409/**
1410 * @module DalekJS
1411 */
1412
14131module.exports = function (opts) {
14140 reporter = opts.reporter;
14150 return Actions;
1416};
1417

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/assertions.js

17%
346
61
285
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
241'use strict';
25
26// ext. libs
271var Q = require('q');
281var uuid = require('./uuid');
291var chai = require('chai');
30
31// Module variable
321var Assertions;
33
34/**
35 * @module Assertions
36 * @namespace Dalek.Internal
37 */
38
391module.exports = function () {
401 return Assertions;
41};
42
43/**
44 * Assertions check if the assumptions you made about a website are correct.
45 * For example they might check if the title of a page or the content text of
46 * an element is as expected, or if your mobile website version only displays
47 * a certain amount of elements.
48 *
49 * @class Assertions
50 * @constructor
51 * @part Assertions
52 * @api
53 */
54
551Assertions = function (opts) {
560 this.test = opts.test;
570 this.proceeded = [];
580 this.chaining = false;
59};
60
61/**
62 * It can be really cumbersome to always write assert, assert & assert
63 * all over the place when your doing multiple assertions.
64 * To avoid this, open an assertion context in your test which allows
65 * you to write (n) assertions without having to write 'assert' before each.
66 *
67 * So, instead of writing this:
68 *
69 * ```javascript
70 * test.open('http://doctorwhotv.co.uk/')
71 * .assert.text('#nav').is('Navigation')
72 * .assert.visible('#nav')
73 * .assert.attr('#nav', 'data-nav', 'true')
74 * .done();
75 * ```
76 *
77 * you can write this:
78 *
79 * ```javascript
80 * test.open('http://doctorwhotv.co.uk/')
81 * .assert.chain()
82 * .text('#nav').is('Navigation')
83 * .visible('#nav')
84 * .attr('#nav', 'data-nav', 'true')
85 * .end()
86 * .done();
87 * ```
88 *
89 * to make it even more concise, you can combine this with the [query](actions.html#meth-query) method:
90 *
91 * ```javascript
92 * test.open('http://doctorwhotv.co.uk/')
93 * .assert.chain()
94 * .query('#nav')
95 * .text().is('Navigation')
96 * .visible()
97 * .attr('data-nav', 'true')
98 * .end()
99 * .end()
100 * .done();
101 * ```
102 *
103 * Always make sure to terminate it with the [end](#meth-end) method!
104 *
105 * @api
106 * @method chain
107 * @chainable
108 */
109
1101Assertions.prototype.chain = function () {
1110 this.test.lastChain.push('chaining');
1120 this.chaining = true;
1130 return this;
114};
115
116/**
117 * Terminates an assertion chain or a query
118 *
119 * ```javascript
120 * test.open('http://doctorwhotv.co.uk/')
121 * .assert.chain()
122 * .query('#nav')
123 * .text().is('Navigation')
124 * .visible()
125 * .attr('data-nav', 'true')
126 * .end()
127 * .end()
128 * .done();
129 * ```
130 *
131 * @api
132 * @method end
133 * @chainable
134 */
135
1361Assertions.prototype.end = function () {
1370 var lastAction = this.test.lastChain.pop();
1380 if (lastAction === 'chaining') {
1390 this.chaining = false;
140 }
141
1420 if (lastAction === 'querying') {
1430 this.test.querying = false;
144 }
1450 return this.test;
146};
147
148/**
149 * Asserts that a given resource does exist in the environment.
150 *
151 * @method resourceExists
152 * @param {string} url URL of the resource to check
153 * @param {string} message Message for the test reporter
154 * @chainable
155 */
156
1571Assertions.prototype.resourceExists = function (url, message) {
1580 var hash = uuid();
1590 var cb = this._generateCallbackAssertion('resourceExists', 'resourceExists', this._testTruthy, hash, {url: url, message: message}).bind(this.test);
1600 this._addToActionQueue([url, hash], 'resourceExists', cb);
1610 return this.chaining ? this : this.test;
162};
163
164/**
165 * Asserts that a given element appears n times on the page.
166 *
167 * Given this portion of HTML, you would like to assure that all of these elements
168 * are ending up in your rendered markup on your page.
169 *
170 * ```html
171 * <section id="blog-overview">
172 * <article class="teaser"></article>
173 * <article class="teaser"></article>
174 * <article class="teaser"></article>
175 * <article class="teaser"></article>
176 * </section>
177 * ```
178 *
179 * The simple solution is to check if all these elements are present
180 *
181 * ```javascript
182 * test.assert.numberOfElements('#blog-overview .teaser', 4, '4 blog teasers are present')
183 * ```
184 * The alternate syntax for this is:
185 *
186 * ```javascript
187 * test.assert.numberOfElements('#blog-overview .teaser')
188 * .is(4, '4 blog teasers are present')
189 * ```
190 *
191 * If you are not sure how many elements will exactly end up in your markup,
192 * you could use the between assertion handler
193 *
194 * ```javascript
195 * test.assert.numberOfElements('#blog-overview .teaser')
196 * .is.between([2, 6], 'Between 2 and 6 blog teasers are present')
197 * ```
198 *
199 * If you dealing with the situation that you have a minimum of elements,
200 * you expect, you can use this helper:
201 *
202 * ```javascript
203 * test.assert.numberOfElements('#blog-overview .teaser')
204 * .is.gt(2, 'At least 3 blog teasers are present')
205 * ```
206 * If you want to know if its 'greater than equal', use this one
207 *
208 * ```javascript
209 * test.assert.numberOfElements('#blog-overview .teaser')
210 * .is.gte(2, 'At least 2 blog teasers are present')
211 * ```
212 * as well as their 'lower than' and 'lower than equal' equivalents.
213 *
214 * ```javascript
215 * test.assert.numberOfElements('#blog-overview .teaser')
216 * .is.lt(5, 'Less than 5 blog teasers are present')
217 * ```
218 *
219 * ```javascript
220 * test.assert.numberOfElements('#blog-overview .teaser')
221 * .is.lte(5, 'Less than, or 5 blog teasers are present')
222 * ```
223 * And if you just want to know, if a certain amount of teasers isnʼt present,
224 * you can still use the not() assertion helper
225 *
226 * ```javascript
227 * test.assert.numberOfElements('#blog-overview .teaser')
228 * .is.not(5, 'There are more or less than 5 teasers present')
229 * ```
230 *
231 * @api
232 * @method numberOfElements
233 * @param {string} selector Selector that matches the elements to test
234 * @param {string} expected Expected test result
235 * @param {string} message Message for the test reporter
236 * @chainable
237 */
238
2391Assertions.prototype.numberOfElements = function (selector, expected, message) {
2400 var hash = uuid();
241
2420 if (this.test.querying === true) {
2430 message = expected;
2440 expected = selector;
2450 selector = this.test.selector;
246 }
247
2480 var cb = this._generateCallbackAssertion('numberOfElements', 'numberOfElements', this._testShallowEquals, hash, {expected: expected, selector: selector, message: message}).bind(this.test);
2490 this._addToActionQueue([selector, expected, hash], 'getNumberOfElements', cb);
2500 return this.chaining ? this : this.test;
251};
252
253/**
254 *
255 * Asserts that a given element is visible n times in the current viewport.
256 *
257 *
258 * Given this portion of HTML, you would like to assure that all of these elements
259 * are ending up in your rendered markup on your page.
260 *
261 * ```html
262 * <section id="blog-overview">
263 * <article class="teaser"></article>
264 * <article class="teaser"></article>
265 * <article class="teaser"></article>
266 * <article class="teaser"></article>
267 * </section>
268 * ```
269 *
270 * The simple solution is to check if all these elements are visible
271 *
272 * ```javascript
273 * test.assert.numberOfVisibleElements('#blog-overview .teaser', 4, '4 blog teasers are visible')
274 * ```
275 * The alternate syntax for this is:
276 *
277 * ```javascript
278 * test.assert.numberOfVisibleElements('#blog-overview .teaser')
279 * .is(4, '4 blog teasers are visible')
280 * ```
281 *
282 * If you are not sure how many elements will exactly be shown in the current viewport,
283 * you could use the between assertion handler
284 *
285 * ```javascript
286 * test.assert.numberOfVisibleElements('#blog-overview .teaser')
287 * .is.between(2, 6, 'Between 2 and 6 blog teasers are visible')
288 * ```
289 *
290 * If you dealing with the situation that you have a minimum of elements,
291 * you expect, use this helper:
292 *
293 * ```javascript
294 * test.assert.numberOfVisibleElements('#blog-overview .teaser')
295 * .is.gt(2, 'At least 3 blog teasers are visible')
296 * ```
297 * If you want to know if its 'greater than equal', you can use this one
298 *
299 * ```javascript
300 * test.assert.numberOfVisibleElements('#blog-overview .teaser')
301 * .is.gte(2, 'At least 2 blog teasers are visible')
302 * ```
303 * as well as their 'lower than' and 'lower than equal' equivalents.
304 *
305 * ```javascript
306 * test.assert.numberOfVisibleElements('#blog-overview .teaser')
307 * .is.lt(5, 'Less than 5 blog teasers are visible')
308 * ```
309 *
310 * ```javascript
311 * test.assert.numberOfVisibleElements('#blog-overview .teaser')
312 * .is.lte(5, 'Less than, or 5 blog teasers are visible')
313 * ```
314 * And if you just want to know, if a certain amount of teasers isnʼt visible,
315 * you can still use the ':not(): assertion helper
316 *
317 * ```javascript
318 * test.assert.numberOfVisibleElements('#blog-overview .teaser')
319 * .is.not(5, 'There are more or less than 5 teasers visible')
320 * ```
321 *
322 * > NOTE: Buggy on all browsers
323 *
324 * @api
325 * @method numberOfVisibleElements
326 * @param {string} selector Selector that matches the elements to test
327 * @param {string} expected Expected test result
328 * @param {string} message Message for the test reporter
329 * @chainable
330 */
331
3321Assertions.prototype.numberOfVisibleElements = function (selector, expected, message) {
3330 var hash = uuid();
334
3350 if (this.test.querying === true) {
3360 message = expected;
3370 expected = selector;
3380 selector = this.test.selector;
339 }
340
3410 var cb = this._generateCallbackAssertion('numberOfVisibleElements', 'numberOfVisibleElements', this._testShallowEquals, hash, {expected: expected, selector: selector, message: message}).bind(this.test);
3420 this._addToActionQueue([selector, expected, hash], 'getNumberOfVisibleElements', cb);
3430 return this.chaining ? this : this.test;
344};
345
346/**
347 * Asserts that a given form field has the provided value.
348 *
349 * Given this portion of HTML, we would like to get the information which option element
350 * is currently selected.
351 *
352 * ```html
353 * <form name="fav-doctor" id="fav-doctor">
354 * <select id="the-doctors">
355 * <option value="9">Eccleston</option>
356 * <option selected value="10">Tennant</option>
357 * <option value="11">Smith</option>
358 * </select>
359 * </form>
360 * ```
361 *
362 * ```javascript
363 * test
364 * .assert.val('#the-doctors', 10, 'David is the favourite')
365 * // lets change the favourite by selection the last option
366 * .click('#the-doctors option:last')
367 * .assert.val('#the-doctors', 11, 'Matt is now my favourite, bow ties are cool')
368 * ```
369 *
370 * This assertion is capable of getting the values from every form element
371 * that holds a value attribute
372 *
373 * Getting texts out of normal input fields is pretty straight forward
374 *
375 * ```html
376 * <label for="fav-enemy">Tell my your favourity Who enemy:</label>
377 * <input id="fav-enemy" name="fav-enemy" type="text" value="Daleks" />
378 * ```
379 *
380 * ```javascript
381 * test
382 * .assert.val('#fav-enemy', 'Daleks', 'Daleks are so cute')
383 * // lets change the favourite by typing smth. new
384 * .type('#fav-enemy', 'Cyberman')
385 * .assert.val('#fav-enemy', 'Cyberman', 'Cyberman are so cyber')
386 * ```
387 *
388 * @api
389 * @method val
390 * @param {string} selector Selector that matches the elements to test
391 * @param {string} expected Expected test result
392 * @param {string} message Message for the test reporter
393 * @chainable
394 */
395
3961Assertions.prototype.val = function (selector, expected, message) {
3970 var hash = uuid();
398
3990 if (this.test.querying === true) {
4000 message = expected;
4010 expected = selector;
4020 selector = this.test.selector;
403 }
404
4050 var cb = this._generateCallbackAssertion('val', 'val', this._testShallowEquals, hash, {expected: expected, selector: selector, message: message}).bind(this.test);
4060 this._addToActionQueue([selector, expected, hash], 'val', cb);
4070 return this.chaining ? this : this.test;
408};
409
410/**
411 * Checks the computed style.
412 * Note: When selecting values like em or percent, they will be converted to px. So it's currently not possible
413 * to check em, %, ... values, only px.
414 *
415 * ```html
416 * <div id="superColoredElement">Rose</div>
417 * ```
418 *
419 * ```css
420 * #superColoredElement {
421 * background-color: rgba(255, 0, 0, 1);
422 * color: rgba(0, 128, 0, 1);
423 * }
424 * ```
425 *
426 * ```javascript
427 * test
428 * .open('http://unicorns.rainbows.io')
429 * .assert.css('#superColoredElement', 'background-color', 'rgba(255, 0, 0, 1)')
430 * .assert.css('#superColoredElement', 'color', 'rgba(0, 128, 0, 1)')
431 * .done();
432 * ```
433 *
434 * Can also check if a computed style is greater or lower than the expected value.
435 * TODO: We might extract the part that determines the comparison operator to reuse it in other test. We might also
436 * add >= and <=.
437 *
438 * ```html
439 * <div id="fancyPlacedElement">Tulip</div>
440 * ```
441 *
442 * ```css
443 * #fancyPlacedElement {
444 * top: 100px;
445 * }
446 * ```
447 *
448 * ```javascript
449 * test
450 * .open('http://unicorns.rainbows.io')
451 * .assert.css('#fancyPlacedElement', 'top', '>50px') // 100 is greater than 50, success
452 * .assert.css('#fancyPlacedElement', 'top', '<150px') // 100 is lower than 150, success
453 * .assert.css('#fancyPlacedElement', 'top', '>150px') // 100 is lower than 150, fail
454 * .assert.css('#fancyPlacedElement', 'top', '<50px') // 100 is greater than 50, fail
455 * .done();
456 * ```
457 *
458 * @api
459 * @method css
460 * @param {string} selector Selector that matches the elements to test
461 * @param {string} property CSS property to check
462 * @param {string} expected Expected test result
463 * @param {string} message Message for the test reporter
464 * @chainable
465 */
466
4671Assertions.prototype.css = function (selector, property, expected, message) {
4680 var hash = uuid();
4690 var assertionMethod;
4700 var comparisonOperator;
471
4720 if (this.test.querying === true) {
4730 message = expected;
4740 expected = property;
4750 property = selector;
4760 selector = this.test.selector;
477 }
478
4790 switch (expected.substr(0, 1)) {
480 case '>':
4810 assertionMethod = this._testLowerThan;
4820 expected = expected.substr(1);
4830 comparisonOperator = '>';
4840 break;
485 case '<':
4860 assertionMethod = this._testGreaterThan;
4870 expected = expected.substr(1);
4880 comparisonOperator = '<';
4890 break;
490 default:
4910 assertionMethod = this._testShallowEquals;
4920 comparisonOperator = '';
4930 break;
494 }
495
4960 var cb = this._generateCallbackAssertion('css', 'css', assertionMethod, hash, {comparisonOperator: comparisonOperator, expected: expected, selector: selector, porperty: property, message: message, parseFloatOnValues: true}).bind(this.test);
4970 this._addToActionQueue([selector, property, expected, hash], 'css', cb);
4980 return this.chaining ? this : this.test;
499};
500
501/**
502 * Checks the actual width of an element.
503 *
504 * ```html
505 * <div id="fixed-dimensions" style="width: 100px"></div>
506 * ```
507 *
508 * ```javascript
509 * test
510 * .open('http://localhost:5000/index.html')
511 * // all true, all pixel
512 * .assert.width('#fixed-dimensions', 100)
513 * .assert.width('#fixed-dimensions').is(100)
514 * .assert.width('#fixed-dimensions').is.not(100)
515 * .assert.width('#fixed-dimensions').is.gt(90)
516 * .assert.width('#fixed-dimensions').is.gte(97)
517 * .assert.width('#fixed-dimensions').is.lt(120)
518 * .assert.width('#fixed-dimensions').is.lte(110)
519 * .assert.width('#fixed-dimensions').is.between([90, 110])
520 * .done();
521 * ```
522 *
523 * @api
524 * @method width
525 * @param {string} selector Selector that matches the elements to test
526 * @param {string} expected Expected test result
527 * @param {string} message Message for the test reporter
528 * @chainable
529 */
530
5311Assertions.prototype.width = function (selector, expected, message) {
5320 var hash = uuid();
533
5340 if (this.test.querying === true) {
5350 message = expected;
5360 expected = selector;
5370 selector = this.test.selector;
538 }
539
5400 var cb = this._generateCallbackAssertion('width', 'width', this._testShallowEquals, hash, {expected: expected, selector: selector, message: message}).bind(this.test);
5410 this._addToActionQueue([selector, expected, hash], 'width', cb);
5420 return this.chaining ? this : this.test;
543};
544
545/**
546 * Checks the actual height of an element.
547 *
548 * ```html
549 * <div id="fixed-dimensions" style="height: 100px"></div>
550 * ```
551 *
552 * ```javascript
553 * test
554 * .open('http://localhost:5000/index.html')
555 * // all true, all pixel
556 * .assert.height('#fixed-dimensions', 100)
557 * .assert.height('#fixed-dimensions').is(100)
558 * .assert.height('#fixed-dimensions').is.not(100)
559 * .assert.height('#fixed-dimensions').is.gt(90)
560 * .assert.height('#fixed-dimensions').is.gte(97)
561 * .assert.height('#fixed-dimensions').is.lt(120)
562 * .assert.height('#fixed-dimensions').is.lte(110)
563 * .assert.height('#fixed-dimensions').is.between([90, 110])
564 * .done();
565 * ```
566 *
567 * @api
568 * @method height
569 * @param {string} selector Selector that matches the elements to test
570 * @param {string} expected Expected test result
571 * @param {string} message Message for the test reporter
572 * @chainable
573 */
574
5751Assertions.prototype.height = function (selector, expected, message) {
5760 var hash = uuid();
577
5780 if (this.test.querying === true) {
5790 message = expected;
5800 expected = selector;
5810 selector = this.test.selector;
582 }
583
5840 var cb = this._generateCallbackAssertion('height', 'height', this._testShallowEquals, hash, {expected: expected, selector: selector, message: message}).bind(this.test);
5850 this._addToActionQueue([selector, expected, hash], 'height', cb);
5860 return this.chaining ? this : this.test;
587};
588
589/**
590 * Determine if an <option> element, or an <input> element of type checkbox or radio is currently selected.
591 *
592 * ```html
593 * <input type="checkbox" id="unchecked_checkbox" name="unchecked_checkbox"/>
594 * <input type="checkbox" id="checked_checkbox" name="checked_checkbox" checked="checked"/>
595 * <select id="select_elm" name="select_elm">
596 * <option value="9">Eccleston</option>
597 * <option selected value="10">Tennant</option>
598 * <option value="11">Smith</option>
599 * </select>
600 * ```
601 *
602 * Checking radio and checkboxes:
603 *
604 * ```javascript
605 * test
606 * .open('http://selectables.org')
607 * .assert.selected('#checked_checkbox')
608 * .done();
609 * ```
610 *
611 * Checking option elements:
612 *
613 * ```javascript
614 * test
615 * .open('http://selectables.org')
616 * .assert.selected('#select_elm option:nth-child(2)')
617 * .done();
618 * ```
619 *
620 * @api
621 * @method selected
622 * @param {string} selector Selector that matches the elements to test
623 * @param {string} message Message for the test reporter
624 * @chainable
625 */
626
6271Assertions.prototype.selected = function (selector, message) {
6280 var hash = uuid();
629
6300 if (this.test.querying === true) {
6310 message = selector;
6320 selector = this.test.selector;
633 }
634
6350 var cb = this._generateCallbackAssertion('selected', 'selected', this._testShallowEquals, hash, {expected: true, selector: selector, message: message}).bind(this.test);
6360 this._addToActionQueue([selector, true, hash], 'selected', cb);
6370 return this.chaining ? this : this.test;
638};
639
640/**
641 * Determine if an <option> element, or an <input> element of type
642 * checkbox or radio is currently not selected.
643 *
644 * ```html
645 * <input type="checkbox" id="unchecked_checkbox" name="unchecked_checkbox"/>
646 * <input type="checkbox" id="checked_checkbox" name="checked_checkbox" checked="checked"/>
647 * <select id="select_elm" name="select_elm">
648 * <option value="9">Eccleston</option>
649 * <option selected value="10">Tennant</option>
650 * <option value="11">Smith</option>
651 * </select>
652 * ```
653 *
654 * Checking radio and checkboxes:
655 *
656 * ```javascript
657 * test
658 * .open('http://selectables.org')
659 * .assert.notSelected('#unchecked_checkbox')
660 * .done();
661 * ```
662 *
663 * Checking option elements:
664 *
665 * ```javascript
666 * test
667 * .open('http://selectables.org')
668 * .assert.notSelected('#select_elm option:last-child')
669 * .done();
670 * ```
671 *
672 * @api
673 * @method notSelected
674 * @param {string} selector Selector that matches the elements to test
675 * @param {string} message Message for the test reporter
676 * @chainable
677 */
678
6791Assertions.prototype.notSelected = function (selector, message) {
6800 var hash = uuid();
681
6820 if (this.test.querying === true) {
6830 message = selector;
6840 selector = this.test.selector;
685 }
686
6870 var cb = this._generateCallbackAssertion('selected', 'selected', this._testShallowEquals, hash, {expected: false, selector: selector, message: message}).bind(this.test);
6880 this._addToActionQueue([selector, false, hash], 'selected', cb);
6890 return this.chaining ? this : this.test;
690};
691
692/**
693 * Determine if an element is currently enabled.
694 *
695 * ```html
696 * <input id="onmars" type="text" size="50" name="onmars" placeholder="State your name, rank and intention!"></input>
697 * ```
698 *
699 * ```javascript
700 * test
701 * .open('http://doctor.thedoctor.com/doctor')
702 * .assert.enabled('#onmars', 'Is enabled!')
703 * .done();
704 * ```
705 *
706 * @api
707 * @method enabled
708 * @param {string} selector Selector that matches the elements to test
709 * @param {string} message Message for the test reporter
710 * @chainable
711 */
712
7131Assertions.prototype.enabled = function (selector, message) {
7140 var hash = uuid();
715
7160 if (this.test.querying === true) {
7170 message = selector;
7180 selector = this.test.selector;
719 }
720
7210 var cb = this._generateCallbackAssertion('enabled', 'enabled', this._testShallowEquals, hash, {expected: true, selector: selector, message: message}).bind(this.test);
7220 this._addToActionQueue([selector, true, hash], 'enabled', cb);
7230 return this.chaining ? this : this.test;
724};
725
726/**
727 * Determine if an element is currently disabled.
728 *
729 * ```html
730 * <input disabled id="onearth" type="text" size="50" name="onearth" placeholder="State your name, rank and intention!"></input>
731 * ```
732 *
733 * ```javascript
734 * test
735 * .open('http://doctor.thedoctor.com/doctor')
736 * .assert.disabled('#onearth', 'Is disabled!')
737 * .done();
738 * ```
739 *
740 * @api
741 * @method disabled
742 * @param {string} selector Selector that matches the elements to test
743 * @param {string} message Message for the test reporter
744 * @chainable
745 */
746
7471Assertions.prototype.disabled = function (selector, message) {
7480 var hash = uuid();
749
7500 if (this.test.querying === true) {
7510 message = selector;
7520 selector = this.test.selector;
753 }
754
7550 var cb = this._generateCallbackAssertion('enabled', 'enabled', this._testShallowEquals, hash, {expected: false, selector: selector, message: message}).bind(this.test);
7560 this._addToActionQueue([selector, false, hash], 'enabled', cb);
7570 return this.chaining ? this : this.test;
758};
759
760/**
761 * Checks the contents of a cookie.
762 *
763 * ```javascript
764 * test
765 * .open('http://cookiejar.io/not_your_mothers_javascript.html')
766 * .setCookie('atestcookie', 'foobar=baz')
767 * .assert.cookie('atestcookie', 'foobar=baz')
768 * .done();
769 * ```
770 *
771 * @api
772 * @method cookie
773 * @param {string} name Name of the cookie
774 * @param {string} expect Expected testresult
775 * @param {string} message Message for the test reporter
776 * @chainable
777 */
778
7791Assertions.prototype.cookie = function (name, expected, message) {
7800 var hash = uuid();
7810 var cb = this._generateCallbackAssertion('cookie', 'cookie', this._testShallowEquals, hash, {expected: expected, name: name, message: message}).bind(this.test);
7820 this._addToActionQueue([name, expected, hash], 'cookie', cb);
7830 return this.chaining ? this : this.test;
784};
785
786/**
787 * Asserts that current HTTP status code is the same as the one passed as argument.
788 * TODO: Needs some work to be implement (maybe JavaScript, Webdriver ha no method for this)
789 *
790 * @method httpStatus
791 * @param {integer} status HTTP status code
792 * @param {string} message Message for the test reporter
793 * @chainable
794 */
795
7961Assertions.prototype.httpStatus = function (status, message) {
7970 var hash = uuid();
7980 var cb = this._generateCallbackAssertion('httpStatus', 'httpStatus', this._testShallowEquals, hash, {expected: status, message: message}).bind(this.test);
7990 this._addToActionQueue([status, hash], 'httpStatus', cb);
8000 return this.chaining ? this : this.test;
801};
802
803/**
804 * Asserts that an element matching the provided selector expression exists in remote DOM environment.
805 *
806 * ```html
807 * <body>
808 * <p id="so-lonely">Last of the timelords</p>
809 * </body>
810 * ```
811 *
812 * ```javascript
813 * test
814 * .open('http://doctor.thedoctor.com/doctor')
815 * .assert.exists('#so-lonely', 'The loneliest element in the universe exists')
816 * .done()
817 * ```
818 *
819 * @api
820 * @method exists
821 * @param {string} selector Selector that matches the elements to test
822 * @param {string} message Message for the test reporter
823 * @chainable
824 */
825
8261Assertions.prototype.exists = function (selector, message) {
8270 var hash = uuid();
828
8290 if (this.test.querying === true) {
8300 message = selector;
8310 selector = this.test.selector;
832 }
833
8340 var cb = this._generateCallbackAssertion('exists', 'exists', this._testTruthy, hash, {selector: selector, message: message}).bind(this.test);
8350 this._addToActionQueue([selector, hash], 'exists', cb);
8360 return this.chaining ? this : this.test;
837};
838
839/**
840 * Asserts that an element matching the provided selector expression doesnʼt
841 * exists within the remote DOM environment.
842 *
843 * ```html
844 * <body>
845 * <p id="so-lonely">Last of the time lords</p>
846 * </body>
847 * ```
848 *
849 * ```javascript
850 * test
851 * .open('http://doctor.thedoctor.com/doctor')
852 * .assert.doesntExist('#the-master', 'The master element has not been seen')
853 * .done();
854 * ```
855 *
856 * @api
857 * @method doesntExist
858 * @param {string} selector Selector that matches the elements to test
859 * @param {string} message Message for the test reporter
860 * @chainable
861 */
862
8631Assertions.prototype.doesntExist = function (selector, message) {
8640 var hash = uuid();
865
8660 if (this.test.querying === true) {
8670 message = selector;
8680 selector = this.test.selector;
869 }
870
8710 var cb = this._generateCallbackAssertion('exists', '!exists', this._testFalsy, hash, {selector: selector, message: message}).bind(this.test);
8720 this._addToActionQueue([selector, hash], 'exists', cb);
8730 return this.chaining ? this : this.test;
874};
875
876/**
877 * Asserts that the element matching the provided selector expression is not visible.
878 *
879 * ```html
880 * <body>
881 * <h1 style="display: none">Me? So hidden …</h1>
882 * <h2>Me? So in viewport...</h2>
883 * </body>
884 * ```
885 *
886 * ```javascript
887 * test
888 * .open('http://allyourviewportsbelongto.us')
889 * .assert.notVisible('h1', 'Element is not visible')
890 * .done();
891 * ```
892 *
893 *
894 * > NOTE: Buggy on all browsers
895 *
896 * @api
897 * @method notVisible
898 * @param {string} selector Selector that matches the elements to test
899 * @param {string} message Message for the test reporter
900 * @chainable
901 */
902
9031Assertions.prototype.notVisible = function (selector, message) {
9040 var hash = uuid();
905
9060 if (this.test.querying === true) {
9070 message = selector;
9080 selector = this.test.selector;
909 }
910
9110 var cb = this._generateCallbackAssertion('visible', '!visible', this._testFalsy, hash, {selector: selector, message: message}).bind(this.test);
9120 this._addToActionQueue([selector, hash], 'visible', cb);
9130 return this.chaining ? this : this.test;
914};
915
916/**
917 * Asserts that the element matching the provided selector expression is visible.
918 *
919 * ```html
920 * <body>
921 * <h1>Me? So in viewport …</h1>
922 * </body>
923 * ```
924 *
925 * ```javascript
926 * test
927 * .open('http://allyourviewportsbelongto.us')
928 * .assert.visible('h1', 'Element is visible')
929 * .done();
930 * ```
931 *
932 * > NOTE: Buggy on all browsers
933 *
934 * @api
935 * @method visible
936 * @param {string} selector Selector that matches the elements to test
937 * @param {string} message Message for the test reporter
938 * @chainable
939 */
940
9411Assertions.prototype.visible = function (selector, message) {
9420 var hash = uuid();
943
9440 if (this.test.querying === true) {
9450 message = selector;
9460 selector = this.test.selector;
947 }
948
9490 var cb = this._generateCallbackAssertion('visible', 'visible', this._testTruthy, hash, {selector: selector, message: message}).bind(this.test);
9500 this._addToActionQueue([selector, hash], 'visible', cb);
9510 return this.chaining ? this : this.test;
952};
953
954/**
955 * Asserts that given text does not exist in the provided selector.
956 *
957 * ```html
958 * <body>
959 * <h1>This is a CasperJS sandbox</h1>
960 * </body>
961 * ```
962 *
963 * ```javascript
964 * test
965 * .open('http://dalekjs.com/guineapig/')
966 * .assert.doesntHaveText('h1', 'This page is a Dalek sandbox', 'It´s a sandbox!')
967 * .done();
968 * ```
969 *
970 * > NOTE: You cant match for a substring with contain() here.
971 *
972 * @api
973 * @method doesntHaveText
974 * @param {string} selector Selector that matches the elements to test
975 * @param {string} expected Expected test result
976 * @param {string} message Message for the test reporter
977 * @chainable
978 */
979
9801Assertions.prototype.doesntHaveText = function (selector, expected, message) {
9810 var hash = uuid();
9820 if (this.test.querying === true) {
9830 message = expected;
9840 expected = selector;
9850 selector = this.test.selector;
986 }
987
9880 var cb = this._generateCallbackAssertion('text', '!text', this._testShallowUnequals, hash, {selector: selector, expected: expected, message: message}).bind(this.test);
9890 this._addToActionQueue([selector, expected, hash], 'text', cb);
9900 return this.chaining ? this : this.test;
991};
992
993/**
994 * Asserts that given text does not exist in the current alert/prompt/confirm dialog.
995 *
996 * ```html
997 * <a href="#" id="alert_confirm" onclick="confirm('Confirm me!')">I make confirm</a>
998 * ```
999 *
1000 * ```javascript
1001 * test
1002 * .open('http://skaaro.com/index.html')
1003 * .click('#alert_confirm')
1004 * .assert.dialogDoesntHaveText('I am an alert')
1005 * .accept()
1006 * .done();
1007 * ```
1008 *
1009 * > NOTE: You cant match for a substring with contain() here.
1010 * > NOTE: Does not work in Firefox & PhantomJS
1011 *
1012 * @api
1013 * @method dialogDoesntHaveText
1014 * @param {string} expected Expected test result
1015 * @param {string} message Message for the test reporter
1016 * @chainable
1017 */
1018
10191Assertions.prototype.dialogDoesntHaveText = function (expected, message) {
10200 var hash = uuid();
10210 var cb = this._generateCallbackAssertion('alertText', '!alertText', this._testShallowUnequals, hash, {expected: expected, message: message}).bind(this.test);
10220 this._addToActionQueue([expected, hash], 'alertText', cb);
10230 return this.chaining ? this : this.test;
1024};
1025
1026/**
1027 * Asserts that given text does exist in the provided selector.
1028 *
1029 * ```html
1030 * <body>
1031 * <h1>This is a Dalek sandbox</h1>
1032 * </body>
1033 * ```
1034 *
1035 * ```javascript
1036 * test
1037 * .open('http://dalekjs.com/guineapig/')
1038 * .assert.text('h1', 'This page is a Dalek sandbox', 'Exterminate!')
1039 * .done();
1040 * ```
1041 *
1042 * of course, text works also with the assertion helpers is() and not()
1043 *
1044 * ```javascript
1045 * test
1046 * .open('http://dalekjs.com/guineapig/')
1047 * .assert.text('h1').is('This page is a Dalek sandbox', 'Exterminate!')
1048 * .done();
1049 * ```
1050 *
1051 * ```javascript
1052 * test
1053 * .open('http://dalekjs.com/guineapig/')
1054 * .assert.text('h1').is.not('This page is a CasperJS sandbox', 'Exterminate!')
1055 * .done();
1056 * ```
1057 *
1058 * and you can also check for the occurrence of a substring with to.contain() (but don't try to chain it with not() as this is not possible)
1059 *
1060 * ```javascript
1061 * test
1062 * .open('http://dalekjs.com/guineapig/')
1063 * .assert.text('h1').to.contain('CasperJS')
1064 * .done();
1065 * ```
1066 *
1067 * @api
1068 * @method text
1069 * @param {string} selector Selector that matches the elements to test
1070 * @param {string} expected Expected test result
1071 * @param {string} message Message for the test reporter
1072 * @chainable
1073 */
1074
10751Assertions.prototype.text = function (selector, expected, message) {
10760 var hash = uuid();
10770 if (this.test.querying === true) {
10780 message = expected;
10790 expected = selector;
10800 selector = this.test.selector;
1081 }
1082
10830 var cb = this._generateCallbackAssertion('text', 'text', this._testShallowEquals, hash, {selector: selector, expected: expected, message: message}).bind(this.test);
10840 this._addToActionQueue([selector, expected, hash], 'text', cb);
10850 return (this.chaining || this.test.querying) ? this : this.test;
1086};
1087
1088/**
1089 * Asserts that given alertText does exist in the provided alert/confirm or prompt dialog.
1090 *
1091 * ```html
1092 * <a href="#" id="alert" onclick="alert('I am an alert')">I make alerts!</a>
1093 * ```
1094 *
1095 * ```javascript
1096 * test
1097 * .open('http://skaaro.com/index.html')
1098 * .click('#alert_confirm')
1099 * .assert.dialogText('I am an alert')
1100 * .accept()
1101 * .done();
1102 * ```
1103 *
1104 * of course, text works also with the assertion helpers is() and not()
1105 *
1106 * ```javascript
1107 * test
1108 * .open('http://dalekjs.com/guineapig/')
1109 * .assert.dialogText().is('I am an alert', 'Exterminate!')
1110 * .done();
1111 * ```
1112 *
1113 * ```javascript
1114 * test
1115 * .open('http://dalekjs.com/guineapig/')
1116 * .assert.dialogText().is.not('I am an prompt', 'Exterminate!')
1117 * .done();
1118 * ```
1119 *
1120 *
1121 * > NOTE: Does not work in Firefox & PhantomJS
1122 *
1123 * @api
1124 * @method dialogText
1125 * @param {string} expected Expected testresult
1126 * @param {string} message Message for the test reporter
1127 * @chainable
1128 */
1129
11301Assertions.prototype.dialogText = function (expected, message) {
11310 var hash = uuid();
11320 var cb = this._generateCallbackAssertion('alertText', 'alertText', this._testShallowEquals, hash, {expected: expected, message: message}).bind(this.test);
11330 this._addToActionQueue([expected, hash], 'alertText', cb);
11340 return (this.chaining || this.test.querying) ? this : this.test;
1135};
1136
1137/**
1138 * Asserts that the page title is as expected.
1139 *
1140 * ```javascript
1141 * test.open('http://doctorwhotv.co.uk/')
1142 * .assert.title('Doctor Who TV', 'Not your Daleks TV')
1143 * .done();
1144 * ```
1145 *
1146 * Yep, using assertion helpers is also possible:
1147 *
1148 * ```javascript
1149 * test.open('http://doctorwhotv.co.uk/')
1150 * .assert.title().is('Doctor Who TV', 'Not your Daleks TV')
1151 * .done();
1152 * ```
1153 *
1154 * the not() helper is available too:
1155 *
1156 * ```javascript
1157 * test.open('http://doctorwhotv.co.uk/')
1158 * .assert.title().is.not('Dalek Emperor TV', 'Not your Daleks TV')
1159 * .done();
1160 * ```
1161 *
1162 * and you can also match for a substring with to.contain() (but don't try to chain it with not() as this is not possible):
1163 *
1164 * ```javascript
1165 * test.open('http://doctorwhotv.co.uk/')
1166 * .assert.title().to.contain('Emperor')
1167 * .done();
1168 * ```
1169 *
1170 * @api
1171 * @method title
1172 * @param {string} expected Expected test result
1173 * @param {string} message Message for the test reporter
1174 * @chainable
1175 */
1176
11771Assertions.prototype.title = function (expected, message) {
11780 var hash = uuid();
11790 var cb = this._generateCallbackAssertion('title', 'title', this._testShallowEquals, hash, {expected: expected, message: message}).bind(this.test);
11800 this._addToActionQueue([expected, hash], 'title', cb);
11810 return this.chaining ? this : this.test;
1182};
1183
1184/**
1185 * Asserts that given title does not match the given expectations.
1186 *
1187 * ```javascript
1188 * test.open('http://doctorwhotv.co.uk/')
1189 * .assert.doesntHaveTitle('Dalek Emperor TV', 'Not your Daleks TV')
1190 * .done();
1191 * ```
1192 *
1193 * @api
1194 * @method doesntHaveTitle
1195 * @param {string} expected Expected test result
1196 * @param {string} message Message for the test reporter
1197 * @chainable
1198 */
1199
12001Assertions.prototype.doesntHaveTitle = function (expected, message) {
12010 var hash = uuid();
12020 var cb = this._generateCallbackAssertion('title', '!title', this._testShallowUnequals, hash, {expected: expected, message: message}).bind(this.test);
12030 this._addToActionQueue([expected, hash], 'title', cb);
12040 return this.chaining ? this : this.test;
1205};
1206
1207/**
1208 * Asserts that the page’s url is as expected.
1209 *
1210 * ```javascript
1211 * test.open('http://doctorwhotv.co.uk/')
1212 * .assert.url('http://doctorwhotv.co.uk/', 'URL is as expected')
1213 * .done();
1214 * ```
1215 *
1216 * You can also check if the protocol changed,
1217 * nice to see when you open GitHub with 'http' instead of 'https'
1218 *
1219 * ```javascript
1220 * test.open('http://github.com')
1221 * .assert.url('https://github.com/', 'Changed prototcols')
1222 * .done();
1223 * ```
1224 *
1225 * Yep, using assertion helpers is also possible:
1226 *
1227 * ```javascript
1228 * test.open('http://github.com')
1229 * .assert.url().is('http://doctorwhotv.co.uk/', 'URL is as expected')
1230 * .done();
1231 * ```
1232 *
1233 * the not() helper is available too:
1234 *
1235 * ```javascript
1236 * test.open('http://doctorwhotv.co.uk/')
1237 * .assert.url().is.not('http://doctorwhotv.co.uk/', 'URL is as expected')
1238 * .done();
1239 * ```
1240 *
1241 * and you can also match for a substring with to.contain() (but don't try to chain it with not() as this is not possible):
1242 *
1243 * ```javascript
1244 * test.open('http://doctorwhotv.co.uk/')
1245 * .assert.url().to.contain('doctor')
1246 * .done();
1247 * ```
1248 *
1249 * @api
1250 * @method url
1251 * @param {string} expected Expected test result
1252 * @param {string} message Message for the test reporter
1253 * @chainable
1254 */
1255
12561Assertions.prototype.url = function (expected, message) {
12570 var hash = uuid();
12580 var cb = this._generateCallbackAssertion('url', 'url', this._testShallowEquals, hash, {expected: expected, message: message}).bind(this.test);
12590 this._addToActionQueue([expected, hash], 'url', cb);
12600 return this.chaining ? this : this.test;
1261};
1262
1263/**
1264 * Asserts that the pages URL does not match the expectation.
1265 *
1266 * ```javascript
1267 * test.open('http://doctorwhotv.co.uk/')
1268 * .assert.doesntHaveUrl('http://doctorwhotv.co.uk/', 'URL is not expected')
1269 * .done();
1270 * ```
1271 *
1272 * Oh, you might also match for a substring with to.contain():
1273 *
1274 * * ```javascript
1275 * test.open('http://doctorwhotv.co.uk/')
1276 * .assert.doesntHaveUrl().to.contain('doctor')
1277 * .done();
1278 * ```
1279 *
1280 * @api
1281 * @method doesntHaveUrl
1282 * @param {string} expected Expected test result
1283 * @param {string} message Message for the test reporter
1284 * @chainable
1285 */
1286
12871Assertions.prototype.doesntHaveUrl = function (expected, message) {
12880 var hash = uuid();
12890 var cb = this._generateCallbackAssertion('url', '!url', this._testShallowUnequals, hash, {expected: expected, message: message}).bind(this.test);
12900 this._addToActionQueue([expected, hash], 'url', cb);
12910 return this.chaining ? this : this.test;
1292};
1293
1294/**
1295 * Asserts that an elements attribute is as expected.
1296 *
1297 * ```html
1298 * <form>
1299 * <button class="jumpButton" type="submit">Fire</button>
1300 * </form>
1301 * ```
1302 *
1303 * ```javascript
1304 * test
1305 * .open('http://dalekjs.com/guineapig/')
1306 * .assert.attr('.jumpButton', 'type', 'submit')
1307 * .done();
1308 * ```
1309 *
1310 * ```html
1311 * <div class="wellImUpperUpperClassHighSociety" id="dataDiv" data-spot="cat"></div>
1312 * ```
1313 *
1314 * ```javascript
1315 * test
1316 * .open('http://dalekjs.com/guineapig/')
1317 * .assert.attr('#dataDiv').is('data-spot', 'cat', 'We found Dataʼs cat!')
1318 * .done();
1319 * ```
1320 *
1321 * ```javascript
1322 * test
1323 * .open('http://dalekjs.com/guineapig/')
1324 * .assert.attr('#dataDiv').is.not('data-spot', 'doc', 'Spot is not a dog!')
1325 * .done();
1326 * ```
1327 *
1328 * You can also use attr() for checking if a class is existent
1329 *
1330 * ```javascript
1331 * test
1332 * .open('http://dalekjs.com/guineapig/')
1333 * .assert.attr('#dataDiv', 'class', 'wellImUpperUpperClassHighSociety')
1334 * .done();
1335 * ```
1336 *
1337 * and you can also match a substring (e. g. a single class if more classes are on that elem) with to.contain():
1338 *
1339 * ```javascript
1340 * test
1341 * .open('http://dalekjs.com/guineapig/')
1342 * .assert.attr('#dataDiv', 'class').to.contain('upperUpperClass')
1343 * .done();
1344 * ```
1345 *
1346 * @api
1347 * @method attr
1348 * @param {string} selector Selector that matches the elements to test
1349 * @param {string} attribute The attribute to test
1350 * @param {string} expected Expected test result
1351 * @param {string} message Message for the test reporter
1352 * @chainable
1353 */
1354
13551Assertions.prototype.attr = function (selector, attribute, expected, message) {
13560 var hash = uuid();
1357
13580 if (this.test.querying === true) {
13590 message = expected;
13600 expected = attribute;
13610 attribute = selector;
13620 selector = this.test.selector;
1363 }
1364
13650 var cb = this._generateCallbackAssertion('attribute', 'attribute', this._testShallowEquals, hash, {expected: expected, message: message, selector: selector, attribute: attribute}).bind(this.test);
13660 this._addToActionQueue([selector, attribute, expected, hash], 'attribute', cb);
13670 return this.chaining ? this : this.test;
1368};
1369
1370// TEST HELPER
1371// -----------
1372
1373/**
1374 * Is helper
1375 *
1376 * @method is
1377 * @param {mixed} expected Value to check
1378 * @param {string} message Test message
1379 * @chainable
1380 */
1381
13821Assertions.prototype.is = function (expected, message) {
13830 return this.generateTestHelper('is', '_testShallowEquals', false)(expected, message);
1384};
1385
1386/**
1387 * Not helper
1388 *
1389 * @method not
1390 * @param {mixed} expected Value to check
1391 * @param {string} message Test message
1392 * @chainable
1393 */
1394
13951Assertions.prototype.not = function (expected, message) {
13960 return this.generateTestHelper('not', '_testShallowEquals', true)(expected, message);
1397};
1398
1399/**
1400 * Between helper
1401 *
1402 * @method between
1403 * @param {mixed} expected Value to check
1404 * @param {string} message Test message
1405 * @chainable
1406 */
1407
14081Assertions.prototype.between = function (expected, message) {
14090 return this.generateTestHelper('between', '_testBetween', false)(expected, message);
1410};
1411
1412/**
1413 * Gt helper
1414 *
1415 * @method gt
1416 * @param {mixed} expected Value to check
1417 * @param {string} message Test message
1418 * @chainable
1419 */
1420
14211Assertions.prototype.gt = function (expected, message) {
14220 return this.generateTestHelper('gt', '_testGreaterThan', false)(expected, message);
1423};
1424
1425/**
1426 * Gte helper
1427 *
1428 * @method gte
1429 * @param {mixed} expected Value to check
1430 * @param {string} message Test message
1431 * @chainable
1432 */
1433
14341Assertions.prototype.gte = function (expected, message) {
14350 return this.generateTestHelper('gte', '_testGreaterThanEqual', false)(expected, message);
1436};
1437
1438/**
1439 * Lt helper
1440 *
1441 * @method lt
1442 * @param {mixed} expected Value to check
1443 * @param {string} message Test message
1444 * @chainable
1445 */
1446
14471Assertions.prototype.lt = function (expected, message) {
14480 return this.generateTestHelper('lt', '_testLowerThan', false)(expected, message);
1449};
1450
1451/**
1452 * Lte helper
1453 *
1454 * @method lte
1455 * @param {mixed} expected Value to check
1456 * @param {string} message Test message
1457 * @chainable
1458 */
1459
14601Assertions.prototype.lte = function (expected, message) {
14610 return this.generateTestHelper('lte', '_testLowerThanEqual', false)(expected, message);
1462};
1463
1464/**
1465 * Contain helper
1466 *
1467 * @method contain
1468 * @param {mixed} expected Value to check
1469 * @param {string} message Test message
1470 * @chainable
1471 */
1472
14731Assertions.prototype.contain = function (expected, message) {
14740 return this.generateTestHelper('contain', '_contain', false)(expected, message);
1475};
1476
1477/**
1478 * Match helper
1479 *
1480 * @method match
1481 * @param {string} expected Regex to match on
1482 * @param {string} message Test message
1483 * @chainable
1484 */
1485
14861Assertions.prototype.match = function (expected, message) {
14870 return this.generateTestHelper('match', '_match', false)(expected, message);
1488};
1489
1490/**
1491 * Equals case insensitive helper
1492 *
1493 * @method equalsCaseInsensitive
1494 * @param {mixed} expected Value to check
1495 * @param {string} message Test message
1496 * @chainable
1497 */
1498
14991Assertions.prototype.equalsCaseInsensitive = function (expected, message) {
15000 return this.generateTestHelper('equalsCaseInsensitive', '_caseInsensitiveMatch', false)(expected, message);
1501};
1502
1503// HELPER METHODS
1504// --------------
1505
1506/**
1507 * Generates a callback that will be fired when the action has been completed.
1508 * The callback itself will then validate the answer and will also emit an event
1509 * that the action has been successfully executed.
1510 *
1511 * @method _generateCallbackAssertion
1512 * @param {string} key Unique key of the action
1513 * @param {string} type Type of the action (usually the actions name)
1514 * @param {string} test test method to be used
1515 * @param {string} hash the uuid
1516 * @param {object} opts the options object
1517 * @return {function} The generated callback function
1518 * @private
1519 */
1520
15211Assertions.prototype._generateCallbackAssertion = function (key, type, test, hash, opts) {
15220 var cb = function (data) {
15230 if (data && data.key === key && data.hash === hash) {
1524
15250 this._lastGeneratedAction = {key: key, type: type, test: test, hash: hash, opts: opts, data: data};
1526
15270 if (!opts.expected && (key === 'title' || key === 'width' || key === 'height' || key === 'url' || key === 'text' || key === 'attribute' || key === 'numberOfElements' || key === 'numberOfVisibleElements')) {
15280 return false;
1529 }
1530
15310 if (typeof opts.expected === 'function') {
15320 opts.expected = opts.expected();
1533 }
1534
15350 var testResult = test(data.value, opts.expected, opts.parseFloatOnValues);
1536
15370 this.reporter.emit('report:assertion', {
1538 success: testResult,
1539 expected: opts.comparisonOperator ? opts.comparisonOperator + opts.expected : opts.expected,
1540 value: data.value,
1541 message: opts.message,
1542 type: type
1543 });
1544
15450 this.incrementExpectations();
15460 if (!testResult) {
15470 this.incrementFailedAssertions();
1548 }
1549 }
1550 };
15510 return cb;
1552};
1553
1554/**
1555 * Adds a method to the queue of actions/assertions to execute
1556 *
1557 * @method _addToActionQueue
1558 * @param {object} opts Options of the action to invoke
1559 * @param {string} driverMethod Name of the method to call on the driver
1560 * @param {function} A callback function that will be executed when the action has been executed
1561 * @private
1562 * @chainable
1563 */
1564
15651Assertions.prototype._addToActionQueue = function (opts, driverMethod, cb) {
15660 this._lastGeneratedShit = {opts: opts, driverMethod: driverMethod};
15670 this.test.actionPromiseQueue.push(function () {
15680 var deferredAction = Q.defer();
15690 this.test.driver[driverMethod].apply(this.test.driver, opts);
15700 deferredAction.resolve();
15710 this.test.driver.events.on('driver:message', cb);
15720 return deferredAction.promise;
1573 }.bind(this));
15740 return this;
1575};
1576
1577/**
1578 * Generates a function that can be used
1579 *
1580 * @method generateTestHelper
1581 * @param name
1582 * @param assertionFn
1583 * @param negate
1584 * @return
1585 * @private
1586 */
1587
15881Assertions.prototype.generateTestHelper = function (name, assertionFn, negate) {
15890 return function (expected, message) {
15900 var gen = this._lastGeneratedShit;
1591
15920 this.test.actionPromiseQueue.push(function () {
15930 var deferredAction = Q.defer();
15940 deferredAction.resolve();
15950 this.test.driver.events.on('driver:message', function () {
1596
15970 if (gen.opts && gen.opts[(gen.opts.length - 1)] && this.test._lastGeneratedAction && this.test._lastGeneratedAction.hash) {
15980 if (gen.opts[(gen.opts.length - 1)] === this.test._lastGeneratedAction.hash && !this.proceeded[this.test._lastGeneratedAction.hash + name]) {
15990 var testResult = this[assertionFn](expected, this.test._lastGeneratedAction.data.value);
1600
16010 if (negate) {
16020 testResult = !testResult;
1603 }
1604
16050 this.proceeded[this.test._lastGeneratedAction.hash + name] = true;
1606
16070 this.test.reporter.emit('report:assertion', {
1608 success: testResult,
1609 expected: expected,
1610 value: this.test._lastGeneratedAction.data.value,
1611 message: message,
1612 type: this.test._lastGeneratedAction.type
1613 });
1614
16150 this.test.incrementExpectations();
1616
16170 if (!testResult) {
16180 this.test.incrementFailedAssertions();
1619 }
1620 }
1621 }
1622 }.bind(this));
16230 return deferredAction.promise;
1624 }.bind(this));
1625
16260 return this.chaining ? this : this.test;
1627 }.bind(this);
1628};
1629
1630// ASSERT METHODS
1631// --------------
1632
1633/**
1634 * Assert if a given value shallow equals a second given value
1635 *
1636 * @method _testShallowEquals
1637 * @param {mixed} a Value to test
1638 * @param {mixed} b Value to test
1639 * @return {bool} false if values donʼt match, true if they match
1640 * @private
1641 */
1642
16431Assertions.prototype._testShallowEquals = function (a, b) {
16440 try {
16450 chai.assert.equal(a, b);
1646 } catch (e) {
16470 return false;
1648 }
1649
16500 return true;
1651};
1652
1653/**
1654 * Assert if a given value shallow does not equal a second given value
1655 *
1656 * @method _testShallowUnequals
1657 * @param {mixed} a Value to test
1658 * @param {mixed} b Value to test
1659 * @return {bool} true if values donʼt match, false if they match
1660 * @private
1661 */
1662
16631Assertions.prototype._testShallowUnequals = function (a, b) {
16640 try {
16650 chai.assert.notEqual(a, b);
1666 } catch (e) {
16670 return false;
1668 }
1669
16700 return true;
1671};
1672
1673/**
1674 * Assert if a given value matches a range
1675 *
1676 * @method _testBetween
1677 * @param {array} a Range to test
1678 * @param {bool} b Value to compare
1679 * @return {bool} test result
1680 * @private
1681 */
1682
16831Assertions.prototype._testBetween = function (a, b) {
16840 try {
16850 chai.expect(b).to.be.within(a[0], a[1]);
1686 } catch (e) {
16870 return false;
1688 }
1689
16900 return true;
1691};
1692
1693/**
1694 * Assert if a given value is greater than the value to compare
1695 *
1696 * @method _testGreaterThan
1697 * @param {bool} a Value to test
1698 * @param {bool} b Value to compare
1699 * @param {parseFloatOnValues} b Whether to apply parseFloat to both values prior comparision
1700 * @return {bool} test result
1701 * @private
1702 */
1703
17041Assertions.prototype._testGreaterThan = function (a, b, parseFloatOnValues) {
17050 if (parseFloatOnValues) {
17060 a = parseFloat(a);
17070 b = parseFloat(b);
1708 }
1709
17100 try {
17110 chai.expect(b).to.be.above(a);
1712 } catch (e) {
17130 return false;
1714 }
1715
17160 return true;
1717};
1718
1719/**
1720 * Assert if a given value is greater or equal than the value to compare
1721 *
1722 * @method _testGreaterThanEqual
1723 * @param {bool} a Value to test
1724 * @param {bool} b Value to compare
1725 * @return {bool} test result
1726 * @private
1727 */
1728
17291Assertions.prototype._testGreaterThanEqual = function (a, b) {
17300 return this._testGreaterThan(a - 1, b);
1731};
1732
1733/**
1734 * Assert if a given value is lower than the value to compare
1735 *
1736 * @method _testLowerThan
1737 * @param {bool} a Value to test
1738 * @param {bool} b Value to compare
1739 * @param {parseFloatOnValues} b Whether to apply parseFloatOnValues to both values prior comparision
1740 * @return {bool} test result
1741 * @private
1742 */
1743
17441Assertions.prototype._testLowerThan = function (a, b, parseFloatOnValues) {
17450 if (parseFloatOnValues) {
17460 a = parseFloat(a);
17470 b = parseFloat(b);
1748 }
1749
17500 try {
17510 chai.expect(b).to.be.below(a);
1752 } catch (e) {
17530 return false;
1754 }
1755
17560 return true;
1757};
1758
1759/**
1760 * Asserts that 2 string match regardless of case
1761 *
1762 * @method _caseInsensitiveMatch
1763 * @param {string} a Value to test
1764 * @param {string} b Value to compare
1765 * @return {bool} testresult
1766 * @private
1767 */
1768
17691Assertions.prototype._caseInsensitiveMatch = function (a, b) {
17700 try {
17710 if(a.toLowerCase && b.toLowerCase) {
17720 chai.expect(b.toLowerCase()).to.eql(a.toLowerCase());
1773 } else {
17740 return false;
1775 }
1776 } catch (e) {
17770 return false;
1778 }
1779
17800 return true;
1781};
1782
1783/**
1784 * Assert if a given value contain another value
1785 *
1786 * @method _contain
1787 * @param {bool} a Value to test
1788 * @param {bool} b Value to compare
1789 * @return {bool} test result
1790 * @private
1791 */
1792
17931Assertions.prototype._contain = function (a, b) {
17940 try {
17950 chai.expect(b).to.include(a);
1796 } catch (e) {
17970 return false;
1798 }
1799
18000 return true;
1801};
1802
1803/**
1804 * Assert if a given value is lower or equal than the value to compare
1805 *
1806 * @method _testLowerThanEqual
1807 * @param {bool} a Value to test
1808 * @param {bool} b Value to compare
1809 * @return {bool} test result
1810 * @private
1811 */
1812
18131Assertions.prototype._testLowerThanEqual = function (a, b) {
18140 return this._testLowerThan(a + 1, b);
1815};
1816
1817/**
1818 * Assert if a given value is boolean 'true'
1819 *
1820 * @method _testTruthy
1821 * @param {bool} a Value to test
1822 * @return {bool} false if value is false, true if value is true
1823 * @private
1824 */
1825
18261Assertions.prototype._testTruthy = function (a) {
18270 return a === 'true' || a === true;
1828};
1829
1830/**
1831 * Assert if a given value is boolean 'false'
1832 *
1833 * @method _testFalsy
1834 * @param {bool} a Value to test
1835 * @return {bool} true if value is false, false if value is true
1836 * @private
1837 */
1838
18391Assertions.prototype._testFalsy = function (a) {
18400 return a === 'false' || a === false;
1841};
1842
1843/**
1844 * Assert a given value matches a RegEx
1845 *
1846 * @method _contain
1847 * @param {mixed} a Value to test
1848 * @param {string} b Value to compare
1849 * @return {bool} test result
1850 * @private
1851 */
1852
18531Assertions.prototype._match = function (a, b) {
18540 try {
18550 chai.expect(b).to.match(a);
1856 } catch (e) {
18570 return false;
1858 }
1859
18600 return true;
1861};
1862

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/config.js

89%
59
53
6
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// ext. libs
281var path = require('path');
291var fs = require('fs');
301var _ = require('lodash');
311var yaml = require('js-yaml');
321var JSON5 = require('json5');
331var glob = require('glob');
341require('coffee-script/register');
35
36/**
37 * Configures the config instance
38 *
39 * @param {object} defaults Default parameter options
40 * @param {object} opts Command line options
41 * @constructor
42 */
43
441var Config = function (defaults, opts, advOpts) {
4520 this.customFilename = null;
4620 this.defaultFilename = 'Dalekfile';
4720 this.supportedExtensions = ['yml', 'json5', 'json', 'js', 'coffee'];
4820 this.advancedOptions = advOpts;
4920 this.config = this.load(defaults, opts.config, opts);
50};
51
52/**
53 * Parses config data & loads config files for [DalekJS](//github.com/dalekjs/dalek) tests.
54 *
55 * This module is a driver plugin for [DalekJS](//github.com/dalekjs/dalek).
56 * It connects Daleks testsuite with the remote testing environment of [Sauce Labs](https://saucelabs.com).
57 *
58 * The driver can be installed with the following command:
59 *
60 * ```bash
61 * $ npm install dalek-driver-sauce --save-dev
62 * ```
63 *
64 * You can use the driver by adding a config option to the your [Dalekfile](/docs/config.html)
65 *
66 * ```javascript
67 * "driver": ["sauce"]
68 * ```
69 *
70 * Or you can tell Dalek that it should run your tests via sauces service via the command line:
71 *
72 * ```bash
73 * $ dalek mytest.js -d sauce
74 * ```
75 *
76 * In order to run your tests within the Sauce Labs infrastructure, you must add your sauce username & key
77 * to your dalek configuration. Those two parameters must be set in order to get this driver up & running.
78 * You can specifiy them within your [Dalekfile](/docs/config.html) like so:
79 *
80 * ```javascript
81 * "driver.sauce": {
82 * "user": "dalekjs",
83 * "key": "aaaaaa-1234-567a-1abc-1br6d9f68689"
84 * }
85 * ```
86 *
87 * It is also possible to specify a set of other extra saucy parameters like `name` & `tags`:
88 *
89 * ```javascript
90 * "driver.sauce": {
91 * "user": "dalekjs",
92 * "key": "aaaaaa-1234-567a-1abc-1br6d9f68689",
93 * "name": "Guineapig",
94 * "tags": ["dalek", "testproject"]
95 * }
96 * ```
97 *
98 * If you would like to have a more control over the browser/OS combinations that are available, you are able
99 * to configure you custom combinations:
100 *
101 * ```javascript
102 * "browsers": [{
103 * "chrome": {
104 * "platform": "OS X 10.6",
105 * "actAs": "chrome",
106 * "version": 27
107 * },
108 * "chromeWin": {
109 * "platform": "Windows 7",
110 * "actAs": "chrome",
111 * "version": 27
112 * },
113 * "chromeLinux": {
114 * "platform": "Linux",
115 * "actAs": "chrome",
116 * "version": 26
117 * }
118 * ```
119 *
120 * You can then call your custom browsers like so:
121 *
122 * ```bash
123 * $ dalek mytest.js -d sauce -b chrome,chromeWin,chromeLinux
124 * ```
125 *
126 * or you can define them in your Dalekfile:
127 *
128 * ```javascript
129 * "browser": ["chrome", "chromeWin", "chromeLinux"]
130 * ```
131 *
132 * A list of all available browser/OS combinations, can be found [here](https://saucelabs.com/docs/platforms).
133 *
134 * @module DalekJS
135 * @class Config
136 * @namespace Dalek
137 * @part Config
138 * @api
139 */
140
1411Config.prototype = {
142
143 /**
144 * Checks if a config file is available
145 *
146 * @method checkAvailabilityOfConfigFile
147 * @param {String} pathname
148 * @return {String} config File path
149 */
150
151 checkAvailabilityOfConfigFile: function (pathname) {
152 // check if a pathname is given,
153 // then check if the file is available
15421 if (pathname && fs.existsSync(pathname)) {
1551 return fs.realpathSync(pathname);
156 }
157
158 // check if any of the default configuration files is available
15920 return this.supportedExtensions.reduce(this._checkFile.bind(this));
160 },
161
162 /**
163 * Iterator function that checks the existance of a given file
164 *
165 * @method _checkFile
166 * @param {String} previousValue Last iterations result
167 * @param {String} ext File extension to check
168 * @param {integer} idx Iteration index
169 * @param {object} data File data
170 * @return {String} config File path
171 * @private
172 */
173
174 _checkFile: function (previousValue, ext, idx, data) {
17583 if (previousValue.length > 6) {
1761 return previousValue;
177 }
178
17982 var fileToCheck = this.defaultFilename + '.' + previousValue;
18082 if (fs.existsSync(fileToCheck)) {
1811 return fs.realpathSync(fileToCheck);
182 }
183
18481 return this._checkDefaultFile(ext, data);
185 },
186
187 /**
188 * Iterator function that checks the existance of a the default file
189 *
190 * @method _checkDefaultFile
191 * @param {String} ext File extension to check
192 * @param {object} data File data
193 * @return {String} config File path
194 * @private
195 */
196
197 _checkDefaultFile: function (ext, data) {
19881 if (ext === data[data.length - 1]) {
19921 var fileToCheck = this.defaultFilename + '.' + ext;
20021 if (fs.existsSync(fileToCheck)) {
2011 return fs.realpathSync(fileToCheck);
202 }
203 }
204
20580 return ext;
206 },
207
208 /**
209 * Loads a file & merges the results with the
210 * commandline options & the default config
211 *
212 * @method load
213 * @param {object} defaults Default config
214 * @param {String} pathname Filename of the config file to load
215 * @param {object} opts Command line options
216 * @return {object} config Merged config data
217 */
218
219 load: function (defaults, pathname, opts) {
22020 var file = this.checkAvailabilityOfConfigFile(pathname);
22120 var data = {};
222
22320 if (!this.advancedOptions || this.advancedOptions.dalekfile !== false) {
22420 data = this.loadFile(file);
225 }
226
227 // remove the tests property if the array length is 0
22820 if (opts.tests.length === 0) {
22914 delete opts.tests;
230 }
231
23220 if (data.tests && _.isArray(data.tests) && data.tests.length > 0) {
2330 var tests = [];
234
235 //get all the files that match
2360 _.forEach(data.tests, function(search) {
2370 tests = tests.concat(glob.sync(search));
238 });
239
240 //remove duplicate files
2410 tests = tests.filter(function(elem, pos, self) {
2420 return self.indexOf(elem) === pos;
243 });
244
2450 data.tests = tests;
246 }
247
24820 return _.merge(defaults, data, opts, (this.advancedOptions || {}));
249 },
250
251 /**
252 * Loads a config file & parses it based on the file extension
253 *
254 * @method loadFile
255 * @param {String} pathname Filename of the config file to load
256 * @return {object} data Config data
257 */
258
259 loadFile: function (pathname) {
26020 var ext = path.extname(pathname).replace('.', '');
26120 return this['read' + ext] ? this['read' + ext](pathname) : {};
262 },
263
264 /**
265 * Fetches & returns a config item
266 *
267 * @method get
268 * @param {String} item Key of the item to load
269 * @return {mixed|null} data Requested config data
270 */
271
272 get: function (item) {
27350 return this.config[item] || null;
274 },
275
276 /**
277 * Loads a json config file
278 *
279 * @method readjson
280 * @return {object} data Parsed config data
281 */
282
283 readjson: function (pathname) {
2841 var contents = fs.readFileSync((pathname || this.defaultFilename + '.json'), 'utf8');
2851 return JSON.parse(contents);
286 },
287
288 /**
289 * Loads a json5 config file
290 *
291 * @method readJson5
292 * @return {object} data Parsed config data
293 */
294
295 readjson5: function (pathname) {
2961 var contents = fs.readFileSync((pathname || this.defaultFilename + '.json5'), 'utf8');
2971 return JSON5.parse(contents);
298 },
299
300 /**
301 * Loads a yaml config file
302 *
303 * @method readyaml
304 * @return {object} data Parsed config data
305 */
306
307 readyml: function (pathname) {
3081 var contents = fs.readFileSync((pathname || this.defaultFilename + '.yml'), 'utf8');
3091 return yaml.load(contents);
310 },
311
312 /**
313 * Loads a javascript config file
314 *
315 * @method readjs
316 * @return {object} data Parsed config data
317 */
318
319 readjs: function (pathname) {
3201 return require((pathname || this.defaultFilename));
321 },
322
323 /**
324 * Loads a coffescript config file
325 *
326 * @method readcoffee
327 * @return {object} data Parsed config data
328 */
329
330 readcoffee: function (pathname) {
3311 return require((pathname || this.defaultFilename));
332 },
333
334 /**
335 * Verifies if a reporter is given, exists & is valid
336 *
337 * @method verifyReporters
338 * @return {array} data List of verified reporters
339 */
340
341 verifyReporters: function (reporters, reporter) {
3428 return _.compact(this._verify(reporters, 'isReporter', reporter));
343 },
344
345 /**
346 * Verifies if a driver is given, exists & is valid
347 *
348 * @method verifyDrivers
349 * @return {array} data List of verified drivers
350 */
351
352 verifyDrivers: function (drivers, driver) {
3538 return _.compact(this._verify(drivers, 'isDriver', driver));
354 },
355
356 /**
357 * Verifies if a driver is given, exists & is valid
358 *
359 * @method _verify
360 * @param {array} check Data that should be mapped
361 * @param {string} fn Name of the function that should be invoked on the veryify object
362 * @param {object} instance Object instance where the verify function should be invoked
363 * @return {array} data List of verified items
364 * @private
365 */
366
367 _verify: function (check, fn, instance) {
36816 return check.map(this._verifyIterator.bind(this, fn, instance));
369 },
370
371 /**
372 * Verifies if a driver is given, exists & is valid
373 *
374 * @method _verifyIterator
375 * @param {string} fn Name of the function that should be invoked on the veryify object
376 * @param {object} instance Object instance where the verify function should be invoked
377 * @param {string} elm Name of the element that should be checked
378 * @return {string|null} element name of the verified element or false if checked failed
379 * @priavte
380 */
381
382 _verifyIterator: function (fn, instance, elm) {
38316 return instance[fn](elm) ? elm : false;
384 }
385};
386
387// export the module
3881module.exports = Config;
389

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/driver.js

19%
84
16
68
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// ext. libs
281var async = require('async');
29
30// int. libs
311var Suite = require('./suite');
32
33/**
34 * Configures the driver instance
35 *
36 * @constructor
37 */
38
391var Driver = function (options) {
40 // add configuration data to the driver instance
417 this.config = options.config;
427 this.browser = this.config.get('browser');
437 this.files = this.config.get('tests');
447 this.drivers = this.config.get('driver');
45
46 // flag if we use the canary driver builds
477 this.driverIsCanary = false;
48
49 // link driver events
507 this.driverEmitter = options.driverEmitter;
517 this.reporterEvents = options.reporterEvents;
52};
53
54/**
55 * Generates & starts drivers & browsers
56 * the tests will be run in
57 *
58 * @module DalekJS
59 * @class Driver
60 * @namespace Dalek
61 * @part Driver
62 * @api
63 */
64
651Driver.prototype = {
66
67 /**
68 * Checks if the requested driver is available
69 *
70 * @method isDriver
71 * @param {string} driver Name of the requested driver
72 * @return {bool} isDriver Driver is availavle
73 */
74
75 isDriver: function (driver) {
767 try {
777 require.resolve('dalek-driver-' + driver);
78 } catch (e) {
790 try {
800 require.resolve('dalek-driver-' + driver + '-canary');
81 } catch (e) {
820 return false;
83 }
840 this.driverIsCanary = true;
850 return true;
86 }
877 return true;
88 },
89
90 /**
91 * Loads the requested driver
92 * Emits an event to the reporter
93 *
94 * @method loadDriver
95 * @param {string} driver Name of the requested driver
96 * @return {object} driverModule Instance of the driver module
97 */
98
99 loadDriver: function (driver) {
1000 this.reporterEvents.emit('report:log:system', 'dalek-internal-driver: Loading driver: "' + driver + '"');
1010 return require('dalek-driver-' + driver + (this.driverIsCanary ? '-canary' : ''));
102 },
103
104 /**
105 * Returns a list with browser driver instances
106 *
107 * @method getDrivers
108 * @return {array} verifiedDrivers
109 */
110
111 getDrivers: function () {
1120 return this.drivers.map(this.getVerifiedBrowser, this)[0];
113 },
114
115 /**
116 * Returns a list with browser driver instances
117 *
118 * @method getVerifiedBrowser
119 * @param {string} driver Name of the requested driver
120 * @return {array} verifiedDrivers Array of dribver 'run' functions
121 */
122
123 getVerifiedBrowser: function (driver) {
1240 return this.browser.map(this.getVerifiedDriver.bind(this, this.loadDriver(driver), driver));
125 },
126
127 /**
128 * Returns a scoped version of the driver run function
129 *
130 * @method getVerifiedDriver
131 * @param {object} driverModule Instance of the used driver
132 * @param {string} driver Name of ther used driver
133 * @param {string} browser Name of the used browser
134 * @return {function} run Function that kicks off execution of a testsuite chain in a browser
135 */
136
137 getVerifiedDriver: function (driverModule, driver, browser) {
1380 return this.run.bind(this, driver, driverModule, browser);
139 },
140
141 /**
142 * Loads a browser driver
143 *
144 * @method loadBrowserConfiguration
145 * @param {string} browser Name of the requested browser driver
146 * @param {object} browsers Configuration options for the requested browser
147 * @return {object} browserConfiguration Browser driver isntance and configuration meta data
148 */
149
150 loadBrowserConfiguration: function (browser, browsers, driver) {
1510 var browserConfiguration;
152
1530 if (driver.dummyBrowser && driver.dummyBrowser()) {
1540 return driver.getBrowser(driver);
155 }
156
1570 try {
1580 browserConfiguration = this.getDefaultBrowserConfiguration(browser, browsers);
159 } catch (e) {
1600 browserConfiguration = this.getUserBrowserConfiguration(browser, browsers);
161 }
162
1630 return browserConfiguration;
164 },
165
166 /**
167 * Loads the default browser driver
168 *
169 * @method getDefaultBrowserConfiguration
170 * @param {string} browser Name of the requested browser driver
171 * @param {object} browsers Configuration options for the requested browser
172 * @return {object} browserConfiguration Browser driver isntance and configuration meta data
173 */
174
175 getDefaultBrowserConfiguration: function (browser, browsers) {
1760 var browserConfiguration = {configuration: null, module: null};
177
178 // set browser configuration
1790 if (browsers[browser]) {
1800 browserConfiguration.configuration = browsers[browser];
181 }
182
183 // try to load `normal` browser modules first,
184 // if that doesnt work, try canary builds
1850 try {
186 // check if the browser is a remote instance
187 // else, try to load the local browser
1880 if (browserConfiguration.configuration && browserConfiguration.configuration.type === 'remote') {
1890 browserConfiguration.module = require('./remote');
190 } else {
1910 browserConfiguration.module = require('dalek-browser-' + browser);
192 }
193 } catch (e) {
1940 browserConfiguration.module = require('dalek-browser-' + browser + '-canary');
195 }
196
1970 return browserConfiguration;
198 },
199
200 /**
201 * Loads a user configured browser driver
202 *
203 * @method getUserBrowserConfiguration
204 * @param {string} browser Name of the requested browser driver
205 * @param {object} browsers Configuration options for the requested browser
206 * @return {object} browserConfiguration Browser driver isntance and configuration meta data
207 */
208
209 getUserBrowserConfiguration: function (browser, browsers) {
2100 var browserConfiguration = {configuration: null, module: null};
211
2120 if (browsers && browsers[browser] && browsers[browser].actAs) {
2130 browserConfiguration.module = require('dalek-browser-' + browsers[browser].actAs);
2140 browserConfiguration.configuration = browsers[browser];
215 }
216
2170 if (!browserConfiguration.module && browser.search(':') !== -1) {
2180 var args = browser.split(':');
2190 var extractedBrowser = args[0].trim();
2200 var browserType = args[1].trim().toLowerCase();
2210 browserConfiguration.module = require('dalek-browser-' + extractedBrowser);
222
2230 if (browserConfiguration.module && browserConfiguration.module.browserTypes && browserConfiguration.module.browserTypes[browserType]) {
2240 var binary = (process.platform === 'win32' ? browserConfiguration.module.browserTypes[browserType].win32 : browserConfiguration.module.browserTypes[browserType].darwin);
2250 browserConfiguration.configuration = {
226 binary: binary,
227 type: browserType
228 };
229 }
230 }
231
2320 return browserConfiguration;
233 },
234
235 /**
236 * Couple driver & session status events for the reporter
237 *
238 * @method coupleReporterEvents
239 * @param {string} driverName Name of the requested driver
240 * @param {string} browser Name of the requested browser
241 * @chainable
242 */
243
244 coupleReporterEvents: function (driverName, browser) {
2450 this.driverEmitter.on('driver:sessionStatus:' + driverName + ':' + browser, this.reporterEvents.emit.bind(this.reporterEvents, 'report:driver:session'));
2460 this.driverEmitter.on('driver:status:' + driverName + ':' + browser, this.reporterEvents.emit.bind(this.reporterEvents, 'report:driver:status'));
2470 return this;
248 },
249
250 /**
251 * Returns a list of testsuite runner functions
252 *
253 * @method getTestsuiteInstances
254 * @param {object} driverInstance Instance of the requested driver
255 * @return {array} testsuiteRunners List of testsuites that should be run
256 */
257
258 getTestsuiteInstances: function (driverInstance) {
2590 return this.files.map(this.createTestsuiteInstance.bind(this, driverInstance));
260 },
261
262 /**
263 * Creates a testsuite runner function
264 *
265 * @method createTestsuiteInstance
266 * @param {object} driverInstance Instance of the requested driver
267 * @param {string} file Filename of the testsuite
268 * @return {function} testsuiteRunner Runner function from the testsuite
269 */
270
271 createTestsuiteInstance: function (driverInstance, file) {
2720 var suite = new Suite({numberOfSuites: this.files.length, file: file, driver: driverInstance, driverEmitter: this.driverEmitter, reporterEmitter: this.reporterEvents});
2730 return suite.run.bind(suite);
274 },
275
276 /**
277 * Generates a testsuite instance, emits the
278 * browser running event & starts a new async() sesries execution
279 * Will be called when the driver is ready
280 *
281 * @method _onDriverReady
282 * @param {string} browser Name of the requested browser
283 * @param {string} driverName Name of the requested driver
284 * @param {function} callback Asyncs next() callback function
285 * @param {object} driverInstance Instance of the requested driver
286 * @chainable
287 * @private
288 */
289
290 _onDriverReady: function (browser, driverName, callback, driverInstance) {
291 // generate testsuite instance from test files
2920 var testsuites = this.getTestsuiteInstances(driverInstance);
2930 this.reporterEvents.emit('report:run:browser', driverInstance.webdriverClient.opts.longName);
2940 async.series(testsuites, this._onTestsuiteComplete.bind(this, callback, driverName, browser));
2950 return this;
296 },
297
298 /**
299 * Emits a 'tests complete' event & calls async's next() callback
300 *
301 * @method _onTestsuiteComplete
302 * @param {function} callback Async's next() callback function
303 * @param {string} driverName Name of the requested driver
304 * @param {string} browser Name of the requested browser
305 * @chainable
306 * @private
307 */
308
309 _onTestsuiteComplete: function (callback, driverName, browser) {
3100 this.driverEmitter.emit('tests:complete:' + driverName + ':' + browser);
3110 callback();
3120 return this;
313 },
314
315 /**
316 * Driver runner function.
317 * Registers event handlers for this run,
318 * loads browser & driver configuration & instances,
319 * emits the 'driver ready' event for the browser/driver combination
320 *
321 * @method run
322 * @param {string} driverName Name of the requested driver
323 * @param {object} driverModule Instance of the used driver module
324 * @param {string} browser Name of the requested browser
325 * @param {function} callback Asyncs next() callback function
326 * @chainable
327 */
328
329 run: function (driverName, driverModule, browser, callback) {
330 // load browser configuration
3310 var browsersRaw = this.config.get('browsers');
3320 var browsers = [];
333
334 // Check if we have a valid browser conf, then get the data out
3350 if (browsersRaw !== null) {
3360 browsers = browsersRaw[0];
337 }
338
339 // init the browser configuration
3400 var browserConfiguration = this.loadBrowserConfiguration(browser, browsers, driverModule);
341
342 // check if we need to inject the browser alias into the browser module
3430 if (browserConfiguration.module.setBrowser) {
3440 browserConfiguration.module.setBrowser(browser);
345 }
346
347 // init the driver instance
3480 var driverInstance = driverModule.create({events: this.driverEmitter, reporter: this.reporterEvents, browser: browser, config: this.config, browserMo: browserConfiguration.module, browserConf: browserConfiguration.configuration});
349 // couple driver & session status events for the reporter
3500 this.coupleReporterEvents(driverName, browser);
351
352 // register shutdown handler
3530 if (driverInstance.webdriverClient.opts.kill) {
3540 this.driverEmitter.on('killAll', driverInstance.webdriverClient.opts.kill.bind(driverInstance.webdriverClient.opts));
355 }
356
357 // dispatch some (web)driver events to the reporter
3580 this.driverEmitter.on('driver:webdriver:response', function (res) {
3590 this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.statusCode + ' ' + res.method + ' ' + res.path);
3600 this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.data);
361 }.bind(this));
362
363 // run the tests in the browser, when the driver is ready
364 // emit the tests:complete event, when all tests have been run
3650 this.driverEmitter.on('driver:ready:' + driverName + ':' + browser, this._onDriverReady.bind(this, browser, driverName, callback, driverInstance));
3660 return this;
367 }
368};
369
370// export driver module
3711module.exports = Driver;
372

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/host.js

7%
95
7
88
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// ext. libs
281var http = require('http');
291var os = require('os');
301var Q = require('q');
31
32/**
33 * Sets the configuration options for the
34 * dalek remote browser executor
35 *
36 * @param {options} opts Configuration options
37 * @constructor
38 */
39
401var Host = function (opts) {
410 this.reporterEvents = opts.reporterEvents;
420 this.config = opts.config;
43};
44
45/**
46 * Remote Dalek host proxy
47 *
48 * @module Dalek
49 * @class Host
50 * @part Remote
51 * @api
52 */
53
541Host.prototype = {
55
56 /**
57 * Default port that the Dalek remote server is linking against
58 *
59 * @property defaultPort
60 * @type {integer}
61 * @default 9020
62 */
63
64 defaultPort: 9020,
65
66 /**
67 * Instance of the local browser
68 *
69 * @property bro
70 * @type {object}
71 * @default null
72 */
73
74 bro: null,
75
76 /**
77 * Instance of the reporter event emitter
78 *
79 * @property reporterEvents
80 * @type {EventEmitter2}
81 * @default null
82 */
83
84 reporterEvents: null,
85
86 /**
87 * Instance of the dalek config
88 *
89 * @property config
90 * @type {Dalek.Config}
91 * @default null
92 */
93
94 config: null,
95
96 /**
97 * Local configuration
98 *
99 * @property configuration
100 * @type {object}
101 * @default {}
102 */
103
104 configuration: {},
105
106 /**
107 * Host address of the called webdriver server
108 *
109 * @property remoteHost
110 * @type {string}
111 * @default null
112 */
113
114 remoteHost: null,
115
116 /**
117 * Path of the webdriver server endpoint
118 *
119 * @property remotePath
120 * @type {string}
121 * @default null
122 */
123
124 remotePath: null,
125
126 /**
127 * Port of the called webdriver server
128 *
129 * @property remotePort
130 * @type {string}
131 * @default null
132 */
133
134 remotePort: null,
135
136 /**
137 * Secret that got emitted by the remote instance
138 *
139 * @property remoteSecret
140 * @type {string}
141 * @default null
142 */
143
144 remoteSecret: null,
145
146 /**
147 * Identifier of the remote client
148 *
149 * @property remoteId
150 * @type {string}
151 * @default null
152 */
153
154 remoteId: null,
155
156 /**
157 * Secret that is stored in the local instance
158 *
159 * @property secret
160 * @type {string}
161 * @default null
162 */
163
164 secret: null,
165
166 /**
167 * Incoming message that needs to be proxied
168 * to the local webdriver server
169 *
170 * @property proxyRequest
171 * @type {http.IncomingMessage}
172 * @default null
173 */
174
175 proxyRequest: null,
176
177 /**
178 * Starts the remote proxy server,
179 * prepares the config
180 *
181 * @method run
182 * @param {object} opts Configuration options
183 * @chainable
184 */
185
186 run: function (opts) {
187 // apply configuration
1880 this.configuration = this.config.get('host') || {};
1890 this.configuration.host = this.configuration.host ? !this.configuration.port : 'localhost';
1900 this.secret = this.configuration.secret ? this.configuration.secret : this.secret;
1910 if (!this.configuration.port || opts.port) {
1920 this.configuration.port = opts.port ? opts.port : this.defaultPort;
193 }
194
195 // start the proxy server// emit the instance ready event
1960 this.server = http.createServer(this._createServer.bind(this)).listen(this.configuration.port, this.reporterEvents.emit.bind(this.reporterEvents, 'report:remote:ready', {ip: this._getLocalIp(), port: this.configuration.port}));
1970 return this;
198 },
199
200 /**
201 * Shutdown the proxy server
202 *
203 * @method kill
204 * @return {object} Promise
205 */
206
207 kill: function () {
2080 var deferred = Q.defer();
2090 this.server.close(deferred.resolve);
2100 return deferred.promise;
211 },
212
213 /**
214 * Launches the local browser
215 *
216 * @method _launcher
217 * @param {object} request Request from the dalek remote caller
218 * @param {object} response Response to the dalek remote caller
219 * @private
220 * @chainable
221 */
222
223 _launcher: function (request, response) {
224 // extract the browser id from the request url
2250 var browser = this._extractBrowser(request.url);
226
227 // load local browser module
2280 this.bro = this._loadBrowserModule(browser, response);
229
230 // launch the local browser
2310 if (this.bro) {
2320 this.bro
233 .launch({}, this.reporterEvents, this.config)
234 .then(this._onBrowserLaunch.bind(this, browser, response));
235 }
236
2370 return this;
238 },
239
240 /**
241 * Shuts the local browser down,
242 * end the otherwise hanging request
243 *
244 * @method _launcher
245 * @param {object} response Response to the dalek remote caller
246 * @private
247 * @chainable
248 */
249
250 _killer: function (response) {
2510 if (this.bro) {
2520 this.bro.kill();
253 }
2540 response.setHeader('Connection', 'close');
2550 response.end();
2560 this.reporterEvents.emit('report:remote:closed', {id: this.remoteId, browser: this.bro.longName});
2570 return this;
258 },
259
260 /**
261 * Requires the local browser module & returns it
262 *
263 * @method _loadBrowserModule
264 * @param {string} browser Name of the browser to load
265 * @param {object} response Response to the dalek remote caller
266 * @return {object} The local browser module
267 * @private
268 */
269
270 _loadBrowserModule: function (browser, response) {
2710 var bro = null;
2720 try {
2730 bro = require('dalek-browser-' + browser);
274 } catch (e) {
2750 try {
2760 bro = require('dalek-browser-' + browser + '-canary');
277 } catch (e) {
2780 response.setHeader('Connection', 'close');
2790 response.end(JSON.stringify({error: 'The requested browser "' + browser + '" could not be loaded'}));
280 }
281 }
282
2830 return bro;
284 },
285
286 /**
287 * Stores network data from the local browser instance,
288 * sends browser specific data to the client
289 *
290 * @method _onBrowserLaunch
291 * @param {string} browser Name of the browser to load
292 * @param {object} response Response to the dalek remote caller
293 * @chainable
294 * @private
295 */
296
297 _onBrowserLaunch: function (browser, response) {
2980 this.remoteHost = this.bro.getHost();
2990 this.remotePort = this.bro.getPort();
3000 this.remotePath = this.bro.path.replace('/', '');
3010 this.reporterEvents.emit('report:remote:established', {id: this.remoteId, browser: this.bro.longName});
3020 response.setHeader('Connection', 'close');
3030 response.end(JSON.stringify({browser: browser, caps: this.bro.desiredCapabilities, defaults: this.bro.driverDefaults, name: this.bro.longName}));
3040 return this;
305 },
306
307 /**
308 * Dispatches all incoming requests,
309 * possible endpoints local webdriver server,
310 * browser launcher, browser shutdown handler
311 *
312 * @method _createServer
313 * @param {object} request Request from the dalek remote caller
314 * @param {object} response Response to the dalek remote caller
315 * @private
316 * @chainable
317 */
318
319 _createServer: function (request, response) {
320 // delegate calls based on url
3210 if (request.url.search('/dalek/launch/') !== -1) {
322
323 // store the remotes ip address
3240 this.remoteId = request.connection.remoteAddress;
325
326 // store the remote secret
3270 if (request.headers['secret-token']) {
3280 this.remoteSecret = request.headers['secret-token'];
329 }
330
331 // check if the secrets match, then launch browser
332 // else emit an error
3330 if (this.secret === this.remoteSecret) {
3340 this._launcher(request, response);
335 } else {
3360 response.setHeader('Connection', 'close');
3370 response.end(JSON.stringify({error: 'Secrets do not match'}));
338 }
339
3400 } else if (request.url.search('/dalek/kill') !== -1) {
3410 this._killer(response);
342 } else {
3430 this.proxyRequest = http.request(this._generateProxyRequestOptions(request.headers, request.method, request.url), this._onProxyRequest.bind(this, response, request));
3440 request.on('data', this._onRequestDataChunk.bind(this));
3450 request.on('end', this.proxyRequest.end.bind(this.proxyRequest));
346 }
347
3480 return this;
349 },
350
351 /**
352 * Proxies data from the local webdriver server to the client
353 *
354 * @method _onRequestDataChunk
355 * @param {buffer} chunk Chunk of the incoming request data
356 * @private
357 * @chainable
358 */
359
360 _onRequestDataChunk: function (chunk) {
3610 this.proxyRequest.write(chunk, 'binary');
3620 return this;
363 },
364
365 /**
366 * Proxies remote data to the webdriver server
367 *
368 * @method _onProxyRequest
369 * @param {object} request Request from the dalek remote caller
370 * @param {object} response Response to the dalek remote caller
371 * @param {object} res Response to the local webdriver server
372 * @private
373 * @chainable
374 */
375
376 _onProxyRequest: function (response, request, res) {
3770 var chunks = [];
378
379 // deny access if the remote ids (onitial request, webdriver request) do not match
3800 if (this.remoteId !== request.connection.remoteAddress) {
3810 response.setHeader('Connection', 'close');
3820 response.end();
3830 return this;
384 }
385
3860 res.on('data', function (chunk) {
3870 chunks.push(chunk+'');
388 }).on('end', this._onProxyRequestEnd.bind(this, res, response, request, chunks));
3890 return this;
390 },
391
392 /**
393 * Handles data exchange between the client and the
394 * local webdriver server
395 *
396 * @method _onProxyRequest
397 * @param {object} request Request from the dalek remote caller
398 * @param {object} response Response to the dalek remote caller
399 * @param {object} res Response to the local webdriver server
400 * @param {array} chunks Array of received data pieces that should be forwarded to the local webdriver server
401 * @private
402 * @chainable
403 */
404
405 _onProxyRequestEnd: function (res, response, request, chunks) {
4060 var buf = '';
407
408 // proxy headers for the session request
4090 if (request.url === '/session') {
4100 response.setHeader('Connection', 'close');
4110 Object.keys(res.headers).forEach(function (key) {
4120 response.setHeader(key, res.headers[key]);
413 });
414 }
415
4160 if (chunks.length) {
4170 buf = chunks.join('');
418 }
419
4200 response.write(buf);
4210 response.end();
4220 return this;
423 },
424
425 /**
426 * Extracts the browser that should be launched
427 * from the launch url request
428 *
429 * @method _extractBrowser
430 * @param {string} url Url to parse
431 * @return {string} Extracted browser
432 * @private
433 */
434
435 _extractBrowser: function (url) {
4360 return url.replace('/dalek/launch/', '');
437 },
438
439 /**
440 * Generates the request options from the incoming
441 * request that should then be forwared to the local
442 * webdriver server
443 *
444 * @method _generateProxyRequestOptions
445 * @param {object} header Header meta data
446 * @param {string} method HTTP method
447 * @param {string} url Webriver server endpoint url
448 * @return {object} Request options
449 * @private
450 */
451
452 _generateProxyRequestOptions: function (headers, method, url) {
4530 var options = {
454 host: this.remoteHost,
455 port: this.remotePort,
456 path: this.remotePath + url,
457 method: method,
458 headers: {
459 'Content-Type': headers['content-type'],
460 'Content-Length': headers['content-length']
461 }
462 };
463
464 // check if the path is valid,
465 // else prepend a `root` slash
4660 if (options.path.charAt(0) !== '/') {
4670 options.path = '/' + options.path;
468 }
469
4700 return options;
471 },
472
473 /**
474 * Gets the local ip address
475 * (should be the IPv4 address where the runner is accessible from)
476 *
477 * @method _getLocalIp
478 * @return {string} Local IP address
479 * @private
480 */
481
482 _getLocalIp: function () {
4830 var ifaces = os.networkInterfaces();
4840 var address = [null];
4850 for (var dev in ifaces) {
4860 var alias = [0];
4870 ifaces[dev].forEach(this._grepIp.bind(this, alias, address));
488 }
489
4900 return address[0];
491 },
492
493 /**
494 * Tries to find the local IP address
495 *
496 * @method _grepIp
497 * @param
498 * @param
499 * @param
500 * @chainable
501 * @private
502 */
503
504 _grepIp: function (alias, address, details) {
5050 if (details.family === 'IPv4') {
5060 if (details.address !== '127.0.0.1') {
5070 address[0] = details.address;
508 }
5090 ++alias[0];
510 }
511
5120 return this;
513 }
514
515};
516
5171module.exports = Host;

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/reporter.js

58%
12
7
5
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27/**
28 * Checks & loads reporter modules
29 *
30 * @module DalekJS
31 * @class Reporter
32 * @namespace Dalek
33 * @part Reporter
34 * @api
35 */
36
371var Reporter = {
38
39 /**
40 * Reporters from the canary channel
41 *
42 * @param isCanary
43 */
44
45 isCanary: {},
46
47 /**
48 * Checks if the requested reporter exists
49 *
50 * @method isReporter
51 * @param {string} reporter Name of the reporter
52 * @return {bool} isReporter Reporter exists
53 */
54
55 isReporter: function (reporter) {
567 try {
577 require.resolve('dalek-reporter-' + reporter);
58 } catch (e) {
590 try {
600 require.resolve('dalek-reporter-' + reporter + '-canary');
61 } catch (e) {
620 return false;
63 }
64
650 this.isCanary[reporter] = true;
660 return true;
67 }
687 return true;
69 },
70
71 /**
72 * Loads a requested reporter
73 *
74 * @method loadReporter
75 * @param {string} reporter Name of the reporter
76 * @param {object} options Options to pass to the reporter
77 * @return {object} reporterInstance Reporter instance
78 */
79
80 loadReporter: function (reporter, options) {
817 return require('dalek-reporter-' + reporter + (this.isCanary[reporter] ? '-canary' : ''))(options);
82 }
83};
84
85// export the module
861module.exports = Reporter;
87

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/suite.js

9%
84
8
76
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// ext. libs
281var _ = require('lodash');
291var fs = require('fs');
301var EventEmitter2 = require('eventemitter2').EventEmitter2;
31
32// int. libs
331var unit = require('./unit');
34
35/**
36 * @constructor
37 * @param {object} options
38 */
39
401var Suite = function (options) {
410 this.emitter = new EventEmitter2();
420 this.emitter.setMaxListeners(Infinity);
430 this.initialize(options);
440 this.suite = this.loadTestsuite(options.file);
45};
46
47/**
48 * Suite (Testsuite)
49 *
50 * @module DalekJS
51 * @class Suite
52 * @namespace Dalek
53 * @part Testsuite
54 * @api
55 */
56
571Suite.prototype = {
58
59 /**
60 * Assigns the initial options
61 * driverEmitter -> the drivers event dispatcher
62 * reporterEmitter -> the reporters event dispatcher
63 * driver -> the driver instance (e.g. native webdriver, selenium, etc.)
64 * name -> the suites filename (default suite name)
65 *
66 * @method initialize
67 * @param {object} options
68 * @chainable
69 */
70
71 initialize: function (options) {
720 this.driverEmitter = options.driverEmitter;
730 this.reporterEmitter = options.reporterEmitter;
740 this.driver = options.driver;
750 this.name = options.file;
760 this.numberOfSuites = options.numberOfSuites;
770 this.error = null;
780 return this;
79 },
80
81 /**
82 * Loads the testsuite that should be executed
83 *
84 * @method loadTestsuite
85 * @param {string} testfile
86 * @return {object} testsuite
87 */
88
89 loadTestsuite: function (testfile) {
900 var suite = {};
91
92 // if the tests were passed in *as* a list of tests, just use them
930 if (testfile && typeof testfile === 'object') {
940 var allAreTestFunctions = true;
950 for (var testname in testfile) {
960 if (typeof testfile[testname] !== 'function') { allAreTestFunctions = false; }
97 }
980 if (allAreTestFunctions) {
990 return testfile;
100 }
101 }
102
103 // catch any errors, like falsy requires & stuff
1040 try {
105
1060 if (fs.existsSync(process.cwd() + '/' + testfile)) {
1070 suite = require(process.cwd() + '/' + testfile.replace('.js', ''));
108 } else {
1090 this.error = 'Suite "' + testfile + '" does not exist. Skipping!';
1100 return suite;
111 }
112 } catch (e) {
1130 this.error = '\n' + e.name + ': ' + e.message + '\nFailure loading suite "' + testfile + '". Skipping!';
1140 return suite;
115 }
116
1170 suite._uid = _.uniqueId('Suite');
1180 return suite;
119 },
120
121 /**
122 * Checks if all tests from the testsuite are executed.
123 * Runs the next test if not.
124 * Triggers `asyncs` callback if the suite is finished.
125 * Decrements the `testsToBeExecuted` counter
126 *
127 * @method testFinished
128 * @param {function} callback
129 * @param {array} tests
130 * @param {object} test
131 * @param {string} event
132 * @chainable
133 */
134
135 testFinished: function (callback, tests) {
136 // run a function after the test, if given
1370 if (this.options.afterEach) {
1380 this.options.afterEach();
139 }
140
141 // check if there are still tests that should be executed in this suite,
142 // if so, run them
1430 if (this.decrementTestsToBeExecuted() > 1) {
1440 this.executeNextTest(tests);
1450 return this;
146 }
147
148 // run a function after the testsuite, if given
1490 if (this.options.teardown) {
1500 this.options.teardown();
151 }
152
153 // emit the suite finished event
1540 this.reporterEmitter.emit('report:testsuite:finished', this.name);
155
156 // move on to the next suite
1570 callback();
1580 return this;
159 },
160
161 /**
162 * Decrements number of tests that should be executed in this suite
163 *
164 * @method decrementTestsToBeExecuted
165 * @return {integer} numberOfTestsToBeExecuted
166 */
167
168 decrementTestsToBeExecuted: function () {
1690 return (this.testsToBeExecuted--) -1;
170 },
171
172 /**
173 * Returns the name of the testsuite
174 * If the suite has no name, it will return the testsuites filename
175 *
176 * @method getName
177 * @return {string} name
178 */
179
180 getName: function () {
1810 if (this.suite.name && _.isString(this.suite.name)) {
1820 var name = this.suite.name;
1830 delete this.suite.name;
1840 return name;
185 }
186
1870 return this.name;
188 },
189
190 /**
191 * Returns the options of the testsuite
192 * If the suite has no options, it will return an empty object
193 *
194 * @method getOptions
195 * @return {object} options Suite options
196 */
197
198 getOptions: function () {
1990 if (this.suite.options && _.isObject(this.suite.options)) {
2000 var options = this.suite.options;
2010 delete this.suite.options;
2020 return options;
203 }
204
2050 return {};
206 },
207
208 /**
209 * Returns all names (aka. object keys) the tests that should be executed
210 *
211 * @method getTests
212 * @return {array} test
213 */
214
215 getTests: function () {
2160 return Object.keys(this.suite);
217 },
218
219 /**
220 * Returns the number of tests to be executed
221 *
222 * @method getNumberOfTests
223 * @param {array} tests
224 * @return {integer} numberOfTests
225 */
226
227 getNumberOfTests: function (tests) {
2280 return tests.length;
229 },
230
231 /**
232 * Returns the next test, that should be executed
233 *
234 * @method getNextTest
235 * @return {string} testName
236 */
237
238 getNextTest: function (tests) {
2390 return tests.shift();
240 },
241
242 /**
243 * Executes the next test in the sequence
244 *
245 * @method executeNextTest
246 * @param {array} tests
247 * @return {mixed} testValue
248 */
249
250 executeNextTest: function (tests) {
251 // grab the next test in the queue
2520 var testName = this.getNextTest(tests),
253 // get the next test function
254 testFunction = this.getTest(testName),
255 // generate an instance of the test
256 test = this.getTestInstance(testName);
257 // run a setup function before the test, if given
2580 if (this.options.beforeEach) {
2590 this.options.beforeEach();
260 }
261 // start it
2620 return testFunction.apply(test,[test]);
263 },
264
265 /**
266 * Generates a new test instance
267 *
268 * @method getTestInstance
269 * @param {string} name
270 * @return {Dalek.Test} test
271 */
272
273 getTestInstance: function (name) {
2740 return unit({events: this.emitter, driver: this.driver, reporter: this.reporterEmitter, name: name});
275 },
276
277 /**
278 * Returns a test function by its name
279 *
280 * @method getTest
281 * @param {string} name
282 * @return {function} test
283 */
284
285 getTest: function (name) {
2860 return this.suite[name] && _.isFunction(this.suite[name]) ? this.suite[name] : this.testDoesNotExist;
287 },
288
289 /**
290 * Will be executed if a test is started, that does not exist
291 *
292 * @method testDoesNotExist
293 * @param {object} options
294 */
295
296 testDoesNotExist: function (options) {
2970 if (options.name) {
2980 this.reporterEmitter.emit('warning', 'Test "' + options.name + '" does not exist! Skipping.');
299 }
3000 return this;
301 },
302
303 /**
304 * Runs any tests from this testsuite in sequence
305 *
306 * @method run
307 * @param {function} callback
308 * @chainable
309 */
310
311 run: function (callback) {
3120 var tests = [];
313
314 // check if the suite is
3150 if (this.error) {
3160 this.reporterEmitter.emit('report:testsuite:started', null);
317 // emit a warning notice
3180 this.reporterEmitter.emit('warning', this.error);
319 // emit the suite finished event
3200 this.reporterEmitter.emit('report:testsuite:finished', null);
321 // move on to the next suite
3220 callback();
323 }
324
325 // extract suite name
3260 this.name = this.getName();
327 // extract suite options
3280 this.options = this.getOptions();
329
330 // extract tests
3310 tests = this.getTests();
3320 this.testsToBeExecuted = this.numberOfTests = this.getNumberOfTests(tests);
333
334 // run a function before the testsuite has been launched, if given
3350 if (this.options.setup) {
3360 this.options.setup();
337 }
338
339 // kickstart the test execution
3400 this.executeNextTest(tests);
341
342 // emit the suite started event
3430 this.reporterEmitter.emit('report:testsuite:started', this.name);
344
345 // listen to the test:finished event & then start the next test
346 // if there are no tests in this suite left,
347 // run the async callback & mark this suite as finished
3480 this.emitter.onAny(this.testFinished.bind(this, callback, tests));
3490 return this;
350 }
351};
352
353// export the testuite instance
3541module.exports = Suite;
355

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/timer.js

100%
22
22
0
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27/**
28 * Initializes the timers default values
29 *
30 * @constructor
31 * @class Timer
32 */
33
341var Timer = function () {
359 this.timerData = [0, 0];
369 this.interval = [0, 0];
37};
38
39/**
40 * Timing module to measure test run times
41 *
42 * @module DalekJS
43 * @class Timer
44 * @namespace Dalek
45 * @part Timer
46 * @api
47 */
48
491Timer.prototype = {
50
51 /**
52 * Starts the timer
53 *
54 * @method start
55 * @chainable
56 */
57
58 start: function () {
591 this.timerData = process.hrtime();
601 return this;
61 },
62
63 /**
64 * Stops the timer
65 *
66 * @method stop
67 * @chainable
68 */
69
70 stop: function () {
712 this.interval = process.hrtime(this.timerData);
722 return this;
73 },
74
75 /**
76 * Returns the elapsed time in ms
77 *
78 * @method getElapsedTime
79 * @return {float}
80 */
81
82 getElapsedTime: function () {
832 return (this.interval[0]*1e3 + this.interval[1]/1e6) / 1000;
84 },
85
86 /**
87 * Returns an object with test run time information
88 * containing hours, minutes & seconds
89 *
90 * @method getElapsedTimeFormatted
91 * @return {Object}
92 */
93
94 getElapsedTimeFormatted: function () {
952 var hours, minutes, seconds;
962 var elapsedTimeInSeconds = this.getElapsedTime();
97
98 // assign elapsed time (in seconds) to the seconds output
992 seconds = elapsedTimeInSeconds;
100
101 // check if the elapsed time in seconds is more than a minute
102 // and convert the raw seconds to minutes & seconds
1032 if (elapsedTimeInSeconds > 60) {
1042 minutes = Math.floor(elapsedTimeInSeconds / 60);
1052 seconds = elapsedTimeInSeconds - minutes * 60;
106 }
107
108 // check if the elapsed time in minutes is more than an hour
109 // and convert the raw seconds to hours, minutes & seconds
1102 if (minutes > 60) {
1112 hours = Math.floor(elapsedTimeInSeconds / 3600);
1122 minutes = elapsedTimeInSeconds - hours * 60;
1132 seconds = elapsedTimeInSeconds - minutes * 3600;
114 }
115
1162 return {hours: hours, minutes: minutes, seconds: seconds};
117 }
118};
119
120// export the timer module
1211module.exports = Timer;
122

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/unit.js

8%
97
8
89
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// Ext. libs
281var _ = require('lodash');
291var Q = require('q');
30
31// Int. libs
321var actions = require('./actions');
331var assertions = require('./assertions');
34
35/**
36 * Prepares the test instance values
37 *
38 * @param {object} opts Options like the tests name, etc.
39 * @constructor
40 */
41
421var Unit = function (opts) {
43 // prepare meta queue data
440 this.actionPromiseQueue = [];
45
46 // prepare assertion data
470 this.expactation = null;
480 this.runnedExpactations = 0;
490 this.failedAssertions = 0;
50
51 // prepare test specific data
520 this.name = opts.name;
530 this.lastChain = [];
540 this.uuids = {};
550 this.contextVars = {};
56
570 if (this.name) {
580 this.timeoutForDone = setTimeout(function () {
590 this.done();
600 this.reporter.emit('warning', 'done not called!');
61 }.bind(this), 2000);
62 }
63};
64
65/**
66 * Generates an test instance
67 *
68 * @module DalekJS
69 * @class Unit
70 * @namespace Dalek
71 * @part test
72 * @api
73 */
74
751Unit.prototype = {
76
77 /**
78 * Specify how many assertions are expected to run within a test.
79 * Very useful for ensuring that all your callbacks and assertions are run.
80 *
81 * @method expect
82 * @param {Integer} expecatation Number of assertions that should be run
83 * @chainable
84 */
85
86 expect: function (expectation) {
870 this.expectation = parseInt(expectation, 10);
880 return this;
89 },
90
91 /**
92 * Global data store (works between the node & browser envs)
93 *
94 * @method expect
95 * @param {string|number} key Key to store or fetch data
96 * @param {mixed} value *optional* Data that should be stored
97 * @return {mixed} Data that has been stored
98 * @chainable
99 */
100
101 data: function (key, value) {
1020 if (value) {
1030 this.contextVars[key] = value;
1040 return this;
105 }
106
1070 return this.contextVars[key];
108 },
109
110 /**
111 * Increment the number of executed assertions
112 *
113 * @method incrementExpectations
114 * @chainable
115 */
116
117 incrementExpectations: function () {
1180 this.runnedExpactations++;
1190 return this;
120 },
121
122 /**
123 * Increment the number of failed assertions
124 *
125 * @method incrementFailedAssertions
126 * @chainable
127 */
128
129 incrementFailedAssertions: function () {
1300 this.failedAssertions++;
1310 return this;
132 },
133
134 /**
135 * Checks if the runned tests fullfill the set expectations
136 * or if no expectations were raised
137 *
138 * @method checkExpectations
139 * @return {bool} checkedExpectations Expectations match
140 */
141
142 checkExpectations: function () {
1430 return (this.expectation === null || !this.expectation || (this.runnedExpactations === this.expectation));
144 },
145
146 /**
147 * Checks if all runned assertions passed
148 *
149 * @method checkAssertions
150 * @return {bool} assertiosnFailed Any expectation failed
151 */
152
153 checkAssertions: function () {
1540 return this.failedAssertions === 0;
155 },
156
157 /**
158 * Sets up all the bindings needed for a test to run
159 *
160 * @method done
161 * @return {object} result A promise
162 * @private
163 */
164
165 done: function () {
1660 var result = Q.resolve();
167 // clear the done error timeout
1680 clearTimeout(this.timeoutForDone);
169 // remove all previously attached event listeners to clear the message queue
1700 this.driver.events.removeAllListeners('driver:message');
171 // resolve the deferred when the test is finished
1720 Unit.testStarted.fin(this._testFinished.bind(this, result));
1730 return result;
174 },
175
176 /**
177 * Emits the test finished events & resolves all promises
178 * when its done
179 *
180 * @method _testFinished
181 * @param {object} result Promised result var
182 * @return {object} result Promised result var
183 * @private
184 */
185
186 _testFinished: function (result) {
187 // add a last deferred function on the end of the action queue,
188 // to tell that this test is finished
1890 this.actionPromiseQueue.push(this._testFin.bind(this));
190
191 // initialize all of the event receiver functions,
192 // that later take the driver result
1930 this.actionPromiseQueue.forEach(function (f) {
1940 result = result.then(f).fail(function () {
1950 console.error(arguments);
1960 process.exit(0);
197 });
198 }.bind(this));
199
200 // run the driver when all actions are stored in the queue
2010 Q.allSettled(this.actionPromiseQueue)
202 .then(this.driver.run.bind(this.driver));
203
2040 return result;
205 },
206
207 /**
208 * Emits the test started event
209 *
210 * @method _reportTestStarted
211 * @param {string} name Name of the test
212 * @chainable
213 * @private
214 */
215
216 _reportTestStarted: function (name) {
2170 this.reporter.emit('report:test:started', {name: name});
2180 return this;
219 },
220
221 /**
222 * Checks if the test run is complete & emits/resolves
223 * all the needed events/promises when the run is complete
224 *
225 * @method _onDriverMessage
226 * @param {object} data Data that is returned by the driver:message event
227 * @chainable
228 * @private
229 */
230
231 _onDriverMessage: function (data) {
232 // check if the test run is complete
2330 if (data && data.key === 'run.complete') {
234 // emit the test finish events & resolve the deferred
2350 this._emitConcreteTestFinished();
2360 this._emitAssertionStatus();
2370 this._emitTestFinished();
2380 this.deferred.resolve();
239 }
240
2410 return this;
242 },
243
244 /**
245 * Emits an event, that the current test run has been finished
246 *
247 * @method _emitConcreteTestFinished
248 * @chainable
249 * @private
250 */
251
252 _emitConcreteTestFinished: function () {
2530 this.events.emit('test:' + this._uid + ':finished', 'test:finished', this);
2540 return this;
255 },
256
257 /**
258 * Emits an event that describes the current state of all assertions
259 *
260 * @method _emitAssertionStatus
261 * @chainable
262 * @private
263 */
264
265 _emitAssertionStatus: function () {
2660 this.reporter.emit('report:assertion:status', {
267 expected: (this.expectation ? this.expectation : this.runnedExpactations),
268 run: this.runnedExpactations,
269 status: this._testStatus()
270 });
2710 return this;
272 },
273
274 /**
275 * Get the overall test status (assertions & expectation)
276 *
277 * @method _testStatus
278 * @return {bool} status The test status
279 * @chainable
280 * @private
281 */
282
283 _testStatus: function () {
2840 return this.checkExpectations() && this.checkAssertions();
285 },
286
287 /**
288 * Emits an event that describes the current state of all assertions.
289 * The event should be fired when a test is finished
290 *
291 * @method _emitTestFinished
292 * @chainable
293 * @private
294 */
295
296 _emitTestFinished: function () {
2970 this.reporter.emit('report:test:finished', {
298 name: this.name,
299 id: this._uid,
300 passedAssertions: this.runnedExpactations - this.failedAssertions,
301 failedAssertions: this.failedAssertions,
302 runnedExpactations: this.runnedExpactations,
303 status: this._testStatus(),
304 nl: true
305 });
306
3070 return this;
308 },
309
310 /**
311 * Kicks off the test & binds all promises/events
312 *
313 * @method _testFin
314 * @return {object} promise A promise
315 * @private
316 */
317
318 _testFin: function () {
3190 this.deferred = Q.defer();
320
3210 if (_.isFunction(this.driver.end)) {
3220 this.driver.end();
323 }
324
325 // emit report startet event
3260 this._reportTestStarted(this.name);
327
328 // listen to all the messages from the driver
3290 this.driver.events.on('driver:message', this._onDriverMessage.bind(this));
3300 return this.deferred.promise;
331 },
332
333 /**
334 * Copies assertion methods
335 *
336 * @method _inheritAssertions
337 * @param {Test} test Instacne of test
338 * @chainable
339 * @private
340 */
341
342 _inheritAssertions: function (test) {
3430 ['is'].forEach(function (method) {
3440 test[method] = test.assert[method].bind(test.assert);
345 });
3460 return test;
347 },
348
349 /**
350 * Copies assertion helper methods
351 *
352 * @method _inheritAssertions
353 * @param {Test} test Instacne of test
354 * @chainable
355 * @private
356 */
357
358 _inheritAssertionHelpers: function (test) {
3590 ['not', 'between', 'gt', 'gte', 'lt', 'lte', 'equalsCaseInsensitive'].forEach(function (method) {
3600 test.is[method] = test.assert[method].bind(test.assert);
3610 test.assert.is[method] = test.assert[method].bind(test.assert);
362 });
3630 ['contain', 'match'].forEach(function (method) {
3640 test.to = test.to || {};
3650 test.assert.to = test.assert.to || {};
366
3670 test.to[method] = test.assert[method].bind(test.assert);
3680 test.assert.to[method] = test.assert[method].bind(test.assert);
369 });
3700 return test;
371 },
372
373 /**
374 * Set up the instance
375 *
376 * @method _inheritAssertions
377 * @param {Test} test Instacne of test
378 * @param {object} opts Options
379 * @chainable
380 * @private
381 */
382
383 _initialize: function (test, opts) {
3840 test._uid = _.uniqueId('test');
3850 test.events = opts.events;
3860 test.driver = opts.driver;
3870 test.reporter = opts.reporter;
3880 return test;
389 }
390
391};
392
393// export a function that generates a new test instance
3941module.exports = function (opts) {
395 // mixin assertions, actions & getters
3960 Unit.prototype = _.extend(Unit.prototype, actions({reporter: opts.reporter}).prototype);
3970 var unit = new Unit(opts);
3980 unit.assert = new (assertions())({test: unit});
3990 unit.assert.done = unit.done.bind(this);
4000 unit.assert.query = unit.query.bind(unit.assert);
4010 unit.assert.$ = unit.query.bind(unit.assert);
4020 unit.end = unit.assert.end.bind(unit.assert);
403
404 // copy log methods
4050 unit.log = {};
4060 unit.log.dom = unit.logger.dom.bind(unit);
4070 unit.log.message = unit.logger.message.bind(unit);
408
409 // copy assertions methods
4100 unit = unit._inheritAssertions(unit);
411
412 // copy assertion helper methods
4130 unit = unit._inheritAssertionHelpers(unit);
414
415 // initialize the instance
4160 unit = unit._initialize(unit, opts);
417
418 // TODO: Promise driver start
419 // so that we can reexecute them and clean the env between tests
4200 Unit.testStarted = unit.driver.start(Q);
4210 return unit;
422};
423

/home/ubuntu/src/github.com/dalekjs/dalek/lib/dalek/uuid.js

75%
4
3
1
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
271var counter = 0;
28
291module.exports = function() {
300 return 'uuid-' + (++counter);
31};
32