UNPKG

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