Find Train Tickets and Estimate Trip Budget

Site thetrainline.comTask find-train-tickets-and-estimate-budgetVersion v2Updated Aug 10, 2026Category travel

Use Trainline route pages to collect visible journey fares and estimate the ticket budget for a rail trip. This skill was captured from a live agent session on thetrainline.com and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

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

  1. 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 as en or en-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.
  2. 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.
  3. If a consent banner blocks interaction or results, dismiss it with button#onetrust-accept-btn-handler when present, then wait for the route results to render. Do not provide credentials or proceed to checkout.
  4. 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),
      ),
    ],
  };
})();
  1. Estimate the requested leg from cheapestListedFare through highestListedFare, 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 /en or /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-handler and 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-input and input#jsf-destination-input; choose the matching first-party suggestion (#jsf-origin-item-0 or #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 article containers. 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.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=thetrainline.com&task=find-train-tickets-and-estimate-budget