Skip to content

Commit e3c2031

Browse files
First attempt at integrating headless PhantomJS tests.
1 parent eb9fdc9 commit e3c2031

File tree

4 files changed

+352
-1
lines changed

4 files changed

+352
-1
lines changed

Rakefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ EOF
109109
end
110110
end
111111

112+
def self.require_phantomjs
113+
success = system("phantomjs -v > /dev/null 2>&1")
114+
if !success
115+
puts "\nYou need phantomjs installed to run this task. Find out how at:"
116+
puts " http://phantomjs.org/download.html"
117+
exit
118+
end
119+
end
120+
112121
def self.syntax_highlighter
113122
if ENV['SYNTAX_HIGHLIGHTER']
114123
highlighter = ENV['SYNTAX_HIGHLIGHTER'].to_sym
@@ -374,6 +383,14 @@ namespace :test_new do
374383
Runner::run(browsers, tests, grep)
375384
end
376385

386+
task :phantom => [:require] do
387+
PrototypeHelper.require_phantomjs
388+
tests, grep = ENV['TESTS'], ENV['GREP']
389+
url = "http://127.0.0.1:4567/test/#{tests}"
390+
url << "?grep=#{grep}" if grep
391+
system(%Q[phantomjs ./test.new/phantomjs/mocha-phantomjs.js "#{url}"])
392+
end
393+
377394
end
378395

379396
namespace :caja do

test.new/phantomjs/core-extensions.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*!
2+
* PhantomJS Runners for Mocha
3+
* https://github.com/metaskills/mocha-phantomjs/
4+
*
5+
* Copyright (c) 2012 Ken Collins
6+
* Released under the MIT license
7+
* http://github.com/metaskills/mocha-phantomjs/blob/master/MIT-LICENSE
8+
*
9+
*/
10+
11+
(function(){
12+
13+
// A shim for non ES5 supporting browsers, like PhantomJS. Lovingly inspired by:
14+
// http://www.angrycoding.com/2011/09/to-bind-or-not-to-bind-that-is-in.html
15+
if (!('bind' in Function.prototype)) {
16+
Function.prototype.bind = function() {
17+
var funcObj = this;
18+
var extraArgs = Array.prototype.slice.call(arguments);
19+
var thisObj = extraArgs.shift();
20+
return function() {
21+
return funcObj.apply(thisObj, extraArgs.concat(Array.prototype.slice.call(arguments)));
22+
};
23+
};
24+
}
25+
26+
// Mocha needs a process.stdout.write in order to change the cursor position.
27+
Mocha.process = Mocha.process || {};
28+
Mocha.process.stdout = Mocha.process.stdout || process.stdout;
29+
Mocha.process.stdout.write = function(s) { window.callPhantom({"Mocha.process.stdout.write":s}); }
30+
31+
// Mocha needs the formating feature of console.log so copy node's format function and
32+
// monkey-patch it into place. This code is copied from node's, links copyright applies.
33+
// https://github.com/joyent/node/blob/master/lib/util.js
34+
console.format = function(f) {
35+
if (typeof f !== 'string') {
36+
var objects = [];
37+
for (var i = 0; i < arguments.length; i++) {
38+
objects.push(JSON.stringify(arguments[i]));
39+
}
40+
return objects.join(' ');
41+
}
42+
var i = 1;
43+
var args = arguments;
44+
var len = args.length;
45+
var str = String(f).replace(/%[sdj%]/g, function(x) {
46+
if (x === '%%') return '%';
47+
if (i >= len) return x;
48+
switch (x) {
49+
case '%s': return String(args[i++]);
50+
case '%d': return Number(args[i++]);
51+
case '%j': return JSON.stringify(args[i++]);
52+
default:
53+
return x;
54+
}
55+
});
56+
for (var x = args[i]; i < len; x = args[++i]) {
57+
if (x === null || typeof x !== 'object') {
58+
str += ' ' + x;
59+
} else {
60+
str += ' ' + JSON.stringify(x);
61+
}
62+
}
63+
return str;
64+
};
65+
var origError = console.error;
66+
console.error = function(){ origError.call(console, console.format.apply(console, arguments)); };
67+
var origLog = console.log;
68+
console.log = function(){ origLog.call(console, console.format.apply(console, arguments)); };
69+
70+
})();

