const { chromium } = require('playwright'); const fs = require('fs'); const path = require('path'); const os = require('os'); const BASE_DIR = path.join(os.homedir(), 'holiday-planning'); const SCREENSHOT_DIR = path.join(BASE_DIR, 'price-evidence'); const PRICES_DIR = path.join(BASE_DIR, 'prices'); async function searchEurocamp() { // Ensure directories exist fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); fs.mkdirSync(PRICES_DIR, { recursive: true }); const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const context = await browser.newContext({ viewport: { width: 1920, height: 1080 }, userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' }); const page = await context.newPage(); let results = { searchDate: new Date().toISOString(), checkIn: '2026-07-18', checkOut: '2026-08-02', nights: 14, adults: 2, children: 1, childAge: 6, campsite: 'Domaine des Ormes', url: 'https://www.eurocamp.co.uk/campsites/france/northern-brittany/domaine-des-ormes-campsite', prices: [], screenshots: [], status: 'in_progress', errors: [], notes: [] }; try { console.log('Step 1: Navigating to Eurocamp Domaine des Ormes page...'); await page.goto(results.url, { waitUntil: 'domcontentloaded', timeout: 60000 }); await page.waitForTimeout(3000); // Screenshot 1: Initial page const screenshot1 = path.join(SCREENSHOT_DIR, 'eurocamp-01-initial-page.png'); await page.screenshot({ path: screenshot1, fullPage: false }); results.screenshots.push(screenshot1); console.log('✓ Screenshot 1: Initial page saved'); // Check page title const title = await page.title(); console.log('Page title:', title); results.notes.push(`Page title: ${title}`); // Look for "Book now" or "Check availability" buttons console.log('\nStep 2: Looking for booking button...'); const selectors = [ 'button:has-text("Book now")', 'a:has-text("Book now")', 'button:has-text("Check availability")', 'a:has-text("Check availability")', 'button:has-text("Search")', '.booking-button', '[data-testid="book-button"]' ]; let buttonFound = false; for (const selector of selectors) { try { const button = await page.$(selector); if (button) { const buttonText = await button.textContent(); console.log(`Found button: "${buttonText}"`); await button.click(); buttonFound = true; await page.waitForTimeout(3000); break; } } catch (e) { // Continue trying } } if (buttonFound) { const screenshot2 = path.join(SCREENSHOT_DIR, 'eurocamp-02-after-book-click.png'); await page.screenshot({ path: screenshot2, fullPage: true }); results.screenshots.push(screenshot2); console.log('✓ Screenshot 2: After book click saved'); } // Check for date picker modal or booking form console.log('\nStep 3: Looking for date picker...'); // Look for date inputs with various possible selectors const datePickerSelectors = [ 'input[placeholder*="Check in"]', 'input[placeholder*="Arrival"]', 'input[name*="checkIn"]', 'input[name*="check-in"]', 'input[name*="arrival"]', '[data-testid="checkin-input"]', '.date-picker input', '.arrival-date input' ]; let datePicker = null; for (const selector of datePickerSelectors) { datePicker = await page.$(selector); if (datePicker) { console.log(`Found date picker: ${selector}`); break; } } if (datePicker) { console.log('Clicking date picker...'); await datePicker.click(); await page.waitForTimeout(1000); const screenshot3 = path.join(SCREENSHOT_DIR, 'eurocamp-03-date-picker-open.png'); await page.screenshot({ path: screenshot3, fullPage: true }); results.screenshots.push(screenshot3); console.log('✓ Screenshot 3: Date picker open saved'); // Try to navigate to July 2026 // Most date pickers have month navigation const nextMonthButtons = await page.$$('button:has-text(">"), .next-month, [aria-label*="next"], .calendar-nav-next'); for (let i = 0; i < 18; i++) { // Up to 18 months ahead const monthYear = await page.$('.current-month, .calendar-month, [class*="month-year"]'); if (monthYear) { const monthText = await monthYear.textContent(); if (monthText && monthText.includes('July 2026')) { console.log('Found July 2026'); break; } } for (const btn of nextMonthButtons) { try { await btn.click(); await page.waitForTimeout(200); } catch (e) {} } } // Select July 18 const day18 = await page.$('text="18":near(.calendar, :text("July")), td:has-text("18"), button:has-text("18")'); if (day18) { await day18.click(); await page.waitForTimeout(500); } // Select August 2 const day2 = await page.$('text="2":near(.calendar, :text("August")), td:has-text("2"), button:has-text("2")'); if (day2) { await day2.click(); await page.waitForTimeout(500); } const screenshot4 = path.join(SCREENSHOT_DIR, 'eurocamp-04-dates-selected.png'); await page.screenshot({ path: screenshot4, fullPage: true }); results.screenshots.push(screenshot4); console.log('✓ Screenshot 4: Dates selected saved'); } // Look for guest/party selector console.log('\nStep 4: Looking for guest selector...'); const guestSelectors = [ 'input[name*="adult"]', 'select[name*="adult"]', '[data-testid="adults-input"]', '.guest-selector', 'button:has-text("2 adults")', 'button:has-text("guests")' ]; for (const selector of guestSelectors) { try { const guestInput = await page.$(selector); if (guestInput) { console.log(`Found guest input: ${selector}`); // Would need to interact based on element type break; } } catch (e) {} } // Look for search/submit button console.log('\nStep 5: Looking for search button...'); const searchButtonSelectors = [ 'button[type="submit"]', 'button:has-text("Search")', 'button:has-text("Find prices")', 'button:has-text("Get quotes")', '[data-testid="search-button"]' ]; for (const selector of searchButtonSelectors) { try { const searchBtn = await page.$(selector); if (searchBtn) { console.log(`Found search button: ${selector}`); await searchBtn.click(); await page.waitForTimeout(5000); const screenshot5 = path.join(SCREENSHOT_DIR, 'eurocamp-05-search-results.png'); await page.screenshot({ path: screenshot5, fullPage: true }); results.screenshots.push(screenshot5); console.log('✓ Screenshot 5: Search results saved'); break; } } catch (e) {} } // Extract any visible prices console.log('\nStep 6: Extracting prices from page...'); const bodyText = await page.textContent('body'); // Find price patterns const ukPrices = bodyText.match(/£[\d,]+(?:\.\d{2})?/g) || []; const euroPrices = bodyText.match(/€[\d,]+(?:\.\d{2})?/g) || []; const totalPrices = bodyText.match(/total[^£€]*[£€][\d,]+/gi) || []; results.rawPrices = { ukPrices: [...new Set(ukPrices)], euroPrices: [...new Set(euroPrices)], totalPrices }; console.log('Found UK prices:', [...new Set(ukPrices)].slice(0, 10)); console.log('Found Euro prices:', [...new Set(euroPrices)].slice(0, 10)); // Look for specific accommodation cards with prices const priceCards = await page.$$('[class*="price"], [class*="accommodation"], [class*="card"]'); console.log(`Found ${priceCards.length} potential price cards`); // Get the final URL results.finalUrl = page.url(); console.log('Final URL:', results.finalUrl); // Save page HTML for manual inspection const html = await page.content(); const htmlPath = path.join(SCREENSHOT_DIR, 'eurocamp-page-content.html'); fs.writeFileSync(htmlPath, html); results.notes.push(`HTML saved to ${htmlPath}`); results.status = 'partial'; results.notes.push('Automated interaction completed. Some manual steps may be needed for complex booking forms.'); } catch (error) { console.error('\n❌ Error:', error.message); results.errors.push(error.message); results.status = 'error'; try { const errorScreenshot = path.join(SCREENSHOT_DIR, 'eurocamp-error-state.png'); await page.screenshot({ path: errorScreenshot, fullPage: true }); results.screenshots.push(errorScreenshot); } catch (e) { results.errors.push('Could not take error screenshot'); } } finally { await browser.close(); } return results; } // Run console.log('Starting Eurocamp price search...\n'); searchEurocamp() .then(results => { const outputPath = path.join(PRICES_DIR, 'eurocamp-domaine-des-ormes.json'); fs.writeFileSync(outputPath, JSON.stringify(results, null, 2)); console.log('\n========================================'); console.log('RESULTS'); console.log('========================================'); console.log('Status:', results.status); console.log('Screenshots saved:', results.screenshots.length); console.log('Prices found:', results.rawPrices?.ukPrices?.length || 0, 'UK,', results.rawPrices?.euroPrices?.length || 0, 'Euro'); console.log('Output file:', outputPath); console.log('========================================\n'); }) .catch(err => { console.error('Fatal error:', err); process.exit(1); });