phantomjs-testrunner.js 8.1 KB

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