test.new/phantomjs/mocha-phantomjs.js

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*!
2+
* PhantomJS Runners for Mocha
3+
* https://github.com/metaskills/mocha-phantomjs/
4+
*
5+
* Copyright (c) 2012 Ken Collins
6+
* Released under the MIT license
7+
* http://github.com/metaskills/mocha-phantomjs/blob/master/MIT-LICENSE
8+
*
9+
*/
10+
11+
// Generated by CoffeeScript 1.7.1
12+
(function() {
13+
var Reporter, USAGE, config, mocha, reporter, system, webpage,
14+
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
15+
16+
system = require('system');
17+
18+
webpage = require('webpage');
19+
20+
USAGE = "Usage: phantomjs mocha-phantomjs.coffee URL REPORTER [CONFIG]";
21+
22+
Reporter = (function() {
23+
function Reporter(reporter, config) {
24+
this.reporter = reporter;
25+
this.config = config;
26+
this.checkStarted = __bind(this.checkStarted, this);
27+
this.waitForRunMocha = __bind(this.waitForRunMocha, this);
28+
this.waitForInitMocha = __bind(this.waitForInitMocha, this);
29+
this.waitForMocha = __bind(this.waitForMocha, this);
30+
this.url = system.args[1];
31+
this.columns = parseInt(system.env.COLUMNS || 75) * .75 | 0;
32+
this.mochaStarted = false;
33+
this.mochaStartWait = this.config.timeout || 6000;
34+
this.startTime = Date.now();
35+
if (!this.url) {
36+
this.fail(USAGE);
37+
}
38+
}
39+
40+
Reporter.prototype.run = function() {
41+
this.initPage();
42+
return this.loadPage();
43+
};
44+
45+
Reporter.prototype.customizeMocha = function(options) {
46+
return Mocha.reporters.Base.window.width = options.columns;
47+
};
48+
49+
Reporter.prototype.customizeOptions = function() {
50+
return {
51+
columns: this.columns
52+
};
53+
};
54+
55+
Reporter.prototype.fail = function(msg, errno) {
56+
if (msg) {
57+
console.log(msg);
58+
}
59+
return phantom.exit(errno || 1);
60+
};
61+
62+
Reporter.prototype.finish = function() {
63+
return phantom.exit(this.page.evaluate(function() {
64+
return mochaPhantomJS.failures;
65+
}));
66+
};
67+
68+
Reporter.prototype.initPage = function() {
69+
var cookie, _i, _len, _ref;
70+
this.page = webpage.create({
71+
settings: this.config.settings
72+
});
73+
if (this.config.headers) {
74+
this.page.customHeaders = this.config.headers;
75+
}
76+
_ref = this.config.cookies || [];
77+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
78+
cookie = _ref[_i];
79+
this.page.addCookie(cookie);
80+
}
81+
if (this.config.viewportSize) {
82+
this.page.viewportSize = this.config.viewportSize;
83+
}
84+
this.page.onConsoleMessage = function(msg) {
85+
return system.stdout.writeLine(msg);
86+
};
87+
this.page.onError = (function(_this) {
88+
return function(msg, traces) {
89+
var file, index, line, _j, _len1, _ref1;
90+
if (_this.page.evaluate(function() {
91+
return window.onerror != null;
92+
})) {
93+
return;
94+
}
95+
for (index = _j = 0, _len1 = traces.length; _j < _len1; index = ++_j) {
96+
_ref1 = traces[index], line = _ref1.line, file = _ref1.file;
97+
traces[index] = " " + file + ":" + line;
98+
}
99+
return _this.fail("" + msg + "\n\n" + (traces.join('\n')));
100+
};
101+
})(this);
102+
return this.page.onInitialized = (function(_this) {
103+
return function() {
104+
return _this.page.evaluate(function(env) {
105+
return window.mochaPhantomJS = {
106+
env: env,
107+
failures: 0,
108+
ended: false,
109+
started: false,
110+
run: function() {
111+
mochaPhantomJS.started = true;
112+
return window.callPhantom({
113+
'mochaPhantomJS.run': true
114+
});
115+
}
116+
};
117+
}, system.env);
118+
};
119+
})(this);
120+
};
121+
122+
Reporter.prototype.loadPage = function() {
123+
this.page.open(this.url);
124+
this.page.onLoadFinished = (function(_this) {
125+
return function(status) {
126+
_this.page.onLoadFinished = function() {};
127+
if (status !== 'success') {
128+
_this.onLoadFailed();
129+
}
130+
return _this.waitForInitMocha();
131+
};
132+
})(this);
133+
return this.page.onCallback = (function(_this) {
134+
return function(data) {
135+
if (data.hasOwnProperty('Mocha.process.stdout.write')) {
136+
system.stdout.write(data['Mocha.process.stdout.write']);
137+
} else if (data.hasOwnProperty('mochaPhantomJS.run')) {
138+
if (_this.injectJS()) {
139+
_this.waitForRunMocha();
140+
}
141+
}
142+
return true;
143+
};
144+
})(this);
145+
};
146+
147+
Reporter.prototype.onLoadFailed = function() {
148+
return this.fail("Failed to load the page. Check the url: " + this.url);
149+
};
150+
151+
Reporter.prototype.injectJS = function() {
152+
if (this.page.evaluate(function() {
153+
return window.mocha != null;
154+
})) {
155+
this.page.injectJs('core-extensions.js');
156+
this.page.evaluate(this.customizeMocha, this.customizeOptions());
157+
return true;
158+
} else {
159+
this.fail("Failed to find mocha on the page.");
160+
return false;
161+
}
162+
};
163+
164+
Reporter.prototype.runMocha = function() {
165+
if (this.config.useColors === false) {
166+
this.page.evaluate(function() {
167+
return Mocha.reporters.Base.useColors = false;
168+
});
169+
}
170+
this.page.evaluate(this.runner, this.reporter);
171+
this.mochaStarted = this.page.evaluate(function() {
172+
return mochaPhantomJS.runner || false;
173+
});
174+
if (this.mochaStarted) {
175+
this.mochaRunAt = new Date().getTime();
176+
return this.waitForMocha();
177+
} else {
178+
return this.fail("Failed to start mocha.");
179+
}
180+
};
181+
182+
Reporter.prototype.waitForMocha = function() {
183+
var ended;
184+
ended = this.page.evaluate(function() {
185+
return mochaPhantomJS.ended;
186+
});
187+
if (ended) {
188+
return this.finish();
189+
} else {
190+
return setTimeout(this.waitForMocha, 100);
191+
}
192+
};
193+
194+
Reporter.prototype.waitForInitMocha = function() {
195+
if (!this.checkStarted()) {
196+
return setTimeout(this.waitForInitMocha, 100);
197+
}
198+
};
199+
200+
Reporter.prototype.waitForRunMocha = function() {
201+
if (this.checkStarted()) {
202+
return this.runMocha();
203+
} else {
204+
return setTimeout(this.waitForRunMocha, 100);
205+
}
206+
};
207+
208+
Reporter.prototype.checkStarted = function() {
209+
var started;
210+
started = this.page.evaluate(function() {
211+
return mochaPhantomJS.started;
212+
});
213+
if (!started && this.mochaStartWait && this.startTime + this.mochaStartWait < Date.now()) {
214+
this.fail("Failed to start mocha: Init timeout", 255);
215+
}
216+
return started;
217+
};
218+
219+
Reporter.prototype.runner = function(reporter) {
220+
var cleanup, error, _ref, _ref1;
221+
try {
222+
mocha.setup({
223+
reporter: reporter
224+
});
225+
mochaPhantomJS.runner = mocha.run();
226+
if (mochaPhantomJS.runner) {
227+
cleanup = function() {
228+
mochaPhantomJS.failures = mochaPhantomJS.runner.failures;
229+
return mochaPhantomJS.ended = true;
230+
};
231+
if ((_ref = mochaPhantomJS.runner) != null ? (_ref1 = _ref.stats) != null ? _ref1.end : void 0 : void 0) {
232+
return cleanup();
233+
} else {
234+
return mochaPhantomJS.runner.on('end', cleanup);
235+
}
236+
}
237+
} catch (_error) {
238+
error = _error;
239+
return false;
240+
}
241+
};
242+
243+
return Reporter;
244+
245+
})();
246+
247+
if (phantom.version.major !== 1 || phantom.version.minor < 9) {
248+
console.log('mocha-phantomjs requires PhantomJS > 1.9.1');
249+
phantom.exit(-1);
250+
}
251+
252+
reporter = system.args[2] || 'spec';
253+
254+
config = JSON.parse(system.args[3] || '{}');
255+
256+
mocha = new Reporter(reporter, config);
257+
258+
mocha.run();
259+
260+
}).call(this);

test.new/views/layout.erb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@
8787

8888
<script type="text/javascript">
8989
Test.setup();
90-
mocha.run();
90+
if (window.mochaPhantomJS) {
91+
mochaPhantomJS.run();
92+
} else {
93+
mocha.run();
94+
}
9195

9296
mocha.suite.suites.each(function (suite) {
9397
suite.beforeAll(function () {

0 commit comments

Comments
 (0)