Skippy le Grand Gourou
Skippy le Grand Gourou

Reputation: 7694

Loop and click on querySelectorAll list with PhantomJS

Consider the following (working) PhantomJS piece of code, supposed to click on the first element of class whatever :

page.evaluate(
    function()  
    {
        var a = document.querySelector(".whatever");
        var e = document.createEvent('MouseEvents');
        e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
        a.dispatchEvent(e);
        waitforload = true;
    }
);

I'm failing to make it loop on all elements of class whatever — the problem not being the loop itself of course but the access to querySelectorAll elements.

I'm stuck here (I replaced the loop by access to first element for easier debugging ; of course there has to be two page.evaluate calls since the second one would be within the loop) :

var aa = page.evaluate(
    function()
    {
        return document.querySelectorAll(".whatever");
    }
)
//for (i=0; i<aa.length; i++)
//{
page.evaluate(
    function()  
    {
        var e = document.createEvent('MouseEvents');
        e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
//      aa[i].dispatchEvent(e);
        aa[0].dispatchEvent(e);
        waitforload = true;
    }
);
//}

I tried several variations, removing var, prefixing variables with document., trying to pass aa as an argument, etc. Nothing worked (I probably missed the right combination, but anyway I'm clearly missing the basis here).

I'm not sure if it has to do with namespaces and scope of variables (I'm very new to JS) or if the issue comes from the querySelectorAll output (though I checked the size of the array to be correct), since I'm encountering both "ReferenceError: Can't find variable" and "TypeError: 'undefined' is not an object" errors.

Any hint would be welcome.

Upvotes: 0

Views: 2487

Answers (1)

Artjom B.
Artjom B.

Reputation: 61892

Based on your comment:

One click loads a box with additional information. The box itself hides the other buttons, so I don't know what happens when one clicks on another button once the box is displayed. My guess is that it will either replace or come on top of the previous box. Anyway my goal is to get all of these boxes. So for a start I would try to click everything one after the other.

The easiest solution would be to retrieve the number of .whatever elements and iterate based on that, but since it needs some page interaction and PhantomJS is asynchronous, you would need to use some framework to run the steps one after the other. async can be used for that or you create something similar just for this script.

function clickInPageContext(selector, index){
    page.evaluate(function(selector, index){
        index = index || 0;
        var e = document.createEvent('MouseEvents');
        e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
        var el = document.querySelectorAll(selector);
        el[index].dispatchEvent(e);
    }, selector, index);
}

var async = require("async"); // install async through npm
var selector = ".whatever";
var num = page.evaluate(function(sel){
    return document.querySelectorAll(sel).length;
}, selector);
var steps = [];
for (var i = 0; i < num; i++) {
    // the immediately invoked function looks bad, but is necessary,
    // because JS has function level scope and this is executed long 
    // after the loop has finished
    steps.push((function(i){
        return function(complete){
            clickInPageContext(selector, i);
            setTimeout(function(){
                clickInPageContext("someCloseButtonSelector"); // maybe also parametrized with i
                setTimeout(function(){
                    complete();
                }, 1000); // time to close
            }, 1000); // time to open
        };
    })(i));
}
async.series(steps, function(){
    console.log("DONE");
    phantom.exit();
});

This test depends on the premise that the number of .whatever elements stays the same. Note: async can be installed through npm (locally). And can be required in PhantomJS.
Also, see this question where I did something similar.

Upvotes: 2

Related Questions