import { chromium } from 'playwright'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const screenshotDir = path.join(__dirname, 'price-evidence'); const formatDate = (date) => { const d = new Date(date); const day = String(d.getDate()).padStart(2, '0'); const month = String(d.getMonth() + 1).padStart(2, '0'); const year = d.getFullYear(); return `${day}/${month}/${year}`; }; async function main() { const browser = await chromium.launch({ headless: true, slowMo: 500 }); 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/120.0.0.0 Safari/537.36' }); const page = await context.newPage(); const result = { url: 'https://www.eurotunnel.com/uk/', searchParams: { route: 'Folkestone to Calais', outbound: '18 July 2026', return: '02 August 2026', vehicle: '1 car (standard)' }, price: null, crossingTimes: [], screenshots: [], timestamp: new Date().toISOString(), notes: [] }; try { console.log('Navigating to Eurotunnel...'); await page.goto('https://www.eurotunnel.com/uk/', { waitUntil: 'networkidle' }); await page.screenshot({ path: path.join(screenshotDir, '01-homepage.png'), fullPage: true }); result.screenshots.push('01-homepage.png'); // Handle cookie consent if present console.log('Checking for cookie banner...'); try { const acceptCookies = page.locator('button:has-text("Accept"), button:has-text("accept"), button:has-text("Allow"), #onetrust-accept-btn-handler, button[id*="accept"]').first(); if (await acceptCookies.isVisible({ timeout: 5000 }).catch(() => false)) { await acceptCookies.click(); console.log('Accepted cookies'); await page.waitForTimeout(1000); } } catch (e) { console.log('No cookie banner found or already handled'); } await page.screenshot({ path: path.join(screenshotDir, '02-after-cookies.png'), fullPage: true }); result.screenshots.push('02-after-cookies.png'); // Look for booking form - try multiple selectors console.log('Looking for booking form...'); // Try to find the booking widget const bookingSelectors = [ '#booking-widget', '.booking-widget', '[data-testid="booking-form"]', 'form[action*="booking"]', '.leisure-booking', '#leisure-booking' ]; let formFound = false; for (const selector of bookingSelectors) { try { const form = page.locator(selector).first(); if (await form.isVisible({ timeout: 2000 }).catch(() => false)) { console.log(`Found form with selector: ${selector}`); formFound = true; break; } } catch (e) {} } // Try to select route (Folkestone to Calais) console.log('Selecting route...'); try { // Look for route selector const routeSelectors = [ 'select[name*="route"]', '[data-testid="route-selector"]', 'select[id*="departure"]', '.route-selector select' ]; for (const sel of routeSelectors) { try { const routeSelect = page.locator(sel).first(); if (await routeSelect.isVisible({ timeout: 2000 }).catch(() => false)) { await routeSelect.selectOption({ label: /Folkestone.*Calais|Calais.*Folkestone/i }); console.log('Selected route'); break; } } catch (e) {} } } catch (e) { result.notes.push(`Route selection issue: ${e.message}`); } // Look for date inputs console.log('Filling in dates...'); // Try to find and fill outbound date const outboundDateSelectors = [ 'input[name*="outbound"]', 'input[name*="departure"]', 'input[name*="outDate"]', '[data-testid="outbound-date"]', '#outbound-date', 'input[placeholder*="outbound"]', 'input[placeholder*="departure"]' ]; for (const sel of outboundDateSelectors) { try { const dateInput = page.locator(sel).first(); if (await dateInput.isVisible({ timeout: 2000 }).catch(() => false)) { await dateInput.click(); await page.waitForTimeout(500); await dateInput.fill('18/07/2026'); console.log('Filled outbound date'); break; } } catch (e) {} } // Try to find and fill return date const returnDateSelectors = [ 'input[name*="return"]', 'input[name*="returnDate"]', '[data-testid="return-date"]', '#return-date', 'input[placeholder*="return"]' ]; for (const sel of returnDateSelectors) { try { const dateInput = page.locator(sel).first(); if (await dateInput.isVisible({ timeout: 2000 }).catch(() => false)) { await dateInput.click(); await page.waitForTimeout(500); await dateInput.fill('02/08/2026'); console.log('Filled return date'); break; } } catch (e) {} } await page.screenshot({ path: path.join(screenshotDir, '03-form-filled.png'), fullPage: true }); result.screenshots.push('03-form-filled.png'); // Look for search/submit button console.log('Looking for search button...'); const searchButtonSelectors = [ 'button[type="submit"]', 'button:has-text("Search")', 'button:has-text("Find")', 'button:has-text("Get prices")', 'button:has-text("Book")', '.search-button', '#search-button', '[data-testid="search-button"]' ]; let searchClicked = false; for (const sel of searchButtonSelectors) { try { const btn = page.locator(sel).first(); if (await btn.isVisible({ timeout: 2000 }).catch(() => false)) { await btn.click(); console.log(`Clicked search button: ${sel}`); searchClicked = true; break; } } catch (e) {} } if (!searchClicked) { // Try pressing Enter as fallback await page.keyboard.press('Enter'); console.log('Pressed Enter as fallback'); } // Wait for results page console.log('Waiting for results...'); await page.waitForTimeout(3000); // Wait for navigation or results to appear try { await page.waitForURL('**/booking/**', { timeout: 30000 }); } catch (e) { console.log('URL didn\'t change to booking, checking current page...'); } await page.screenshot({ path: path.join(screenshotDir, '04-results-page.png'), fullPage: true }); result.screenshots.push('04-results-page.png'); // Extract price console.log('Extracting price...'); const priceSelectors = [ '.price', '.total-price', '[data-testid="price"]', '.booking-price', 'span:has-text("£")' ]; for (const sel of priceSelectors) { try { const priceElements = await page.locator(sel).all(); for (const el of priceElements) { const text = await el.textContent(); const priceMatch = text.match(/£[\d,]+\.?\d*/); if (priceMatch) { console.log(`Found price: ${priceMatch[0]}`); if (!result.price || priceMatch[0].length > result.price.length) { result.price = priceMatch[0]; } } } } catch (e) {} } // Extract crossing times console.log('Extracting crossing times...'); try { const timeElements = await page.locator('time, .time, [data-testid*="time"], .departure-time, .arrival-time').all(); for (const el of timeElements) { const text = await el.textContent(); if (text && text.trim()) { result.crossingTimes.push(text.trim()); } } } catch (e) { result.notes.push(`Time extraction issue: ${e.message}`); } // Get final URL result.url = page.url(); await page.screenshot({ path: path.join(screenshotDir, '05-final-state.png'), fullPage: true }); result.screenshots.push('05-final-state.png'); // Try to get page content for analysis const pageContent = await page.content(); fs.writeFileSync(path.join(screenshotDir, 'page-content.html'), pageContent); } catch (error) { result.error = error.message; result.notes.push(`Error: ${error.message}`); console.error('Error:', error); await page.screenshot({ path: path.join(screenshotDir, 'error-screenshot.png'), fullPage: true }); result.screenshots.push('error-screenshot.png'); } await browser.close(); // Save results fs.writeFileSync( path.join(__dirname, 'prices', 'eurotunnel.json'), JSON.stringify(result, null, 2) ); console.log('\n=== Results ==='); console.log(JSON.stringify(result, null, 2)); return result; } main().catch(console.error);