skip to Main Content

Issue: I cannot select the calendar widget on hotel website (https://www.ihg.com/hotels/us/en/reservation).

Error: Node is either not clickable or not an Element

What I tried (below is my code):

const puppeteer = require('puppeteer')
const fs = require('fs/promises')

async function start(){
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto("https://www.ihg.com/hotels/us/en/reservation", {waitUntil: "domcontentloaded"});

    //! This is not working - Fill Number of NIGHTS
    await page.click('#datePicker div[class="input-adjacent-icon fa fa-calendar"]');

    // waitForSelector 
    await page.waitForSelector('span[class="datepicker-field"] span[class="input-and-icon brand-outline"]');

    await browser.close() 
}

start() 

2

Answers


  1. If you were to run document.querySelectorAll('#datePicker div[class="input-adjacent-icon fa fa-calendar"]') you would see that there are 6 elements that match your selector. However only the last 2 are viewable.

    Here’s what the new code would look like:

    const puppeteer = require('puppeteer')
    const fs = require('fs/promises')
    
    async function start(){
        const browser = await puppeteer.launch();
        const page = await browser.newPage();
        await page.goto("https://www.ihg.com/hotels/us/en/reservation", {waitUntil: "domcontentloaded"});
    
        // This is for check in
        await page.click('#datePicker > span > span > lib-datepicker-date-field:nth-child(2) > span > span > div');
    
        // run other stuff
    
        // Check out
        await page.click('#datePicker > span > span > lib-datepicker-date-field:nth-child(4) > span > span > div');
    
        // run other stuff
    
        // waitForSelector 
        await page.waitForSelector('span[class="datepicker-field"] span[class="input-and-icon brand-outline"]');
    
        await browser.close() 
    }
    
    start() 
    
    Login or Signup to reply.
  2. Preamble: this website is a mess of what looks like AngularJS. It has duplicate ID attributes on elements, and plenty of async behavior and general weirdness, so it won’t be easy to automate.

    This may be an xy problem that happens so often in web scraping: there’s a tendency to assume you need to act like a user might ("I need to open the date picker"). But rather than getting too fixated on proposed solution Y, consider a different solution to the real problem X ("How can I populate the date inputs using any technique that the site accepts?") and use .type() and the keyboard to manipulate the input boxes, without worrying about automating the date picker.

    Keeping in mind that the last id seems to be the correct input for each element, you can select the elements, then use the keyboard to erase the current date and type in a new one. Pressing Enter helps ensure the proper events are triggered so the site understands the change. There’s a bit of async fussiness as the site validates and manipulates the dates, so I’m entering the check in date, then the destination, then the check out date.

    const puppeteer = require("puppeteer"); // ^21.6.0
    
    const url = "<Your URL>";
    
    let browser;
    (async () => {
      browser = await puppeteer.launch({headless: "new"});
      const [page] = await browser.pages();
      const ua =
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36";
      await page.setUserAgent(ua);
      await page.goto(url);
    
      const enterDate = async (el, date) => {
        const length = await el.evaluate(el => el.value.length);
        await el.focus();
    
        for (let i = 0; i < length; i++) {
          await page.keyboard.press("Backspace");
          await page.keyboard.press("Delete");
        }
    
        await el.type(date);
        await page.keyboard.press("Enter");
      };
    
      const getLastEl = sel => page.evaluateHandle(`
        [...document.querySelectorAll("${sel}")].pop()
      `);
    
      const destinationEl = await getLastEl("#dest-input");
      const checkInEl = await getLastEl("#checkInDate");
      const checkOutEl = await getLastEl("#checkOutDate");
      await enterDate(checkInEl, "05/21/2024");
      await destinationEl.type("miami");
      await enterDate(checkOutEl, "06/07/2024");
      await page.screenshot({path: "test.png"});
    })()
      .catch(err => console.error(err))
      .finally(() => browser?.close());
    

    Using ‘domcontentloaded’ is generally good, but I noticed that the inputs weren’t quite ready in some cases, so I reverted to ‘load’, which is slower. There may be a predicate you can check for readiness that’s more precise than ‘load’, letting you get back to ‘domcontentloaded’, but I suggest focusing on correctness first, then speed later (left as an exercise to keep this post in scope).

    You may need to play with this a bit, but hopefully it communicates one possible general strategy. Proceed carefully.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search