3

I'm trying to pass a variable to .evaluate so I can use them in the scope of the web page but I can't get it to work.

await nightmare.evaluate(function() {
        let links = document.querySelectorAll('div.fsl a');
        return Array.prototype.map.call(links, function(e) {
            return e.getAttribute('href');
        });
    })

    .evaluate(function(result) {
        for(var i = 0; i < result.length; i++) {
            var matchResult = result[i].match(/.com\/(.*?)\?fref/);
            if (matchResult) {
                console.log(matchResult[1]);
            }
        }
    });

For this I get Cannot read property 'match' of undefined. I then tried:

const evaluated = await nightmare.evaluate(function() {
        let links = document.querySelectorAll('div.fsl a');
        return Array.prototype.map.call(links, function(e) {
            return e.getAttribute('href');
        });
    });

    await nightmare.evaluate(function(evaluated) {
        for(var i = 0; i < evaluated.length; i++) {
            var matchResult = evaluated[i].match(/.com\/(.*?)\?fref/);
            if (matchResult) {
                console.log(matchResult[1]);
            }
        }
    });

With the same result. I then tried:

const evaluated = await nightmare.evaluate(function() {
        let links = document.querySelectorAll('div.fsl a');
        return Array.prototype.map.call(links, function(e) {
            return e.getAttribute('href');
        });
    });

for(var i = 0; i < evaluated.length; i++){
        var matchResult = evaluated[i].match(/.com\/(.*?)\?fref/);
        if(matchResult) {
            console.log(matchResult[1]);
            await nightmare.evaluate(function(matchResult) {
                return document.body.innerHTML += '<a href="https://www.example.com/'+matchResult[0]+'">'+matchResult[0]+'</a>';
            });
            await nightmare.click('a[href="https://www.example.com/'+matchResult[0]+'"]');
            await nightmare.wait(5000);
        }
    }
await nightmare.end();

For this, the first iteration of the loop is executed and matchResult[1] is logged to the console. I then get Error: Evaluation timed out after 30000msec. Are you calling done() or resolving your promises?.

I also tried something like this:

await nightmare.evaluate(function() {
        let links = document.querySelectorAll('div.fsl a');
        return Array.prototype.map.call(links, function(e) {
            return e.getAttribute('href');
        });
    }).then(function(users){

    }).end();

Which does pass the return to .then() but how do I pass the array into the next evaluate? And now this is the last thing I can think of but doesn't work:

await nightmare.evaluate(function() {
        let links = document.querySelectorAll('div.fsl a');
        return Array.prototype.map.call(links, function(e) {
            return e.getAttribute('href');
        });
    }).evaluate((users) => {
        console.log(users);
    }).end();

I found a solution here but it returns undefined for me. I also found this which talks about a similar thing in PhantomJS but haven't come up with a workable code yet.

xendi
  • 2,332
  • 5
  • 40
  • 64
  • 4
    `Array.prototype.forEach` returns `undefined`. Use [`Array.prototype.find`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). – Noah Freitas Sep 29 '17 at 23:41
  • What is `nightmare.evaluate`? What you have doesn't look like a promise, which is the only thing `await` can control. The way you would expect, at least. – Andrew Sep 30 '17 at 02:37
  • I have updated the question with a better example of the problem and several versions of code I've tired. I was able to resolve the previous code I posted by using `Array.prototype.find` but the code I had to write immediately after that suffers from the same sort of problem that `find` will not fix. – xendi Sep 30 '17 at 19:54
  • Updated again with more attempts. – xendi Sep 30 '17 at 20:16
  • In the first attempt, change the second `.evaluate()` to `.then()` with the same function body. – Patrick Roberts Sep 30 '17 at 20:23
  • Yeah but the code I need in `.then` I need it to run in the context of the web page. That's what `.evaluate` is for. I'll try it though. – xendi Sep 30 '17 at 20:27
  • Yeah I need it: https://github.com/segmentio/nightmare#evaluatefn-arg1-arg2 – xendi Sep 30 '17 at 20:30
  • Updated question at bottom with 2 more solutions I'm trying. Also updated title of question to better reflect the problem. – xendi Sep 30 '17 at 21:33
  • Posted an answer. – xendi Sep 30 '17 at 22:04

1 Answers1

1

I have found the answer. It seems as though half my attempts were probably working and I just didn't realize it. Within the scope of .evaluate(), we're in the browser scope (As intended) but I wasn't considering that I can't log to the console from that scope. Specifically, I can't log to my console. If I run console.log from that scope, it's logging to the browser console. The following code works:

    var foo = "stuff";
    var bar = "stuff for the remote page";

    var result = await nightmare.evaluate(function(bar2) {
        // this function executes in the remote context
        // it has access to the DOM, remote libraries, and args you pass in
        console.log("bla "+bar2); // this will not output
        // but not to outer-scope vars
        return typeof foo + " " + bar2;
    }, bar);
    console.log(result); // this logs bar2!

I think this solves it. It might be a bit tricky to debug what's going on in the document scope but certainly possible.

xendi
  • 2,332
  • 5
  • 40
  • 64