I'm pretty new to PhantomJS, so I'm just trying to stumble through some practical examples (the documentation feels a little light on those).
One thing I was attempting was to submit votes to a phony PollDaddy poll, but I'm struggling. Using the script below, I can tell from the first screenshot that my option button is being clicked/selected. But why does my answer not get submitted? The second screenshot looks identical to the first, despite my code executing a click event on the VOTE button in the second evaluate. Anyone know why? Know how to make it work?
var webPage = require('webpage');
var page = webPage.create();
//******* BEGIN LOGGING METHODS *******
// http://phantomjs.org/api/webpage/handler/on-url-changed.html
page.onUrlChanged = function(targetUrl) {
console.log('New URL: ' + targetUrl);
};
// http://phantomjs.org/api/webpage/handler/on-console-message.html
page.onConsoleMessage = function(msg, lineNum, sourceId) {
console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};
// http://phantomjs.org/api/webpage/handler/on-error.html
page.onError = function(msg, trace) {
var msgStack = ['ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
console.error(msgStack.join('\n'));
};
// http://phantomjs.org/api/webpage/handler/on-resource-error.html
page.onResourceError = function(resourceError) {
console.log('Unable to load resource (#' + resourceError.id + ' URL:' + resourceError.url + ')');
console.log('Error code: ' + resourceError.errorCode + '. Description: ' + resourceError.errorString);
};
// http://phantomjs.org/api/webpage/handler/on-resource-timeout.html
page.onResourceTimeout = function(request) {
console.log('Response Timeout (#' + request.id + '): ' + JSON.stringify(request));
};
//******* END LOGGING METHODS *******
page.open('https://polldaddy.com/poll/9424638/', function(status) {
console.log('Status: ' + status);
//make selection
var myselection = page.evaluate(function() {
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent("click", true, true, window, null,0, 0, 0, 0, false, false, false, false, 0, null);
//radio button for Curly has id=PDI_answer42988707
var myselection = document.querySelector("#PDI_answer42988707");
(myselection).dispatchEvent(ev);
return document.title;
});
//screen capture the selection
page.render('selection.png');
console.log(myselection);
//click the Vote button
var dovote = page.evaluate(function() {
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent("click", true, true, window, null,0, 0, 0, 0, false, false, false, false, 0, null);
//get a handle to the vote button
var votebutton = document.querySelector("a.vote-button");
//click the vote button
(votebutton).dispatchEvent(ev);
return document.title;
});
//delay, then take screenshot...
setTimeout(
function(){
page.render('voted.png');
console.log(dovote);
}
,2000
);
});
I'm executing the script using PhantomJS 1.9.0 on Linux Mint. The execution parameters are like this to overcome any SSL Handshake failures:
phantomjs --ignore-ssl-errors=true --ssl-protocol=any myscript.js
I've also tried setting --cookies-file and --local-storage-path on the command line, but that didn't help either.
The output I get from all of the console logging above is this:
New URL: https://polldaddy.com/poll/9424638/ <-- from urlChange
CONSOLE: JQMIGRATE: Logging is active (from line # in "") <-- from onConsoleMessage
Status: success
Who is your favorite Stooge (poll 9424638) | Polldaddy.com
Who is your favorite Stooge (poll 9424638) | Polldaddy.com
My useless poll is here: https://polldaddy.com/poll/9424638/, and you'll see from my script that I'm trying to stack the results for Curly.
If you look at the javascript that is being included with the polldaddy page, you'll find a vote function with the following check:
if (event.pageX) {
eventX = event.pageX;
eventY = event.pageY;
} else {
eventX = event.clientX;
eventY = event.clientY;
}
...
if (eventX == 0 && eventY == 0) {
return false
}
Basically they are looking at the event location and short-circuiting if the x and y location are both equal to 0.
If you change the mouse event that you create so that the clientX or clientY value is not 0, then your script should work.
Related
The javascript is located on the same website that I'm opening.
Example: http://test.site.com
js => http://test.site.com/cache/6343019445fb7d95bd2bd09c5bbfb002.js
I'm also including jQuery from googleapis and that appears to load! I tried getting some data from events like onResourceError but nothing shows up.
This is my test code:
var page = require('webpage').create();
page.viewportSize = {
width: 1366,
height: 720
};
page.settings.loadImages = true;
page.settings.localToRemoteUrlAccessEnabled = true;
page.settings.javascriptEnabled = true;
page.settings.webSecurityEnabled = false;
page.settings.XSSAuditingEnabled = false;
var uri = 'http://test.site.com';
page.onConsoleMessage = function(msg, lineNum, sourceId) {
console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};
page.onResourceError = function(resourceError) {
console.log('Unable to load resource (#' + resourceError.id + 'URL:' + resourceError.url + ')');
console.log('Error code: ' + resourceError.errorCode + '. Description: ' + resourceError.errorString);
};
page.onResourceTimeout = function(request) {
console.log('Response (#' + request.id + '): ' + JSON.stringify(request));
};
page.onError = function(msg, trace) {
var msgStack = ['ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
console.error(msgStack.join('\n'));
};
page.onLoadFinished = function(){
setTimeout(function(){
console.log(page.content); // <script> is there
console.log('render');
page.render('test.png');
phantom.exit();
}, 4000);
};
page.open(uri, function() {
console.log('loaded');
});
I see no other console messages besides render, loaded and the page content.
What am I doing wrong?
You'll have to evaluate the page. I believe it's something like this.
page.evaluate(function () {
// This is after page is evaluated and javascript is run
});
Note that evaluate doesn't give you access to the JS functions or variables, it only runs it for you. If you need to do access the JS code from Phantom, you'll want to put the JavaScript code inside the page.evaluate yourself so that you can console.log or manipulate as you need to.
I am having issues on my live server with fullcalendar. It was working just fine on localhost.
Basically:
when the page loads, it does an ajax call to load all events into the calendar. When the user selects a location, it redoes that ajax request to update all the events in that location. Which it must a) remove the events and b) load the new events returned from the server.
Here is my code:
on page load:
$('#calendar').fullCalendar({
fixedWeekCount: false,
defaultView: 'month',
eventRender: function(event, element) {
element.html('');
var seats_available = (typeof counts[event.post_id] != 'undefined') ? event.seats - counts[event.post_id] : event.seats;
var dateString = moment(event.start).format('YYYY-MM-DD');
var cal = $('#calendar').find('.fc-day-number[data-date="' + dateString + '"]').css('background-color', 'red').addClass('tooltip-here');
var html = '<div><span>Course: </span><p>' + event.title + '</p><br /><span>Seats Available: </span><p>' + seats_available + ' / ' + event.seats + '</p><br /><span>Price: </span><p>R ' + event.cost.toCurrency(2, '.', ',') + '</p><br /><br /><div class="button"><button type="button" class="primary book-course" data-date="' + dateString + '" >Book Course</button></div></div>';
Tipped.create('.tooltip-here[data-date="' + dateString + '"]', html, {position: 'bottom'});
}
});
The get events method:
function getEventData() {
$.ajax({
url: EVENT.ajaxurl, // this is a variable passed through by wordpress
type: 'POST',
dataType: 'json',
data: {
action: 'get_event_data',
selected_location: $('#location-select').val(),
security: NINJA_EVNT.security
},
success: function( res ) {
console.log('#location-select', $('#location-select').val());
console.log('response', res);
var events_raw = JSON.parse(res.data.posts);
counts = res.data.counts;
var events = [];
console.log('events', events);
var seats_available = (typeof counts[events_raw.post_id] != 'undefined') ? events_raw.seats - counts[events_raw.post_id] : events_raw.seats;
$('#calendar').fullCalendar( 'removeEvents'); // this isn't removing the events anymore
for(var i = 0; i < events_raw.length; i++) {
console.log('events_raw[i]', events_raw[i]);
var obj = {
post_id: events_raw[i].post_id,
title: events_raw[i].title,
start: moment(events_raw[i].start_date).toDate(),
seats: events_raw[i].seats,
seats_available: seats_available,
description: events_raw[i].description,
cost: events_raw[i].cost,
eventMouseover : function(data, event, view) {
var content = '<span class="hint--bottom"><h3>'+events_raw[i].title+'</h3>' +
'<p><b>Start:</b> '+moment(events_raw[i].start_date).toDate()+'</p>' +
'<p><b>Seats:</b> '+ seats_available +' / ' + events_raw[i].seats + '</p>' +
'<p><b>Description</b> '+events_raw[i].description+ '</p></span>';
tooltip.set({
'content.text': content
})
.reposition(event).show(event);
}
};
events.push(obj);
console.log('obj', obj);
$('#calendar').fullCalendar('renderEvent', obj, true); // this adds the new events just fine
}
},
error: function( error ) {
console.log('error', error);
}
});
}
UPDATE:
Forgot to add, if I reload the events and the events don't remove, if I go to the next month and back, it loads just fine.
I realized that this was just a latency issue. Putting a loading overlay until it had loaded properly and populated the dropdown worked just fine.
I'm having a hard time converting this code for it to be usable in a node server. So this code is written to run in a PhantomJS process (i.e. $: phantomjs index.js) but I want to run it in a node server using the package require("phantom"); I'm having a trouble getting these two callbacks to work however.
page.onLoadFinished = function(status){
console.log("Load Finished");
};
page.onUrlChanged = function(){
console.log("URL Changed");
};
Here is my pathetic attempt at trying to nodefy the whole situation.
phantom.create(['--ignore-ssl-errors=yes','--load-images=no']).then(function(ph) {
console.log("here");
ph.createPage().then(function(page) {
page.property('onResourceRequested', function(requestData, networkRequest) {
console.log(requestData.url);
});
page.open('https://example.com/login').then(function(status) {
console.log(status);
if (status !== 'success') { console.log("failed connection")} else {
page.evaluate(function() {
document.getElementById('email').value = "stuff";
document.getElementById('password').value = "things";
setTimeout(document.getElementsByTagName('button')[0].click(),5000);
console.log("login attempt");
setTimeout(document.URL, 2000);
});
page.onLoadFinished = function(status){
console.log("Load Finished");
};
page.onUrlChanged = function(){
console.log("url changed");
};
}
});
});
});
Also the code works and gets the page and clicks the button, however the problem is after the phantom logs in, I need data from the next page which I was going to use the onUrlChanged and onLoadFinished to do.
page.onLoadFinished and page.onUrlChanged are callback functions that are executed after page has been opened, so it makes sense to assign them before opening an url.
It is also a useful habit to subscribe to console.log and error messages from a webpage.
var phantom = require('phantom');
phantom.create(['--ignore-ssl-errors=yes','--load-images=no']).then(function(ph) {
console.log("here");
ph.createPage().then(function(page) {
page.property('onError', function(msg, trace) {
var msgStack = ['ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
console.error(msgStack.join('\n'));
});
page.property('onConsoleMessage', function(msg, lineNum, sourceId) {
console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
});
page.property('onResourceRequested', function(requestData, networkRequest) {
console.log(requestData.url);
});
page.property('onLoadFinished', function(status) {
console.log("Load Finished with status " + status);
});
page.property('onUrlChanged', function(targetUrl) {
console.log("URL changed to: " + targetUrl);
});
page.open('https://example.com/login').then(function(status) {
if (status !== 'success') { console.log("failed connection")} else {
page.evaluate(function() {
document.getElementById('email').value = "email";
document.getElementById('password').value = "password";
setTimeout(function(){
console.log("login attempt");
document.getElementsByTagName('button')[0].click();
}, 5000);
});
});
}
});
});
});
I've just started using CasperJS so I'm pretty stuck on an issue.
What I want to do is load a URL (Login protected), find a list of links (Which change the theme of the forum, specifically this part: ".styleChooser .overlayScroll li a"), click each of them and screenshot the result of the page after the click at two resolutions.
My code is currently just a grouping of other suggestions around the net trying to get this working, however I believe all the necessary code is basically there I just can't get it working. Any help would be very much appreciated!
var casper = require("casper").create();
var screenshotUrl = "URL Here";
var screenshotPaths = "rivals";
function getLinks() {
var links = document.querySelectorAll('.styleChooser .overlayScroll li a');
links = Array.prototype.map.call(links,function(link){
return link.getAttribute('href');
});
return links;
}
screenshotNow = new Date(),
screenshotDateTime = screenshotNow.getFullYear() + pad(screenshotNow.getMonth() + 1) + pad(screenshotNow.getDate()),
viewports = [
{
'name': 'smartphone-portrait',
'viewport': {width: 320, height: 480}
},
{
'name': 'desktop-standard',
'viewport': {width: 1280, height: 1024}
}
];
i = -1;
casper.start();
casper.setHttpAuth('Username', 'Password');
casper.thenOpen(screenshotUrl, function(response) {
var linksArray = this.evaluate(getLinks);
this.eachThen(linksArray, function(response) {
var url = response.data;
this.each(viewports, function(casper, viewport) {
this.then(function() {
this.viewport(viewport.viewport.width, viewport.viewport.height);
});
this.thenOpen(url, function() {
this.wait(5000);
});
casper.then(function(){
casper.echo('Screenshot for '+ screenshotPaths + '/' + "homepage " + viewport.name + ' (' + viewport.viewport.width + 'x' + viewport.viewport.height + ')', 'info');
casper.capture('screenshots/' + screenshotPaths + '/' + screenshotDateTime + '/' + "homepage" + '/' + viewport.name + '-' + viewport.viewport.width + 'x' + viewport.viewport.height + '.png', {
top: 0,
left: 0,
width: viewport.viewport.width,
height: viewport.viewport.height
});
});
});
});
++i;
}); // error is here
casper.run();
function pad(number) {
var r = String(number);
if ( r.length === 1 ) {
r = '0' + r;
}
return r;
}
And this is the error:
C:\xampp\htdocs\caspertest>casperjs newestcasper.js
CasperError: You can only define a step as a function
C:/casperjs/modules/casper.js:1755 in then
C:/xampp/htdocs/caspertest/newestcasper.js:52
Unsafe JavaScript attempt to access frame with URL about:blank from frame with U
RL file:///C:/casperjs/bin/bootstrap.js. Domains, protocols and ports must match
.
If you want to open the page that you want to take a screenshot of, you should use thenOpen. Right now, you use then which is only a step function without opening anything.
casper.thenOpen(screenshotUrl, function(response) {
I have a form in which I am expected to do some file processing which takes some time, so I want that finish event executes only after the processing is complete, right now
node is processing the file and while it is processing the file and executes commands node if finds finish event it fires it. so, how do i make sure that the finish event is fired only after processing of all files.
busboy.on('file', function(fieldname, file, filename,transferEncoding,mimeType) {
var fName = uuid.v4();
var fileext = filename.substr(filename.lastIndexOf('.') + 1);
var filepath = path.normalize(__dirname + '/../../');
var fstream = fs.createWriteStream(filepath+'/server/uploads/'+fName+'.'+fileext);
var uploadFileCompletion = file.pipe(fstream);
uploadFileCompletion.on('finish',function(){
console.log('uploaded now');
var cmd = 'libreoffice --headless --convert-to pdf --outdir '+ filepath + 'server/uploads ' + filepath + 'server/uploads/' + fName + '.' + fileext;
exec(cmd, function(error,stdout,stderr){
sys.puts(stdout);
var encryptCmd = 'java -jar server/uploads/pdfbox-app-1.8.6.jar Encrypt -canAssemble false -canExtractContent false -canExtractForAccessibility false ' +
'-canModify false -canModifyAnnotations false -canPrint false -canPrintDegraded false server/uploads/' + fName + '.' + 'pdf'
+ ' ' + 'server/uploads/' +fName + '.' + 'pdf';
exec(encryptCmd, function(error,stdout,stderr){
fs.unlink(filepath+'server/uploads/'+fName + '.' + fileext, function(){
console.log("removed " +filepath+'server/uploads/'+fName + '.' + fileext);
actualFileName.push(filename);
storedFileName.push(fName+'.'+'pdf');
});
});
});
});
});
busboy.on('field', function(fieldname, val, valTruncated,keyTruncated) {
noteData = JSON.parse(val);
});
busboy.on('finish',function(){
noteData.uploader = req.user.username;
noteData.actualFileName = actualFileName;
noteData.storedFileName = storedFileName;
noteData.noteId = uuid.v4();
Campusnotes.create(noteData,function(err,note){
if(err){
res.status(400);
return res.send({reason:err.toString()});
}
console.log('finish');
res.status(200);
res.end();
})
});
now the console log for this is as follows -
finish
uploaded now
convert /home/unknown/public_html/campustop/server/uploads/8465f9a9-d6b7-4d53-8cb5-a8dbf3aed6a5.odt -> /home/unknown/public_html/campustop/server/uploads/8465f9a9-d6b7-4d53-8cb5-a8dbf3aed6a5.pdf using writer_pdf_Export
removed /home/unknown/public_html/campustop/server/uploads/8465f9a9-d6b7-4d53-8cb5-a8dbf3aed6a5.odt
indicating that the finish event is getting fired again and again
You could try something like:
var files = 0;
busboy.on('file', function(fieldname, file, filename,transferEncoding,mimeType) {
++files;
var fName = uuid.v4();
var fileext = filename.substr(filename.lastIndexOf('.') + 1);
var filepath = path.normalize(__dirname + '/../../');
var fstream = fs.createWriteStream(filepath+'/server/uploads/'+fName+'.'+fileext);
file.pipe(fstream).on('finish',function() {
console.log('uploaded now');
var cmd = 'libreoffice --headless --convert-to pdf --outdir '+ filepath + 'server/uploads ' + filepath + 'server/uploads/' + fName + '.' + fileext;
exec(cmd, function(error,stdout,stderr) {
console.log(stdout);
var encryptCmd = 'java -jar server/uploads/pdfbox-app-1.8.6.jar Encrypt -canAssemble false -canExtractContent false -canExtractForAccessibility false ' +
'-canModify false -canModifyAnnotations false -canPrint false -canPrintDegraded false server/uploads/' + fName + '.' + 'pdf'
+ ' ' + 'server/uploads/' +fName + '.' + 'pdf';
exec(encryptCmd, function(error,stdout,stderr) {
fs.unlink(filepath+'server/uploads/'+fName + '.' + fileext, function() {
console.log("removed " +filepath+'server/uploads/'+fName + '.' + fileext);
actualFileName.push(filename);
storedFileName.push(fName+'.'+'pdf');
});
});
--files;
onFinish();
});
});
});
busboy.on('field', function(fieldname, val, valTruncated,keyTruncated) {
noteData = JSON.parse(val);
});
busboy.on('finish', onFinish);
function onFinish() {
if (!busboy.writable && files === 0) {
noteData.uploader = req.user.username;
noteData.actualFileName = actualFileName;
noteData.storedFileName = storedFileName;
noteData.noteId = uuid.v4();
Campusnotes.create(noteData,function(err,note){
if (err){
res.status(400);
return res.send({reason:err.toString()});
}
console.log('finish');
res.status(200);
res.end();
});
}
}
On an unrelated note, you should probably do some sanitizing/checking of the filename, someone could be malicious and use something like '../../../../../../../../../etc/passwd' (I'm not sure if createWriteStream() resolves/normalizes the path given to it or not).