John Doe
John Doe

Reputation: 1

jQuery bug using PhantomJS

I work at the moment on an automated tests scripts which needs a headless browser (PhantomJS) and all the DOM navigation and actions possible (except downloads). So PhantomJS should fit.

However I can't use PhantomJS with javascript jQuery as the following errors :

var page = require('webpage').create();
page.open('http://mywebsite.com/', function() {
    function dothat() {
        console.log('0 started');
        $('#id').children().eq(0).click();
        console.log('0 finished');
        return main(1, 0);
    }
}
dothat()

Here the scripts stop a '0 started'. The page I load uses jQuery so I though I wouldn't need to download it back. Even so, let's try with loading it.

var page = require('webpage').create();
page.open('http://mywebsite.com/', function() {
    page.injectJs('jquery-2.2.1.min.js');
    function dothat() {
        console.log('0 started');
        $('#id').children().eq(0).click();
        console.log('0 finished');
        return main(1, 0);
    }
}
dothat()

Yes I have the file locally in the same directory... And it shows that error (very fastly) :

TypeError: undefined is not a constructor (evaluating 'jQuery.easing[jQuery.easing.def](t,e,n,i,a)')

What do you suggest doing to fix that? Is this an internal bug in PhantomJS and so I should try a Firefox and Xvfb on Ubuntu or is it my error somewhere? I though first about changing code to a total non-jQuery version, but I need great selectors with classes and to click on some objects...

Thank you very much for pointing me the solution!

EDIT 1: I changed this according to what I have understand from a first answer. Now the console does not print anything

var page = require('webpage').create();
page.onConsoleMessage = function(msg, lineNum, sourceId) {
    console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};
page.open('http://mywebsite.com/', function() {
    page.injectJs('jquery-2.2.1.min.js');
    var result = page.evaluate(function() {
        function dothat() {
            console.log('0 started');
            $('#id').children().eq(0).click();
            console.log('0 finished');
            return main(1, 0);
        }
        function main(x, y) {
            if (x === 0) {
                return setTimeout(dothat(), y);
            } else if (x === 1) {
                return setTimeout(dothis(), y);
            }
        }
        return main(0, 5000); //first launch
    }
});

I'm not sure that was what you was pointed out though.

EDIT 2: This is a real script that wants to go on google.com and login as me. However the script does not work as intended, see below.

var page = require('webpage').create();
page.onConsoleMessage = function(msg) {
    console.log('CONSOLE: ' + msg);
};
page.open('https://google.com/', function() {
    page.injectJs('jquery-2.2.1.min.js');
    page.evaluate(function() {
        function dofirst() {
            $('#gb_70').click();
            return main(1, 0);
        }
        function dosecond() {
            document.getElementById('Email').value = '[email protected]';
            $('#next').click();
            return main(2, 0);
        }
        function dothird() {
            document.getElementById('Passwd').value = 'mypwd';
            $('#signIn').click();
        }
        function main(i, j) {
            if (i === 0) {
                console.log('launching 0');
                return setTimeout(dofirst(), j); // connections
            }
            else if (i === 1) {
                console.log('launching 1');
                return setTimeout(dosecond(), 5000);
            }
            else if (i === 2) {
                console.log('launching 2');
                return setTimeout(dothird(), 5000);
            }
        }
        return main(0, 5000);
    });
});

As you can see, main is a function I'm using for delaying steps. However the code does not work and returns me this error (or those) :

TypeError: null is not an object (evaluating 'document.getElementById('Email').value = '[email protected]'')

  undefined:7 in dosecond
  :22 in main
  :4 in dofirst
  :18 in main
  :29
  :30

Thank you for keeping trying to help me!

Upvotes: 0

Views: 197

Answers (2)

Vaviloff
Vaviloff

Reputation: 16838

In PhantomJS there's always two javascirpt contexts where functions and variables exist:

  1. PhantomJS context
  2. Opened page context

They are not aware of each other and do not intersect!

PhantomJS cannot access any of the target page objects or DOM, that is why $('#id').children().eq(0).click(); won't work - there is no jQuery in PhantomJS script, there are no DOM elements. They are in the second context, or, in other words, sandbox.

To do anything with the page you have to sort of teleport code there. It is done with the help of page.evaluate() method.

var page = require('webpage').create();
page.open('http://mywebsite.com/', function() {
    page.injectJs('jquery-2.2.1.min.js');
    var result = page.evaluate(function(){

        // You can certainly use functions
        // Be sure to declare them also 
        // inside of page.evaluate 
        function dothat() {
            console.log('0 started');
            $('#id').children().eq(0).click();
            console.log('0 finished');
            return main(1, 0);
        }

        // Important: don't forget to return result
        // back to PhantomJS main context
        return dothat();
    });
});

Note that to receieve console messages from sandboxed page context you must subscribe to them via page.onConsoleMessage callback:

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

After EDIT 1: My bad, I've lost a bracket editing your example (fixed above) which led to a syntax error, a fact that PhantomJS v2.x won't complain about, it'll just hang there silently. That's what you mistook for no console messages. Could be useful to test script for syntax errors in v1.9.8 first or in some IDE with syntax highlighting.

Your current code should look this (the missing bracket also fixed):

var page = require('webpage').create();
page.onConsoleMessage = function(msg, lineNum, sourceId) {
    console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};
page.open('http://mywebsite.com/', function() {
    page.injectJs('jquery-2.2.1.min.js');
    var result = page.evaluate(function() {
        function dothat() {
            console.log('0 started');
            $('#id').children().eq(0).click();
            console.log('0 finished');
            return main(1, 0);
        }
        function main(x, y) {
            if (x === 0) {
                return setTimeout(dothat(), y);
            } else if (x === 1) {
                return setTimeout(dothis(), y);
            }
        }
        return main(0, 5000); //first launch
    }); // 
});

Note: will complain about inextistent dothis() function.

Upvotes: 1

JonSG
JonSG

Reputation: 13087

Don't you want to do it the other way around?

function dothat() {
    console.log('0 started');
    $('#id').children().eq(0).click();
    console.log('0 finished');
    return main(1, 0);
}

var page = require('webpage').create();
page.open('http://mywebsite.com/', dothat)

Upvotes: 0

Related Questions