jackwener/OpenCLI · error · CommandExecutionError

Trip.com train DOM extraction returned malformed rows

Error message

Trip.com train DOM extraction returned malformed rows

What it means

CommandExecutionError thrown when page.evaluate(buildTrainExtractJs()) returns a non-array. The wait probe said 'content' (results rendered), but the extraction script came back in an unexpected shape, meaning the DOM structure changed or the script was sabotaged by page code.

Source

Thrown at clis/trip/train.js:60

    ],
    func: async (page, kwargs) => {
        const from = parseKeyword('from', kwargs.from);
        const to = parseKeyword('to', kwargs.to);
        const country = parseKeyword('country', kwargs.country);
        const limit = parseListLimit(kwargs.limit);

        const searchUrl = buildTrainRouteUrl(country, from, to);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_TRAINS_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('trip.com', 'Trip.com is asking for a verification; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Trip.com train timetable did not render (state=${String(waitResult)}); check the city names and --country`);
        }
        const raw = await page.evaluate(buildTrainExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Trip.com train DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            throw new EmptyResultError('trip train', `No timetable for ${from} to ${to} (${country})`);
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            departureTime: r.departureTime,
            fromStation: r.fromStation,
            arrivalTime: r.arrivalTime,
            toStation: r.toStation,
            duration: r.duration,
            changes: r.changes,
            url: searchUrl,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Update buildTrainExtractJs() selectors to match the current Trip.com train-card markup
  2. Log the raw evaluate result to see the actual shape returned
  3. Pin or bypass A/B test variants (cookies/URL params) so the stable layout renders
  4. Add a defensive Array.isArray check with diagnostics upstream of this throw

Example fix

// before
const raw = await page.evaluate(buildTrainExtractJs());
if (!Array.isArray(raw)) throw new CommandExecutionError('malformed rows');
// after
const raw = await page.evaluate(buildTrainExtractJs());
if (!Array.isArray(raw)) {
  console.error('extract returned:', raw);
  throw new CommandExecutionError('malformed rows: ' + JSON.stringify(raw).slice(0, 200));
}
Defensive patterns

Strategy: type-guard

Validate before calling

// cannot pre-validate; guard the result instead
const raw = await page.evaluate(buildTrainExtractJs());
if (raw == null) throw new Error('extract returned null — selector drift likely');

Type guard

function isTrainRows(v) {
  return Array.isArray(v) && v.every(r =>
    r && typeof r.departureTime === 'string' && typeof r.fromStation === 'string');
}

Try / catch

try {
  const rows = await getTripTrains(args);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('malformed rows')) {
    console.error('Trip.com DOM changed; capture page HTML for selector update');
  }
  throw e;
}

Prevention

When it happens

Trigger: Trip.com renders train rows in a new DOM structure so buildTrainExtractJs() returns null/an object instead of an array; a page-side script overwrites the expected global; or the evaluate result fails to serialize as an array.

Common situations: Trip.com frontend redesign/A-B test changing row markup; extraction script pinned to old class names; headless browser returning undefined because of a serialization issue in the evaluate payload.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/83143a67dff58f51. Report an issue: GitHub.