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 | | |
25 | 1 | 'use strict'; |
26 | | |
27 | | // ext. libs |
28 | 1 | var fs = require('fs'); |
29 | 1 | var path = require('path'); |
30 | | |
31 | | // int. global |
32 | 1 | var reporter = null; |
33 | | |
34 | | /** |
35 | | * The JSON reporter can produce a file with the results of your testrun. |
36 | | * |
37 | | * The reporter can be installed with the following command: |
38 | | * ``` |
39 | | * $ npm install dalek-reporter-json --save-dev |
40 | | * ``` |
41 | | * |
42 | | * The file will follow the following format. This is a first draft and will |
43 | | * definitly change in future versions. |
44 | | * |
45 | | * ```javascript |
46 | | * { |
47 | | * "tests": [ |
48 | | * { |
49 | | * "id": "test806", |
50 | | * "name": "Can get !url (OK, TDD style, message, chained)", |
51 | | * "browser": "Chrome", |
52 | | * "status": true, |
53 | | * "passedAssertions": 1, |
54 | | * "failedAssertions": 0, |
55 | | * "actions": [ |
56 | | * { |
57 | | * "value": "http://localhost:5000/index.html", |
58 | | * "type": "open", |
59 | | * "uuid": "6ea84fc0-58bf-4e1f-bb9c-f035c6e6fae2", |
60 | | * "kind": "action", |
61 | | * "isAction": true |
62 | | * }, |
63 | | * { |
64 | | * "success": true, |
65 | | * "expected": "http://localhost:5000/guinea.html", |
66 | | * "value": "http://localhost:5000/index.html", |
67 | | * "message": "Url is not whatever", |
68 | | * "type": "url", |
69 | | * "kind": "assertion", |
70 | | * "isAssertion": true |
71 | | * } |
72 | | * ] |
73 | | * } |
74 | | * ], |
75 | | * "elapsedTime": { |
76 | | * "minutes": 1, |
77 | | * "seconds": 43.328535046 |
78 | | * }, |
79 | | * "status": true, |
80 | | * "assertions": 1, |
81 | | * "assertionsFailed": 0, |
82 | | * "assertionsPassed": 1 |
83 | | * } |
84 | | * ``` |
85 | | * |
86 | | * By default the file will be written to `report/dalek.json`, |
87 | | * you can change this by adding a config option to the your Dalekfile |
88 | | * |
89 | | * ```javascript |
90 | | * "json-reporter": { |
91 | | * "dest": "your/folder/your_file.json" |
92 | | * } |
93 | | * ``` |
94 | | * |
95 | | * @class Reporter |
96 | | * @constructor |
97 | | * @part JSON |
98 | | * @api |
99 | | */ |
100 | | |
101 | 1 | function Reporter (opts) { |
102 | 1 | this.events = opts.events; |
103 | 1 | this.config = opts.config; |
104 | 1 | this.data = {}; |
105 | 1 | this.actionQueue = []; |
106 | 1 | this.data.tests = []; |
107 | 1 | this.browser = null; |
108 | | |
109 | 1 | var defaultReportFolder = 'report'; |
110 | 1 | this.dest = this.config.get('json-reporter') && this.config.get('json-reporter').dest ? this.config.get('json-reporter').dest : defaultReportFolder; |
111 | | |
112 | 1 | this.startListening(); |
113 | | } |
114 | | |
115 | | /** |
116 | | * @module Reporter |
117 | | */ |
118 | | |
119 | 1 | module.exports = function (opts) { |
120 | 6 | if (reporter === null) { |
121 | 1 | reporter = new Reporter(opts); |
122 | | } |
123 | | |
124 | 6 | return reporter; |
125 | | }; |
126 | | |
127 | 1 | Reporter.prototype = { |
128 | | |
129 | | /** |
130 | | * Connects to all the event listeners |
131 | | * |
132 | | * @method startListening |
133 | | * @chainable |
134 | | */ |
135 | | |
136 | | startListening: function () { |
137 | 1 | this.events.on('report:run:browser', this.runBrowser.bind(this)); |
138 | 1 | this.events.on('report:assertion', this.assertion.bind(this)); |
139 | 1 | this.events.on('report:action', this.action.bind(this)); |
140 | 1 | this.events.on('report:test:started', this.testStarted.bind(this)); |
141 | 1 | this.events.on('report:test:finished', this.testFinished.bind(this)); |
142 | 1 | this.events.on('report:runner:finished', this.runnerFinished.bind(this)); |
143 | 1 | this.events.on('report:log:user',this.messageLog.bind(this)); |
144 | 1 | this.events.on('report:screenshot',this.screenshot.bind(this)); |
145 | 1 | return this; |
146 | | }, |
147 | | |
148 | | /** |
149 | | * Stores the current browser name |
150 | | * |
151 | | * @method runBrowser |
152 | | * @param {string} browser Browser name |
153 | | * @chainable |
154 | | */ |
155 | | |
156 | | runBrowser: function (browser) { |
157 | 3 | this.browser = browser; |
158 | 3 | return this; |
159 | | }, |
160 | | |
161 | | /** |
162 | | * Generates JSON for an action |
163 | | * |
164 | | * @method action |
165 | | * @param {object} data Event data |
166 | | * @chainable |
167 | | */ |
168 | | |
169 | | action: function (data) { |
170 | 1 | data.kind = 'action'; |
171 | 1 | this.actionQueue.push(data); |
172 | 1 | return this; |
173 | | }, |
174 | | |
175 | | /** |
176 | | * Generates JSON for an assertion |
177 | | * |
178 | | * @method assertion |
179 | | * @param {object} data Event data |
180 | | * @chainable |
181 | | */ |
182 | | |
183 | | assertion: function (data) { |
184 | 1 | data.kind = 'assertion'; |
185 | 1 | this.actionQueue.push(data); |
186 | 1 | return this; |
187 | | }, |
188 | | |
189 | | /** |
190 | | * Sets up a new testcase |
191 | | * |
192 | | * @method testStarted |
193 | | * @param {object} data Event data |
194 | | * @chainable |
195 | | */ |
196 | | |
197 | | testStarted: function (data) { |
198 | 1 | this.currentTest = data; |
199 | 1 | this.actionQueue = []; |
200 | 1 | return this; |
201 | | }, |
202 | | |
203 | | /** |
204 | | * Writes data for a finished testcase |
205 | | * |
206 | | * @method testFinished |
207 | | * @param {object} data Event data |
208 | | * @chainable |
209 | | */ |
210 | | |
211 | | testFinished: function (data) { |
212 | 1 | this.data.tests.push({ |
213 | | id: data.id, |
214 | | name: data.name, |
215 | | browser: this.browser, |
216 | | status: data.status, |
217 | | passedAssertions: data.passedAssertions, |
218 | | failedAssertions: data.failedAssertions, |
219 | | actions: this.actionQueue |
220 | | }); |
221 | 1 | return this; |
222 | | }, |
223 | | |
224 | | /** |
225 | | * Serializes JSON and writes file to the file system |
226 | | * |
227 | | * @method runnerFinished |
228 | | * @param {object} data Event data |
229 | | * @chainable |
230 | | */ |
231 | | |
232 | | runnerFinished: function (data) { |
233 | 0 | this.data.elapsedTime = data.elapsedTime; |
234 | 0 | this.data.status = data.status; |
235 | 0 | this.data.assertions = data.assertions; |
236 | 0 | this.data.assertionsFailed = data.assertionsFailed; |
237 | 0 | this.data.assertionsPassed = data.assertionsPassed; |
238 | | |
239 | 0 | var contents = JSON.stringify(this.data, false, 4); |
240 | | |
241 | 0 | if (path.extname(this.dest) !== '.json') { |
242 | 0 | this.dest = this.dest + '/dalek.json'; |
243 | | } |
244 | | |
245 | 0 | this.events.emit('report:written', {type: 'json', dest: this.dest}); |
246 | 0 | this._recursiveMakeDirSync(path.dirname(this.dest.replace(path.basename(this.dest, '')))); |
247 | 0 | fs.writeFileSync(this.dest, contents, 'utf8'); |
248 | 0 | return this; |
249 | | }, |
250 | | |
251 | | /** |
252 | | * Generates JSON for a message.log |
253 | | * |
254 | | * @method assertion |
255 | | * @param {object} data Event data |
256 | | * @chainable |
257 | | */ |
258 | | |
259 | | messageLog: function(data) { |
260 | 0 | var logData = { |
261 | | kind: 'message', |
262 | | message: data |
263 | | }; |
264 | 0 | this.actionQueue.push(logData); |
265 | 0 | return this; |
266 | | }, |
267 | | |
268 | | /** |
269 | | * Generates JSON for a screenshot |
270 | | * |
271 | | * @method assertion |
272 | | * @param {object} data Event data |
273 | | * @chainable |
274 | | */ |
275 | | |
276 | | screenshot : function(data) { |
277 | 0 | data.kind = 'screenshot'; |
278 | 0 | this.actionQueue.push(data); |
279 | 0 | return this; |
280 | | }, |
281 | | |
282 | | |
283 | | /** |
284 | | * Helper method to generate deeper nested directory structures |
285 | | * |
286 | | * @method _recursiveMakeDirSync |
287 | | * @param {string} path PAth to create |
288 | | */ |
289 | | |
290 | | _recursiveMakeDirSync: function (path) { |
291 | 0 | var pathSep = require('path').sep; |
292 | 0 | var dirs = path.split(pathSep); |
293 | 0 | var root = ''; |
294 | | |
295 | 0 | while (dirs.length > 0) { |
296 | 0 | var dir = dirs.shift(); |
297 | 0 | if (dir === '') { |
298 | 0 | root = pathSep; |
299 | | } |
300 | 0 | if (!fs.existsSync(root + dir)) { |
301 | 0 | fs.mkdirSync(root + dir); |
302 | | } |
303 | 0 | root += dir + pathSep; |
304 | | } |
305 | | } |
306 | | }; |
307 | | |