jackwener/OpenCLI · error · CommandExecutionError

Ctrip cruise port page did not render (state=${String(portWa

Error message

Ctrip cruise port page did not render (state=${String(portWait)})

What it means

CommandExecutionError thrown when the port's cruise.ctrip.com page neither rendered content nor showed the known empty state — WAIT_FOR_CRUISE_JS returned some other state (timeout, unknown selector state, navigation hiccup). The library cannot classify the page, so it fails with the raw wait state embedded in the message for diagnosis. It indicates a page-load/render problem rather than a data or auth problem.

Source

Thrown at clis/ctrip/cruise.js:74

        }
        const portCode = await page.evaluate(buildCruisePortLookupJs(port));
        if (!portCode) {
            throw new EmptyResultError('ctrip cruise', `No cruise departure port matching "${port}" (try a listed sea-cruise port like 上海 / 威尼斯 / 罗马)`);
        }

        let searchUrl = indexUrl;
        if (portCode !== PORT_INDEX_CODE) {
            searchUrl = buildCruiseSearchUrl(portCode);
            await page.goto(searchUrl);
            const portWait = await page.evaluate(WAIT_FOR_CRUISE_JS);
            if (portWait === 'captcha') {
                throw new AuthRequiredError('cruise.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
            }
            if (portWait === 'empty') {
                throw new EmptyResultError('ctrip cruise', `No cruises currently departing "${port}"`);
            }
            if (portWait !== 'content') {
                throw new CommandExecutionError(`Ctrip cruise port page did not render (state=${String(portWait)})`);
            }
        }

        const raw = await page.evaluate(buildCruiseExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip cruise DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            throw new CommandExecutionError('Ctrip cruise cards rendered but parser did not find required itinerary anchors');
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            title: r.title,
            star: r.star,
            boarding: r.boarding,
            sailingDate: r.sailingDate,
            tags: r.tags,
            price: r.price,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the state value in the message and rerun once — transient loads often succeed on retry
  2. Increase the browser wait/navigation timeout used by the CLI before rerunning
  3. Open buildCruiseSearchUrl(portCode) in a real browser to see what actually renders (captcha, error page, new layout)
  4. Update the CLI (or WAIT_FOR_CRUISE_JS in clis/ctrip/utils.js) if Ctrip changed its page structure

Example fix

// before: single fragile attempt
await run(['ctrip', 'cruise', '威尼斯']);
// after: one retry on render failure
try {
  await run(['ctrip', 'cruise', '威尼斯']);
} catch (e) {
  if (e.name === 'CommandExecutionError' && /did not render/.test(e.message)) {
    await sleep(3000);
    await run(['ctrip', 'cruise', '威尼斯']);
  } else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: probe network reachability and page load latency before running the command
const t0 = Date.now();
await fetch('https://cruise.ctrip.com/', { method: 'HEAD' });
if (Date.now() - t0 > 5000) console.warn('Slow route to Ctrip; raise the CLI wait timeout');

Type guard

function isRenderFailure(e) {
  return e instanceof Error && e.name === 'CommandExecutionError' && /did not render/.test(e.message);
}

Try / catch

try {
  return await run(['ctrip', 'cruise', port]);
} catch (e) {
  if (isRenderFailure(e)) {
    await sleep(3000);
    return run(['ctrip', 'cruise', port]); // one retry with backoff
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `ctrip cruise <port>` where after page.goto on the port results URL the in-page probe times out or returns an unrecognized state (network slowness, SPA hydration failure, unexpected page markup, redirect to an error page).

Common situations: Slow or proxied network causing the wait timeout to expire before the SPA hydrates; Ctrip A/B-deploying new markup so the probe's known selectors vanish; transient 5xx or interstitial pages breaking navigation; headless environment where some resources never load.

Related errors


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