javascriptnode.jspuppeteer

How to choose date range in calendar with Puppeteer


I am trying to fill out a form on a hotel website (https://www.marriott.com/default.mi), in particular, I want to select Aug 7 to Aug 10 2023 date in the website's calendar (note URL does not change so I have to manipulate DOM).

Question: After I click into the calendar (via await page.click(".h-r-form-field-txt.search__calendar-value");, how do I select date range? enter image description here

Using page.waitForSelector also gave me an error:

        _Callback_error.set(this, new Errors_js_1.ProtocolError());
                                  ^

ProtocolError: Waiting for selector `[class$="search-dates-popup"]` failed: Protocol error (Runtime.callFunctionOn): Target closed

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.marriott.com/default.mi", {waitUntil: "domcontentloaded"});

    /**FILL OUT FORM*/ 
    // 1) Fill DESTINATION
    await page.type('[id$="-search-destination"]', "Chicago, IL, USA");
    // TODO: Need to press ENTER or something after putting in Chicago

    // 2) Fill Number of NIGHTS
    // click into the box first
    await page.click(".h-r-form-field-txt.search__calendar-value");

    // waitForSelector 
    page.waitForSelector('[class$="search-dates-popup"]');

    // TODO: Figure how to screenshot AFTER calendar is loaded
    await page.screenshot({ path: "test5_click.png" });
    // pick month and pick date

    await browser.close() 
}

start() 

Solution

  • See this previous thread for context.

    As described in comments, you can iterate over the months until you find one that matches your desired start date, then click the day you want and repeat the process again for the end month/day pair.

    const puppeteer = require("puppeteer"); // ^19.7.2
    
    const url = "<Your URL>";
    const startMonth = "August";
    const startDay = 7;
    const endMonth = "August";
    const endDay = 10;
    
    let browser;
    (async () => {
      browser = await puppeteer.launch();
      const [page] = await browser.pages();
      const ua =
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36";
      await page.setUserAgent(ua);
      await page.setViewport({width: 800, height: 600});
    
      await page.goto(url, {waitUntil: "domcontentloaded"});
      //await page.type('[id$="-search-destination"]', "Chicago, IL, USA");
      await page.click(".search__calendar-value");
      await page.waitForSelector(".search-dates-popup");
    
      const findMonth = async targetMonth => {
        for (let i = 0; i < 12; i++) {
          const month = await page.$eval(
            ".DayPicker-Month",
            el => el.textContent
          );
        
          if (month.includes(targetMonth)) {
            break;
          }
        
          await page.click('[aria-label="Next Month"]');
        }
      };
    
      const chooseDay = day => page.$$eval(
        ".DayPicker-Day-value",
        (els, day) => els.find(e => e.textContent.trim() === day).click(),
        String(day)
      );
    
      await findMonth(startMonth);
      await chooseDay(startDay);
      await findMonth(endMonth);
      await chooseDay(endDay);
    
      await page.screenshot({path: "test.png"});
    })()
      .catch(err => console.error(err))
      .finally(() => browser?.close());
    

    Be sure to always await every Puppeteer API call that returns a promise, which is most of them. Not awaiting page.waitForSelector is a bug.

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