scratch – Blame information for rev 84
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
58 | office | 1 | /* globals jasmineRequire, phantom */ |
2 | // Verify arguments |
||
3 | var system = require('system'); |
||
4 | var args; |
||
5 | |||
6 | if(phantom.args) { |
||
7 | args = phantom.args; |
||
8 | } else { |
||
9 | args = system.args.slice(1);//use system args for phantom 2.0+ |
||
10 | } |
||
11 | |||
12 | if (args.length === 0) { |
||
13 | console.log("Simple JasmineBDD test runner for phantom.js"); |
||
14 | console.log("Usage: phantomjs-testrunner.js url_to_runner.html"); |
||
15 | console.log("Accepts http:// and file:// urls"); |
||
16 | console.log(""); |
||
17 | console.log("NOTE: This script depends on jasmine.HtmlReporter being used\non the page, for the DOM elements it creates.\n"); |
||
18 | phantom.exit(2); |
||
19 | } |
||
20 | else { |
||
21 | var fs = require("fs"), |
||
22 | pages = [], |
||
23 | page, address, resultsKey, i, l; |
||
24 | |||
25 | |||
26 | var setupPageFn = function(p, k) { |
||
27 | return function() { |
||
28 | overloadPageEvaluate(p); |
||
29 | setupWriteFileFunction(p, k, fs.separator); |
||
30 | }; |
||
31 | }; |
||
32 | |||
33 | for (i = 0, l = args.length; i < l; i++) { |
||
34 | address = args[i]; |
||
35 | console.log("Loading " + address); |
||
36 | |||
37 | // if provided a url without a protocol, try to use file:// |
||
38 | address = address.indexOf("://") === -1 ? "file://" + address : address; |
||
39 | |||
40 | // create a WebPage object to work with |
||
41 | page = require("webpage").create(); |
||
42 | page.url = address; |
||
43 | |||
44 | // When initialized, inject the reporting functions before the page is loaded |
||
45 | // (and thus before it will try to utilize the functions) |
||
46 | resultsKey = "__jr" + Math.ceil(Math.random() * 1000000); |
||
47 | page.onInitialized = setupPageFn(page, resultsKey); |
||
48 | page.open(address, processPage(null, page, resultsKey)); |
||
49 | pages.push(page); |
||
50 | |||
51 | page.onConsoleMessage = logAndWorkAroundDefaultLineBreaking; |
||
52 | } |
||
53 | |||
54 | // bail when all pages have been processed |
||
55 | setInterval(function(){ |
||
56 | var exit_code = 0; |
||
57 | for (i = 0, l = pages.length; i < l; i++) { |
||
58 | page = pages[i]; |
||
59 | if (page.__exit_code === null) { |
||
60 | // wait until later |
||
61 | return; |
||
62 | } |
||
63 | exit_code |= page.__exit_code; |
||
64 | } |
||
65 | phantom.exit(exit_code); |
||
66 | }, 100); |
||
67 | } |
||
68 | |||
69 | // Thanks to hoisting, these helpers are still available when needed above |
||
70 | /** |
||
71 | * Logs a message. Does not add a line-break for single characters '.' and 'F' or lines ending in ' ...' |
||
72 | * |
||
73 | * @param msg |
||
74 | */ |
||
75 | function logAndWorkAroundDefaultLineBreaking(msg) { |
||
76 | var interpretAsWithoutNewline = /(^(\033\[\d+m)*[\.F](\033\[\d+m)*$)|( \.\.\.$)/; |
||
77 | if (navigator.userAgent.indexOf("Windows") < 0 && interpretAsWithoutNewline.test(msg)) { |
||
78 | try { |
||
79 | system.stdout.write(msg); |
||
80 | } catch (e) { |
||
81 | var fs = require('fs'); |
||
82 | fs.write('/dev/stdout', msg, 'w'); |
||
83 | } |
||
84 | } else { |
||
85 | console.log(msg); |
||
86 | } |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * Stringifies the function, replacing any %placeholders% with mapped values. |
||
91 | * |
||
92 | * @param {function} fn The function to replace occurrences within. |
||
93 | * @param {object} replacements Key => Value object of string replacements. |
||
94 | */ |
||
95 | function replaceFunctionPlaceholders(fn, replacements) { |
||
96 | if (replacements && typeof replacements === "object") { |
||
97 | fn = fn.toString(); |
||
98 | for (var p in replacements) { |
||
99 | if (replacements.hasOwnProperty(p)) { |
||
100 | var match = new RegExp("%" + p + "%", "g"); |
||
101 | do { |
||
102 | fn = fn.replace(match, replacements[p]); |
||
103 | } while(fn.indexOf(match) !== -1); |
||
104 | } |
||
105 | } |
||
106 | } |
||
107 | return fn; |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Replaces the "evaluate" method with one we can easily do substitution with. |
||
112 | * |
||
113 | * @param {phantomjs.WebPage} page The WebPage object to overload |
||
114 | */ |
||
115 | function overloadPageEvaluate(page) { |
||
116 | page._evaluate = page.evaluate; |
||
117 | page.evaluate = function(fn, replacements) { return page._evaluate(replaceFunctionPlaceholders(fn, replacements)); }; |
||
118 | return page; |
||
119 | } |
||
120 | |||
121 | /** Stubs a fake writeFile function into the test runner. |
||
122 | * |
||
123 | * @param {phantomjs.WebPage} page The WebPage object to inject functions into. |
||
124 | * @param {string} key The name of the global object in which file data should |
||
125 | * be stored for later retrieval. |
||
126 | */ |
||
127 | // TODO: not bothering with error checking for now (closed environment) |
||
128 | function setupWriteFileFunction(page, key, path_separator) { |
||
129 | page.evaluate(function(){ |
||
130 | window["%resultsObj%"] = {}; |
||
131 | window.fs_path_separator = "%fs_path_separator%"; |
||
132 | window.__phantom_writeFile = function(filename, text) { |
||
133 | window["%resultsObj%"][filename] = text; |
||
134 | }; |
||
135 | }, {resultsObj: key, fs_path_separator: path_separator.replace("\\", "\\\\")}); |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Returns the loaded page's filename => output object. |
||
140 | * |
||
141 | * @param {phantomjs.WebPage} page The WebPage object to retrieve data from. |
||
142 | * @param {string} key The name of the global object to be returned. Should |
||
143 | * be the same key provided to setupWriteFileFunction. |
||
144 | */ |
||
145 | function getXmlResults(page, key) { |
||
146 | return page.evaluate(function(){ |
||
147 | return window["%resultsObj%"] || {}; |
||
148 | }, {resultsObj: key}); |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Processes a page. |
||
153 | * |
||
154 | * @param {string} status The status from opening the page via WebPage#open. |
||
155 | * @param {phantomjs.WebPage} page The WebPage to be processed. |
||
156 | */ |
||
157 | function processPage(status, page, resultsKey) { |
||
158 | if (status === null && page) { |
||
159 | page.__exit_code = null; |
||
160 | return function(stat){ |
||
161 | processPage(stat, page, resultsKey); |
||
162 | }; |
||
163 | } |
||
164 | if (status !== "success") { |
||
165 | console.error("Unable to load resource: " + address); |
||
166 | page.__exit_code = 2; |
||
167 | } |
||
168 | else { |
||
169 | var isFinished = function() { |
||
170 | return page.evaluate(function(){ |
||
171 | // if there's a JUnitXmlReporter, return a boolean indicating if it is finished |
||
172 | if (window.jasmineReporters && window.jasmineReporters.startTime) { |
||
173 | return !!window.jasmineReporters.endTime; |
||
174 | } |
||
175 | // otherwise, scrape the DOM for the HtmlReporter "finished in ..." output |
||
176 | var durElem = document.querySelector(".html-reporter .duration"); |
||
177 | if (!durElem) { |
||
178 | durElem = document.querySelector(".jasmine_html-reporter .duration"); |
||
179 | } |
||
180 | return durElem && durElem.textContent && durElem.textContent.toLowerCase().indexOf("finished in") === 0; |
||
181 | }); |
||
182 | }; |
||
183 | var getResultsFromHtmlRunner = function() { |
||
184 | return page.evaluate(function(){ |
||
185 | var resultElem = document.querySelector(".html-reporter .alert .bar"); |
||
186 | if (!resultElem) { |
||
187 | resultElem = document.querySelector(".jasmine_html-reporter .alert .bar"); |
||
188 | } |
||
189 | return resultElem && resultElem.textContent && |
||
190 | resultElem.textContent.match(/(\d+) spec.* (\d+) failure.*/) || |
||
191 | ["Unable to determine success or failure."]; |
||
192 | }); |
||
193 | }; |
||
194 | var timeout = 60000; |
||
195 | var loopInterval = 100; |
||
196 | var ival = setInterval(function(){ |
||
197 | if (isFinished()) { |
||
198 | // get the results that need to be written to disk |
||
199 | var fs = require("fs"), |
||
200 | xml_results = getXmlResults(page, resultsKey), |
||
201 | output; |
||
202 | for (var filename in xml_results) { |
||
203 | if (xml_results.hasOwnProperty(filename) && (output = xml_results[filename]) && typeof(output) === "string") { |
||
204 | fs.write(filename, output, "w"); |
||
205 | } |
||
206 | } |
||
207 | |||
208 | // print out a success / failure message of the results |
||
209 | var results = getResultsFromHtmlRunner(); |
||
210 | var failures = Number(results[2]); |
||
211 | if (failures > 0) { |
||
212 | page.__exit_code = 1; |
||
213 | clearInterval(ival); |
||
214 | } |
||
215 | else { |
||
216 | page.__exit_code = 0; |
||
217 | clearInterval(ival); |
||
218 | } |
||
219 | } |
||
220 | else { |
||
221 | timeout -= loopInterval; |
||
222 | if (timeout <= 0) { |
||
223 | console.log('Page has timed out; aborting.'); |
||
224 | page.__exit_code = 2; |
||
225 | clearInterval(ival); |
||
226 | } |
||
227 | } |
||
228 | }, loopInterval); |
||
229 | } |
||
230 | } |