Purpose
Find available train journeys and visible prices for an origin-to-destination trip, then provide a fare-based budget estimate without purchasing tickets.
When to Use
Use when the caller provides an origin, destination, travel date or date range, passenger details, and optionally a preferred currency, cabin, or railcard. The route page can be used for an initial upcoming-service estimate; exact-date results may require the page's date control.
Workflow
- Normalize station or city names to lowercase hyphenated slugs and navigate directly to
https://www.thetrainline.com/{locale}/train-times/{origin-slug}-to-{destination-slug}, normally using the caller's locale such asenoren-us. Do not visit the homepage first. Confirm the loaded heading matches both requested endpoints; Trainline may resolve ambiguous city names to a particular station. - If an exact date is required and the direct route page is not already showing that date, use the route page's date control to select
{date}. Keep the requested passenger count, railcard, class, and one-way/return choice unchanged. - If a consent banner blocks interaction or results, dismiss it with
button#onetrust-accept-btn-handlerwhen present, then wait for the route results to render. Do not provide credentials or proceed to checkout. - In the same browser call after the results are loaded, run this evaluator to extract journey cards and fare values:
(() => {
const norm = (s) => (s || "").replace(/\\s+/g, " ").trim();
const priceRe =
/(?:(?:CHF|EUR|GBP|USD|\\$|£|€)\\s*[0-9][0-9.,]*|[0-9][0-9.,]*\\s*(?:CHF|EUR|GBP|USD|\\$|£|€))/gi;
const parsePrice = (s) => {
const m = s.match(/[0-9][0-9.,]*/);
if (!m) return null;
let v = m[0];
if (v.includes(",") && v.includes("."))
v =
v.lastIndexOf(",") > v.lastIndexOf(".")
? v.replace(/\\./g, "").replace(",", ".")
: v.replace(/,/g, "");
else if (v.includes(",")) v = v.replace(",", ".");
const n = Number(v);
return Number.isFinite(n) ? n : null;
};
const candidates = [
...document.querySelectorAll(
'[data-testid*="journey"], [data-testid*="Journey"], [data-qa*="journey"], [data-test*="journey"], article',
),
];
const seen = new Set();
const journeys = candidates
.map((el, index) => {
const text = norm(el.innerText);
if (!text || seen.has(text) || !priceRe.test(text)) return null;
priceRe.lastIndex = 0;
seen.add(text);
const prices = [...text.matchAll(priceRe)]
.map((m) => ({ raw: m[0], value: parsePrice(m[0]) }))
.filter((x) => x.value !== null);
if (!prices.length) return null;
const times = [...text.matchAll(/\\b(?:[01]?\\d|2[0-3]):[0-5]\\d\\b/g)].map(
(m) => m[0],
);
const duration =
(text.match(/\\b\\d+\\s*h(?:\\s*\\d+\\s*m)?\\b/i) || [])[0] || null;
return {
index,
departure: times[0] || null,
arrival: times[1] || null,
duration,
prices,
text,
};
})
.filter(Boolean);
const all = journeys.flatMap((j) => j.prices);
const values = all.map((x) => x.value);
return {
url: location.href,
title: document.title,
journeys,
cheapestListedFare: values.length ? Math.min(...values) : null,
highestListedFare: values.length ? Math.max(...values) : null,
currencyHints: [
...new Set(
all
.map((x) => (x.raw.match(/CHF|EUR|GBP|USD|[$£€]/i) || [])[0])
.filter(Boolean),
),
],
};
})();- Estimate the requested leg from
cheapestListedFarethroughhighestListedFare, clearly distinguishing a cheapest feasible fare from a flexible or typical budget. For a return trip, repeat the same direct route pattern in reverse for the return date when separate return results are needed; do not assume the outbound fare applies to the return. Add separately displayed booking, seat, or reservation fees only when they are visible, and state when taxes or fees are not shown.
Site-Specific Gotchas
- The durable direct route scheme is
/en/train-times/{origin-slug}-to-{destination-slug}; retain the appropriate locale prefix, such as/enor/en-us. - Route pages may default to an upcoming date and may render results asynchronously, so wait for journey cards before evaluating. The page heading and selected date must be checked before using prices.
- A OneTrust consent banner may appear before the form or results are usable. When present, accept it with
button#onetrust-accept-btn-handlerand then wait for rendering. - City slugs can represent a station group or a specific station. Never assume the slug uniquely identifies the intended station; verify the displayed origin and destination.
- If direct slug construction does not resolve the requested endpoints, the homepage form provides autocomplete controls
input#jsf-origin-inputandinput#jsf-destination-input; choose the matching first-party suggestion (#jsf-origin-item-0or#jsf-destination-item-0) and then verify the resulting route. This is a fallback for ambiguous names, not the normal path. - Fare cards can contain multiple fare tiers. Preserve each visible price and use the lowest valid tier only as the minimum budget, not as a guaranteed fare.
- The extractor depends on journey-card data attributes or
articlecontainers. If no cards are returned, the page likely has not finished rendering or is showing a different result layout; inspect only the loaded page before revising selectors.
Expected Output
Return the matched route, travel date, passenger assumptions, journey options with departure, arrival, duration, and visible fares, plus a budget range. For one-way travel report the cheapest and practical/highest listed fare; for return travel report outbound and inbound subtotals, visible fees, and the resulting estimated total, with currency and limitations stated.