5

I try to make a screenshot of www.fallswoodsmith.com with PhantomJS. My code is:

var page = require('webpage').create();
page.viewportSize = { width: 1024, height: 768 };
page.clipRect = {top: 0, left: 0, width: 1024, height: 768};

page.open('http://www.fallswoodsmith.com', function () {
    page.render('cache/www.fallswoodsmith.com123567266_1024_768.png', {format: 'png', quality: '10'});
    phantom.exit();
});

This page is JS only, so without JS you get no content. For some reason PhantomJS is not executing this JS. I've also tried to set a timeout of 5 secs for the page.render() and phantom.exit(), but this did not change something. If I do a console.log(page.content) before the page.render() I get the full HTML of the page - just without changes that JS does.

Why does PhantomJS not execute the page's JS?

UPDATE 1: I've added the following debug stuff:

page.onConsoleMessage = function(msg, lineNum, sourceId) {
    console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};

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.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));
};

No console.log() output in my console...

MaxiNet
  • 1,008
  • 2
  • 14
  • 22
  • 2
    Please register to the [`onConsoleMessage`](http://phantomjs.org/api/webpage/handler/on-console-message.html), [`onError`](http://phantomjs.org/api/webpage/handler/on-error.html), [`onResourceError`](http://phantomjs.org/api/webpage/handler/on-resource-error.html), [`onResourceTimeout`](http://phantomjs.org/api/webpage/handler/on-resource-timeout.html) events. Maybe there are errors. – Artjom B. Jan 18 '15 at 15:58
  • @ArtjomB. I've registered these events, but there's no output on my console... (see update1) – MaxiNet Jan 18 '15 at 16:05

2 Answers2

8

If I may politely ask, who made that site? I would very much recommend not building sites in such a way that they are 100% dependent on JavaScript. Turning off JavaScript and "loading" that site (www.fallswoodsmith.com) results in nothing. zip. nada. zilch. </rant>

The answer you are looking for

Running the screenshot script you have above, I get the following output:

TypeError: 'undefined' is not a function (evaluating 'joinURL.bind(null, staticServerUrl)')

  http://static.parastorage.com/services/santa-versions/1.150.0/main-r.js:353 in wixRenderSite

To fix that issue, you can polyfill Function.prototype.bind (which is missing from PhantomJS 1.x, as per this issue) after the web page object is created but before a URL is loaded (i.e. onInitialized).

The result:

var page = require('webpage').create();

page.onInitialized = function () {
    page.evaluate(function () {
        var isFunction = function (obj) {
            return typeof obj == 'function' || false;
        };
        var slice = Array.prototype.slice;
        Function.prototype.bind = function bind(obj) {
            var args = slice.call(arguments, 1);
            var self = this;
            var F = function () {};
            var bounded = function() {
                return self.apply(
                    this instanceof F ? this : (obj || {}),
                    args.concat(slice.call(arguments))
                );
            };
            F.prototype = this.prototype || {};
            bounded.prototype = new F();
            return bounded;
        };
    });
};

page.open('http://www.fallswoodsmith.com', function () {
    setTimeout(function screenshot() {
        page.render('WORKS.png', {
            format: 'png',
            quality: '10',
        });
        phantom.exit();
    }, 10 * 1000);
});

Why wait 10 seconds before taking a screenshot? Well, since the site is completely reliant on JS, there is no obvious event (that I can think of) to wait for that indicates the loading of the page. Your mileage may vary. Increase or decrease the timeout as you see fit.

Note: the output filename above is WORKS.png.

PhantomJS versions

The above example was tested and works with PhantomJS 1.9.7. The script seems to work with PhantomJS 1.9.8 as well, but 1.9.8 has this issue (Unsafe JavaScript attempt to access frame in 1.9.8) that, while fixed, is not part of any release and causes the following error-looking output:

Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL file://28011634.js. Domains, protocols and ports must match.

Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL file://28011634.js. Domains, protocols and ports must match.

Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL file://28011634.js. Domains, protocols and ports must match.

Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL file://28011634.js. Domains, protocols and ports must match.

Viewport size

By default, the rendered image will be a full page screenshot. To fix the viewport size, you can add back in the following at the top of the script:

page.viewportSize = {
    width: 1024,
    height: 768
};
page.clipRect = {
    top: 0,
    left: 0,
    width: 1024,
    height: 768
};

Polyfilling .bind

The polyfill found on MDN, doesn't seem to work without a bit of modification, but that, combined with the underscore.js source code and this answer resulted in the above.

Community
  • 1
  • 1
Whymarrh
  • 13,139
  • 14
  • 57
  • 108
  • I don't know the owner of this page. But it is made with wix.com and I think all there pages are built like that. And I totally agree that this is... not the best way. – MaxiNet Feb 01 '15 at 13:39
0

Since version 2.1 phantomjs has polyfill included into distribution's javascript engine. Try their latest version.

Zon
  • 18,610
  • 7
  • 91
  • 99