javascriptnode.jsgoogle-chrome-devtoolspuppeteer

How to fill an input element that is in a popup using puppeteer?


I'm using puppeteer to automate access to a specific newspaper. The problem is that to enter the email and password it is necessary to click on the sign in button and wait for a popup to open.

However, when waiting for a selector that is in the login popup, the following error is reported:

UnhandledPromiseRejectionWarning: TimeoutError: Waiting for selector `input[fieldloginemail]` failed: Waiting failed: 30000ms exceeded.

Could anyone help me in this case? I'm using puppeteer 19.8.5.

My code:

const puppeteer = require("puppeteer");

puppeteer
  .launch({ headless: false, defaultViewport: null })
  .then(async (browser) => {
    const page = await browser.newPage();
    await page.goto("https://pressreader.df.cl/diario-financiero");
    await page.waitForSelector(".btn-login, .toolbar-button-signin", {
      visible: true,
    });
    await page.click(".btn-login, .toolbar-button-signin");

    const emailSelector = 'input[fieldloginemail]'
    const passwordSelector = 'input[fieldloginpassword]'

    await page.waitForSelector(emailSelector)
    await page.waitForSelector(passwordSelector)

    await page.type(emailSelector, 'foo@bar.com')
    await page.type(passwordSelector, 'myPassword')
    await page.click('button[actionlogin]')

    await browser.close();
  });

Solution

  • The main problem is that the auth modal is inside an iframe. Here's how I'd go about this:

    const puppeteer = require("puppeteer"); // ^19.7.2
    
    const url = "<Your URL>";
    
    let browser;
    (async () => {
      browser = await puppeteer.launch();
      const [page] = await browser.pages();
      await page.goto(url, {waitUntil: "domcontentloaded"});
    
      const btn = await page.waitForSelector(".btn-login, .toolbar-button-signin");
      await btn.evaluate(el => el.click());
    
      const frameEl = await page.waitForSelector('iframe[id^="piano-id"]');
      const frame = await frameEl.contentFrame();
    
      const email = await frame.waitForSelector("input[fieldloginemail]");
      await email.focus();
      await email.type("foo@bar.com");
    
      const pw = await frame.waitForSelector("input[fieldloginpassword]");
      await pw.focus();
      await pw.type("myPassword");
    
      await frame.click("button[actionlogin]");
      await page.screenshot({path: "login.png"});
    })()
      .catch(err => console.error(err))
      .finally(() => browser?.close());
    

    The .focus() seems to be necessary because some asynchronous event seems to take focus away from the input, causing characters to be missed. I haven't tested it extensively, so keep an eye on it.

    I'm also using .evaluate(el => el.click()) for the main button since visibility seems spotty and it triggers fine with an untrusted event.

    In general, the site is a slow, fussy, heavily-animated modern site. Blocking CSS and images might help speed and reliability.

    Minor point, but you can use the return value of waitForSelector.

    Disclosure: I'm the author of the linked blog post.