From c6fad421c1e6e0d4a988012741681983c2192b32 Mon Sep 17 00:00:00 2001 From: Alex Scheel Meyer Date: Thu, 5 Jul 2012 19:33:09 +0200 Subject: [PATCH] first public commit --- .gitignore | 1 + bridge.js | 94 ++++++++++++++++++++++++ phantom-node.js | 140 ++++++++++++++++++++++++++++++++++++ test/files/injecttest.js | 1 + test/files/modifytest.js | 1 + test/files/uploadtest.txt | 1 + test/files/verifyrender.png | Bin 0 -> 2451 bytes test/testcreatepage.js | 11 +++ test/testinjectjs.js | 11 +++ test/testpageevaluate.js | 28 ++++++++ test/testpageincludejs.js | 41 +++++++++++ test/testpageinjectjs.js | 41 +++++++++++ test/testpageopen.js | 22 ++++++ test/testpagerelease.js | 14 ++++ test/testpagerender.js | 39 ++++++++++ test/testpagesendevent.js | 31 ++++++++ test/testpageuploadfile.js | 41 +++++++++++ 17 files changed, 517 insertions(+) create mode 100644 .gitignore create mode 100644 bridge.js create mode 100644 phantom-node.js create mode 100644 test/files/injecttest.js create mode 100644 test/files/modifytest.js create mode 100644 test/files/uploadtest.txt create mode 100644 test/files/verifyrender.png create mode 100644 test/testcreatepage.js create mode 100644 test/testinjectjs.js create mode 100644 test/testpageevaluate.js create mode 100644 test/testpageincludejs.js create mode 100644 test/testpageinjectjs.js create mode 100644 test/testpageopen.js create mode 100644 test/testpagerelease.js create mode 100644 test/testpagerender.js create mode 100644 test/testpagesendevent.js create mode 100644 test/testpageuploadfile.js 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 0000000000000000000000000000000000000000..fafc20ba5d7dbb17ee993bcdcc88ecd5f35a33eb GIT binary patch literal 2451 zcmeHI`&ZK07Pp+6SX$h)ERBrm`Y1J~F)0LbUDL|)F_figYK3KUNl?i|6da3mw8BZ# z#%HD1R0K@(85re^5Su)6Ty)l^{=;+k>xaAC=vy`$xu0g4fd6C% zb#u0nI}9(_PQ$Y#Fob2{8d1F_SsIHYiltmA0O{}ccCAmhs_|`-ztK>?Vdi`}*gg(X z+68mb&j0a52g7ar=0{cyhM%cmF!mZk-6tm}jd4QeZc8_igM)+qPWyoQPww9@;3aP; z%dkrg^9CB#9CGpo&MH$-wA6(pT)S{VIL030S3G|{rYU_&U8w`R-D-EJ?|pH$oLP(O z&)#8XW^)|WQ}gpD-Ai0QI|)jgpUNYs3LL|`VC3bmB6Olon`u9b5A_jgNQzd z!SqOc!YIFV7qqsvRzYbuzI-e(Ntf9jI1pFPWZv|S?GLQEfr**gln|yNIRw^}In%(b zOO1=%TQ^0oV2HRAF4N(*I>^Cbj zlD^(vEt&=%Um%&@Zz)m?>>tLMw#>}TP(?jGJ+mcGLYzyVJ-aLw#Y!j?`<=7VSUP_~ z?o;5b6kKc14&LL@bG|CTeuoDJsnVwbg>aEk-uN z@gv_@jYO#q1%p+xUcQXfj5HDxt8RVy*e2q{CYFhF9K*$2KmvETTy9;=tk5`}KY{VD z-I!3gr{6fNpB;Q0P7tF|j_{b7Ugmy&aJ0Z`x%>CS6RM1ijHz{sMIg9VdTke;_;c)sE=n-}( zkJ_P@KIL5=sZRCipCq$+p4AJ5)*f z1}prlRlP!15HJ60D3`Qg1?aFn+aa9lM=AGX#NzQ^lN9P!#g|Ws9EwJDXMBA8GtJN> z`yRMCI98-=H@1L3@ag{I!%j;}+aX^|8r9EH*45M)y>DxC<^mZ#|DqelbLOnW_q`R2 zHc*qi3Q_7F;I8Yn))NoSW1_AjbG!<(PW?;laOe;f0U55o)_$?Eu~AdmIF(z>mJAF8 zn0sJk4;;G&GU*R@9WFXR1BZ~t?h$C~o2f@CHrV2=w9|&Ff56^;WUmM2W*_6c#Fq~+Ew?(8?S&SOEK4MyJa~k87JW{{ zsrLmzA7^LdQ9~nJ4mDi9228AN9r=hQp=YLbUkbPxARKob zDDllX#`%ga)n_hkCswb#pLqetHdV7$1`Rk@+^y&^TU}+aPF$C@n1yPGviw8Te0Hm)L;vDrJ4`0wB?g6rh%M4xR0A~jOgiu& zSvezD%=XJ!(BsKk%1A@h!*egZE+)*Bm+G)8*K5B!iIc2)@yd8= zhomlI__tNf@-nh3-{p1gr=g+THBV{V

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