jackwener/OpenCLI · error · CommandExecutionError

Trip.com attraction DOM extraction returned malformed rows

Error message

Trip.com attraction DOM extraction returned malformed rows

What it means

This CommandExecutionError is thrown when page.evaluate(buildAttractionExtractJs()) returns something other than an array — i.e. the DOM extraction script produced malformed output instead of a list of attraction rows. It indicates the extraction script and the live page DOM are out of sync, not that results are empty (that would be error 3905 or 3902).

Source

Thrown at clis/trip/attraction.js:58

    func: async (page, kwargs) => {
        const query = parseKeyword('query', kwargs.query);
        const limit = parseListLimit(kwargs.limit);

        const searchUrl = buildAttractionSearchUrl(query);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_ATTRACTIONS_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 === 'empty') {
            throw new EmptyResultError('trip attraction', `No attractions for "${query}"`);
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Trip.com things-to-do page did not render product cards (state=${String(waitResult)})`);
        }
        const raw = await page.evaluate(buildAttractionExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Trip.com attraction DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            throw new CommandExecutionError('Trip.com attraction cards rendered but parser did not find required detail-link anchors');
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            name: r.name,
            rating: r.rating,
            reviews: r.reviews,
            booked: r.booked,
            price: r.price,
            currency: 'USD',
            url: r.url,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry once — a transient render race can produce a malformed extraction; persistent failures mean a DOM change.
  2. Open the things-to-do URL in a normal browser and compare card markup against the selectors used by buildAttractionExtractJs in clis/trip/utils.js.
  3. Update the extraction script's selectors to match the new Trip.com card structure (detail-link anchors).
  4. Report/patch the library if Trip.com changed its layout; pin to an older working version meanwhile.

Example fix

// before: assuming array always returned
const raw = await page.evaluate(buildAttractionExtractJs());
const rows = raw.slice(0, limit);
// after: caller-side guard
const raw = await page.evaluate(buildAttractionExtractJs());
if (!Array.isArray(raw)) throw new Error('Trip.com extraction returned non-array; selectors likely stale');
const rows = raw.slice(0, limit);
Defensive patterns

Strategy: type-guard

Validate before calling

null

Type guard

function isExtractionRows(value) {
  return Array.isArray(value) && value.every(
    (r) => r && typeof r === 'object' && typeof r.url === 'string'
  );
}

Try / catch

try {
  return await runCli(['trip', 'attraction', query]);
} catch (e) {
  if (/malformed rows/.test(e.message)) {
    // selectors stale vs live DOM — surface actionable error, optionally retry once
    throw new Error('Trip.com attraction extraction is stale; update buildAttractionExtractJs selectors');
  }
  throw e;
}

Prevention

When it happens

Trigger: Running `trip attraction` when the in-page extraction JS returns null/undefined/an object instead of an array — typically because the things-to-do page markup changed so the script's row-collection logic fails, or evaluate was interrupted.

Common situations: Trip.com deployed a redesign of the things-to-do cards so buildAttractionExtractJs no longer collects rows; a partially rendered/interactive-error page where expected DOM containers are absent; a Trip.com error page that still passes the 'content' wait check but has a different structure.

Understand the failure class

Related errors


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