| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- /* globals jasmineRequire, phantom */
- // Verify arguments
- var system = require('system');
- var args;
- if(phantom.args) {
- args = phantom.args;
- } else {
- args = system.args.slice(1);//use system args for phantom 2.0+
- }
- if (args.length === 0) {
- console.log("Simple JasmineBDD test runner for phantom.js");
- console.log("Usage: phantomjs-testrunner.js url_to_runner.html");
- console.log("Accepts http:// and file:// urls");
- console.log("");
- console.log("NOTE: This script depends on jasmine.HtmlReporter being used\non the page, for the DOM elements it creates.\n");
- phantom.exit(2);
- }
- else {
- var fs = require("fs"),
- pages = [],
- page, address, resultsKey, i, l;
- var setupPageFn = function(p, k) {
- return function() {
- setupWriteFileFunction(p, k, fs.separator);
- };
- };
- for (i = 0, l = args.length; i < l; i++) {
- address = args[i];
- console.log("Loading " + address);
- // if provided a url without a protocol, try to use file://
- address = address.indexOf("://") === -1 ? "file://" + address : address;
- // create a WebPage object to work with
- page = require("webpage").create();
- page.url = address;
- // When initialized, inject the reporting functions before the page is loaded
- // (and thus before it will try to utilize the functions)
- resultsKey = "__jr" + Math.ceil(Math.random() * 1000000);
- page.onInitialized = setupPageFn(page, resultsKey);
- page.open(address, processPage(null, page, resultsKey));
- pages.push(page);
- page.onConsoleMessage = logAndWorkAroundDefaultLineBreaking;
- }
- // bail when all pages have been processed
- setInterval(function(){
- var exit_code = 0;
- for (i = 0, l = pages.length; i < l; i++) {
- page = pages[i];
- if (page.__exit_code === null) {
- // wait until later
- return;
- }
- exit_code |= page.__exit_code;
- }
- phantom.exit(exit_code);
- }, 100);
- }
- // Thanks to hoisting, these helpers are still available when needed above
- /**
- * Logs a message. Does not add a line-break for single characters '.' and 'F' or lines ending in ' ...'
- *
- * @param msg
- */
- function logAndWorkAroundDefaultLineBreaking(msg) {
- var interpretAsWithoutNewline = /(^(\033\[\d+m)*[\.F](\033\[\d+m)*$)|( \.\.\.$)/;
- if (navigator.userAgent.indexOf("Windows") < 0 && interpretAsWithoutNewline.test(msg)) {
- try {
- system.stdout.write(msg);
- } catch (e) {
- var fs = require('fs');
- fs.write('/dev/stdout', msg, 'w');
- }
- } else {
- console.log(msg);
- }
- }
- /**
- * Stringifies the function, replacing any %placeholders% with mapped values.
- *
- * @param {function} fn The function to replace occurrences within.
- * @param {object} replacements Key => Value object of string replacements.
- */
- function replaceFunctionPlaceholders(fn, replacements) {
- if (replacements && typeof replacements === "object") {
- fn = fn.toString();
- for (var p in replacements) {
- if (replacements.hasOwnProperty(p)) {
- var match = new RegExp("%" + p + "%", "g");
- do {
- fn = fn.replace(match, replacements[p]);
- } while(fn.indexOf(match) !== -1);
- }
- }
- }
- return fn;
- }
- /**
- * Custom "evaluate" method which we can easily do substitution with.
- *
- * @param {phantomjs.WebPage} page The WebPage object to overload
- * @param {function} fn The function to replace occurrences within.
- * @param {object} replacements Key => Value object of string replacements.
- */
- function evaluate(page, fn, replacements) {
- return page.evaluate(replaceFunctionPlaceholders(fn, replacements));
- }
- /** Stubs a fake writeFile function into the test runner.
- *
- * @param {phantomjs.WebPage} page The WebPage object to inject functions into.
- * @param {string} key The name of the global object in which file data should
- * be stored for later retrieval.
- */
- // TODO: not bothering with error checking for now (closed environment)
- function setupWriteFileFunction(page, key, path_separator) {
- evaluate(page, function(){
- window["%resultsObj%"] = {};
- window.fs_path_separator = "%fs_path_separator%";
- window.__phantom_writeFile = function(filename, text) {
- window["%resultsObj%"][filename] = text;
- };
- }, {resultsObj: key, fs_path_separator: path_separator.replace("\\", "\\\\")});
- }
- /**
- * Returns the loaded page's filename => output object.
- *
- * @param {phantomjs.WebPage} page The WebPage object to retrieve data from.
- * @param {string} key The name of the global object to be returned. Should
- * be the same key provided to setupWriteFileFunction.
- */
- function getXmlResults(page, key) {
- return evaluate(page, function(){
- return window["%resultsObj%"] || {};
- }, {resultsObj: key});
- }
- /**
- * Processes a page.
- *
- * @param {string} status The status from opening the page via WebPage#open.
- * @param {phantomjs.WebPage} page The WebPage to be processed.
- */
- function processPage(status, page, resultsKey) {
- if (status === null && page) {
- page.__exit_code = null;
- return function(stat){
- processPage(stat, page, resultsKey);
- };
- }
- if (status !== "success") {
- console.error("Unable to load resource: " + address);
- page.__exit_code = 2;
- }
- else {
- var isFinished = function() {
- return evaluate(page, function(){
- // if there's a JUnitXmlReporter, return a boolean indicating if it is finished
- if (window.jasmineReporters && window.jasmineReporters.startTime) {
- return !!window.jasmineReporters.endTime;
- }
- // otherwise, scrape the DOM for the HtmlReporter "finished in ..." output
- var durElem = document.querySelector(".html-reporter .duration");
- if (!durElem) {
- durElem = document.querySelector(".jasmine_html-reporter .duration");
- }
- return durElem && durElem.textContent && durElem.textContent.toLowerCase().indexOf("finished in") === 0;
- });
- };
- var getResultsFromHtmlRunner = function() {
- return evaluate(page, function(){
- var resultElem = document.querySelector(".html-reporter .alert .bar");
- if (!resultElem) {
- resultElem = document.querySelector(".jasmine_html-reporter .alert .bar");
- }
- return resultElem && resultElem.textContent &&
- resultElem.textContent.match(/(\d+) spec.* (\d+) failure.*/) ||
- ["Unable to determine success or failure."];
- });
- };
- var timeout = 60000;
- var loopInterval = 100;
- var ival = setInterval(function(){
- if (isFinished()) {
- // get the results that need to be written to disk
- var fs = require("fs"),
- xml_results = getXmlResults(page, resultsKey),
- output;
- for (var filename in xml_results) {
- if (xml_results.hasOwnProperty(filename) && (output = xml_results[filename]) && typeof(output) === "string") {
- fs.write(filename, output, "w");
- }
- }
- // print out a success / failure message of the results
- var results = getResultsFromHtmlRunner();
- var failures = Number(results[2]);
- if (failures > 0) {
- page.__exit_code = 1;
- clearInterval(ival);
- }
- else {
- page.__exit_code = 0;
- clearInterval(ival);
- }
- }
- else {
- timeout -= loopInterval;
- if (timeout <= 0) {
- console.log('Page has timed out; aborting.');
- page.__exit_code = 2;
- clearInterval(ival);
- }
- }
- }, loopInterval);
- }
- }
|