Coverage

26%
1288
336
952

/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

23%
296
70
226
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 () {
455 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 or css element.
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.png');
435 * // creates 'my/page/in/safari/homepage.png'
436 * test.screenshot('my/page/in/:browser/homepage.png');
437 * // creates 'my/page/in/safari_6_0_1/homepage.png'
438 * test.screenshot('my/page/in/:browser_:version/homepage.png');
439 * // creates 'my/page/in/safari_6_0_1/on/osx/homepage.png'
440 * test.screenshot('my/page/in/:browser_:version/on/:os/homepage.png');
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.png');
443 * // creates 'my/page/at/w1024_h768/homepage.png'
444 * test.screenshot('my/page/at/:viewport/homepage.png');
445 * // creates 'my/page/at/637657345/homepage.png'
446 * test.screenshot('my/page/in_time/:timestamp/homepage.png');
447 * // creates 'my/page/at/12_24_2013/homepage.png'
448 * test.screenshot('my/page/in_time/:date/homepage.png');
449 * // creates 'my/page/at/12_24_2013_14_55_23/homepage.png'
450 * test.screenshot('my/page/in_time/:datetime/homepage.png');
451 * ```
452 *
453 * @api
454 * @method screenshot
455 * @param {string} pathname Name of the folder and file the screenshot should be saved to
456 * @param {string} css selector of element should be screeshoted
457 * @return chainable
458 */
459
4601Actions.prototype.screenshot = function (pathname, selector) {
4613 var hash = uuid();
462
4633 if (this.querying === true) {
4640 selector = this.selector;
465 }
466
4673 var opts = {
468 realpath : undefined,
469 selector : selector
470 };
471
4723 this.screenshotParams = opts;
473
4743 var screenshotcb = this._generatePlainCallback('screenshot', hash, opts, 'realpath', typeof selector === 'undefined');
4753 this._addToActionQueue(['', pathname, hash], 'screenshot', screenshotcb.bind(this));
476
4773 if (selector) {
4781 var imagecutcb = this._generateCallbackAssertion('imagecut', 'screenshot element', opts, hash);
4791 this._addToActionQueue([opts, hash], 'imagecut', imagecutcb);
480 }
481
4823 this.reporter.emit('report:screenshot', {
483 'pathname' : pathname,
484 'uuid' : hash
485 });
486
4873 return this;
488};
489
490/**
491 * Generates a callback that will be fired when the action has been completed.
492 * The callback will then store value into opts variable.
493 *
494 * @method _generateCallbackAssertion
495 * @param {string} type Type of the action (normalle the actions name)
496 * @param {string} hash Unique id of the action
497 * @param {string} opts Variable where will be stored result of execution of the action
498 * @param {string} key Name of the property where will be stored result of execution of the action
499 * @return {function} The generated callback function
500 * @private
501 */
5021Actions.prototype._generatePlainCallback = function (type, hash, opts, property, last) {
5033 var cb = function (data) {
5040 if (data.hash === hash && data.key === type && !this.uuids[data.uuid]) {
5050 if (typeof opts === 'object' && typeof property === 'string') {
5060 opts[property] = data.value;
507 }
5080 if (data.key === 'screenshot') {
5090 this.reporter.emit('report:action', {
510 value: data.value,
511 type: type,
512 uuid: data.uuid
513 });
514 }
515
5160 if (last) {
5170 this.uuids[data.uuid] = true;
518 }
519 }
520 };
5213 return cb;
522};
523
524/**
525 * Pause steps suite execution for a given amount of time, and optionally execute a step on done.
526 *
527 * This makes sense, if you have a ticker for example, tht scrolls like every ten seconds
528 * & you want to assure that the visible content changes every ten seconds
529 *
530 * ```javascript
531 * test.open('http://myticker.org')
532 * .assert.visible('.ticker-element:first-child', 'First ticker element is visible')
533 * .wait(10000)
534 * .assert.visible('.ticker-element:nth-child(2)', 'Snd. ticker element is visible')
535 * .wait(10000)
536 * .assert.visible('.ticker-element:last-child', 'Third ticker element is visible')
537 * .done();
538 * ```
539 * If no timeout argument is given, a default timeout of 5 seconds will be used
540 *
541 * ```javascript
542 * test.open('http://myticker.org')
543 * .assert.visible('.ticker-element:first-child', 'First ticker element is visible')
544 * .wait()
545 * .assert.visible('.ticker-element:nth-child(2)', 'Snd. ticker element is visible')
546 * .wait()
547 * .assert.visible('.ticker-element:last-child', 'Third ticker element is visible')
548 * .done();
549 * ```
550 *
551 * @api
552 * @method wait
553 * @param {number} timeout in milliseconds
554 * @chainable
555 */
556
5571Actions.prototype.wait = function (timeout) {
5580 var hash = uuid();
5590 var cb = this._generateCallbackAssertion('wait', 'wait', timeout, hash);
5600 this._addToActionQueue([(timeout ? parseInt(timeout, 10) : 5000), hash], 'wait', cb);
5610 return this;
562};
563
564/**
565 * Reloads current page location.
566 *
567 * This is basically the same as hitting F5/refresh in your browser
568 *
569 * ```javascript
570 * test.open('http://google.com')
571 * .reload()
572 * .done();
573 * ```
574 *
575 * @api
576 * @method reload
577 * @chainable
578 */
579
5801Actions.prototype.reload = function () {
5810 var hash = uuid();
5820 var cb = this._generateCallbackAssertion('refresh', 'refresh', '', hash);
5830 this._addToActionQueue([hash], 'refresh', cb);
5840 return this;
585};
586
587/**
588 * Moves a step forward in browser's history.
589 *
590 * This is basically the same as hitting the forward button in your browser
591 *
592 * ```javascript
593 * test.open('http://google.com')
594 * .open('https://github.com')
595 * .assert.url.is('https://github.com/', 'We are at GitHub')
596 * .back()
597 * .assert.url.is('http://google.com', 'We are at Google!')
598 * .forward()
599 * .assert.url.is('https://github.com/', 'Back at GitHub! Timetravel FTW')
600 * .done();
601 * ```
602 *
603 * @api
604 * @method forward
605 * @chainable
606 */
607
6081Actions.prototype.forward = function () {
6090 var hash = uuid();
6100 var cb = this._generateCallbackAssertion('forward', 'forward', '', hash);
6110 this._addToActionQueue([hash], 'forward', cb);
6120 return this;
613};
614
615/**
616 * Moves back a step in browser's history.
617 *
618 * This is basically the same as hitting the back button in your browser
619 *
620 * ```javascript
621 * test.open('http://google.com')
622 * .open('https://github.com')
623 * .assert.url.is('https://github.com/', 'We are at GitHub')
624 * .back()
625 * .assert.url.is('http://google.com', 'We are at Google!')
626 * .forward()
627 * .assert.url.is('https://github.com/', 'Back at GitHub! Timetravel FTW');
628 * .done();
629 * ```
630 *
631 * @api
632 * @method back
633 * @chainable
634 */
635
6361Actions.prototype.back = function () {
6370 var hash = uuid();
6380 var cb = this._generateCallbackAssertion('back', 'back', '', hash);
6390 this._addToActionQueue([hash], 'back', cb);
6400 return this;
641};
642
643/**
644 * Performs a click on the element matching the provided selector expression.
645 *
646 * If we take Daleks homepage (the one you're probably visiting right now),
647 * the HTML looks something like this (it does not really, but hey, lets assume this for a second)
648 *
649 * ```html
650 * <nav>
651 * <ul>
652 * <li><a id="homeapge" href="/index.html">DalekJS</a></li>
653 * <li><a id="docs" href="/docs.html">Documentation</a></li>
654 * <li><a id="faq" href="/faq.html">F.A.Q</a></li>
655 * </ul>
656 * </nav>
657 * ```
658 *
659 * ```javascript
660 * test.open('http://dalekjs.com')
661 * .click('#faq')
662 * .assert.title().is('DalekJS - Frequently asked questions', 'What the F.A.Q.')
663 * .done();
664 * ```
665 *
666 * By default, this performs a left click.
667 * In the future it might become the ability to also execute a "right button" click.
668 *
669 * > Note: Does not work correctly in Firefox when used on `<select>` & `<option>` elements
670 *
671 * @api
672 * @method click
673 * @param {string} selector Selector of the element to be clicked
674 * @chainable
675 */
676
6771Actions.prototype.click = function (selector) {
6780 var hash = uuid();
679
6800 if (this.querying === true) {
6810 selector = this.selector;
682 }
683
6840 var cb = this._generateCallbackAssertion('click', 'click', selector, hash);
6850 this._addToActionQueue([selector, hash], 'click', cb);
6860 return this;
687};
688
689/**
690 * Submits a form.
691 *
692 * ```html
693 * <form id="skaaro" action="skaaro.php" method="GET">
694 * <input type="hidden" name="intheshadows" value="itis"/>
695 * <input type="text" name="truth" id="truth" value=""/>
696 * </form>
697 * ```
698 *
699 * ```javascript
700 * test.open('http://home.dalek.com')
701 * .type('#truth', 'out there is')
702 * .submit('#skaaro')
703 * .done();
704 * ```
705 *
706 * > Note: Does not work in Firefox yet
707 *
708 * @api
709 * @method submit
710 * @param {string} selector Selector of the form to be submitted
711 * @chainable
712 */
713
7141Actions.prototype.submit = function (selector) {
7150 var hash = uuid();
716
7170 if (this.querying === true) {
7180 selector = this.selector;
719 }
720
7210 var cb = this._generateCallbackAssertion('submit', 'submit', selector, hash);
7220 this._addToActionQueue([selector, hash], 'submit', cb);
7230 return this;
724};
725
726/**
727 * Performs an HTTP request for opening a given location.
728 * You can forge GET, POST, PUT, DELETE and HEAD requests.
729 *
730 * Basically the same as typing a location into your browsers URL bar and
731 * hitting return.
732 *
733 * ```javascript
734 * test.open('http://dalekjs.com')
735 * .assert.url().is('http://dalekjs.com', 'DalekJS I\'m in you')
736 * .done();
737 * ```
738 *
739 * @api
740 * @method open
741 * @param {string} location URL of the page to open
742 * @chainable
743 */
744
7451Actions.prototype.open = function (location) {
746 //see if we should prepend the location with the configured base url is available and needed
7470 if(location.substr(0, 1) === '/' && this.driver.config.config.baseUrl) {
7480 location = this.driver.config.config.baseUrl + location;
749 }
750
7510 var hash = uuid();
7520 var cb = this._generateCallbackAssertion('open', 'open', location, hash);
7530 this._addToActionQueue([location, hash], 'open', cb);
7540 return this;
755};
756
757/**
758 * Types a text into an input field or text area.
759 * And yes, it really types, character for character, like you would
760 * do when using your keyboard.
761 *
762 *
763 * ```html
764 * <form id="skaaro" action="skaaro.php" method="GET">
765 * <input type="hidden" name="intheshadows" value="itis"/>
766 * <input type="text" name="truth" id="truth" value=""/>
767 * </form>
768 * ```
769 *
770 * ```javascript
771 * test.open('http://home.dalek.com')
772 * .type('#truth', 'out there is')
773 * .assert.val('#truth', 'out there is', 'Text has been set')
774 * .done();
775 * ```
776 *
777 * You can also send special keys using unicode.
778 *
779 * * ```javascript
780 * test.open('http://home.dalek.com')
781 * .type('#truth', 'out \uE008there\uE008 is')
782 * .assert.val('#truth', 'out THERE is', 'Text has been set')
783 * .done();
784 * ```
785 * 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).
786 *
787 * > Note: Does not work correctly in Firefox with special keys
788 *
789 * @api
790 * @method type
791 * @param {string} selector Selector of the form field to be filled
792 * @param {string} keystrokes Text to be applied to the element
793 * @chainable
794 */
795
7961Actions.prototype.type = function (selector, keystrokes) {
7970 var hash = uuid();
798
7990 if (this.querying === true) {
8000 keystrokes = selector;
8010 selector = this.selector;
802 }
803
8040 var cb = this._generateCallbackAssertion('type', 'type', selector, keystrokes, hash);
8050 this._addToActionQueue([selector, keystrokes], 'type', cb);
8060 return this;
807};
808
809/**
810 * This acts just like .type() with a key difference.
811 * This action can be used on non-input elements (useful for test site wide keyboard shortcuts and the like).
812 * So assumeing we have a keyboard shortcut that display an alert box, we could test that with something like this:
813 *
814 * ```javascript
815 * test.open('http://home.dalek.com')
816 * .sendKeys('body', '\uE00C')
817 * .assert.dialogText('press the escape key give this alert text')
818 * .done();
819 * ```
820 *
821 *
822 * > Note: Does not work correctly in Firefox with special keys
823 *
824 * @api
825 * @method sendKeys
826 * @param {string} selector Selector of the form field to be filled
827 * @param {string} keystrokes Text to be applied to the element
828 * @chainable
829 */
830
8311Actions.prototype.sendKeys = function (selector, keystrokes) {
8320 var hash = uuid();
833
8340 if (this.querying === true) {
8350 keystrokes = selector;
8360 selector = this.selector;
837 }
838
8390 var cb = this._generateCallbackAssertion('sendKeys', 'sendKeys', selector, keystrokes, hash);
8400 this._addToActionQueue([selector, keystrokes], 'sendKeys', cb);
8410 return this;
842};
843
844/**
845 * Types a text into the text input field of a prompt dialog.
846 * Like you would do when using your keyboard.
847 *
848 * ```html
849 * <div>
850 * <a id="aquestion" onclick="this.innerText = window.prompt('Your favourite companion:')">????</a>
851 * </div>
852 * ```
853 *
854 * ```javascript
855 * test.open('http://adomain.com')
856 * .click('#aquestion')
857 * .answer('Rose')
858 * .assert.text('#aquestion').is('Rose', 'Awesome she was!')
859 * .done();
860 * ```
861 *
862 *
863 * > Note: Does not work in Firefox & PhantomJS
864 *
865 * @api
866 * @method answer
867 * @param {string} keystrokes Text to be applied to the element
868 * @return chainable
869 */
870
8711Actions.prototype.answer = function (keystrokes) {
8720 var hash = uuid();
8730 var cb = this._generateCallbackAssertion('promptText', 'promptText', keystrokes, hash);
8740 this._addToActionQueue([keystrokes, hash], 'promptText', cb);
8750 return this;
876};
877
878/**
879 * Executes a JavaScript function within the browser context
880 *
881 * ```javascript
882 * test.open('http://adomain.com')
883 * .execute(function () {
884 * window.myFramework.addRow('foo');
885 * window.myFramework.addRow('bar');
886 * })
887 * .done();
888 * ```
889 *
890 * You can also apply arguments to the function
891 *
892 * ```javascript
893 * test.open('http://adomain.com')
894 * .execute(function (paramFoo, aBar) {
895 * window.myFramework.addRow(paramFoo);
896 * window.myFramework.addRow(aBar);
897 * }, 'foo', 'bar')
898 * .done();
899 * ```
900 *
901 * > Note: Buggy in Firefox
902 *
903 * @api
904 * @method execute
905 * @param {function} script JavaScript function that should be executed
906 * @return chainable
907 */
908
9091Actions.prototype.execute = function (script) {
9100 var hash = uuid();
9110 var args = [this.contextVars].concat(Array.prototype.slice.call(arguments, 1) || []);
9120 var cb = this._generateCallbackAssertion('execute', 'execute', script, args, hash);
9130 this._addToActionQueue([script, args, hash], 'execute', cb);
9140 return this;
915};
916
917/**
918 * Waits until a function returns true to process any next step.
919 *
920 * You can also set a callback on timeout using the onTimeout argument,
921 * and set the timeout using the timeout one, in milliseconds. The default timeout is set to 5000ms.
922 *
923 * ```javascript
924 * test.open('http://adomain.com')
925 * .waitFor(function () {
926 * return window.myCheck === true;
927 * })
928 * .done();
929 * ```
930 *
931 * You can also apply arguments to the function, as well as a timeout
932 *
933 * ```javascript
934 * test.open('http://adomain.com')
935 * .waitFor(function (aCheck) {
936 * return window.myThing === aCheck;
937 * }, ['arg1', 'arg2'], 10000)
938 * .done();
939 * ```
940 *
941 * > Note: Buggy in Firefox
942 *
943 * @method waitFor
944 * @param {function} fn Async function that resolves an promise when ready
945 * @param {array} args Additional arguments
946 * @param {number} timeout Timeout in miliseconds
947 * @chainable
948 * @api
949 */
950
9511Actions.prototype.waitFor = function (script, args, timeout) {
9520 var hash = uuid();
9530 timeout = timeout || 5000;
9540 args = [this.contextVars].concat(Array.prototype.slice.call(arguments, 1) || []);
9550 var cb = this._generateCallbackAssertion('waitFor', 'waitFor', script, args, timeout, hash);
9560 this._addToActionQueue([script, args, timeout, hash], 'waitFor', cb);
9570 return this;
958};
959
960/**
961 * Accepts an alert/prompt/confirm dialog. This is basically the same actions as when
962 * you are clicking okay or hitting return in one of that dialogs.
963 *
964 * ```html
965 * <div>
966 * <a id="attentione" onclick="window.alert('Alonsy!')">ALERT!ALERT!</a>
967 * </div>
968 * ```
969 *
970 * ```javascript
971 * test.open('http://adomain.com')
972 * // alert appears
973 * .click('#attentione')
974 * // alert is gone
975 * .accept()
976 * .done();
977 * ```
978 *
979 * > Note: Does not work in Firefox & PhantomJS
980 *
981 * @api
982 * @method accept
983 * @return chainable
984 */
985
9861Actions.prototype.accept = function () {
9870 var hash = uuid();
9880 var cb = this._generateCallbackAssertion('acceptAlert', 'acceptAlert', hash);
9890 this._addToActionQueue([hash], 'acceptAlert', cb);
9900 return this;
991};
992
993/**
994 * Dismisses an prompt/confirm dialog. This is basically the same actions as when
995 * you are clicking cancel in one of that dialogs.
996 *
997 * ```html
998 * <div>
999 * <a id="nonono" onclick="(this.innerText = window.confirm('No classic doctors in the 50th?') ? 'Buh!' : ':(') ">What!</a>
1000 * </div>
1001 * ```
1002 *
1003 * ```javascript
1004 * test.open('http://adomain.com')
1005 * // prompt appears
1006 * .click('#nonono')
1007 * // prompt is gone
1008 * .dismiss()
1009 * .assert.text('#nonono').is(':(', 'So sad')
1010 * .done();
1011 * ```
1012 *
1013 * > Note: Does not work in Firefox & PhantomJS
1014 *
1015 * @api
1016 * @method dismiss
1017 * @return chainable
1018 */
1019
10201Actions.prototype.dismiss = function () {
10210 var hash = uuid();
10220 var cb = this._generateCallbackAssertion('dismissAlert', 'dismissAlert', hash);
10230 this._addToActionQueue([hash], 'dismissAlert', cb);
10240 return this;
1025};
1026
1027/**
1028 * Resizes the browser window to a set of given dimensions (in px).
1029 * The default configuration of dalek opening pages is a width of 1280px
1030 * and a height of 1024px. You can specify your own default in the configuration.
1031 *
1032 * ```html
1033 * <div>
1034 * <span id="magicspan">The span in the fireplace</span>
1035 * </div>
1036 * ```
1037 *
1038 * ```css
1039 * #magicspan {
1040 * display: inline;
1041 * }
1042 *
1043 * // @media all and (max-width: 500px) and (min-width: 300px)
1044 * #magicspan {
1045 * display: none;
1046 * }
1047 * ```
1048 *
1049 * ```javascript
1050 * test.open('http://adomain.com')
1051 * .assert.visible('#magicspan', 'Big screen, visible span')
1052 * .resize({width: 400, height: 500})
1053 * .assert.notVisible('#magicspan', 'Small screen, no visible span magic!')
1054 * .done();
1055 * ```
1056 *
1057 *
1058 * > Note: Does not work in Firefox
1059 *
1060 * @api
1061 * @method resize
1062 * @param {object} dimensions Width and height as properties to apply
1063 * @chainable
1064 */
1065
10661Actions.prototype.resize = function (dimensions) {
10670 var hash = uuid();
10680 var cb = this._generateCallbackAssertion('resize', 'resize', dimensions, hash);
10690 this._addToActionQueue([dimensions, hash], 'resize', cb);
10700 return this;
1071};
1072
1073/**
1074 * Maximizes the browser window.
1075 *
1076 * ```html
1077 * <div>
1078 * <span id="magicspan">The span in the fireplace</span>
1079 * </div>
1080 * ```
1081 *
1082 * ```css
1083 * #magicspan {
1084 * display: inline;
1085 * }
1086 *
1087 * @media all and (max-width: 500px) and (min-width: 300px) {
1088 * #magicspan {
1089 * display: none;
1090 * }
1091 * }
1092 * ```
1093 *
1094 * ```javascript
1095 * test.open('http://adomain.com')
1096 * .resize({width: 400, height: 500})
1097 * .assert.notVisible('#magicspan', 'Small screen, no visible span magic!')
1098 * .maximize()
1099 * .assert.visible('#magicspan', 'Big screen, visible span')
1100 * .done();
1101 * ```
1102 *
1103 * > Note: Does not work in Firefox and PhantomJS
1104 *
1105 * @api
1106 * @method maximize
1107 * @chainable
1108 */
1109
11101Actions.prototype.maximize = function () {
11110 var hash = uuid();
11120 var cb = this._generateCallbackAssertion('maximize', 'maximize', hash);
11130 this._addToActionQueue([hash], 'maximize', cb);
11140 return this;
1115};
1116
1117/**
1118 * Sets a cookie.
1119 * More configuration options will be implemented in the future,
1120 * by now, you can only set a cookie with a specific name and contents.
1121 * This will be a domain wide set cookie.
1122 *
1123 * ```javascript
1124 * test.open('http://adomain.com')
1125 * .setCookie('my_cookie_name', 'my=content')
1126 * .done();
1127 * ```
1128 *
1129 * @api
1130 * @method setCookie
1131 * @chainable
1132 */
1133
11341Actions.prototype.setCookie = function (name, contents) {
11350 var hash = uuid();
11360 var cb = this._generateCallbackAssertion('setCookie', 'setCookie', name, contents, hash);
11370 this._addToActionQueue([name, contents, hash], 'setCookie', cb);
11380 return this;
1139};
1140
1141/**
1142 * Waits until an element matching the provided
1143 * selector expression exists in remote DOM to process any next step.
1144 *
1145 * Lets assume we have a ticker that loads its contents via AJAX,
1146 * and appends new elements, when the call has been successfully answered:
1147 *
1148 * ```javascript
1149 * test.open('http://myticker.org')
1150 * .assert.text('.ticker-element:first-child', 'First!', 'First ticker element is visible')
1151 * // now we load the next ticker element, defsult timeout is 5 seconds
1152 * .waitForElement('.ticker-element:nth-child(2)')
1153 * .assert.text('.ticker-element:nth-child(2)', 'Me snd. one', 'Snd. ticker element is visible')
1154 * // Lets assume that this AJAX call can take longer, so we raise the default timeout to 10 seconds
1155 * .waitForElement('.ticker-element:last-child', 10000)
1156 * .assert.text('.ticker-element:last-child', 'Me, third one!', 'Third ticker element is visible')
1157 * .done();
1158 * ```
1159 *
1160 * Note that the function exits succesfully when the first element is found, matching the given selector
1161 *
1162 * @api
1163 * @method waitForElement
1164 * @param {string} selector Selector that matches the element to wait for
1165 * @param {number} timeout Timeout in milliseconds
1166 * @chainable
1167 */
1168
11691Actions.prototype.waitForElement = function (selector, timeout) {
11700 var hash = uuid();
1171
11720 if (this.querying === true) {
11730 timeout = selector;
11740 selector = this.selector;
1175 }
1176
11770 var cb = this._generateCallbackAssertion('waitForElement', 'waitForElement', selector + ' : ' + timeout, hash);
11780 this._addToActionQueue([selector, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitForElement', cb);
11790 return this;
1180};
1181
1182/**
1183 * Fills the fields of a form with given values.
1184 *
1185 * ```html
1186 * <input type="text" value="not really a value" id="ijustwannahaveavalue"/>
1187 * ```
1188 *
1189 * ```javascript
1190 * test.open('http://dalekjs.com')
1191 * .setValue('#ijustwannahaveavalue', 'a value')
1192 * .assert.val('#ijustwannahaveavalue', 'a value', 'Value is changed');
1193 * ```
1194 *
1195 * @api
1196 * @method setValue
1197 * @param {string} selector
1198 * @param {string} value
1199 * @return {Actions}
1200 */
1201
12021Actions.prototype.setValue = function (selector, value) {
12030 var hash = uuid();
1204
12050 if (this.querying === true) {
12060 value = selector;
12070 selector = this.selector;
1208 }
1209
12100 var cb = this._generateCallbackAssertion('setValue', 'setValue', selector + ' : ' + value, hash);
12110 this._addToActionQueue([selector, value, hash], 'setValue', cb);
12120 return this;
1213};
1214
1215// LOG (May should live in its own module)
1216// ---------------------------------------
1217
12181Actions.prototype.logger = {};
1219
1220/**
1221 * Logs a part of the remote dom
1222 *
1223 * ```html
1224 * <body>
1225 * <div id="smth">
1226 * <input type="hidden" value="not really a value" id="ijustwannahaveavalue"/>
1227 * </div>
1228 * </body>
1229 * ```
1230 *
1231 * ```javascript
1232 * test.open('http://dalekjs.com/guineapig')
1233 * .log.dom('#smth')
1234 * .done();
1235 * ```
1236 *
1237 * Will output this:
1238 *
1239 * ```html
1240 * DOM: #smth <input type="hidden" value="not really a value" id="ijustwannahaveavalue"/>
1241 * ```
1242
1243 *
1244 * @api
1245 * @method log.dom
1246 * @param {string} selector CSS selector
1247 * @chainable
1248 */
1249
12501Actions.prototype.logger.dom = function (selector) {
12510 var hash = uuid();
1252
12530 var cb = function logDomCb (data) {
12540 if (data && data.key === 'source' && !this.uuids[data.uuid]) {
12550 this.uuids[data.uuid] = true;
12560 var $ = cheerio.load(data.value);
12570 var result = selector ? $(selector).html() : $.html();
12580 selector = selector ? selector : ' ';
12590 result = !result ? ' Not found' : result;
12600 this.reporter.emit('report:log:user', 'DOM: ' + selector + ' ' + result);
1261 }
1262 }.bind(this);
1263
12640 this._addToActionQueue([hash], 'source', cb);
12650 return this;
1266};
1267
1268/**
1269 * Logs a user defined message
1270 *
1271 * ```javascript
1272 * test.open('http://dalekjs.com/guineapig')
1273 * .execute(function () {
1274 * this.data('aKey', 'aValue');
1275 * })
1276 * .log.message(function () {
1277 * return test.data('aKey'); // outputs MESSAGE: 'aValue'
1278 * })
1279 * .done();
1280 * ```
1281 *
1282 * 'Normal' messages can be logged too:
1283 *
1284 * ```javascript
1285 * test.open('http://dalekjs.com/guineapig')
1286 * .log.message('FooBar') // outputs MESSAGE: FooBar
1287 * .done();
1288 * ```
1289 *
1290 * @api
1291 * @method log.message
1292 * @param {function|string} message
1293 * @chainable
1294 */
1295
12961Actions.prototype.logger.message = function (message) {
12970 var hash = uuid();
1298
12990 var cb = function logMessageCb (data) {
13000 if (data && data.key === 'noop' && !this.uuids[data.hash]) {
13010 this.uuids[data.hash] = true;
13020 var result = (typeof(data.value) === 'function') ? data.value.bind(this)() : data.value;
13030 this.reporter.emit('report:log:user', 'MESSAGE: ' + result);
1304 }
1305 }.bind(this);
1306
13070 this._addToActionQueue([message, hash], 'noop', cb);
13080 return this;
1309};
1310
1311/**
1312 * Generates a callback that will be fired when the action has been completed.
1313 * The callback itself will then validate the answer and will also emit an event
1314 * that the action has been successfully executed.
1315 *
1316 * @method _generateCallbackAssertion
1317 * @param {string} key Unique key of the action
1318 * @param {string} type Type of the action (normalle the actions name)
1319 * @return {function} The generated callback function
1320 * @private
1321 */
1322
13231Actions.prototype._generateCallbackAssertion = function (key, type) {
13241 var cb = function (data) {
13250 if (data && data.key === key && !this.uuids[data.uuid]) {
13260 if (!data || (data.value && data.value === null)) {
13270 data.value = '';
1328 }
1329
13300 if (key === 'execute') {
13310 Object.keys(data.value.dalek).forEach(function (key) {
13320 this.contextVars[key] = data.value.dalek[key];
1333 }.bind(this));
1334
13350 data.value.test.forEach(function (test) {
13360 this.reporter.emit('report:assertion', {
1337 success: test.ok,
1338 expected: true,
1339 value: test.ok,
1340 message: test.message,
1341 type: 'OK'
1342 });
1343
13440 this.incrementExpectations();
1345
13460 if (!test.ok) {
13470 this.incrementFailedAssertions();
1348 }
1349 }.bind(this));
1350
13510 data.value = '';
1352 }
1353
13540 this.uuids[data.uuid] = true;
13550 reporter.emit('report:action', {
1356 value: data.value,
1357 type: type,
1358 uuid: data.uuid
1359 });
1360 }
1361 }.bind(this);
13621 return cb;
1363};
1364
1365/**
1366 * Adds a method to the queue of actions/assertions to execute
1367 *
1368 * @method _addToActionQueue
1369 * @param {object} opts Options of the action to invoke
1370 * @param {string} driverMethod Name of the method to call on the driver
1371 * @param {function} A callback function that will be executed when the action has been executed
1372 * @private
1373 * @chainable
1374 */
1375
13761Actions.prototype._addToActionQueue = function (opts, driverMethod, cb) {
13774 if (driverMethod !== 'screenshot' && driverMethod !== 'imagecut') {
13780 this.screenshotParams = undefined;
1379 }
1380
13814 this.actionPromiseQueue.push(function () {
13820 var deferred = Q.defer();
1383 // add a generic identifier as the last argument to any action method call
13840 opts.push(uuid());
1385 // check the method on the driver object && the callback function
13860 if (typeof(this.driver[driverMethod]) === 'function' && typeof(cb) === 'function') {
1387 // call the method on the driver object
13880 this.driver[driverMethod].apply(this.driver, opts);
13890 deferred.resolve();
1390 } else {
13910 deferred.reject();
1392 }
1393
1394 // listen to driver message events & apply the callback argument
13950 this.driver.events.on('driver:message', cb);
13960 return deferred.promise;
1397 }.bind(this));
13984 return this;
1399};
1400
14011Actions.prototype._button = function(button) {
14020 var buttons = {LEFT: 0, MIDDLE: 1, RIGHT: 2};
1403
14040 if (button === undefined) {
14050 button = 0;
14060 } else if (typeof button !== 'number') {
14070 button = buttons[button.toUpperCase()] || 0;
1408 }
1409
14100 return button;
1411};
1412
1413// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/click
14141Actions.prototype.buttonClick = function (button) {
14150 var hash = uuid();
14160 button = this._button(button);
1417
14180 var cb = this._generateCallbackAssertion('buttonClick', 'buttonClick');
14190 this._addToActionQueue([button, hash], 'buttonClick', cb);
1420
14210 return this;
1422};
1423
1424// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/moveto
14251Actions.prototype.moveTo = function (selector, x, y) {
14260 var hash = uuid();
1427
14280 if (this.querying === true) {
14290 selector = this.selector;
1430 }
1431
14320 if (x === undefined) {
14330 x = null;
1434 }
1435
14360 if (y === undefined) {
14370 y = null;
1438 }
1439
1440 // move to coordinate
14410 var cb = this._generateCallbackAssertion('moveto', 'moveto');
14420 this._addToActionQueue([selector, x, y, hash], 'moveto', cb);
1443
14440 return this;
1445};
1446
1447/**
1448 * Close the active window and automatically selects the parent window.
1449 *
1450 * ```javascript
1451 * this.test.toWindow('test');
1452 * this.test.close();
1453 *
1454 * //you can now write your code as if the original parent window was selected because .close()
1455 * //selects that automatically for you so you don't have to call .toParentWindow() everytime
1456 * ```
1457 *
1458 * @api
1459 * @method close
1460 * @chainable
1461 */
14621Actions.prototype.close = function () {
14630 var hash = uuid();
14640 var cb = this._generateCallbackAssertion('close', 'close', hash);
14650 this._addToActionQueue([hash], 'close', cb);
1466
1467 //since the current window is now closed, make sense to automatically select the parent window since you would have to do this anyway
14680 this.toParentWindow();
1469
14700 return this;
1471};
1472
1473/**
1474 * @module DalekJS
1475 */
1476
14771module.exports = function (opts) {
14781 reporter = opts.reporter;
14791 return Actions;
1480};
1481

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

17%
408
70
338
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 you're 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 screenshot is equal to already stored image.
1209* Should follow only after screenshot action
1210*
1211* ```javascript
1212* test.open('http://google.com')
1213* .wait(500)
1214* .screenshot('test/screenshots/google.png','#lga')
1215* .assert.screenshotIsEqualTo('test/screenshots/google_etalon.png', true, 'Google Doodles')
1216* .done();
1217* ```
1218*
1219* @api
1220* @method doesntHaveTitle
1221* @param {string} expected Path to expected png image
1222* @param {boolean} do make diff image
1223* @param {string} message Message for the test reporter
1224* @chainable
1225*/
12261Assertions.prototype.screenshotIsEqualTo = function (expected, makediff, message) {
12270 if (this.test.screenshotParams) {
12280 var hash = uuid();
1229
12300 if (typeof makediff === 'string') {
12310 message = makediff;
12320 makediff = true;
12330 } else if (typeof makediff === 'undefined') {
12340 makediff = true;
1235 }
1236
12370 var cb = this._generateCallbackAssertion('imagecompare', 'compare with etalon', this._testImagecompare, hash, { expected: expected, message: message, comparisonOperator: 'Screenshot is equal to ' }).bind(this.test);
12380 this._addToActionQueue([this.test.screenshotParams, expected, makediff, hash], 'imagecompare', cb);
1239 } else {
12400 this.test.reporter.emit('error', 'Assert screenshotIsEqualTo can follow only after screenshot action!');
1241 }
1242
12430 return this.chaining ? this : this.test;
1244};
1245
1246/**
1247 * Asserts that the page’s url is as expected.
1248 *
1249 * ```javascript
1250 * test.open('http://doctorwhotv.co.uk/')
1251 * .assert.url('http://doctorwhotv.co.uk/', 'URL is as expected')
1252 * .done();
1253 * ```
1254 *
1255 * You can also check if the protocol changed,
1256 * nice to see when you open GitHub with 'http' instead of 'https'
1257 *
1258 * ```javascript
1259 * test.open('http://github.com')
1260 * .assert.url('https://github.com/', 'Changed prototcols')
1261 * .done();
1262 * ```
1263 *
1264 * Yep, using assertion helpers is also possible:
1265 *
1266 * ```javascript
1267 * test.open('http://github.com')
1268 * .assert.url().is('http://doctorwhotv.co.uk/', 'URL is as expected')
1269 * .done();
1270 * ```
1271 *
1272 * the not() helper is available too:
1273 *
1274 * ```javascript
1275 * test.open('http://doctorwhotv.co.uk/')
1276 * .assert.url().is.not('http://doctorwhotv.co.uk/', 'URL is as expected')
1277 * .done();
1278 * ```
1279 *
1280 * 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):
1281 *
1282 * ```javascript
1283 * test.open('http://doctorwhotv.co.uk/')
1284 * .assert.url().to.contain('doctor')
1285 * .done();
1286 * ```
1287 *
1288 * @api
1289 * @method url
1290 * @param {string} expected Expected test result
1291 * @param {string} message Message for the test reporter
1292 * @chainable
1293 */
1294
12951Assertions.prototype.url = function (expected, message) {
12960 var hash = uuid();
12970 var cb = this._generateCallbackAssertion('url', 'url', this._testShallowEquals, hash, {expected: expected, message: message}).bind(this.test);
12980 this._addToActionQueue([expected, hash], 'url', cb);
12990 return this.chaining ? this : this.test;
1300};
1301
1302/**
1303 * Asserts that the pages URL does not match the expectation.
1304 *
1305 * ```javascript
1306 * test.open('http://doctorwhotv.co.uk/')
1307 * .assert.doesntHaveUrl('http://doctorwhotv.co.uk/', 'URL is not expected')
1308 * .done();
1309 * ```
1310 *
1311 * Oh, you might also match for a substring with to.contain():
1312 *
1313 * * ```javascript
1314 * test.open('http://doctorwhotv.co.uk/')
1315 * .assert.doesntHaveUrl().to.contain('doctor')
1316 * .done();
1317 * ```
1318 *
1319 * @api
1320 * @method doesntHaveUrl
1321 * @param {string} expected Expected test result
1322 * @param {string} message Message for the test reporter
1323 * @chainable
1324 */
1325
13261Assertions.prototype.doesntHaveUrl = function (expected, message) {
13270 var hash = uuid();
13280 var cb = this._generateCallbackAssertion('url', '!url', this._testShallowUnequals, hash, {expected: expected, message: message}).bind(this.test);
13290 this._addToActionQueue([expected, hash], 'url', cb);
13300 return this.chaining ? this : this.test;
1331};
1332
1333/**
1334 * Asserts that an elements attribute is as expected.
1335 *
1336 * ```html
1337 * <form>
1338 * <button class="jumpButton" type="submit">Fire</button>
1339 * </form>
1340 * ```
1341 *
1342 * ```javascript
1343 * test
1344 * .open('http://dalekjs.com/guineapig/')
1345 * .assert.attr('.jumpButton', 'type', 'submit')
1346 * .done();
1347 * ```
1348 *
1349 * ```html
1350 * <div class="wellImUpperUpperClassHighSociety" id="dataDiv" data-spot="cat"></div>
1351 * ```
1352 *
1353 * ```javascript
1354 * test
1355 * .open('http://dalekjs.com/guineapig/')
1356 * .assert.attr('#dataDiv').is('data-spot', 'cat', 'We found Dataʼs cat!')
1357 * .done();
1358 * ```
1359 *
1360 * ```javascript
1361 * test
1362 * .open('http://dalekjs.com/guineapig/')
1363 * .assert.attr('#dataDiv').is.not('data-spot', 'doc', 'Spot is not a dog!')
1364 * .done();
1365 * ```
1366 *
1367 * You can also use attr() for checking if a class is existent
1368 *
1369 * ```javascript
1370 * test
1371 * .open('http://dalekjs.com/guineapig/')
1372 * .assert.attr('#dataDiv', 'class', 'wellImUpperUpperClassHighSociety')
1373 * .done();
1374 * ```
1375 *
1376 * and you can also match a substring (e. g. a single class if more classes are on that elem) with to.contain():
1377 *
1378 * ```javascript
1379 * test
1380 * .open('http://dalekjs.com/guineapig/')
1381 * .assert.attr('#dataDiv', 'class').to.contain('upperUpperClass')
1382 * .done();
1383 * ```
1384 *
1385 * @api
1386 * @method attr
1387 * @param {string} selector Selector that matches the elements to test
1388 * @param {string} attribute The attribute to test
1389 * @param {string} expected Expected test result
1390 * @param {string} message Message for the test reporter
1391 * @chainable
1392 */
1393
13941Assertions.prototype.attr = function (selector, attribute, expected, message) {
13950 var hash = uuid();
1396
13970 if (this.test.querying === true) {
13980 message = expected;
13990 expected = attribute;
14000 attribute = selector;
14010 selector = this.test.selector;
1402 }
1403
14040 var cb = this._generateCallbackAssertion('attribute', 'attribute', this._testShallowEquals, hash, {expected: expected, message: message, selector: selector, attribute: attribute}).bind(this.test);
14050 this._addToActionQueue([selector, attribute, expected, hash], 'attribute', cb);
14060 return this.chaining ? this : this.test;
1407};
1408
1409
1410/**
1411* Asserts that an element contains class.
1412*
1413* ```html
1414* <form>
1415* <button class="plain-btn action submit-product" type="submit">Fire</button>
1416* </form>
1417* ```
1418*
1419* ```javascript
1420* test
1421* .open('http://dalekjs.com/index.html')
1422* .assert.hasClass('.grid__item.one-whole.info-box', 'info-box', 'div has info-box class')
1423* .done();
1424* ```
1425*
1426* @api
1427* @method attr
1428* @param {string} selector Selector that matches the elements to test
1429* @param {string} expected Expected test result
1430* @param {string} message Message for the test reporter
1431* @chainable
1432*/
1433
14341Assertions.prototype.hasClass = function (selector, expected, message) {
14350 var hash = uuid();
14360 var attribute = 'class';
1437
14380 if (this.test.querying === true) {
14390 message = expected;
14400 expected = selector;
14410 selector = this.test.selector;
1442 }
1443
14440 var cb = this._generateCallbackAssertion('attribute', 'hasClass', this._containsItem, hash, {expected: expected, message: message, selector: selector, attribute: attribute}).bind(this.test);
14450 this._addToActionQueue([selector, attribute, expected, hash], 'attribute', cb);
14460 return this.chaining ? this : this.test;
1447};
1448
1449
1450/**
1451* Asserts that element doesn't contain class.
1452*
1453* ```html
1454* <form>
1455* <button class="plain-btn action submit-product" type="submit">Fire</button>
1456* </form>
1457* ```
1458*
1459* ```javascript
1460* test
1461* .open('http://dalekjs.com/index.html')
1462* .assert.doesntHaveClass('.grid__item.one-whole.info-box', 'info', 'div doesn\'t have info class')
1463* .done();
1464* ```
1465*
1466* @api
1467* @method attr
1468* @param {string} selector Selector that matches the elements to test
1469* @param {string} expected Expected test result
1470* @param {string} message Message for the test reporter
1471* @chainable
1472*/
1473
14741Assertions.prototype.doesntHaveClass = function (selector, expected, message) {
14750 var hash = uuid();
14760 var attribute = 'class';
1477
14780 if (this.test.querying === true) {
14790 message = expected;
14800 expected = selector;
14810 selector = this.test.selector;
1482 }
1483
14840 var cb = this._generateCallbackAssertion('attribute', '!hasClass', this._doesntContainItem, hash, {expected: expected, message: message, selector: selector, attribute: attribute}).bind(this.test);
14850 this._addToActionQueue([selector, attribute, expected, hash], 'attribute', cb);
14860 return this.chaining ? this : this.test;
1487};
1488
1489/**
1490Assert result of the execution of JavaScript function within the browser context.
1491
1492 test
1493 .open('http://dalekjs.com/index.html')
1494 .assert.evaluate(function () {
1495 return document.getElementsByClassName('grid').length;
1496 }).is(2, 'Count of grid on page is equal 2')
1497 .done();
1498
1499* > Note: Buggy in Firefox
1500*
1501* @api
1502* @method execute
1503* @param {function} script JavaScript function that should be executed
1504* @return chainable
1505*/
1506
15071Assertions.prototype.evaluate = function (script) {
15080 var hash = uuid();
15090 var args = [this.contextVars].concat(Array.prototype.slice.call(arguments, 1) || []);
1510
15110 var cb = this._generateCallbackAssertion('evaluate', 'evaluate', this._testTruthy, hash, {script: script, args: args, has:hash}).bind(this.test);
15120 this._addToActionQueue([script, args, hash], 'evaluate', cb);
15130 return this.chaining ? this : this.test;
1514};
1515
1516
1517// TEST HELPER
1518// -----------
1519
1520/**
1521 * Is helper
1522 *
1523 * @method is
1524 * @param {mixed} expected Value to check
1525 * @param {string} message Test message
1526 * @chainable
1527 */
1528
15291Assertions.prototype.is = function (expected, message) {
15300 return this.generateTestHelper('is', '_testShallowEquals', false)(expected, message);
1531};
1532
1533/**
1534 * Not helper
1535 *
1536 * @method not
1537 * @param {mixed} expected Value to check
1538 * @param {string} message Test message
1539 * @chainable
1540 */
1541
15421Assertions.prototype.not = function (expected, message) {
15430 return this.generateTestHelper('not', '_testShallowEquals', true)(expected, message);
1544};
1545
1546/**
1547 * Between helper
1548 *
1549 * @method between
1550 * @param {mixed} expected Value to check
1551 * @param {string} message Test message
1552 * @chainable
1553 */
1554
15551Assertions.prototype.between = function (expected, message) {
15560 return this.generateTestHelper('between', '_testBetween', false)(expected, message);
1557};
1558
1559/**
1560 * Gt helper
1561 *
1562 * @method gt
1563 * @param {mixed} expected Value to check
1564 * @param {string} message Test message
1565 * @chainable
1566 */
1567
15681Assertions.prototype.gt = function (expected, message) {
15690 return this.generateTestHelper('gt', '_testGreaterThan', false)(expected, message);
1570};
1571
1572/**
1573 * Gte helper
1574 *
1575 * @method gte
1576 * @param {mixed} expected Value to check
1577 * @param {string} message Test message
1578 * @chainable
1579 */
1580
15811Assertions.prototype.gte = function (expected, message) {
15820 return this.generateTestHelper('gte', '_testGreaterThanEqual', false)(expected, message);
1583};
1584
1585/**
1586 * Lt helper
1587 *
1588 * @method lt
1589 * @param {mixed} expected Value to check
1590 * @param {string} message Test message
1591 * @chainable
1592 */
1593
15941Assertions.prototype.lt = function (expected, message) {
15950 return this.generateTestHelper('lt', '_testLowerThan', false)(expected, message);
1596};
1597
1598/**
1599 * Lte helper
1600 *
1601 * @method lte
1602 * @param {mixed} expected Value to check
1603 * @param {string} message Test message
1604 * @chainable
1605 */
1606
16071Assertions.prototype.lte = function (expected, message) {
16080 return this.generateTestHelper('lte', '_testLowerThanEqual', false)(expected, message);
1609};
1610
1611/**
1612* Assert if a given value contians a second given value
1613*
1614* @method _containsItem
1615* @param {mixed} a Value to test
1616* @param {mixed} b Value to test
1617* @return {bool} false if values doesn't contain, true if contains
1618* @private
1619*/
1620
16211Assertions.prototype._containsItem = function (a, b) {
16220 a = a || '';
16230 var index = a.split(' ').indexOf(b);
1624
16250 try {
16260 chai.expect(index).to.be.above(-1);
1627 } catch (e) {
16280 return false;
1629 }
1630
16310 return true;
1632};
1633
1634/**
1635* Assert if a given value doesn't contian a second given value
1636*
1637* @method _containsItem
1638* @param {mixed} a Value to test
1639* @param {mixed} b Value to test
1640* @return {bool} false if values contains, true if don't contain
1641* @private
1642*/
16431Assertions.prototype._doesntContainItem = function (a, b) {
16440 a = a || '';
16450 var index = a.split(' ').indexOf(b);
16460 try {
16470 chai.assert.equal(index, -1);
1648 } catch (e) {
16490 return false;
1650 }
1651
16520 return true;
1653};
1654
1655/**
1656 * Contain helper
1657 *
1658 * @method contain
1659 * @param {mixed} expected Value to check
1660 * @param {string} message Test message
1661 * @chainable
1662 */
1663
16641Assertions.prototype.contain = function (expected, message) {
16650 return this.generateTestHelper('contain', '_contain', false)(expected, message);
1666};
1667
1668/**
1669 * Not contain helper
1670 *
1671 * @method notContain
1672 * @param {mixed} expected Value to check
1673 * @param {string} message Test message
1674 * @chainable
1675 */
1676
16771Assertions.prototype.notContain = function (expected, message) {
16780 return this.generateTestHelper('notContain', '_notContain', false)(expected, message);
1679};
1680
1681/**
1682 * Match helper
1683 *
1684 * @method match
1685 * @param {string} expected Regex to match on
1686 * @param {string} message Test message
1687 * @chainable
1688 */
1689
16901Assertions.prototype.match = function (expected, message) {
16910 return this.generateTestHelper('match', '_match', false)(expected, message);
1692};
1693
1694/**
1695 * Equals case insensitive helper
1696 *
1697 * @method equalsCaseInsensitive
1698 * @param {mixed} expected Value to check
1699 * @param {string} message Test message
1700 * @chainable
1701 */
1702
17031Assertions.prototype.equalsCaseInsensitive = function (expected, message) {
17040 return this.generateTestHelper('equalsCaseInsensitive', '_caseInsensitiveMatch', false)(expected, message);
1705};
1706
1707// HELPER METHODS
1708// --------------
1709
1710/**
1711 * Generates a callback that will be fired when the action has been completed.
1712 * The callback itself will then validate the answer and will also emit an event
1713 * that the action has been successfully executed.
1714 *
1715 * @method _generateCallbackAssertion
1716 * @param {string} key Unique key of the action
1717 * @param {string} type Type of the action (usually the actions name)
1718 * @param {string} test test method to be used
1719 * @param {string} hash the uuid
1720 * @param {object} opts the options object
1721 * @return {function} The generated callback function
1722 * @private
1723 */
1724
17251Assertions.prototype._generateCallbackAssertion = function (key, type, test, hash, opts) {
17260 var cb = function (data) {
17270 if (data && data.key === key && data.hash === hash) {
1728
17290 this._lastGeneratedAction = {key: key, type: type, test: test, hash: hash, opts: opts, data: data};
1730
17310 var chainingKeys = ['title', 'width', 'height', 'url', 'text' , 'attribute', 'numberOfElements', 'numberOfVisibleElements', 'evaluate'];
17320 if (!opts.expected && chainingKeys.indexOf(key) > -1) {
17330 return false;
1734 }
1735
17360 if (typeof opts.expected === 'function') {
17370 opts.expected = opts.expected();
1738 }
1739
17400 var testResult = test(data.value, opts.expected, opts.parseFloatOnValues);
17410 this.reporter.emit('report:assertion', {
1742 success: testResult,
1743 expected: opts.comparisonOperator ? opts.comparisonOperator + opts.expected : opts.expected,
1744 value: data.value,
1745 message: opts.message,
1746 type: type,
1747 selector: data.selector
1748 });
1749
17500 this.incrementExpectations();
17510 if (!testResult) {
17520 this.incrementFailedAssertions();
1753 }
1754 }
1755 };
17560 return cb;
1757};
1758
1759/**
1760 * Adds a method to the queue of actions/assertions to execute
1761 *
1762 * @method _addToActionQueue
1763 * @param {object} opts Options of the action to invoke
1764 * @param {string} driverMethod Name of the method to call on the driver
1765 * @param {function} A callback function that will be executed when the action has been executed
1766 * @private
1767 * @chainable
1768 */
1769
17701Assertions.prototype._addToActionQueue = function (opts, driverMethod, cb) {
17710 this._lastGeneratedShit = {opts: opts, driverMethod: driverMethod};
17720 this.test.actionPromiseQueue.push(function () {
17730 var deferredAction = Q.defer();
17740 this.test.driver[driverMethod].apply(this.test.driver, opts);
17750 deferredAction.resolve();
17760 this.test.driver.events.on('driver:message', cb);
17770 return deferredAction.promise;
1778 }.bind(this));
17790 return this;
1780};
1781
1782/**
1783 * Generates a function that can be used
1784 *
1785 * @method generateTestHelper
1786 * @param name
1787 * @param assertionFn
1788 * @param negate
1789 * @return
1790 * @private
1791 */
1792
17931Assertions.prototype.generateTestHelper = function (name, assertionFn, negate) {
17940 return function (expected, message) {
17950 var gen = this._lastGeneratedShit;
1796
17970 this.test.actionPromiseQueue.push(function () {
17980 var deferredAction = Q.defer();
17990 deferredAction.resolve();
18000 this.test.driver.events.on('driver:message', function () {
1801
18020 if (gen.opts && gen.opts[(gen.opts.length - 1)] && this.test._lastGeneratedAction && this.test._lastGeneratedAction.hash) {
18030 if (gen.opts[(gen.opts.length - 1)] === this.test._lastGeneratedAction.hash && !this.proceeded[this.test._lastGeneratedAction.hash + name]) {
18040 var testResult = this[assertionFn](expected, this.test._lastGeneratedAction.data.value);
1805
18060 if (negate) {
18070 testResult = !testResult;
1808 }
1809
18100 this.proceeded[this.test._lastGeneratedAction.hash + name] = true;
1811
18120 this.test.reporter.emit('report:assertion', {
1813 success: testResult,
1814 expected: expected,
1815 value: this.test._lastGeneratedAction.data.value,
1816 message: message,
1817 type: this.test._lastGeneratedAction.type
1818 });
1819
18200 this.test.incrementExpectations();
1821
18220 if (!testResult) {
18230 this.test.incrementFailedAssertions();
1824 }
1825 }
1826 }
1827 }.bind(this));
18280 return deferredAction.promise;
1829 }.bind(this));
1830
18310 return this.chaining ? this : this.test;
1832 }.bind(this);
1833};
1834
1835// ASSERT METHODS
1836// --------------
1837
1838/**
1839 * Assert if a given value shallow equals a second given value
1840 *
1841 * @method _testShallowEquals
1842 * @param {mixed} a Value to test
1843 * @param {mixed} b Value to test
1844 * @return {bool} false if values donʼt match, true if they match
1845 * @private
1846 */
1847
18481Assertions.prototype._testShallowEquals = function (a, b) {
18490 try {
18500 chai.assert.equal(a, b);
1851 } catch (e) {
18520 return false;
1853 }
1854
18550 return true;
1856};
1857
1858/**
1859 * Assert if a given value shallow does not equal a second given value
1860 *
1861 * @method _testShallowUnequals
1862 * @param {mixed} a Value to test
1863 * @param {mixed} b Value to test
1864 * @return {bool} true if values donʼt match, false if they match
1865 * @private
1866 */
1867
18681Assertions.prototype._testShallowUnequals = function (a, b) {
18690 try {
18700 chai.assert.notEqual(a, b);
1871 } catch (e) {
18720 return false;
1873 }
1874
18750 return true;
1876};
1877
1878/**
1879 * Assert if a given value matches a range
1880 *
1881 * @method _testBetween
1882 * @param {array} a Range to test
1883 * @param {bool} b Value to compare
1884 * @return {bool} test result
1885 * @private
1886 */
1887
18881Assertions.prototype._testBetween = function (a, b) {
18890 try {
18900 chai.expect(b).to.be.within(a[0], a[1]);
1891 } catch (e) {
18920 return false;
1893 }
1894
18950 return true;
1896};
1897
1898/**
1899 * Assert if a given value is greater than the value to compare
1900 *
1901 * @method _testGreaterThan
1902 * @param {bool} a Value to test
1903 * @param {bool} b Value to compare
1904 * @param {parseFloatOnValues} b Whether to apply parseFloat to both values prior comparision
1905 * @return {bool} test result
1906 * @private
1907 */
1908
19091Assertions.prototype._testGreaterThan = function (a, b, parseFloatOnValues) {
19100 if (parseFloatOnValues) {
19110 a = parseFloat(a);
19120 b = parseFloat(b);
1913 }
1914
19150 try {
19160 chai.expect(b).to.be.above(a);
1917 } catch (e) {
19180 return false;
1919 }
1920
19210 return true;
1922};
1923
1924/**
1925 * Assert if a given value is greater or equal than the value to compare
1926 *
1927 * @method _testGreaterThanEqual
1928 * @param {bool} a Value to test
1929 * @param {bool} b Value to compare
1930 * @return {bool} test result
1931 * @private
1932 */
1933
19341Assertions.prototype._testGreaterThanEqual = function (a, b) {
19350 return this._testGreaterThan(a - 1, b);
1936};
1937
1938/**
1939 * Assert if a given value is lower than the value to compare
1940 *
1941 * @method _testLowerThan
1942 * @param {bool} a Value to test
1943 * @param {bool} b Value to compare
1944 * @param {parseFloatOnValues} b Whether to apply parseFloatOnValues to both values prior comparision
1945 * @return {bool} test result
1946 * @private
1947 */
1948
19491Assertions.prototype._testLowerThan = function (a, b, parseFloatOnValues) {
19500 if (parseFloatOnValues) {
19510 a = parseFloat(a);
19520 b = parseFloat(b);
1953 }
1954
19550 try {
19560 chai.expect(b).to.be.below(a);
1957 } catch (e) {
19580 return false;
1959 }
1960
19610 return true;
1962};
1963
1964/**
1965 * Asserts that 2 string match regardless of case
1966 *
1967 * @method _caseInsensitiveMatch
1968 * @param {string} a Value to test
1969 * @param {string} b Value to compare
1970 * @return {bool} testresult
1971 * @private
1972 */
1973
19741Assertions.prototype._caseInsensitiveMatch = function (a, b) {
19750 try {
19760 if(a.toLowerCase && b.toLowerCase) {
19770 chai.expect(b.toLowerCase()).to.eql(a.toLowerCase());
1978 } else {
19790 return false;
1980 }
1981 } catch (e) {
19820 return false;
1983 }
1984
19850 return true;
1986};
1987
1988/**
1989 * Assert if a given value contain another value
1990 *
1991 * @method _contain
1992 * @param {bool} a Value to test
1993 * @param {bool} b Value to compare
1994 * @return {bool} test result
1995 * @private
1996 */
1997
19981Assertions.prototype._contain = function (a, b) {
19990 try {
20000 chai.expect(b).to.include(a);
2001 } catch (e) {
20020 return false;
2003 }
2004
20050 return true;
2006};
2007
2008/**
2009 * Assert if a given value doesn't contain another value
2010 *
2011 * @method _notContain
2012 * @param {bool} a Value to test
2013 * @param {bool} b Value to compare
2014 * @return {bool} test result
2015 * @private
2016 */
20171Assertions.prototype._notContain = function (a, b) {
20180 try {
20190 chai.expect(b).to.not.include(a);
2020 } catch (e) {
20210 return false;
2022 }
2023
20240 return true;
2025};
2026
2027/**
2028 * Assert if a given value is lower or equal than the value to compare
2029 *
2030 * @method _testLowerThanEqual
2031 * @param {bool} a Value to test
2032 * @param {bool} b Value to compare
2033 * @return {bool} test result
2034 * @private
2035 */
2036
20371Assertions.prototype._testLowerThanEqual = function (a, b) {
20380 return this._testLowerThan(a + 1, b);
2039};
2040
2041/**
2042 * Assert if a given value is boolean 'true'
2043 *
2044 * @method _testTruthy
2045 * @param {bool} a Value to test
2046 * @return {bool} false if value is false, true if value is true
2047 * @private
2048 */
2049
20501Assertions.prototype._testTruthy = function (a) {
20510 return a === 'true' || a === true;
2052};
2053
2054/**
2055 * Assert if a given value is boolean 'false'
2056 *
2057 * @method _testFalsy
2058 * @param {bool} a Value to test
2059 * @return {bool} true if value is false, false if value is true
2060 * @private
2061 */
2062
20631Assertions.prototype._testFalsy = function (a) {
20640 return a === 'false' || a === false;
2065};
2066
2067/**
2068 * Assert a given value matches a RegEx
2069 *
2070 * @method _contain
2071 * @param {mixed} a Value to test
2072 * @param {string} b Value to compare
2073 * @return {bool} test result
2074 * @private
2075 */
2076
20771Assertions.prototype._match = function (a, b) {
20780 try {
20790 chai.expect(b).to.match(a);
2080 } catch (e) {
20810 return false;
2082 }
2083
20840 return true;
2085};
2086
2087
2088/**
2089 * Assert if a image compare result is equal
2090 *
2091 * @method _testImagecompare
2092 * @param {string} a Value to test if it equal to 'equal'
2093 * @return {bool} true if value is 'equal', false if value has some other values
2094 * @private
2095 */
2096
20971Assertions.prototype._testImagecompare = function (a) {
20980 return a === 'equal';
2099};
2100

/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

18%
86
16
70
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 && driverInstance.webdriverClient.opts.kill) {
3540 this.driverEmitter.on('killAll', driverInstance.webdriverClient.opts.kill.bind(driverInstance.webdriverClient.opts));
355 }
356
3570 if (driverInstance.webdriverClient.quit) {
3580 this.driverEmitter.on('killAll', driverInstance.webdriverClient.quit.bind(driverInstance.webdriverClient));
359 }
360
361 // dispatch some (web)driver events to the reporter
3620 this.driverEmitter.on('driver:webdriver:response', function (res) {
3630 this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.statusCode + ' ' + res.method + ' ' + res.path);
3640 this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.data);
365 }.bind(this));
366
367 // run the tests in the browser, when the driver is ready
368 // emit the tests:complete event, when all tests have been run
3690 this.driverEmitter.on('driver:ready:' + driverName + ':' + browser, this._onDriverReady.bind(this, browser, driverName, callback, driverInstance));
3700 return this;
371 }
372};
373
374// export driver module
3751module.exports = Driver;
376

/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

7%
102
8
94
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!' + e;
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) {
1360 var complete = function() {
137 // check if there are still tests that should be executed in this suite,
138 // if so, run them
1390 if (this.decrementTestsToBeExecuted() > 1) {
1400 this.executeNextTest(tests);
1410 return this;
142 }
143
144 // run a function after the testsuite, if given
1450 if (this.options.teardown) {
1460 this.options.teardown();
147 }
148
149 // emit the suite finished event
1500 this.reporterEmitter.emit('report:testsuite:finished', this.name);
151
152 // move on to the next suite
1530 callback();
1540 return this;
155 }.bind(this);
156
157 // run a function after the test, if given
158
1590 if (typeof this.options.afterEach === 'function') {
160 // If there is an argument, assume async.
1610 if (this.options.afterEach.length === 1) {
1620 this.options.afterEach(function() {
1630 return complete();
164 }.bind(this));
165 } else {
166 // no argument, assume sync.
1670 this.options.afterEach();
1680 return complete();
169 }
170 } else {
1710 return complete();
172 }
173
174 },
175
176 /**
177 * Decrements number of tests that should be executed in this suite
178 *
179 * @method decrementTestsToBeExecuted
180 * @return {integer} numberOfTestsToBeExecuted
181 */
182
183 decrementTestsToBeExecuted: function () {
1840 return (this.testsToBeExecuted--) -1;
185 },
186
187 /**
188 * Returns the name of the testsuite
189 * If the suite has no name, it will return the testsuites filename
190 *
191 * @method getName
192 * @return {string} name
193 */
194
195 getName: function () {
1960 if (this.suite.name && _.isString(this.suite.name)) {
1970 var name = this.suite.name;
1980 delete this.suite.name;
1990 return name;
200 }
201
2020 return this.name;
203 },
204
205 /**
206 * Returns the options of the testsuite
207 * If the suite has no options, it will return an empty object
208 *
209 * @method getOptions
210 * @return {object} options Suite options
211 */
212
213 getOptions: function () {
2140 if (this.suite.options && _.isObject(this.suite.options)) {
2150 var options = this.suite.options;
2160 delete this.suite.options;
2170 return options;
218 }
219
2200 return {};
221 },
222
223 /**
224 * Returns all names (aka. object keys) the tests that should be executed
225 *
226 * @method getTests
227 * @return {array} test
228 */
229
230 getTests: function () {
2310 return Object.keys(this.suite);
232 },
233
234 /**
235 * Returns the number of tests to be executed
236 *
237 * @method getNumberOfTests
238 * @param {array} tests
239 * @return {integer} numberOfTests
240 */
241
242 getNumberOfTests: function (tests) {
2430 return tests.length;
244 },
245
246 /**
247 * Returns the next test, that should be executed
248 *
249 * @method getNextTest
250 * @return {string} testName
251 */
252
253 getNextTest: function (tests) {
2540 return tests.shift();
255 },
256
257 /**
258 * Executes the next test in the sequence
259 *
260 * @method executeNextTest
261 * @param {array} tests
262 * @return {mixed} testValue
263 */
264
265 executeNextTest: function (tests, callback) {
2660 var cb = callback || function() {};
267 // grab the next test in the queue
2680 var testName = this.getNextTest(tests);
269 // get the next test function
2700 var testFunction = this.getTest(testName);
271 // generate an instance of the test
2720 var test = this.getTestInstance(testName);
273 // run a setup function before the test, if given
2740 if (typeof this.options.beforeEach !== 'function') {
2750 cb(null, null);
2760 testFunction.apply(test,[test]);
2770 return this;
278 }
2790 if (this.options.beforeEach.length === 1) {
280 // if function takes an argument, assume async
2810 this.options.beforeEach(function() {
282 // start it
2830 testFunction.apply(test,[test]);
2840 cb(null, null);
285 });
286 } else {
287 // otherwise, assume sync
2880 this.options.beforeEach();
2890 testFunction.apply(test,[test]);
2900 cb(null, null);
291 }
2920 return this;
293 },
294
295 /**
296 * Generates a new test instance
297 *
298 * @method getTestInstance
299 * @param {string} name
300 * @return {Dalek.Test} test
301 */
302
303 getTestInstance: function (name) {
3040 return unit({events: this.emitter, driver: this.driver, reporter: this.reporterEmitter, name: name});
305 },
306
307 /**
308 * Returns a test function by its name
309 *
310 * @method getTest
311 * @param {string} name
312 * @return {function} test
313 */
314
315 getTest: function (name) {
3160 return this.suite[name] && _.isFunction(this.suite[name]) ? this.suite[name] : this.testDoesNotExist;
317 },
318
319 /**
320 * Will be executed if a test is started, that does not exist
321 *
322 * @method testDoesNotExist
323 * @param {object} options
324 */
325
326 testDoesNotExist: function (options) {
3270 if (options.name) {
3280 this.reporterEmitter.emit('warning', 'Test "' + options.name + '" does not exist! Skipping.');
329 }
3300 return this;
331 },
332
333 /**
334 * Runs any tests from this testsuite in sequence
335 *
336 * @method run
337 * @param {function} callback
338 * @chainable
339 */
340
341 run: function (callback) {
3420 var tests = [];
343
344 // check if the suite is
3450 if (this.error) {
3460 this.reporterEmitter.emit('report:testsuite:started', null);
347 // emit a warning notice
3480 this.reporterEmitter.emit('warning', this.error);
349 // emit the suite finished event
3500 this.reporterEmitter.emit('report:testsuite:finished', null);
351 // move on to the next suite
3520 callback();
353 }
354
355 // extract suite name
3560 this.name = this.getName();
357 // extract suite options
3580 this.options = this.getOptions();
359
360 // extract tests
3610 tests = this.getTests();
3620 this.testsToBeExecuted = this.numberOfTests = this.getNumberOfTests(tests);
363
364 // run a function before the testsuite has been launched, if given
3650 if (this.options.setup) {
3660 this.options.setup();
367 }
368
369 // kickstart the test execution
3700 this.executeNextTest(tests, function() {
371 // emit the suite started event
3720 this.reporterEmitter.emit('report:testsuite:started', this.name);
373 // listen to the test:finished event & then start the next test
374 // if there are no tests in this suite left,
375 // run the async callback & mark this suite as finished
3760 this.emitter.onAny(this.testFinished.bind(this, callback, tests));
377 }.bind(this));
378
3790 return this;
380 }
381};
382
383// export the testuite instance
3841module.exports = Suite;
385

/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%
117
10
107
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// Default timeout for calling done
361var DONE_TIMEOUT = 10000;
37
38/**
39 * Prepares the test instance values
40 *
41 * @param {object} opts Options like the tests name, etc.
42 * @constructor
43 */
44
451var Unit = function (opts) {
46 // prepare meta queue data
470 this.actionPromiseQueue = [];
48
49 // prepare assertion data
500 this.expectation = null;
510 this.runnedExpactations = 0;
520 this.failedAssertions = 0;
53
54 // prepare test specific data
550 this.name = opts.name;
560 this.lastChain = [];
570 this.uuids = {};
580 this.contextVars = {};
59
600 if (this.name) {
610 this.timeoutForDone = setTimeout(function () {
620 this.done();
630 this.reporter.emit('warning', 'done() not called before timeout!');
64 }.bind(this), DONE_TIMEOUT);
65 }
66};
67
68/**
69 * Generates an test instance
70 *
71 * @module DalekJS
72 * @class Unit
73 * @namespace Dalek
74 * @part test
75 * @api
76 */
77
781Unit.prototype = {
79
80 /**
81 * Specify how many assertions are expected to run within a test.
82 * Very useful for ensuring that all your callbacks and assertions are run.
83 *
84 * @method expect
85 * @param {Integer} expecatation Number of assertions that should be run
86 * @chainable
87 */
88
89 expect: function (expectation) {
900 this.expectation = parseInt(expectation, 10);
910 return this;
92 },
93
94 /**
95 * Global data store (works between the node & browser envs)
96 *
97 * @method data
98 * @param {string|number} key Key to store or fetch data
99 * @param {mixed} value *optional* Data that should be stored
100 * @return {mixed} Data that has been stored
101 * @chainable
102 */
103
104 data: function (key, value) {
1050 if (value) {
1060 this.contextVars[key] = value;
1070 return this;
108 }
109
1100 return this.contextVars[key];
111 },
112
113 /**
114 * Increment the number of executed assertions
115 *
116 * @method incrementExpectations
117 * @chainable
118 */
119
120 incrementExpectations: function () {
1210 this.runnedExpactations++;
1220 return this;
123 },
124
125 /**
126 * Increment the number of failed assertions
127 *
128 * @method incrementFailedAssertions
129 * @chainable
130 */
131
132 incrementFailedAssertions: function () {
1330 this.failedAssertions++;
1340 return this;
135 },
136
137 /**
138 * Checks if the runned tests fullfill the set expectations
139 * or if no expectations were raised
140 *
141 * @method checkExpectations
142 * @return {bool} checkedExpectations Expectations match
143 */
144
145 checkExpectations: function () {
1460 return (this.expectation === null || !this.expectation || (this.runnedExpactations === this.expectation));
147 },
148
149 /**
150 * Checks if all runned assertions passed
151 *
152 * @method checkAssertions
153 * @return {bool} assertionFailed Any expectation failed
154 */
155
156 checkAssertions: function () {
1570 return this.failedAssertions === 0;
158 },
159
160 /**
161 * Sets up all the bindings needed for a test to run
162 *
163 * @method done
164 * @return {object} result A promise
165 * @private
166 */
167
168 done: function () {
1690 var result = Q.resolve();
170 // clear the done error timeout
1710 clearTimeout(this.timeoutForDone);
172 // remove all previously attached event listeners to clear the message queue
1730 this.driver.events.removeAllListeners('driver:message');
174 // resolve the deferred when the test is finished
1750 Unit.testStarted.fin(this._testFinished.bind(this, result));
1760 return result;
177 },
178
179 /**
180 * Allow to use custom functions in order to embrace code reuse across
181 * multiple files (for example for use in Page Objects).
182 *
183 * @method andThen
184 * @param {function} a function, where 'this' references the test
185 * @chainable
186 */
187 andThen: function(func) {
1880 return func.call(this);
189 },
190
191 /**
192 * Adds a node style function (with node err callback) style to the test.
193 *
194 * @method node
195 * @param {function} a node function that is executed in the context of the test.
196 * @chainable
197 */
198 node: function(nodeFunction) {
1990 var deferred = Q.defer(),
200 that = this;
2010 nodeFunction.call(this, function(err) {
2020 if (typeof err !== 'undefined') {
2030 that.reporter.emit('error', err);
2040 that.incrementFailedAssertions();
2050 deferred.reject();
206 } else {
2070 deferred.resolve();
208 }
209 });
2100 this.promise(deferred);
2110 return this;
212 },
213
214 /**
215 * Adds a promise to the chain of tests.
216 *
217 * @method promise
218 * @param {promise} a q promise
219 * @chainable
220 */
221 promise: function(deferred) {
2220 this.actionPromiseQueue.push(deferred);
2230 return this;
224 },
225
226 /**
227 * Emits the test finished events & resolves all promises
228 * when its done
229 *
230 * @method _testFinished
231 * @param {object} result Promised result var
232 * @return {object} result Promised result var
233 * @private
234 */
235
236 _testFinished: function (result) {
237 // add a last deferred function on the end of the action queue,
238 // to tell that this test is finished
2390 this.actionPromiseQueue.push(this._testFin.bind(this));
240
241 // initialize all of the event receiver functions,
242 // that later take the driver result
2430 this.actionPromiseQueue.forEach(function (f) {
2440 result = result.then(f).fail(function () {
2450 console.error(arguments);
2460 process.exit(0);
247 });
248 }.bind(this));
249
250 // run the driver when all actions are stored in the queue
2510 Q.allSettled(this.actionPromiseQueue)
252 .then(this.driver.run.bind(this.driver));
253
2540 return result;
255 },
256
257 /**
258 * Emits the test started event
259 *
260 * @method _reportTestStarted
261 * @param {string} name Name of the test
262 * @chainable
263 * @private
264 */
265
266 _reportTestStarted: function (name) {
2670 this.reporter.emit('report:test:started', {name: name});
2680 return this;
269 },
270
271 /**
272 * Checks if the test run is complete & emits/resolves
273 * all the needed events/promises when the run is complete
274 *
275 * @method _onDriverMessage
276 * @param {object} data Data that is returned by the driver:message event
277 * @chainable
278 * @private
279 */
280
281 _onDriverMessage: function (data) {
282 // check if the test run is complete
2830 if (data && data.key === 'run.complete') {
284 // emit the test finish events & resolve the deferred
2850 this._emitConcreteTestFinished();
2860 this._emitAssertionStatus();
2870 this._emitTestFinished();
2880 this.deferred.resolve();
289 }
290
2910 return this;
292 },
293
294 /**
295 * Emits an event, that the current test run has been finished
296 *
297 * @method _emitConcreteTestFinished
298 * @chainable
299 * @private
300 */
301
302 _emitConcreteTestFinished: function () {
3030 this.events.emit('test:' + this._uid + ':finished', 'test:finished', this);
3040 return this;
305 },
306
307 /**
308 * Emits an event that describes the current state of all assertions
309 *
310 * @method _emitAssertionStatus
311 * @chainable
312 * @private
313 */
314
315 _emitAssertionStatus: function () {
3160 this.reporter.emit('report:assertion:status', {
317 expected: (this.expectation ? this.expectation : this.runnedExpactations),
318 run: this.runnedExpactations,
319 status: this._testStatus()
320 });
3210 return this;
322 },
323
324 /**
325 * Get the overall test status (assertions & expectation)
326 *
327 * @method _testStatus
328 * @return {bool} status The test status
329 * @chainable
330 * @private
331 */
332
333 _testStatus: function () {
3340 return this.checkExpectations() && this.checkAssertions();
335 },
336
337 /**
338 * Emits an event that describes the current state of all assertions.
339 * The event should be fired when a test is finished
340 *
341 * @method _emitTestFinished
342 * @chainable
343 * @private
344 */
345
346 _emitTestFinished: function () {
3470 this.reporter.emit('report:test:finished', {
348 name: this.name,
349 id: this._uid,
350 passedAssertions: this.runnedExpactations - this.failedAssertions,
351 failedAssertions: this.failedAssertions,
352 runnedExpactations: this.runnedExpactations,
353 status: this._testStatus(),
354 nl: true
355 });
356
3570 return this;
358 },
359
360 /**
361 * Kicks off the test & binds all promises/events
362 *
363 * @method _testFin
364 * @return {object} promise A promise
365 * @private
366 */
367
368 _testFin: function () {
3690 this.deferred = Q.defer();
370
3710 if (_.isFunction(this.driver.end)) {
3720 this.driver.end();
373 }
374
375 // emit report startet event
3760 this._reportTestStarted(this.name);
377
378 // listen to all the messages from the driver
3790 this.driver.events.on('driver:message', this._onDriverMessage.bind(this));
3800 return this.deferred.promise;
381 },
382
383 /**
384 * Copies assertion methods
385 *
386 * @method _inheritAssertions
387 * @param {Test} test Instacne of test
388 * @chainable
389 * @private
390 */
391
392 _inheritAssertions: function (test) {
3930 ['is'].forEach(function (method) {
3940 test[method] = test.assert[method].bind(test.assert);
395 });
3960 return test;
397 },
398
399 /**
400 * Copies assertion helper methods
401 *
402 * @method _inheritAssertions
403 * @param {Test} test Instacne of test
404 * @chainable
405 * @private
406 */
407
408 _inheritAssertionHelpers: function (test) {
4090 ['not', 'between', 'gt', 'gte', 'lt', 'lte', 'equalsCaseInsensitive'].forEach(function (method) {
4100 test.is[method] = test.assert[method].bind(test.assert);
4110 test.assert.is[method] = test.assert[method].bind(test.assert);
412 });
4130 ['contain', 'match'].forEach(function (method) {
4140 test.to = test.to || {};
4150 test.assert.to = test.assert.to || {};
416
4170 test.to[method] = test.assert[method].bind(test.assert);
4180 test.assert.to[method] = test.assert[method].bind(test.assert);
419 });
4200 ['notContain'].forEach(function (method) {
4210 var apiName = method.substr(3, 1).toLowerCase() + method.substr(4);
4220 test.to.not = test.to.not || {};
4230 test.assert.to.not = test.assert.to.not || {};
424
4250 test.to.not[apiName] = test.assert[method].bind(test.assert);
4260 test.assert.to.not[apiName] = test.assert[method].bind(test.assert);
427 });
4280 return test;
429 },
430
431 /**
432 * Set up the instance
433 *
434 * @method _inheritAssertions
435 * @param {Test} test Instacne of test
436 * @param {object} opts Options
437 * @chainable
438 * @private
439 */
440
441 _initialize: function (test, opts) {
4420 test._uid = _.uniqueId('test');
4430 test.events = opts.events;
4440 test.driver = opts.driver;
4450 test.reporter = opts.reporter;
4460 return test;
447 }
448
449};
450
451/**
452 * Alias for 'andThen'; use if it is the first function called in the test case.
453 *
454 * @method start
455 * @chainable
456 */
4571Unit.prototype.start = Unit.prototype.andThen;
458
459// export a function that generates a new test instance
4601module.exports = function (opts) {
461 // mixin assertions, actions & getters
4620 Unit.prototype = _.extend(Unit.prototype, actions({reporter: opts.reporter}).prototype);
4630 var unit = new Unit(opts);
4640 unit.assert = new (assertions())({test: unit});
4650 unit.assert.done = unit.done.bind(this);
4660 unit.assert.query = unit.query.bind(unit.assert);
4670 unit.assert.$ = unit.query.bind(unit.assert);
4680 unit.end = unit.assert.end.bind(unit.assert);
469
470 // copy log methods
4710 unit.log = {};
4720 unit.log.dom = unit.logger.dom.bind(unit);
4730 unit.log.message = unit.logger.message.bind(unit);
474
475 // copy assertions methods
4760 unit = unit._inheritAssertions(unit);
477
478 // copy assertion helper methods
4790 unit = unit._inheritAssertionHelpers(unit);
480
481 // initialize the instance
4820 unit = unit._initialize(unit, opts);
483
484 // TODO: Promise driver start
485 // so that we can reexecute them and clean the env between tests
4860 Unit.testStarted = unit.driver.start(Q);
4870 return unit;
488};
489

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

100%
4
4
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
271var counter = 0;
28
291module.exports = function() {
303 return 'uuid-' + (++counter);
31};
32