const { chromium } = require('playwright'); const fs = require('fs'); const path = require('path'); const SCREENSHOT_DIR = path.join(process.env.HOME, 'holiday-planning', 'price-evidence'); async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function main() { console.log('Starting Brittany Ferries price search...'); 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 (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 results = { searchDate: new Date().toISOString(), route: 'Plymouth to Roscoff', outboundDate: '2026-07-18', returnDate: '2026-08-02', passengers: { adults: 2, children: 1, childAges: [6] }, vehicle: '1 car', screenshots: [], status: 'in_progress' }; try { // Step 1: Navigate to Brittany Ferries console.log('Navigating to Brittany Ferries...'); await page.goto('https://www.brittany-ferries.co.uk', { waitUntil: 'networkidle', timeout: 60000 }); await sleep(2000); // Take initial screenshot await page.screenshot({ path: path.join(SCREENSHOT_DIR, '01-homepage.png'), fullPage: false }); results.screenshots.push('01-homepage.png'); console.log('Screenshot: homepage'); // Step 2: Accept cookies if prompted console.log('Checking for cookie banner...'); try { const acceptCookies = await page.locator('button:has-text("Accept"), button:has-text("accept"), button:has-text("OK"), #onetrust-accept-btn-handler, button[id*="cookie"]').first(); if (await acceptCookies.isVisible({ timeout: 3000 })) { await acceptCookies.click(); console.log('Accepted cookies'); await sleep(1000); } } catch (e) { console.log('No cookie banner found or already accepted'); } // Step 3: Look for and interact with booking form console.log('Looking for booking form...'); await page.screenshot({ path: path.join(SCREENSHOT_DIR, '02-before-form.png'), fullPage: false }); results.screenshots.push('02-before-form.png'); // Try to find route selector const routeSelectors = [ 'select[name*="route"]', 'select[id*="route"]', '[data-testid*="route"]', '.route-selector', '#route' ]; let routeSelect = null; for (const selector of routeSelectors) { try { const el = page.locator(selector).first(); if (await el.isVisible({ timeout: 1000 })) { routeSelect = el; console.log(`Found route selector: ${selector}`); break; } } catch (e) {} } // Look for booking form elements more broadly console.log('Searching for form elements...'); // Find outbound port const outboundSelectors = [ 'select[name*="departure"], select[id*="departure"]', 'select[name*="from"], select[id*="from"]', '[data-testid*="departure-port"]', '#departure-port' ]; // Try to find and click on booking form area const bookingFormSelectors = [ '.booking-form', '#booking-form', '[data-testid="booking-form"]', '.ferry-search', '.search-form' ]; for (const selector of bookingFormSelectors) { try { const form = page.locator(selector).first(); if (await form.isVisible({ timeout: 1000 })) { console.log(`Found booking form: ${selector}`); break; } } catch (e) {} } // Try to find any dropdown/button to start booking console.log('Looking for booking initiation elements...'); // Common patterns for ferry booking forms const possibleStartButtons = [ 'button:has-text("Book")', 'button:has-text("Search")', 'button:has-text("Get a quote")', 'a:has-text("Book now")', '.book-now', '#book-now' ]; // Wait for page to be fully interactive await sleep(2000); // Take a screenshot of current state await page.screenshot({ path: path.join(SCREENSHOT_DIR, '03-form-state.png'), fullPage: true }); results.screenshots.push('03-form-state.png'); // Get all form elements on page for debugging const formElements = await page.evaluate(() => { const inputs = Array.from(document.querySelectorAll('input, select, button')); return inputs.map(el => ({ tag: el.tagName, type: el.type || el.tagName, name: el.name || el.id || el.className, placeholder: el.placeholder, value: el.value })); }); console.log('Found form elements:', JSON.stringify(formElements, null, 2)); // Try to interact with the route/departure selection // Look for Plymouth specifically try { // Common ferry booking form patterns const routeSelect = page.locator('select').first(); if (await routeSelect.isVisible({ timeout: 2000 })) { await routeSelect.click(); await sleep(500); // Look for Plymouth option const plymouthOption = page.locator('option:has-text("Plymouth"), li:has-text("Plymouth")').first(); if (await plymouthOption.isVisible({ timeout: 1000 })) { await plymouthOption.click(); console.log('Selected Plymouth'); } } } catch (e) { console.log('Could not select route via standard select'); } // Try alternative - maybe it's a custom dropdown try { // Look for dropdown triggers const dropdownTriggers = await page.locator('[class*="dropdown"], [class*="select"], [role="combobox"]').all(); console.log(`Found ${dropdownTriggers.length} dropdown elements`); for (const trigger of dropdownTriggers.slice(0, 3)) { try { if (await trigger.isVisible()) { await trigger.click(); await sleep(300); await page.screenshot({ path: path.join(SCREENSHOT_DIR, '04-dropdown-open.png'), fullPage: false }); results.screenshots.push('04-dropdown-open.png'); break; } } catch (e) {} } } catch (e) { console.log('No custom dropdowns found'); } // Take screenshot of current state await page.screenshot({ path: path.join(SCREENSHOT_DIR, '05-current-state.png'), fullPage: true }); results.screenshots.push('05-current-state.png'); // Get page HTML for analysis const html = await page.content(); fs.writeFileSync(path.join(SCREENSHOT_DIR, 'page-source.html'), html); console.log('Saved page source'); // Get all visible text for analysis const visibleText = await page.evaluate(() => document.body.innerText); fs.writeFileSync(path.join(SCREENSHOT_DIR, 'page-text.txt'), visibleText); console.log('Saved page text'); // Check if we can find booking elements by looking at the page structure const pageAnalysis = await page.evaluate(() => { return { title: document.title, url: window.location.href, hasBookingForm: !!document.querySelector('form[action*="book"], form[action*="search"]'), buttons: Array.from(document.querySelectorAll('button')).map(b => b.innerText || b.textContent).slice(0, 10), links: Array.from(document.querySelectorAll('a')).filter(a => a.href.includes('book') || a.href.includes('quote')).map(a => ({ text: a.innerText, href: a.href })).slice(0, 10) }; }); console.log('Page analysis:', JSON.stringify(pageAnalysis, null, 2)); // If we found a booking link, try clicking it if (pageAnalysis.links.length > 0) { console.log('Found booking links, trying first one...'); await page.goto(pageAnalysis.links[0].href, { waitUntil: 'networkidle', timeout: 60000 }); await sleep(2000); await page.screenshot({ path: path.join(SCREENSHOT_DIR, '06-booking-page.png'), fullPage: true }); results.screenshots.push('06-booking-page.png'); } results.status = 'partial'; results.pageAnalysis = pageAnalysis; results.url = page.url(); results.note = 'Page loaded but complex booking form requires further interaction'; } catch (error) { console.error('Error during scraping:', error); results.status = 'error'; results.error = error.message; await page.screenshot({ path: path.join(SCREENSHOT_DIR, 'error-screenshot.png'), fullPage: true }); results.screenshots.push('error-screenshot.png'); } await browser.close(); // Save results const outputPath = path.join(process.env.HOME, 'holiday-planning', 'prices', 'brittany-ferries-plymouth-roscoff.json'); fs.writeFileSync(outputPath, JSON.stringify(results, null, 2)); console.log(`\nResults saved to: ${outputPath}`); return results; } main().catch(console.error);