jackwener/OpenCLI · error · CommandExecutionError

Trip.com tour search captured products but none carried a na

Error message

Trip.com tour search captured products but none carried a name (the product markup may have changed)

What it means

This CommandExecutionError distinguishes schema drift from a genuine no-match: the page reported status 'content' (results were captured), but after filtering rows for a truthy name, none remained. The product markup Trip.com uses for tour titles likely changed, so map/filter assumptions no longer hold.

Source

Thrown at clis/trip/tour.js:71

        await page.goto(searchUrl);
        const result = await page.evaluate(buildTourSearchJs(query));
        if (!result || typeof result !== 'object') {
            throw new CommandExecutionError('Trip.com tour search returned malformed data');
        }
        if (result.status === 'captcha') {
            throw new AuthRequiredError('trip.com', 'Trip.com is asking for a verification; complete it in your browser session and retry');
        }
        if (result.status === 'empty') {
            throw new EmptyResultError('trip tour', `No ${kwargs.type || 'private'} tours for "${query}"`);
        }
        if (result.status !== 'content') {
            throw new CommandExecutionError(`Trip.com tour search did not return results (state=${String(result.status)})`);
        }
        // Products captured but none carry a name is drift (schema moved), not an empty search;
        // a genuine no-match resolves as status 'empty' above off the page's "0 routes found".
        const rows = Array.isArray(result.rows) ? result.rows.filter((r) => r.name) : [];
        if (rows.length === 0) {
            throw new CommandExecutionError('Trip.com tour search captured products but none carried a name (the product markup may have changed)');
        }
        return rows.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            name: r.name,
            type: r.type,
            rating: r.rating,
            reviews: r.reviews,
            price: r.price,
            currency: 'USD',
            url: r.url,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Capture a raw result.rows sample and update buildTourSearchJs to the new title field/selector.
  2. Verify against the live page which element holds the tour name and adjust the extraction selector.
  3. Update the CLI to a version patched for the new markup.
  4. Compare across regions/locales — a variant page may explain the differing DOM.

Example fix

// before
name: card.querySelector('.title')?.textContent,
// after (new markup)
name: card.querySelector('[data-testid="tour-name"]')?.textContent ?? card.querySelector('.title')?.textContent,
Defensive patterns

Strategy: type-guard

Validate before calling

const rows = Array.isArray(result?.rows) ? result.rows : [];
if (rows.length && rows.every(r => !r || typeof r.name !== 'string' || !r.name.trim())) {
  console.warn('Tour markup drift suspected: no named rows extracted');
}

Type guard

function hasName(r) { return r != null && typeof r.name === 'string' && r.name.trim().length > 0; }

Try / catch

try {
  const rows = await tourSearch(query, type);
} catch (e) {
  if (/none carried a name/.test(e.message)) {
    // treat as upstream markup change: capture diagnostics, alert maintainers
    captureDiagnostics(e);
  }
  throw e;
}

Prevention

When it happens

Trigger: result.rows is an array whose entries all lack r.name — e.g. Trip.com renamed the title field in tour cards, or the extraction selector now grabs non-product elements.

Common situations: Trip.com redesigning tour card markup; extraction JS reading an old class/data attribute; region-specific page variants with different DOM structure.

Related errors


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