0

Follow my previous article Built method time is not a function, I managed to successfully implement the functions with an appropriate wait time by following a combination of @ggorlen's comment and @Konrad Linkowski answer, additionally, this article puppeteer: wait N seconds before continuing to the next line that @ggorlen answered in, this comment especially helped: -

Something else? Run an evaluate block and add your own code to wait for a DOM mutation or poll with setInterval or requestAnimationFrame and effectively reimplement waitForFunction as fits your needs.

Instead I incorporated waitForSelector, produces the following script:

const puppeteer = require('puppeteer')

const EXTENSION = '/Users/usr/Library/Application Support/Google/Chrome/Profile 1/Extensions/gidnphnamcemailggkemcgclnjeeokaa/1.14.4_0'

class Agent {
  constructor(extension) {
    this._extension = extension
  }
  
  async runBrowser() {
    const browser = await puppeteer.launch({
      headless:false,
      devtools:true,
      args:[`--disable-extensions-except=${this._extension}`,
            `--load-extension=${this._extension}`,
            '--enable-automation']
    })
    return browser
  }
  
  async getPage(twitch) {
    const page = await (await this.runBrowser()).newPage()
    
    await page.goto('chrome-extension://gidnphnamcemailggkemcgclnjeeokaa/popup.html')

    const nextEvent = await  page.evaluate(async () => {
      document.getElementById('launch-trace').click()
    })
    const waitSelector = await page.waitForSelector('.popup-body')
    const finalEvent = (twitch) => new Promise(async (twitch) => page.evaluate(async (twitch) => {
      const input = document.getElementById('user-trace-id')
      input.focus()
      input.value = twitch

    }))
    await finalEvent(twitch)
  }
}
const test = new Agent(EXTENSION)


test.getPage('test')

However, my webpage produces undefined rather than test, I am a little confused by the parameters twich and k, and how to properly assert the parameter twitch so its entered inside the function finalEvent.

Alternatively, I have also tried wrapping finalEvent into a Promise so I can assert the parameter twitch into it as a function, but this does not fill any value:

    const finalEvent = (val) => new Promise(async () => await page.evaluate(async () => {
      const nextTime = () => new Promise(async () => setInterval(async () => {
        const input = document.getElementById('user-trace-id')
        
        input.focus()
        input.value = val
      }, 3000))
      //await nextTime(k)
    }))
    await finalEvent(twitch)

enter image description here

me.limes
  • 441
  • 1
  • 13

1 Answers1

0

There are a few issues here. First,

const page = await (await this.runBrowser()).newPage()

hangs the browser handle and leaks memory which keeps the process alive. Always close the browser when you finish using it:

const browser = await this.runBrowser();
const page = await browser.newPage();
// ... do your work ...
await browser.close();

Or use await page.browser().close() if you lose track of the browser variable.

Here, though, Puppeteer can throw, again leaking the browser and preventing your app from cleanly exiting, so I suggest adding a try/catch block with a finally block that closes the browser.

Generally speaking, try to get the logic working first, then do a refactor to break code into functions and classes. Writing abstractions and thinking about design while you're still battling bugs and logical problems winds up making both tasks harder.

Secondly, there's no need to async a function if you never use await in it, as in:

const nextEvent = await  page.evaluate(async () => {
  document.getElementById('launch-trace').click()
})

Here, nextEvent is undefined because evaluate()'s callback returned nothing. Luckily, you didn't attempt to use it. You also have const waitSelector = await page.waitForSelector('.popup-body') which does return the element, but it goes unused. I suggest enabling eslint no-unused-vars, because these unused variables make a confusing situation worse and often indicate typos and bugs.


On to the main problem,

const finalEvent = (twitch) => new Promise(async (twitch) => page.evaluate(async (twitch) => {
  const input = document.getElementById('user-trace-id')
  input.focus()
  input.value = twitch

}))
await finalEvent(twitch)

There are a number of misunderstandings here.

The first is the age-old Puppeteer gotcha, confusing which code executes in the browser process and which code executes in the Node process. Everything in an evaluate() callback (or any of its family, $eval, evaluateHandle, etc) executes in the browser, so Node variables that look like they should be in scope won't be. You have to pass and return serializable data or element handles to and from these callbacks. In this case, twitch isn't in scope of the evaluate callback. See the canonical How can I pass a variable into an evaluate function? for details.

The second misunderstanding is technically cosmetic in that you can make the code work with it, but it's a serious code smell that indicates significant confusion and should be fixed. See What is the explicit promise construction antipattern and how do I avoid it? for details, but the gist is that when you're working with a promise-based API like Puppeteer, you should never need to use new Promise(). Puppeteer's methods already return promises, so it's superfluous at best to wrap more promises on top of the them, and at worst, introduces bugs and messes up error handling.

A third issue is that the first parameter to new Promise((resolve, reject) => {}) is always a resolve function, so twitch is a confusing mislabel. Luckily, it won't matter as we'll be dispensing with the new Promise idiom when using Puppeteer 99.9% of the time.

So let's fix the code, keeping these points in mind:

await page.evaluate(twitch => {
    const input = document.getElementById('user-trace-id');
    input.focus();
    input.value = twitch;
  },
  twitch
);

Note that I'm not assigning the return value to anything because there's nothing being returned by the evaluate() callback.

"Selecting, then doing something with the selected element" is such a common pattern that Puppeteer provides a handy method to shorten the above code:

await page.$eval("#user-trace-id", (input, twitch) => {
    input.focus();
    input.value = twitch;
  },
  twitch
);

Now, I can't run or reproduce your code as I don't have your extension, and I'm not sure what goal you're trying to achieve, but even the above code looks potentially problematic.

Usually, you want to use Puppeteer's page.type() method rather than a raw DOM input.value = ..., which doesn't fire any event handlers that might be attached to the input. Many inputs won't register such a change, and it's an untrusted event.

Also, it's weird that you'd have to .focus() on the input before setting its value. Usually focus is irrelevant to setting a value property, and the value will be set either way.

So there may be more work to do, but hopefully this will point you in the right direction by resolving the first layer of immediate issues at hand. If you're still stuck, I suggest taking a step back and providing context in your next question of what you're really trying to accomplish here in order to avoid an xy problem. There's a strong chance that there's a fundamentally better approach than this.

ggorlen
  • 44,755
  • 7
  • 76
  • 106