javascriptgetelementbyidnightmare

Passing through a parameter to document.getElementById when using it in Nightmare evaluate()


I am trying to run my checkPrice function with parameters that I pass in:

const nightmare = require('nightmare')()

let url = "https://www.amazon.co.uk/Guards-Discworld-City-Watch-Collection/dp/1473200180/"
let identifier = "price"

checkPrice(url, identifier)

async function checkPrice(url2, identifier2) {
    console.log(identifier2)
    const priceString = await nightmare.goto(url2)
        .wait(`#${identifier2}`)
        .evaluate(() => document.getElementById(identifier2).innerText)
        .end()
    console.log(priceString)
    console.log("Hello!")
}

The problem that I'm getting is that I keep getting "UnhandledPromiseRejectionWarning: ReferenceError: identifier2 is not defined".

If I change the document.getElementById(identifier2).innerText) to document.getElementById("price").innerText) then it works fine.

Is there any way to pass through a parameter to getElementById() in the way I am trying to do?

I have tried putting in a hardcoded value for identifier2 which did work but would mean I am not able to use the function in the way I was planning. I also tried using identifier2 inside of a string (${identifier2}) which did not work.


Solution

  • Nightmare runs both a Node.js process and a browser process. The main testing code is run in the Node.js process, but Nightmare serializes the function you pass to evaluate and runs it in the browser process. Since they're in completely different processes, you can't just close over variables. Instead, pass the values you need into evaluate as subsequent arguments, and update your function signature to expect them as paramters. Here's the example showing this in the documentation:

    const selector = 'h1'
    nightmare
      .evaluate(selector => {
        // now we're executing inside the browser scope.
        return document.querySelector(selector).innerText
      }, selector) // <-- that's how you pass parameters from Node scope to browser scope
      .then(text => {
        // ...
      })
    

    (That's their code comment. Note that "scope" isn't quite the right word, but it's the general idea.)

    Adapting your code to do that:

    async function checkPrice(url2, identifier2) {
        console.log(identifier2)
        const priceString = await nightmare.goto(url2)
            .wait(`#${identifier2}`)
            .evaluate((identifier2) => document.getElementById(identifier2).innerText, identifier2)
            //         ^^^^^^^^^^^                                                   ^^^^^^^^^^^^^
            .end()
        console.log(priceString)
        console.log("Hello!")
    }