diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/bridge.js b/bridge.js
new file mode 100644
index 0000000..98ad572
--- /dev/null
+++ b/bridge.js
@@ -0,0 +1,94 @@
+var port=phantom.args[0];
+var webpage=require('webpage');
+var controlpage=webpage.create();
+
+
+function respond(response){
+// console.log('responding:'+response);
+ controlpage.evaluate('function(){socket.emit("res",'+JSON.stringify(response)+');}');
+}
+
+var pages={};
+var pageId=1;
+
+
+controlpage.onAlert=function(msg){
+ var request=JSON.parse(msg);
+ var cmdId=request[1];
+// console.log(request);
+ if(request[0]===0){
+ switch(request[2]){
+ case 'createPage':
+ var id=pageId++;
+ var page=webpage.create();
+ pages[id]=page;
+ respond([id,cmdId,'pageCreated']);
+ break;
+ case 'injectJs':
+ var success=phantom.injectJs(request[3]);
+ respond([0,cmdId,'jsInjected',success]);
+ break;
+ case 'exit':
+ respond([0,cmdId,'phantomExited']); //optimistically to get the response back before the line is cut
+ break;
+ case 'exitAck':
+ phantom.exit();
+ break;
+ default:
+ console.error('unrecognized request:'+request);
+ break;
+ }
+ }
+ else{
+ var id=request[0];
+ var page=pages[id];
+ switch(request[2]){
+ case 'pageOpen':
+ page.open(request[3],function(status){
+ respond([id,cmdId,'pageOpened',status]);
+ });
+ break;
+ case 'pageRelease':
+ page.release();
+ respond([id,cmdId,'pageReleased']);
+ break;
+ case 'pageInjectJs':
+ var result=page.injectJs(request[3]);
+ respond([id,cmdId,'pageJsInjected',JSON.stringify(result)]);
+ break;
+ case 'pageIncludeJs':
+ page.includeJs(request[3]);
+ respond([id,cmdId,'pageJsIncluded']);
+ break;
+ case 'pageSendEvent':
+ page.sendEvent(request[3],request[4],request[5]);
+ respond([id,cmdId,'pageEventSent']);
+ break;
+ case 'pageUploadFile':
+ page.uploadFile(request[3],request[4]);
+ respond([id,cmdId,'pageFileUploaded']);
+ break;
+ case 'pageEvaluate':
+ var result=page.evaluate(request[3]);
+ respond([id,cmdId,'pageEvaluated',JSON.stringify(result)]);
+ break;
+ case 'pageRender':
+ page.render(request[3]);
+ respond([id,cmdId,'pageRendered',JSON.stringify(result)]);
+ break;
+ default:
+ console.error('unrecognized request:'+request);
+ break;
+ }
+ }
+ //console.log('command:'+parts[1]);
+ return;
+};
+
+controlpage.onConsoleMessage=function(msg){
+ return console.log('console msg:'+msg);
+};
+
+controlpage.open('http://127.0.0.1:'+port+'/',function(status){
+ //console.log(status);
+});
diff --git a/phantom-node.js b/phantom-node.js
new file mode 100644
index 0000000..2392728
--- /dev/null
+++ b/phantom-node.js
@@ -0,0 +1,140 @@
+var http=require('http');
+var socketio=require('socket.io');
+var child=require('child_process');
+
+function callbackOrDummy(callback){
+ if(callback===undefined)callback=function(){};
+ return callback;
+}
+
+module.exports={
+ create:function(callback){
+ function spawnPhantom(port){
+ var phantom=child.spawn('phantomjs',[__dirname + '/bridge.js',port]);
+ phantom.stdout.on('data',function(data){
+ return console.log('phantom stdout: '+data);
+ });
+ phantom.stderr.on('data',function(data){
+ if (data.toString('utf8').match(/No such method.*socketSentData/)) return;
+ return console.warn('phantom stderr: '+data);
+ });
+ return phantom;
+ };
+
+ var server=http.createServer(function(request,response){
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('
');
+ }).listen();
+
+ var port=server.address().port;
+ var phantom=spawnPhantom(port);
+
+ var cmds={};
+ var cmdid=0;
+ function request(socket,args,callback){
+ args.splice(1,0,cmdid);
+// console.log('requesting:'+args);
+ socket.emit('cmd',JSON.stringify(args));
+
+ cmds[cmdid]={cb:callback};
+ cmdid++;
+ }
+
+ var io=socketio.listen(server,{'log level':1});
+
+ io.sockets.on('connection',function(socket){
+ socket.on('res',function(response){
+// console.log(response);
+ var id=response[0];
+ var cmdId=response[1];
+ switch(response[2]){
+ case 'pageCreated':
+ var pageProxy={
+ open:function(url,callback){
+ request(socket,[id,'pageOpen',url],callbackOrDummy(callback));
+ },
+ release:function(callback){
+ request(socket,[id,'pageRelease'],callbackOrDummy(callback));
+ },
+ render:function(filename,callback){
+ request(socket,[id,'pageRender',filename],callbackOrDummy(callback));
+ },
+ injectJs:function(url,callback){
+ request(socket,[id,'pageInjectJs',url],callbackOrDummy(callback));
+ },
+ includeJs:function(url,callback){
+ request(socket,[id,'pageIncludeJs',url],callbackOrDummy(callback));
+ },
+ sendEvent:function(event,x,y,callback){
+ request(socket,[id,'pageSendEvent',event,x,y],callbackOrDummy(callback));
+ },
+ uploadFile:function(selector,filename,callback){
+ request(socket,[id,'pageUploadFile',selector,filename],callbackOrDummy(callback));
+ },
+ evaluate:function(evaluator,callback){
+ request(socket,[id,'pageEvaluate',evaluator.toString()],callbackOrDummy(callback));
+ }
+ };
+ cmds[cmdId].cb(null,pageProxy);
+ delete cmds[cmdId];
+ break;
+ case 'phantomExited':
+ request(socket,[0,'exitAck']);
+ server.close();
+ cmds[cmdId].cb();
+ delete cmds[cmdId];
+ break;
+ case 'pageJsInjected':
+ case 'jsInjected':
+ cmds[cmdId].cb(JSON.parse(response[3])===true ? null : true);
+ delete cmds[cmdId];
+ break;
+ case 'pageOpened':
+ if(cmds[cmdId]!==undefined){ //if page is redirected, the pageopen event is called again - we do not want that currently.
+ cmds[cmdId].cb(null,response[3]);
+ delete cmds[cmdId];
+ }
+ break;
+ case 'pageEvaluated':
+ cmds[cmdId].cb(null,JSON.parse(response[3]));
+ delete cmds[cmdId];
+ break;
+ case 'pageReleased':
+ case 'pageJsIncluded':
+ case 'pageRendered':
+ case 'pageEventSent':
+ case 'pageFileUploaded':
+ cmds[cmdId].cb(null);
+ delete cmds[cmdId];
+ break;
+ default:
+ console.error('got unrecognized response:'+response);
+ break;
+ }
+ });
+
+ var proxy={
+ createPage:function(callback){
+ request(socket,[0,'createPage'],callbackOrDummy(callback));
+ },
+ injectJs:function(filename,callback){
+ request(socket,[0,'injectJs',filename],callbackOrDummy(callback));
+ },
+ exit:function(callback){
+ request(socket,[0,'exit'],callbackOrDummy(callback));
+ }
+ };
+
+ callback(null,proxy);
+ });
+
+ }
+};
diff --git a/test/files/injecttest.js b/test/files/injecttest.js
new file mode 100644
index 0000000..a4b9803
--- /dev/null
+++ b/test/files/injecttest.js
@@ -0,0 +1 @@
+console.log('injected');
\ No newline at end of file
diff --git a/test/files/modifytest.js b/test/files/modifytest.js
new file mode 100644
index 0000000..a5ae93e
--- /dev/null
+++ b/test/files/modifytest.js
@@ -0,0 +1 @@
+document.getElementsByTagName("h1")[0].innerText="Hello Test";
\ No newline at end of file
diff --git a/test/files/uploadtest.txt b/test/files/uploadtest.txt
new file mode 100644
index 0000000..5e1c309
--- /dev/null
+++ b/test/files/uploadtest.txt
@@ -0,0 +1 @@
+Hello World
\ No newline at end of file
diff --git a/test/files/verifyrender.png b/test/files/verifyrender.png
new file mode 100644
index 0000000..fafc20b
Binary files /dev/null and b/test/files/verifyrender.png differ
diff --git a/test/testcreatepage.js b/test/testcreatepage.js
new file mode 100644
index 0000000..07d9944
--- /dev/null
+++ b/test/testcreatepage.js
@@ -0,0 +1,11 @@
+var phantom=require('../phantom-node');
+
+exports.testPhantomCreatePage=function(beforeExit,assert) {
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ ph.exit();
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testinjectjs.js b/test/testinjectjs.js
new file mode 100644
index 0000000..b6f973c
--- /dev/null
+++ b/test/testinjectjs.js
@@ -0,0 +1,11 @@
+var phantom=require('../phantom-node');
+
+exports.testPhantomInjectJs=function(beforeExit,assert) {
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.injectJs('test/files/injecttest.js',function(err){
+ assert.ifError(err);
+ ph.exit();
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpageevaluate.js b/test/testpageevaluate.js
new file mode 100644
index 0000000..2f544f3
--- /dev/null
+++ b/test/testpageevaluate.js
@@ -0,0 +1,28 @@
+var http=require('http');
+var phantom=require('../phantom-node');
+
+var server=http.createServer(function(request,response){
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('Hello World
');
+}).listen();
+
+exports.testPhantomPageEvaluate=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.open('http://localhost:'+server.address().port,function(err,status){
+ assert.ifError(err);
+ assert.equal(status,'success');
+ page.evaluate(function(){
+ return document.getElementsByTagName('h1')[0].innerText;
+ },function(err,result){
+ assert.ifError(err);
+ assert.equal(result,'Hello World');
+ server.close();
+ ph.exit();
+ });
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpageincludejs.js b/test/testpageincludejs.js
new file mode 100644
index 0000000..a13ead2
--- /dev/null
+++ b/test/testpageincludejs.js
@@ -0,0 +1,41 @@
+var http=require('http');
+var phantom=require('../phantom-node');
+
+var server=http.createServer(function(request,response){
+ if(request.url==='/test.js'){
+ response.writeHead(200,{"Content-Type": "text/javascript"});
+ response.end('document.getElementsByTagName("h1")[0].innerText="Hello Test";');
+ }
+ else{
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('Hello World
');
+ }
+
+}).listen();
+
+exports.testPhantomPageEvaluate=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.open('http://localhost:'+server.address().port,function(err,status){
+ assert.ifError(err);
+ assert.equal(status,'success');
+ page.includeJs('http://localhost:'+server.address().port+'/test.js',function(err){
+ assert.ifError(err);
+ setTimeout(function(){
+ page.evaluate(function(){
+ return [document.getElementsByTagName('h1')[0].innerText,document.getElementsByTagName('script').length];
+ },function(err,result){
+ assert.ifError(err);
+ assert.equal(result[0],'Hello Test'); //the script should have been executed
+ assert.equal(result[1],1); //it should have added a new script-tag (see: https://groups.google.com/forum/?fromgroups#!topic/phantomjs/G4xcnSLrMw8)
+ server.close();
+ ph.exit();
+ });
+ },500); //delay this to make sure the script has been executed
+ });
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpageinjectjs.js b/test/testpageinjectjs.js
new file mode 100644
index 0000000..2922f6a
--- /dev/null
+++ b/test/testpageinjectjs.js
@@ -0,0 +1,41 @@
+var http=require('http');
+var phantom=require('../phantom-node');
+
+var server=http.createServer(function(request,response){
+ if(request.url==='/test.js'){
+ console.log('gotten');
+ response.writeHead(200,{"Content-Type": "text/javascript"});
+ response.end('document.getElementsByTagName("h1")[0].innerText="Hello Test";');
+ }
+ else{
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('Hello World
');
+ }
+
+}).listen();
+
+exports.testPhantomPageEvaluate=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.open('http://localhost:'+server.address().port,function(err,status){
+ assert.ifError(err);
+ assert.equal(status,'success');
+ page.injectJs('test/files/modifytest.js',function(err){
+ //no delay necessary because it should have been executed synchronously
+ assert.ifError(err);
+ page.evaluate(function(){
+ return [document.getElementsByTagName('h1')[0].innerText,document.getElementsByTagName('script').length];
+ },function(err,result){
+ assert.ifError(err);
+ assert.equal(result[0],'Hello Test'); //the script should have been executed
+ assert.equal(result[1],0); //it should not have added a new script-tag (see: https://groups.google.com/forum/?fromgroups#!topic/phantomjs/G4xcnSLrMw8)
+ server.close();
+ ph.exit();
+ });
+ });
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpageopen.js b/test/testpageopen.js
new file mode 100644
index 0000000..5e56563
--- /dev/null
+++ b/test/testpageopen.js
@@ -0,0 +1,22 @@
+var http=require('http');
+var phantom=require('../phantom-node');
+
+var server=http.createServer(function(request,response){
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('Hello World');
+}).listen();
+
+exports.testPhantomPageOpen=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.open('http://localhost:'+server.address().port,function(err,status){
+ assert.ifError(err);
+ assert.equal(status,'success');
+ server.close();
+ ph.exit();
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpagerelease.js b/test/testpagerelease.js
new file mode 100644
index 0000000..2e0f538
--- /dev/null
+++ b/test/testpagerelease.js
@@ -0,0 +1,14 @@
+var phantom=require('../phantom-node');
+
+exports.testPhantomPageRelease=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.release(function(err){
+ assert.ifError(err);
+ ph.exit();
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpagerender.js b/test/testpagerender.js
new file mode 100644
index 0000000..d7dd246
--- /dev/null
+++ b/test/testpagerender.js
@@ -0,0 +1,39 @@
+var http=require('http');
+var phantom=require('../phantom-node');
+var fs=require('fs');
+var crypto = require('crypto');
+
+function fileHash(filename){
+ var shasum=crypto.createHash('sha256');
+ var f=fs.readFileSync(filename);
+ shasum.update(f);
+ return shasum.digest('hex');
+}
+
+var server=http.createServer(function(request,response){
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('Hello World');
+}).listen();
+
+var testFilename=__dirname+'/files/testrender.png';
+var verifyFilename=__dirname+'/files/verifyrender.png';
+
+exports.testPhantomPageRender=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.open('http://localhost:'+server.address().port,function(err,status){
+ assert.ifError(err);
+ assert.equal(status,'success');
+ page.render(testFilename,function(err){
+ assert.ifError(err);
+ assert.equal(fileHash(testFilename),fileHash(verifyFilename));
+ fs.unlinkSync(testFilename); //clean up the testfile
+ server.close();
+ ph.exit();
+ });
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpagesendevent.js b/test/testpagesendevent.js
new file mode 100644
index 0000000..3b5c98f
--- /dev/null
+++ b/test/testpagesendevent.js
@@ -0,0 +1,31 @@
+var http=require('http');
+var phantom=require('../phantom-node');
+
+var server=http.createServer(function(request,response){
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('Hello World
');
+}).listen();
+
+exports.testPhantomPageSendEvent=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.open('http://localhost:'+server.address().port,function(err,status){
+ assert.ifError(err);
+ assert.equal(status,'success');
+ page.sendEvent('click',30,20,function(err){
+ assert.ifError(err);
+ page.evaluate(function(){
+ return document.getElementsByTagName('h1')[0].innerText;
+ },function(err,result){
+ assert.ifError(err);
+ assert.equal(result,'Hello Test');
+ server.close();
+ ph.exit();
+ });
+ });
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/test/testpageuploadfile.js b/test/testpageuploadfile.js
new file mode 100644
index 0000000..ef4b35d
--- /dev/null
+++ b/test/testpageuploadfile.js
@@ -0,0 +1,41 @@
+var http=require('http');
+var phantom=require('../phantom-node');
+
+var gotFile=false;
+var server=http.createServer(function(request,response){
+ if(request.url==='/upload'){
+ request.on('data',function(buffer){
+ gotFile=buffer.toString('ascii').indexOf('Hello World')>0;
+ });
+ }
+ else{
+ response.writeHead(200,{"Content-Type": "text/html"});
+ response.end('');
+ }
+}).listen();
+
+exports.testPhantomPageUploadFile=function(beforeExit,assert){
+ phantom.create(function(error,ph){
+ assert.ifError(error);
+ ph.createPage(function(err,page){
+ assert.ifError(err);
+ page.open('http://localhost:'+server.address().port,function(err,status){
+ assert.ifError(err);
+ assert.equal(status,'success');
+ page.uploadFile('input[name=test]',__dirname+'/files/uploadtest.txt',function(err){
+ assert.ifError(err);
+ page.evaluate(function(){
+ document.forms['testform'].submit();
+ },function(err,result){
+ assert.ifError(err);
+ setTimeout(function(){
+ assert.ok(gotFile);
+ server.close();
+ ph.exit();
+ },100);
+ });
+ });
+ });
+ });
+ });
+};
\ No newline at end of file