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 }); console.log('Starting Eurocamp search for La Grande Métairie...'); console.log('Screenshot dir:', SCREENSHOT_DIR); console.log('Prices dir:', PRICES_DIR); const browser = await chromium.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled' ] }); const context = await browser.newContext({ viewport: { width: 1920, height: 1080 }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', locale: 'en-GB', timezoneId: 'Europe/London' }); // Add headers to appear more like a real browser await context.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', { get: () => false }); }); const page = await context.newPage(); const results = { searchDate: new Date().toISOString(), checkIn: '2026-07-18', checkOut: '2026-08-02', nights: 14, adults: 2, children: 1, childAge: 6, campsite: 'La Grande Métairie', url: 'https://www.eurocamp.co.uk/campsites/france/brittany/la-grande-metairie-campsite', prices: [], screenshots: [], status: 'in_progress', errors: [], notes: [] }; try { // Step 1: Go to the campsite page console.log('\n=== Step 1: Loading campsite page ==='); await page.goto(results.url, { waitUntil: 'domcontentloaded', timeout: 90000 }); console.log('Page loaded, waiting for content...'); await page.waitForTimeout(5000); // Take screenshot const screenshot1 = path.join(SCREENSHOT_DIR, 'eurocamp-01-initial.png'); await page.screenshot({ path: screenshot1, fullPage: false }); results.screenshots.push(screenshot1); console.log('✓ Screenshot saved:', screenshot1); // Log page title const title = await page.title(); results.notes.push(`Page title: ${title}`); console.log('Page title:', title); // Save page HTML for debugging const html = await page.content(); fs.writeFileSync(path.join(SCREENSHOT_DIR, 'eurocamp-page.html'), html); console.log('✓ HTML saved'); // Step 2: Try to find and interact with the booking widget console.log('\n=== Step 2: Looking for booking elements ==='); // Look for various booking elements const bookingElements = await page.evaluate(() => { const elements = []; // Look for buttons document.querySelectorAll('button, a').forEach(el => { const text = el.textContent.trim(); if (text.toLowerCase().includes('book') || text.toLowerCase().includes('check') || text.toLowerCase().includes('search') || text.toLowerCase().includes('availability')) { elements.push({ tag: el.tagName, text: text.substring(0, 100), class: el.className, id: el.id }); } }); // Look for date inputs document.querySelectorAll('input').forEach(el => { elements.push({ tag: 'INPUT', type: el.type, placeholder: el.placeholder, name: el.name, id: el.id }); }); return elements; }); console.log('Found booking elements:', JSON.stringify(bookingElements.slice(0, 15), null, 2)); results.notes.push(`Found ${bookingElements.length} potential booking elements`); // Step 3: Try to find prices on the page console.log('\n=== Step 3: Looking for prices ==='); const priceInfo = await page.evaluate(() => { const prices = []; const bodyText = document.body.innerText; // Look for price patterns const priceRegex = /£[\d,]+/g; const matches = bodyText.match(priceRegex); if (matches) { matches.slice(0, 20).forEach(m => prices.push(m)); } // Look for accommodation cards const cards = document.querySelectorAll('[class*="accommodation"], [class*="card"], [class*="result"], [class*="price"]'); const cardInfo = []; cards.forEach(card => { const text = card.textContent.trim(); if (text.length > 50 && text.length < 1000) { cardInfo.push(text.substring(0, 300)); } }); return { prices, cards: cardInfo.slice(0, 10) }; }); console.log('Prices found on page:', priceInfo.prices); results.notes.push(`Prices found: ${priceInfo.prices.join(', ')}`); if (priceInfo.cards.length > 0) { console.log('\nAccommodation cards found:'); priceInfo.cards.forEach((card, i) => { console.log(`Card ${i + 1}:`, card.substring(0, 150) + '...'); }); } // Step 4: Try to construct a direct booking URL console.log('\n=== Step 4: Trying direct search URL ==='); // Eurocamp uses various URL patterns for search const searchUrls = [ `https://www.eurocamp.co.uk/search?campsite=la-grande-metairie&checkIn=2026-07-18&nights=14&adults=2&children=1&childAge1=6`, `https://www.eurocamp.co.uk/campsites/france/brittany/la-grande-metairie-campsite?checkIn=2026-07-18&nights=14&adults=2&children=1&childAge1=6`, `https://www.eurocamp.co.uk/booking?campsite=la-grande-metairie&arrival=2026-07-18&departure=2026-08-02&adults=2&children=1&childAges=6` ]; for (const searchUrl of searchUrls) { try { console.log('Trying URL:', searchUrl); await page.goto(searchUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }); await page.waitForTimeout(3000); const urlName = searchUrl.split('?')[0].split('/').pop(); const screenshot = path.join(SCREENSHOT_DIR, `eurocamp-search-${urlName}.png`); await page.screenshot({ path: screenshot, fullPage: false }); results.screenshots.push(screenshot); console.log('✓ Screenshot saved for search URL'); // Check for prices on this page const searchPrices = await page.evaluate(() => { const prices = []; const bodyText = document.body.innerText; const priceRegex = /£[\d,]+/g; const matches = bodyText.match(priceRegex); if (matches) { matches.forEach(m => { if (!prices.includes(m)) prices.push(m); }); } return prices; }); if (searchPrices.length > 0) { console.log('Found prices on search page:', searchPrices); results.prices.push(...searchPrices); } } catch (e) { console.log('Error with URL:', e.message); results.errors.push(`Search URL error: ${e.message}`); } } // Take final screenshot const finalScreenshot = path.join(SCREENSHOT_DIR, 'eurocamp-final.png'); await page.screenshot({ path: finalScreenshot, fullPage: true }); results.screenshots.push(finalScreenshot); // Get final URL results.url = page.url(); console.log('\nFinal URL:', results.url); // Step 5: Check if we need to try a different approach console.log('\n=== Step 5: Trying to find actual booking form ==='); // Look for iframe booking widgets const frames = page.frames(); console.log(`Found ${frames.length} frames`); for (const frame of frames) { if (frame !== page.mainFrame()) { console.log('Checking frame:', frame.url()); try { const frameContent = await frame.content(); if (frameContent.includes('price') || frameContent.includes('£')) { console.log('Found potential price content in frame'); results.notes.push('Found booking iframe with potential price data'); } } catch (e) { console.log('Could not access frame:', e.message); } } } results.status = 'completed'; } catch (error) { console.error('Error:', error.message); results.errors.push(error.message); results.status = 'error'; // Take error screenshot const errorScreenshot = path.join(SCREENSHOT_DIR, 'eurocamp-error.png'); await page.screenshot({ path: errorScreenshot, fullPage: true }); results.screenshots.push(errorScreenshot); } finally { await browser.close(); } // Remove duplicates from prices results.prices = [...new Set(results.prices)]; // Save results const outputPath = path.join(PRICES_DIR, 'eurocamp-la-grande-metairie.json'); fs.writeFileSync(outputPath, JSON.stringify(results, null, 2)); console.log('\n=== Results saved ==='); console.log('Output file:', outputPath); return results; } searchEurocamp().then(results => { console.log('\n=== Search Complete ==='); console.log('Status:', results.status); console.log('Prices found:', results.prices); console.log('Screenshots:', results.screensshots); console.log('Errors:', results.errors); }).catch(err => { console.error('Fatal error:', err); process.exit(1); });