1import { Page } from 'playwright';
2import { LoadedRequest, PlaywrightCrawlingContext, Request } from 'crawlee';
3import { waitWhileGoogleLoading } from './utils.js';
4import { GoogleHotelsOptions } from './options.js';
5import { DEFAULT_NUM_OF_ADULTS, DEFAULT_NUM_OF_CHILDREN, MAX_NUM_OF_PEOPLE } from '../constants.js';
6
7
8type EnqueueDetails = (urls: string[]) => Promise<void>;
9
10export const getDetailsUrls = async <Context extends PlaywrightCrawlingContext>(ctx: Omit<Context, 'request'> & {
11 request: LoadedRequest<Request>;
12}, options: GoogleHotelsOptions, enqueueDetails: EnqueueDetails) => {
13 const { page, log } = ctx;
14
15 const element = await page.waitForSelector('input[aria-label="Search for places, hotels and more"]');
16 log.info(await element.inputValue());
17
18 await fillInputForm(page, options);
19 await waitWhileGoogleLoading(page);
20 await page.waitForTimeout(1000);
21
22
23 let hasNextPage = true;
24 let pageNumber = 1;
25 let totalItems = 0;
26 do {
27 const items = await page.$$('main > c-wiz > span > c-wiz > c-wiz > div > a');
28 log.info(`Found ${items.length} items on the page ${pageNumber}`);
29 const urls = await Promise.all(items.map(async (item) => (
30 `https://www.google.com${await item.getAttribute('href')}`
31 ))) as string[];
32
33 if (options.maxResults === undefined) {
34 await enqueueDetails(urls);
35 } else {
36 await enqueueDetails(urls.slice(0, options.maxResults - totalItems));
37 }
38
39 totalItems += items.length;
40 const nextPageButton = page.getByRole('button').filter({ hasText: 'Next' }).first();
41
42 if (nextPageButton !== null && (options.maxResults === undefined || totalItems < options.maxResults!)) {
43 await nextPageButton.click();
44 await waitWhileGoogleLoading(page);
45 await page.waitForTimeout(1000);
46 pageNumber++;
47 } else {
48 hasNextPage = false;
49 }
50 } while (hasNextPage);
51};
52
53const fillInputForm = async (page: Page, options: GoogleHotelsOptions) => {
54 let checkInElement = await page.waitForSelector('input[aria-label="Check-in"]');
55
56 await checkInElement.click();
57
58 checkInElement = await page.waitForSelector(
59 'div[role="dialog"] > div:nth-of-type(2) > div:nth-of-type(2) > div:nth-of-type(2) > div > div > input[aria-label="Check-in"]',
60 );
61 const checkOutElement = await page.waitForSelector(
62 'div[role="dialog"] > div:nth-of-type(2) > div:nth-of-type(2) > div:nth-of-type(2) > div > div > input[aria-label="Check-out"]',
63 );
64
65 await checkInElement.fill(options.checkInDate);
66 await checkOutElement.click();
67 await page.waitForTimeout(1000);
68 await checkOutElement.fill(options.checkOutDate);
69 await checkOutElement.press('Enter');
70
71 const submitButton = await page.waitForSelector('div[role="dialog"] > div:nth-of-type(4) > div > button:nth-of-type(2)');
72 await submitButton.click();
73
74 const peopleButton = await page.waitForSelector('div[role="button"][aria-label^="Number of travelers"]');
75 await peopleButton.click();
76 await page.waitForTimeout(1000);
77
78 let adults = DEFAULT_NUM_OF_ADULTS;
79 let children = DEFAULT_NUM_OF_CHILDREN;
80
81 while (adults > options.numberOfAdults && adults > 0) {
82 const removeAdultButton = await page.waitForSelector('button[aria-label="Remove adult"]');
83 await removeAdultButton.click();
84 adults--;
85 }
86 while (adults < options.numberOfAdults && (adults + children) <= MAX_NUM_OF_PEOPLE) {
87 const addAdultButton = await page.waitForSelector('button[aria-label="Add adult"]');
88 await addAdultButton.click();
89 adults++;
90 }
91 while (children > options.numberOfChildren && children >= 0) {
92 const removeChildButton = await page.waitForSelector('button[aria-label="Remove child"]');
93 await removeChildButton.click();
94 children--;
95 }
96 while (children < options.numberOfChildren && (adults + children) <= MAX_NUM_OF_PEOPLE) {
97 const addChildButton = await page.waitForSelector('button[aria-label="Add child"]');
98 await addChildButton.click();
99 children++;
100 }
101
102 const peopleDoneButton = await page.waitForSelector(
103 'div[data-default-adult-num="2"] > div > div > div:nth-of-type(2) > div:nth-of-type(2) > div:nth-of-type(2) > div:nth-of-type(2) > button',
104 );
105 await peopleDoneButton.click();
106
107 const currencyButton = await page.waitForSelector('footer div c-wiz button');
108 await currencyButton.click();
109 await page.waitForTimeout(1000);
110 const requiredCurrency = options.currencyCode;
111 const currencyRadio = await page.waitForSelector(`div[role="radio"][data-value="${requiredCurrency.toUpperCase()}"]`);
112 await currencyRadio.click();
113 const currencyDoneButton = await page.waitForSelector('div[aria-label="Select currency"] > div:nth-of-type(3) > div:nth-of-type(2) > button');
114 await currencyDoneButton.click();
115};