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 | 1 | 'use strict'; |
25 | | |
26 | | // ext. libs |
27 | 1 | var Q = require('q'); |
28 | 1 | var uuid = require('node-uuid'); |
29 | 1 | var cheerio = require('cheerio'); |
30 | | |
31 | | // int. global |
32 | 1 | var 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 | | |
44 | 1 | var Actions = function () { |
45 | 1 | 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, you terminate it with the [assertions.html#meth-end](end) method! |
79 | | * |
80 | | * @api |
81 | | * @method query |
82 | | * @param {string} selector Selector of the element to query |
83 | | * @chainable |
84 | | */ |
85 | | |
86 | 1 | Actions.prototype.query = function (selector) { |
87 | 0 | var that = !this.test ? this : this.test; |
88 | 0 | that.lastChain.push('querying'); |
89 | 0 | that.selector = selector; |
90 | 0 | that.querying = true; |
91 | 0 | 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 | | |
103 | 1 | Actions.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 | | |
116 | 1 | Actions.prototype.mouseEvent = function (type, selector) { |
117 | 0 | var hash = uuid.v4(); |
118 | 0 | var cb = this._generateCallbackAssertion('mouseEvent', 'mouseEvent', type, selector, hash); |
119 | 0 | this._addToActionQueue([type, selector, hash], 'mouseEvent', cb); |
120 | 0 | 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 | | |
173 | 1 | Actions.prototype.setHttpAuth = function (username, password) { |
174 | 0 | var hash = uuid.v4(); |
175 | 0 | var cb = this._generateCallbackAssertion('setHttpAuth', 'setHttpAuth', username, password, hash); |
176 | 0 | this._addToActionQueue([username, password, hash], 'setHttpAuth', cb); |
177 | 0 | 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 mehtod, 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 | | |
213 | 1 | Actions.prototype.toFrame = function (selector) { |
214 | 0 | var hash = uuid.v4(); |
215 | | |
216 | 0 | if (this.querying === true) { |
217 | 0 | selector = this.selector; |
218 | | } |
219 | | |
220 | 0 | var cb = this._generateCallbackAssertion('toFrame', 'toFrame', selector, hash); |
221 | 0 | this._addToActionQueue([selector, hash], 'toFrame', cb); |
222 | 0 | 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 | | |
252 | 1 | Actions.prototype.toParent = function () { |
253 | 0 | var hash = uuid.v4(); |
254 | 0 | var cb = this._generateCallbackAssertion('toFrame', 'toFrame', null, hash); |
255 | 0 | this._addToActionQueue([null, hash], 'toFrame', cb); |
256 | 0 | return this; |
257 | | }; |
258 | | |
259 | | /** |
260 | | * Switches to a different window context |
261 | | * |
262 | | * Sometimes you encounter situations, where you need to access a . |
263 | | * You can access such frames with this mehtod, 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 | | |
292 | 1 | Actions.prototype.toWindow = function (name) { |
293 | 0 | var hash = uuid.v4(); |
294 | 0 | var cb = this._generateCallbackAssertion('toWindow', 'toWindow', name, hash); |
295 | 0 | this._addToActionQueue([name, hash], 'toWindow', cb); |
296 | 0 | return this; |
297 | | }; |
298 | | |
299 | | /** |
300 | | * Switches back to the parent windoe context when the test context has been |
301 | | * switched to a different windoe 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 | | |
326 | 1 | Actions.prototype.toParentWindow = function () { |
327 | 0 | var hash = uuid.v4(); |
328 | 0 | var cb = this._generateCallbackAssertion('toWindow', 'toWindow', null, hash); |
329 | 0 | this._addToActionQueue([null, hash], 'toWindow', cb); |
330 | 0 | 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 | | |
344 | 1 | Actions.prototype.waitForResource = function (ressource, timeout) { |
345 | 0 | var hash = uuid.v4(); |
346 | 0 | var cb = this._generateCallbackAssertion('waitForResource', 'waitForResource', ressource, timeout, hash); |
347 | 0 | this._addToActionQueue([ressource, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitForResource', cb); |
348 | 0 | 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 | | |
362 | 1 | Actions.prototype.waitForText = function (text, timeout) { |
363 | 0 | var hash = uuid.v4(); |
364 | 0 | var cb = this._generateCallbackAssertion('waitForText', 'waitForText', text, timeout, hash); |
365 | 0 | this._addToActionQueue([text, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitForText', cb); |
366 | 0 | 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 | | |
380 | 1 | Actions.prototype.waitUntilVisible = function (selector, timeout) { |
381 | 0 | var hash = uuid.v4(); |
382 | | |
383 | 0 | if (this.querying === true) { |
384 | 0 | timeout = selector; |
385 | 0 | selector = this.selector; |
386 | | } |
387 | | |
388 | 0 | var cb = this._generateCallbackAssertion('waitUntilVisible', 'waitUntilVisible', selector, timeout, hash); |
389 | 0 | this._addToActionQueue([selector, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitUntilVisible', cb); |
390 | 0 | 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 | | |
404 | 1 | Actions.prototype.waitWhileVisible = function (selector, timeout) { |
405 | 0 | var hash = uuid.v4(); |
406 | | |
407 | 0 | if (this.querying === true) { |
408 | 0 | timeout = selector; |
409 | 0 | selector = this.selector; |
410 | | } |
411 | | |
412 | 0 | var cb = this._generateCallbackAssertion('waitWhileVisible', 'waitWhileVisible', selector, timeout, hash); |
413 | 0 | this._addToActionQueue([selector, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitWhileVisible', cb); |
414 | 0 | return this; |
415 | | }; |
416 | | |
417 | | /** |
418 | | * Take a screenshot of the current page. |
419 | | * |
420 | | * The pathname argument takes some placeholders that will be replaced |
421 | | * Placeholder: |
422 | | * |
423 | | * - `:browser` - The browser name (e.g. ‘Chrome‘, ‘Safari‘, ‘Firefox‘, etc.) |
424 | | * - `:version` - The browser version (e.g. ‘10_0‘, ‘23_11_5‘, etc.) |
425 | | * - `:os` - The operating system (e.g. `OSX`, `Windows`, `Linux`) |
426 | | * - `:osVersion` - The operating system version (e.g `XP`, `7`, `10_8`, etc.) |
427 | | * - `:viewport` - The current viewport in pixels (e.g. `w1024_h768`) |
428 | | * - `:timestamp` - UNIX like timestapm (e.g. `637657345`) |
429 | | * - `:date` - Current date in format MM_DD_YYYY (e.g. `12_24_2013`) |
430 | | * - `:datetime` - Current datetime in format MM_DD_YYYY_HH_mm_ss (e.g. `12_24_2013_14_55_23`) |
431 | | * |
432 | | * ```javascript |
433 | | * // creates 'my/folder/my_file.png' |
434 | | * test.screenshot('my/folder/my_file'); |
435 | | * // creates 'my/page/in/safari/homepage.png' |
436 | | * test.screenshot('my/page/in/:browser/homepage'); |
437 | | * // creates 'my/page/in/safari_6_0_1/homepage.png' |
438 | | * test.screenshot('my/page/in/:browser_:version/homepage'); |
439 | | * // creates 'my/page/in/safari_6_0_1/on/osx/homepage.png' |
440 | | * test.screenshot('my/page/in/:browser_:version/on/:os/homepage'); |
441 | | * // creates 'my/page/in/safari_6_0_1/on/osx_10_8/homepage.png' |
442 | | * test.screenshot('my/page/in/:browser_:version/on/:os_:osVersion/homepage'); |
443 | | * // creates 'my/page/at/w1024_h768/homepage.png' |
444 | | * test.screenshot('my/page/at/:viewport/homepage'); |
445 | | * // creates 'my/page/at/637657345/homepage.png' |
446 | | * test.screenshot('my/page/in_time/:timestamp/homepage'); |
447 | | * // creates 'my/page/at/12_24_2013/homepage.png' |
448 | | * test.screenshot('my/page/in_time/:date/homepage'); |
449 | | * // creates 'my/page/at/12_24_2013_14_55_23/homepage.png' |
450 | | * test.screenshot('my/page/in_time/:datetime/homepage'); |
451 | | * ``` |
452 | | * |
453 | | * @api |
454 | | * @method screenshot |
455 | | * @param {string} pathname Name of the folder and file the screenshot should be saved to |
456 | | * @return chainable |
457 | | */ |
458 | | |
459 | 1 | Actions.prototype.screenshot = function (pathname) { |
460 | 0 | var hash = uuid.v4(); |
461 | 0 | var cb = this._generateCallbackAssertion('screenshot', 'screenshot', pathname, hash); |
462 | 0 | this._addToActionQueue(['', pathname, hash], 'screenshot', cb); |
463 | 0 | return this; |
464 | | }; |
465 | | |
466 | | /** |
467 | | * Pause steps suite execution for a given amount of time, and optionally execute a step on done. |
468 | | * |
469 | | * This makes sense, if you have a ticker for example, tht scrolls like every ten seconds |
470 | | * & you want to assure that the visible content changes every ten seconds |
471 | | * |
472 | | * ```javascript |
473 | | * test.open('http://myticker.org') |
474 | | * .assert.visible('.ticker-element:first-child', 'First ticker element is visible') |
475 | | * .wait(10000) |
476 | | * .assert.visible('.ticker-element:nth-child(2)', 'Snd. ticker element is visible') |
477 | | * .wait(10000) |
478 | | * .assert.visible('.ticker-element:last-child', 'Third ticker element is visible') |
479 | | * .done(); |
480 | | * ``` |
481 | | * If no timeout argument is given, a default timeout of 5 seconds will be used |
482 | | * |
483 | | * ```javascript |
484 | | * test.open('http://myticker.org') |
485 | | * .assert.visible('.ticker-element:first-child', 'First ticker element is visible') |
486 | | * .wait() |
487 | | * .assert.visible('.ticker-element:nth-child(2)', 'Snd. ticker element is visible') |
488 | | * .wait() |
489 | | * .assert.visible('.ticker-element:last-child', 'Third ticker element is visible') |
490 | | * .done(); |
491 | | * ``` |
492 | | * |
493 | | * @api |
494 | | * @method wait |
495 | | * @param {number} timeout in milliseconds |
496 | | * @chainable |
497 | | */ |
498 | | |
499 | 1 | Actions.prototype.wait = function (timeout) { |
500 | 0 | var hash = uuid.v4(); |
501 | 0 | var cb = this._generateCallbackAssertion('wait', 'wait', timeout, hash); |
502 | 0 | this._addToActionQueue([(timeout ? parseInt(timeout, 10) : 5000), hash], 'wait', cb); |
503 | 0 | return this; |
504 | | }; |
505 | | |
506 | | /** |
507 | | * Reloads current page location. |
508 | | * |
509 | | * This is basically the same as hitting F5/refresh in your browser |
510 | | * |
511 | | * ```javascript |
512 | | * test.open('http://google.com') |
513 | | * .reload() |
514 | | * .done(); |
515 | | * ``` |
516 | | * |
517 | | * @api |
518 | | * @method reload |
519 | | * @chainable |
520 | | */ |
521 | | |
522 | 1 | Actions.prototype.reload = function () { |
523 | 0 | var hash = uuid.v4(); |
524 | 0 | var cb = this._generateCallbackAssertion('refresh', 'refresh', '', hash); |
525 | 0 | this._addToActionQueue([hash], 'refresh', cb); |
526 | 0 | return this; |
527 | | }; |
528 | | |
529 | | /** |
530 | | * Moves a step forward in browser's history. |
531 | | * |
532 | | * This is basically the same as hitting the forward button in your browser |
533 | | * |
534 | | * ```javascript |
535 | | * test.open('http://google.com') |
536 | | * .open('https://github.com') |
537 | | * .assert.url.is('https://github.com/', 'We are at GitHub') |
538 | | * .back() |
539 | | * .assert.url.is('http://google.com', 'We are at Google!') |
540 | | * .forward() |
541 | | * .assert.url.is('https://github.com/', 'Back at GitHub! Timetravel FTW') |
542 | | * .done(); |
543 | | * ``` |
544 | | * |
545 | | * @api |
546 | | * @method forward |
547 | | * @chainable |
548 | | */ |
549 | | |
550 | 1 | Actions.prototype.forward = function () { |
551 | 0 | var hash = uuid.v4(); |
552 | 0 | var cb = this._generateCallbackAssertion('forward', 'forward', '', hash); |
553 | 0 | this._addToActionQueue([hash], 'forward', cb); |
554 | 0 | return this; |
555 | | }; |
556 | | |
557 | | /** |
558 | | * Moves back a step in browser's history. |
559 | | * |
560 | | * This is basically the same as hitting the back button in your browser |
561 | | * |
562 | | * ```javascript |
563 | | * test.open('http://google.com') |
564 | | * .open('https://github.com') |
565 | | * .assert.url.is('https://github.com/', 'We are at GitHub') |
566 | | * .back() |
567 | | * .assert.url.is('http://google.com', 'We are at Google!') |
568 | | * .forward() |
569 | | * .assert.url.is('https://github.com/', 'Back at GitHub! Timetravel FTW'); |
570 | | * .done(); |
571 | | * ``` |
572 | | * |
573 | | * @api |
574 | | * @method back |
575 | | * @chainable |
576 | | */ |
577 | | |
578 | 1 | Actions.prototype.back = function () { |
579 | 0 | var hash = uuid.v4(); |
580 | 0 | var cb = this._generateCallbackAssertion('back', 'back', '', hash); |
581 | 0 | this._addToActionQueue([hash], 'back', cb); |
582 | 0 | return this; |
583 | | }; |
584 | | |
585 | | /** |
586 | | * Performs a click on the element matching the provided selector expression. |
587 | | * |
588 | | * If we take Daleks homepage (the one you're probably visiting right now), |
589 | | * the HTML looks something like this (it does not really, but hey, lets assume this for a second) |
590 | | * |
591 | | * ```html |
592 | | * <nav> |
593 | | * <ul> |
594 | | * <li><a id="homeapge" href="/index.html">DalekJS</a></li> |
595 | | * <li><a id="docs" href="/docs.html">Documentation</a></li> |
596 | | * <li><a id="faq" href="/faq.html">F.A.Q</a></li> |
597 | | * </ul> |
598 | | * </nav> |
599 | | * ``` |
600 | | * |
601 | | * ```javascript |
602 | | * test.open('http://dalekjs.com') |
603 | | * .click('#faq') |
604 | | * .assert.title().is('DalekJS - Frequently asked questions', 'What the F.A.Q.') |
605 | | * .done(); |
606 | | * ``` |
607 | | * |
608 | | * By default, this performs a left click. |
609 | | * In the future it might become the ability to also execute a "right button" click. |
610 | | * |
611 | | * > Note: Does not work correctly in Firefox when used on `<select>` & `<option>` elements |
612 | | * |
613 | | * @api |
614 | | * @method click |
615 | | * @param {string} selector Selector of the element to be clicked |
616 | | * @chainable |
617 | | */ |
618 | | |
619 | 1 | Actions.prototype.click = function (selector) { |
620 | 0 | var hash = uuid.v4(); |
621 | | |
622 | 0 | if (this.querying === true) { |
623 | 0 | selector = this.selector; |
624 | | } |
625 | | |
626 | 0 | var cb = this._generateCallbackAssertion('click', 'click', selector, hash); |
627 | 0 | this._addToActionQueue([selector, hash], 'click', cb); |
628 | 0 | return this; |
629 | | }; |
630 | | |
631 | | /** |
632 | | * Submits a form. |
633 | | * |
634 | | * ```html |
635 | | * <form id="skaaro" action="skaaro.php" method="GET"> |
636 | | * <input type="hidden" name="intheshadows" value="itis"/> |
637 | | * <input type="text" name="truth" id="truth" value=""/> |
638 | | * </form> |
639 | | * ``` |
640 | | * |
641 | | * ```javascript |
642 | | * test.open('http://home.dalek.com') |
643 | | * .type('#truth', 'out there is') |
644 | | * .submit('#skaaro') |
645 | | * .done(); |
646 | | * ``` |
647 | | * |
648 | | * > Note: Does not work in Firefox yet |
649 | | * |
650 | | * @api |
651 | | * @method submit |
652 | | * @param {string} selector Selector of the form to be submitted |
653 | | * @chainable |
654 | | */ |
655 | | |
656 | 1 | Actions.prototype.submit = function (selector) { |
657 | 0 | var hash = uuid.v4(); |
658 | | |
659 | 0 | if (this.querying === true) { |
660 | 0 | selector = this.selector; |
661 | | } |
662 | | |
663 | 0 | var cb = this._generateCallbackAssertion('submit', 'submit', selector, hash); |
664 | 0 | this._addToActionQueue([selector, hash], 'submit', cb); |
665 | 0 | return this; |
666 | | }; |
667 | | |
668 | | /** |
669 | | * Performs an HTTP request for opening a given location. |
670 | | * You can forge GET, POST, PUT, DELETE and HEAD requests. |
671 | | * |
672 | | * Basically the same as typing a location into your browsers URL bar and |
673 | | * hitting return. |
674 | | * |
675 | | * ```javascript |
676 | | * test.open('http://dalekjs.com') |
677 | | * .assert.url().is('http://dalekjs.com', 'DalekJS I'm in you') |
678 | | * .done(); |
679 | | * ``` |
680 | | * |
681 | | * @api |
682 | | * @method open |
683 | | * @param {string} location URL of the page to open |
684 | | * @chainable |
685 | | */ |
686 | | |
687 | 1 | Actions.prototype.open = function (location) { |
688 | 0 | var hash = uuid.v4(); |
689 | 0 | var cb = this._generateCallbackAssertion('open', 'open', location, hash); |
690 | 0 | this._addToActionQueue([location, hash], 'open', cb); |
691 | 0 | return this; |
692 | | }; |
693 | | |
694 | | /** |
695 | | * Types a text into an input field or text area. |
696 | | * And yes, it really types, character for character, like you would |
697 | | * do when using your keyboard. |
698 | | * |
699 | | * |
700 | | * ```html |
701 | | * <form id="skaaro" action="skaaro.php" method="GET"> |
702 | | * <input type="hidden" name="intheshadows" value="itis"/> |
703 | | * <input type="text" name="truth" id="truth" value=""/> |
704 | | * </form> |
705 | | * ``` |
706 | | * |
707 | | * ```javascript |
708 | | * test.open('http://home.dalek.com') |
709 | | * .type('#truth', 'out there is') |
710 | | * .assert.val('#truth', 'out there is', 'Text has been set') |
711 | | * .done(); |
712 | | * ``` |
713 | | * |
714 | | * You can also send special keys using unicode. |
715 | | * |
716 | | * * ```javascript |
717 | | * test.open('http://home.dalek.com') |
718 | | * .type('#truth', 'out \uE008there\uE008 is') |
719 | | * .assert.val('#truth', 'out THERE is', 'Text has been set') |
720 | | * .done(); |
721 | | * ``` |
722 | | * 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). |
723 | | * |
724 | | * > Note: Does not work correctly in Firefox with special keys |
725 | | * |
726 | | * @api |
727 | | * @method type |
728 | | * @param {string} selector Selector of the form field to be filled |
729 | | * @param {string} keystrokes Text to be applied to the element |
730 | | * @chainable |
731 | | */ |
732 | | |
733 | 1 | Actions.prototype.type = function (selector, keystrokes) { |
734 | 0 | var hash = uuid.v4(); |
735 | | |
736 | 0 | if (this.querying === true) { |
737 | 0 | keystrokes = selector; |
738 | 0 | selector = this.selector; |
739 | | } |
740 | | |
741 | 0 | var cb = this._generateCallbackAssertion('type', 'type', selector, keystrokes, hash); |
742 | 0 | this._addToActionQueue([selector, keystrokes], 'type', cb); |
743 | 0 | return this; |
744 | | }; |
745 | | |
746 | | /** |
747 | | * This acts just like .type() with a key difference. |
748 | | * This action can be used on non-input elements (useful for test site wide keyboard shortcuts and the like). |
749 | | * So assumeing we have a keyboard shortcut that display an alert box, we could test that with something like this: |
750 | | * |
751 | | * ```javascript |
752 | | * test.open('http://home.dalek.com') |
753 | | * .sendKeys('body', '\uE00C') |
754 | | * .assert.dialogText('press the escape key give this alert text') |
755 | | * .done(); |
756 | | * ``` |
757 | | * |
758 | | * |
759 | | * > Note: Does not work correctly in Firefox with special keys |
760 | | * |
761 | | * @api |
762 | | * @method sendKeys |
763 | | * @param {string} selector Selector of the form field to be filled |
764 | | * @param {string} keystrokes Text to be applied to the element |
765 | | * @chainable |
766 | | */ |
767 | | |
768 | 1 | Actions.prototype.sendKeys = function (selector, keystrokes) { |
769 | 0 | var hash = uuid.v4(); |
770 | | |
771 | 0 | if (this.querying === true) { |
772 | 0 | keystrokes = selector; |
773 | 0 | selector = this.selector; |
774 | | } |
775 | | |
776 | 0 | var cb = this._generateCallbackAssertion('sendKeys', 'sendKeys', selector, keystrokes, hash); |
777 | 0 | this._addToActionQueue([selector, keystrokes], 'sendKeys', cb); |
778 | 0 | return this; |
779 | | }; |
780 | | |
781 | | /** |
782 | | * Types a text into the text inout field of a prompt dialog. |
783 | | * Like you would do when using your keyboard. |
784 | | * |
785 | | * ```html |
786 | | * <div> |
787 | | * <a id="aquestion" onclick="this.innerText = window.prompt('Your favourite companion:')">????</a> |
788 | | * </div> |
789 | | * ``` |
790 | | * |
791 | | * ```javascript |
792 | | * test.open('http://adomain.com') |
793 | | * .click('#aquestion') |
794 | | * .answer('Rose') |
795 | | * .assert.text('#aquestion').is('Rose', 'Awesome she was!') |
796 | | * .done(); |
797 | | * ``` |
798 | | * |
799 | | * |
800 | | * > Note: Does not work in Firefox & PhantomJS |
801 | | * |
802 | | * @api |
803 | | * @method answer |
804 | | * @param {string} keystrokes Text to be applied to the element |
805 | | * @return chainable |
806 | | */ |
807 | | |
808 | 1 | Actions.prototype.answer = function (keystrokes) { |
809 | 0 | var hash = uuid.v4(); |
810 | 0 | var cb = this._generateCallbackAssertion('promptText', 'promptText', keystrokes, hash); |
811 | 0 | this._addToActionQueue([keystrokes, hash], 'promptText', cb); |
812 | 0 | return this; |
813 | | }; |
814 | | |
815 | | /** |
816 | | * Executes a JavaScript function within the browser context |
817 | | * |
818 | | * ```javascript |
819 | | * test.open('http://adomain.com') |
820 | | * .execute(function () { |
821 | | * window.myFramework.addRow('foo'); |
822 | | * window.myFramework.addRow('bar'); |
823 | | * }) |
824 | | * .done(); |
825 | | * ``` |
826 | | * |
827 | | * You can also apply arguments to the function |
828 | | * |
829 | | * ```javascript |
830 | | * test.open('http://adomain.com') |
831 | | * .execute(function (paramFoo, aBar) { |
832 | | * window.myFramework.addRow(paramFoo); |
833 | | * window.myFramework.addRow(aBar); |
834 | | * }, 'foo', 'bar') |
835 | | * .done(); |
836 | | * ``` |
837 | | * |
838 | | * > Note: Buggy in Firefox |
839 | | * |
840 | | * @api |
841 | | * @method execute |
842 | | * @param {function} script JavaScript function that should be executed |
843 | | * @return chainable |
844 | | */ |
845 | | |
846 | 1 | Actions.prototype.execute = function (script) { |
847 | 0 | var hash = uuid.v4(); |
848 | 0 | var args = [this.contextVars].concat(Array.prototype.slice.call(arguments, 1) || []); |
849 | 0 | var cb = this._generateCallbackAssertion('execute', 'execute', script, args, hash); |
850 | 0 | this._addToActionQueue([script, args, hash], 'execute', cb); |
851 | 0 | return this; |
852 | | }; |
853 | | |
854 | | /** |
855 | | * Waits until a function returns true to process any next step. |
856 | | * |
857 | | * You can also set a callback on timeout using the onTimeout argument, |
858 | | * and set the timeout using the timeout one, in milliseconds. The default timeout is set to 5000ms. |
859 | | * |
860 | | * ```javascript |
861 | | * test.open('http://adomain.com') |
862 | | * .waitFor(function () { |
863 | | * return window.myCheck === true; |
864 | | * }) |
865 | | * .done(); |
866 | | * ``` |
867 | | * |
868 | | * You can also apply arguments to the function, as well as a timeout |
869 | | * |
870 | | * ```javascript |
871 | | * test.open('http://adomain.com') |
872 | | * .waitFor(function (aCheck) { |
873 | | * return window.myThing === aCheck; |
874 | | * }, 'aValue', 10000) |
875 | | * .done(); |
876 | | * ``` |
877 | | * |
878 | | * > Note: Buggy in Firefox |
879 | | * |
880 | | * @method waitFor |
881 | | * @param {function} fn Async function that resolves an promise when ready |
882 | | * @param {array} args Additional arguments |
883 | | * @param {number} timeout Timeout in miliseconds |
884 | | * @chainable |
885 | | * @api |
886 | | */ |
887 | | |
888 | 1 | Actions.prototype.waitFor = function (script, args, timeout) { |
889 | 0 | var hash = uuid.v4(); |
890 | 0 | timeout = timeout || 5000; |
891 | 0 | var cb = this._generateCallbackAssertion('waitFor', 'waitFor', script, args, timeout, hash); |
892 | 0 | this._addToActionQueue([script, args, timeout, hash], 'waitFor', cb); |
893 | 0 | return this; |
894 | | }; |
895 | | |
896 | | /** |
897 | | * Accepts an alert/prompt/confirm dialog. This is basically the same actions as when |
898 | | * you are clicking okay or hitting return in one of that dialogs. |
899 | | * |
900 | | * ```html |
901 | | * <div> |
902 | | * <a id="attentione" onclick="window.alert('Alonsy!')">ALERT!ALERT!</a> |
903 | | * </div> |
904 | | * ``` |
905 | | * |
906 | | * ```javascript |
907 | | * test.open('http://adomain.com') |
908 | | * // alert appears |
909 | | * .click('#attentione') |
910 | | * // alert is gone |
911 | | * .accept() |
912 | | * .done(); |
913 | | * ``` |
914 | | * |
915 | | * > Note: Does not work in Firefox & PhantomJS |
916 | | * |
917 | | * @api |
918 | | * @method accept |
919 | | * @return chainable |
920 | | */ |
921 | | |
922 | 1 | Actions.prototype.accept = function () { |
923 | 0 | var hash = uuid.v4(); |
924 | 0 | var cb = this._generateCallbackAssertion('acceptAlert', 'acceptAlert', hash); |
925 | 0 | this._addToActionQueue([hash], 'acceptAlert', cb); |
926 | 0 | return this; |
927 | | }; |
928 | | |
929 | | /** |
930 | | * Dismisses an prompt/confirm dialog. This is basically the same actions as when |
931 | | * you are clicking cancel in one of that dialogs. |
932 | | * |
933 | | * ```html |
934 | | * <div> |
935 | | * <a id="nonono" onclick="(this.innerText = window.confirm('No classic doctors in the 50th?') ? 'Buh!' : ':(') ">What!</a> |
936 | | * </div> |
937 | | * ``` |
938 | | * |
939 | | * ```javascript |
940 | | * test.open('http://adomain.com') |
941 | | * // prompt appears |
942 | | * .click('#nonono') |
943 | | * // prompt is gone |
944 | | * .dismiss() |
945 | | * .assert.text('#nonono').is(':(', 'So sad') |
946 | | * .done(); |
947 | | * ``` |
948 | | * |
949 | | * > Note: Does not work in Firefox & PhantomJS |
950 | | * |
951 | | * @api |
952 | | * @method dismiss |
953 | | * @return chainable |
954 | | */ |
955 | | |
956 | 1 | Actions.prototype.dismiss = function () { |
957 | 0 | var hash = uuid.v4(); |
958 | 0 | var cb = this._generateCallbackAssertion('dismissAlert', 'dismissAlert', hash); |
959 | 0 | this._addToActionQueue([hash], 'dismissAlert', cb); |
960 | 0 | return this; |
961 | | }; |
962 | | |
963 | | /** |
964 | | * Resizes the browser window to a set of given dimensions (in px). |
965 | | * The default configuration of dalek opening pages is a width of 1280px |
966 | | * and a height of 1024px. You can specify your own default in the configuration. |
967 | | * |
968 | | * ```html |
969 | | * <div> |
970 | | * <span id="magicspan">The span in the fireplace</span> |
971 | | * </div> |
972 | | * ``` |
973 | | * |
974 | | * ```css |
975 | | * #magicspan { |
976 | | * display: inline; |
977 | | * } |
978 | | * |
979 | | * // @media all and (max-width: 500px) and (min-width: 300px) |
980 | | * #magicspan { |
981 | | * display: none; |
982 | | * } |
983 | | * ``` |
984 | | * |
985 | | * ```javascript |
986 | | * test.open('http://adomain.com') |
987 | | * .assert.visible('#magicspan', 'Big screen, visible span') |
988 | | * .resize({width: 400, height: 500}) |
989 | | * .assert.notVisible('#magicspan', 'Small screen, no visible span magic!') |
990 | | * .done(); |
991 | | * ``` |
992 | | * |
993 | | * |
994 | | * > Note: Does not work in Firefox |
995 | | * |
996 | | * @api |
997 | | * @method resize |
998 | | * @param {object} dimensions Width and height as properties to apply |
999 | | * @chainable |
1000 | | */ |
1001 | | |
1002 | 1 | Actions.prototype.resize = function (dimensions) { |
1003 | 0 | var hash = uuid.v4(); |
1004 | 0 | var cb = this._generateCallbackAssertion('resize', 'resize', dimensions, hash); |
1005 | 0 | this._addToActionQueue([dimensions, hash], 'resize', cb); |
1006 | 0 | return this; |
1007 | | }; |
1008 | | |
1009 | | /** |
1010 | | * Maximizes the browser window. |
1011 | | * |
1012 | | * ```html |
1013 | | * <div> |
1014 | | * <span id="magicspan">The span in the fireplace</span> |
1015 | | * </div> |
1016 | | * ``` |
1017 | | * |
1018 | | * ```css |
1019 | | * #magicspan { |
1020 | | * display: inline; |
1021 | | * } |
1022 | | * |
1023 | | * @media all and (max-width: 500px) and (min-width: 300px) { |
1024 | | * #magicspan { |
1025 | | * display: none; |
1026 | | * } |
1027 | | * } |
1028 | | * ``` |
1029 | | * |
1030 | | * ```javascript |
1031 | | * test.open('http://adomain.com') |
1032 | | * .resize({width: 400, height: 500}) |
1033 | | * .assert.notVisible('#magicspan', 'Small screen, no visible span magic!') |
1034 | | * .maximize() |
1035 | | * .assert.visible('#magicspan', 'Big screen, visible span') |
1036 | | * .done(); |
1037 | | * ``` |
1038 | | * |
1039 | | * > Note: Does not work in Firefox and PhantomJS |
1040 | | * |
1041 | | * @api |
1042 | | * @method maximize |
1043 | | * @chainable |
1044 | | */ |
1045 | | |
1046 | 1 | Actions.prototype.maximize = function () { |
1047 | 0 | var hash = uuid.v4(); |
1048 | 0 | var cb = this._generateCallbackAssertion('maximize', 'maximize', hash); |
1049 | 0 | this._addToActionQueue([hash], 'maximize', cb); |
1050 | 0 | return this; |
1051 | | }; |
1052 | | |
1053 | | /** |
1054 | | * Sets a cookie. |
1055 | | * More configuration options will be implemented in the future, |
1056 | | * by now, you can only set a cookie with a specific name and contents. |
1057 | | * This will be a domain wide set cookie. |
1058 | | * |
1059 | | * ```javascript |
1060 | | * test.open('http://adomain.com') |
1061 | | * .setCookie('my_cookie_name', 'my=content') |
1062 | | * .done(); |
1063 | | * ``` |
1064 | | * |
1065 | | * @api |
1066 | | * @method setCookie |
1067 | | * @chainable |
1068 | | */ |
1069 | | |
1070 | 1 | Actions.prototype.setCookie = function (name, contents) { |
1071 | 0 | var hash = uuid.v4(); |
1072 | 0 | var cb = this._generateCallbackAssertion('setCookie', 'setCookie', name, contents, hash); |
1073 | 0 | this._addToActionQueue([name, contents, hash], 'setCookie', cb); |
1074 | 0 | return this; |
1075 | | }; |
1076 | | |
1077 | | /** |
1078 | | * Waits until an element matching the provided |
1079 | | * selector expression exists in remote DOM to process any next step. |
1080 | | * |
1081 | | * Lets assume we have a ticker that loads its contents via AJAX, |
1082 | | * and appends new elements, when the call has been successfully answered: |
1083 | | * |
1084 | | * ```javascript |
1085 | | * test.open('http://myticker.org') |
1086 | | * .assert.text('.ticker-element:first-child', 'First!', 'First ticker element is visible') |
1087 | | * // now we load the next ticker element, defsult timeout is 5 seconds |
1088 | | * .waitForElement('.ticker-element:nth-child(2)') |
1089 | | * .assert.text('.ticker-element:nth-child(2)', 'Me snd. one', 'Snd. ticker element is visible') |
1090 | | * // Lets assume that this AJAX call can take longer, so we raise the default timeout to 10 seconds |
1091 | | * .waitForElement('.ticker-element:last-child', 10000) |
1092 | | * .assert.text('.ticker-element:last-child', 'Me, third one!', 'Third ticker element is visible') |
1093 | | * .done(); |
1094 | | * ``` |
1095 | | * |
1096 | | * @api |
1097 | | * @method waitForElement |
1098 | | * @param {string} selector Selector that matches the element to wait for |
1099 | | * @param {number} timeout Timeout in milliseconds |
1100 | | * @chainable |
1101 | | */ |
1102 | | |
1103 | 1 | Actions.prototype.waitForElement = function (selector, timeout) { |
1104 | 0 | var hash = uuid.v4(); |
1105 | | |
1106 | 0 | if (this.querying === true) { |
1107 | 0 | timeout = selector; |
1108 | 0 | selector = this.selector; |
1109 | | } |
1110 | | |
1111 | 0 | var cb = this._generateCallbackAssertion('waitForElement', 'waitForElement', selector + ' : ' + timeout, hash); |
1112 | 0 | this._addToActionQueue([selector, (timeout ? parseInt(timeout, 10) : 5000), hash], 'waitForElement', cb); |
1113 | 0 | return this; |
1114 | | }; |
1115 | | |
1116 | | /** |
1117 | | * Fills the fields of a form with given values. |
1118 | | * |
1119 | | * ```html |
1120 | | * <input type="hidden" value="not really a value" id="ijustwannahaveavalue"/> |
1121 | | * ``` |
1122 | | * |
1123 | | * ```javascript |
1124 | | * test.open('http://dalekjs.com') |
1125 | | * .setValue('#ijustwannahaveavalue', 'a value') |
1126 | | * .title().is('DalekJS - Frequently asked questions', 'What the F.A.Q.'); |
1127 | | * ``` |
1128 | | * |
1129 | | * @api |
1130 | | * @method setValue |
1131 | | * @param {string} selector |
1132 | | * @param {string} value |
1133 | | * @return {Actions} |
1134 | | */ |
1135 | | |
1136 | 1 | Actions.prototype.setValue = function (selector, value) { |
1137 | 0 | var hash = uuid.v4(); |
1138 | | |
1139 | 0 | if (this.querying === true) { |
1140 | 0 | value = selector; |
1141 | 0 | selector = this.selector; |
1142 | | } |
1143 | | |
1144 | 0 | var cb = this._generateCallbackAssertion('setValue', 'setValue', selector + ' : ' + value, hash); |
1145 | 0 | this._addToActionQueue([selector, value, hash], 'setValue', cb); |
1146 | 0 | return this; |
1147 | | }; |
1148 | | |
1149 | | // LOG (May should live in its own module) |
1150 | | // --------------------------------------- |
1151 | | |
1152 | 1 | Actions.prototype.logger = {}; |
1153 | | |
1154 | | /** |
1155 | | * Logs a part of the remote dom |
1156 | | * |
1157 | | * ```html |
1158 | | * <body> |
1159 | | * <div id="smth"> |
1160 | | * <input type="hidden" value="not really a value" id="ijustwannahaveavalue"/> |
1161 | | * </div> |
1162 | | * </body> |
1163 | | * ``` |
1164 | | * |
1165 | | * ```javascript |
1166 | | * test.open('http://dalekjs.com/guineapig') |
1167 | | * .log.dom('#smth') |
1168 | | * .done(); |
1169 | | * ``` |
1170 | | * |
1171 | | * Will output this: |
1172 | | * |
1173 | | * ```html |
1174 | | * DOM: #smth <input type="hidden" value="not really a value" id="ijustwannahaveavalue"/> |
1175 | | * ``` |
1176 | | |
1177 | | * |
1178 | | * @api |
1179 | | * @method log.dom |
1180 | | * @param {string} selector CSS selector |
1181 | | * @chainable |
1182 | | */ |
1183 | | |
1184 | 1 | Actions.prototype.logger.dom = function (selector) { |
1185 | 0 | var hash = uuid.v4(); |
1186 | | |
1187 | 0 | var cb = function logDomCb (data) { |
1188 | 0 | if (data && data.key === 'source' && !this.uuids[data.uuid]) { |
1189 | 0 | this.uuids[data.uuid] = true; |
1190 | 0 | var $ = cheerio.load(data.value); |
1191 | 0 | var result = selector ? $(selector).html() : $.html(); |
1192 | 0 | selector = selector ? selector : ' '; |
1193 | 0 | result = !result ? ' Not found' : result; |
1194 | 0 | this.reporter.emit('report:log:user', 'DOM: ' + selector + ' ' + result); |
1195 | | } |
1196 | | }.bind(this); |
1197 | | |
1198 | 0 | this._addToActionQueue([hash], 'source', cb); |
1199 | 0 | return this; |
1200 | | }; |
1201 | | |
1202 | | /** |
1203 | | * Logs a user defined message |
1204 | | * |
1205 | | * ```javascript |
1206 | | * test.open('http://dalekjs.com/guineapig') |
1207 | | * .execute(function () { |
1208 | | * this.data('aKey', 'aValue'); |
1209 | | * }) |
1210 | | * .log.message(function () { |
1211 | | * return test.data('aKey'); // outputs MESSAGE: 'aValue' |
1212 | | * }) |
1213 | | * .done(); |
1214 | | * ``` |
1215 | | * |
1216 | | * 'Normal' messages can be logged too: |
1217 | | * |
1218 | | * ```javascript |
1219 | | * test.open('http://dalekjs.com/guineapig') |
1220 | | * .log.message('FooBar') // outputs MESSAGE: FooBar |
1221 | | * .done(); |
1222 | | * ``` |
1223 | | * |
1224 | | * @api |
1225 | | * @method log.message |
1226 | | * @param {function|string} message |
1227 | | * @chainable |
1228 | | */ |
1229 | | |
1230 | 1 | Actions.prototype.logger.message = function (message) { |
1231 | 0 | var hash = uuid.v4(); |
1232 | | |
1233 | 0 | var cb = function logMessageCb (data) { |
1234 | 0 | if (data && data.key === 'noop' && !this.uuids[data.hash]) { |
1235 | 0 | this.uuids[data.hash] = true; |
1236 | 0 | var result = (typeof(data.value) === 'function') ? data.value.bind(this)() : data.value; |
1237 | 0 | this.reporter.emit('report:log:user', 'MESSAGE: ' + result); |
1238 | | } |
1239 | | }.bind(this); |
1240 | | |
1241 | 0 | this._addToActionQueue([message, hash], 'noop', cb); |
1242 | 0 | return this; |
1243 | | }; |
1244 | | |
1245 | | /** |
1246 | | * Generates a callback that will be fired when the action has been completed. |
1247 | | * The callback itself will then validate the answer and will also emit an event |
1248 | | * that the action has been successfully executed. |
1249 | | * |
1250 | | * @method _generateCallbackAssertion |
1251 | | * @param {string} key Unique key of the action |
1252 | | * @param {string} type Type of the action (normalle the actions name) |
1253 | | * @return {function} The generated callback function |
1254 | | * @private |
1255 | | */ |
1256 | | |
1257 | 1 | Actions.prototype._generateCallbackAssertion = function (key, type) { |
1258 | 0 | var cb = function (data) { |
1259 | 0 | if (data && data.key === key && !this.uuids[data.uuid]) { |
1260 | 0 | if (!data || (data.value && data.value === null)) { |
1261 | 0 | data.value = ''; |
1262 | | } |
1263 | | |
1264 | 0 | if (key === 'execute') { |
1265 | 0 | Object.keys(data.value.dalek).forEach(function (key) { |
1266 | 0 | this.contextVars[key] = data.value.dalek[key]; |
1267 | | }.bind(this)); |
1268 | | |
1269 | 0 | data.value.test.forEach(function (test) { |
1270 | 0 | this.reporter.emit('report:assertion', { |
1271 | | success: test.ok, |
1272 | | expected: true, |
1273 | | value: test.ok, |
1274 | | message: test.message, |
1275 | | type: 'OK' |
1276 | | }); |
1277 | | |
1278 | 0 | this.incrementExpectations(); |
1279 | | |
1280 | 0 | if (!test.ok) { |
1281 | 0 | this.incrementFailedAssertions(); |
1282 | | } |
1283 | | }.bind(this)); |
1284 | | |
1285 | 0 | data.value = ''; |
1286 | | } |
1287 | | |
1288 | 0 | this.uuids[data.uuid] = true; |
1289 | 0 | reporter.emit('report:action', { |
1290 | | value: data.value, |
1291 | | type: type, |
1292 | | uuid: data.uuid |
1293 | | }); |
1294 | | } |
1295 | | }.bind(this); |
1296 | 0 | return cb; |
1297 | | }; |
1298 | | |
1299 | | /** |
1300 | | * Adds a method to the queue of actions/assertions to execute |
1301 | | * |
1302 | | * @method _addToActionQueue |
1303 | | * @param {object} opts Options of the action to invoke |
1304 | | * @param {string} driverMethod Name of the method to call on the driver |
1305 | | * @param {function} A callback function that will be executed when the action has been executed |
1306 | | * @private |
1307 | | * @chainable |
1308 | | */ |
1309 | | |
1310 | 1 | Actions.prototype._addToActionQueue = function (opts, driverMethod, cb) { |
1311 | 0 | this.actionPromiseQueue.push(function () { |
1312 | 0 | var deferred = Q.defer(); |
1313 | | // add a generic identifier as the last argument to any action method call |
1314 | 0 | opts.push(uuid.v4()); |
1315 | | // check the method on the driver object && the callback function |
1316 | 0 | if (typeof(this.driver[driverMethod]) === 'function' && typeof(cb) === 'function') { |
1317 | | // call the method on the driver object |
1318 | 0 | this.driver[driverMethod].apply(this.driver, opts); |
1319 | 0 | deferred.resolve(); |
1320 | | } else { |
1321 | 0 | deferred.reject(); |
1322 | | } |
1323 | | |
1324 | | // listen to driver message events & apply the callback argument |
1325 | 0 | this.driver.events.on('driver:message', cb); |
1326 | 0 | return deferred.promise; |
1327 | | }.bind(this)); |
1328 | 0 | return this; |
1329 | | }; |
1330 | | |
1331 | | /** |
1332 | | * @module DalekJS |
1333 | | */ |
1334 | | |
1335 | 1 | module.exports = function (opts) { |
1336 | 1 | reporter = opts.reporter; |
1337 | 1 | return Actions; |
1338 | | }; |
1339 | | |