jackwener/OpenCLI · error · CommandExecutionError

Trip.com hotel DOM extraction returned malformed rows

Error message

Trip.com hotel DOM extraction returned malformed rows

What it means

This CommandExecutionError from clis/trip/hotel-search.js is thrown when buildHotelExtractJs() returns a non-array after the page did reach the content state, meaning the extractor encountered hotel cards whose markup it could not parse. Unlike the flight command, no per-card reason is extracted here — the message is fixed. It almost always reflects a Trip.com DOM/layout change or unusual card content (e.g. ads) rather than user error.

Source

Thrown at clis/trip/hotel-search.js:62

        const checkin = parseIsoDate('checkin', kwargs.checkin);
        const checkout = parseIsoDate('checkout', kwargs.checkout);
        if (checkin >= checkout) {
            throw new ArgumentError(`--checkin must be before --checkout (got ${checkin} .. ${checkout})`);
        }
        const limit = parseListLimit(kwargs.limit);

        const searchUrl = buildHotelSearchUrl(cityId, checkin, checkout);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_HOTELS_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 hotel page did not render hotel cards (state=${String(waitResult)})`);
        }
        const raw = await page.evaluate(buildHotelExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Trip.com hotel DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            throw new EmptyResultError('trip hotel-search', `No hotels for city ${cityId} on ${checkin} .. ${checkout}`);
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            name: r.name,
            score: r.score,
            reviewLabel: r.reviewLabel,
            reviews: r.reviews,
            location: r.location,
            room: r.room,
            price: r.price,
            currency: r.currency,
            url: searchUrl,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the current Trip.com hotel card DOM in a browser and diff against the selectors in buildHotelExtractJs().
  2. Update buildHotelExtractJs() to handle the new markup and to skip (rather than fail on) non-standard cards.
  3. Add a debug dump of the non-array return value at throw time to aid future diagnosis.
  4. Pin/verify against a known-good Trip.com layout and re-run after fixing the extractor.

Example fix

// before
const raw = await page.evaluate(buildHotelExtractJs());
if (!Array.isArray(raw)) {
  throw new CommandExecutionError('Trip.com hotel DOM extraction returned malformed rows');
}
// after
const raw = await page.evaluate(buildHotelExtractJs());
if (!Array.isArray(raw)) {
  console.error('hotel extract returned:', JSON.stringify(raw).slice(0, 500));
  throw new CommandExecutionError(`Trip.com hotel DOM extraction returned malformed rows: ${raw && raw.error || 'unknown'}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Post-hoc guard on any scraped payload you consume downstream:
const safeRows = Array.isArray(rows) && rows.every((r) => r && typeof r.hotelName === 'string' && typeof r.price !== 'undefined');
if (!safeRows) console.warn('hotel payload shape unexpected; Trip.com DOM may have changed');

Type guard

const isExtractFailure = (e) => e instanceof CommandExecutionError && /malformed rows/.test(e.message);
const isHotelRowArray = (v) => Array.isArray(v) && v.every((r) => r && typeof r === 'object');

Try / catch

try {
  const hotels = await runTripHotelSearch(args);
} catch (e) {
  if (isExtractFailure(e)) {
    console.error('Trip.com hotel extractor needs updating (DOM change likely)');
    return null; // degrade and alert
  }
  throw e;
}

Prevention

When it happens

Trigger: page.evaluate(buildHotelExtractJs()) returns null/undefined/an object instead of an array, immediately after WAIT_FOR_HOTELS_JS returned 'content'.

Common situations: Trip.com rolling out a new hotel card layout or A/B test; sponsored/featured hotel tiles with different DOM interleaved in results; price/availability widgets replacing standard card nodes; regional markup differences.

Understand the failure class

Related errors


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